%{

/*
 * Copyright 2012 Hans Leidekker for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdarg.h>

#include "windef.h"
#include "winbase.h"
#include "wbemcli.h"
#include "wbemprox_private.h"

#include "wine/list.h"
#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(wbemprox);

struct parser
{
    const WCHAR *cmd;
    UINT idx;
    UINT len;
    HRESULT error;
    struct view **view;
    struct list *mem;
};

struct string
{
    const WCHAR *data;
    int len;
};

static void *alloc_mem( struct parser *parser, UINT size )
{
    struct list *mem = heap_alloc( sizeof(struct list) + size );
    list_add_tail( parser->mem, mem );
    return &mem[1];
}

static struct property *alloc_property( struct parser *parser, const WCHAR *class, const WCHAR *name )
{
    struct property *prop = alloc_mem( parser, sizeof(*prop) );
    if (prop)
    {
        prop->name  = name;
        prop->class = class;
        prop->next  = NULL;
    }
    return prop;
}

static struct keyword *alloc_keyword( struct parser *parser, const WCHAR *name, const WCHAR *value )
{
    struct keyword *keyword = alloc_mem( parser, sizeof(*keyword) );
    if (keyword)
    {
        keyword->name  = name;
        keyword->value = value;
        keyword->next  = NULL;
    }
    return keyword;
}

static WCHAR *get_string( struct parser *parser, const struct string *str )
{
    const WCHAR *p = str->data;
    int len = str->len;
    WCHAR *ret;

    if ((p[0] == '\"' && p[len - 1] != '\"') ||
        (p[0] == '\'' && p[len - 1] != '\'')) return NULL;
    if ((p[0] == '\"' && p[len - 1] == '\"') ||
        (p[0] == '\'' && p[len - 1] == '\''))
    {
        p++;
        len -= 2;
    }
    if (!(ret = alloc_mem( parser, (len + 1) * sizeof(WCHAR) ))) return NULL;
    memcpy( ret, p, len * sizeof(WCHAR) );
    ret[len] = 0;
    return ret;
}

static WCHAR *get_path( struct parser *parser, const struct string *str )
{
    const WCHAR *p = str->data;
    int len = str->len;
    WCHAR *ret;

    if (p[0] == '{' && p[len - 1] == '}')
    {
        p++;
        len -= 2;
    }

    if (!(ret = alloc_mem( parser, (len + 1) * sizeof(WCHAR) ))) return NULL;
    memcpy( ret, p, len * sizeof(WCHAR) );
    ret[len] = 0;
    return ret;
}

static int get_int( struct parser *parser )
{
    const WCHAR *p = &parser->cmd[parser->idx];
    int i, ret = 0;

    for (i = 0; i < parser->len; i++)
    {
        if (p[i] < '0' || p[i] > '9')
        {
            ERR("should only be numbers here!\n");
            break;
        }
        ret = (p[i] - '0') + ret * 10;
    }
    return ret;
}

static struct expr *expr_complex( struct parser *parser, struct expr *l, UINT op, struct expr *r )
{
    struct expr *e = alloc_mem( parser, sizeof(*e) );
    if (e)
    {
        e->type = EXPR_COMPLEX;
        e->u.expr.left = l;
        e->u.expr.op = op;
        e->u.expr.right = r;
    }
    return e;
}

static struct expr *expr_unary( struct parser *parser, struct expr *l, UINT op )
{
    struct expr *e = alloc_mem( parser, sizeof(*e) );
    if (e)
    {
        e->type = EXPR_UNARY;
        e->u.expr.left = l;
        e->u.expr.op = op;
        e->u.expr.right = NULL;
    }
    return e;
}

static struct expr *expr_ival( struct parser *parser, int val )
{
    struct expr *e = alloc_mem( parser, sizeof *e );
    if (e)
    {
        e->type = EXPR_IVAL;
        e->u.ival = val;
    }
    return e;
}

static struct expr *expr_sval( struct parser *parser, const struct string *str )
{
    struct expr *e = alloc_mem( parser, sizeof *e );
    if (e)
    {
        e->type = EXPR_SVAL;
        e->u.sval = get_string( parser, str );
        if (!e->u.sval)
            return NULL; /* e will be freed by query destructor */
    }
    return e;
}

static struct expr *expr_bval( struct parser *parser, int val )
{
    struct expr *e = alloc_mem( parser, sizeof *e );
    if (e)
    {
        e->type = EXPR_BVAL;
        e->u.ival = val;
    }
    return e;
}

static struct expr *expr_propval( struct parser *parser, const struct property *prop )
{
    struct expr *e = alloc_mem( parser, sizeof *e );
    if (e)
    {
        e->type = EXPR_PROPVAL;
        e->u.propval = prop;
    }
    return e;
}

static int wql_error( struct parser *parser, const char *str );
static int wql_lex( void *val, struct parser *parser );

#define PARSER_BUBBLE_UP_VIEW( parser, result, current_view ) \
    *parser->view = current_view; \
    result = current_view

%}

%lex-param { struct parser *ctx }
%parse-param { struct parser *ctx }
%define parse.error verbose
%define api.pure

%union
{
    struct string str;
    WCHAR *string;
    struct property *proplist;
    struct keyword *keywordlist;
    struct view *view;
    struct expr *expr;
    int integer;
}

%token TK_SELECT TK_FROM TK_STAR TK_COMMA TK_DOT TK_IS TK_LP TK_RP TK_NULL TK_FALSE TK_TRUE
%token TK_INTEGER TK_WHERE TK_SPACE TK_MINUS TK_ILLEGAL TK_BY TK_ASSOCIATORS TK_OF
%token <str> TK_STRING TK_ID TK_PATH

%type <string> id path
%type <proplist> prop proplist
%type <keywordlist> keyword keywordlist
%type <view> query select associatorsof
%type <expr> expr prop_val const_val string_val
%type <integer> number

%left TK_OR TK_AND TK_NOT TK_EQ TK_NE TK_LT TK_GT TK_LE TK_GE TK_LIKE

%%

query:
    select
  |
    associatorsof
    ;

path:
    TK_PATH
        {
            $$ = get_path( ctx, &$1 );
            if (!$$)
                YYABORT;
        }
    ;

keyword:
    id
        {
            $$ = alloc_keyword( ctx, $1, NULL );
            if (!$$)
                YYABORT;
        }
 | id TK_EQ id
        {
            $$ = alloc_keyword( ctx, $1, $3 );
            if (!$$)
                YYABORT;
        }
    ;

keywordlist:
    keyword
  | keyword keywordlist
        {
            $1->next = $2;
        }
    ;

associatorsof:
    TK_ASSOCIATORS TK_OF path
        {
            HRESULT hr;
            struct parser *parser = ctx;
            struct view *view;

            hr = create_view( VIEW_TYPE_ASSOCIATORS, $3, NULL, NULL, NULL, NULL, &view );
            if (hr != S_OK)
                YYABORT;

            PARSER_BUBBLE_UP_VIEW( parser, $$, view );
        }
  | TK_ASSOCIATORS TK_OF path TK_WHERE keywordlist
        {
            HRESULT hr;
            struct parser *parser = ctx;
            struct view *view;

            hr = create_view( VIEW_TYPE_ASSOCIATORS, $3, $5, NULL, NULL, NULL, &view );
            if (hr != S_OK)
                YYABORT;

            PARSER_BUBBLE_UP_VIEW( parser, $$, view );
        }
    ;

select:
    TK_SELECT TK_FROM id
        {
            HRESULT hr;
            struct parser *parser = ctx;
            struct view *view;

            hr = create_view( VIEW_TYPE_SELECT, NULL, NULL, $3, NULL, NULL, &view );
            if (hr != S_OK)
                YYABORT;

            PARSER_BUBBLE_UP_VIEW( parser, $$, view );
        }
  | TK_SELECT proplist TK_FROM id
        {
            HRESULT hr;
            struct parser *parser = ctx;
            struct view *view;

            hr = create_view( VIEW_TYPE_SELECT, NULL, NULL, $4, $2, NULL, &view );
            if (hr != S_OK)
                YYABORT;

            PARSER_BUBBLE_UP_VIEW( parser, $$, view );
        }
  | TK_SELECT proplist TK_FROM id TK_WHERE expr
        {
            HRESULT hr;
            struct parser *parser = ctx;
            struct view *view;

            hr = create_view( VIEW_TYPE_SELECT, NULL, NULL, $4, $2, $6, &view );
            if (hr != S_OK)
                YYABORT;

            PARSER_BUBBLE_UP_VIEW( parser, $$, view );
        }
    ;

proplist:
    prop
  | prop TK_COMMA proplist
        {
            $1->next = $3;
        }
  | TK_STAR
        {
            $$ = NULL;
        }
    ;

prop:
    id TK_DOT id
        {
            $$ = alloc_property( ctx, $1, $3 );
            if (!$$)
                YYABORT;
        }
  | id
        {
            $$ = alloc_property( ctx, NULL, $1 );
            if (!$$)
                YYABORT;
        }
    ;

id:
    TK_ID
        {
            $$ = get_string( ctx, &$1 );
            if (!$$)
                YYABORT;
        }
    ;

number:
    TK_INTEGER
        {
            $$ = get_int( ctx );
        }
    ;

expr:
    TK_LP expr TK_RP
        {
            $$ = $2;
            if (!$$)
                YYABORT;
        }
  | expr TK_AND expr
        {
            $$ = expr_complex( ctx, $1, OP_AND, $3 );
            if (!$$)
                YYABORT;
        }
  | expr TK_OR expr
        {
            $$ = expr_complex( ctx, $1, OP_OR, $3 );
            if (!$$)
                YYABORT;
        }
  | TK_NOT expr
        {
            $$ = expr_unary( ctx, $2, OP_NOT );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_EQ const_val
        {
            $$ = expr_complex( ctx, $1, OP_EQ, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_GT const_val
        {
            $$ = expr_complex( ctx, $1, OP_GT, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_LT const_val
        {
            $$ = expr_complex( ctx, $1, OP_LT, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_LE const_val
        {
            $$ = expr_complex( ctx, $1, OP_LE, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_GE const_val
        {
            $$ = expr_complex( ctx, $1, OP_GE, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_NE const_val
        {
            $$ = expr_complex( ctx, $1, OP_NE, $3 );
            if (!$$)
                YYABORT;
        }
  | const_val TK_EQ prop_val
        {
            $$ = expr_complex( ctx, $1, OP_EQ, $3 );
            if (!$$)
                YYABORT;
        }
  | const_val TK_GT prop_val
        {
            $$ = expr_complex( ctx, $1, OP_GT, $3 );
            if (!$$)
                YYABORT;
        }
  | const_val TK_LT prop_val
        {
            $$ = expr_complex( ctx, $1, OP_LT, $3 );
            if (!$$)
                YYABORT;
        }
  | const_val TK_LE prop_val
        {
            $$ = expr_complex( ctx, $1, OP_LE, $3 );
            if (!$$)
                YYABORT;
        }
  | const_val TK_GE prop_val
        {
            $$ = expr_complex( ctx, $1, OP_GE, $3 );
            if (!$$)
                YYABORT;
        }
  | const_val TK_NE prop_val
        {
            $$ = expr_complex( ctx, $1, OP_NE, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_LIKE string_val
        {
            $$ = expr_complex( ctx, $1, OP_LIKE, $3 );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_IS TK_NULL
        {
            $$ = expr_unary( ctx, $1, OP_ISNULL );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_IS TK_NOT TK_NULL
        {
            $$ = expr_unary( ctx, $1, OP_NOTNULL );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_EQ TK_NULL
        {
            $$ = expr_unary( ctx, $1, OP_ISNULL );
            if (!$$)
                YYABORT;
        }
  | TK_NULL TK_EQ prop_val
        {
            $$ = expr_unary( ctx, $3, OP_ISNULL );
            if (!$$)
                YYABORT;
        }
  | prop_val TK_NE TK_NULL
        {
            $$ = expr_unary( ctx, $1, OP_NOTNULL );
            if (!$$)
                YYABORT;
        }
  | TK_NULL TK_NE prop_val
        {
            $$ = expr_unary( ctx, $3, OP_NOTNULL );
            if (!$$)
                YYABORT;
        }
    ;

string_val:
    TK_STRING
        {
            $$ = expr_sval( ctx, &$1 );
            if (!$$)
                YYABORT;
        }
    ;

prop_val:
    prop
        {
            $$ = expr_propval( ctx, $1 );
            if (!$$)
                YYABORT;
        }
    ;

const_val:
    number
        {
            $$ = expr_ival( ctx, $1 );
            if (!$$)
                YYABORT;
        }
  | TK_STRING
        {
            $$ = expr_sval( ctx, &$1 );
            if (!$$)
                YYABORT;
        }
  | TK_TRUE
        {
            $$ = expr_bval( ctx, -1 );
            if (!$$)
                YYABORT;
        }
  | TK_FALSE
        {
            $$ = expr_bval( ctx, 0 );
            if (!$$)
                YYABORT;
        }
    ;

%%

HRESULT parse_query( const WCHAR *str, struct view **view, struct list *mem )
{
    struct parser parser;
    int ret;

    *view = NULL;

    parser.cmd   = str;
    parser.idx   = 0;
    parser.len   = 0;
    parser.error = WBEM_E_INVALID_QUERY;
    parser.view  = view;
    parser.mem   = mem;

    ret = wql_parse( &parser );
    TRACE("wql_parse returned %d\n", ret);
    if (ret)
    {
        if (*parser.view)
        {
            destroy_view( *parser.view );
            *parser.view = NULL;
        }
        return parser.error;
    }
    return S_OK;
}

static const char id_char[] =
{
    0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
    0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
    1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};

struct wql_keyword
{
    const WCHAR *name;
    unsigned int len;
    int type;
};

#define MIN_TOKEN_LEN 2
#define MAX_TOKEN_LEN 11

#define X(str)  str, ARRAY_SIZE(str) - 1
static const struct wql_keyword keyword_table[] =
{
    { X(L"AND"),         TK_AND },
    { X(L"ASSOCIATORS"), TK_ASSOCIATORS },
    { X(L"BY"),          TK_BY },
    { X(L"FALSE"),       TK_FALSE },
    { X(L"FROM"),        TK_FROM },
    { X(L"IS"),          TK_IS },
    { X(L"LIKE"),        TK_LIKE },
    { X(L"NOT"),         TK_NOT },
    { X(L"NULL"),        TK_NULL },
    { X(L"OF"),          TK_OF },
    { X(L"OR"),          TK_OR },
    { X(L"SELECT"),      TK_SELECT },
    { X(L"TRUE"),        TK_TRUE },
    { X(L"WHERE"),       TK_WHERE }
};
#undef X

static int __cdecl cmp_keyword( const void *arg1, const void *arg2 )
{
    const struct wql_keyword *key1 = arg1, *key2 = arg2;
    int len = min( key1->len, key2->len );
    int ret;

    if ((ret = wcsnicmp( key1->name, key2->name, len ))) return ret;
    if (key1->len < key2->len) return -1;
    else if (key1->len > key2->len) return 1;
    return 0;
}

static int keyword_type( const WCHAR *str, unsigned int len )
{
    struct wql_keyword key, *ret;

    if (len < MIN_TOKEN_LEN || len > MAX_TOKEN_LEN) return TK_ID;

    key.name = str;
    key.len  = len;
    key.type = 0;
    ret = bsearch( &key, keyword_table, ARRAY_SIZE(keyword_table), sizeof(struct wql_keyword), cmp_keyword );
    if (ret) return ret->type;
    return TK_ID;
}

static int get_token( const WCHAR *s, int *token )
{
    int i;

    switch (*s)
    {
    case ' ':
    case '\t':
    case '\r':
    case '\n':
        for (i = 1; iswspace( s[i] ); i++) {}
        *token = TK_SPACE;
        return i;
    case '-':
        if (!s[1]) return -1;
        *token = TK_MINUS;
        return 1;
    case '(':
        *token = TK_LP;
        return 1;
    case ')':
        *token = TK_RP;
        return 1;
    case '{':
        for (i = 1; s[i] && s[i] != '}'; i++) {}
        if (s[i] != '}')
        {
            *token = TK_ILLEGAL;
            return i;
        }
        *token = TK_PATH;
        return i + 1;
    case '*':
        *token = TK_STAR;
        return 1;
    case '=':
        *token = TK_EQ;
        return 1;
    case '<':
        if (s[1] == '=' )
        {
            *token = TK_LE;
            return 2;
        }
        else if (s[1] == '>')
        {
            *token = TK_NE;
            return 2;
        }
        else
        {
            *token = TK_LT;
            return 1;
        }
    case '>':
        if (s[1] == '=')
        {
            *token = TK_GE;
            return 2;
        }
        else
        {
            *token = TK_GT;
            return 1;
        }
    case '!':
        if (s[1] != '=')
        {
            *token = TK_ILLEGAL;
            return 2;
        }
        else
        {
            *token = TK_NE;
            return 2;
        }
    case ',':
        *token = TK_COMMA;
        return 1;
    case '\"':
    case '\'':
        for (i = 1; s[i]; i++)
        {
            if (s[i] == s[0]) break;
        }
        if (s[i]) i++;
        *token = TK_STRING;
        return i;
    case '.':
        if (!is_digit( s[1] ))
        {
            *token = TK_DOT;
            return 1;
        }
        /* fall through */
    case '0': case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
        *token = TK_INTEGER;
        for (i = 1; is_digit( s[i] ); i++) {}
        return i;
    default:
        if (!id_char[*s]) break;

        for (i = 1; id_char[s[i]]; i++) {}
        *token = keyword_type( s, i );
        return i;
    }
    *token = TK_ILLEGAL;
    return 1;
}

static int wql_lex( void *p, struct parser *parser )
{
    struct string *str = p;
    int token = -1;
    do
    {
        parser->idx += parser->len;
        if (!parser->cmd[parser->idx]) return 0;
        parser->len = get_token( &parser->cmd[parser->idx], &token );
        if (!parser->len) break;

        str->data = &parser->cmd[parser->idx];
        str->len = parser->len;
    } while (token == TK_SPACE);
    return token;
}

static int wql_error( struct parser *parser, const char *str )
{
    ERR("%s\n", str);
    return 0;
}