blob: e6d6b12097afb5f7e6ffb07e4c3daf6cdc6bef6b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "parse_tree.h"
#include "qpid/dispatch/alloc.h"
#include "qpid/dispatch/hash.h"
#include "qpid/dispatch/log.h"
#include <inttypes.h>
#include <stdio.h>
// token parsing
// parse a string of tokens separated by a boundary character.
//
// The format of a pattern string is a series of tokens. A token can contain
// any characters but those reserved for wildcards or the boundary character.
//
// TODO(kgiusti): really should create a token parsing qd_iterator_t view.
// Then all this token/token_iterator stuff can be removed...
//
typedef struct token {
const char *begin;
const char *end;
} token_t;
#define TOKEN_LEN(t) ((t).end - (t).begin)
// for iterating over a string of tokens:
typedef struct token_iterator {
const char *separators;
const char *terminator; // end of entire string
token_t token; // token at head of string
char match_1; // match any 1 token
char match_glob; // match 0 or more tokens
} token_iterator_t;
static void token_iterator_init(token_iterator_t *t,
qd_parse_tree_type_t type,
const char *str)
{
switch (type) {
default: // See DISPATCH-1567: shuts up some bogus compiler warnings
assert(false);
// fall through
case QD_PARSE_TREE_ADDRESS:
t->separators = "./"; // accept either for backward compatibility
t->match_1 = '*';
t->match_glob = '#';
break;
case QD_PARSE_TREE_AMQP_0_10:
t->separators = ".";
t->match_1 = '*';
t->match_glob = '#';
break;
case QD_PARSE_TREE_MQTT:
t->separators = "/";
t->match_1 = '+';
t->match_glob = '#';
break;
}
while (*str && strchr(t->separators, *str))
str++; // skip any leading separators
const char *tend = strpbrk(str, t->separators);
t->terminator = str + strlen(str);
t->token.begin = str;
t->token.end = (tend) ? tend : t->terminator;
}
static void token_iterator_next(token_iterator_t *t)
{
if (t->token.end == t->terminator) {
t->token.begin = t->terminator;
} else {
const char *tend;
t->token.begin = t->token.end + 1;
tend = strpbrk(t->token.begin, t->separators);
t->token.end = (tend) ? tend : t->terminator;
}
}
const char address_token_sep[] = "./";
const char *qd_parse_address_token_sep()
{
return address_token_sep;
}
static bool token_iterator_done(const token_iterator_t *t)
{
return t->token.begin == t->terminator;
}
static void token_iterator_pop(token_iterator_t *t, token_t *head)
{
if (head) *head = t->token;
token_iterator_next(t);
}
static bool token_match_str(const token_t *t, const char *str)
{
return (TOKEN_LEN(*t) == strlen(str) && !strncmp(t->begin, str, TOKEN_LEN(*t)));
}
// True if current token is the match one wildcard
static bool token_iterator_is_match_1(const token_iterator_t *t)
{
return TOKEN_LEN(t->token) == 1 &&
*t->token.begin == t->match_1;
}
// True if current token is the match zero or more wildcard
static bool token_iterator_is_match_glob(const token_iterator_t *t)
{
return TOKEN_LEN(t->token) == 1 &&
*t->token.begin == t->match_glob;
}
// Optimize the pattern match code by performing the following
// transformations to the pattern:
// #.* ---> *.#
// #.# ---> #
// This only applies to non-MQTT patterns since in MQTT it is illegal for the #
// to appear anywhere but the last token.
// Returns true if pattern was rewritten.
//
static bool normalize_pattern(qd_parse_tree_type_t type, char *pattern)
{
token_iterator_t t;
bool modified = false;
char *original = NULL;
if (type == QD_PARSE_TREE_MQTT) return false;
token_iterator_init(&t, type, pattern);
while (!token_iterator_done(&t)) {
if (token_iterator_is_match_glob(&t)) {
token_t last_token;
token_iterator_pop(&t, &last_token);
if (token_iterator_done(&t)) break;
if (token_iterator_is_match_glob(&t)) { // #.# --> #
if (!modified) original = strdup(pattern);
modified = true;
char *src = (char *)t.token.begin;
char *dest = (char *)last_token.begin;
// note: overlapping strings, can't strcpy
while (*src)
*dest++ = *src++;
*dest = (char)0;
t.terminator = dest;
t.token = last_token;
} else if (token_iterator_is_match_1(&t)) { // #.* --> *.#
if (!modified) original = strdup(pattern);
modified = true;
*(char *)t.token.begin = t.match_glob;
*(char *)last_token.begin = t.match_1;
} else {
token_iterator_next(&t);
}
} else {
token_iterator_next(&t);
}
}
if (original) {
qd_log(qd_log_source("DEFAULT"), QD_LOG_NOTICE,
"configured pattern '%s' optimized and re-written to '%s'",
original, pattern);
free(original);
}
return modified;
}
// Parse Tree
//
// The pattern is broken up into tokens delimited by a separation character and
// stored in a directed tree graph. Common pattern prefixes are merged. This
// allows the lookup alogrithm to quickly isolate those sub-trees that match a
// given input string.
// For example, given the patterns:
// a.b.c.<...>
// a.b.d.<...>
// a.x.y.<...>
// The resulting parse tree would be:
// a-->b-->c-->...
// | +-->d-->...
// +-->x-->y-->...
//
typedef struct qd_parse_node qd_parse_node_t;
struct qd_parse_tree {
qd_parse_node_t *root;
qd_log_source_t *log_source;
qd_hash_t *hash;
qd_parse_tree_type_t type;
uint32_t next_hkey_prefix; // next # for hash key prefix
};
ALLOC_DECLARE(qd_parse_tree_t);
ALLOC_DEFINE(qd_parse_tree_t);
typedef enum {
QD_PARSE_NODE_ROOT,
QD_PARSE_NODE_TOKEN,
QD_PARSE_NODE_MATCH_ONE,
QD_PARSE_NODE_MATCH_GLOB,
} qd_parse_node_type_t;
DEQ_DECLARE(qd_parse_node_t, qd_parse_node_list_t);
struct qd_parse_node {
DEQ_LINKS(qd_parse_node_t); // siblings
char *token; // portion of pattern represented by this node
char *pattern; // entire normalized pattern matching this node
// sub-trees of this node:
qd_parse_node_t *match_1_child; // starts with a match 1 wildcard
qd_parse_node_t *match_glob_child; // starts with 0 or more wildcard
qd_parse_node_list_t children; // all that start with a non-wildcard token
qd_parse_node_t *parent; // NULL if root node
qd_hash_handle_t *handle;
void *payload;
// prefix for hash keys of this node's children
uint32_t hkey_prefix;
qd_parse_node_type_t match_type;
};
ALLOC_DECLARE(qd_parse_node_t);
ALLOC_DEFINE(qd_parse_node_t);
// Hash key for a token node is in the following format:
// "<parent node hkey-prefix in hex>/<token>
//
#define HKEY_PREFIX_LEN (8 + 1) // 8 hex chars (32 bit integer) + '/'
static inline void generate_hkey(char *hkey, size_t buf_len, uint32_t pprefix, const token_t *t)
{
int rc = snprintf(hkey, buf_len, "%"PRIX32"/%.*s", pprefix, (int)TOKEN_LEN(*t), t->begin);
assert(rc > 0 && rc < buf_len);
(void)rc; // prevent warnings for unused var
}
// return count of child nodes
static int parse_node_child_count(const qd_parse_node_t *n)
{
return DEQ_SIZE(n->children)
+ (n->match_1_child ? 1 : 0)
+ (n->match_glob_child ? 1 : 0);
}
// Free a parse node
static void free_parse_node(qd_parse_tree_t *tree, qd_parse_node_t *n)
{
if (n) {
assert(parse_node_child_count(n) == 0);
free(n->token);
free(n->pattern);
if (n->handle) {
qd_hash_remove_by_handle(tree->hash, n->handle);
qd_hash_handle_free(n->handle);
}
free_qd_parse_node_t(n);
}
}
// Allocate a new parse tree node.
//
// Allocate a new hash node. If a token is given the hash key for this node is
// generated by concatenating the parent prefix with the token.
//
static qd_parse_node_t *new_parse_node(qd_parse_tree_t *tree,
qd_parse_node_type_t match_type,
const token_t *t,
qd_parse_node_t *parent)
{
qd_parse_node_t *n = new_qd_parse_node_t();
if (n) {
ZERO(n);
DEQ_ITEM_INIT(n);
DEQ_INIT(n->children);
n->match_type = match_type;
// generate a new hash key prefix for this node's children
n->hkey_prefix = tree->next_hkey_prefix++;
if (match_type == QD_PARSE_NODE_TOKEN) {
assert(t);
assert(parent);
const size_t tlen = TOKEN_LEN(*t);
n->token = malloc(tlen + 1);
if (!n->token) {
free_qd_parse_node_t(n);
return 0;
}
strncpy(n->token, t->begin, tlen);
n->token[tlen] = 0;
{
const size_t hkey_size = HKEY_PREFIX_LEN + tlen + 1;
char *hkey = qd_malloc(hkey_size);
generate_hkey(hkey, hkey_size, parent->hkey_prefix, t);
if (qd_hash_insert_str(tree->hash, (unsigned char *)hkey, (void *)n, &n->handle) != QD_ERROR_NONE) {
free_parse_node(tree, n);
free(hkey);
return 0;
}
free(hkey);
n->parent = parent;
}
}
}
return n;
}
// find immediate child node matching token
static qd_parse_node_t *parse_node_find_child(qd_parse_tree_t *tree, const qd_parse_node_t *node, const token_t *token)
{
qd_parse_node_t *child = 0;
const size_t tlen = TOKEN_LEN(*token);
const size_t hkey_size = HKEY_PREFIX_LEN + tlen + 1;
char *hkey = qd_malloc(hkey_size);
generate_hkey(hkey, hkey_size, node->hkey_prefix, token);
qd_hash_retrieve_str(tree->hash, (const unsigned char *)hkey, (void **)&child);
if (child) {
assert(child->parent == node);
}
free(hkey);
return child;
}
// Add a new pattern and associated data to the tree.
// Return QD_ERROR_ALREADY_EXISTS if the pattern is already in the tree.
// caller transfers ownership of "pattern"
//
static qd_error_t parse_node_add_pattern(qd_parse_tree_t *tree, char *pattern, void *payload)
{
qd_parse_node_t *node = tree->root;
qd_error_t result = QD_ERROR_NONE;
token_iterator_t iterator;
normalize_pattern(tree->type, pattern);
// worst case maximum for hash key if pattern is a single token
const size_t buf_size = HKEY_PREFIX_LEN + strlen(pattern) + 1;
char *hkey = malloc(buf_size);
if (!hkey) {
result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern);
free(pattern);
return result;
}
token_iterator_init(&iterator, tree->type, pattern);
//
// walk the iterator through the tree adding nodes for each token/wildcard as needed
//
while (!token_iterator_done(&iterator)) {
if (token_iterator_is_match_1(&iterator)) {
if (!node->match_1_child) {
node->match_1_child = new_parse_node(tree, QD_PARSE_NODE_MATCH_ONE, 0, node);
if (!node->match_1_child) {
result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern);
break;
}
node->match_1_child->parent = node;
}
node = node->match_1_child;
token_iterator_next(&iterator);
} else if (token_iterator_is_match_glob(&iterator)) {
if (!node->match_glob_child) {
node->match_glob_child = new_parse_node(tree, QD_PARSE_NODE_MATCH_GLOB, 0, node);
if (!node->match_glob_child) {
result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern);
break;
}
node->match_glob_child->parent = node;
}
node = node->match_glob_child;
token_iterator_next(&iterator);
} else { // non-wildcard token
token_t new_token;
token_iterator_pop(&iterator, &new_token);
generate_hkey(hkey, buf_size, node->hkey_prefix, &new_token);
qd_parse_node_t *child = 0;
qd_hash_retrieve_str(tree->hash, (const unsigned char *)hkey, (void **)&child);
if (child) {
assert(child->parent == node);
node = child;
} else {
// add new node for new_token
child = new_parse_node(tree, QD_PARSE_NODE_TOKEN, &new_token, node);
if (!child) {
result = qd_error(QD_ERROR_ALLOC, "Pattern '%s' not added to parse tree", pattern);
break;
}
DEQ_INSERT_TAIL(node->children, child);
node = child;
}
}
}
// done iterating over pattern.
if (result == QD_ERROR_NONE) {
if (node == tree->root) {
result = qd_error(QD_ERROR_VALUE, "Invalid pattern '%s' not added to parse tree", pattern);
} else if (node->pattern) {
result = qd_error(QD_ERROR_ALREADY_EXISTS, "Duplicate pattern '%s' not added to parse tree", pattern);
} else {
node->pattern = pattern;
pattern = 0;
node->payload = payload;
qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree add pattern '%s'", node->pattern);
}
}
free(pattern);
free(hkey);
return result;
}
// Find the leaf node matching the pattern. Returns 0 if pattern is not in the tree
//
static qd_parse_node_t *parse_node_get_pattern(qd_parse_tree_t *tree, char *pattern)
{
qd_parse_node_t *node = tree->root;
token_iterator_t iterator;
normalize_pattern(tree->type, pattern);
// worst case maximum for hash key if pattern is a single token
const size_t buf_size = HKEY_PREFIX_LEN + strlen(pattern) + 1;
char *hkey = malloc(buf_size);
if (!hkey) {
return 0;
}
token_iterator_init(&iterator, tree->type, pattern);
while (node && !token_iterator_done(&iterator)) {
if (token_iterator_is_match_1(&iterator)) {
node = node->match_1_child;
token_iterator_next(&iterator);
} else if (token_iterator_is_match_glob(&iterator)) {
node = node->match_glob_child;
token_iterator_next(&iterator);
} else {
token_t new_token;
token_iterator_pop(&iterator, &new_token);
// search for new_token child
assert(node->hkey_prefix);
generate_hkey(hkey, buf_size, node->hkey_prefix, &new_token);
qd_parse_node_t *child = 0;
qd_hash_retrieve_str(tree->hash, (const unsigned char *)hkey, (void **)&child);
assert(!child || child->parent == node);
node = child;
}
}
free(hkey);
if (node && node->pattern) {
assert(strcmp(node->pattern, pattern) == 0);
return node;
}
return 0;
}
static bool parse_node_find(qd_parse_tree_t *, qd_parse_node_t *, token_iterator_t *,
qd_parse_tree_visit_t *, void *);
// search the sub-trees of this node.
// This function returns false to stop the search
static bool parse_node_find_children(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value,
qd_parse_tree_visit_t *callback, void *handle)
{
if (!token_iterator_done(value)) {
// check exact match first (precedence)
if (DEQ_SIZE(node->children) > 0) {
token_iterator_t tmp = *value;
token_t child_token;
token_iterator_pop(&tmp, &child_token);
qd_parse_node_t *child = parse_node_find_child(tree, node, &child_token);
if (child) {
if (!parse_node_find(tree, child, &tmp, callback, handle))
return false;
}
}
if (node->match_1_child) {
token_iterator_t tmp = *value;
if (!parse_node_find(tree, node->match_1_child, &tmp, callback, handle))
return false;
}
}
// always try glob - even if empty (it can match empty keys)
if (node->match_glob_child) {
token_iterator_t tmp = *value;
if (!parse_node_find(tree, node->match_glob_child, &tmp, callback, handle))
return false;
}
return true; // continue search
}
// This node matched the last token.
// This function returns false to stop the search
static bool parse_node_find_token(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value,
qd_parse_tree_visit_t *callback, void *handle)
{
if (token_iterator_done(value) && node->pattern) {
// exact match current node
if (!callback(handle, node->pattern, node->payload))
return false;
}
// no payload or more tokens. Continue to lower sub-trees. Even if no more
// tokens (may have a # match)
return parse_node_find_children(tree, node, value, callback, handle);
}
// this node matches any one token
// returns false to stop the search
static bool parse_node_match_1(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value,
qd_parse_tree_visit_t *callback, void *handle)
{
// must match exactly one token:
if (token_iterator_done(value))
return true; // no match here, but continue searching
// pop the topmost token (matched)
token_iterator_next(value);
if (token_iterator_done(value) && node->pattern) {
// exact match current node
if (!callback(handle, node->pattern, node->payload))
return false;
}
// no payload or more tokens: continue to lower sub-trees
return parse_node_find_children(tree, node, value, callback, handle);
}
// current node is hash, match the remainder of the string
// return false to stop search
static bool parse_node_match_glob(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value,
qd_parse_tree_visit_t *callback, void *handle)
{
// consume each token and look for a match on the
// remaining key.
while (!token_iterator_done(value)) {
if (!parse_node_find_children(tree, node, value, callback, handle))
return false;
token_iterator_next(value);
}
// this node matches
if (node->pattern)
return callback(handle, node->pattern, node->payload);
return true;
}
// find the payload associated with the input string 'value'
static bool parse_node_find(qd_parse_tree_t *tree, qd_parse_node_t *node, token_iterator_t *value,
qd_parse_tree_visit_t *callback, void *handle)
{
switch (node->match_type) {
case QD_PARSE_NODE_MATCH_ONE:
return parse_node_match_1(tree, node, value, callback, handle);
case QD_PARSE_NODE_MATCH_GLOB:
return parse_node_match_glob(tree, node, value, callback, handle);
case QD_PARSE_NODE_TOKEN:
return parse_node_find_token(tree, node, value, callback, handle);
case QD_PARSE_NODE_ROOT:
return parse_node_find_children(tree, node, value, callback, handle);
}
return true;
}
static void parse_node_free(qd_parse_tree_t *tree, qd_parse_node_t *node)
{
if (node) {
if (node->match_1_child) parse_node_free(tree, node->match_1_child);
if (node->match_glob_child) parse_node_free(tree, node->match_glob_child);
node->match_1_child = node->match_glob_child = NULL;
while (!DEQ_IS_EMPTY(node->children)) {
qd_parse_node_t *child = DEQ_HEAD(node->children);
DEQ_REMOVE_HEAD(node->children);
parse_node_free(tree, child);
}
free_parse_node(tree, node);
}
}
qd_parse_tree_t *qd_parse_tree_new(qd_parse_tree_type_t type)
{
qd_parse_tree_t *tree = new_qd_parse_tree_t();
if (tree) {
ZERO(tree);
tree->type = type;
tree->log_source = qd_log_source("DEFAULT");
tree->next_hkey_prefix = 1;
tree->root = new_parse_node(tree, QD_PARSE_NODE_ROOT, 0, 0);
if (!tree->root) {
free_qd_parse_tree_t(tree);
return 0;
}
tree->hash = qd_hash(10, 32, false);
if (!tree->hash) {
parse_node_free(tree, tree->root);
free_qd_parse_tree_t(tree);
return 0;
}
}
return tree;
}
// find best match for value
//
static bool get_first(void *handle, const char *pattern, void *payload)
{
*(void **)handle = payload;
return false;
}
bool qd_parse_tree_retrieve_match(qd_parse_tree_t *tree,
const qd_iterator_t *value,
void **payload)
{
*payload = NULL;
qd_parse_tree_search(tree, value, get_first, payload);
if (*payload == NULL)
qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree match not found");
return *payload != NULL;
}
// Invoke callback for each pattern that matches 'value'
void qd_parse_tree_search(qd_parse_tree_t *tree,
const qd_iterator_t *value,
qd_parse_tree_visit_t *callback, void *handle)
{
token_iterator_t t_iter;
char *str = (char *)qd_iterator_copy_const(value);
qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree search for '%s'", str);
token_iterator_init(&t_iter, tree->type, str);
parse_node_find(tree, tree->root, &t_iter, callback, handle);
free(str);
}
qd_error_t qd_parse_tree_add_pattern(qd_parse_tree_t *tree,
const qd_iterator_t *pattern,
void *payload)
{
char *str = (char *)qd_iterator_copy_const(pattern);
if (!str)
return QD_ERROR_ALLOC;
return parse_node_add_pattern(tree, str, payload);
}
// returns true if pattern exists in tree
bool qd_parse_tree_get_pattern(qd_parse_tree_t *tree,
const qd_iterator_t *pattern,
void **payload)
{
char *str = (char *)qd_iterator_copy_const(pattern);
if (!str)
return false;
qd_parse_node_t *found = parse_node_get_pattern(tree, str);
free(str);
*payload = found ? found->payload : NULL;
return !!found;
}
// returns the payload void *
void *qd_parse_tree_remove_pattern(qd_parse_tree_t *tree,
const qd_iterator_t *pattern)
{
char *str = (char *)qd_iterator_copy_const(pattern);
if (!str)
return 0;
qd_parse_node_t *node = parse_node_get_pattern(tree, str);
if (!node) {
free(str);
return 0;
}
assert(strcmp(node->pattern, str) == 0);
free(str);
void *value = node->payload;
// now clean up this node. Be aware we can only free the entire node if it does not have children.
// If it has children just zero out the pattern field indicates it is no longer a terminal node.
// The fun happens if it has no children: in this case free the node then
// see if the parent node is now childless and NOT a terminal node. If so
// clean it up as well. Repeat.
free(node->pattern);
node->pattern = 0;
node->payload = 0;
qd_parse_node_t *parent = node->parent;
while (node && node->pattern == 0 && parse_node_child_count(node) == 0 && parent) {
switch (node->match_type) {
case QD_PARSE_NODE_MATCH_ONE:
assert(parent->match_1_child == node);
parent->match_1_child = 0;
break;
case QD_PARSE_NODE_MATCH_GLOB:
assert(parent->match_glob_child == node);
parent->match_glob_child = 0;
break;
case QD_PARSE_NODE_TOKEN:
DEQ_REMOVE(parent->children, node);
break;
case QD_PARSE_NODE_ROOT:
default:
// cannot get here
assert(false);
break;
}
free_parse_node(tree, node);
node = parent;
parent = node->parent;
}
return value;
}
static bool parse_tree_walk(qd_parse_node_t *node, qd_parse_tree_visit_t *callback, void *handle)
{
if (node->pattern) { // terminal node for pattern
if (!callback(handle, node->pattern, node->payload))
return false;
}
qd_parse_node_t *child = DEQ_HEAD(node->children);
while (child) {
if (!parse_tree_walk(child, callback, handle))
return false;
child = DEQ_NEXT(child);
}
if (node->match_1_child)
if (!parse_tree_walk(node->match_1_child, callback, handle))
return false;
if (node->match_glob_child)
if (!parse_tree_walk(node->match_glob_child, callback, handle))
return false;
return true;
}
bool qd_parse_tree_walk(qd_parse_tree_t *tree, qd_parse_tree_visit_t *callback, void *handle)
{
return parse_tree_walk(tree->root, callback, handle);
}
bool qd_parse_tree_validate_pattern(const qd_parse_tree_t *tree,
const qd_iterator_t *pattern)
{
char *str = (char *)qd_iterator_copy_const(pattern);
if (!str)
return false;
token_iterator_t ti;
token_iterator_init(&ti, tree->type, str);
if (token_iterator_done(&ti)) {
// empty iterator
free(str);
return false;
}
if (tree->type == QD_PARSE_TREE_MQTT) {
// simply ensure that if a '#' is present it is the last token in the
// pattern
bool valid = true;
while (!token_iterator_done(&ti)) {
token_t head;
token_iterator_pop(&ti, &head);
if (token_match_str(&head, "#")) {
valid = token_iterator_done(&ti);
break;
}
}
free(str);
return valid;
}
free(str);
return true;
}
qd_parse_tree_type_t qd_parse_tree_type(const qd_parse_tree_t *tree)
{
return tree->type;
}
static void _parse_tree_free(qd_parse_tree_t *tree)
{
if (tree) {
parse_node_free(tree, tree->root);
qd_hash_free(tree->hash);
free_qd_parse_tree_t(tree);
}
}
#if 0
static void _node_dump(const qd_parse_node_t *node, const int depth)
{
for (int i = 0; i < depth; i++)
fprintf(stdout, " ");
char *type = (node->match_type == QD_PARSE_NODE_ROOT
? "ROOT"
: (node->match_type == QD_PARSE_NODE_TOKEN
? "TOKEN"
: (node->match_type == QD_PARSE_NODE_MATCH_ONE
? "STAR"
: "GLOB")));
fprintf(stdout, "token: %s pattern: %s type: %s # childs: %d hkey: %s hprefix %"PRIX32" payload: %p\n",
node->token ? node->token : "*NONE*",
node->pattern ? node->pattern: "*NONE*",
type,
parse_node_child_count(node),
(node->handle ? (const char *)qd_hash_key_by_handle(node->handle) : "*NONE*"),
node->hkey_prefix,
node->payload);
if (node->match_1_child) _node_dump(node->match_1_child, depth + 1);
if (node->match_glob_child) _node_dump(node->match_glob_child, depth + 1);
qd_parse_node_t *child = DEQ_HEAD(node->children);
while (child) {
_node_dump(child, depth + 1);
child = DEQ_NEXT(child);
}
}
void qd_parse_tree_dump(const qd_parse_tree_t *tree)
{
if (tree && tree->root) {
_node_dump(tree->root, 0);
fflush(stdout);
}
}
void qd_parse_tree_free(qd_parse_tree_t *tree)
{
fprintf(stdout, "PARSE TREE DUMP\n");
fprintf(stdout, "===============\n");
qd_parse_tree_dump(tree);
_parse_tree_free(tree);
}
#else
void qd_parse_tree_free(qd_parse_tree_t *tree)
{
_parse_tree_free(tree);
}
#endif
//
// parse tree functions using string interface
//
qd_error_t qd_parse_tree_add_pattern_str(qd_parse_tree_t *tree,
const char *pattern,
void *payload)
{
char *str = strdup(pattern);
if (!str)
return QD_ERROR_ALLOC;
return parse_node_add_pattern(tree, str, payload);
}
// visit each matching pattern that matches value in the order based on the
// precedence rules
void qd_parse_tree_search_str(qd_parse_tree_t *tree,
const char *value,
qd_parse_tree_visit_t *callback, void *handle)
{
token_iterator_t t_iter;
// @TODO(kgiusti) for now:
char *str = strdup(value);
qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree(str) search for '%s'", str);
token_iterator_init(&t_iter, tree->type, str);
parse_node_find(tree, tree->root, &t_iter, callback, handle);
free(str);
}
// returns true on match and sets *payload
bool qd_parse_tree_retrieve_match_str(qd_parse_tree_t *tree,
const char *value,
void **payload)
{
*payload = NULL;
qd_parse_tree_search_str(tree, value, get_first, payload);
if (*payload == NULL)
qd_log(tree->log_source, QD_LOG_TRACE, "Parse tree(str) match not found");
return *payload != NULL;
}
// returns old payload or NULL if not present
void *qd_parse_tree_remove_pattern_str(qd_parse_tree_t *tree,
const char *pattern)
{
qd_iterator_t *piter = qd_iterator_string(pattern, ITER_VIEW_ALL);
void *result = qd_parse_tree_remove_pattern(tree, piter);
qd_iterator_free(piter);
return result;
}