This chapter is a different spin from the rest of the manual. Here I will show practices and conventions to embed and extend Sleep for your purposes. I will also cover how to manipulate data. Reading through this chapter will get you started with Sleep integration. To go further read through the Sleep API Javadoc at http://sleep.dashnine.org/docs/api/.
The first integration task is loading a script and catching syntax errors.
import sleep.runtime.ScriptInstance; import sleep.runtime.ScriptLoader; import sleep.error.YourCodeSucksException; ... ScriptLoader loader = new ScriptLoader(); try { ScriptInstance script = loader.loadScript("script.sl"); } catch (YourCodeSucksException syntaxErrors) { System.out.println(syntaxErrors.formatErrors()); } catch (IOException ioError) { ... }
This code instantiates a sleep.runtime.ScriptLoader. This class manages a lot of details for loading scripts and installing extensions. You should use it even if your application has one script.
When a script is loaded it is first read in from a file or a stream. The textual representation of the script is then compiled into a
sleep.engine.Block. Naturually there is some overhead to compile a script into a Block. To skip this overhead you can use the compileScript
method of ScriptLoader.
Optionally, the ScriptLoader can cache scripts for you. To enable this feature call the setGlobalCache
method.
Sleep only throws exceptions at compile time. Runtime script errors are caught through a sleep.error.RuntimeWarningWatcher interface. The purpose of this mechanism is to allow you to intercept errors and notify the user of them.
import sleep.error.RuntimeWarningWatcher; import sleep.error.ScriptWarning; public class Watchdog implements RuntimeWarningWatcher { public void processScriptWarning(ScriptWarning warning) { System.out.println(warning); } }
To catch errors, you must install a RuntimeWarningWatcher into a sleep.runtime.ScriptInstance.
script.addWarningWatcher(new Watchdog());
Debug and trace messages are also passed through the RuntimeWarningWatcher mechanism. Use the isDebugTrace
method from
sleep.error.ScriptWarning to query if the message is a trace or not.
The last step is to execute the script itself.
script.runScript()
As we move through this chapter you will notice there is an abundance of stuff to do everytime a script is loaded. To help manage this Sleep includes a mechanism called sleep.interfaces.Loadable bridges. Bridges are extensions to the Sleep language. A Loadable bridge is called every time a script is loaded and unloaded. You may associate any number of Loadable bridges with a ScriptLoader instance.
This Loadable bridge assigns the Watchdog RuntimeWarningWatcher to every new ScriptInstance:
import sleep.interfaces.Loadable; import sleep.runtime.ScriptInstance; public class ThingsToDo implements Loadable { public void scriptLoaded(ScriptInstance script) { script.addWarningWatcher(new Watchdog());
Loadable bridges do nothing without the ScriptLoader. There are two ways to install this type of bridge. A Loadable extension installed as a specific bridge is executed for every script load and unload, no matter what. Use a specific bridge when a task has to be done for every script. Adding a RuntimeWarningWatcher is an example of such a task.
ScriptLoader loader = new ScriptLoader(); loader.addSpecificBridge(new ThingsTodDo());
A global bridge is installed once for each script environment. Sharing of a script environment is discussed in a later section. The purpose of a global bridge is to populate a script environment with functions, operators, and predicates. Global bridges save the ScriptLoader from unnecessary work.
Add global bridges with the addGlobalBridge
method of ScriptLoader.
Use the putScalar
method of sleep.runtime.ScriptVariables to set a value in a script.
ScriptVariables variables = script.getScriptVariables(); variables.putScalar("$foo", SleepUtils.getScalar("foo!"));
To obtain a scalar from a script:
Scalar value = variables.getScalar("$foo"); System.out.println(value.toString());
The ScriptVariables object mantains variables and scope information for a script instance. Each scope is its own container of scalars. Sleep holds a scope in an object that implements the sleep.interfaces.Variable interface. You can implement the Variable interface and alter how scalars are stored and accessed.
It is possible to implement the Variable interface and alter how scalars are stored and accessed. This next example installs a hypothetical
MyVariable
implementation into a script.
ScriptVariables variableManager = new ScriptVariables(new MyVariable()); script.setScriptVariables(variableManager);
The sleep console is an interactive console for working with sleep scripts. The console includes commands for loading scripts, running scripts, and dumping an abstract syntax tree of parsed scripts. Integrating the sleep console consists of building a console proxy that provides input/output services for the actual Sleep Console.
An example console proxy (using STDIN/STDOUT) is below:
import sleep.io.*; import sleep.console.ConsoleProxy; public class MyConsoleProxy implements ConsoleProxy { protected BufferedRead in; public MyConsoleProxy() { in = new BufferedReader(new InputStreamReader(System.in)); } public void consolePrint(String message) { System.out.print(message); } public void consolePrintln(Object message) { System.out.println(message.toString()); } public String consoleReadln() { try { return in.readLine(); } catch (IOException ex) { ex.printStackTrace(); return null; } } }
To instantiate the Sleep Console and install a custom console proxy:
import sleep.runtime.ScriptEnvironment; ConsoleImplementation console = new ConsoleImplementation(environment, variables, loader); console.setProxy(new MyConsoleProxy()); console.rppl(); // starts the console
This code instantiates a sleep console with the specified function environment, script variables, and script loader. Once an implementation of the sleep.console.ConsoleProxy interface is installed the sleep console is ready to use. Any application taking advantage of the sleep console should :instantiate it before scripts are loaded. This is necessary as the sleep console installs itself as a Loadable bridge into the ScriptLoader.
Java 1.6 includes a new programming interface (the javax.script API) to allow interchangeable use of different script engines. Sleep 2.1 supports this interface.
To obtain the Sleep script engine factory:
import javax.script.ScriptEngineManager; import javax.script.ScriptEngine; import javax.script.ScriptException; ... ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine sleepEngine = manager.getEngineByName("sleep");
To evaluate a script:
try { sleepEngine.eval("println('hello world!');"); } catch (ScriptException exception) { /* syntax errors are wrapped in a ScriptException object */ System.out.println(exception.getMessage()); }
The javax.script API supports the concept of "bindings" to act as shared variables between scripts. A binding
named 'frame' containing a JFrame
object is available as a variable named $frame that contains a
sleep object scalar with the JFrame
object.
The ScriptContext.GLOBAL_SCOPE
bindings are installed as global variables. The ScriptContext.ENGINE_SCOPE
bindings are available as local variables.
The javax.script API also has a ScriptContext object for specifying where to dump errors, receive input strings, and
write output strings. My experiments honoring the values of this object with jrunscript were less than happy. As such
Sleep ignores these values and dumps everything to System.in
and System.out
.
For more information see the javax.script API documentation: http://java.sun.com/javase/6/docs/api/javax/script/package-summary.html
Sleep was built to manage multiple scripts operating within a single application. When managing multiple scripts there are issues surrounding multi-threading and allowing scripts to share functions and data. Of course you get to choose how much sharing should occur. Scripts can either execute completely isolated of eachother or they can share everything including variables.
Every script has an environment associated with it. The environment is a java.util.Hashtable object associated with the sleep.runtime.ScriptEnvironment class. This Hashtable object contains all of the bridges (functions, operators) available to your Sleep scripts. Every time a script is loaded the environment is populated with the default bridges and any bridges that you elect to install through a Loadable interface. There is some overhead constructing this environment for each script. Bridge objects are not supposed to save state so it is generally safe to share the environment Hashtable object amongst scripts. This will enable all scripts to share functions and bridges amongst eachother.
The ScriptLoader class has several versions of the loadScript
method that accept a Hashtable environment as a parameter.
import java.util.Hashtable; import sleep.runtime.ScriptLoader; import sleep.runtime.ScriptInstance; ... Hashtable environment = new Hashtable(); ScriptLoader loader = new ScriptLoader(); try { ScriptInstance a = loader.loadScript("script1.sl", environment); ... ScriptInstance b = loader.loadScript("script2.sl", environment);
There is some potential for data leakage amongst multiple scripts. Sleep closures (every subroutine is created as a closure) maintain state within themselves. This includes their closure scope variables and their continuation data (in the event of callcc, yield, etc.).
To avoid nasty data leakage issues, call the makeSafe
method on the newer ScriptInstance objects. This method will sanitize the
script environment so you get the benefit of shared bridges without the headache of shared closures.
b.makeSafe();
You should do this before calling runScript
.
Script variables are managed by a ScriptVariables instance held by the ScriptInstance object. You can obtain this object with
the getScriptVariables
method and you are free to set it with the setScriptVariables
method.
This example shows how to share variables amongst two scripts.
import sleep.runtime.ScriptVariables import sleep.runtime.ScriptLoader import sleep.runtime.ScriptInstance ... ScriptVariables variables = new ScriptVariables(); try { ScriptInstance a = loader.loadScript("script1.sl", environment); a.setScriptVariables(variables); ... ScriptInstance b = loader.loadScript("script2.sl", environment); b.setScriptVariables(variables);
In my oppinion it makes no sense to share script variables without also sharing the environment.
Sleep scripts happily work in a multi-threaded application. This is accomplished by internal synchronization that allows only one function to execute at any given time. As soon as a function begins it must complete before another Sleep function in another thread may execute. Sleep internally synchronizes on the ScriptVariables object held by each ScriptInstance.
Functions such as &fork work by creating an isolated clone of the current ScriptInstance. This is accomplished through the fork
method of
ScriptInstance. Forked code can run in multiple threads without blocking. This is because it shares as little as possible with its parent script.
&fork creates an sleep.bridges.io.IOObject that acts as a handle for the thread. After initialization the setThread
method is called to associate
the child thread with the IOObject. The thread can pass a return value for the thread (obtained with &wait) with the setToken
method.
Earlier we discussed an environment Hashtable object that stores operators, functions, etc. Operations, functions, predicates, and certain constructs are added to the language after the script is parsed. These are called bridges. The interpreter is flexible enough to look for them in the environment and send a request to them. Bridges are a powerful means for introducing new abstractions and functionality into the Sleep language.
Before extending Sleep it is worth asking yourself if you really have to? As discussed in chapter 7, Sleep is capable of accessing anything from the Java class library. Bridges have the benefit of being faster due to the lack of reflection overhead and you can design an interface that is appropriate for Sleep.
The simplest bridge is the sleep.interfaces.Function bridge. This bridge adds one or more new functions to the Sleep language.
public class FooFunction implements Function { public Scalar evaluate(String name, ScriptInstance script, Stack args) { System.out.println("function foo has been called"); return SleepUtils.getEmptyScalar(); } }
Install this Function into the environment to make it available to scripts.
void scriptLoaded(ScriptInstance script) { Hashtable env = script.getScriptEnvironment().getEnvironment(); env.put("&foo", new FooFunction());
Notice that we begin the foo
function with an ampersand. All functions begin with an ampersand. Now that &foo
is installed
it can be called as foo()
within a script.
The evaluate method is expected to return a sleep.runtime.Scalar object. A Scalar is the universal container for Sleep data. Never return null
directly. To return a null scalar use the getEmptyScalar
method of sleep.runtime.SleepUtils.
To work with arguments passed to a built-in function:
public class MyAddFunction implements Function { public Scalar evaluate(String name, ScriptInstance script, Stack args) { int arg1 = BridgeUtilities.getInt(args, 0); int arg2 = BridgeUtilities.getInt(args, 0); return SleepUtils.getScalar(arg1 + arg2); } }
This function takes two arguments. The arguments are passed in as a java.util.Stack object. The sleep.bridges.BridgeUtilities class contains methods for safely extracting parameters from the arguments stack. In this example two integer parameters are extracted. The BridgeUtilities class allows a default value to be specified in case the stack is empty (i.e. not enough parameters were passed).
The last part of the above function is the return statement. Notice that the result of adding arg1 and arg2 is passed to getScalar(int)
in
SleepUtils. The
SleepUtils class contains static methods for converting just about any type you can think of into a Scalar usable by sleep scripts.
A predicate is an operator used inside of comparisons. Comparisons are used in if statements and loop constructs. sleep.interfaces.Predicate bridges are used to add new predicates to the language.
A binary predicate can have any name. A unary predicate always begins with the minus symbol. isin
is a binary predicate and
-isletter
is a unary predicate.
An operator is anything used to operate on two variables inside of an expression. For example 2 + 3
is the expression to add 2
and 3
. The plus
sign is the operator.
A predicate is created by implementing the interface Predicate. Likewise an operator is created by implementing the sleep.interfaces.Operator interface.
Register new predicates and operators with the sleep.parser.ParserConfig class before loading scripts.
ParserConfig.addKeyword("predicate");
The keyword registering practice is in place to clear up ambiguity when parsing scripts. The Sleep parser does not know what operators, functions, keywords it has. If you create an operator that follows the same naming rules as a function name, sleep might confuse left_hand_side operator (expression) for a function call. This is due to operator (expression) looking the same as function (expression) to the parser.
In the Sleep language a block of code associated with an identifier is processed by an sleep.interfaces.Environment bridge. Sleep implements the sub keyword with these. To declare a subroutine you use:
sub identifier { commands;
}
The environment bridge associated with the keyword sub
creates a new closure and saves it into the script environment as a function named
&identifier
.
In general a block of code is associated with an environment using the following syntax:
keyword identifier { commands;
}
An environment bridge is created by implementing the Environment interface. An environment bridge should register its keyword with the script parser before any scripts are loaded. Sleep also offers two variations on the Environment.
The syntax for a sleep.interfaces.FilterEnvironment is:
keyword identifier "string" { commands;
}
And the sleep.interfaces.PredicateEnvironment:
keyword (predicate) { commands;
}
Environment bridges are great for implementing different paradigms.
I really started to appreciate Sleep when I used scripts to implement the menus and keyboard shortcuts in jIRCii. Hardcoding these things is a pain. Not to mention powerusers will want to change them anywyas. By making these items scriptable I reduced my overall development effort, gave users an example to script from, and I was able to eat my own dogfood with the scripting interface.
The jIRCii key bindings bridge uses an Environment installed with the keyword bind
. This bridge lets users write scripts like this one:
bind Ctrl+D { closeWindow(getActiveWindow()); }
The implementation has three components. The Environment bridge itself, the installation of the key dispatch listener, and the key dispatch listener.
/* A bridge that creates a bind keyword for use within Sleep scripts. */ public class KeyBindingsBridge implements KeyEventDispatcher, Environment, Loadable { /** Storage for our bindings <String, Runnable> */ protected Map bindings = new HashMap(); /** From the Environment interface */ public void bindFunction(ScriptInstance script, String typeKeyword, String functionName, Block functionBody) { SleepClosure runme = new SleepClosure(script, functionBody); bindings.put(functionName, runme); }
This method is called whenever the bind
keyword is encountered in a script. This method constructs a closure out
of the code block and stores it into a java.util.Map for safe keeping. The key to the map is the actual keyboard shortcut to bind.
Of course Sleep can't use this Environment until you install it. This code installs this bridge into the ScriptEnvironment.
public void scriptLoaded(ScriptInstance script) { script.getScriptEnvironment().getEnvironment().put("bind", this);
This code registers the KeyBindingsBridge as a global listener for key dispatches.
/* install this object as a listener for all key presses... don't worry only bound key events are swallowed. Everything else passes through harmlessly */ KeyboardFocusManager manager; manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); manager.addKeyEventDispatcher(this);
This code responds to a key press.
/* called whenever a key is pressed. */ public boolean dispatchKeyEvent(KeyEvent ev) { /* ignore non-key press events */ if (ev.getID() != KeyEvent.KEY_PRESSED) { return false; } /* construct a description of the key press event */ StringBuffer description = new StringBuffer(); if (ev.getModifiers() != 0) { description.append(getKeyModifiers(ev)); } description.append(getKeyText(ev)); /* check if a script has bound this combination */ if (bindings.containsKey(description.toString())) { /* grab the scripted handler from our bindings map */ Runnable scriptedBind; scriptedBind = (Runnable)bindings.get(description.toString()); // in case you're confused, SleepClosure implements the // Runnable interface. /* invoke the script in the event dispatcher thread */ SwingUtilities.invokeLater(scriptedBind); /* consume the event */ ev.consume(); return true; } return false; }
And thats it. Whenever a key is pressed this method will check if the key combination is bound to a script. The script associated with the key combination is invoked. Any desktop app can immediately benefit from this type of extensibility.
Due to space constraints I am unable to include the entire source code for this example. To download it and play with it, visit:
No discussion of Sleep extension is complete without a discussion of the I/O pipeline. Sleep's I/O is built around IOObject. Each IOObject instance contains the read and write pipeline of a handle.
Sleep chains together multiple I/O streams to support its read, write, mark, and reset functions. This I/O chain is available from IOObject to implement your own I/O functions.
All Sleep handles are object scalars referencing an IOObject instance. To allow Sleep to interact with an I/O stream use the openRead
and
openWrite
methods on a freshly instantiated IOObject.
public class FetchBridge implements Loadable, Function { public void scriptLoaded(ScriptInstance script) { Hashtable env = script.getScriptEnvironment().getEnvironment(); env.put("&fetch", this); } public Scalar evaluate(String name, ScriptInstance script, Stack args) { /* this function bridge implements a new I/O handle &fetch. &fetch accepts a URL and opens a stream for reading in the contents of the URL */ URL address = new URL(BridgeUtilities.getString(args)); IOObject temp = new IOObject(); temp.openRead(address.openStream()); return temp; }
Sleep refers to STDIN and STDOUT as the console. The console is specific to each script (regardless of shared environment). To obtain the console for
a script use the getConsole
method of IOObject. Likewise to set the console for a script use the setConsole
method.
Sleep scripts share bridges and compiled scripts amongst eachother and sometimes across multiple threads. Sleep has a thread model that relies on isolation of script instances. To keep this isolation (while sharing where reasonable) make sure you don't store state in your scripts.
This is an example of where you can learn from my mistakes. The regular expression bridge used to store state to allow &matched to retrieve the results of the most recent ismatch and hasmatch comparisons. After I implemented &fork I started receiving complaints of very strange bugs in the regex engine. Eventually I figured out the problem. I created a content metadata mechanism to help solve it once and for all.
Sleep provides a context metadata mechanism for storing state. Context metadata is associated with the current closure scope. You can store and retrieve
context objects using the setContextMetadata("key", $object)
and getContextMetadata("key")
methods in ScriptEnvironment.
This example demonstrates an accumulator function. The purpose of an accumulator is to store an integer and add 1 to it each time it is called. Naturally this accumulator function has to store some internal state. For this we use the context metadata mechanism:
public class Accumulator implements Function, Loadable { public void scriptLoaded(ScriptInstance script) { script.getScriptEnvironment().getEnvironment().put("&accum", this); } public Scalar evaluate(String name, ScriptInstance script, Stack args) { ScriptEnvironment env = script.getScriptEnvironment(); /* retrieve the accumulator value */ Integer value = env.getContextMetadata("accum", new Integer(0)); /* add one to the accumulator value */ env.setContextMetadata("accum", new Integer(value.intValue() + 1)); /* return the accumulated value (prior to accumulation) */ return SleepUtils.getScalar(value.intValue());
Sleep makes a distinction between hard errors and soft errors. A hard error indicates a failure somewhere within Sleep (sometimes caused by the scripter!). Attempting to use a non-existent operator is an example of a hard error. A soft error is an error that can be recovered from. Attempting to access a non-existent file is a soft error.
Soft errors are accessible with the &checkError function or as a Sleep exception when debug mode 34 is enabled. Sleep accepts any type of object for soft errors.
public class FearFactor implements Function, Loadable { public void scriptLoaded(ScriptInstance script) { script.getScriptEnvironment().put("&fear", this) } public Scalar evaluate(String name, ScriptInstance script, Stack args) { /* checkError($blah) will now return "fear me!" after calling this function */ script.getScriptEnvironment().flagError("fear me!");
The best way to communicate a hard error is through a Java exception. This will allow Sleep to report the exception with the appropriate line number and script file. If a user failed to supply the correct arguments use an java.lang.IllegalArgumentException. Wrap everything else in a java.lang.RuntimeException.
public Scalar evaluate(String name, ScriptInstance script, Stack args) { if (args.isEmpty()) { throw new IllegalArgumentException(name + ": grrr!!!"); }
The drawback to exceptions is they will cause the current function to exit with a $null return value. If you really want to display a warning
use the showDebugMessage
method of the ScriptEnvironment class.
public Scalar evaluate(String name, ScriptInstance script, Stack args) { script.getScriptEnvironment().showDebugMessage("hello world!");
Warning: hello world! at script.sl:3
To support security (and because I care), Sleep has a taint mode. Taint mode forces Sleep to taint variables received from external sources. This is a security mechanism to help educate scripters when they may be using tainted data within dangerous operations.
Terminology used here comes from the 'Run-time taint support proposal' [http://news.php.net/php.internals/26979] by Wietse Venema posted to the PHP internals mailing list.
Sleep's implementation of taint is designed to have no performance impact when turned off. When enabled taint mode wraps interpreter instructions with taint wrappers. These wrappers enforce the taint policy for an operation based on its category.
Wrapped instructions include operations and function calls. Parsed literals are treated as a special case.
Sleep has 4 taint value categories:
The taint mechanism depends on you to flag your Sleep extensions into the appropriate category. With this in mind Sleep tries to make this process as easy and transparent as possible.
public void scriptLoaded(ScriptInstance si) { Hashtable env = si.getScriptEnvironment().getEnvironment(); // install &foo as a Tainter function. env.put("&foo", TaintUtils.Tainter(this)); // install &bar as a Sanitizer function. env.put("&bar", TaintUtils.Sanitizer(this)); // install &dbquery as a Sensitive function. env.put("&dbquery", TaintUtils.Sensitive(this));
The sleep.taint.TaintUtils class contains static methods that accept different Sleep bridges as parameters. They return wrapped versions of these bridges if tainting is enabled. If tainting is disabled these functions merely return the original bridges that were passed in. If you're writing a bridge you merely need to identify which of your functions are sanitizers, tainters, or sensitive and wrap them as shown.
To set taint mode (globally):
/* do this before loading any scripts */ System.setProperty("sleep.taint", "true");
To check if taint mode is enabled use the isTaintMode
method of the TaintUtils class. You shouldn't have to do this often as all of the taint
mode methods check it for you.
To manually flag data as tainted use the taintAll
method within the TaintUtils class. This method handles nearly every type of Scalar container and
marks it as tainted.
Scalar value = SleepUtils.getScalar("SELECT " + userInput + " FROM: *"); TaintUtils.taintAll(value);
This last step is necessary if your bridge returns data from an external source.
Sleep bridges are useful for abstracting your application API to end users. They are also useful as a means for creating a third party extension to the Sleep language that anyone can utilize in their scripts. If you have a bridge you'd like to distribute, simply create a .jar file and take note of the Loadable class in your bridge. The Loadable class is the entry point to your extension.
Users then have the option to import your jar file and instantiate your bridge on the fly with the &use function. Example:
import org.dashnine.MyLoadable from: extension.jar use(^MyLoadable);
The SleepUtils class contains several getScalar
methods. These methods accept a Java primitive or object and return a Scalar.
/* create an int scalar */ Scalar a = SleepUtils.getScalar(3); /* create a String scalar */ Scalar b = SleepUtils.getScalar("hello world!"); /* create an Object scalar referencing frame */ JFrame frame = new JFrame("Mona Lisa"); Scalar c = SleepUtils.getScalar(frame);
These methods are good for passing values into Sleep. Sleep will gladly wrap java.util.Collection and Map objects into Sleep arrays and hashes. To do this:
public Scalar toSleepArray(List aList) { return SleepUtils.getArrayWrapper(aList); } public Scalar toSleepMap(Map aMap) { return SleepUtils.getHashWrapper(aMap); }
These wrappers return read-only versions of these data structures. You can create an instance of a scalar hash or array and populate them yourself.
/* populate an array */ Scalar array = SleepUtils.getArrayScalar(); array.getArray().push(SleepUtils.getScalar(1)); array.getArray().push(SleepUtils.getScalar(2)); /* populate a hash */ Scalar hash = SleepUtils.getHashScalar(); Scalar value = hash.getHash().get("key"); value.putValue(SleepUtils.getScalar("some object"));
Notice that the hash population called the setValue
method on the Scalar. setValue
sets the value referenced by the container. This is
good when you intentionally want to change what a scalar references. Just remember the container may be referenced elsewhere.
To create your own implementation of Sleep types implement the sleep.runtime.ScalarType, sleep.runtime.ScalarHash, or sleep.runtime.ScalarArray interfaces.
Use the describe
method to obtain a scalar string description. This method also works on arrays and hashes.
Scalar a = SleepUtils.getScalar("this is a string"); Scaalr b = SleepUtils.getScalar(3L); System.out.println("a: " + SleepUtils.describe(a)); System.out.println("b: " + SleepUtils.describe(a));
The getEmptyScalar
method of SleepUtils is the only acceptable way to construct a scalar equivalent to $null. Use the isEmptyScalar
method
to test if a scalar is null or not.
Test wether a scalar evaluates to true with the isTrueScalar
method. The Sleep definition of truth is covered in chapter 4.
Sleep can marshall data back and forth between scalars and Java types. The sleep.engine.ObjectUtilities class has several methods for this. The
BuildScalar
method generates the most appropriate Sleep scalar from an arbitrary Java object:
/* construct an arbitrary object */ Object blah = new Integer(4); /* creates an int scalar */ Scalar bleh = ObjectUtilities.BuildScalar(true, blah);
Use the buildArgument
method to convert a Sleep scalar to the specified class. This method will do its best to handle arrays, Maps, and Collections
as well.
The sleep.engine.ProxyInterface class has several methods for generating an anonymous object that implements one or more interfaces. This anonymous object is backed by a Sleep closure. This class is the implementation of the &newInstance function.
These are the same methods used by Sleep's object expressions to convert values.
Sleep stores functions in an object Scalar as a sleep.bridges.SleepClosure object. You can use the isFunctionScalar
method of the SleepUtils class to check if
a Scalar contains a function.
public Scalar evaluate(String name, ScriptInstance script, Stack args) { Scalar value = BridgeUtilities.getScalar(args); if (SleepUtils.isFunctionScalar(value) {
The getFunctionFromScalar
method of SleepUtils can extract a SleepClosure from a Scalar.
SleepClosure function; function = SleepUtils.getFunctionFromScalar(value, script);
You can pass arguments to a SleepClosure. Here I setup several named arguments and create an argument Stack out of them.
/* create a locals stack with named values */ Map values = new HashMap(); values.put("$a", SleepUtils.getScalar("apple")); values.put("$b", SleepUtils.getScalar("bannana")); Stack locals = SleepUtils.getArgumentStack(values);
You can also pass anonymous arguments to a SleepClosure. Simply push them onto the stack in order.
/* push anonymous arguments on to the stack */ locals.push(SleepUtils.getScalar("first arg")); locals.push(SleepUtils.getScalar("second arg"));
To execute a SleepClosure use the runCode
method of the SleepUtils class.
/* call the closure */ Scalar value = SleepUtils.runCode(function, name, script, locals);
And that's it.