Hi Marko

I produced the attached patch which makes all the previous examples
compile (apart from the plain a~b example).

..Steve
Index: src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java
===================================================================
--- src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java      (revision 1490)
+++ src/uk/me/parabola/mkgmap/osmstyle/RuleFileReader.java      Mon Jan 18 
16:19:11 GMT 2010
@@ -25,22 +25,18 @@
 import uk.me.parabola.mkgmap.general.LevelInfo;
 import uk.me.parabola.mkgmap.osmstyle.actions.Action;
 import uk.me.parabola.mkgmap.osmstyle.actions.ActionReader;
+import uk.me.parabola.mkgmap.osmstyle.eval.AndOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.BinaryOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.ExpressionReader;
 import uk.me.parabola.mkgmap.osmstyle.eval.Op;
-import static uk.me.parabola.mkgmap.osmstyle.eval.Op.AND;
-import static uk.me.parabola.mkgmap.osmstyle.eval.Op.EQUALS;
-import static uk.me.parabola.mkgmap.osmstyle.eval.Op.EXISTS;
-import static uk.me.parabola.mkgmap.osmstyle.eval.Op.NOT_EXISTS;
-import static uk.me.parabola.mkgmap.osmstyle.eval.Op.OR;
-import static uk.me.parabola.mkgmap.osmstyle.eval.Op.VALUE;
-import uk.me.parabola.mkgmap.osmstyle.eval.SyntaxException;
-import uk.me.parabola.mkgmap.osmstyle.eval.AndOp;
 import uk.me.parabola.mkgmap.osmstyle.eval.OrOp;
+import uk.me.parabola.mkgmap.osmstyle.eval.SyntaxException;
 import uk.me.parabola.mkgmap.reader.osm.GType;
 import uk.me.parabola.mkgmap.reader.osm.Rule;
 import uk.me.parabola.mkgmap.scan.TokenScanner;
 
+import static uk.me.parabola.mkgmap.osmstyle.eval.Op.*;
+
 /**
  * Read a rules file.  A rules file contains a list of rules and the
  * resulting garmin type, should the rule match.
@@ -115,54 +111,139 @@
        private void saveRule(Op op, List<Action> actions, GType gt) {
                log.info("EXP", op, ", type=", gt);
 
-               // E1 | E2 {type...} is exactly the same as the two rules:
-               // E1 {type...}
-               // E2 {type...}
-               // so just recurse on each term, throwing away the original OR.
-               if (op.isType(OR)) {
-                       saveRule(op.getFirst(), actions, gt);
-                       saveRule(((BinaryOp) op).getSecond(), actions, gt);
-                       return;
-               }
+               //System.out.println("From: " + op);
+               Op op2 = rearrangeExpression(op);
+               //System.out.println("RE: " + op2);
 
-               if (op instanceof BinaryOp) {
-                       optimiseAndSaveBinaryOp((BinaryOp) op, actions, gt);
+               if (op2 instanceof BinaryOp) {
+                       optimiseAndSaveBinaryOp((BinaryOp) op2, actions, gt);
                } else {
-                       optimiseAndSaveOtherOp(op, actions, gt);
-                       //throw new SyntaxException(scanner, "Invalid operation 
'" + op.getType() + "' at top level");
+                       optimiseAndSaveOtherOp(op2, actions, gt);
                }
        }
 
-       private void optimiseAndSaveOtherOp(Op op, List<Action> actions, GType 
gt) {
-               if (op.isType(EXISTS)) {
-                       createAndSaveRule(op.value() + "=*", null, actions, gt);
+       /**
+        * Rearrange the expression so that it is solvable, that is it starts 
with
+        * an EQUALS or an EXISTS.
+        * @param op The expression to be rearranged.
+        * @return An equivelent expression re-arranged so that it starts with 
an
+        * indexable term. If that is not possible then the original expression 
is
+        * returned.
+        */
+       private Op rearrangeExpression(Op op) {
+               if (isFinished(op))
+                       return op;
+
+               if (op.isType(AND)) {
+                       // If the first term is and EQUALS or EXISTS then this 
subtree is
+                       // already solved and we need to do no more.
+                       Op op1 = rearrangeExpression(op.getFirst());
+                       Op op2 = rearrangeExpression(((BinaryOp) 
op).getSecond());
+
+                       if (isSolved(op1)) {
+                               return rearrangeAnd((BinaryOp) op, op1, op2);
+                       } else if (isSolved(op2)) {
+                               return rearrangeAnd((BinaryOp) op, op2, op1);
+                       }
+               }
+
+               return op;
+       }
+
+       /**
+        * Rearrange an AND expression so that it can be executed with indexable
+        * terms at the front.
+        * @param top This will be an AndOp.
+        * @param op1 This is a child of top that is guaranteed to be
+        * solved already.
+        * @param op2 This expression is the other child of top.
+        * @return A re-arranged expression with an indexable term at the 
beginning
+        * or several such expressions ORed together.
+        */
+       private Op rearrangeAnd(BinaryOp top, Op op1, Op op2) {
+               if (isIndexable(op1)) {
+                       top.setFirst(op1);
+                       top.setSecond(op2);
+                       return top;
+               } else if (op1.isType(AND)) {
+                       // If the first term is AND and its first is EQUALS or 
EXIST,
+                       // then make that our first term.
+                       Op first = op1.getFirst();
+                       if (isIndexable(first)) {
+                               top.setFirst(first);
+                               op1.setFirst(op2);
+                               top.setSecond(op1);
+                               return top;
-               } else {
+                       } else {
-                       throw new SyntaxException(scanner, "Invalid operation 
'" + op.getType() + "' at top level");
+                               assert false;
-               }
+                       }
+               } else if (op1.isType(OR)) {
+                       Op first = op1.getFirst();
+                       OrOp orOp = new OrOp();
+
+                       Op topSecond = top.getSecond();
+
+                       AndOp and1 = new AndOp();
+                       and1.setFirst(first);
+                       and1.setSecond(topSecond);
+
+                       AndOp and2 = new AndOp();
+                       Op second = rearrangeExpression(((BinaryOp) 
op1).getSecond());
+                       and2.setFirst(second);
+                       and2.setSecond(topSecond);
+
+                       orOp.setFirst(and1);
+                       orOp.setSecond(and2);
+                       return orOp;
+               } else {
+                       // This shouldn't happen
+                       throw new SyntaxException("X3" + op1.getType());
-       }
+               }
+               return top;
+       }
 
        /**
-        * Translate (a=b|a=c) &amp; d~f
-        * into (a=b&amp;d~f) | (a=c&amp;d~f)
-        * @param first an OrOp that is the operand of an AndOp
-        * @param second the other operand of the AndOp
-        * @param actions list of actions to execute on match
-        * @param gt the Garmin type of the element
+        * True if this operation can be indexed.  It is a plain equality or
+        * Exists operation.
         */
-       private void optimiseAndSaveAndOr(OrOp first, Op second, List<Action> 
actions, GType gt) {
-               // (a=b|a=c) & d~f => (a=b&d~f) | (a=c&d~f) => solved
-               BinaryOp and1 = new AndOp();
-               and1.setFirst(first.getFirst());
-               and1.setSecond(second);
-               optimiseAndSaveBinaryOp(and1, actions, gt);
+       private boolean isIndexable(Op op) {
+               return op.isType(EQUALS) || op.isType(EXISTS);
+       }
 
-               BinaryOp and2 = new AndOp();
-               and2.setFirst(first.getSecond());
-               and2.setSecond(second);
-               optimiseAndSaveBinaryOp(and2, actions, gt);
+       /**
+        * True if this expression is 'solved'.  This means that the first term
+        * is indexable or it is indexable itself.
+        */
+       private boolean isSolved(Op op) {
+               return isIndexable(op) || isIndexable(op.getFirst());
        }
 
        /**
+        * True if there is nothing more that we can do to rearrange this 
expression.
+        * It is either solved or it cannot be solved.
+        */
+       private boolean isFinished(Op op) {
+               if (isSolved(op))
+                       return true;
+               char type = op.getType();
+               switch (type) {
+               case AND:
+               case OR: // possibly not required
+                       return false;
+               default:
+                       return true;
+               }
+       }
+
+       private void optimiseAndSaveOtherOp(Op op, List<Action> actions, GType 
gt) {
+               if (op.isType(EXISTS)) {
+                       createAndSaveRule(op.value() + "=*", null, actions, gt);
+               } else {
+                       throw new SyntaxException(scanner, "Invalid operation 
'" + op.getType() + "' at top level");
+               }
+       }
+
+       /**
         * Optimise the expression tree, extract the primary key and
         * save it as a rule.
         * @param op a binary expression
@@ -195,27 +276,20 @@
                        if (first.isType(EQUALS)) {
                                keystring = first.toString();
                                expr = second;
-                       } else if (second.isType(EQUALS)) {
-                               // Swap the terms and everything will be fine.
-                               keystring = second.toString();
-                               expr = first;
+
                        } else if (first.isType(EXISTS)) {
                                keystring = first.value() + "=*";
                                expr = second;
-                       } else if (second.isType(EXISTS)) {
-                               keystring = second.value() + "=*";
-                               expr = first;
-                       } else if (first.isType(OR)) {
-                               optimiseAndSaveAndOr((OrOp) first, second, 
actions, gt);
-                               return;
-                       } else if (second.isType(OR)) {
-                               optimiseAndSaveAndOr((OrOp) second, first, 
actions, gt);
-                               return;
+
                        } else if (first.isType(NOT_EXISTS)) {
                                throw new SyntaxException(scanner, "Cannot 
start rule with tag!=*");
                        } else {
                                throw new SyntaxException(scanner, "Invalid 
rule file (expr " + op.getType() +')');
                        }
+               } else if (op.isType(OR)) {
+                       saveRule(first, actions, gt);
+                       saveRule(second, actions, gt);
+                       return;
                } else {
                        throw new SyntaxException(scanner, "Invalid operation 
'" + op.getType() + "' at top level");
                }
Index: test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java
===================================================================
--- test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java (revision 1159)
+++ test/uk/me/parabola/mkgmap/osmstyle/RuleFileReaderTest.java Mon Jan 18 
16:19:11 GMT 2010
@@ -26,10 +26,11 @@
 import uk.me.parabola.mkgmap.reader.osm.Rule;
 import uk.me.parabola.mkgmap.reader.osm.Way;
 
-import static org.junit.Assert.*;
 import org.junit.Test;
 
+import static org.junit.Assert.*;
 
+
 public class RuleFileReaderTest {
        /**
         * Test of a file containing a number of different rules, with varying
@@ -58,9 +59,10 @@
                assertEquals("rough footway", "[0x2 level 2]", type.toString());
 
                el.addTag("oneway", "true");
-               rule = ruleMap.get("oneway=true");
+               el.addTag("highway", "primary");
+               rule = ruleMap.get("highway=*");
                type = rule.resolveType(el);
-               assertEquals("oneway footway", "[0x6 level 1]", 
type.toString());
+               assertEquals("oneway primary", "[0x6 level 1]", 
type.toString());
        }
 
        /**
@@ -391,6 +393,32 @@
        }
 
        /**
+        * The main point of this test is to ensure that all the examples 
compile.
+        */
+       @Test
+       public void testComplexRegex() {
+               RuleSet rs = makeRuleSet(
+                               //"a~b       [0x0] # doesn't work\n" +
+                               "a~b & c=d  [0x1]# works\n" +
+                               "a~b & c~d & e=f   [0x2] # doesn't work.\n" +
+                               "(a~b | c~d) & e=f  [0x3]# works\n" +
+                               "(a~b | c~d) & e=f & g=h  [0x4] # doesn't 
work\n" +
+                               "((a~b | c~d) & e=f) & g=h [0x5]# works!\n" +
+                               "e=f & g=h & (a~b | c~'d.*')  [0x6]  # works\n" 
+
+                               "(e=f & g=h) & (a~b | c~'d.*')  [0x7]# doesn't 
work" +
+                               ""
+               );
+
+               Way el = new Way(1);
+               el.addTag("c", "df");
+               el.addTag("g", "h");
+               el.addTag("e", "f");
+
+               GType type = rs.resolveType(el);
+               assertNotNull("matches a rule", type);
+       }
+
+       /**
         * Create a rule set out of a string.  The string is processed
         * as if it were in a file and the levels spec had been set.
         */
_______________________________________________
mkgmap-dev mailing list
[email protected]
http://www.mkgmap.org.uk/mailman/listinfo/mkgmap-dev

Reply via email to