Rev 3408 |
Blame |
Compare with Previous |
Last modification |
View Log
| RSS feed
/*
* Copyright (C) 2013
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
package uk.me.parabola.mkgmap.osmstyle.eval;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.osmstyle.function.FunctionFactory;
import uk.me.parabola.mkgmap.osmstyle.function.GetTagFunction;
import uk.me.parabola.mkgmap.osmstyle.function.StyleFunction;
import uk.me.parabola.mkgmap.reader.osm.FeatureKind;
import uk.me.parabola.mkgmap.scan.SyntaxException;
import uk.me.parabola.mkgmap.scan.TokenScanner;
import uk.me.parabola.mkgmap.scan.WordInfo;
import static uk.me.parabola.mkgmap.osmstyle.eval.NodeType.*;
/**
* Read an expression from a style file.
*/
public class ExpressionReader
{
private static final Logger log =
Logger.
getLogger(ExpressionReader.
class);
private final Stack<Op
> stack =
new Stack<Op
>();
private final Stack<Op
> opStack =
new Stack<Op
>();
private final TokenScanner scanner
;
private final FeatureKind kind
;
private final Set<String> usedTags =
new HashSet<String>();
public ExpressionReader
(TokenScanner scanner, FeatureKind kind
) {
this.
scanner = scanner
;
this.
kind = kind
;
}
/**
* Read the conditions. They are terminated by a '[' or '{' character
* or by end of file.
*/
public Op readConditions
() {
while (!scanner.
isEndOfFile()) {
scanner.
skipSpace();
if (scanner.
checkToken("[") || scanner.
checkToken("{"))
break;
WordInfo wordInfo = scanner.
nextWordWithInfo();
if (isOperation
(wordInfo
)) {
saveOp
(wordInfo.
getText());
} else if (wordInfo.
isQuoted()) {
pushValue
(wordInfo.
getText());
} else if (wordInfo.
getText().
charAt(0) ==
'$') {
String tagname = scanner.
nextWord();
if (tagname.
equals("{")) {
tagname = scanner.
nextWord();
scanner.
validateNext("}");
}
stack.
push(new GetTagFunction
(tagname
));
} else if (scanner.
checkToken("(")) {
// it is a function
// this requires a () after the function name
scanner.
validateNext("(");
scanner.
validateNext(")");
saveFunction
(wordInfo.
getText());
} else {
pushValue
(wordInfo.
getText());
}
}
// Complete building the tree
while (!opStack.
isEmpty())
runOp
(scanner
);
// The stack should contain one entry which is the complete tree
if (stack.
size() !=
1)
throw new SyntaxException
(scanner,
"Stack size is "+stack.
size());
assert stack.
size() ==
1;
Op op = stack.
pop();
if (op
instanceof ValueOp
)
throw new SyntaxException
(scanner,
"Incomplete expression, just a single symbol: " + op
);
return op
;
}
/**
* Is this a token representing an operation?
* @param token The string to test.
* @return True if this looks like an operator.
*/
private boolean isOperation
(WordInfo token
) {
// A quoted word is not an operator eg: '=' is a string.
if (token.
isQuoted())
return false;
// Quick check, operators are 1 or 2 characters long.
String text = token.
getText();
if (text.
length() > 2 || text.
isEmpty())
return false;
// If first character is an operation character then it is an operator
// (or a syntax error)
char first = text.
charAt(0);
String chars =
"&|!=~()><";
return chars.
indexOf(first
) >=
0;
}
/**
* Tags used in all the expressions in this file.
* @return A set of tag names.
*/
public Set<String> getUsedTags
() {
return usedTags
;
}
/**
* An operation is saved on the operation stack. The tree is built
* as operations of different priorities arrive.
*/
private void saveOp
(String value
) {
log.
debug("save op", value
);
if (value.
equals("#")) {
scanner.
skipLine();
return;
}
Op op
;
try {
op = AbstractOp.
createOp(value
);
while (!opStack.
isEmpty() && opStack.
peek().
hasHigherPriority(op
))
runOp
(scanner
);
} catch (SyntaxException e
) {
throw new SyntaxException
(scanner, e.
getRawMessage());
}
if (op.
getType() == CLOSE_PAREN
) {
// Check that there was an opening parenthesis and remove it
if (opStack.
isEmpty() ||
!opStack.
peek().
isType(OPEN_PAREN
))
throw new SyntaxException
(scanner,
"No matching open parenthesis");
opStack.
pop();
} else {
opStack.
push(op
);
}
}
/**
* Combine the operation at the top of its stack with its values.
* @param scanner The token scanner; used for line numbers.
*/
private void runOp
(TokenScanner scanner
) {
Op op = opStack.
pop();
log.
debug("Running op...", op.
getType());
if (op
instanceof BinaryOp
) {
if (stack.
size() < 2) {
throw new SyntaxException
(scanner,
String.
format("Not enough arguments for '%s' operator",
op.
getType().
toSymbol()));
}
Op arg2 = stack.
pop();
Op arg1 = stack.
pop();
if (arg1.
isType(VALUE
) /*&& arg2.isType(VALUE)*/)
arg1 =
new GetTagFunction
(arg1.
getKeyValue());
BinaryOp binaryOp =
(BinaryOp
) op
;
binaryOp.
setFirst(arg1
);
binaryOp.
setSecond(arg2
);
// Deal with the case where you have: a & b=2. The 'a' is a syntax error in this case.
if (op.
isType(OR
) || op.
isType(AND
)) {
if (arg1.
isType(VALUE
) || arg1.
isType(FUNCTION
))
throw new SyntaxException
(scanner,
String.
format("Value '%s' is not part of an expression", arg1
));
if (arg2.
isType(VALUE
) || arg2.
isType(FUNCTION
))
throw new SyntaxException
(scanner,
String.
format("Value '%s' is not part of an expression", arg2
));
} else {
// All binary ops other than OR and AND take two values. A function is a value
// type too.
if (!(arg1.
isType(VALUE
) || arg1.
isType(FUNCTION
))
||
!(arg2.
isType(VALUE
) || arg2.
isType(FUNCTION
)))
{
String msg =
String.
format("Invalid arguments to %s: %s (%s) and %s (%s)",
op.
getType(), arg1.
getType(), arg1, arg2.
getType(), arg2
);
throw new SyntaxException
(scanner, msg
);
}
}
// The combination foo=* is converted to exists(foo).
if (op.
isType(EQUALS
) && arg2.
isType(VALUE
) && ((ValueOp
) arg2
).
isValue("*")) {
log.
debug("convert to EXISTS");
op =
new ExistsOp
();
op.
setFirst(arg1
);
} else if (op.
isType(NOT_EQUALS
) && arg2.
isType(VALUE
) && ((ValueOp
) arg2
).
isValue("*")) {
log.
debug("convert to NOT EXISTS");
op =
new NotExistsOp
();
op.
setFirst(arg1
);
}
} else if (!op.
isType(OPEN_PAREN
)) {
if (stack.
size() < 1)
throw new SyntaxException
(scanner,
String.
format("Missing argument for %s operator",
op.
getType().
toSymbol()));
op.
setFirst(stack.
pop());
}
Op first = op.
getFirst();
if (first ==
null)
throw new SyntaxException
(scanner,
"Invalid expression");
if (first.
isType(FUNCTION
))
usedTags.
add(first.
getKeyValue());
stack.
push(op
);
}
/**
* Lookup a function by its name and check that it is allowed for the kind of features that we
* are reading.
*
* @param functionName A name to look up.
*/
private void saveFunction
(String functionName
) {
StyleFunction function = FunctionFactory.
createFunction(functionName
);
if (function ==
null)
throw new SyntaxException
(String.
format("No function with name '%s()'", functionName
));
// TODO: supportsWay split into supportsPoly{line,gon}, or one function supports(kind)
boolean supported =
false;
switch (kind
) {
case POINT:
if (function.
supportsNode()) supported =
true;
break;
case POLYLINE:
if (function.
supportsWay()) supported =
true;
break;
case POLYGON:
if (function.
supportsWay()) supported =
true;
break;
case RELATION:
if (function.
supportsRelation()) supported =
true;
break;
case ALL:
if (function.
supportsNode() || function.
supportsWay() || function.
supportsRelation()) supported =
true;
break;
}
if (!supported
)
throw new SyntaxException
(String.
format("Function '%s()' not supported for %s", functionName, kind
));
stack.
push(function
);
}
private void pushValue
(String value
) {
stack.
push(new ValueOp
(value
));
}
}