Syntax

Simple literals

Bex script uses the same syntax as Java for simple literals. Numeric, character an boolean literals are exactly like in Java. See Java Language Specification for details. Valid literals are for example:

bex> 0xf
15
bex> 1127498877973L
1127498877973
bex> 1.2f
1.2
bex> 1.3e3
1300.0
bex> 'a'
a
bex> true
true

Bex script supports the same syntax for string literals as Java. However in addition to Java like string literals, bex script supports also multiline string literals. For example:

bex> "hello world"
hello world
bex> """hello
multiline
world
"""
hello
multiline
world

Note that the normal character escapes are not supported in multiline string literals.

From version 1.1, bex also supports embedded expressions inside strings. For example:

bex> "Current date is: ${Date.new()}"
Current date is: Sun Oct 01 18:28:46 EEST 2006

Embedded expressions work both with normal strings and with multiline strings.

Bex script has two additional special literals: null and void. Null is the value of a reference that does not refer to any object. Void is the value of an expression that does not have any value.

Variables

The variables in bex script are untyped. A variable has a name and a value. A variable is defined by assigning a value to a name. For example:

bex> a = 5
5
bex> hello = "world"
world
bex> a
5

Variables live inside containers called environments. An environment is basically a Map that maps variable names to values. In addition an environment is associated with a parent environment unless it is the top environment. The parent of the top environment is null.

Special variables

In addition to user defined variables bex script has the following "magic" variables:

this Refers to the environment of the currently executing function. This is the environment where the function call arguments and other "local" variables live.
super Refers to the parent of an environment.
$context Refers to the evaluation context. The evaluation context provides access to the script call stack. For example $context.caller() returns the environment of the caller of the currently executing function.
$0, $1, ... Refers to the arguments of a function that were not declared when the function was defined
$args Refers to the array containing the command line arguments.

Identifiers

Bex script variables are identified by name. Other identifiers in bex script are Java class names and java class member names.

Valid bex script variable names begin with a letter [A-Za-z] or character underscode '_' or dollar sign '$'. The rest of the characters are either letters, digits, underscores or dollars.

The syntax for Java class names differs from Java. The package name separator is double colon '::' and the inner class name separator is the dollar sign '$'. So for example:

bex> java::util::Map$Entry
interface java.util.Map$Entry

The syntax for referencing the members of java objects and classes is the same as in Java, for example:

bex> java::lang::System.out.println("hello")
hello
void

The reason not to use '.' as the separator is that I don't like using the same token for both member access and as a name separator. Having ambiguous tokens like that just feels dirty - and makes the implementation messy.

Expressions

A bex script program is a sequence of expressions. A program is executed by the bex script interpreter by evaluating the expressions in order. Expressions are built from literals, variable names and Java class and class member names by combining them using operators.

Logical operators

==a == b True if a equals b. For Java objects the comparison is done with the Object.equals method.
!=a != b True if a does not equal to b. This is equivalent with !(a == b).
&&a && b True if a both a and b evaluate to boolean true.
||a || b True if either a or b evaluate to boolean true.
!!a True if a evaluates to boolean false.

Arithmetic operators

+a + b Addition of a and b. If a is a string then concatenates b to a.
-a - b Subtraction of b from a.
*a * b Multiplication of a and b.
/a / b Division of a and b.
%a % b Modulus of a and b (the remainder of a divided by b).

Bitwise operators

&a & b Bitwise and of a and b.
|a | b Bitwise or of a and b.
^a ^ b Bitwise xor of a and b.
<<a << b Bitwise shift of a by b bits to left.
>>a >> b Signed bitwise shift of a by b bits to right.
>>>a >>> b Unsigned bitwise shift of a by b bits to right.
~~a Bitwise negation of a.

Assignment operators

For all binary assignment operators the value of the assignment expression is assigned to the left hand side of the expression.

=a = b Assigns b to a.
+=a += b Addition of a and b. If a is a string then concatenates b to a.
-=a -= b Subtraction of b from a.
*=a *= b Multiplication of a and b.
/=a /= b Division of a and b.
%=a %= b Modulus of a and b.
&=a &= b Bitwise and of a and b.
|=a |= b Bitwise or of a and b.
^=a ^= b Bitwise xor of a and b.
<<=a <<= b Bitwise shift of a by b bits to left.
>>=a >>= b Signed bitwise shift of a by b bits to right.
>>>=a >>>= b Unsigned bitwise shift of a by b bits to right.
++a++ ++a Incrementation of a by one. The value of the suffix form is the value of a before the incrementation. The value of the prefix form is the value of a after the incrementation.
--a-- --a Subtraction of one from a. The value of the suffix form is the value of a before the subtraction. The value of the prefix form is the value of a after the subtraction.

Special operators

.a.b The member b of a. If a is a bex script environment then a.b is the value of the variable with name b. If a is a Java object or class then a.b is the value of the class member with name b.
[]a[b] "Lookup" of b from a. If a is a Java array, b is expected to be an integer and the value of a[b] is the array element with index b. If a is an instance of java.util.Map then a[b] is equivalent with a.get(b). If a is a bex script environment then a[b] is the value of variable with name b.
()a(b,c) The return value of calling a with arguments b and c.

Block expressions

Functions are created in bex script by assigning a block expression to a variable. A block expression consists of an optional declaration of unbind variables and a sequence of expressions forming the body of the block expression. For example:

bex> a = |x,y|{ print(x + y) }
bex.Block@67ac19
bex> a("hello","world")
helloworld
void

The unbind variables (arguments) are declared inside a pair of vertical bar '|' characters and separated by commas. The argument declaration is optional. Any arguments that are not declared will be available to the body expressions as special variables $0, $1 and so on. If the last argument ends with token '...' the argument will contain the rest of the arguments as an array. For example:

bex> a = { print($0) }
bex.Block@253498
bex> a("hello")
hello
void
bex> a = |args...|{ args }
bex.Block@9fef6f
bex> b = a(1,2,3,4,5)
[Ljava.lang.Object;@1a457b6
bex> Arrays.asList(b)
[1, 2, 3, 4, 5]

Function call

A block expression can be called with the special operator (). When called like this the block expressions effectively behaves like a regular function: a new environment is created, the unbind variables are bound according to the argument declaration of the block expression and finally the expressions forming the body are evaluated. The following text uses the terms block expression and function interchangeably. Note however that block expressions also support other operations in addition to the call-operation.

Normally the arguments to a block expression are specified as a comma separated list inside the parentheses. In addition to this, bex script provides special syntactic sugar for arguments that are block expressions. They can be specified outsidethe parentheses and commas are not needed between them. If all the arguments are block expressions then the parentheses are not needed at all. This syntactic trick allows writing bex script functions that are syntactically similar to some control flow statements in Java.

The following example illustrates different ways to call a block expression. In the example the construct {void} is just an example function literal.

bex> fun = |args...|{ print(Arrays.asList(args)) }
bex.Block@2b58b0
bex> fun(1,2,3)
[1, 2, 3]
void
bex> fun(1,2,{void})
[1, 2, bex.Block@275860]
void
bex> fun(1,2) {void}
[1, 2, bex.Block@2d8e70]
void
bex> fun {void} (2) {void}
[bex.Block@2d8890, 2, bex.Block@2d8880]
void
bex> fun {void} {void} {void}
[bex.Block@2d8320, bex.Block@2d8310, bex.Block@2d8300]
void

Normally a call terminates when all the expressions of the block expression body have been evaluated. A call can also terminate before that if an expression in the block expression body evaluates to an instance of bex.ReturnControl. The builtin functions return, break and continue evaluate to bex.ReturnControl instances, so they can be used as control flow statements.

Another way a block expression can be evaluated is by inlining it. Inlining a block expression is very much like calling it. The only differences are in the way "this" and "super" are resolved and how it behaves with respect to ReturnControls. In a normal call, if an expression evaluates to an instance of ReturnControl, the interpreter stops evaluating the expressions and the value of the call is the value associated with the ReturnControl. However, when the block expression is being inlined the value of the inlining is not the value associated with the ReturnControl object but the ReturnControl object itself. So if block expression b is being inlined inside a block expression a and b calls return(1), then also a returns and the return value is 1. This feature can be used to implement loops and other control structures that are often implemented as special statements in other languages.

Special variables "this" and "super" are normally resolved so that this resolves to current environment and super to the lexical parent environment. When a block expression is inlined, however, they are resolved to the closest non-inlined environment and its parent. This means that you can do:

if (true) { this.foo = "bar" }
print(this.foo)
and expect it to print "bar" instead of void because if inlines the block expression it receives as its second argument instead of calling it. The builtin function inline can be used to create function from a block expression that will be inlined when the expression is called.

There is yet another way to use a block expression which is especially useful when constructing domain specific languages. In addition to calling and inlining a block expression can be just evaluated. This is different from calling/inlining because no new environment is created. The effect is the same as if the body of the block expression was written right there where it is being evaluated. If the block expression had any unbind variables they will be resolved according to normal variable resolution procedure and if they are unresolved an exception is thrown. The builtin function eval can be used to evaluate a block expression.

Scripted objects and meta object protocol

To create a scripted object (i.e. an entity with attributes and functions that manipulate those attributes) one needs to just return (or pass) a reference to an environment. The returned environment behaves like a closure: it contains all the variable bindings that existed before it was returned. Now this is very much like in BeanShell and while extremely useful not particularily exciting. However, bex script also provides a powerful meta object protocol (MOP) that is more flexible than BeanShell's invoke. By using special variables $set, $get and $dir one can extend the regular variable resolution procedure and do all kinds of clever tricks.

For example to make javax.servlet.http.HttpSession to appear as a bex script object, code similar to the following example could be used:

wrapSession = |session|{
	ws = object()
	ws.$get = |name|{ session.getAttribute(name) }
	ws.$set = |name, value|{ session.setAttribute(name, value) }
	ws.$dir = { session.getAttributeNames() }
	ws
}

Builtins

Bex script comes with a bunch of useful builtin functions. These are defined in the script default.bex which is automatically evaluated when the interpreter is started.

array
array(byte,5)
Creates a Java array of the type given as the first argument, having the dimensions specified by the rest of the arguments. For example array(byte, 5) is equivalent with new byte[5] in Java.

cast
cast(10, Double.TYPE)
Casts the first argument to the class specified by the second argument.

dir
dir(this)
Lists the variable bindings in the specified environment. Returns a collection containing the names of the variables in the specified environment.

each
each(list) { print($0) }
Inlines the function specified by the second argument with every item in the collection specified by the first argument. See also extensions to standard Java collections.

eval
eval { foo = "bar" }
Evaluates the given block in the caller's environment. The example above would introduce a variable "foo" to the caller's environment. This is useful when creating DSLs.

if
if (a < 0) { 
  print("negative") 
} { 
  print("positive") 
}
If the first argument is "condition true", the function specified by the second argument is inlined. Otherwise, if there is a third argument that is a function, it is inlined. Here "condition true" is anything that is not: boolean false, null, void or empty string.

import
import("java::awt", javax::awt::event")
Adds the packages given as arguments to the package search list. When the interpreer resolves Java class names it searches the packages in this package list for a class with the given name.

lock
lock(obj) { obj.foo++ }
Synchronizes to the first argument and then inlines the second argument. This is equivalent with Java's synchronized(obj) { obj.foo++; } statement.

throw
throw(Exception.new())
Throws the given exception.

try
try { 
	out.write(str.length())
} (NullPointerException) {
	print("null string")
} (IOException) {
	print("write failed: " + $0)
} {
	out.close()
}
Inlines the function given as the first argument. If this function throws an exception then try goes through the rest of the arguments. If an argument matches the thrown exception then the function specified by the next argument is inlined. Finally if the last argument is a function not associated with any exception then it is inlined after everything else.

while
while { a < 10 } {
	print(a)
	a++
}
While the first argument function evaluates to condition true (see if for the definition of condition true), the second argument function is inlined.

inline
inline(print)
Returns a function that inlines the given function.

print
print("hello world")
Prints the given string + newline to standard output.

list
list(1,2,3,4)
Creates a list that contains the arguments. The returned list implements java.util.List interface.

tuple
tuple(1,2,3)
Creates a tuple of the arguments. The returned object is an array that contains the arguments.

hash
hash("name1","value1","name2","value2")
Creates a HashMap with the given arguments. The arguments are processed in pairs: the first is treated as the key and the second as the value.

return
return(1)
Returns the given value from a function.

break
break(1)
Breaks the execution of a loop and returns the given value as the value of the loop expression.

continue
continue()
Continues the execution of a loop.

object
object { firstName = "Juha"; lastName = "Lindström" }
Creates a scripted object by evaluating the body of the function given as first argument. This is syntactic sugar for declaring and calling a function that returns its own environment. For example, the above example is equivalent with: { firstName = "Juha"; lastName = "Lindström"; this }()

implement
implement(Runnable,Iterator,this)
Creates a dynamic proxy that implements the specified interfaces by delegating calls to the given bex object. The last argument is either a bex object or a block implementing the specified interfaces.

include
include("script.bex")
Evaluates the given file(s) in the current environment. The files are first searched from the file system and then from the classpath.

Java extensions

Bex script supports registering extension methods to existing Java classes. This functionality can be used to make accessing Java APIs from scripts easier and more natural. The default startup script default.bex contains the following extensions:

java::util::Collection.each
bex> list(1,2,3).each |it|{ print("item: void") }
item: 1
item: 2
item: 3
void
Loops through the items in the collection and inlines the specified function. The current item is passed to the function as the first argument. This extension works also for Java arrays since bex script treats them in the same manner as Collections with regards to extensions.

java::util::Iterator.each
bex> list(1,2,3).iterator().each |it|{ print("item: void") }
item: 1
item: 2
item: 3
void
Loops through the items in the iterator and inlines the specified function. The current item is passed to the function as the first argument.

bex::Primitive.times
bex> 3.times |i|{ print("count: void") }
count: 0
count: 1
count: 2
void
Loops from 0 to the target value and inlines the specified function.

java::io::InputStream.load
bex> FileInputStream.new("test.txt").load()
[B@1ea2dfe
Loads all bytes from the stream and returns the data as a byte array.

java::io::Reader.load
bex> FileReader.new("test.txt").load()
hello
world
Reads all characters from the reader and returns the data as a string.

java::io::File.load
bex> File.new("test.txt").load()
line: hello
line: world
void
Loads the contents of the file and returns it as a byte array.

java::io::InputStream.eachLine
bex> FileInputStream.new("test.txt").eachLine |l|{ print("line: void") }
line: hello
line: world
void
Inlines the given function for each line read from the reader. The line is passed to the function as the first argument. Note that the above example is flawed since it does not close the FileReader.

java::io::Reader.eachLine
bex> FileReader.new("test.txt").eachLine |l|{ print("line: void") }
line: hello
line: world
void
Inlines the given function for each line read from the reader. The line is passed to the function as the first argument. Note that the above example is flawed since it does not close the FileReader.

java::io::File.eachLine
bex> File.new("test.txt").eachLine |l|{ print("line: void") }
line: hello
line: world
void
Inlines the given function for each line in the file. The line is passed to the function as the first argument.

java::io::InputStream.with
FileInputStream.new("test.txt").with |in|{ in.read() }
Ensures that the InputStream is closed after the function is inlined no matter what happens.

java::io::OutputStream.with
FileOutputStream.new("test.txt").with |out|{ out.write(1) }
Ensures that the OutputStream is closed after the function is inlined no matter what happens.

java::io::Reader.with
FileReader.new("test.txt").with |in|{ in.read() }
Ensures that the Reader is closed after the function is inlined no matter what happens.

java::io::Writer.with
FileWriter.new("test.txt").with |in|{ out.write("foo") }
Ensures that the Writer is closed after the function is inlined no matter what happens.

java::net::Socket.with
Socket.new("localhost", 80).with |s|{
	s.outputStream.with { $0.write("GET / HTTP/1.0\r\n\r\n".bytes) }
	s.inputStream.with { InputStreamReader.new($0).eachLine { print($0) } }
}
Ensures that the Socket is closed after the function is inlined no matter what happens.

java::net::ServerSocket.with
ServerSocket.new(4314).with |ss|{ handle(ss.accept()) }
Ensures that the ServerSocket is closed after the function is inlined no matter what happens.

Adding new Java extensions is easy: just declare a new function and assign it to a static member of a Java class that does not exist. The first argument to the function will be the object that received the call. For example:

bex> String.toInt=|s|{ Integer.parseInt(s) }
bex.Block@19fcc69
bex> "123".toInt() + 1
124

Interactive use

The bex script interpreter can be started in interactive mode with the following command:

java bex.Interpreter [-q] [-i] [file1] [arg1 arg2 ...]

If there are no command line arguments the interpreter starts in interactive mode. Option -i forces the interpreter to enter interactive mode even if the arguments also specify a file "file1" to be evaluated. The rest of the arguments arg1 arg2 ... are passed to the script as an array of strings with name $args. Option -q tells the interpreter to be quiet and not print any prompts or welcome messages.

Tip: You can get get command history and other shell like features by using a very nice utility program called rlwrap. Just start the interpreter with rlwrap java bex.Interpreter and working with the interpreter will be much more pleasant.

Tip2: When used from the command line, the interpreter evaluates file $HOME/.bexrc if it exists. You use this facility to setup the root environment the way you want (e.g. by adding some functions not provided by the default environment).

Embedded use

The interpreter can be embedded inside a Java application in the following way:

// Create the interpreter
bex.Interpreter i = new bex.Interpreter();

// Sets a variable inside the interpreter
i.put("myname", "bex");

// Evaluate file myscript.bex
FileReader in = new FileReader("myscript.bex");
try { i.eval(fr); } finally { in.close(); }

// Evaluate a string
i.eval("greeting = \"hello \" + myname");

// Print the value of the script variable "greeting"
System.out.println(i.get("greeting"));

Use from within a shell script

The following trick can be used to run a bex script from within a shell script:

#!/bin/bash
export CLASSPATH=/home/jli/projects/bexscript/trunk/dist/bex-1.0.1.jar
java bex.Interpreter -q <<EOF
10.times { print("hello from bexscript") }
EOF

The -q switch causes the interpreter not to output any prompts or welcome messages when reading input from stdin.