BeanShell 2.2b0 with Enhanced Syntax

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
Report Content as Inappropriate

BeanShell 2.2b0 with Enhanced Syntax

Scott Stevenson

I just committed a new branch of BeanShell: v2.2b0.  This features some new and enhanced syntax structures I discussed with Peter Jodeleit and Pat earlier this Fall.  Sorry I did not follow procedure by creating an issue for this before checking in.  I didn't read up on procedures until after I created the branch.  I started this branch from v2.1b0, but merged all changes to 2.1b1 after committing my changes.  

Please try this out and comment.  I've tested it quite a bit in the project I am working on, but haven't created any dedicated test cases for release as of yet.  Below is a description of the new features.  I have this in html format ready to put into the documentation, but I need someone to re-generate the documentation since I don't have htmldoc.


BeanShell version 2.2 adds the following enhancements to basic Java syntax:

    * Enhanced syntax for creating Arrays, Lists, and Maps
    * Operator overloading
    * Extended methods
    * Automatic type casting for operators and extended methods
    * Defined cast operator methods
    * Mathematical power operator "**", as in y = x**2;
    * Range operator for int, double, and char types: myIntArray = [0:10];

Syntax enhancements are mostly borrowed from Groovy, Python, and MATLAB. BeanShell 2.2 can be thought of as "Groovy Lite" for embedded scripting. The major differences are that Groovy is designed to be a primary development language, whereas the BeanShell 2.2 aims to provide a rich high level scripting language for existing Java classes. Groovy has an extremely large footprint, where as BeanShell is very small. Groovy classes and scripts must be compiled, whereas BeanShell is much more supportive of embedded scripting. In fact, the main motivation for adding these enhancements to BeanShell was frustration with trying to use Groovy as an embedded scripting language.

A requirement of any syntax enhancements was to maintain 100% backward compatibility with standard Java syntax. This effected some of the choices in making these enhancements as described below.
Enhanced Syntax for Arrays, Lists, and Maps
Arrays and Lists

The following examples show how to create arrays and List using BeanShell enhanced syntax.

// Create int[] array
intArray = [1,2,3,4,5];

// Create double[] array
doubleArray = [1.,2.,3.,4.,5.];

// Create double[] array (With mixed data types, array type determined by the highest order value in the list)
doubleArray2 = [1,2.,3,4,5];

// Convert the array to a List of Integer values
intList = [1,2,3,4,5].asList();
// or
intList = asList([1,2,3,4,5]);

// Notice the "asList()" method for int[] class above.  This is an extended method added to all array types.
// You can define your own extended methods on any class type via compiled Java code.
// Extension methods are described in detail later.

// You can also cast an array to a list as follows:
intList = (List)([1,2,3,4,5]);

// Generally asList() returns a thin List wrapper that is backed by the original array.
// Casting as shown above returns a new List that can be modified (depending on the underlying "cast" function)
// When directly casting an array created using the [] syntax, the expression must be parenthesized as shown above.

// To create a String[] array:
strArray = ["hello", "world"];

// Convert the String[] array to a list:
strList = (List)strArray;

New "Range" Operator

Numeric and character arrays can be creating using the new "range" operator as shown in the examples below.

// Create int[] array from 0 to 10 (inclusive)
intArray1 = [0:10];

// Create int[] array from 0 to 10 by 2 (inclusive)
intArray2 = [0:2:10];

// Create int[] array with mixed values
intArray2 = [0:10, 12:2:20, 25, 30];

// This syntax also works for 2D arrays, or N-Dimensional arrays
intArray2D = [ [0:10], [20:30] ];  // Creates 2D array

// The range operator also works on double and char data types
doubleArray1 = [1.0:5.0];  // 1.0 to 5.0 by default 1.0

charArray1 = ['a':'z'];  // a to z

// The range operator does not work outside of the [] brackets
intArray1 = [0:10];  // Correct
intArray1 = 0:10;    // Error!!!  This form conflicted with other Java syntax.

The "range" operator is a new operator. Other scripting languages like Python and Groovy implement a range operator using ".." as in [0..10]. This syntax conflicted with other standard Java syntax so the ":' character was used instead. The ":" character was chosen because it was also used in MATLAB to delimit ranges. In BeanShell 2.2 developers can define their own methods to implement operator overloading, which means you can also define your own range operator methods. Defining new operators (or extended methods) must be done in Java code you supply. Scripted operator overloads or extended methods is not yet supported. Instructions on how you define your own overloaded methods are presented later. By default, BeanShell 2.2 supports a range operator on int, double, and char data types; and any types that can be cast to those types.

BeanShell also features an enhanced syntax for creating Maps (Associative Arrays). The syntax is similar to that for arrays as shown in the following examples.

// Create a map that maps String names to their values
map1 = ["one"=1, "two"=2, "three"=3, "four"=4, "five"=5];

// Create the reverse map that maps numeric values to their String names
map1 = [1="one", 2="two", 3="three", 4="four", 5="five"];

// Format for maps is:  map = [key1=value2, key2=value2, etc...];

BeanShell uses the "=" character as the delimiter between key and value pairs. Other scripting languages like Python and Groovy use the ":" character, however since ":" is used as the range delimiter in BeanShell, it was un-available to use for maps.
Operator Overloading

BeanShell 2.2 implements operator overloading in a similar way as Groovy and Jython, by invoking a function corresponding to the operator symbol. For example, the + operator in the expression:

result = lhs+rhs;

is mapped to a static function plus(Type1 lhs, Type2 rhs), where Type1 and Type2 are the class types of each argument. Out of the box support is provided for numeric values and arrays.

// Out of the box, BeanShell 2.2 defines Mathematical overloads for primitive arrays.
array1 = [1.,2,3,4,5,6];
array2 = array1+5;        // Add 5 to each element in the array
array2 = 5+array1;        // Add 5 to each element in the array (works left or right)
array3 = 2*array1+3;      // Multiply array by 2, then add 3
array4 = array1 + array3; // Adds arrays element by element.  Must have same length or an Exception will be thrown.

// BeanShell 2.2 defines a new mathematical "power" operator
array1Squared = array1**2;  // Squares each element in array1

The power operator works on scalar values, and can be overloaded by your own methods.
A complete list of operator overloads currently supported are given later.
More may be added in the future.

The following table list all of the current "overloadable" operators and the function name they are mapped to.

Operator Symbol Function/Method Name
+ plus
- minus
* times
/ divide
+= plusEquals
-= minusEquals
*= timesEquals
/= divideEquals
[] getAt
[] putAt
() cast
- negate
** power
: range

If BeanShell cannot find an overloaded method for specific data types, it will try to "cast" the data types to the closest matching types. It does this by looking for a cast method to convert the data. Casting is itself an operator overload. The following list shows the operator overloading functions and cast methods built into BeanShell 2.2. The BeanShell API javadocs documents the classes that implement these operators in the package bsh.operators.standard.
List Pre-Defined Operator Overloads
String = plus(String,String)
String[] = plus(String[],String[])
String[] = plus(String[],String)
String[] = plus(String,String[])
double[] = plus(double[],Double)
double[] = plus(Double,double[])
double[] = plus(double[],double[])
Double = plus(Double,Double)
int[] = plus(int[],int[])
Integer = plus(Integer,Integer)
int[] = plus(Integer,int[])
int[] = plus(int[],Integer)
float[] = plus(float[],float[])
float[] = plus(Float,float[])
float[] = plus(float[],Float)
Float = plus(Float,Float)
int[] = minus(Integer,int[])
int[] = minus(int[],int[])
Integer = minus(Integer,Integer)
int[] = minus(int[],Integer)
float[] = minus(float[],float[])
Float = minus(Float,Float)
float[] = minus(float[],Float)
float[] = minus(Float,float[])
double[] = minus(Double,double[])
Double = minus(Double,Double)
double[] = minus(double[],Double)
double[] = minus(double[],double[])
int[] = times(int[],int[])
int[] = times(int[],Integer)
int[] = times(Integer,int[])
Integer = times(Integer,Integer)
double[] = times(Double,double[])
double[] = times(double[],Double)
double[] = times(double[],double[])
Double = times(Double,Double)
Float = times(Float,Float)
float[] = times(Float,float[])
float[] = times(float[],Float)
float[] = times(float[],float[])
float[] = divide(Float,float[])
Float = divide(Float,Float)
float[] = divide(float[],Float)
float[] = divide(float[],float[])
double[] = divide(Double,double[])
double[] = divide(double[],Double)
double[] = divide(double[],double[])
Double = divide(Double,Double)
Integer = divide(Integer,Integer)
int[] = divide(int[],Integer)
int[] = divide(int[],int[])
int[] = divide(Integer,int[])
int[] = getAt(int[],int[])
Integer = getAt(int[],Integer)
Map = getAt(Map,Object[])
Object = getAt(Map,Object)
Object = getAt(List,Integer)
List = getAt(List,int[])
Double = getAt(double[],Integer)
double[] = getAt(double[],int[])
float[] = getAt(float[],int[])
Float = getAt(float[],Integer)
void = putAt(int[],Integer,Integer)
void = putAt(int[],int[],int[])
void = putAt(float[],int[],float[])
void = putAt(float[],Integer,Float)
void = putAt(double[],int[],double[])
void = putAt(double[],Integer,Double)
void = putAt(List,int[],int[])
void = putAt(List,Integer,Integer)
void = putAt(Map,Object,Object)
void = putAt(Map,Object[],Map)
void = putAt(Map,Object[],Object[])
float[] = power(float[],Float)
Float = power(Float,Float)
Double = power(Double,Double)
double[] = power(double[],Double)
int[] = range(Integer,Integer,Integer)
int[] = range(Integer,Integer)
double[] = range(Double,Double,Double)
double[] = range(Double,Double)
char[] = range(Character,Character)

// Casting Syntax
castObjectType = (TargetType)originalType;

Double = cast(int)
Double = cast(float)
Double = cast(double)
double[] = cast(float[])
Double = cast(Integer)
Double = cast(Float)
double[] = cast(int[])
Object[] = cast(double[])
List = cast(double[])
List = cast(float[])
List = cast(int[])
List = cast(Object[])
Object[] = cast(int[])
Integer = cast(int)
float[] = cast(int[])
Float = cast(Integer)
Float = cast(int)
Float = cast(float)
Object[] = cast(float[])
Enhanced Index Access for Arrays, List, and Maps

In the operator overlading table notice the [] "indexing" operator. This is mapped to a "getAt" function or a "putAt" function depending on whether the target object is on the left hand side of an equals sign. This is used to implement enhanced getter and setter behavior for Arrays, Lists, and Maps.

// Normal array indexing works as usual

x2 = doubleArray[2];
doubleArray[2] = x2+1;

// You can also work on sub-arrays by providing array of indices

subsetArray = doubleArray[ [2,3,4] ];     // gets elements 2,3,4 from array named doubleArray.  "getAt" operation.

doubleArray[ [2,3,4] ] = subsetArray+1;   // adds 1 to subset, sets elements 2,3,4 of array.  "putAt" operation.

// This type of indexing works equally well on List and Map data types

// Lists
element2 = myList[2];       // gets element 2 from List

subList = myList[ [2:5] ];  // gets elements 2 to 5 inclusive

myList[ [2:5] ] = subList;  // sets elements 2 to 5 inclusive

// Maps
entry2 = myMap["two"];                       // equivalent to Java syntax: myMap.get("two").  "getAt" operator for map

subMap = myMap[ ["two","three","four"] ];    // get sub-map,  syntax not available in Java

myMap[ ["two","three","four"] ] = subMap;    // assign entries from other map, syntax not available in Java

myMap[ ["two","three","four"] ] = [2,3,4];   // assign entries from other array or list, syntax not available in Java

Extended Methods/Functions

BeanShell 2.2 supports something called Extended Methods. An extended method in BeanShell is a static function that acts or appears as if it were an instance method of a class. Any static method can be used as if it were an instance method of the first argument type. Extended methods (and operator overloads) can only be defined in Java code. They cannot be defined in script. Below is an example of how to define an extended method in Java code, and use it in BeanShell script.

// In Java code define class Foo and Bar
class Foo {

 * Declare static method sayHello
 * Must mark it as an extended method using the @Extension annotation
  public static void sayHello(Bar bar, String lastName) {
     print("Hello " + bar.firstName + " " + lastName);


// In Java code define class Bar
class Bar {
  String firstName;

When you invoke BeanShell, the Java classes above must be part of your classpath

// In BeanShell Script, use the Java classes as follows
import Foo;
import Bar;

bar = new Bar();
bar.firstName = "Scott";

// Invoke sayHello() as if it were an instance method of Bar

// Extended methods can also be invoked as if they were static functions
sayHello(bar, "Stevenson");

When you invoke an instance method in BeanShell, it first looks to see if there is an actual instance method defined for the target class and invokes that if present. If it cannot find an actual instance method, BeanShell will look for a static method on any imported class where the first argument type is compatible with the target object. If a method is found it is invoked. The target object is passed into the function as the first argument, and any additional arguments are provided in the argument list to the instance function.

Extended methods must be marked using the @Extension annotation. This is unfortunate because it creates a dependency on BeanShell in the target source code, but without this nearly every static method would be considered an extended method. I originally tried not using this annotation tag and the results were not good. If you do not want to make your target code dependent on BeanShell, an alternative is to simply copy the source for this annotation and place it inside your own source code folder. The bare bones definition of this annotation is listed below.

// Create a package and source file for Extension.java in your source tree
// Copy and paste this code to the file
package bsh.operators;

import java.lang.annotation.*;

public @interface Extension {


Out of the box, BeanShell defines only a few extended methods for mathematical operations. The classes that implement these are imported by default in new instances of a NameSpace or Interpreter. The API javadocs for these are part of the bsh.operators.standard package. They are summarized below.

double[] = cos(double[]); // cosine function (radians)
double[] = cosd(double[]); // cosine function (degrees)
double[] = sin(double[]); // etc.
double[] = sind(double[])
double[] = tan(double[])
double[] = tand(double[])
double[] = toDegrees(double[])
double[] = toRadians(double[])
Double = cos(Double)
Double = cosd(Double)
Double = sin(Double)
Double = sind(Double)
Double = tan(Double)
Double = tand(Double)
Double = toDegrees(Double)
Double = toRadians(Double)

// These methods also support integer and float types by vurtue of automatic casting.

How To Define Your Own Operator Overloads and Extended Functions

The previous section showed how to define your own extended function for use in BeanShell. Operator overloading functions, including casting and indexing, work the same way except there is no need to mark them with an annotation tag. To overload an operator, you provide a static Java function that follows the naming convention listed for overloaded operators. The following example shows how to implement overloads for + and -.

We first create two classes that represent a 3-Dimensional Point with coordinates x, y, z, and a Vector with direction x, y, z. These must be Java classes, not scripted classes, and be part of the classpath in BeanShell.

// Define/provide Point class as Java
public class Point {
  public double x;
  public double y;
  public double z;

  // Overload + operator
  public static Point plus(Point p, Vector v) {
     Point r = new Point();
     r.x = p.x+v.x;
     r.y = p.y+v.y;
     r.z = p.z+v.z;
     return r;
  // Overload - operator
  public static Point minus(Point p, Vector v) {
     Point r = new Point();
     r.x = p.x-v.x;
     r.y = p.y-v.y;
     r.z = p.z-v.z;
     return r;
  // Overload - operator
  public static Vector minus(Point p1, Point p2) {
     Vector r = new Vector();
     r.x = p1.x-p2.x;
     r.y = p1.y-p2.y;
     r.z = p1.z-p2.z;
     return r;


// Define/provide Vector class as Java
public class Vector {
  public double x;
  public double y;
  public double z;

  // Overload + operator
  public static Vector plus(Vector v1, Vector v2) {
     Vector r = new Vector();
     r.x = v1.x+v2.x;
     r.y = v1.y+v2.y;
     r.z = v1.z+v2.z;
     return r;
  // Overload - operator
  public static Vector minus(Vector v1, Vector v2) {
     Vector r = new Vector();
     r.x = v1.x-v2.x;
     r.y = v1.y-v2.y;
     r.z = v1.z-v2.z;
     return r;

Run BeanShell and include the above Java classes in your classpath.  The classes must then be
imported into the script/scope where you want to use them.  To define the operator overloads,
it is not good enough to import the package that contains the classes, the specific classes that
define the overloading functions must be imported explicitly.

// BeanShell script
import Point;
import Vector;

p1 = new Point();
p2 = new Point();
p3 = new Point();

v1 = p2-p1;  // Subtracting two points creates a Vector defining the distance and direction between them.
p4 = p3+v1;  // Add that vector to another point creates a new Point at the offset location.

Reply | Threaded
Open this post in threaded view
Report Content as Inappropriate

Re: BeanShell 2.2b0 with Enhanced Syntax

Where is the code with the enhanced syntax checked in?

I checked out http://beanshell2.googlecode.com/svn/trunk/ but it doesn't appear to contain any of the enhancements Scott mentioned.

BeanShell 2.2.0 - http://code.google.com/p/beanshell2
bsh % intArray = [1,2,3,4,5];
// Error: Parser Error: Parse error at line 1, column 12.  Encountered: [
bsh % // Error: Parser Error: Parse error at line 1, column 2.  Encountered: ,
bsh % // Error: Parser Error: Parse error at line 1, column 2.  Encountered: ,
bsh % // Error: Parser Error: Parse error at line 1, column 2.  Encountered: ,
bsh % // Error: Parser Error: Parse error at line 1, column 2.  Encountered: ,
bsh % // Error: Parser Error: Parse error at line 1, column 2.  Encountered: ]

Is it on a branch not merged with the trunk?