TechHui

Hawaiʻi's Technology Community

Hacking BeanShell with JavaCC (Continued)

We will ease into the second part of this series (see Hacking BeanShell with JavaCC - Building a Language, Part 1) with a relatively simple task: implementing a "<>" equivalence operator. This operator calls .equals on its operands and handles nulls on either side. The first step is adding the operator to the JJTree grammar. We will also add a not equivalent operator to be consistent with equals and not equals. The first step is to add the operators to the TOKEN : /* OPERATORS */ block which starts on line 377 of bsh.jjt. Just under the equals operator definition (| < EQ: "==" >) add:
| < EQUIVALENT: "<>" >
| < NOT_EQUIVALENT: "!<>" >
The easiest way to plug these operators into the grammar is to add them to the EqualityExpression(). We could also define a separate EquivalenceExpression() but that would require additional modifications to the grammar and in either case the BinaryExpression node is used to evaluate the expression. The new EqualityExpression() reads:
void EqualityExpression() :
{ Token t = null; }
{
InstanceOfExpression() (
( t= "==" | t= "!=" | t="<>" | t="!<>")
InstanceOfExpression()
{ jjtThis.kind = t.kind; } #BinaryExpression(2)
)*
}
To implement the evaluation we need to modify BSHBinaryExpression.java. First we add a utility method to perform the test:
/**
* Checks for equivalence (.equals), handles nulls
*/
private static boolean equals(Object o1, Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
}
Now we add the following section to the switch statement in the eval method:
case EQUIVALENT:
return equals(Primitive.unwrap(lhs), Primitive.unwrap(rhs));

case NOT_EQUIVALENT:
return !equals(Primitive.unwrap(lhs), Primitive.unwrap(rhs));
Note the Primitive.unwrap(...) calls in the code above. BeanShell requires a primitive wrapper class to distinguish primitives from their object counterparts (ex. int from Integer.) When performing operations on operands its important to call unwrap(...) to coerce any Primitive wrappers to regular Java wrappers. In this case, it ensures that an instance of bsh.Primitive representing an int will test true when compared to a java.lang.Integer with the same value. Now we are ready to test our new operator. Run the "compile" ant target and fire up the interpreter (bsh.Interpreter.) Here are a few tests:
bx> Point a = new Point(5,5);
bx> Point b = new Point(5,5);
bx> print(a==b);
false
bx> print(a<>b);
true
bx> print(null<>b);
false
bx> print(a<>null);
false
bx> print(null<>null);
true
bx> print(5<>new Integer(5));
true
As you can see, the operator correctly tests equivalence and handles nulls safely. Next we will implement named arguments. Named arguments are a popular feature of languages like Python and Groovy. In the interest of brevity, we will support them only for constructors and use them to set properties. This functionality could easily be extended to methods. Example usage:
JButton button = new JButton("Hello", background:Color.white);
In this example the button's constructor is called with a single string argument and the background property is set to white. In languages like Python and Groovy keyword arguments can only occur at the end of an argument list (after any positional arguments.) To keep things simple, we will allow positional and keyword arguments to occur in any order. Keyword arguments will be consumed prior to evaluating the positional arguments so method resolution for overridden methods will remain unaffected. We are doing this to avoid an overly complex pattern for matching method invocations. The grammar changes occur in the argument list. Currently the argument list pattern looks like this:
void ArgumentList() :
{ }
{
( Expression() )
( "," ( Expression() ) )*
}
Here is the expanded version taking into account keyword arguments:
void ArgumentList() :
{ }
{
( Argument() )
( "," ( Argument() ) )*
}

void Argument() :
{ }
{
LOOKAHEAD( ":" )
KeywordArgument() | Expression()
}

void KeywordArgument() #KeywordArgument :
{ Token t; }
{
t= { jjtThis.name = t.image; } ":" Expression()
}
The LOOKAHEAD( ":" ) is required because both Expression() and KeywordExpression() can start with an IDENTIFIER token. We need to tell the parser to look ahead for ":" to disambiguate. Notice we have a new node type: KeywordArgument. This node evals to a simple data structure defined in BSHKeywordArgument.java:
class BSHKeywordArgument extends SimpleNode {

public String name;

BSHKeywordArgument(int id) { super(id); }

public Object eval(CallStack callstack , Interpreter interpreter)
throws EvalError {

SimpleNode node =((SimpleNode)jjtGetChild(0));
Object value = node.eval(callstack, interpreter);
return new KeywordArgument(name, value);
}

static class KeywordArgument {
private String key;
private Object value;

KeywordArgument(String key, Object value) {
this.key = key;
this.value = value;
}

String getKey() { return key; }
Object getValue() { return value; }
}
}
Finally we need to add the property setting functionality to the BSHAllocationExpression class. This is the class that handles constructors. In the objectAllocation() method called by eval() we add the following code after the object is instantiated:
Map keyArgs = argumentsNode
.getKeywordArguments( callstack, interpreter );
if(!keyArgs.isEmpty()) {
try {
setProperties(newObj, keyArgs);
} catch (ReflectError e) {
throw new EvalError("Constructor error: " + e.getMessage(),
this, callstack );
} catch (UtilEvalError e) {
throw e.toEvalError( this, callstack );
}
}
The setProperties() method looks like this:
private void setProperties(Object newObj, Map keyArgs)
throws ReflectError, UtilEvalError {

for(Map.Entry entry:keyArgs.entrySet()) {
Reflect.setObjectProperty(newObj, (String)entry.getKey(),
entry.getValue());
}
}
Thats it! Run the compile ant target and fire up the interpreter. Here are some tests:
frame(new JButton("Aloha"));
frame(new JButton(background:Color.yellow));
frame(new JButton("Aloha", background:Color.yellow));
Thats it for today. I hope this helps those looking to add features or build their own domain specific languages to get started with JavaCC and JJTree. As always, comments, questions and creative insults are welcome. bx-.2beta.jar
bx-.2beta-src.zip
Ikayzo - Design • Build • Localize | Web • Desktop • Mobile

Views: 954

Comment

You need to be a member of TechHui to add comments!

Join TechHui

Comment by Cameron Souza on March 16, 2011 at 4:08pm
I've always wanted to add features to the languages I use. DSLs are also very interesting to me. I hope to see a part 3.

Sponsors

web design, web development, localization

© 2024   Created by Daniel Leuck.   Powered by

Badges  |  Report an Issue  |  Terms of Service