Documented gel.* packages.

git-svn-id: http://google-refine.googlecode.com/svn/trunk@333 7d457c2a-affb-35e4-300a-418c747d4874
This commit is contained in:
David Huynh 2010-03-22 05:44:35 +00:00
parent 60dd7eab82
commit ec0110d65b
16 changed files with 135 additions and 48 deletions

View File

@ -91,6 +91,18 @@ public class ExpressionUtils {
new EvalError(v.getClass().getSimpleName() + " value not storable"); new EvalError(v.getClass().getSimpleName() + " value not storable");
} }
static public boolean isArray(Object v) {
return v != null && v.getClass().isArray();
}
static public boolean isArrayOrCollection(Object v) {
return v != null && (v.getClass().isArray() || v instanceof Collection<?>);
}
static public boolean isArrayOrList(Object v) {
return v != null && (v.getClass().isArray() || v instanceof List<?>);
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static public List<Object> toObjectList(Object v) { static public List<Object> toObjectList(Object v) {
return (List<Object>) v; return (List<Object>) v;

View File

@ -5,6 +5,11 @@ import java.util.Properties;
import com.metaweb.gridworks.Jsonizable; import com.metaweb.gridworks.Jsonizable;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
/**
* Interface of GEL controls such as if, forEach, forNonBlank, with. A control can
* decide which part of the code to execute and can affect the environment bindings.
* Functions, on the other hand, can't do either.
*/
public interface Control extends Jsonizable { public interface Control extends Jsonizable {
public Object call(Properties bindings, Evaluable[] args); public Object call(Properties bindings, Evaluable[] args);

View File

@ -4,6 +4,10 @@ import java.util.Properties;
import com.metaweb.gridworks.Jsonizable; import com.metaweb.gridworks.Jsonizable;
/**
* Interface for functions. When a function is called, its arguments have already
* been evaluated down into non-error values.
*/
public interface Function extends Jsonizable { public interface Function extends Jsonizable {
public Object call(Properties bindings, Object[] args); public Object call(Properties bindings, Object[] args);
} }

View File

@ -18,7 +18,7 @@ import com.metaweb.gridworks.gel.ast.OperatorCallExpr;
import com.metaweb.gridworks.gel.ast.VariableExpr; import com.metaweb.gridworks.gel.ast.VariableExpr;
public class Parser { public class Parser {
protected Scanner _scanner; protected Scanner _scanner;
protected Token _token; protected Token _token;
protected Evaluable _root; protected Evaluable _root;
@ -47,6 +47,10 @@ public class Parser {
return new ParsingException("Parsing error at offset " + index + ": " + desc); return new ParsingException("Parsing error at offset " + index + ": " + desc);
} }
/**
* <expression> := <sub-expression>
* | <expression> [ "<" "<=" ">" ">=" "==" "!=" ] <sub-expression>
*/
protected Evaluable parseExpression() throws ParsingException { protected Evaluable parseExpression() throws ParsingException {
Evaluable sub = parseSubExpression(); Evaluable sub = parseSubExpression();
@ -66,6 +70,10 @@ public class Parser {
return sub; return sub;
} }
/**
* <sub-expression> := <term>
* | <sub-expression> [ "+" "-" ] <term>
*/
protected Evaluable parseSubExpression() throws ParsingException { protected Evaluable parseSubExpression() throws ParsingException {
Evaluable sub = parseTerm(); Evaluable sub = parseTerm();
@ -85,6 +93,10 @@ public class Parser {
return sub; return sub;
} }
/**
* <term> := <factor>
* | <term> [ "*" "/" ] <factor>
*/
protected Evaluable parseTerm() throws ParsingException { protected Evaluable parseTerm() throws ParsingException {
Evaluable factor = parseFactor(); Evaluable factor = parseFactor();
@ -104,6 +116,17 @@ public class Parser {
return factor; return factor;
} }
/**
* <term> := <term-start> ( <path-segment> )*
* <term-start> :=
* <string> | <number> | - <number> | <regex> | <identifier> |
* <identifier> ( <expression-list> )
*
* <path-segment> := "[" <expression-list> "]"
* | "." <identifier>
* | "." <identifier> "(" <expression-list> ")"
*
*/
protected Evaluable parseFactor() throws ParsingException { protected Evaluable parseFactor() throws ParsingException {
if (_token == null) { if (_token == null) {
throw makeException("Expecting something more at end of expression"); throw makeException("Expecting something more at end of expression");
@ -214,6 +237,11 @@ public class Parser {
return eval; return eval;
} }
/**
* <expression-list> := <empty>
* | <expression> ( "," <expression> )*
*
*/
protected List<Evaluable> parseExpressionList(String closingDelimiter) throws ParsingException { protected List<Evaluable> parseExpressionList(String closingDelimiter) throws ParsingException {
List<Evaluable> l = new LinkedList<Evaluable>(); List<Evaluable> l = new LinkedList<Evaluable>();

View File

@ -14,8 +14,8 @@ public class Scanner {
static public class Token { static public class Token {
final public int start; final public int start;
final public int end; final public int end;
final public TokenType type; final public TokenType type;
final public String text; final public String text;
Token(int start, int end, TokenType type, String text) { Token(int start, int end, TokenType type, String text) {
this.start = start; this.start = start;
@ -26,7 +26,7 @@ public class Scanner {
} }
static public class ErrorToken extends Token { static public class ErrorToken extends Token {
final public String detail; // error detail final public String detail; // error detail
public ErrorToken(int start, int end, String text, String detail) { public ErrorToken(int start, int end, String text, String detail) {
super(start, end, TokenType.Error, text); super(start, end, TokenType.Error, text);
@ -52,9 +52,9 @@ public class Scanner {
} }
} }
protected String _text; protected String _text; // input text to tokenize
protected int _index; protected int _index; // index of the next character to process
protected int _limit; protected int _limit; // process up to this index
public Scanner(String s) { public Scanner(String s) {
this(s, 0, s.length()); this(s, 0, s.length());
@ -70,6 +70,16 @@ public class Scanner {
return _index; return _index;
} }
/**
* The regexPossible flag is used by the parser to hint the scanner what to do
* when it encounters a slash. Since the divide operator / and the opening
* delimiter of a regex literal are the same, but divide operators and regex
* literals can't occur at the same place in an expression, this flag is a cheap
* way to distinguish the two without having to look ahead.
*
* @param regexPossible
* @return
*/
public Token next(boolean regexPossible) { public Token next(boolean regexPossible) {
// skip whitespace // skip whitespace
while (_index < _limit && Character.isWhitespace(_text.charAt(_index))) { while (_index < _limit && Character.isWhitespace(_text.charAt(_index))) {

View File

@ -5,6 +5,9 @@ import java.util.Properties;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.gel.Control; import com.metaweb.gridworks.gel.Control;
/**
* An abstract syntax tree node encapsulating a control call, such as "if".
*/
public class ControlCallExpr implements Evaluable { public class ControlCallExpr implements Evaluable {
final protected Evaluable[] _args; final protected Evaluable[] _args;
final protected Control _control; final protected Control _control;

View File

@ -7,6 +7,11 @@ import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.ExpressionUtils; import com.metaweb.gridworks.expr.ExpressionUtils;
import com.metaweb.gridworks.expr.HasFields; import com.metaweb.gridworks.expr.HasFields;
/**
* An abstract syntax tree node encapsulating a field accessor,
* e.g., "cell.value" is accessing the field named "value" on the
* variable called "cell".
*/
public class FieldAccessorExpr implements Evaluable { public class FieldAccessorExpr implements Evaluable {
final protected Evaluable _inner; final protected Evaluable _inner;
final protected String _fieldName; final protected String _fieldName;
@ -19,7 +24,7 @@ public class FieldAccessorExpr implements Evaluable {
public Object evaluate(Properties bindings) { public Object evaluate(Properties bindings) {
Object o = _inner.evaluate(bindings); Object o = _inner.evaluate(bindings);
if (ExpressionUtils.isError(o)) { if (ExpressionUtils.isError(o)) {
return o; return o; // bubble the error up
} else if (o == null) { } else if (o == null) {
return new EvalError("Cannot retrieve field from null"); return new EvalError("Cannot retrieve field from null");
} else if (o instanceof HasFields) { } else if (o instanceof HasFields) {

View File

@ -6,6 +6,12 @@ import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.ExpressionUtils; import com.metaweb.gridworks.expr.ExpressionUtils;
import com.metaweb.gridworks.gel.Function; import com.metaweb.gridworks.gel.Function;
/**
* An abstract syntax tree node encapsulating a function call. The function's
* arguments are all evaluated down to values before the function is applied.
* If any argument is an error, the function is not applied, and the error is
* the result of the expression.
*/
public class FunctionCallExpr implements Evaluable { public class FunctionCallExpr implements Evaluable {
final protected Evaluable[] _args; final protected Evaluable[] _args;
final protected Function _function; final protected Function _function;
@ -20,7 +26,7 @@ public class FunctionCallExpr implements Evaluable {
for (int i = 0; i < _args.length; i++) { for (int i = 0; i < _args.length; i++) {
Object v = _args[i].evaluate(bindings); Object v = _args[i].evaluate(bindings);
if (ExpressionUtils.isError(v)) { if (ExpressionUtils.isError(v)) {
return v; return v; // bubble up the error
} }
args[i] = v; args[i] = v;
} }

View File

@ -6,6 +6,9 @@ import org.json.JSONObject;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
/**
* An abstract syntax tree node encapsulating a literal value.
*/
public class LiteralExpr implements Evaluable { public class LiteralExpr implements Evaluable {
final protected Object _value; final protected Object _value;

View File

@ -5,6 +5,9 @@ import java.util.Properties;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
import com.metaweb.gridworks.expr.ExpressionUtils; import com.metaweb.gridworks.expr.ExpressionUtils;
/**
* An abstract syntax tree node encapsulating an operator call, such as "+".
*/
public class OperatorCallExpr implements Evaluable { public class OperatorCallExpr implements Evaluable {
final protected Evaluable[] _args; final protected Evaluable[] _args;
final protected String _op; final protected String _op;

View File

@ -4,6 +4,9 @@ import java.util.Properties;
import com.metaweb.gridworks.expr.Evaluable; import com.metaweb.gridworks.expr.Evaluable;
/**
* An abstract syntax tree node encapsulating the retrieval of a variable's content.
*/
public class VariableExpr implements Evaluable { public class VariableExpr implements Evaluable {
final protected String _name; final protected String _name;

View File

@ -2,7 +2,6 @@ package com.metaweb.gridworks.gel.controls;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
@ -21,7 +20,8 @@ public class ForEach implements Control {
if (args.length != 3) { if (args.length != 3) {
return ControlFunctionRegistry.getControlName(this) + " expects 3 arguments"; return ControlFunctionRegistry.getControlName(this) + " expects 3 arguments";
} else if (!(args[1] instanceof VariableExpr)) { } else if (!(args[1] instanceof VariableExpr)) {
return ControlFunctionRegistry.getControlName(this) + " expects second argument to be a variable name"; return ControlFunctionRegistry.getControlName(this) +
" expects second argument to be a variable name";
} }
return null; return null;
} }
@ -30,7 +30,7 @@ public class ForEach implements Control {
Object o = args[0].evaluate(bindings); Object o = args[0].evaluate(bindings);
if (ExpressionUtils.isError(o)) { if (ExpressionUtils.isError(o)) {
return o; return o;
} else if (o == null || (!o.getClass().isArray() && !(o instanceof Iterable<?>))) { } else if (!ExpressionUtils.isArrayOrCollection(o)) {
return new EvalError("First argument to forEach is not an array"); return new EvalError("First argument to forEach is not an array");
} }
@ -40,36 +40,36 @@ public class ForEach implements Control {
try { try {
List<Object> results = null; List<Object> results = null;
if (o.getClass().isArray()) { if (o.getClass().isArray()) {
Object[] values = (Object[]) o; Object[] values = (Object[]) o;
results = new ArrayList<Object>(values.length); results = new ArrayList<Object>(values.length);
for (Object v : values) { for (Object v : values) {
bindings.put(name, v); bindings.put(name, v);
Object r = args[2].evaluate(bindings); Object r = args[2].evaluate(bindings);
results.add(r); results.add(r);
} }
} else { } else {
results = o instanceof Collection<?> ? Collection<Object> collection = ExpressionUtils.toObjectCollection(o);
new ArrayList<Object>(ExpressionUtils.toObjectCollection(o).size()) :
new ArrayList<Object>(); results = new ArrayList<Object>(collection.size());
Iterator<Object> i = ExpressionUtils.toObjectCollection(o).iterator(); for (Object v : collection) {
while (i.hasNext()) { bindings.put(name, v);
Object v = i.next();
Object r = args[2].evaluate(bindings);
bindings.put(name, v);
results.add(r);
Object r = args[2].evaluate(bindings); }
}
results.add(r);
}
}
return results.toArray(); return results.toArray();
} finally { } finally {
/*
* Restore the old value bound to the variable, if any.
*/
if (oldValue != null) { if (oldValue != null) {
bindings.put(name, oldValue); bindings.put(name, oldValue);
} else { } else {

View File

@ -16,7 +16,8 @@ public class ForNonBlank implements Control {
if (args.length != 4) { if (args.length != 4) {
return ControlFunctionRegistry.getControlName(this) + " expects 4 arguments"; return ControlFunctionRegistry.getControlName(this) + " expects 4 arguments";
} else if (!(args[1] instanceof VariableExpr)) { } else if (!(args[1] instanceof VariableExpr)) {
return ControlFunctionRegistry.getControlName(this) + " expects second argument to be a variable name"; return ControlFunctionRegistry.getControlName(this) +
" expects second argument to be a variable name";
} }
return null; return null;
} }
@ -28,12 +29,15 @@ public class ForNonBlank implements Control {
String name = ((VariableExpr) var).getName(); String name = ((VariableExpr) var).getName();
if (ExpressionUtils.isNonBlankData(o)) { if (ExpressionUtils.isNonBlankData(o)) {
Object oldValue = bindings.containsKey(name) ? bindings.get(name) : null; Object oldValue = bindings.get(name);
bindings.put(name, o); bindings.put(name, o);
try { try {
return args[2].evaluate(bindings); return args[2].evaluate(bindings);
} finally { } finally {
/*
* Restore the old value bound to the variable, if any.
*/
if (oldValue != null) { if (oldValue != null) {
bindings.put(name, oldValue); bindings.put(name, oldValue);
} else { } else {

View File

@ -21,7 +21,7 @@ public class If implements Control {
public Object call(Properties bindings, Evaluable[] args) { public Object call(Properties bindings, Evaluable[] args) {
Object o = args[0].evaluate(bindings); Object o = args[0].evaluate(bindings);
if (ExpressionUtils.isError(o)) { if (ExpressionUtils.isError(o)) {
return o; return o; // bubble the error up
} else if (ExpressionUtils.isTrue(o)) { } else if (ExpressionUtils.isTrue(o)) {
return args[1].evaluate(bindings); return args[1].evaluate(bindings);
} else { } else {

View File

@ -27,10 +27,7 @@ abstract class IsTest implements Control {
throws JSONException { throws JSONException {
writer.object(); writer.object();
writer.key("description"); writer.value( writer.key("description"); writer.value(getDescription());
"Evaluates expression o. If it is true, evaluates expression eTrue and returns the result. " +
"Otherwise, evaluates expression eFalse and returns that result instead."
);
writer.key("params"); writer.value("expression o"); writer.key("params"); writer.value("expression o");
writer.key("returns"); writer.value("boolean"); writer.key("returns"); writer.value("boolean");
writer.endObject(); writer.endObject();

View File

@ -15,7 +15,8 @@ public class With implements Control {
if (args.length != 3) { if (args.length != 3) {
return ControlFunctionRegistry.getControlName(this) + " expects 3 arguments"; return ControlFunctionRegistry.getControlName(this) + " expects 3 arguments";
} else if (!(args[1] instanceof VariableExpr)) { } else if (!(args[1] instanceof VariableExpr)) {
return ControlFunctionRegistry.getControlName(this) + " expects second argument to be a variable name"; return ControlFunctionRegistry.getControlName(this) +
" expects second argument to be a variable name";
} }
return null; return null;
} }
@ -34,6 +35,9 @@ public class With implements Control {
return args[2].evaluate(bindings); return args[2].evaluate(bindings);
} finally { } finally {
/*
* Restore the old value bound to the variable, if any.
*/
if (oldValue != null) { if (oldValue != null) {
bindings.put(name, oldValue); bindings.put(name, oldValue);
} else { } else {