merge

Sat, 02 May 2015 11:00:28 +0200

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 02 May 2015 11:00:28 +0200
changeset 101
95a215337b53
parent 100
f4127c4d1018 (current diff)
parent 99
579238097973 (diff)
child 102
e9ae1318a559

merge

--- a/libidav/davqlparser.c	Sat May 02 10:59:02 2015 +0200
+++ b/libidav/davqlparser.c	Sat May 02 11:00:28 2015 +0200
@@ -49,7 +49,10 @@
 
 static const char* _map_exprtype(davqlexprtype_t type) {
     switch(type) {
-    case DAVQL_LITERAL: return "LITERAL";
+    case DAVQL_UNDEFINED_TYP: 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";
@@ -90,13 +93,12 @@
     // Basic information
     size_t fieldcount = ucx_list_size(stmt->fields);
     int specialfield = 0;
-    UCX_FOREACH(elm, stmt->fields) {
-        DavQLExpression* expr = (DavQLExpression*)elm->data;
-        if (expr->type == DAVQL_IDENTIFIER && expr->srctext.length == 1) {
-            if (expr->srctext.ptr[0] == '*') {
-                specialfield = 1;
-            } else if (expr->srctext.ptr[0] == '-') {
-                specialfield = 2;
+    if (stmt->fields) {
+        DavQLField* firstfield = (DavQLField*)stmt->fields->data;
+        if (firstfield->expr->type == DAVQL_IDENTIFIER) {
+            switch (firstfield->expr->srctext.ptr[0]) {
+            case '*': specialfield = 1; break;
+            case '-': specialfield = 2; break;
             }
         }
     }
@@ -111,18 +113,30 @@
         _map_specialfield(specialfield),
         sfmtarg(stmt->path),
         stmt->where ? "yes" : "no");
-    if (stmt->type == DAVQL_SET) {
-        printf("Value list size matches: %s",
-            ucx_list_size(stmt->fields) == ucx_list_size(stmt->setvalues)
-            ? "yes" : "no");
-    }
     
     // WITH attributes
-    if (stmt->depth < 0) {
+    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) {
+        UCX_FOREACH(crit, stmt->orderby) {
+            DavQLOrderCriterion *critdata = crit->data;
+            printf("%.*s %s%s", sfmtarg(critdata->column->srctext),
+                critdata->descending ? "desc" : "asc",
+                crit->next ? ", " : "\n");
+        }
+    } else {
+        printf("nothing\n");
+    }
+    
+    // error messages
     if (stmt->errorcode) {
         printf("\nError code: %d\nError: %s\n",
             stmt->errorcode, stmt->errormessage);
@@ -142,29 +156,41 @@
     if (dav_debug_ql_expr_selected(expr)) {
         sstr_t empty = ST("(empty)");
         printf(
-            "Text: %.*s\nType: %s\nOperator: %s\n"
-            "Left hand: %.*s\nRight hand: %.*s\n",
+            "Text: %.*s\nType: %s\nOperator: %s\n",
             sfmtarg(expr->srctext),
             _map_exprtype(expr->type),
-            _map_operator(expr->op),
-            sfmtarg(expr->left?expr->left->srctext:empty),
-            sfmtarg(expr->right?expr->right->srctext:empty));
+            _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));
+        }
     }
 }
 
 #define DQLD_CMD_Q     0
 #define DQLD_CMD_PS    1
 #define DQLD_CMD_PE    2
-#define DQLD_CMD_P    10
+#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[16];
-    fgets(buffer, 16, stdin);
+    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")) {
@@ -177,6 +203,16 @@
         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;
     }
@@ -196,6 +232,8 @@
     }
     
     DavQLExpression *examineexpr = NULL;
+    UcxList *examineelem = NULL;
+    int examineclause = 0;
     
     while(1) {
         int cmd = dav_debug_ql_command();
@@ -203,6 +241,52 @@
         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_F:
+            if (examineclause != DQLD_CMD_F) {
+                examineclause = DQLD_CMD_F;
+                examineelem = stmt->fields;
+                examineexpr = stmt->fields ?
+                    ((DavQLField*)stmt->fields->data)->expr : NULL;
+                dav_debug_ql_expr_print(examineexpr);
+            }
+            break;
+        case DQLD_CMD_W:
+            examineclause = 0; examineelem = NULL;
+            examineexpr = stmt->where;
+            dav_debug_ql_expr_print(examineexpr);
+            break;
+        case DQLD_CMD_O:
+            if (examineclause != DQLD_CMD_O) {
+                examineclause = DQLD_CMD_O;
+                examineelem = stmt->orderby;
+                examineexpr = stmt->orderby ?
+                    ((DavQLOrderCriterion*)stmt->orderby->data)->column : NULL;
+                dav_debug_ql_expr_print(examineexpr);
+            }
+            break;
+        case DQLD_CMD_N:
+        case DQLD_CMD_P:
+            if (examineelem) {
+                UcxList *newelem = (cmd == DQLD_CMD_N ?
+                    examineelem->next : examineelem->prev);
+                if (newelem) {
+                    examineelem = newelem;
+                    if (examineclause == DQLD_CMD_O) {
+                        examineexpr = ((DavQLOrderCriterion*)
+                            examineelem->data)->column;
+                    } else if (examineclause == DQLD_CMD_F) {
+                        examineexpr = ((DavQLField*)examineelem->data)->expr;
+                    } else {
+                        printf("Examining unknown clause type.");
+                    }
+                    dav_debug_ql_expr_print(examineexpr);
+                } 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) {
@@ -227,6 +311,13 @@
             printf(
                 "\nCommands:\n"
                 "ps:  print statement information\n"
+                "o:   examine order by clause\n"
+                "f:   examine field list\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"
@@ -245,12 +336,41 @@
 #define _unexpected_end_msg "unexpected end of statement"
 #define _invalid_msg "invalid statement"
 #define _unexpected_token "unexpected token (%.*s [->]%.*s %.*s)"
+#define _expected_token "expected token '%s' before '%.*s'"
+#define _expected_by "expected 'by' after 'order' (order [->]%.*s)"
+#define _missing_fmtspec "format specifier missing (%.*s [->]%.*s %.*s)"
+#define _invalid_fmtspec "invalid format specifier (%.*s [->]%.*s %.*s)"
+#define _unknown_fmtspec "unknown format specifier (%.*s [->]%.*s %.*s)"
 #define _missing_quote "missing closing quote symbol (%.*s)"
+#define _parser_state "parser reached invalid state"
+#define _unknown_attribute "unknown attribute '%.*s'"
+#define _duplicated_attribute "duplicated attribute '%.*s'"
+#define _invalid_depth "invalid depth"
+#define _invalid_path "invalid path"
+
+#define _identifier_expected "identifier expected (%.*s [->]%.*s %.*s)"
+#define _idornum_expected "identifier or number expected (%.*s [->]%.*s %.*s)"
+#define _idorstr_expected "identifier or string expected (%.*s [->]%.*s %.*s)"
+#define _idorts_expected "identifier or timestamp expected (%.*s [->]%.*s %.*s)"
+
+#define token_sstr(listelem) ((sstr_t*)(listelem)->data)
+
+static void dav_error_in_context(int errorcode, const char *errormsg,
+        DavQLStatement *stmt, UcxList *token) {
+    sstr_t emptystring = ST("");
+    stmt->errorcode = errorcode;
+    stmt->errormessage = ucx_sprintf(errormsg,
+        sfmtarg(token->prev?*token_sstr(token->prev):emptystring),
+        sfmtarg(*token_sstr(token)),
+        sfmtarg(token->next?*token_sstr(token->next):emptystring)).ptr;
+}
+
+// special symbols are single tokens - the % sign MUST NOT be a special symbol
+static const char *special_token_symbols = ",()+-*/&|^~=!<>";
 
 static UcxList* dav_parse_tokenize(sstr_t src) {
     UcxList *tokens = NULL;
     
-    // Delimiters: whitespace and dead whitespace around commas
     sstr_t *token = NULL;
     char insequence = '\0';
     for (size_t i = 0 ; i < src.length ; i++) {
@@ -283,13 +403,13 @@
                 tokens = ucx_list_append(tokens, token);
                 token = NULL;
             }
-        } else if (src.ptr[i] == ',') {
-            // add token before comma to list (if any)
+        } else if (strchr(special_token_symbols, src.ptr[i])) {
+            // add token before special symbol to list (if any)
             if (token) {
                 tokens = ucx_list_append(tokens, token);
                 token = NULL;
             }
-            // add comma as token to list
+            // add special symbol as single token to list
             token = malloc(sizeof(sstr_t));
             token->ptr = src.ptr + i;
             token->length = 1;
@@ -315,7 +435,6 @@
     return tokens;
 }
 
-#define token_sstr(listelem) ((sstr_t*)(listelem)->data)
 static DavQLExpression* dav_parse_expression(
         DavQLStatement* stmt, UcxList* starttoken, size_t n) {
     if (n == 0) {
@@ -332,8 +451,27 @@
         expr->srctext.length = token_sstr(starttoken)->length;
         char firstchar = expr->srctext.ptr[0];
         char lastchar = expr->srctext.ptr[expr->srctext.length-1];
-        if (firstchar == '\'' || isdigit(firstchar)) {
-            expr->type = DAVQL_LITERAL;
+        if (firstchar == '\'') {
+            expr->type = DAVQL_STRING;
+        } else if (isdigit(firstchar)) {
+            expr->type = DAVQL_NUMBER;
+        } else if (firstchar == '%') {
+            if (expr->srctext.length == 1) {
+                dav_error_in_context(DAVQL_ERROR_MISSING_FMTSPEC,
+                    _missing_fmtspec, stmt, starttoken);
+            } else if (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_UNKNOWN_FMTSPEC,
+                        _unknown_fmtspec, stmt, starttoken);
+                }
+            } else {
+                dav_error_in_context(DAVQL_ERROR_INVALID_FMTSPEC,
+                        _invalid_fmtspec, stmt, starttoken);
+            }
         } else {
             expr->type = DAVQL_IDENTIFIER;
         }
@@ -367,6 +505,7 @@
 
         // process tokens        
         for (size_t i = 0 ; i < n ; i++) {
+            sstr_t tokendata = *token_sstr(token);
             
             // TODO: make it so
 
@@ -393,107 +532,437 @@
     }
     free(expr);
 }
+    
+#define _step_fieldlist_    10 // field list
+#define _step_FROM_         20 // FROM clause
+#define _step_WITH_         30 // WITH clause
+#define _step_WHERE_        40 // WHERE clause
+#define _step_ORDER_BYopt_ 552 // expecting more ORDER BY details or end
+#define _step_ORDER_BY_     50 // ORDER BY clause
+#define _step_end_         500 // expect end
 
-static void dav_parse_unexpected_token(DavQLStatement *stmt, UcxList *token) {
-    sstr_t emptystring = ST("");
-    stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN;
-    sstr_t errormsg = ucx_sprintf(_unexpected_token,
-        sfmtarg(token->prev?*token_sstr(token->prev):emptystring),
-        sfmtarg(*token_sstr(token)),
-        sfmtarg(token->next?*token_sstr(token->next):emptystring));
-    stmt->errormessage = errormsg.ptr;
+struct fieldlist_parser_state {
+    UcxList *expr_firsttoken;
+    DavQLField *currentfield;
+    size_t expr_len;
+    /*
+     * 0: begin of field list - may encounter "*" or "-" special fields
+     * 1: collect expression token
+     *    switch to step 2 on keyword "as"
+     *    expect "," or "from" only if expr_len is 1 (add to list and continue)
+     * 2: expect one token (identifier) for as clause
+     * 3: expect a ",": continue with step 1
+     *    or a "from": leave field list parser
+     * 4: expect end of field list (i.e. a "from" keyword)
+     */
+    int step;
+};
+
+static void dav_free_field(DavQLField *field) {
+    dav_free_expression(field->expr);
+    free(field);
+}
+
+static int dav_parse_fieldlist(DavQLStatement *stmt, UcxList *token,
+        struct fieldlist_parser_state *state) {
+    sstr_t tokendata = *token_sstr(token);
+
+    _Bool fromkeyword = !sstrcasecmp(tokendata, S("from"));
+    _Bool comma = !sstrcmp(tokendata, S(","));
+    
+    switch (state->step) {
+    case 0:
+        if (!sstrcmp(tokendata, S("*")) || !sstrcmp(tokendata, S("-"))) {
+            DavQLField *field = malloc(sizeof(DavQLField));
+            field->name = tokendata;
+            field->expr = calloc(1, sizeof(DavQLExpression));
+            field->expr->type = DAVQL_IDENTIFIER;
+            field->expr->srctext = tokendata;
+            stmt->fields = ucx_list_append(stmt->fields, field);
+            
+            if (tokendata.ptr[0] == '-') {
+                // no further fields may follow, if dash symbol has been found
+                state->step = 4;
+            } else {
+                state->step = 3;
+            }
+            return _step_fieldlist_;
+        }
+        // did not encounter special field, fall through to step 1
+        state->step = 1;
+    case 1:
+        if (fromkeyword || comma) {
+            // add possible identifier to list
+            if (state->expr_firsttoken) {
+                // TODO: skip comma in function call)
+                if (state->expr_len > 1) {
+                    stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN;
+                    stmt->errormessage = ucx_sprintf(_expected_token,
+                        "AS", sfmtarg(tokendata)).ptr;
+                    return 0;
+                }
+                
+                DavQLExpression *expr = dav_parse_expression(
+                    stmt, state->expr_firsttoken, state->expr_len);
+                
+                if (expr->type != DAVQL_IDENTIFIER) {
+                    dav_free_expression(expr);
+                    stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN;
+                    stmt->errormessage = ucx_sprintf(_expected_token,
+                        "AS", sfmtarg(tokendata)).ptr;
+                    return 0;
+                } // TODO: do not allow identifier when wildcard is present
+                
+                DavQLField *field = malloc(sizeof(DavQLField));
+                field->expr = expr;
+                field->name = field->expr->srctext;
+                stmt->fields = ucx_list_append(stmt->fields, field);
+                
+                state->expr_firsttoken = NULL;
+                state->expr_len = 0;
+
+                if (fromkeyword) {
+                    return _step_FROM_;
+                }
+            } else {
+                dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                    _unexpected_token, stmt, token);
+                return 0;
+            }
+        } else if (!sstrcasecmp(tokendata, S("as"))) {
+            // TODO: return error, if expr_first_token is NULL
+            state->currentfield = malloc(sizeof(DavQLField));
+            state->currentfield->expr = dav_parse_expression(
+                stmt, state->expr_firsttoken, state->expr_len);
+                
+            state->expr_firsttoken = NULL;
+            state->expr_len = 0;
+                
+            state->step = 2;
+        } else {
+            // collect tokens for field expression
+            if (state->expr_firsttoken) {
+                state->expr_len++;
+            } else {
+                state->expr_firsttoken = token;
+                state->expr_len = 1;
+            }
+        }
+        
+        return _step_fieldlist_;
+    case 2: {
+        DavQLExpression *expr = dav_parse_expression(stmt, token, 1);
+        if (expr->type == DAVQL_IDENTIFIER) {
+            state->currentfield->name = expr->srctext;
+            stmt->fields = ucx_list_append(stmt->fields, state->currentfield);
+            state->currentfield = NULL;
+        } else {
+            dav_free_field(state->currentfield);
+            dav_error_in_context(DAVQL_ERROR_IDENTIFIER_EXPECTED,
+                _identifier_expected, stmt, token);
+            
+        }
+        dav_free_expression(expr);
+        state->step = 3;
+        
+        return _step_fieldlist_;
+    }
+    case 3:
+        if (fromkeyword) {
+            return _step_FROM_;
+        } else if (comma) {
+            state->step = 1;
+            return _step_fieldlist_;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _unexpected_token, stmt, token);
+            return 0;
+        }
+    case 4:
+        if (fromkeyword) {
+            return _step_FROM_;
+        } else {
+            stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN;
+            stmt->errormessage = ucx_sprintf(_expected_token,
+                "FROM", sfmtarg(tokendata)).ptr;
+            return 0;
+        }
+    }
+}
+
+static int dav_parse_from(DavQLStatement *stmt, UcxList *token) {
+    sstr_t tokendata = *token_sstr(token);
+    
+    if (!sstrcasecmp(tokendata, S("with"))) {
+        return _step_WITH_;
+    } else if (!sstrcasecmp(tokendata, S("where"))) {
+        return _step_WHERE_;
+    } else if (!sstrcasecmp(tokendata, S("order"))) {
+        return _step_ORDER_BY_;
+    } else {
+        if (stmt->path.ptr) {
+            if (stmt->path.ptr[0] == '/') {
+                char *end = tokendata.ptr+tokendata.length;
+                stmt->path.length = end - stmt->path.ptr;
+            } else {
+                stmt->errorcode = DAVQL_ERROR_INVALID_PATH;
+                stmt->errormessage = strdup(_invalid_path);
+            }
+        } else {
+            if (tokendata.ptr[0] == '/' || !sstrcmp(tokendata, S("%s"))) {
+                stmt->path = tokendata;
+            } else {
+                stmt->errorcode = DAVQL_ERROR_INVALID_PATH;
+                stmt->errormessage = strdup(_invalid_path);
+            }
+        }
+        return _step_FROM_;
+    }
+}
+
+struct with_parser_state {
+    /*
+     * 0: key
+     * 1: =
+     * 2: value
+     * 3: comma or new clause
+     */
+    int step;
+    /*
+     * 1: depth
+     */
+    int key;
+    int keymask;
+};
+
+static int dav_parse_with_clause(DavQLStatement *stmt, UcxList *token,
+        struct with_parser_state *state) {
+    sstr_t tokendata = *token_sstr(token);
+
+    switch (state->step) {
+    case 0:
+        if (!sstrcasecmp(tokendata, S("depth"))) {
+            state->key = 1;
+            state->step = 1;
+        } else {
+            stmt->errorcode = DAVQL_ERROR_UNKNOWN_ATTRIBUTE;
+            stmt->errormessage = ucx_sprintf(_unknown_attribute,
+                sfmtarg(tokendata)).ptr;
+            break;
+        }
+        if (state->keymask & state->key) {
+            stmt->errorcode = DAVQL_ERROR_DUPLICATED_ATTRIBUTE;
+            stmt->errormessage = ucx_sprintf(_duplicated_attribute,
+                sfmtarg(tokendata)).ptr;
+        } else {
+            state->keymask |= state->key;
+        }
+        return _step_WITH_; // continue parsing WITH clause
+    case 1:
+        if (sstrcmp(tokendata, S("="))) {
+            stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN;
+            stmt->errormessage = ucx_sprintf(_expected_token,
+                "=", sfmtarg(tokendata)).ptr;
+        } else {
+            state->step = 2;
+        }
+        return _step_WITH_; // continue parsing WITH clause
+    case 2:
+        switch (state->key) {
+        case 1: /* depth */
+            if (!sstrcasecmp(tokendata, S("infinity"))) {
+                stmt->depth = DAV_DEPTH_INFINITY;
+            } else {
+                DavQLExpression *depthexpr =
+                    dav_parse_expression(stmt, token, 1);
+                
+                if (depthexpr->type == DAVQL_NUMBER) {
+                    if (depthexpr->srctext.ptr[0] == '%') {
+                        stmt->depth = DAV_DEPTH_PLACEHOLDER;
+                    } else {
+                        sstr_t depthstr = depthexpr->srctext;
+                        char *conv = malloc(depthstr.length+1);
+                        char *chk;
+                        memcpy(conv, depthstr.ptr, depthstr.length);
+                        conv[depthstr.length] = '\0';
+                        stmt->depth = strtol(conv, &chk, 10);
+                        if (*chk || stmt->depth < -1) {
+                            stmt->errorcode = DAVQL_ERROR_INVALID_DEPTH;
+                            stmt->errormessage = strdup(_invalid_depth);
+                        }
+                        free(conv);
+                    }
+                } else {
+                    stmt->errorcode = DAVQL_ERROR_INVALID_DEPTH;
+                    stmt->errormessage = strdup(_invalid_depth);
+                }
+                
+                dav_free_expression(depthexpr);
+            }
+            break;
+        }
+        state->step = 3;
+        return _step_WITH_; // continue parsing WITH clause
+    case 3:
+        // a with clause may be continued with a comma
+        // or another clause may follow
+        if (!sstrcmp(tokendata, S(","))) {
+            state->step = 0; // reset clause parser
+            return _step_WITH_;
+        } else if (!sstrcasecmp(tokendata, S("where"))) {
+            return _step_WHERE_;
+        } else if (!sstrcasecmp(tokendata, S("order"))) {
+            return _step_ORDER_BY_;
+        } else {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _unexpected_token, stmt, token);
+            return 0;
+        }
+    default:
+        stmt->errorcode = DAVQL_ERROR_INVALID;
+        stmt->errormessage = strdup(_parser_state);
+        return 0;
+    }
+}
+
+struct orderby_parser_state {
+    /*
+     * 0: expect by keyword
+     * 1: expect identifier / number
+     * 2: expect asc / desc or comma
+     * 3: expect comma
+     */
+    int step;
+    DavQLOrderCriterion *crit;
+};
+
+static int dav_parse_orderby_clause(DavQLStatement *stmt, UcxList *token,
+        struct orderby_parser_state *state) {
+    
+    sstr_t tokendata = *token_sstr(token);
+    
+    switch (state->step) {
+    case 0:
+        if (!sstrcasecmp(tokendata, S("by"))) {
+            state->step++;
+        } else {
+            stmt->errorcode = DAVQL_ERROR_UNEXPECTED_TOKEN;
+            stmt->errormessage = ucx_sprintf(_expected_by,
+                sfmtarg(tokendata)).ptr;
+        }
+        return _step_ORDER_BY_;
+    case 1:
+        state->crit = malloc(sizeof(DavQLOrderCriterion));
+        state->crit->column = dav_parse_expression(stmt, token, 1);
+        state->crit->descending = 0;
+        
+        if (!state->crit->column || (
+            state->crit->column->type != DAVQL_NUMBER &&
+            state->crit->column->type != DAVQL_IDENTIFIER)) {
+            free(state->crit);
+            dav_error_in_context(DAVQL_ERROR_IDORNUM_EXPECTED,
+                _idornum_expected, stmt, token);
+        } else {
+            stmt->orderby = ucx_list_append(stmt->orderby, state->crit);
+        }
+        
+        // continue parsing clause, if more tokens available
+        state->step++;
+        return _step_ORDER_BYopt_;
+    case 2:
+        if (!sstrcasecmp(tokendata, S("desc"))) {
+            state->crit->descending = 1;
+        } else if (!sstrcasecmp(tokendata, S("asc"))) {
+            state->crit->descending = 0;
+        } else if (!sstrcmp(tokendata, S(","))) {
+            state->step = 1; // reset clause parser
+            return _step_ORDER_BY_; // statement must not end now
+        } else {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _unexpected_token, stmt, token);
+            return 0;
+        }
+         // continue parsing clause, if more tokens available
+        state++;
+        return _step_ORDER_BYopt_;
+    case 3:
+        if (!sstrcmp(tokendata, S(","))) {
+            state->step = 1; // reset clause parser
+            return _step_ORDER_BY_; // statement must not end now
+        } else {
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _unexpected_token, stmt, token);
+            return 0;
+        }
+    }
+    
+    return _step_end_;
+}
+
+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);
+    }
+    free(crit);
 }
 
 static void dav_parse_get_statement(DavQLStatement *stmt, UcxList *tokens) {
     stmt->type = DAVQL_GET;
+
+    int step = _step_fieldlist_;
     
-    /*
-     *   10: field list
-     *   20: FROM clause
-     *  520: expecting WHERE or WITH clause
-     *   30: WHERE clause
-     *  530: expecting WITH clause
-     *   40: WITH clause
-     *  500: ready to quit
-     *  999: error
-     * 
-     */
-    int step = 10;
-    
-    // Variables for token sublists for expressions
-    UcxList *exprstart = NULL;
-    size_t exprlen = 0;
+    struct with_parser_state state_with;
+    memset(&state_with, 0, sizeof(struct with_parser_state));
+    struct orderby_parser_state state_orderby;
+    memset(&state_orderby, 0, sizeof(struct orderby_parser_state));
+    struct fieldlist_parser_state state_fieldlist;
+    memset(&state_fieldlist, 0, sizeof(struct fieldlist_parser_state));
     
     // Process tokens
     UCX_FOREACH(token, tokens) {
-        if (stmt->errorcode) {
-            ultrabreak: break;
-        }
-        
-        sstr_t tokendata = *token_sstr(token);
-        
         switch (step) {
-        // optional clauses
-        case 520:
-            if (!sstrcasecmp(tokendata, S("where"))) {
-                step = 30;
-            }
-            /* no break and no else*/
-        case 530:
-            if (!sstrcasecmp(tokendata, S("with"))) {
-                step = 40;
-            } else {
-                dav_parse_unexpected_token(stmt, token);
-                goto ultrabreak;
-            }
+        // too much input data
+        case _step_end_:
+            dav_error_in_context(DAVQL_ERROR_UNEXPECTED_TOKEN,
+                _unexpected_token, stmt, token);
             break;
         // field list
-        case 10: {
-            _Bool fromkeyword = !sstrcasecmp(tokendata, S("from"));
-            if (fromkeyword || !sstrcmp(tokendata, S(","))) {
-                if (exprstart) {
-                    stmt->fields = ucx_list_append(stmt->fields,
-                        dav_parse_expression(stmt, exprstart, exprlen));
-                    exprstart = NULL;
-                    exprlen = 0;
-                } else {
-                    // TODO: throw syntax error
-                }
-                
-                if (fromkeyword) {
-                    step = 20;
-                }
-            } else {
-                // collect tokens for field expression
-                if (exprstart) {
-                    exprlen++;
-                } else {
-                    exprstart = token;
-                    exprlen = 1;
-                }
-            }
+        case _step_fieldlist_: {
+            step = dav_parse_fieldlist(stmt, token, &state_fieldlist);
             break;
         }
         // from clause
-        case 20: {
-            DavQLExpression *expr = dav_parse_expression(stmt, token, 1);
-            stmt->path = expr->srctext;
-            dav_free_expression(expr);
-            step = 520;
+        case _step_FROM_: {
+            step = dav_parse_from(stmt, token);
+            break;
+        }
+        // with clause
+        case _step_WITH_: {
+            step = dav_parse_with_clause(stmt, token, &state_with);
             break;
         }
         // where clause
-        case 30:
-            step = 530;
+        case _step_WHERE_:
+            // TODO: implement
+            step = _step_end_;
             break;
-        // with clause
-        case 40:
-            step = 500;
+        // order by clause
+        case _step_ORDER_BY_:
+        case _step_ORDER_BYopt_:
+            step = dav_parse_orderby_clause(stmt, token, &state_orderby);
+            break;
+        default:
+            stmt->errorcode = DAVQL_ERROR_INVALID;
+            stmt->errormessage = strdup(_parser_state);
+        }
+        
+        // cancel processing, when an error has been detected
+        if (stmt->errorcode) {
             break;
         }
     }
     
-    if (step < 500) {
+    if (!stmt->errorcode && step < _step_end_) {
         stmt->errorcode = DAVQL_ERROR_UNEXPECTED_END;
         stmt->errormessage = strdup(_unexpected_end_msg);
     }
@@ -553,13 +1022,9 @@
 
 void dav_free_statement(DavQLStatement *stmt) {
     UCX_FOREACH(expr, stmt->fields) {
-        dav_free_expression(expr->data);
+        dav_free_field(expr->data);
     }
     ucx_list_free(stmt->fields);
-    UCX_FOREACH(expr, stmt->setvalues) {
-        dav_free_expression(expr->data);
-    }
-    ucx_list_free(stmt->setvalues);
     
     if (stmt->where) {
         dav_free_expression(stmt->where);
@@ -567,5 +1032,9 @@
     if (stmt->errormessage) {
         free(stmt->errormessage);
     }
+    UCX_FOREACH(crit, stmt->orderby) {
+        dav_free_order_criterion(crit->data);
+    }
+    ucx_list_free(stmt->orderby);
     free(stmt);
 }
--- a/libidav/davqlparser.h	Sat May 02 10:59:02 2015 +0200
+++ b/libidav/davqlparser.h	Sat May 02 11:00:28 2015 +0200
@@ -46,7 +46,8 @@
  * Enumeration of possible expression types.
  */
 typedef enum {
-    DAVQL_LITERAL, DAVQL_IDENTIFIER,
+    DAVQL_UNDEFINED_TYP,
+    DAVQL_NUMBER, DAVQL_STRING, DAVQL_TIMESTAMP, DAVQL_IDENTIFIER,
     DAVQL_UNARY, DAVQL_BINARY, DAVQL_LOGICAL, DAVQL_FUNCCALL
 } davqlexprtype_t;
 
@@ -54,12 +55,12 @@
  * Enumeration of possible expression operators.
  */
 typedef enum {
-    DAVQL_NOOP,
+    DAVQL_NOOP, DAVQL_CALL, DAVQL_ARGLIST, // internal representations
     DAVQL_ADD, DAVQL_SUB, DAVQL_MUL, DAVQL_DIV,
-    DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG,
-    DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR,
+    DAVQL_AND, DAVQL_OR, DAVQL_XOR, DAVQL_NEG,  // airthmetic
+    DAVQL_NOT, DAVQL_LAND, DAVQL_LOR, DAVQL_LXOR, // logical
     DAVQL_EQ, DAVQL_NEQ, DAVQL_LT, DAVQL_GT, DAVQL_LE, DAVQL_GE,
-    DAVQL_LIKE, DAVQL_UNLIKE
+    DAVQL_LIKE, DAVQL_UNLIKE // comparisons
 } davqloperator_t;
 
 /**
@@ -82,7 +83,6 @@
     davqlexprtype_t type;
     /**
      * Operator.
-     * 
      */
     davqloperator_t op;
     /**
@@ -97,6 +97,42 @@
     DavQLExpression *right;
 };
 
+/**
+ * A tuple representing an order criterion.
+ */
+typedef struct {
+    /**
+     * The column.
+     */
+    DavQLExpression *column;
+    /**
+     * True, if the result shall be sorted descending, false otherwise.
+     * Default is false (ascending).
+     */
+    _Bool descending;
+} DavQLOrderCriterion;
+
+/**
+ * A tuple representing a field.
+ */
+typedef struct {
+    /**
+     * The field name.
+     * <ul>
+     * <li>GET: the identifier or an alias name</li>
+     * <li>SET: the identifier</li>
+     * </ul>
+     */
+    sstr_t name;
+    /**
+     * The field expression.
+     * <ul>
+     * <li>GET: the queried property (identifier) or an expression</li>
+     * <li>SET: the expression for the value to be set</li>
+     * </ul>
+     */
+    DavQLExpression *expr;
+} DavQLField;
 
 /**
  * Query statement object.
@@ -110,13 +146,15 @@
  *            | FunctionCall | Identifier | Literal
  *            | "(", Expression, ")";
  * 
- * FunctionCall    = Identifier, "(", Expression, ")";
+ * FunctionCall    = Identifier, "(", ArgumentList, ")";
+ * ArgumentList    = Expression, {",", Expression};
  * Identifier      = IdentifierChar - ?Digit?, {IdentifierChar}
- *                 | "`", ?Character?, {?Character?}, "`";
- * IdentifierChar  = ?Character - (" "|",")?;
- * Literal         = Number | String;
+ *                 | "`", ?Character? - "`", {?Character? - "`"}, "`";
+ * IdentifierChar  = ?Character? - (" "|",");
+ * Literal         = Number | String | Timestamp;
  * Number          = ?Digit?, {?Digit?} | "%d";
- * String          = "'", {?Character - "'"? | "'''"} , "'";
+ * String          = "'", {?Character? - "'" | "'''"} , "'" | "%s";
+ * Timestamp       = "%t"; // TODO: maybe introduce a real literal 
  * 
  * LogicalExpression = LogicalExpression, LogicalOperator, LogicalExpression
  *                   | "not ", LogicalExpression
@@ -129,16 +167,23 @@
  * LogicalOperator = " and " | " or " | " xor ";
  * Comparison      = | "=" | "<" | ">" | "<=" | ">=" | "!=";
  * 
- * FieldExpressions = "*", {",", Expression, " as ", String}
+ * FieldExpressions = "*", {",", Expression, " as ", Identifier}
  *                  | FieldExpression, {",", FieldExpression}
  *                  | "-";
  * FieldExpression  = Identifier
- *                  | Expression, " as ", String;
- * SetExpressions   = SetExpression, {",", {SetExpressions};
+ *                  | Expression, " as ", Identifier;
+ * SetExpressions   = SetExpression, {",", SetExpressions};
  * SetExpression    = Identifier, "=", Expression;
  * 
+ * Path = "%s"
+ *      | "/", {?Character? - " "}
+ *      | "'/", {?Character?}, "'";
+ * 
  * WithClause = "depth", "=", (Number | "infinity");
  * 
+ * OrderByClause    = OrderByCriterion, {",", OrderByCriterion};
+ * OrderByCriterion = (Identifier | Number), [" asc"|" desc"];
+ * 
  * </pre>
  * 
  * Note: mandatory spaces are part of the grammar. But you may also insert an
@@ -148,17 +193,18 @@
  * <b>GET:</b>
  * <pre>
  * GetStatement = "get ", FieldExpressions,
- * " from ", Identifier,
+ * " from ", Path,
+ * [" with ", WithClause],
  * [" where ", LogicalExpression],
- * [" with ", WithClause];
+ * [" order by ", OrderByClause];
   * </pre>
  * 
  * <b>SET:</b>
  * <pre>
  * "set ",SetExpressions,
- * " at ", Identifier,
- * (" where ", LogicalExpression) | " anywhere",
- * [" with ", WithClause];
+ * " at ", Path,
+ * [" with ", WithClause],
+ * (" where ", LogicalExpression) | " anywhere";
  * </pre>
  * 
  */
@@ -180,15 +226,10 @@
      */
     char* errormessage;
     /**
-     * The list of field expressions.
+     * The list of DavQLFields.
      */
     UcxList* fields;
     /**
-     * The list of DavQLExpressions for the new DAV property values.
-     * This is <code>NULL</code> for GET queries.
-     */
-    UcxList* setvalues;
-    /**
      * A string that denotes the queried path.
      */
     sstr_t path;
@@ -198,8 +239,16 @@
      */
     DavQLExpression* where;
     /**
+     * The list of DavQLOrderCriterions.
+     * This is <code>NULL</code> for SET queries and may be <code>NULL</code>
+     * if the result doesn't need to be sorted.
+     */
+    UcxList* orderby;
+    /**
      * The recursion depth for the statement.
      * Defaults to 1.
+     * Magic numbers are DAV_DEPTH_INFINITY for infinity and
+     * DAV_DEPTH_PLACEHOLDER for a placeholder.
      */
     int depth;
 } DavQLStatement;
@@ -207,6 +256,45 @@
 /** Infinity recursion depth for a DavQLStatement. */
 #define DAV_DEPTH_INFINITY -1
 
+/** Depth needs to be specified at runtime. */
+#define DAV_DEPTH_PLACEHOLDER -2
+
+/** Invalid path. */
+#define DAVQL_ERROR_INVALID_PATH 1
+
+/** Expected an identifier, but found something else. */
+#define DAVQL_ERROR_IDENTIFIER_EXPECTED 10
+
+/** Expected an identifier or literal, but found something else. */
+#define DAVQL_ERROR_IDORLIT_EXPECTED 11
+
+/** Expected an identifier or number, but found something else. */
+#define DAVQL_ERROR_IDORNUM_EXPECTED 12
+
+/** Expected an identifier or string, but found something else. */
+#define DAVQL_ERROR_IDORSTR_EXPECTED 13
+
+/** Expected an identifier or timestamp, but found something else. */
+#define DAVQL_ERROR_IDORTS_EXPECTED 14
+
+/** The with-clause contains an unknown attribute. */
+#define DAVQL_ERROR_UNKNOWN_ATTRIBUTE 20
+
+/** Depth must be greater than zero or infinity. */
+#define DAVQL_ERROR_INVALID_DEPTH 21
+
+/** The with-clause contains an attribute more than once. */
+#define DAVQL_ERROR_DUPLICATED_ATTRIBUTE 29
+
+/** The format specifier is missing. */
+#define DAVQL_ERROR_MISSING_FMTSPEC 30
+
+/** The format specifier is unknown. */
+#define DAVQL_ERROR_UNKNOWN_FMTSPEC 31
+
+/** The format specifier is invalid. */
+#define DAVQL_ERROR_INVALID_FMTSPEC 39
+
 /** A quote symbol (' or `) is missing. */
 #define DAVQL_ERROR_MISSING_QUOTE 50
 

mercurial