Wednesday, August 27, 2008

How to add a new piece to the console

For several good resaons I wanted to teach the JGrass Console to speak groovy.
Well, since groovy supports java syntax, I thought this would go on the fly, but I was very, very wrong.

Nevertheless the speed of Groovy in handling files and many other things forced me to get my fingers dirty in the console compiler system, an IT magic created in coorperation with Andreas Hamm last year.

So the problems found were basically 3:
1) java blocks without labels are not supported in groovy, because of their ambiguity with closures
2) the direct assignment array construct is not supported: String[] str = {"a", "b"};
3) the Classloader works differently from the one of beanshell, and we don't see the needed classes

So Hamm and I spent an evening together in chat to make me understand how the internals of the console work.
And here I want to share that with you, by showing you how I solved problem 2, which contains also 1.It is not for the weak of heart (also because the code snippets are not well formatted), but believe me, after a starting desperation, I had quite some fun :)

0) identification of the problem

Groovy doesn't support an array creation like:
String[] __arg_v = {"a", "b" };

we need to change it to:
Argument[] __arg_v = new Argument[2];
__arg_v[0] = "a";
__arg_v[1] = "b";


and this should be general also to other types of arrays.

The problem in this case that no Abstract Syntax Tree operator was available to deal with array and therefor a new piece of syntac had to be created.

Step by step this was done like that:

1) add a new enumeration type to eu.hydrologis.jgrass.console.core.runtime.analysis.ASTs.java

In this case I added:

/**
*
* The identifier of an operator of the array keyword for the creation of an
* array e.g., "array[2]; array[0] = string1; array[1] = string2;
*
*
* @see eu.hydrologis.jgrass.console.core.runtime.nodes.AST_while
*/
AST_ARRAY(null, "ARRAY"); //$NON-NLS-1$



2) add a new class in eu.hydrologis.jgrass.console.core.runtime.nodes

ex. AST_array with a Default constructor and a Copyconstruktor

public final class AST_array extends AbstractAST implements AST {

// Construction
/**
*
* Constructs this object.
*
*/
public AST_array() {

super(ASTs.AST_ARRAY.expression(), ASTs.AST_ARRAY.annotation(), ASTs.AST_ARRAY);
} // AST_array

/**
*
* Constructs this object with the specified operands.
*
*
* @param operands - can be null or either a single operand or a list of operands.
*/
public AST_array( AST... operands ) {

super(ASTs.AST_ARRAY.expression(), ASTs.AST_ARRAY.annotation(), ASTs.AST_ARRAY, operands);
} // AST_array
} // AST_array



3) add the grammar to the NativeML4j

Add the Argument array type:
final SYM_type_array __typedef_argument = new SYM_type_array("Argument"); //$NON-NLS-1$

Search for the part in which the proper child is added in the method __native_model.

Before:
Argument[] args = {arg1, arg2}

Created by:
new AST_expression(
new AST_variable_definition(
new AST_type(__typedef_argv.type()),
new AST_identifier(__variable_argv),
new AST_assign_statement(
new AST_block(
__parameter_definition(
symtable,
operator))))),

Now we need an array, which changes things to the following:

new AST_variable_definition(
new AST_type(__typedef_argv.type()),
new AST_identifier(__variable_argv),
new AST_assign_statement(
new AST_array(
__parameter_definition(
symtable,
operator),
new AST_type(
__typedef_argument.type()),
new AST_identifier(__variable_argv))))

note how to the AST_array we pass the normal operators throught __parameter_definition, but also an AST_type, which will tell us at generation time which class to use for the array creation and also the name of the variable needed. The last said is needed to create the different array elements:

varname[0] = ...
varname[1] = ...
...


4) add the grammar to the JavaML4j

Exactly the same way done for grass models, we proceed for java models.

In __java_model I change the expression that was creating arrays the wrong way for groovy:

new AST_block(
__argument_definition(
( APT_argument_definition
)operator.argument_defs()
)
)

and change it with:

new AST_array(
__argument_definition((APT_argument_definition) operator
.argument_defs()),
new AST_type(__typedef_argv.type()),
new AST_identifier(__variable_argv))


The same is needed in __model_input and __model_output.


5) In eu.hydrologis.jgrass.console.core.runtime.compiler.AbstractML4j.java add the case.

In the method generate, crate a case based on the enum of 1) and create the proper generation of code there:


case AST_ARRAY:
/*
* this should be a set of strings (also concatenated) divided by commas
*/

targetCode.append("new "); //$NON-NLS-1$

AST typeChild = null;
AST commaChild = null;
AST variableChild = null;
for( int i = 0; i <> child = op.getChild(i);
if (child.identifier().annotation().equals(ASTs.AST_TYPE.annotation())) {
typeChild = child;
}
if (child.identifier().annotation().equals(ASTs.AST_COMMA.annotation())) {
commaChild = child;
}
if (child.identifier().annotation().equals(ASTs.AST_IDENTIFIER.annotation())) {
variableChild = child;
}
}
if (typeChild != null && commaChild != null && variableChild != null) {
int nums = commaChild.size();
String arrayString = typeChild.expression();
arrayString = arrayString.substring(0, arrayString.length() - 1) + nums + "]";
targetCode.append(arrayString).append(";\n"); //$NON-NLS-1$

// now the arrays
String varName = variableChild.expression();
for( int i = 0; i < j =" 0;"> child = commaChild.getChild(i);
generate(indentCount + 1, child, targetCode);
targetCode.append(";\n"); //$NON-NLS-1$
}
}

break;





The result?
Now the JGrass Console can use Beanshell and Groovy by defining the language as a session setting (in the settings lines that start with # in the script).

But hei, images tell more than thousand words:



Go, closure, go!!!

No comments: