MetaAttributes

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

MetaAttributes

Judson, Ross
It is often desirable to stay 'close' to the source language when
metaprogramming.  Rewrite toolkits like Stratego/XT try to provide
transformation capabilities that use the source language as much as
possible; trying to identify patterns and rewrites in terms of the
underlying trees is difficult for those who have not implemented the
target language.  Can we implement metaprogramming without trees or
reflection?

I've done some thinking on the relationship between attribute
metaprogramming and the code it operates against, and have come up with
what looks to be a viable approach for handling "aspects" in an nsc
phase right before "refchecks".

There are no modifications to the Scala language (hats off to the Scala
team for creating a language where this is possible!).  There is no
requirement to have any compiler classes available (like trees); the
attributes are self-contained.  This is not a "deep" transformation
method, in that extensive analysis of the AST can't be performed.  A
large number of common macro-type operations can be, though.

Metaprogramming is divided into "selection" and "transformation" phases.
A series of attribute classes are defined that allow for fairly powerful
selection of "target" entities (like members).  These attribute classes
are combined together using type parameters; the metatransform phase
evaluates the tree of type parameters like an expression.  Example:

[regex[before[defFilter[members]]]("perform[A-Z].*")]
private def beforePerform = Security.checkWrite;

The transformation phase relies on "marker code" inserted into
conventional Scala methods that are to be used as templates.  The
compiler copies the entire method's tree, identifies particular marker
patterns, replaces them, then inserts the marked up tree into the
destination.  Example:

[beforeDefs("perform[A-Z].*")]
private def beforeExecute = {
  if (!Security.validateUser(metaParameter[String]('user))) {
    securityFailed;
  }
}

"beforeDefs" is a metaprogram selection shortcut, defined as follows:

type beforeDefs = regex[before[defFilter[members]]]

I've pasted in some possible definitions for the metaprogram attributes,
and followed that with some sample usage.  Conventional import and
inheritance can be used to dictate which metaprograms are active within
any given scope.  I note that if we could attach attributes to local val
and var statements, the notation can become even tighter.  

MetaCode.scala:

package metacode;

/** The base class of all metaprogramming attributes. */
abstract class MetaAttribute extends Attribute;
abstract class MetaCode extends MetaAttribute;

abstract class PointCut[STYLE <: CallStyle] extends MetaCode;
class before[M <: MetaSelect] extends PointCut[inline];
class after[M <: MetaSelect] extends PointCut[inline];
class around[M <: MetaSelect] extends PointCut[inline];
/** Replaces method entirely. */
class replace[M <: MetaSelect] extends PointCut[inline];

/** Indicates which style of code alteration to use. */
abstract class CallStyle extends MetaAttribute;

/** Lifts method and inserts body. */
class inline extends CallStyle;

/** Inserts call to method. */
class invoke extends CallStyle;

/** MetaSelect is the base of all member/symbol selection indicators. */
abstract class MetaSelect extends MetaAttribute;

/** MetaSelectLogic provides a base for logical operations over
selectors. */
abstract class MetaLogic extends MetaSelect;

/** Reverses the logic of the inner selector. */
class MetaNot[TAG <: MetaSelect] extends MetaLogic;

/** Requires both selectors. */
class MetaAnd[A <: MetaSelect, B <: MetaSelect] extends MetaLogic;

/** Requires either selector. */
class MetaOr[A <: MetaSelect, B <: MetaSelect] extends MetaLogic;

/** Requires one or the other. */
class MetaXor[A <: MetaSelect, B <: MetaSelect] extends MetaLogic;

/** Base class for sources of selection logic. */
abstract class MemberSelect extends MetaSelect;

/** Selects all members of the attributed element. */
class members extends MemberSelect;

/** Selects all members of the type T. */
class typeMembers[T] extends MemberSelect;

/** Select the members of BASE that CLASS is inheriting (either
implementing or not). */
class inherited[BASE,CLASS <: BASE] extends MemberSelect;

/** Base class for filtering. */
abstract class MemberFilter extends MemberSelect;

class named[M <: MetaAttribute](val name: String) extends MetaSelect;
class regex[M <: MetaAttribute](val regex: String) extends MetaSelect;

/** Restricts to vals. */
class valFilter[M <: MetaSelect] extends MetaSelect;

/** Restricts to vars. */
class varFilter[M <: MetaSelect] extends MetaSelect;

/** Restricts to defs. */
class defFilter[M <: MetaSelect] extends MetaSelect;

/** Restricts to members of type T. */
class returnType[T, M <: MetaSelect] extends MetaSelect;

/** A family of filters that match against the target of the
attribute. */
abstract class MetaLike extends MetaSelect;
class namedLike[M <: MetaSelect] extends MetaLike;
class typedLike[M <: MetaSelect] extends MetaLike;

/** Filter to include only those members that are deferred. */
class deferred[M <: MetaSelect] extends MetaSelect;

/** Filter to include only overrides. */
class overridden[M <: MetaSelect] extends MetaSelect;

/** Base class for code placeholders. */
abstract class ValueSelect extends MetaSelect;

/** Choose from parameters. */
class parameters extends ValueSelect;

/** Choose from local values. */
class locals extends ValueSelect;

/** Choose from fields. */
class fields extends ValueSelect;

/** Base class for code markup. MetaPoints are used to mark up
methods that are metacode.  MetaPoints do not appear in the resulting
code; they are fully removed by the compiler in the MetaCode
phase and replaced with equivalent trees of appropriate type.  */
abstract class MetaPoint;

/** Signifies a value-producing entity, requiring matching
against the target. */
abstract class MetaVariable[R] extends MetaPoint {
  private var ret: R = _
  def apply(): R = ret
}
/** Match against a named entity. */
case class MetaName[SRC <: MetaSelect,R](name: String) extends
MetaVariable[R];

/** Match against the nth entity. */
case class MetaN[SRC <: MetaSelect,R](n: int) extends MetaVariable[R];

/** Invoke the target method, with t as the target of the method. */
case class MetaInvoke[OBJTYPE,R](t: OBJTYPE) extends MetaVariable[R] {
  private var named: String = _
  def name = named;
}

case class MetaTarget() extends MetaVariable {
  // compiler should replace these with appropriate targe information
  def name = "<unknown>";
  def fullName = "<unknown>";
}

/** Create a sequence of values based on a pattern filter. */
case class MetaRegex[SRC <: MetaSelect,R](pattern: String) extends
MetaVariable[Seq[R]];

/** Signifies the point at which to call a targetted method, without
changing
the receiver. */
case class MetaCall[R] extends MetaVariable[R];

/** Names a block; used to create divisions in code that can be
manipulated. */
class MetaBlock(val name: String) extends MetaPoint;


/** MetaCode provides some convenient shorthand for common usages. */
object MetaCode {
  type implemented[M <: MemberSelect] = MetaNot[deferred[M]];
  type delegate[BASE, CLASS <: BASE] =
replace[deferred[inherited[BASE,CLASS]]] ;
  type beforeDefs = regex[before[defFilter[members]]] ;
  type MetaParameter[R] = MetaName[parameters, R] ;

  //
  // The "meta" family of function calls are intended to be replaced in
  // the compiled code.  The compiler should pattern-match against the
  // trees to detect these calls, then substitute the correct symbols
  // or constants or invocations.
  //
 
  /** Invoke the current target method with the same "this". */
  def metaCall[T](): T = blank[T]()
 
  /** Invoke the target method. */
  def metaInvoke[OBJTYPE, T](obj: OBJTYPE): T = blank[T]()
 
  /** Retrieve an arbitrarily sourced value from the current target
context. */
  def metaValue[SRC <: MetaSelect, T](): T = blank[T]()
 
  /** Retrieve an arbitrarily sourced values from the current target
context,
  whose names match against the given pattern.  */
  def metaRegex[SRC <: MetaSelect, T <: Object](pattern: String): Seq[T]
= Array[T](blank[T]())
 
  /** Retrieve a named parameter for the current metacode target. */
  def metaParameter[T](name: Symbol):T = blank[T]()
 
  /** Retrieve a named value for the current metacode target. */
  def metaNamed[SRC <: MetaSelect, T](name: Symbol):T = blank[T]()
 
  /** Retrieve a named parameter for the current metacode target. */
  def metaNthParameter[T](n: int):T = blank[T]()
 
  /** Retrieve a local value. */
  def metaLocal[T](name: Symbol):T = blank[T]();
 
  /** Find the current metacode target's name. */
  def metaTargetName = "<unknown>";  
   
  private def blank[T]() = new Blank[T]().get
   
  private class Blank[T] {
    var ret: T = _
    def get = ret
  }
}


DemoMetaCode.scala:

package metacode;

//Custom selectors
class reader extends MetaSelect;
class writer extends MetaSelect;
class editor extends MetaSelect;

trait Assist {
  def add(x: int, y: int): int;
  def subtract(x: int, y: int): int;
  def explain: String = "No explanation."
}

object Security {
  def checkRead = { }
  def checkWrite = { }
  def validateUser(user: String) = System.currentTimeMillis() % 5 == 0
  def record(logEntry: LogEntry) = { }
}

case class LogEntry(user: String, operation: String);

class Sample extends Assist {
 
  import MetaCode._
 
  val inner = new Assist {
    def add(x: int, y: int) = x + y;
    def subtract(x: int, y: int) = x - y;
  }
 
  val inner2 = new Assist {
    def add(x: int, y: int) = x + y;
    def subtract(x: int, y: int) = x - y;
  }
 
  // we need to wrap methods around these.
  def performX = { }
  def performY = { }
 
  def executeX(user: String) = { }
  def executeY(user: String) = { }

  // note annotations of these methods -- we can match against these
  [writer] def updateBalances = { }
  [reader] def calculate = 0
 
  private def securityFailed = {
    error("You do not have the security required to perform this
operation.")
  }
 
  // Delegate any unimplemented methods in trait "Assist" to inner.
  // The compiler will _copy_ and _mark up_ the code tree represented
  // by this method, then insert it into the right position.  
  // We are therefore able to use conventional Scala code as a
"template"
  // for our metaprogramming efforts, then rely on the compilation phase
  // to rearrange as appropriate.  Read the annotation as follows:
  // "Replace the deferred members inherited from Assist by Sample"
  [replace[deferred[inherited[Assist, Sample]]]]
  private def delegateToInner: Any = {
    val result = metaInvoke[Assist,Any](inner)
    result
  }

  // Do write security check before all performXXX members.
  [regex[before[defFilter[members]]]("perform[A-Z].*")]
  private def beforePerform = Security.checkWrite;

  // Validate user before performs.
  [beforeDefs("perform[A-Z].*")]
  private def beforeExecute = {
    // grab the user parameter
    val user = metaParameter[String]('user)
    // if we knew it was the first parameter we could have done this:
    val user2 = metaNthParameter[String](1)
    if (!Security.validateUser(user)) {
      securityFailed;
    }
  }
 
  // domain classes can easily be used as filters
  [after[MetaAnd[MetaOr[reader,writer],MetaNot[editor]]]]
  private def logReadersOrWriters = {
    // this is another way of getting a meta-parameter --
    // we use the functions in MetaCode.  The compiler looks
    // for calls to these functions and replaces them with
    // the right values.
    val user = metaParameter[String]('user)
    val le = LogEntry(user, metaTargetName)
    Security.record(le)
  }
 
  // Note call of any function that returns an int.
  [after[returnType[LogEntry,defFilter[members]]]]
  private def noteIntFunctions = Console.println("Called int func " +
metaTargetName);
 
  // if compiler implements metacode the following methods do not need
  // to be implemented -- they will be generated.
  def add(x: int, y: int) = x + y;
  def subtract(x: int, y: int) = x - y;
}

object DemoMetaCode {

}


Reply | Threaded
Open this post in threaded view
|

[open], [close] meta-attributes for virtual classes (Re: attributes for meta-programming)

Adriaan Moors-2
Hi Ross,

I just wanted to say I think these meta-programming attributes are  
extremely interesting stuff! When I saw your examples, I thought it  
would also be interesting to have attributes that provide direct  
support for virtual classes, so you could write:

trait ECore {
        [open] trait Expr[t]
          case class Num(n :Int) extends Expr[Int]
}

trait EEval[SelfT<:ECore with EEval[SelfT]] requires SelfT {
        trait Expr[t] {
                def eval :t
        }
       
        case class Num(n :Int) extends Expr[Int]
                def eval = n
        }
}

[close] class Deployment extends ECore
                     with EEval[Deployment]


And the meta-program would encode this to:
trait ECore {
        type TExpr <: Expr$ECore
        type TNum <: TExpr with Num$ECore
       
        def Num(n :Int) :TNum
       
        trait Expr$ECore requires TExpr {type t}
          trait Num$ECore requires TNum extends Expr$ECore{type t=Int; val  
n :Int}
}

trait EEval[selfT<:ECore with EEval[selfT]] requires selfT {
        type TExpr <: Expr$ECore with Expr$EEval
        type TNum <: TExpr with Num$ECore
                           with Num$EEval

        trait Expr$EEval requires TExpr {
                def eval :t
        }
       
        trait Num$EEval requires TNum
                         extends Expr$EEval {
                def eval = n
        }
}

class Deployment extends ECore
                     with EEval[Deployment] {
        type TExpr = Expr$ECore with Expr$EEval
        type TNum = Num$ECore with Num$EEval
       
        def Num(nn :Int) = new Num$ECore with
                               Num$EEval{val n=nn}
}

(note that pattern matching is lost here, haven't really thought  
about how to do that properly, except for defining case classes in  
the Deployment class, and instantiating those instead of e.g. "Num
$ECore with Num$EEval")

I'm considering to try to implement this in June, unless someone else  
has beaten me to it ;-)


adriaan

Disclaimer: http://www.kuleuven.be/cwis/email_disclaimer.htm