libidav/davqlparser.c

changeset 1
b5bb7b3cd597
child 49
2f71f4ee247a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libidav/davqlparser.c	Mon Jan 22 17:27:47 2024 +0100
@@ -0,0 +1,1860 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2018 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "davqlparser.h"
+#include <cx/utils.h>
+#include <cx/linked_list.h>
+#include <cx/printf.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#define sfmtarg(s) ((int)(s).length), (s).ptr
+
+// ------------------------------------------------------------------------
+//                        D E B U G E R
+// ------------------------------------------------------------------------
+
+static const char* _map_querytype(davqltype_t type) {
+    switch(type) {
+    case DAVQL_ERROR: return "ERROR";
+    case DAVQL_SELECT: return "SELECT";
+    case DAVQL_SET: return "SET";
+    default: return "unknown";
+    }
+}
+
+static const char* _map_exprtype(davqlexprtype_t type) {
+    switch(type) {
+    case DAVQL_UNDEFINED_TYPE: return "undefined";
+    case DAVQL_NUMBER: return "NUMBER";
+    case DAVQL_STRING: return "STRING";
+    case DAVQL_TIMESTAMP: return "TIMESTAMP";
+    case DAVQL_IDENTIFIER: return "IDENTIFIER";
+    case DAVQL_UNARY: return "UNARY";
+    case DAVQL_BINARY: return "BINARY";
+    case DAVQL_LOGICAL: return "LOGICAL";
+    case DAVQL_FUNCCALL: return "FUNCCALL";
+    default: return "unknown";
+    }
+}
+
+static const char* _map_specialfield(int info) {
+    switch(info) {
+    case 0: return "";
+    case 1: return "with wildcard";
+    case 2: return "(resource data only)";
+    default: return "with mysterious identifier";
+    }
+}
+
+static const char* _map_operator(davqloperator_t op) {
+    // don't use string array, because enum values may change
+    switch(op) {
+    case DAVQL_NOOP: return "no operator";
+    case DAVQL_CALL: return "function call"; case DAVQL_ARGLIST: return ",";
+    case DAVQL_ADD: return "+"; case DAVQL_SUB: return "-";
+    case DAVQL_MUL: return "*"; case DAVQL_DIV: return "/";
+    case DAVQL_AND: return "&"; case DAVQL_OR: return "|";
+    case DAVQL_XOR: return "^"; case DAVQL_NEG: return "~";
+    case DAVQL_NOT: return "NOT"; case DAVQL_LAND: return "AND";
+    case DAVQL_LOR: return "OR"; case DAVQL_LXOR: return "XOR";
+    case DAVQL_EQ: return "="; case DAVQL_NEQ: return "!=";
+    case DAVQL_LT: return "<"; case DAVQL_GT: return ">";
+    case DAVQL_LE: return "<="; case DAVQL_GE: return ">=";
+    case DAVQL_LIKE: return "LIKE"; case DAVQL_UNLIKE: return "UNLIKE";
+    default: return "unknown";
+    }
+}
+
+static void dav_debug_ql_fnames_print(DavQLStatement *stmt) {
+    if (stmt->fields) {
+        printf("Field names: ");
+        CxIterator i = cxListIterator(stmt->fields);
+        cx_foreach(DavQLField *, f, i) {
+            printf("%.*s, ", (int)f->name.length, f->name.ptr);
+        }
+        printf("\b\b  \b\b\n");
+    }
+}
+
+static void dav_debug_ql_stmt_print(DavQLStatement *stmt) {
+    // Basic information
+    size_t fieldcount = stmt->fields ? stmt->fields->size : 0;
+    int specialfield = 0;
+    if (stmt->fields && stmt->fields->size > 0) {
+        DavQLField* firstfield = (DavQLField*)cxListAt(stmt->fields, 0);
+        if (firstfield->expr->type == DAVQL_IDENTIFIER) {
+            switch (firstfield->expr->srctext.ptr[0]) {
+            case '*': specialfield = 1; break;
+            case '-': specialfield = 2; break;
+            }
+        }
+    }
+    if (specialfield) {
+        fieldcount--;
+    }
+    printf("Statement: %.*s\nType: %s\nField count: %zu %s\n",
+        (int)stmt->srctext.length, stmt->srctext.ptr,
+        _map_querytype(stmt->type),
+        fieldcount,
+        _map_specialfield(specialfield));
+    
+    dav_debug_ql_fnames_print(stmt);
+    printf("Path: %.*s\nHas where clause: %s\n",
+        (int)stmt->path.length, stmt->path.ptr,
+        stmt->where ? "yes" : "no");
+    
+    // WITH attributes
+    if (stmt->depth == DAV_DEPTH_INFINITY) {
+        printf("Depth: infinity\n");
+    } else if (stmt->depth == DAV_DEPTH_PLACEHOLDER) {
+        printf("Depth: placeholder\n");
+    } else {
+        printf("Depth: %d\n", stmt->depth);
+    }
+    
+    // order by clause
+    printf("Order by: ");
+    if (stmt->orderby) {
+        CxIterator i = cxListIterator(stmt->orderby);
+        cx_foreach(DavQLOrderCriterion*, critdata, i) {
+            printf("%.*s %s%s", (int)critdata->column->srctext.length, critdata->column->srctext.ptr,
+                critdata->descending ? "desc" : "asc",
+                i.index+1 < stmt->orderby->size ? ", " : "\n");
+        }
+    } else {
+        printf("nothing\n");
+    }
+    
+    // error messages
+    if (stmt->errorcode) {
+        printf("\nError code: %d\nError: %s\n",
+            stmt->errorcode, stmt->errormessage);
+    }
+}
+
+static int dav_debug_ql_expr_selected(DavQLExpression *expr) {
+    if (!expr) {
+        printf("Currently no expression selected.\n");
+        return 0;
+    } else {
+        return 1;
+    }
+}
+
+static void dav_debug_ql_expr_print(DavQLExpression *expr) {
+    if (dav_debug_ql_expr_selected(expr)) {
+        cxstring empty = CX_STR("(empty)");
+        printf(
+            "Text: %.*s\nType: %s\nOperator: %s\n",
+            sfmtarg(expr->srctext),
+            _map_exprtype(expr->type),
+            _map_operator(expr->op));
+        if (expr->left || expr->right) {
+            printf("Left hand: %.*s\nRight hand: %.*s\n",
+                sfmtarg(expr->left?expr->left->srctext:empty),
+                sfmtarg(expr->right?expr->right->srctext:empty));
+        }
+    }
+}
+
+static void dav_debug_ql_field_print(DavQLField *field) {
+    if (field) {
+        printf("Name: %.*s\n", sfmtarg(field->name));
+        if (field->expr) {
+            dav_debug_ql_expr_print(field->expr);
+        } else {
+            printf("No expression.\n");
+        }
+    } else {
+        printf("No field selected.\n");
+    }
+}
+
+static void dav_debug_ql_tree_print(DavQLExpression *expr, int depth) {
+    if (expr) {
+        if (expr->left) {
+            printf("%*c%s\n", depth, ' ', _map_operator(expr->op));
+            dav_debug_ql_tree_print(expr->left, depth+1);
+            dav_debug_ql_tree_print(expr->right, depth+1);
+        } else if (expr->type == DAVQL_UNARY) {
+            printf("%*c%s %.*s\n", depth, ' ', _map_operator(expr->op),
+                sfmtarg(expr->srctext));
+        } else {
+            printf("%*c%.*s\n", depth, ' ', sfmtarg(expr->srctext));
+        }
+    }
+}
+
+#define DQLD_CMD_Q     0
+#define DQLD_CMD_PS    1
+#define DQLD_CMD_PE    2
+#define DQLD_CMD_PF    3
+#define DQLD_CMD_PT    4
+#define DQLD_CMD_F    10
+#define DQLD_CMD_W    11
+#define DQLD_CMD_O    12
+#define DQLD_CMD_L    21
+#define DQLD_CMD_R    22
+#define DQLD_CMD_N    23
+#define DQLD_CMD_P    24
+#define DQLD_CMD_H   100
+
+static int dav_debug_ql_command() {
+    printf("> ");
+    
+    char buffer[8];
+    fgets(buffer, 8, stdin);
+     // discard remaining chars
+    if (!strchr(buffer, '\n')) {
+        int chr;
+        while ((chr = fgetc(stdin) != '\n') && chr != EOF);
+    }
+    
+    if (!strcmp(buffer, "q\n")) {
+        return DQLD_CMD_Q;
+    } else if (!strcmp(buffer, "ps\n")) {
+        return DQLD_CMD_PS;
+    } else if (!strcmp(buffer, "pe\n")) {
+        return DQLD_CMD_PE;
+    } else if (!strcmp(buffer, "pf\n")) {
+        return DQLD_CMD_PF;
+    } else if (!strcmp(buffer, "pt\n")) {
+        return DQLD_CMD_PT;
+    } else if (!strcmp(buffer, "l\n")) {
+        return DQLD_CMD_L;
+    } else if (!strcmp(buffer, "r\n")) {
+        return DQLD_CMD_R;
+    } else if (!strcmp(buffer, "h\n")) {
+        return DQLD_CMD_H;
+    } else if (!strcmp(buffer, "f\n")) {
+        return DQLD_CMD_F;
+    } else if (!strcmp(buffer, "w\n")) {
+        return DQLD_CMD_W;
+    } else if (!strcmp(buffer, "o\n")) {
+        return DQLD_CMD_O;
+    } else if (!strcmp(buffer, "n\n")) {
+        return DQLD_CMD_N;
+    } else if (!strcmp(buffer, "p\n")) {
+        return DQLD_CMD_P;
+    } else {
+        return -1;
+    }
+}
+
+void dav_debug_statement(DavQLStatement *stmt) {
+    if (!stmt) {
+        fprintf(stderr, "Debug DavQLStatement failed: null pointer");
+        return;
+    }
+
+    printf("Starting DavQL debugger (type 'h' for help)...\n\n");
+    dav_debug_ql_stmt_print(stmt);
+    
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    DavQLExpression *examineexpr = NULL;
+    CxList *examineelem = NULL;
+    int examineclause = 0;
+    
+    while(1) {
+        int cmd = dav_debug_ql_command();
+        switch (cmd) {
+        case DQLD_CMD_Q: return;
+        case DQLD_CMD_PS: dav_debug_ql_stmt_print(stmt); break;
+        case DQLD_CMD_PE: dav_debug_ql_expr_print(examineexpr); break;
+        case DQLD_CMD_PT: dav_debug_ql_tree_print(examineexpr, 1); break;
+        case DQLD_CMD_PF: dav_debug_ql_fnames_print(stmt); break;
+        case DQLD_CMD_F:
+            examineclause = DQLD_CMD_F;
+            examineelem = stmt->fields;
+            if (stmt->fields && stmt->fields->size > 0) {
+                DavQLField* field = cxListAt(stmt->fields, 0);
+                examineexpr = field->expr;
+                dav_debug_ql_field_print(field);
+            } else {
+                examineexpr = NULL;
+            }
+            break;
+        case DQLD_CMD_W:
+            examineclause = 0; examineelem = NULL;
+            examineexpr = stmt->where;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_O:
+            examineclause = DQLD_CMD_O;
+            examineelem = stmt->orderby;
+            examineexpr = stmt->orderby && stmt->orderby->size > 0 ?
+                ((DavQLOrderCriterion*)cxListAt(stmt->orderby, 0))->column : NULL;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_N:
+        case DQLD_CMD_P:
+            printf("TODO: port code to ucx 3\n");
+            /*
+            if (examineelem) {
+                CxList *newelem = (cmd == DQLD_CMD_N ?
+                    examineelem->next : examineelem->prev);
+                if (newelem) {
+                    examineelem = newelem;
+                    if (examineclause == DQLD_CMD_O) {
+                        examineexpr = ((DavQLOrderCriterion*)
+                            examineelem->data)->column;
+                        dav_debug_ql_expr_print(examineexpr);
+                    } else if (examineclause == DQLD_CMD_F) {
+                        DavQLField* field = (DavQLField*)examineelem->data;
+                        examineexpr = field->expr;
+                        dav_debug_ql_field_print(field);
+                    } else {
+                        printf("Examining unknown clause type.");
+                    }
+                } else {
+                    printf("Reached end of list.\n");
+                }
+            } else {
+                printf("Currently not examining an expression list.\n");
+            }
+            */
+            break;
+        case DQLD_CMD_L:
+            if (dav_debug_ql_expr_selected(examineexpr)) {
+                if (examineexpr->left) {
+                    examineexpr = examineexpr->left;
+                    dav_debug_ql_expr_print(examineexpr);
+                } else {
+                    printf("There is no left subtree.\n");
+                }
+            }
+            break;
+        case DQLD_CMD_R:
+            if (dav_debug_ql_expr_selected(examineexpr)) {
+                if (examineexpr->right) {
+                    examineexpr = examineexpr->right;
+                    dav_debug_ql_expr_print(examineexpr);
+                } else {
+                    printf("There is no right subtree.\n");
+                }
+            }
+            break;
+        case DQLD_CMD_H:
+            printf(
+                "\nCommands:\n"
+                "ps:  print statement information\n"
+                "o:   examine order by clause\n"
+                "f:   examine field list\n"
+                "pf:  print field names\n"
+                "w:   examine where clause\n"
+                "n:   examine next expression "
+                    "(in order by clause or field list)\n"
+                "p:   examine previous expression "
+                    "(in order by clause or field list)\n"
+                "q:   quit\n\n"
+                "\nExpression examination:\n"
+                "pe:  print expression information\n"
+                "pt:  print full syntax tree of current (sub-)expression\n"
+                "l:   enter left subtree\n"
+                "r:   enter right subtree\n");
+            break;
+        default: printf("unknown command\n");
+        }
+    }
+}
+
+// ------------------------------------------------------------------------
+//                         P A R S E R
+// ------------------------------------------------------------------------
+
+#define _error_context "(%.*s[->]%.*s%.*s)"
+#define _error_invalid "invalid statement"
+#define _error_out_of_memory "out of memory"
+#define _error_unexpected_token "unexpected token " _error_context
+#define _error_invalid_token "invalid token " _error_context
+#define _error_missing_path "expected path " _error_context
+#define _error_missing_from "expecting FROM keyword " _error_context
+#define _error_missing_at "expecting AT keyword " _error_context
+#define _error_missing_by "expecting BY keyword " _error_context
+#define _error_missing_as "expecting alias ('as <identifier>') " _error_context
+#define _error_missing_identifier "expecting identifier " _error_context
+#define _error_missing_par "missing closed parenthesis " _error_context
+#define _error_missing_assign "expecting assignment ('=') " _error_context
+#define _error_missing_where "SET statements must have a WHERE clause or " \
+                        "explicitly use ANYWHERE " _error_context
+#define _error_invalid_depth "invalid depth " _error_context
+#define _error_missing_expr "missing expression " _error_context
+#define _error_invalid_expr "invalid expression " _error_context
+#define _error_invalid_unary_op "invalid unary operator "  _error_context
+#define _error_invalid_logical_op "invalid logical operator "  _error_context
+#define _error_invalid_fmtspec "invalid format specifier " _error_context
+#define _error_invalid_string "string expected " _error_context
+#define _error_invalid_order_criterion "invalid order criterion " _error_context
+
+#define token_sstr(token) ((token)->value)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+        DavQLStatement *stmt, DavQLToken *token) {
+    
+    // we try to achieve two things: get as many information as possible
+    // and recover the concrete source string (and not the token strings)
+    cxstring emptystring = CX_STR("");
+    cxstring prev = token->prev ? (token->prev->prev ?
+        token_sstr(token->prev->prev) : token_sstr(token->prev))
+        : emptystring;
+    cxstring tokenstr = token_sstr(token);
+    cxstring next = token->next ? (token->next->next ?
+        token_sstr(token->next->next) : token_sstr(token->next))
+        : emptystring;
+    
+    int lp = prev.length == 0 ? 0 : tokenstr.ptr-prev.ptr;
+    const char *pn = tokenstr.ptr + tokenstr.length;
+    int ln = next.ptr+next.length - pn;
+    
+    stmt->errorcode = errorcode;
+    stmt->errormessage = cx_asprintf(errormsg,
+        lp, prev.ptr,
+        sfmtarg(tokenstr),
+        ln, pn).ptr;
+}
+
+#define dqlsec_alloc_failed(ptr, stmt)                                  \
+                    if (!(ptr)) do {                                    \
+                        (stmt)->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;  \
+                        return 0;                                       \
+                    } while(0)
+#define dqlsec_malloc(stmt, ptr, type) \
+        dqlsec_alloc_failed(ptr = malloc(sizeof(type)), stmt)
+#define dqlsec_mallocz(stmt, ptr, type) \
+        dqlsec_alloc_failed(ptr = calloc(1, sizeof(type)), stmt)
+
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
+
+static _Bool iskeyword(DavQLToken *token) {
+    cxstring keywords[] ={CX_STR("select"), CX_STR("set"), CX_STR("from"), CX_STR("at"), CX_STR("as"),
+        CX_STR("where"), CX_STR("anywhere"), CX_STR("like"), CX_STR("unlike"), CX_STR("and"),
+        CX_STR("or"), CX_STR("not"), CX_STR("xor"), CX_STR("with"), CX_STR("infinity"),
+        CX_STR("order"), CX_STR("by"), CX_STR("asc"), CX_STR("desc")
+    };
+    for (int i = 0 ; i < sizeof(keywords)/sizeof(cxstring) ; i++) {
+        if (!cx_strcasecmp(token->value, keywords[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static _Bool islongoperator(DavQLToken *token) {
+    cxstring operators[] = {CX_STR("and"), CX_STR("or"), CX_STR("not"), CX_STR("xor"),
+        CX_STR("like"), CX_STR("unlike")
+    };
+    for (int i = 0 ; i < sizeof(operators)/sizeof(cxstring) ; i++) {
+        if (!cx_strcasecmp(token->value, operators[i])) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int dav_stmt_add_field(DavQLStatement *stmt, DavQLField *field) {
+    if(!stmt->fields) {
+        stmt->fields = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+        if(!stmt->fields) {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 1;
+        }
+    }
+    
+    if(cxListAdd(stmt->fields, field)) {
+        stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 1;
+    }
+    
+    return 0;
+}
+    
+
+static void tokenlist_free(DavQLToken *tokenlist) {
+    DavQLToken *token = tokenlist;
+    while(token) {
+        DavQLToken *next = token->next;
+        free(token);
+        token = next;
+    }
+}
+
+static int dav_parse_add_token(DavQLToken **begin, DavQLToken **end, DavQLToken *token) {
+    
+    // determine token class (order of if-statements is very important!)
+    char firstchar = token->value.ptr[0];
+
+    if (isdigit(firstchar)) {
+        token->tokenclass = DAVQL_TOKEN_NUMBER;
+        // check, if all characters are digits
+        for (size_t i = 1 ; i < token->value.length ; i++) {
+            if (!isdigit(token->value.ptr[i])) {
+                token->tokenclass = DAVQL_TOKEN_INVALID;
+                break;
+            }
+        }
+    } else if (firstchar == '%') {
+        token->tokenclass = DAVQL_TOKEN_FMTSPEC;
+    } else if (token->value.length == 1) {
+        switch (firstchar) {
+        case '(': token->tokenclass = DAVQL_TOKEN_OPENP; break;
+        case ')': token->tokenclass = DAVQL_TOKEN_CLOSEP; break;
+        case ',': token->tokenclass = DAVQL_TOKEN_COMMA; break;
+        default:
+            token->tokenclass = strchr(special_token_symbols, firstchar) ?
+                DAVQL_TOKEN_OPERATOR : DAVQL_TOKEN_IDENTIFIER;
+        }
+    } else if (islongoperator(token)) {
+        token->tokenclass = DAVQL_TOKEN_OPERATOR;
+    } else if (firstchar == '\'') {
+        token->tokenclass = DAVQL_TOKEN_STRING;
+    } else if (firstchar == '`') {
+        token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+    } else if (iskeyword(token)) {
+        token->tokenclass = DAVQL_TOKEN_KEYWORD;
+    } else {
+        token->tokenclass = DAVQL_TOKEN_IDENTIFIER;
+        // TODO: check for illegal characters
+    }
+    
+    // remove quotes (extreme cool feature)
+    if (token->tokenclass == DAVQL_TOKEN_STRING ||
+        (token->tokenclass == DAVQL_TOKEN_IDENTIFIER && firstchar == '`')) {
+        
+        char lastchar = token->value.ptr[token->value.length-1];
+        if (firstchar == lastchar) {
+            token->value.ptr++;
+            token->value.length -= 2;
+        } else {
+            token->tokenclass = DAVQL_TOKEN_INVALID;
+        }
+    }
+    
+    cx_linked_list_add((void**)begin, (void**)end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+    return 0;
+}
+
+
+
+static DavQLToken* dav_parse_tokenize(cxstring src) {
+#define alloc_token() do {token = calloc(1, sizeof(DavQLToken));\
+        if(!token) {tokenlist_free(tokens_begin); return NULL;}} while(0)
+#define add_token() if(dav_parse_add_token(&tokens_begin, &tokens_end, token)) return NULL;
+    
+    DavQLToken *tokens_begin = NULL;
+    DavQLToken *tokens_end = NULL;
+    
+    DavQLToken *token = NULL;
+    
+    char insequence = '\0';
+    for (size_t i = 0 ; i < src.length ; i++) {
+        // quoted strings / identifiers are a single token
+        if (src.ptr[i] == '\'' || src.ptr[i] == '`') {
+            if (src.ptr[i] == insequence) {
+                // lookahead for escaped string quotes
+                if (src.ptr[i] == '\'' && i+2 < src.length &&
+                    src.ptr[i+1] == src.ptr[i] && src.ptr[i+2] == src.ptr[i]) {
+                    token->value.length += 3;
+                    i += 2;
+                } else {
+                    // add quoted token to list
+                    token->value.length++;
+                    add_token();
+                    token = NULL;
+                    insequence = '\0';
+                }
+            } else if (insequence == '\0') {
+                insequence = src.ptr[i];
+                // always create new token for quoted strings
+                if (token) {
+                    add_token();
+                }
+                alloc_token();
+                token->value.ptr = src.ptr + i;
+                token->value.length = 1;
+            } else {
+                // add other kind of quotes to token
+                token->value.length++;
+            }
+        } else if (insequence) {
+            token->value.length++;
+        } else if (isspace(src.ptr[i])) {
+            // add token before spaces to list (if any)
+            if (token) {
+                add_token();
+                token = NULL;
+            }
+        } else if (strchr(special_token_symbols, src.ptr[i])) {
+            // add token before special symbol to list (if any)
+            if (token) {
+                add_token();
+                token = NULL;
+            }
+            // add special symbol as single token to list
+            alloc_token();
+            token->value.ptr = src.ptr + i;
+            token->value.length = 1;
+            add_token();
+            // set tokenizer ready to read more tokens
+            token = NULL;
+        } else {
+            // if this is a new token, create memory for it
+            if (!token) {
+                alloc_token();
+                token->value.ptr = src.ptr + i;
+                token->value.length = 0;
+            }
+            // extend token length when reading more bytes
+            token->value.length++;
+        }
+    }
+    
+    if (token) {
+        add_token();
+    }
+    
+    alloc_token();
+    token->tokenclass = DAVQL_TOKEN_END;
+    token->value = CX_STR("");
+    
+    cx_linked_list_add((void**)&tokens_begin, (void**)&tokens_end, offsetof(DavQLToken, prev), offsetof(DavQLToken, next), token);
+    return tokens_begin;
+#undef alloc_token
+#undef add_token
+}
+
+static void dav_free_expression(DavQLExpression *expr) {
+    if (expr) {
+        if (expr->left) {
+            dav_free_expression(expr->left);
+        }
+        if (expr->right) {
+            dav_free_expression(expr->right);
+        }
+        free(expr);
+    }
+}
+
+static void dav_free_field(DavQLField *field) {
+    dav_free_expression(field->expr);
+    free(field);
+}
+
+static void dav_free_order_criterion(DavQLOrderCriterion *crit) {
+    if (crit->column) { // do it null-safe though column is expected to be set
+        dav_free_expression(crit->column);
+    }
+}
+
+#define token_is(token, expectedclass) (token && \
+    (token->tokenclass == expectedclass))
+
+#define tokenvalue_is(token, expectedvalue) (token && \
+    !cx_strcasecmp(token->value, cx_str(expectedvalue)))
+
+typedef int(*exprparser_f)(DavQLStatement*,DavQLToken*,DavQLExpression*);
+
+static int dav_parse_binary_expr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr, exprparser_f parseL, char* opc, int* opv,
+        exprparser_f parseR) {
+    
+    if (!token) {
+        return 0;
+    }
+    
+    int total_consumed = 0, consumed;
+    
+    // save temporarily on stack (copy to heap later on)
+    DavQLExpression left, right;
+    
+    // RULE:    LEFT, [Operator, RIGHT]
+    memset(&left, 0, sizeof(DavQLExpression));
+    consumed = parseL(stmt, token, &left);
+    if (!consumed || stmt->errorcode) {
+        return 0;
+    }
+    total_consumed += consumed;
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+    char *op;
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) &&
+            (op = strchr(opc, token_sstr(token).ptr[0]))) {
+        expr->op = opv[op-opc];
+        expr->type = DAVQL_BINARY;
+        total_consumed++;
+        token = token->next;
+        memset(&right, 0, sizeof(DavQLExpression));
+        consumed = parseR(stmt, token, &right);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                _error_missing_expr, stmt, token);
+            return 0;
+        }
+        total_consumed += consumed;
+    }
+    
+    if (expr->op == DAVQL_NOOP) {
+        memcpy(expr, &left, sizeof(DavQLExpression));
+    } else {
+        dqlsec_malloc(stmt, expr->left, DavQLExpression);
+        memcpy(expr->left, &left, sizeof(DavQLExpression));
+        dqlsec_malloc(stmt, expr->right, DavQLExpression);
+        memcpy(expr->right, &right, sizeof(DavQLExpression));
+        
+        expr->srctext.ptr = expr->left->srctext.ptr;
+        expr->srctext.length = 
+            expr->right->srctext.ptr -
+            expr->left->srctext.ptr + expr->right->srctext.length;
+    }
+    
+    return total_consumed;
+}
+
+static void fmt_args_add(DavQLStatement *stmt, void *data) {
+    if(!stmt->args) {
+        stmt->args = cxLinkedListCreateSimple(CX_STORE_POINTERS);
+    }
+    cxListAdd(stmt->args, data);
+}
+
+static void dav_add_fmt_args(DavQLStatement *stmt, cxstring str) {
+    int placeholder = 0;
+    for (size_t i=0;i<str.length;i++) {
+        char c = str.ptr[i];
+        if (placeholder) {
+            if (c != '%') {
+                fmt_args_add(stmt, (void*)(intptr_t)c);
+            }
+            placeholder = 0;
+        } else if (c == '%') {
+            placeholder = 1;
+        }
+    }
+}
+
+static int dav_parse_literal(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    expr->srctext = token_sstr(token);
+    if (token_is(token, DAVQL_TOKEN_NUMBER)) {
+        expr->type = DAVQL_NUMBER;
+    } else if (token_is(token, DAVQL_TOKEN_STRING)) {
+        expr->type = DAVQL_STRING;
+        // check for format specifiers and add args
+        dav_add_fmt_args(stmt, expr->srctext);
+    } else if (token_is(token, DAVQL_TOKEN_TIMESTAMP)) {
+        expr->type = DAVQL_TIMESTAMP;
+    } else if (token_is(token, DAVQL_TOKEN_FMTSPEC)
+            && expr->srctext.length == 2) {
+        switch (expr->srctext.ptr[1]) {
+        case 'd': expr->type = DAVQL_NUMBER; break;
+        case 's': expr->type = DAVQL_STRING; break;
+        case 't': expr->type = DAVQL_TIMESTAMP; break;
+        default:
+            dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC,
+                _error_invalid_fmtspec, stmt, token);
+            return 0;
+        }
+        // add fmtspec type to query arg list
+        fmt_args_add(stmt, (void*)(intptr_t)expr->srctext.ptr[1]);
+    } else {
+        return 0;
+    }
+    
+    return 1;
+}
+
+// forward declaration
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr);
+
+static int dav_parse_arglist(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    expr->srctext.ptr = token_sstr(token).ptr;
+    expr->srctext.length = 0;
+    expr->left = expr->right = NULL; // in case we fail, we want them to be sane
+    
+    int total_consumed = 0;
+    
+    // RULE:    Expression, {",", Expression};
+    DavQLExpression *arglist = expr;
+    DavQLExpression arg;
+    const char *lastchar = expr->srctext.ptr;
+    int consumed;
+    do {
+        memset(&arg, 0, sizeof(DavQLExpression));
+        consumed = dav_parse_expression(stmt, token, &arg);
+        if (consumed) {
+            lastchar = arg.srctext.ptr + arg.srctext.length;
+            total_consumed += consumed;
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            // look ahead for a comma
+            if (token_is(token, DAVQL_TOKEN_COMMA)) {
+                total_consumed++;
+                token = token->next;
+                /* we have more arguments, so put the current argument to the
+                 * left subtree and create a new node to the right
+                 */
+                dqlsec_malloc(stmt, arglist->left, DavQLExpression);
+                memcpy(arglist->left, &arg, sizeof(DavQLExpression));
+                arglist->srctext.ptr = arg.srctext.ptr;
+                arglist->op = DAVQL_ARGLIST;
+                arglist->type = DAVQL_FUNCCALL;
+                dqlsec_mallocz(stmt, arglist->right, DavQLExpression);
+                arglist = arglist->right;
+            } else {
+                // this was the last argument, so write it to the current node
+                memcpy(arglist, &arg, sizeof(DavQLExpression));
+                consumed = 0;
+            }
+        }
+    } while (consumed && !stmt->errorcode);
+    
+    // recover source text
+    arglist = expr;
+    while (arglist && arglist->type == DAVQL_FUNCCALL) {
+        arglist->srctext.length = lastchar - arglist->srctext.ptr;
+        arglist = arglist->right;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_funccall(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    // RULE:    Identifier, "(", ArgumentList, ")";
+    if (token_is(token, DAVQL_TOKEN_IDENTIFIER) &&
+            token_is(token->next, DAVQL_TOKEN_OPENP)) {
+
+        expr->type = DAVQL_FUNCCALL;
+        expr->op = DAVQL_CALL;
+        
+        dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+        expr->left->type = DAVQL_IDENTIFIER;
+        expr->left->srctext = token_sstr(token);
+        expr->right = NULL;
+        
+        token = token->next->next;
+        
+        DavQLExpression arg;
+        int argtokens = dav_parse_arglist(stmt, token, &arg);
+        if (stmt->errorcode) {
+            // if an error occurred while parsing the arglist, return now
+            return 2;
+        }
+        if (argtokens) {
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), argtokens);
+            dqlsec_malloc(stmt, expr->right, DavQLExpression);
+            memcpy(expr->right, &arg, sizeof(DavQLExpression));
+        } else {
+            // arg list may be empty
+            expr->right = NULL;
+        }
+        
+        if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+            return 3 + argtokens;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+                stmt, token);
+            return 2; // it MUST be a function call, but it is invalid
+        }
+    } else {
+        return 0;
+    }
+}
+
+static int dav_parse_unary_expr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    DavQLToken *firsttoken = token; // save for srctext recovery
+    
+    DavQLExpression* atom = expr;
+    int total_consumed = 0;
+   
+    // optional unary operator
+    if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+        char *op = strchr("+-~", token_sstr(token).ptr[0]);
+        if (op) {
+            expr->type = DAVQL_UNARY;
+            switch (*op) {
+            case '+': expr->op = DAVQL_ADD; break;
+            case '-': expr->op = DAVQL_SUB; break;
+            case '~': expr->op = DAVQL_NEG; break;
+            }
+            dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+            atom = expr->left;
+            total_consumed++;
+            token = token->next;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_INVALID_UNARY_OP,
+                _error_invalid_unary_op, stmt, token);
+            return 0;
+        }
+    }
+    
+    // RULE:    (ParExpression | AtomicExpression)
+    if (token_is(token, DAVQL_TOKEN_OPENP)) {
+        token = token->next; total_consumed++;
+        // RULE:    "(", Expression, ")"
+        int consumed = dav_parse_expression(stmt, token, atom);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+                _error_invalid_expr, stmt, token);
+            return 0;
+        }
+        token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+        total_consumed += consumed;
+        if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+            token = token->next; total_consumed++;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_PAR,
+                _error_missing_par, stmt, token);
+            return 0;
+        }
+    } else {
+        // RULE:    FunctionCall
+        int consumed = dav_parse_funccall(stmt, token, atom);
+        if (consumed) {
+            total_consumed += consumed;
+        } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+            // RULE:    Identifier
+            total_consumed++;
+            atom->type = DAVQL_IDENTIFIER;
+            atom->srctext = token_sstr(token);
+        } else {
+            // RULE:    Literal
+            total_consumed += dav_parse_literal(stmt, token, atom);
+        }
+    }
+    
+    // recover source text
+    expr->srctext.ptr = token_sstr(firsttoken).ptr;
+    if (total_consumed > 0) {
+        cxstring lasttoken =
+            token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed-1));
+        expr->srctext.length =
+            lasttoken.ptr - expr->srctext.ptr + lasttoken.length;
+    } else {
+        // the expression should not be used anyway, but we want to be safe
+        expr->srctext.length = 0;
+    }
+    
+    
+    return total_consumed;
+}
+
+static int dav_parse_bitexpr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_unary_expr,
+        "&|^", (int[]){DAVQL_AND, DAVQL_OR, DAVQL_XOR},
+        dav_parse_bitexpr);
+}
+
+static int dav_parse_multexpr(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_bitexpr,
+        "*/", (int[]){DAVQL_MUL, DAVQL_DIV},
+        dav_parse_multexpr);
+}
+
+static int dav_parse_expression(DavQLStatement* stmt, DavQLToken* token,
+        DavQLExpression* expr) {
+    
+    return dav_parse_binary_expr(stmt, token, expr,
+        dav_parse_multexpr,
+        "+-", (int[]){DAVQL_ADD, DAVQL_SUB},
+        dav_parse_expression);
+}
+
+static int dav_parse_named_field(DavQLStatement *stmt, DavQLToken *token,
+        DavQLField *field) {
+    int total_consumed = 0, consumed;
+    
+    // RULE:    Expression, " as ", Identifier;
+    DavQLExpression *expr;
+    dqlsec_mallocz(stmt, expr, DavQLExpression);
+    consumed = dav_parse_expression(stmt, token, expr);
+    if (stmt->errorcode) {
+        dav_free_expression(expr);
+        return 0;
+    }
+    if (expr->type == DAVQL_UNDEFINED_TYPE) {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_INVALID_EXPR,
+            _error_invalid_expr, stmt, token);
+        return 0;
+    }
+
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+    total_consumed += consumed;    
+    
+    if (token_is(token, DAVQL_TOKEN_KEYWORD) && tokenvalue_is(token, "as")) {
+        token = token->next; total_consumed++;
+    } else {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_as, stmt, token);
+        return 0;
+    }
+
+    if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+        field->name = token_sstr(token);
+        field->expr = expr;
+        return total_consumed + 1;
+    } else {
+        dav_free_expression(expr);
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_identifier, stmt, token);
+        return 0;
+    }
+}
+
+static int dav_parse_fieldlist(DavQLStatement *stmt, DavQLToken *token) {
+    
+    // RULE:    "-"
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "-")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        if(dav_stmt_add_field(stmt, field)) {
+            free(field);
+            return 0;
+        }
+        dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+        field->expr->type = DAVQL_IDENTIFIER;
+        field->expr->srctext = field->name = token_sstr(token);
+        return 1;
+    }
+    
+    // RULE:    "*", {",", NamedExpression}
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "*")) {
+        DavQLField *field;
+        dqlsec_malloc(stmt, field, DavQLField);
+        if(dav_stmt_add_field(stmt, field)) {
+            free(field);
+            return 0;
+        }
+        dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+        field->expr->type = DAVQL_IDENTIFIER;
+        field->expr->srctext = field->name = token_sstr(token);
+        
+        int total_consumed = 0;
+        int consumed = 1;
+        
+        do {
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            total_consumed += consumed;
+            
+            if (token_is(token, DAVQL_TOKEN_COMMA)) {
+                total_consumed++; token = token->next;
+                DavQLField localfield;
+                consumed = dav_parse_named_field(stmt, token, &localfield);
+                if (!stmt->errorcode && consumed) {
+                    DavQLField *field;
+                    dqlsec_malloc(stmt, field, DavQLField);
+                    memcpy(field, &localfield, sizeof(DavQLField));
+                    if(dav_stmt_add_field(stmt, field)) {
+                        free(field);
+                        return 0;
+                    }
+                }                
+            } else {
+                consumed = 0;
+            }
+        } while (consumed > 0);
+        
+        return total_consumed;
+    }
+    
+    // RULE:    FieldExpression, {",", FieldExpression}
+    {
+        int total_consumed = 0, consumed;
+        do {
+            // RULE:    NamedField | Identifier
+            DavQLField localfield;
+            consumed = dav_parse_named_field(stmt, token, &localfield);
+            if (consumed) {
+                DavQLField *field;
+                dqlsec_malloc(stmt, field, DavQLField);
+                memcpy(field, &localfield, sizeof(DavQLField));
+                if(dav_stmt_add_field(stmt, field)) {
+                    free(field);
+                    return 0;
+                }
+                token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+                total_consumed += consumed;
+            } else if (token_is(token, DAVQL_TOKEN_IDENTIFIER)
+                // look ahead, if the field is JUST the identifier
+                && (token_is(token->next, DAVQL_TOKEN_COMMA) ||
+                    tokenvalue_is(token->next, "from"))) {
+                
+                DavQLField *field;
+                dqlsec_malloc(stmt, field, DavQLField);
+                dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+                field->expr->type = DAVQL_IDENTIFIER;
+                field->expr->srctext = field->name = token_sstr(token);
+                if(dav_stmt_add_field(stmt, field)) {
+                    free(field);
+                    return 0;
+                }
+
+                consumed = 1;
+                total_consumed++;
+                token = token->next;
+                
+                // we found a valid solution, so erase any errors
+                stmt->errorcode = 0;
+                if (stmt->errormessage) {
+                    free(stmt->errormessage);
+                    stmt->errormessage = NULL;
+                }
+            } else {
+                // dav_parse_named_field has already thrown a good error
+                consumed = 0;
+            }
+            
+            // field has been parsed, now try to get a comma
+            if (consumed) {
+                consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+                if (consumed) {
+                    token = token->next;
+                    total_consumed++;
+                }
+            }
+        } while (consumed);
+
+        return total_consumed;
+    }
+}
+
+// forward declaration
+static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr);
+
+static int dav_parse_bool_prim(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    expr->type = DAVQL_LOGICAL;
+    expr->srctext = token_sstr(token);
+    
+    int total_consumed = 0;
+
+    DavQLExpression bexpr;
+    memset(&bexpr, 0, sizeof(DavQLExpression));
+    total_consumed = dav_parse_expression(stmt, token, &bexpr);
+    if (!total_consumed || stmt->errorcode) {
+        return 0;
+    }
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), total_consumed);
+
+    DavQLToken* optok = token;
+    // RULE:    Expression, (" like " | " unlike "), String
+    if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (tokenvalue_is(optok,
+            "like") || tokenvalue_is(optok, "unlike"))) {
+
+        total_consumed++;
+        token = token->next;
+        if (token_is(token, DAVQL_TOKEN_STRING)) {
+            expr->op = tokenvalue_is(optok, "like") ?
+                DAVQL_LIKE : DAVQL_UNLIKE;
+            dqlsec_malloc(stmt, expr->left, DavQLExpression);
+            memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+            dqlsec_mallocz(stmt, expr->right, DavQLExpression);
+            expr->right->type = DAVQL_STRING;
+            expr->right->srctext = token_sstr(token);
+            expr->srctext.length = expr->right->srctext.ptr -
+                expr->srctext.ptr + expr->right->srctext.length;
+            
+            // fmt args
+            dav_add_fmt_args(stmt, expr->right->srctext);
+
+            return total_consumed + 1;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_INVALID_STRING,
+                _error_invalid_string, stmt, token);
+            return 0;
+        }        
+    }
+    // RULE:    Expression, Comparison, Expression
+    else if (token_is(optok, DAVQL_TOKEN_OPERATOR) && (
+            tokenvalue_is(optok, "=") || tokenvalue_is(optok, "!") ||
+            tokenvalue_is(optok, "<") || tokenvalue_is(optok, ">"))) {
+
+        total_consumed++;
+        token = token->next;
+
+        if (tokenvalue_is(optok, "=")) {
+            expr->op = DAVQL_EQ;
+        } else {
+            if (tokenvalue_is(token, "=")) {
+                if (tokenvalue_is(optok, "!")) {
+                    expr->op = DAVQL_NEQ;
+                } else if (tokenvalue_is(optok, "<")) {
+                    expr->op = DAVQL_LE;
+                } else if (tokenvalue_is(optok, ">")) {
+                    expr->op = DAVQL_GE;
+                }
+                total_consumed++;
+                token = token->next;
+            } else {
+                if (tokenvalue_is(optok, "<")) {
+                    expr->op = DAVQL_LT;
+                } else if (tokenvalue_is(optok, ">")) {
+                    expr->op = DAVQL_GT;
+                }
+            }
+        }
+
+        DavQLExpression rexpr;
+        memset(&rexpr, 0, sizeof(DavQLExpression));
+        int consumed = dav_parse_expression(stmt, token, &rexpr);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(
+                DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+                stmt, token);
+            return 0;
+        }
+
+        total_consumed += consumed;
+        dqlsec_malloc(stmt, expr->left, DavQLExpression);
+        memcpy(expr->left, &bexpr, sizeof(DavQLExpression));
+        dqlsec_malloc(stmt, expr->right, DavQLExpression);
+        memcpy(expr->right, &rexpr, sizeof(DavQLExpression));
+        
+        expr->srctext.length = expr->right->srctext.ptr -
+                expr->srctext.ptr + expr->right->srctext.length;
+
+        return total_consumed;
+    }
+    // RULE:    FunctionCall | Identifier;
+    else if (bexpr.type == DAVQL_FUNCCALL || bexpr.type == DAVQL_IDENTIFIER) {
+        memcpy(expr, &bexpr, sizeof(DavQLExpression));
+
+        return total_consumed;
+    } else {
+        return 0;
+    }
+}
+
+static int dav_parse_bool_expr(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    // RULE:    "not ", LogicalExpression
+    if (token_is(token, DAVQL_TOKEN_OPERATOR) && tokenvalue_is(token, "not")) {
+        expr->type = DAVQL_LOGICAL;
+        expr->op = DAVQL_NOT;
+        dqlsec_mallocz(stmt, expr->left, DavQLExpression);
+        expr->srctext = token_sstr(token);
+
+        token = token->next;
+        int consumed = dav_parse_bool_expr(stmt, token, expr->left);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (consumed) {
+            cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed-1));
+            expr->srctext.length =
+                lasttok.ptr - expr->srctext.ptr + lasttok.length;
+            return consumed + 1;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                _error_missing_expr, stmt, token);
+            return 0;
+        }
+    }
+    // RULE:    "(", LogicalExpression, ")"
+    else if (token_is(token, DAVQL_TOKEN_OPENP)) {
+        int consumed = dav_parse_logical_expr(stmt, token->next, expr);
+        if (consumed) {
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+            if (token_is(token, DAVQL_TOKEN_CLOSEP)) {
+                token = token->next;
+                return consumed + 2;
+            } else {
+                dav_error_in_context(DAVQL_ERROR_MISSING_PAR, _error_missing_par,
+                    stmt, token);
+                return 0;
+            }
+        } else {
+            // don't handle errors here, we can also try a boolean primary
+            stmt->errorcode = 0;
+            if (stmt->errormessage) {
+                free(stmt->errormessage);
+            }
+        }
+    }
+    
+    // RULE:    BooleanPrimary
+    return dav_parse_bool_prim(stmt, token, expr);
+}
+
+static int dav_parse_logical_expr(DavQLStatement *stmt, DavQLToken *token,
+        DavQLExpression *expr) {
+    
+    DavQLToken *firsttoken = token;
+    int total_consumed = 0;
+    
+    // RULE:    BooleanLiteral, [LogicalOperator, LogicalExpression];
+    DavQLExpression left, right;
+    memset(&left, 0, sizeof(DavQLExpression));
+    int consumed = dav_parse_bool_expr(stmt, token, &left);
+    if (stmt->errorcode) {
+        return 0;
+    }
+    if (!consumed) {
+        dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+            _error_missing_expr, stmt, token);
+        return 0;
+    }
+    total_consumed += consumed;
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+    if (token_is(token, DAVQL_TOKEN_OPERATOR)) {
+        expr->type = DAVQL_LOGICAL;
+
+        davqloperator_t op = DAVQL_NOOP;
+        if (tokenvalue_is(token, "and")) {
+            op = DAVQL_LAND;
+        } else if (tokenvalue_is(token, "or")) {
+            op = DAVQL_LOR;
+        } else if (tokenvalue_is(token, "xor")) {
+            op = DAVQL_LXOR;
+        }
+
+        if (op == DAVQL_NOOP) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_LOGICAL_OP,
+                _error_invalid_logical_op, stmt, token);
+            return 0;
+        } else {
+            expr->op = op;
+            total_consumed++;
+            token = token->next;
+
+            memset(&right, 0, sizeof(DavQLExpression));
+            consumed = dav_parse_logical_expr(stmt, token, &right);
+            if (stmt->errorcode) {
+                return 0;
+            }
+            if (!consumed) {
+                dav_error_in_context(DAVQL_ERROR_MISSING_EXPR,
+                    _error_missing_expr, stmt, token);
+                return 0;
+            }
+            total_consumed += consumed;
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+
+            dqlsec_malloc(stmt, expr->left, DavQLExpression);
+            memcpy(expr->left, &left, sizeof(DavQLExpression));
+            dqlsec_malloc(stmt, expr->right, DavQLExpression);
+            memcpy(expr->right, &right, sizeof(DavQLExpression));
+        }
+    } else {
+        memcpy(expr, &left, sizeof(DavQLExpression));
+    }
+    
+    // set type and recover source text
+    if (total_consumed > 0) {        
+        expr->srctext.ptr = token_sstr(firsttoken).ptr;
+        cxstring lasttok = token_sstr((DavQLToken*)cx_linked_list_at(firsttoken, 0, offsetof(DavQLToken, next), total_consumed-1));
+        expr->srctext.length = lasttok.ptr-expr->srctext.ptr+lasttok.length;
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_where_clause(DavQLStatement *stmt, DavQLToken *token) {
+    dqlsec_mallocz(stmt, stmt->where, DavQLExpression);
+    
+    return dav_parse_logical_expr(stmt, token, stmt->where);
+}
+
+static int dav_parse_with_clause(DavQLStatement *stmt, DavQLToken *token) {
+
+    int total_consumed = 0;
+    
+    // RULE:    "depth", "=", (Number | "infinity")
+    if (tokenvalue_is(token, "depth")) {
+        token = token->next; total_consumed++;
+        if (tokenvalue_is(token, "=")) {
+            token = token->next; total_consumed++;
+            if (tokenvalue_is(token, "infinity")) {
+                stmt->depth = DAV_DEPTH_INFINITY;
+                token = token->next; total_consumed++;
+            } else {
+                DavQLExpression *depthexpr;
+                dqlsec_mallocz(stmt, depthexpr, DavQLExpression);
+                
+                int consumed = dav_parse_expression(stmt, token, depthexpr);
+
+                if (consumed) {
+                    if (depthexpr->type == DAVQL_NUMBER) {
+                        if (depthexpr->srctext.ptr[0] == '%') {
+                            stmt->depth = DAV_DEPTH_PLACEHOLDER;
+                        } else {
+                            cxstring depthstr = depthexpr->srctext;
+                            char *conv = malloc(depthstr.length+1);
+                            if (!conv) {
+                                dav_free_expression(depthexpr);
+                                stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+                                return 0;
+                            }
+                            char *chk;
+                            memcpy(conv, depthstr.ptr, depthstr.length);
+                            conv[depthstr.length] = '\0';
+                            stmt->depth = strtol(conv, &chk, 10);
+                            if (*chk || stmt->depth < -1) {
+                                dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+                                    _error_invalid_depth, stmt, token);
+                            }
+                            free(conv);
+                        }
+                        total_consumed += consumed;
+                    } else {
+                        dav_error_in_context(DAVQL_ERROR_INVALID_DEPTH,
+                            _error_invalid_depth, stmt, token);
+                    }
+                }
+                
+                dav_free_expression(depthexpr);
+            }
+        }
+    }
+    
+    return total_consumed;
+}
+
+static int dav_parse_order_crit(DavQLStatement *stmt, DavQLToken *token,
+    DavQLOrderCriterion *crit) {
+    
+    // RULE:    (Identifier | Number), [" asc"|" desc"];
+    DavQLExpression expr;
+    memset(&expr, 0, sizeof(DavQLExpression));
+    int consumed = dav_parse_expression(stmt, token, &expr);
+    if (stmt->errorcode || !consumed) {
+        return 0;
+    }
+    
+    if (expr.type != DAVQL_IDENTIFIER && expr.type != DAVQL_NUMBER) {
+        dav_error_in_context(DAVQL_ERROR_INVALID_ORDER_CRITERION,
+            _error_invalid_order_criterion, stmt, token);
+        return 0;
+    }
+    
+    dqlsec_malloc(stmt, crit->column, DavQLExpression);
+    memcpy(crit->column, &expr, sizeof(DavQLExpression));
+    
+    token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+    if (token_is(token, DAVQL_TOKEN_KEYWORD) && (
+            tokenvalue_is(token, "asc") || tokenvalue_is(token, "desc"))) {
+        
+        crit->descending = tokenvalue_is(token, "desc");
+        
+        return consumed+1;
+    } else {
+        crit->descending = 0;
+        return consumed;
+    }
+}
+
+static int dav_parse_orderby_clause(DavQLStatement *stmt, DavQLToken *token) {
+    
+    int total_consumed = 0, consumed;
+    
+    DavQLOrderCriterion crit;
+    
+    if(!stmt->orderby) {
+        stmt->orderby = cxLinkedListCreateSimple(sizeof(DavQLOrderCriterion));
+        if(!stmt->orderby) {
+            return 0;
+        }
+    }
+    
+    // RULE:    OrderByCriterion, {",", OrderByCriterion};
+    do {
+        consumed = dav_parse_order_crit(stmt, token, &crit);
+        if (stmt->errorcode) {
+            return 0;
+        }
+        if (!consumed) {
+            dav_error_in_context(DAVQL_ERROR_MISSING_EXPR, _error_missing_expr,
+                stmt, token);
+            return 0;
+        }
+        token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+        total_consumed += consumed;
+        
+        if(cxListAdd(stmt->orderby, &crit)) {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+            return 0;
+        }
+        
+        if (token_is(token, DAVQL_TOKEN_COMMA)) {
+            total_consumed++;
+            token = token->next;
+        } else {
+            consumed = 0;
+        }
+    } while (consumed);
+    
+    return total_consumed;
+}
+
+
+static int dav_parse_assignments(DavQLStatement *stmt, DavQLToken *token) {
+    
+    // RULE:    Assignment, {",", Assignment}
+    int total_consumed = 0, consumed;
+    do {
+        // RULE:    Identifier, "=", Expression
+        if (token_is(token, DAVQL_TOKEN_IDENTIFIER)) {
+
+            // Identifier
+            DavQLField *field;
+            dqlsec_malloc(stmt, field, DavQLField);
+            field->name = token_sstr(token);
+            total_consumed++;
+            token = token->next;
+            
+            // "="
+            if (!token_is(token, DAVQL_TOKEN_OPERATOR)
+                    || !tokenvalue_is(token, "=")) {
+                dav_free_field(field);
+                
+                dav_error_in_context(DAVQL_ERROR_MISSING_ASSIGN,
+                    _error_missing_assign, stmt, token);
+                return total_consumed;
+            }
+            total_consumed++;
+            token = token->next;
+
+            // Expression
+            dqlsec_mallocz(stmt, field->expr, DavQLExpression);
+            consumed = dav_parse_expression(stmt, token, field->expr);
+            if (stmt->errorcode) {
+                dav_free_field(field);
+                return total_consumed;
+            }
+            token = cx_linked_list_at(token, 0, offsetof(DavQLToken, next), consumed);
+            total_consumed += consumed;
+            
+            // Add assignment to list and check if there's another one
+            if(dav_stmt_add_field(stmt, field)) {
+                free(field);
+                return 0;
+            }
+            consumed = token_is(token, DAVQL_TOKEN_COMMA) ? 1 : 0;
+            if (consumed) {
+                token = token->next;
+                total_consumed++;
+            }
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                    _error_missing_identifier, stmt, token);
+            return total_consumed;
+        }
+    } while (consumed);
+
+    return total_consumed;
+}
+
+static int dav_parse_path(DavQLStatement *stmt, DavQLToken *tokens) {
+    if (token_is(tokens, DAVQL_TOKEN_STRING)) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        return 1;
+    } else if (token_is(tokens, DAVQL_TOKEN_OPERATOR)
+            && tokenvalue_is(tokens, "/")) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        int consumed = 1;
+        while (!token_is(tokens, DAVQL_TOKEN_KEYWORD) &&
+                !token_is(tokens, DAVQL_TOKEN_END)) {
+            cxstring toksstr = token_sstr(tokens);
+            stmt->path.length = toksstr.ptr-stmt->path.ptr+toksstr.length;
+            tokens = tokens->next;
+            consumed++;
+        }
+        return consumed;
+    } else if (token_is(tokens, DAVQL_TOKEN_FMTSPEC) &&
+            tokenvalue_is(tokens, "%s")) {
+        stmt->path = token_sstr(tokens);
+        tokens = tokens->next;
+        fmt_args_add(stmt, (void*)(intptr_t)'s');
+        return 1;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+            _error_missing_path, stmt, tokens);
+        return 0;
+    }
+}
+
+/**
+ * Parser of a select statement.
+ * @param stmt the statement object that shall contain the syntax tree
+ * @param tokens the token list
+ */
+static void dav_parse_select_statement(DavQLStatement *stmt, DavQLToken *tokens) {
+    stmt->type = DAVQL_SELECT;
+
+    // Consume field list
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_fieldlist(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume FROM keyword
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "from")) {
+        tokens = tokens->next;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_from, stmt, tokens);
+        return;
+    }
+    
+    // Consume path
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    //dav_add_fmt_args(stmt, stmt->path); // add possible path args
+    
+    // Consume with clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "with")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+            dav_parse_with_clause(stmt, tokens));
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume where clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "where")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+            dav_parse_where_clause(stmt, tokens));
+    } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "anywhere")) {
+        // useless, but the user may want to explicitly express his intent
+        tokens = tokens->next;
+        stmt->where = NULL;
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume order by clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "order")) {
+        tokens = tokens->next;
+        if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+                && tokenvalue_is(tokens, "by")) {
+            tokens = tokens->next;
+            tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next),
+                dav_parse_orderby_clause(stmt, tokens));
+        } else {
+            dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_by, stmt, tokens);
+            return;
+        }
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    
+    if (tokens) {
+        if (token_is(tokens, DAVQL_TOKEN_INVALID)) {
+            dav_error_in_context(DAVQL_ERROR_INVALID_TOKEN,
+                _error_invalid_token, stmt, tokens);
+        } else if (!token_is(tokens, DAVQL_TOKEN_END)) {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _error_unexpected_token, stmt, tokens);
+        }
+    }
+}
+
+static void dav_parse_set_statement(DavQLStatement *stmt, DavQLToken *tokens) {
+    stmt->type = DAVQL_SET;
+    
+    // Consume assignments
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_assignments(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume AT keyword
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "at")) {
+        tokens = tokens->next;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_at, stmt, tokens);
+        return;
+    }
+
+    // Consume path
+    tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), dav_parse_path(stmt, tokens));
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume with clause (if any)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "with")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 
+            dav_parse_with_clause(stmt, tokens));
+    }
+    if (stmt->errorcode) {
+        return;
+    }
+    
+    // Consume mandatory where clause (or anywhere keyword)
+    if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "where")) {
+        tokens = tokens->next;
+        tokens = cx_linked_list_at(tokens, 0, offsetof(DavQLToken, next), 
+            dav_parse_where_clause(stmt, tokens));
+    } else if (token_is(tokens, DAVQL_TOKEN_KEYWORD)
+            && tokenvalue_is(tokens, "anywhere")) {
+        // no-op, but we want the user to be explicit about this
+        tokens = tokens->next;
+        stmt->where = NULL;
+    } else {
+        dav_error_in_context(DAVQL_ERROR_MISSING_TOKEN,
+                _error_missing_where, stmt, tokens);
+        return;
+    }
+}
+
+DavQLStatement* dav_parse_statement(cxstring srctext) {
+    DavQLStatement *stmt = calloc(1, sizeof(DavQLStatement));
+    
+    // if we can't even get enough memory for the statement object or an error
+    // message, we can simply die without returning anything
+    if (!stmt) {
+        return NULL;
+    }
+    char *oommsg = strdup(_error_out_of_memory);
+    if (!oommsg) {
+        free(stmt);
+        return NULL;
+    }
+    
+    // default values
+    stmt->type = -1;
+    stmt->depth = 1;
+    
+    // save trimmed source text
+    stmt->srctext = cx_strtrim(srctext);
+    
+    if (stmt->srctext.length) {   
+        // tokenization
+        DavQLToken* tokens = dav_parse_tokenize(stmt->srctext);
+
+        if (tokens) {
+            // use first token to determine query type
+
+            if (tokenvalue_is(tokens, "select")) {
+                dav_parse_select_statement(stmt, tokens->next);
+            } else if (tokenvalue_is(tokens, "set")) {
+                dav_parse_set_statement(stmt, tokens->next);
+            } else {
+                stmt->type = DAVQL_ERROR;
+                stmt->errorcode = DAVQL_ERROR_INVALID;
+                stmt->errormessage = strdup(_error_invalid);
+            }
+
+            // free token data
+            tokenlist_free(tokens);
+        } else {
+            stmt->errorcode = DAVQL_ERROR_OUT_OF_MEMORY;
+        }
+    } else {
+        stmt->type = DAVQL_ERROR;
+        stmt->errorcode = DAVQL_ERROR_INVALID;
+        stmt->errormessage = strdup(_error_invalid);
+    }
+    
+    if (stmt->errorcode == DAVQL_ERROR_OUT_OF_MEMORY) {
+        stmt->type = DAVQL_ERROR;
+        stmt->errormessage = oommsg;
+    } else {
+        free(oommsg);
+    }
+    
+    return stmt;
+}
+
+void dav_free_statement(DavQLStatement *stmt) {
+    if(stmt->fields) {
+        stmt->fields->simple_destructor = (cx_destructor_func)dav_free_field;
+        cxListDestroy(stmt->fields);
+    }
+    
+    if (stmt->where) {
+        dav_free_expression(stmt->where);
+    }
+    if (stmt->errormessage) {
+        free(stmt->errormessage);
+    }
+    
+    if(stmt->orderby) {
+        stmt->orderby->simple_destructor = (cx_destructor_func)dav_free_order_criterion;
+        cxListDestroy(stmt->orderby);
+    }
+    if(stmt->args) {
+        cxListDestroy(stmt->args);
+    }
+    free(stmt);
+}

mercurial