Imported Upstream version 1.6.0
[platform/upstream/augeas.git] / src / pathx.c
index 8d8dbbb..bebbf78 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * pathx.c: handling path expressions
  *
- * Copyright (C) 2007-2011 David Lutterkort
+ * Copyright (C) 2007-2015 David Lutterkort
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -184,7 +184,7 @@ struct value {
     enum type tag;
     union {
         struct nodeset  *nodeset;     /* T_NODESET */
-        int              number;      /* T_NUMBER  */
+        int64_t          number;      /* T_NUMBER  */
         char            *string;      /* T_STRING  */
         bool             boolval;     /* T_BOOLEAN */
         struct regexp   *regexp;      /* T_REGEXP  */
@@ -495,11 +495,14 @@ static struct nodeset *make_nodeset(struct state *state) {
     return result;
 }
 
+/* Add NODE to NS if it is not in NS yet. This relies on the flag
+ * NODE->ADDED and care must be taken that NS_CLEAR_ADDED is called on NS
+ * as soon as we are done adding nodes to it.
+ */
 static void ns_add(struct nodeset *ns, struct tree *node,
                    struct state *state) {
-    for (int i=0; i < ns->used; i++)
-        if (ns->nodes[i] == node)
-            return;
+    if (node->added)
+        return;
     if (ns->used >= ns->size) {
         size_t size = 2 * ns->size;
         if (size < 10) size = 10;
@@ -508,9 +511,15 @@ static void ns_add(struct nodeset *ns, struct tree *node,
         ns->size = size;
     }
     ns->nodes[ns->used] = node;
+    node->added = 1;
     ns->used += 1;
 }
 
+static void ns_clear_added(struct nodeset *ns) {
+    for (int i=0; i < ns->used; i++)
+        ns->nodes[i]->added = 0;
+}
+
 static struct nodeset *
 clone_nodeset(struct nodeset *ns, struct state *state)
 {
@@ -1003,10 +1012,13 @@ static void eval_union(struct state *state) {
     RET_ON_ERROR;
     for (int i=0; i < r->nodeset->used; i++) {
         ns_add(res, r->nodeset->nodes[i], state);
-        RET_ON_ERROR;
+        if (HAS_ERROR(state))
+            goto error;
     }
     state->value_pool[vind].nodeset = res;
     push_value(vind, state);
+ error:
+    ns_clear_added(res);
 }
 
 static void eval_concat_string(struct state *state) {
@@ -1146,10 +1158,14 @@ static bool eval_pred(struct expr *expr, struct state *state) {
     }
 }
 
-static void ns_remove(struct nodeset *ns, int ind) {
-    memmove(ns->nodes + ind, ns->nodes + ind+1,
-            sizeof(ns->nodes[0]) * (ns->used - (ind+1)));
-    ns->used -= 1;
+/* Remove COUNT successive entries from NS. The first entry to remove is at
+   IND */
+static void ns_remove(struct nodeset *ns, int ind, int count) {
+    if (count < 1)
+        return;
+    memmove(ns->nodes + ind, ns->nodes + ind+count,
+            sizeof(ns->nodes[0]) * (ns->used - (ind+count)));
+    ns->used -= count;
 }
 
 /*
@@ -1165,18 +1181,34 @@ static void ns_filter(struct nodeset *ns, struct pred *predicates,
     uint old_ctx_pos = state->ctx_pos;
 
     for (int p=0; p < predicates->nexpr; p++) {
+        int first_bad = -1;  /* The index of the first non-matching node */
         state->ctx_len = ns->used;
         state->ctx_pos = 1;
         for (int i=0; i < ns->used; state->ctx_pos++) {
             state->ctx = ns->nodes[i];
             bool match = eval_pred(predicates->exprs[p], state);
             RET_ON_ERROR;
+            /* We remove non-matching nodes from NS in batches; this logic
+             * makes sure that we only call ns_remove at the end of a run
+             * of non-matching nodes
+             */
             if (match) {
-                i+=1;
+                if (first_bad >= 0) {
+                    ns_remove(ns, first_bad, i - first_bad);
+                    i = first_bad + 1;
+                } else {
+                    i += 1;
+                }
+                first_bad = -1;
             } else {
-                ns_remove(ns, i);
+                if (first_bad == -1)
+                    first_bad = i;
+                i += 1;
             }
         }
+        if (first_bad >= 0) {
+            ns_remove(ns, first_bad, ns->used - first_bad);
+        }
     }
 
     state->ctx = old_ctx;
@@ -1184,6 +1216,40 @@ static void ns_filter(struct nodeset *ns, struct pred *predicates,
     state->ctx_len = old_ctx_len;
 }
 
+/* Return true if PRED is solely the predicate '[n]' as in 'foo[17]' */
+static bool position_pred(struct pred *pred) {
+    return pred != NULL &&
+        pred->nexpr == 1 &&
+        pred->exprs[0]->tag == E_VALUE &&
+        pred->exprs[0]->type == T_NUMBER;
+}
+
+/* Return the tree node at the position implied by STEP->PREDICATES. It is
+   assumed and required that STEP->PREDICATES is actually a
+   POSITION_PRED.
+
+   This method hand-optimizes the important case of a path expression like
+   'service[42]'
+*/
+static struct tree *position_filter(struct nodeset *ns,
+                                    struct step *step,
+                                    struct state *state) {
+    int value_ind = step->predicates->exprs[0]->value_ind;
+    int number = state->value_pool[value_ind].number;
+
+    int pos = 1;
+    for (int i=0; i < ns->used; i++) {
+        for (struct tree *node = step_first(step, ns->nodes[i]);
+             node != NULL;
+             node = step_next(step, ns->nodes[i], node), pos++) {
+            if (pos == number)
+                return node;
+        }
+    }
+
+    return NULL;
+}
+
 /* Return an array of nodesets, one for each step in the locpath.
  *
  * On return, (*NS)[0] will contain state->ctx, and (*NS)[*MAXNS] will
@@ -1217,9 +1283,11 @@ static void ns_from_locpath(struct locpath *lp, uint *maxns,
         struct tree *root_tree;
         root_tree = step_root(first_step, state->ctx, state->root_ctx);
         ns_add((*ns)[0], root_tree, state);
+        ns_clear_added((*ns)[0]);
     } else {
         for (int i=0; i < root->used; i++)
             ns_add((*ns)[0], root->nodes[i], state);
+        ns_clear_added((*ns)[0]);
     }
 
     if (HAS_ERROR(state))
@@ -1229,15 +1297,25 @@ static void ns_from_locpath(struct locpath *lp, uint *maxns,
     list_for_each(step, lp->steps) {
         struct nodeset *work = (*ns)[cur_ns];
         struct nodeset *next = (*ns)[cur_ns + 1];
-        for (int i=0; i < work->used; i++) {
-            for (struct tree *node = step_first(step, work->nodes[i]);
-                 node != NULL;
-                 node = step_next(step, work->nodes[i], node))
+        if (position_pred(step->predicates)) {
+            struct tree *node = position_filter(work, step, state);
+            if (node) {
                 ns_add(next, node, state);
+                ns_clear_added(next);
+            }
+        } else {
+            for (int i=0; i < work->used; i++) {
+                for (struct tree *node = step_first(step, work->nodes[i]);
+                     node != NULL;
+                     node = step_next(step, work->nodes[i], node)) {
+                    ns_add(next, node, state);
+                }
+            }
+            ns_clear_added(next);
+            ns_filter(next, step->predicates, state);
+            if (HAS_ERROR(state))
+                goto error;
         }
-        ns_filter(next, step->predicates, state);
-        if (HAS_ERROR(state))
-            goto error;
         cur_ns += 1;
     }
 
@@ -2468,7 +2546,11 @@ int pathx_parse(const struct tree *tree,
  *************************************************************************/
 
 static bool step_matches(struct step *step, struct tree *tree) {
-    return (step->name == NULL || streqx(step->name, tree->label));
+    if (step->name == NULL) {
+        return step->axis == ROOT || tree->label != NULL;
+    } else {
+        return streqx(step->name, tree->label);
+    }
 }
 
 static struct tree *tree_prev(struct tree *pos) {
@@ -2937,7 +3019,7 @@ void pathx_symtab_remove_descendants(struct pathx_symtab *symtab,
             while (t != t->parent && t != tree)
                 t = t->parent;
             if (t == tree)
-                ns_remove(ns, i);
+                ns_remove(ns, i, 1);
             else
                 i += 1;
         }