SkinScriptSyntax
The SkinScript Language Reference
Preface
This document describes the syntax for SkinScript as defined in ZPatterns 0.4.3, which is not yet publically available. If you are using an earlier version of ZPatterns, the following features are not available:
- The
QUERY
keyword and theOTHERWISE LET
clause onWITH/COMPUTE
declarations - The
INITIALIZE OBJECT WITH
declaration - The
DEPENDENT ON
clause ofWITH/COMPUTE
declarations
It is also recommended that you read about HowTriggersWork before trying to define complex rules with SkinScript.
Basic Concepts and Syntax
If you're familiar with ZPatterns, you know that DataSkins get their attributes and behavior from AttributeProviders and RuleAgents, respectively. Providers and Agents (known as DataPlugIns) can be written directly in Python, but it is much easier to write them in SkinScript. SkinScript lets you define an individual attribute provider or rule agent in a single declarative statement, using standard Zope tools such as SQL Methods, DTML Methods, Python Methods, and so on to do the actual data retrieval or storage. In effect, SkinScript is a "glue language" which lets you define the linkages between a DataSkin and the methods you want to implement its "skeleton" of data storage and triggered behavior.
In most languages, the order that statements appear in makes a difference to the results you get, and SkinScript is no exception. Although each SkinScript statement defines a seperate and independent data plug-in, the ordering makes a difference in how they will be used by DataSkins. When a DataSkin needs to perform an operation (such as retrieving an attribute), it asks its DataManager for a list of suitable DataPlugIns. This list is ordered according to the original order of plug-ins as listed on the Data Plug-ins tab of the Racks or Customizers involved. And the declarations of a SkinScript Method are treated as though they were individual plug-ins appearing in place of the SkinScript Method in that list.
In other words, SkinScript is literally a language for defining
data plug-ins. A SkinScript script consists of a series of
declarations, seperated by whitespace. Each declaration is
compiled into a single data plug-in. SkinScript is a
keyword-driven language, is case-sensitive, and is not whitespace
sensitive, even for Python expressions contained within
declarations. All whitespace which occurs outside of quoted strings
is treated as though it were a single space. (Incidentally, this
means that the usual Python rules about where you can put linebreaks
in expressions do not apply. You can write the expression 1+2
split across three lines, if you so desire.) Comments are marked as
in Python, using a #
symbol to mean that the rest of the current
line is a comment. Comments are treated as simple whitespace.
Declarations
Declarations are the basic building block of SkinScript. Declarations are compiled into AttributeProviders or RuleAgents, one per declaration. Some declarations provide attribute values:
INITIALIZE OBJECT WITH
assignmentlist- 'WITH [QUERY]' expression
COMPUTE
nameorassignlist [OTHERWISE LET assignmentlist] [DEPENDENT ON dependencies] WITH SELF COMPUTE
assignmentlist
Some handle attribute storage:
- [WHEN eventspec]
STORE
attributelistUSING
expression [SAVING mementolist] STORE
attributelistIN SELF
And others can call an expression upon transaction commit:
WHEN
eventspecCALL
expression [SAVING mementolist]
Each declaration has its own parameters, but there are certain conventions which are followed across most kinds of declarations.
Declaration Parameters
- expression
- A DTML-style Python expression. As with DTML, the "_" object is available for access to Python built-ins and library functions. Please see the section below on "Variables and Functions Available in Expressions" for details on how names other than "_" are looked up in SkinScript expressions.
- assignmentlist
- A comma-seperated list of assignments in the form attributename = expression, similar to passing keyword arguments to a function or method.
- nameorassignlist
- Similar to assignmentlist, but with a
special shorthand for the case where attributename and
expression are the same (e.g.
foo=foo
). To make such an assignment, you can replace attributename=
expression with just attributename. Sofoo=bar,baz=baz,bada=bing
can be simplified tofoo=bar,baz,bada=bing
if a clause's syntax allows a nameorassignmentlist. - mementolist
- A mementolist is syntactically identical to a
nameorassignlist, but has a different context and purpose. A
mementolist is used to save old values of expressions for later
comparison purposes when a (sub)transaction commits, and appears
only in
SAVING
clauses. Expression variables are looked up in the context of the DataSkin whose snapshot is being taken. So in the clauseSAVING bar,foo=baz
the DataSkin?'sbar
attribute will be saved as OLD['bar'], and itsbaz
attribute will be saved as OLD['foo'].A mementolist is computed only once per (sub)transaction for a given DataSkin. The first time the DataSkin is changed in a (sub)transaction, the
SAVING
clause is executed for all declarations that have one, even if the declaration never ends up firing. - eventspec
- A clause of the form
OBJECT ADDED, CHANGED, DELETED
, where theADDED
,CHANGED
andDELETED
keywords may be used in any combination. For example,OBJECT ADDED,DELETED
andOBJECT CHANGED
are both valid eventspec clauses. Please see HowTriggersWork for details on how ZPatterns events are interpreted by SkinScript. - attributelist
- A comma-seperated list of attribute names, to which the declaration will be applied. An asterisk ("*") may be used as a wildcard attribute name, meaning that the declaration will be applicable for any attribute name. Remember, however, that declarations associated with specific names will take precedence over wildcard declarations, even if the wildcard declaration comes before the specific declaration and would match the name being looked up.
- dependencies
- A comma-seperated list of attribute names which, when changed, will cause the dependent attributes to be recalculated on their next access. Wildcards cannot be used, only actual attribute names.
Variables and Functions Available in Expressions
Names used in an expression are usually looked up from the
attributes of individual SkinScript statement and its acquisition
parents (e.g. the Rack or Customizer which it's contained in, and so
on up the line). One exception to this rule is in the COMPUTE
clause of a WITH/COMPUTE declaration, where names are first looked
up in the RESULT
object returned by the WITH
clause. The other
exception is in SAVING
clauses, where names are looked up in the
context of the DataSkin whose snapshot is being taken.
The following variable names and functions are provided by SkinScript for use in expressions.
- Generally available:
-
self
- The DataSkin instance which the declaration is being applied to at the time the expression is being called.
-
NOT_FOUND
- A special value which causes an attribute to be
non-existent, if this is the value provided. In a WITH/COMPUTE
declaration, if the
WITH
expression returnsNOT_FOUND
, theCOMPUTE
clause is ignored, and theOTHERWISE LET
clause is activated if one exists.
-
- Available for WITH/COMPUTE declarations:
-
RESULT
- The result of the
WITH
expression in a WITH/COMPUTE declaration. Available only from expressions in theCOMPUTE
clause. This variable is placed atop the DTML namespace stack during execution of theCOMPUTE
clause, so just referring to a name in aCOMPUTE
expression will look up that name in theRESULT
object first. Only if it is not found there, will the search continue to the declaration and its acquisition context. -
ATTRIBUTE_NAME
- A string containing the name of the attribute
which the DataSkin is currently trying to retrieve. Available in
both the
WITH
andCOMPUTE
clauses.
-
- Available for WHEN/STORE and WHEN/CALL declarations:
-
TRIGGER_EVENT
- A string, either "ADD", "CHANGE", or "DELETE", denoting the type of event which caused the expression to be executed.
- 'OLD["name"]'
OLD
is a mapping object containing the values saved by the "SAVING mementolist" clause of a WHEN/STORE or WHEN/CALL declaration.-
HAS_CHANGED("name")
- returns a true value if the attribute named "name" has been changed or deleted during the current (sub)transaction.
-
CHANGED_ATTRS()
- returns a list of the names of attributes which have been changed or deleted during the current (sub)transaction.
- 'ORIGINAL["name"]'
- returns the value of the attribute named
"name" before it was changed or deleted, or the
NOT_FOUND
value if the attribute did not exist before this (sub)transaction. AKeyError
will be raised if "name" was not changed/deleted, so useHAS_CHANGED()
to check before using this.
-
INITIALIZE OBJECT WITH assignmentlist
What It's For
Providing default attribute values to newly created objects.
How It Works
This declaration assigns the specified attribute values to the object when it is created. Due to limitations of the built-in Zope event model, it is not possible to unequivocally determine when DataSkins outside of Racks are created, so this declaration applies only to objects created inside Racks. The assignmentlist is executed in context of the DataSkin, so later assignments can reference the values of earlier assignments.
Examples
- Constant values:
INITIALIZE OBJECT WITH foo=1, bar='baz', degrees=360, radians = degrees / (180 / _.math.pi)
- Defaults from properties in acquisition context:
INITIALIZE OBJECT WITH foo=myfoo, bar=mybar
WITH [QUERY] expression COMPUTE nameorassignlist [OTHERWISE LET assignmentlist] [DEPENDENT ON dependencies]
What It's For
Providing readable values for computed attributes and/or attributes which are loaded from an external data source (e.g. via an LDAP or SQL query). WITH/COMPUTE statements minimize redundant calculations by determining some attribute values before they are actually needed, as soon as one of a set of related values are needed. This is especially important where database retrievals are involved. It would be very bad to have to issue an SQL query for each attribute access, even if the result was then cached. So a WITH/COMPUTE statement executes the expensive expression once, and then caches the attributes computed by the nameorassignlist. Notice, by the way, that this means you can do performance tuning by changing the grouping of attributes in your WITH/COMPUTE statements, so that computations which are very expensive and infrequently used are placed in standalone statements, and so on. And if you're using data from outer-joined SQL tables, you may even decide to split them into seperate queries (one per table) and corresponding WITH/COMPUTE statements, if some of the tables' data is rarely used.
How It Works
When an attribute defined in the "nameorassignlist" is requested,
call "expression". If the result of expression (available as
RESULT
in the "nameorassignlist" expressions) is not NOT_FOUND
,
compute all attributes from nameorassignlist, in the order they are
listed, caching the results for the remainder of the
(sub)transaction (unless later reset by a change to a DEPENDENT
ON
attribute).
If the RESULT
is NOT_FOUND
, the search for the attribute value
falls through to the next declaration (or attribute provider if the
SkinScript is finished). If there is an OTHERWISE LET
clause,
the assignments given there are computed and cached for the
remainder of the (sub)transaction. (This is to prevent repeated
execution of "expression" that will only result in further
failures.)
The optional QUERY
keyword states that "expression" is a query
method such as an LDAP or SQL query that returns a sequence of
objects. When the QUERY
keyword is used, the declaration
automatically takes the first item of the sequence to be used as
RESULT
, unless the sequence is empty, in which case it behaves as
though the RESULT
were NOT_FOUND
.
The optional DEPENDENT ON
clause declares that the computed
attributes depend on the listed attributes for their values, and
that if any of the DEPENDENT ON
attributes are overwritten, the
cached values should be reset. (And thus recomputed the next time
they are accessed.) So, if for example you had a ZODB-stored field
which was a key to be looked up in an RDBMS, listing the key field
in the DEPENDENT ON
clause would ensure that any access to the
RDBMS-stored fields after a change in key would return data from
the correct record. (Note, however, that the attributes are only
reset if a DEPENDENT ON
attribute is changed by being written
to. A change in the value calculated or retrieved by another
statement or provider has no effect on the attribute cache.)
Examples
- Simple SQL query.
SomeSQLMethod
is an SQL Method (located somewhere in the acquisition context of this SkinScript method) that takessomeparam
as a parameter to return a row by primary key, and returns a result which contains the fieldsfoo
,bar
, andbaz
, which we want to use as attributes of the same names:WITH QUERY SomeSQLMethod(someparam=self.id) COMPUTE foo,bar,baz
- SQL query with renaming and computations. Again, notice how the
WITH
expression is computed in the acquisition context of the SkinScript method itself, but theCOMPUTE
expressions are in the context of the result of theWITH
expression:WITH QUERY GetRoomData(roomname=self.id) COMPUTE labor_cost=labor_rate*labor_hours, material_cost=materials, height=roomdim1, width=roomdim2
- A simple computed attribute. The formula will be computed the
first time the DataSkin needs to know its
total_cost
attribute in a given (sub)transaction, and again if thelabor_cost
ormaterial_cost
attributes are written to:WITH self.labor_cost + self.material_cost COMPUTE total_cost = RESULT DEPENDENT ON labor_cost,material_cost
- Interdependent computed attributes. The first time that
area
,square_yards
, orprice_per_sq_yd
is asked for in a (sub)transaction, all three will be computed (and recomputed if the non-calculated attributes are written to):WITH self.width * self.height COMPUTE area = RESULT, square_yards = self.area / 9, price_per_sq_yd = self.total_cost / self.square_yards # We have to list 'real' attributes, not intermediate values! DEPENDENT ON width,height,labor_cost,material_cost
- Object re-mapping; convert data from some object in another part
of the application. Notice that we can even save a reference to
the original object so we can call methods on it later to save our
changed attributes back into it... (And of course, we can also
seperate more expensive and less-often used attribute computations
into other WITH self.original_object COMPUTE statements):
WITH SomeSpecialist.getItem(self.id) COMPUTE my_foo=its_foo, my_bar=its_bar, original_object=RESULT
WITH SELF COMPUTE nameorassignlist [DEPENDENT ON dependencies]
What It's For
A shorthand way of defining computed attributes. This variant
form of WITH/COMPUTE simply treats self
as RESULT
, placing it
on the namespace stack for the execution of the
nameorassignlist.
How It Works
When an attribute defined in the "nameorassignlist" is requested,
compute all attributes from nameorassignlist, in the order they are
listed, caching the results for the remainder of the
(sub)transaction (unless reset by a change to a DEPENDENT ON
attribute). The DataSkin itself is placed on the namespace stack
during execution of the nameorassignlist, so expressions can
refer to other attributes without having to prefix them with
self.
. WITH SELF COMPUTE
executes slightly faster than an
otherwise equivalent WITH self COMPUTE
statement, and a lot
faster than repeating self.foo
, self.bar
, and so on in an
expression.
The optional DEPENDENT ON
clause declares that the computed
attributes depend on the listed attributes for their values, and
that if any of the DEPENDENT ON
attributes are overwritten, the
cached values should be reset. (And thus recomputed the next time
they are accessed.) This is helpful for ensuring that
calculation-based attributes always reflect the object's current
state. Note, however, that the attributes are only reset if a
DEPENDENT ON
attribute is changed by being written to. A
change in the value calculated or retrieved by another statement or
provider has no effect on the attribute cache.
Examples
- Simple computed attribute. The formula will be computed the first
time the DataSkin needs to know its
total_cost
attribute in a given (sub)transaction. Notice how much shorter this is than the equivalent example shown under the general WITH/COMPUTE examples. It will also execute more quickly, although by an utterly insignificant amount:WITH SELF COMPUTE total_cost = labor_cost+material_cost DEPENDENT ON labor_cost,material_cost
- Interdependent computed attributes. Again, it's much less
verbose, and a hair faster than its generic WITH/COMPUTE cousin:
WITH SELF COMPUTE area = width * height, square_yards = area / 9, price_per_sq_yd = total_cost / square_yards # We have to list 'real' attributes, not intermediate values! DEPENDENT ON width,height,labor_cost,material_cost
[WHEN eventspec] STORE attributelist USING expression [SAVING mementolist]
What It's For
Storing/updating attributes in an external data source. The
attributelist can contain an asterisk (*
) to mean "all
attributes".
How It Works
When an attribute listed in "attributelist" is changed/deleted, the new value is cached in the DataSkin until a (sub)transaction commit occurs. At that time, "expression" is called, with the 'OLD[]' variable containing the values saved by "mementolist", if specified. (See the section above on "Declaration Parameters" for more details on how memento lists work.)
If the optional "WHEN eventspec" clause is used, "expression" will only be called if the eventspec matches the object-level event. (Note that this means that if you use the WHEN clause, you will need declarations for each possible event for each attribute to ensure that the attributes will be saved under all possible circumstances.)
A STORE/USING declaration is compiled into a single Data Plug-in
with two functions: to act as an attribute setter, and to act as a
rule agent. The attribute setter part simply caches changes to
attributes until (sub)transaction commit time, when the rule agent
part becomes active. The rule agent part executes the USING
expression if and only if one of the STORE
attributes have been
changed, and the WHEN clause (if any) is applicable to the
situation as of commit time. Note that the seperation of these
parts means that the USING expression can and will be called even
if more than one declaration (or other Attribute Provider)
handles storage for the same attributes! This is actually quite
useful when you have data that you want updated in more than one
back-end database, but it can be surprising if you expect
first-come, only-served behavior as is the case for attribute
getters.
There is one interesting side effect, however, if a wildcard (*
)
is used in the STORE
list. Since the *
matches any
attribute, the USING
expression will be called at subtransaction
commit as long as any attribute has changed and the WHEN
clause is applicable. Again, this is a bit different from
attribute getters and other attribute setters, where *
effectively means "any attribute that hasn't already been claimed
by another provider."
Examples
- Context-dependent method-driven storage reusing already-written
methods. In this example, we use different methods for each
situation, which we assume are already-written and which we do not
want to change for our specific situation. Notice that for the
ADD
andDELETE
events we useWHEN ... CALL
declarations, because we want the widget data to be added or deleted regardless of whether thefoo
orbar
attributes have specifically changed:WHEN OBJECT ADDED CALL AddWidget(widget_id=self.id, foo_field=self.foo, wbar=self.bar) WHEN OBJECT CHANGED STORE foo,bar USING UpdateMethod(widget_id=self.id, foo_field=self.foo, wbar=self.bar) WHEN OBJECT DELETED CALL DeleteWidget(widget_id=self.id)
- Object remapping. This example shows how to create a "virtual"
object whose attributes
my_foo
andmy_bar
are mapped to/from another object from a different specialist (SomeSpecialist
) using the same id value, but whose fields are namedits_foo
andits_bar
. The example works by maintaining anoriginal_object
attribute which contains the object from the other specialist. This is actually a pretty simple remapping example; a single SkinScript script might contain multiple remappings like this to combine parts of different objects into a whole. Also, this example assumes that we are creating an object inSomeSpecialist
for every DataSkin created, while common real-life uses will often have theoriginal_object
created on demand for an existing DataSkin, or conversely, have the DataSkin created on demand for an existing 'original_object':# Handle the case where we just got created... INITIALIZE OBJECT WITH original_object=SomeSpecialist.newItem(self.id) # ...and the case where we already existed WITH SomeSpecialist.getItem(self.id) COMPUTE original_object=RESULT # Map the other object's names to mine... WITH self.original_object COMPUTE my_foo=its_foo, my_bar=its_bar # ...and mine to its WHEN OBJECT ADDED,CHANGED STORE my_foo, my_bar USING self.original_object.mange_changeProperties( its_foo=self.my_foo, its_bar=self.my_bar ) # Last, but not least, get rid of the other guy when I'm deleted WHEN OBJECT DELETED CALL self.original_object.manage_delete()
- Complex storage mapping using a memento. This example
illustrates mapping a more complex kind of attribute into data
storage.
DelKeywords
andAddKeywords
are methods (in the SkinScript method's acquisition context) that delete or add keyword records in some (unspecified) kind of database, given an item id and a list of keywords. The example calls a method on the DataSkin calledgetKeywords()
to extract keywords from thedescription
attribute. If thedescription
attribute is changed during the (sub)transaction, at commit time the external database will be updated. For illustrative purposes, appropriate WHEN/CALL declarations are included to ensure that adds and deletes are properly handled. Notice that the DELETED declaration uses the value for the keywords which existed at the start of the (sub)transaction, since it could theoretically have changed before the object ended up being deleted:WHEN OBJECT CHANGED STORE description USING DelKeywords(item=self.id, kwlist=OLD['kw']), AddKeywords(item=self.id, kwlist=self.getKeywords(self.description)) SAVING kw=getKeywords(description) WHEN OBJECT ADDED CALL AddKeywords(item=self.id,kwlist=getKeywords(self.description)) WHEN OBJECT DELETED CALL DelKeywords(item=self.id,kwlist=OLD['kw']) SAVING kw=getKeywords(description)
STORE attributelist IN SELF
What It's For
Specifying attributes to be stored persistently within the object.
How It Works
When an attribute listed in "attributelist" is changed/deleted, it
will be stored directly in the object as a persistent attribute.
Of course, this will only work if the object itself is stored in
the ZODB, and no previous declarations or AttributeProviders
declared storage for the attribute. "attributelist" can be just an
asterisk (*
) to indicate that all attributes not stored in some
other way should be stored persistently.
Examples
- Simple storage. The attributes
foo
,bar
, andbaz
will be stored persistently in the ZODB. (Note that if the DataSkin is stored in a Rack which is not using persistent storage, this will not work, and data will silently be lost.)STORE foo, bar, baz IN SELF
- Wildcard. Any attribute which is not explicitly claimed for
storage by another attribute provider/declaration will be stored
persistently, assuming that there is not another wildcard attribute
setter which appears earlier than this declaration in precedence
order.
STORE * IN SELF
WHEN eventspec CALL expression [SAVING mementolist]
What It's For
Calling an expression at (sub)transaction commit if any of the specified events have occurred.
How It Works
At (sub)transaction commit time, if one of the events specified in "eventspec" occurred, "expression" is called, with the 'OLD[]' variable containing the values saved by "mementolist", if specified.
Examples
- E-mail notification upon object add.
EMailToManager
is a DTML document or method in the acquisition context of the SkinScript method, which expects anew_item
parameter containing the object which the notification is supposed to be about. Note that if this snippet were used in a Customizer, theADD
event can occur when an object is moved into the area covered by that Customizer. This would mean notifications would be sent for objects which were not necessarily newly created:WHEN OBJECT ADDED CALL EMailToManager(_.None,_,new_item=self)
- Catalog example #1. This is an interesting way of doing
automatic cataloging/re-cataloging of objects without having them
be CatalogAware. Note that the order of declarations here is very
important: when an object is changed,
uncatalog_object
will be called beforecatalog_object
.SomeCatalog
, of course, is a catalog somewhere in the SkinScript method's acquisition context:WHEN OBJECT CHANGED,DELETED CALL SomeCatalog.uncatalog_object(self.absolute_url(1)) WHEN OBJECT ADDED,CHANGED CALL SomeCatalog.catalog_object(self,self.absolute_url(1))
- Complex event alert. This example triggers if there is a 5% or
greater change in the ROI of an investment, based on any of the
input factors having changed (e.g. cashflow amounts or dates). It
works by saving the old result of the DataSkin?'s
computeROI()
method right before any change is made to the DataSkin?'s attributes. TheCALL
expression is called at (sub)transaction commit if any changes have been made to the object. That expression compares the new value ofcomputeROI()
with the old value as a percentage change, and if it's greater than 5%, calls theSendROIAlert()
function which is somewhere in the SkinScript method's acquisition context:WHEN OBJECT CHANGED CALL (abs(self.computeROI()-OLD['ROI'])/OLD['ROI'] > .05 ) and SendROIAlert(investment=self) SAVING ROI=computeROI()
- Catalog example #2. The previous catalog example recatalogs the
object if any attribute is changed, even if it is simply set back
to what it was in the first place. While future versions of
ZCatalog may optimize this case, we can do it ourselves with a bit
more complex SkinScript. This example will only recatalog the
object if attributes
foo
orbar
were set or the result of methodbaz
has changed:WHEN OBJECT ADDED CALL SomeCatalog.catalog_object(self,self.absolute_url(1)) WHEN OBJECT DELETED CALL SomeCatalog.uncatalog_object(self.absolute_url(1)) WHEN OBJECT CHANGED CALL (HAS_CHANGED('foo') or HAS_CHANGED('bar') or self.baz()<>OLD['baz']) and ( SomeCatalog.uncatalog_object(self.absolute_url(1)), SomeCatalog.catalog_object(self,self.absolute_url(1)) ) SAVING baz=baz()
- Generic method-driven storage with custom method. In this
example, it is assumed that
SomeMethod
is a DTML, SQL, or Python method that expects to get a DataSkin and save its attributes in an external database. The method must be smart enough to handle anADD
,CHANGE
, orDELETE
event, and takes a parameter calledwhat_happened
to tell it which one happened. In the case of an SQL method, it's pretty straightforward to write DTML that generates an appropriateINSERT
,UPDATE
, orDELETE
statement according to which event happened. By using theHAS_CHANGED
function,SomeMethod
can even generate an optimalUPDATE
statement that only changes fields that have actually changed. As you can see, this example puts the bulk of the processing burden onSomeMethod
, which has to be written specifically for the situation. This may be optimal, however, if you are going to have to write the methods anyway:WHEN OBJECT ADDED,CHANGED,DELETED CALL SomeMethod(object=self, what_happened=TRIGGER_EVENT, has_changed=HAS_CHANGED)
Language Keywords
The following keywords are SkinScript reserved words and cannot be used as attribute names or appear in expressions. If you must access an attribute with one of these names in an expression, you must use '_["name"]?' syntax, as is sometimes used in DTML expressions to access otherwise inaccessible names.
Keywords: ADDED CALL CHANGED COMPUTE DELETED DEPENDENT IN INCLUDE
INITIALIZE LET OBJECT ON OTHERWISE QUERY SAVING SELF SLOT STORE
USING WHEN WITH