#lang scribble/doc @(require scribble/manual scribble/eval ; "aspectscheme.ss" ; (for-label scheme "aspectscheme.ss") ) @title{AspectScheme} @defmodulelang[aspectscheme]{This is the documentation for the implementation of @schememodname[aspectscheme] language, introduced in @italic{Aspects in Higher-Order Languages}.} It can be found online at @link{http://dx.doi.org/10.1016/j.scico.2006.01.003}. The language implementation can be found at @link{http://www.cs.usask.ca/faculty/cjd032/downloads/AspectScheme}. @table-of-contents[] @section{Installing AspectScheme} AspectScheme comes in two forms: @itemize{ @item{a user-installable language} @item{a planet package} } @subsection[#:tag "installing"]{Installing the AspectScheme Language} Use the PLT Menu item File -> Install to install the provided @tt{aspectscheme.plt} file. Then select @italic{AspectScheme} as your default language; or write your code in a module based on the @schememodname[aspectscheme] language. To enable this language in DrScheme, under the @italic{Language} menu, select @italic{Choose Language...}. Under the section @italic{Experimental Languages}, you will find the following language: @itemize{ @item{@seclink["aspectscheme"]{AspectScheme}} } It is also available as the module language @schememodname[aspectscheme]. @subsection{Programming using the planet package} Preface your code with @scheme[(require (planet "aspectscheme.ss" ("dutchyn" "aspectscheme.plt" 4 0)))] @section[#:tag "aspectscheme"]{AspectScheme - the language} AspectScheme is derived from the @schememodname[scheme] language, providing support for pointcuts and advice aspect-oriented programming. AspectScheme considers procedure applications as join points, within a context of in-progress join points. These join points in context are recognized by pointcuts -- predicates over the join point and context (represented as a list of join points). When a pointcut matches, advice transforms the join point into a new procedure. @section[#:tag "pointcuts"]{Pointcuts} Pointcuts identify @italic{join points}, principled locations in the execution of the program. Derived from continuations, the fundamental join points for a Scheme program are procedure calls, procedure executions, and by extension, advice executions. @subsection[#:tag "fundamental-pointcuts"]{Fundamental Pointcuts} @defproc[(call [_proc procedure?]) (or/c '() #f)] recognizes a call to @scheme[_proc] @defproc[(exec [_proc procedure?]) (or/c '() #f)] recognizes an execution of @scheme[_proc] @defproc[(advice [_proc procedure?]) (or/c '() #f)] recognizes an execution of advice @scheme[_proc] @subsection[#:tag "binding-pointcuts"]{Binding Pointcuts} These pointcuts expose the arguments available at the matched join point. @defthing[target [pointcut]] exposes the target (procedure or advice) of the matched join point as part of the context values @defthing[args pointcut]] exposes the entire list of arguments to the matched join point as part of the context values @defproc[(some-args [as (listof boolean?)]) pointcut?] exposes the selected (by @scheme[#t]) items in the context matched. @defproc[(with-args [_val any?] ...) pointcut?] appends additional values to the context @subsection[#:tag "contextual-pointcuts"]{Contextual Pointcuts} In addition, join points may be filtered by control-flow context (i.e. continuation nesting). @defproc[(cflowtop [_pc pointcut?]) (or/c (listof any?) #f)] recognizes all join points which are below a join point matching @scheme[_pc], but with no intervening join points matching that pointcut. Values bound by the pointcut are inserted into the context argument of the advice. @defproc[(cflowbelow [_pc pointcut?]) (or/c (listof any?) #f)] Recognizes all join points which are @italic{strictly} below a join point matching @scheme[_pc]. The values bound by the pointcut are taken from the nearest enclosing join point that matches @scheme[_pc]. Using this construct, one may peel away join point context from the current join point upwards toward the top level. Nesting these allows one to access levels of nesting from the inside to the outside. @defproc[(cflowabove [_pc pointcut?]) (or/c (listof any?) #f)] recognizes join points which are @italic{strictly} above a join point matching @scheme[_pc]. The values bound by the pointcut are taken from the nearest enclosed join point that matches @scheme[_pc]. Using this construct, one may worm back into join point context toward the currrently instantiated join point. This is useful for locating outermost executions from a known contextual join point identified by @scheme[cflowtop] or @scheme[cflowbelow]. Nestin these allows one to access levels of nesting from the outside to the inside. @defproc[(cflowbottom [_pc pointcut?]) (or/c (listof any?) #f)] recognizes all join points which do not enclose another join point that matches @defproc[(cflow [_pc pointcut?]) (or/c (listof any?) #f)] (for compatibility) matches join points at the current level or any below; note that context values exposed by the binding join points are not replaceable in a proceed (unlike AspectJ), the replaceable arguments are supplied separately to the advice. @subsection[#:tag "pointcut-combinators"]{Pointcut Combinators} @defproc[(&& [_pc pointcut?] ...) (or/c (listof any?) #f)] merges many sub-pointcuts into one where all sub-pointcuts must match; context is merged from left-to-right to expose the context from all of the sub-pointcuts @defproc[(|| [_pc pointcut?] ...) (or/c (listof any?) #f)] merges many sub-pointcuts into one where at least one of the sub-pointcuts must match; context is exposed in a short-circuit fashion: sub-pointcuts are tested until the first match, and its context is exposed. All sub-pointcuts must return the same number of context values (the @scheme[some-args] and @scheme[with-args] pointcuts may help satisfy this restriction); but, this is not statically checked. @defproc[(! [_pc pointcut?]) (or/c '() #f)] matches any join point that does not match the given sub-pointcut. Note that all context built up by the sub-pointcut is discarded. @subsection[#:tag "primitive-pointcuts"]{Primitive Pointcuts} All the above pointcuts are actually derived from a few primitive pointcuts, using the higher-order property of pointcuts. @subsubsection[#:tag "join-point-stream"]{Join Point Stream} The primitive level pointcuts actually know about the join point stream. The join point stream is the list of join points currently extant between the start of program execution (the top) and the current expression to be evaluated (the bottom). As suggested by the connection between join points and continuations (hence the use of @scheme[continuation-mark]s), the stream of join points corresponds with the continuations awaiting their values. Whenever a new join point is created, it needs to be matched against the available (dynamic and static) aspects. But that matching process can entail join points in the rest of the stream from the bottom to the top. In particular, cflowbelow forces a sub-match that climbs up the join point stream toward the top, looking for the first match in the awaiting join points. That is, cflowbelow tests whether the current join point of interest is below one that corresponds to the sub-match. Naturally, the cflowbelow sub-match is another match, looking at join points of interest and moving upwards. AspectScheme reifies the join point stream for any joinpoint matcher (i.e. pointcut) as three arguments @scheme[jp- jp jp+]: @itemize{ @item{@scheme[jp-] are the join points that are @scheme[cflowbelow] the join point of interest} @item{@scheme[jp] is the current join point of interest} @item{@scheme[jp+] are the join points which the current join point of interest is @scheme[cflowbelow]} } Dually, this could all be phrased in terms of @scheme[cflowabove], where the join point of interest traversal moves down the list. For each pointcut, the join point stream starts as @scheme['() njp jp-stream]) where @scheme[njp] is the newly-created join point, and @scheme[jp-stream] is the stream of join points leading to @scheme[njp]. Each join point knows (has access to) @itemize{ @item{@scheme[target]: the procedure (or advice) corresponding to the join point -- as a single-element list to conform to the usual result from a pointcut} @item{@scheme[args]: the arguments to the @scheme[target]} } A pointcut is a function from the jp stream to a list of context values -- the targets and arguments (as appropriate) from the join points when they match. @defproc[((focus-jp [_p? procedure?]) [_jp- (listof joinpoint?)] [_jp joinpoint] [_jp+ (listof joinpoint?)]) (or/c (listof any) #f)]{ simply focuses the join point predicate onto @scheme[_jp], the join point of interest.} The primitive pointcuts @scheme[call], @scheme[exec], @scheme[adv] are a simple combination of @scheme[focus-jp] and a tag check of the join point of interest. The primitive pointcuts @scheme[target] and @scheme[args] simply return take the appropriate fields of the join point of interest as a list of context values. With two step functions, @itemize{ @item{@scheme[below] walk up the join point stream} @item{@scheme[above] walk down the join point stream} }, two pointcuts that recognize the end of the join point stream @itemize{ @item{@scheme[top?] there are no more join points above} @item{@scheme[bottom?] there are no more join points below} } and a simple iterator, @scheme[cflow-walk], that takes a step function and a end recognizer, all of the control flow pointcuts are constructed. @section[#:tag "advice"]{Advice} @scheme[(lambda (_proceed) (lambda (_ctxt_id ...) (lambda (_arg_id ...) _body)))] is the general form of advice. It takes @itemize{ @item{@scheme[_proceed] a procedure that continues the computation} @item{@scheme[_ctxt_id ...] any context matched by the pointcut it is combined with} @item{@scheme[_arg_id] the arguments at the join point} } and performs the desired semantic behaviour, including continuing the computation (potentially with new arguments) via @scheme[(proceed _new_arg ...)] zero, one, or multiple times; or performing some completely different operation. It is important to note that context values and current join point arguments are disjoint in our model. This makes clear what values are actually replacable by an @scheme[around] advice. Context values are not, but join point arguments are; hence the @scheme[proceed] only takes arguments. In contrast, AspectJ does not separate these, and pretends to proceed with new context values; but they're not used: only the values bound in arguments are actually seen as changed. This can get tricky with @scheme[cflow] because it might match @itemize{ @item{the currently executing join point (whereupon supplying new arguments are actually seen as new value), or} @item{a join point above the currently executing join point (whereupon new arguments are @italic{silently} ignored).} } @section[#:tag "aspects"]{Aspects} Pointcuts and advice are combined by an aspect declaration. These combinations can be static, dynamic, or top-level. @subsection[#:tag "static-aspects"]{Static Aspects @scheme[around]} @defform[(around [_pc pointcut?] [_adv advice?] _body ...)] indicates whenever the pointcut @scheme[_pc] matches within the static scope of the @scheme[_body ...], the advice @scheme[_adv] is given control. These aspects apply lexically; outside of the lexical scope of the @scheme[body ...] they do not apply. @subsubsection[#:tag "static-aspect-predefined"]{Common Static Aspect Control Structures} For convenience, several simpler versions are provided to satisfy common situations of performing semantic actions in addition to the usual ones. In these cases, @scheme[proceed] may not be called, although it must be present in the advice definition. @defform[(before _pc _adv _body ...)] at join points matching @scheme[_pc], the advice @scheme[_adv] is performed, then the join point continues with the original arguments. @defform[(after-throwing _pc _adv _body ...)] at join points statically within @scheme[_body ...] matching @scheme[_pc], the join point proceeds with the original arguments, and if an exception is thrown, the advice @scheme[_adv] is performed; then the exception is re-thrown. @defform[(after-returning _pc _adv _body ...)] at join points statically within @scheme[_body ...] matching @scheme[_pc], the join point proceeds with the original arguments, and if an exception is not thrown, the advice @scheme[_adv] is performed; then result returned by the join point is returned. @defform[(after- _pc _adv _body ...)] at join points statically within @scheme[_body ...] matching @scheme[_pc], the join point proceeds with the original arguments, and if an exception is not thrown, the advice @scheme[_adv] is performed; then result returned by the join point is returned. If an exception is thrown within the proceeding join point, the advice @scheme[_adv] is performed and then the exception is re-thrown. @subsection[#:tag "fluid-aspects"]{Fluid Aspects @scheme[fluid-around]} @defform[(fluid-around [_pc pointcut?] [_adv advice?] body ...)] just like around, but using dynamic scoping. Hence, the implementation essentially uses @scheme[fluid-let] to provide this facility. Unfortunately, @scheme[fluid-let] fails across module boundaries, so a replacement @scheme[fluid-let-parameter] is implemented using @scheme[dynamic-wind] and @scheme[let-parameter]. @subsubsection[#:tag "fluid-aspect-predefined"]{Common Fluid- Aspect Control Structures} @defform[(fluid-before _pc _adv _body)] @defform[(fluid-after-throwing _pc _adv _body)] @defform[(fluid-after-returning _pc _adv _body)] @defform[(fluid-after _pc _adv _body)] @subsection[#:tag "top-level-aspects"]{Top-Level Aspects @scheme[top-level-around]} @defform[(top-level-around [_pc pointcut?] [_adv advice?])] These aspects apply for the remainder of the program. They are installed at the top level and continue until execution terminates. @subsubsection[#:tag "top-level-aspect-predefined"]{Common Top-Level Aspect Control Structures} @defform[(top-level-before _pc _adv)] @defform[(top-level-after-throwing _pc _adv)] @defform[(top-level-after-returning _pc _adv)] @defform[(top-level-after _pc _adv)]