%{ /* -*-C-*- */
/*
 * Help Viewer
 *
 * Copyright 1996 Ulrich Schmid
 * Copyright 2002,2008 Eric Pouech
 *
 * 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
 */
%}
%option noinput nounput interactive 8bit
%x quote
%{
#include "config.h"
#include <assert.h>
#include <stdarg.h>

#ifndef HAVE_UNISTD_H
#define YY_NO_UNISTD_H
#endif

#include "windef.h"
#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winhelp.h"

#include "wine/debug.h"

WINE_DEFAULT_DEBUG_CHANNEL(winhelp);

struct lex_data {
    LPCSTR   macroptr;
    LPSTR    strptr;
    int      quote_stack[32];
    unsigned quote_stk_idx;
    LPSTR    cache_string[32];
    int      cache_used;
    WINHELP_WINDOW* window;
};
static struct lex_data* lex_data = NULL;

struct lexret  yylval;

#define YY_INPUT(buf,result,max_size)\
  if ((result = *lex_data->macroptr ? 1 : 0)) buf[0] = *lex_data->macroptr++;

%}
%%

[-+]?[0-9]+             yylval.integer = strtol(yytext, NULL, 10);	return INTEGER;
[-+]?0[xX][0-9a-f]+	yylval.integer = strtol(yytext, NULL, 16);	return INTEGER;

[a-zA-Z][_0-9a-zA-Z]*   return MACRO_Lookup(yytext, &yylval);

\`	    |
\"	    |
\'          |
<quote>\`   |
<quote>\"   |
<quote>\'   {
    if (lex_data->quote_stk_idx == 0 ||
        (yytext[0] == '\"' && lex_data->quote_stack[lex_data->quote_stk_idx - 1] != '\"') ||
        (yytext[0] == '`'))
    {
        /* opening a new one */
        if (lex_data->quote_stk_idx == 0)
        {
            assert(lex_data->cache_used < sizeof(lex_data->cache_string) / sizeof(lex_data->cache_string[0]));
            lex_data->strptr = lex_data->cache_string[lex_data->cache_used] = HeapAlloc(GetProcessHeap(), 0, strlen(lex_data->macroptr) + 1);
            yylval.string = lex_data->strptr;
            lex_data->cache_used++;
            BEGIN(quote);
        }
        else *lex_data->strptr++ = yytext[0];
        lex_data->quote_stack[lex_data->quote_stk_idx++] = yytext[0];
        assert(lex_data->quote_stk_idx < sizeof(lex_data->quote_stack) / sizeof(lex_data->quote_stack[0]));
    }
    else
    {
        if (yytext[0] == '`') assert(0);
        /* close the current quote */
        if (--lex_data->quote_stk_idx == 0)
        {
            BEGIN INITIAL;
            *lex_data->strptr++ = '\0';
            return STRING;
        }
        else *lex_data->strptr++ = yytext[0];
    }
}

<quote>.                *lex_data->strptr++ = yytext[0];
<quote>\\.	        *lex_data->strptr++ = yytext[1];
<quote><<EOF>>	        return 0;

" "
.			return yytext[0];
%%

#if 0
/* all code for testing macros */
#include "winhelp.h"
static CHAR szTestMacro[256];

static LRESULT CALLBACK MACRO_TestDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if (msg == WM_COMMAND && wParam == IDOK)
    {
        GetDlgItemText(hDlg, 99, szTestMacro, sizeof(szTestMacro));
        EndDialog(hDlg, IDOK);
        return TRUE;
    }
    return FALSE;
}

void macro_test(void)
{
    WNDPROC lpfnDlg = MakeProcInstance(MACRO_TestDialogProc, Globals.hInstance);
    DialogBox(Globals.hInstance, STRING_DIALOG_TEST, Globals.active_win->hMainWnd, (DLGPROC)lpfnDlg);
    FreeProcInstance(lpfnDlg);
    macro = szTestMacro;
}
#endif

/* small helper function for debug messages */
static const char* ts(int t)
{
    static char c[2] = {0,0};

    switch (t)
    {
    case EMPTY: return "EMPTY";
    case VOID_FUNCTION: return "VOID_FUNCTION";
    case BOOL_FUNCTION: return "BOOL_FUNCTION";
    case INTEGER: return "INTEGER";
    case STRING: return "STRING";
    case IDENTIFIER: return "IDENTIFIER";
    default: c[0] = (char)t; return c;
    }
}

static int MACRO_CallBoolFunc(void *fn, const char* args, void** ret);

/******************************************************************
 *		MACRO_CheckArgs
 *
 * checks number of arguments against prototype, and stores arguments on
 * stack pa for later call
 * returns -1 on error, otherwise the number of pushed parameters
 */
static int MACRO_CheckArgs(void* pa[], unsigned max, const char* args)
{
    int t;
    unsigned int len = 0, idx = 0;

    WINE_TRACE("Checking %s\n", args);

    if (yylex() != '(') {WINE_WARN("missing (\n");return -1;}

    if (*args)
    {
        len = strlen(args);
        for (;;)
        {
            t = yylex();
            WINE_TRACE("Got %s <=> %c\n", ts(t), *args);

            switch (*args)
            {
            case 'S': 
                if (t != STRING)
                {WINE_WARN("missing S\n");return -1;}
                pa[idx] = (void*)yylval.string;  
                break;
            case 'U':
            case 'I':
                if (t != INTEGER)
                {WINE_WARN("missing U\n");return -1;}   
                pa[idx] = LongToPtr(yylval.integer);
                break;
            case 'B':
                if (t != BOOL_FUNCTION) 
                {WINE_WARN("missing B\n");return -1;}   
                if (MACRO_CallBoolFunc(yylval.function, yylval.proto, &pa[idx]) == 0)
                    return -1;
                break;
            default: 
                WINE_WARN("unexpected %s while args is %c\n", ts(t), *args);
                return -1;
            }
            idx++;
            if (*++args == '\0') break;
            t = yylex();
            if (t == ')') goto CheckArgs_end;
            if (t != ',') {WINE_WARN("missing ,\n");return -1;}
            if (idx >= max) {WINE_FIXME("stack overflow (%d)\n", max);return -1;}
        }
    }
    if (yylex() != ')') {WINE_WARN("missing )\n");return -1;}

CheckArgs_end:
    while (len > idx) pa[--len] = NULL;
    return idx;
}

/******************************************************************
 *		MACRO_CallBoolFunc
 *
 * Invokes boolean function fn, which arguments are defined by args
 * stores bool result into ret
 */
static int MACRO_CallBoolFunc(void *fn, const char* args, void** ret)
{
    void*       pa[2];
    int         idx = MACRO_CheckArgs(pa, sizeof(pa)/sizeof(pa[0]), args);

    if (idx < 0) return 0;
    if (!fn)     return 1;

    WINE_TRACE("calling with %u pmts\n", idx);

    switch (strlen(args))
    {
    case 0:
    {
        BOOL (WINAPI *func)(void) = fn;
        *ret = (void *)(ULONG_PTR)func();
        break;
    }
    case 1:
    {
        BOOL (WINAPI *func)(void *) = fn;
        *ret = (void *)(ULONG_PTR)func( pa[0]);
        break;
    }
    default: WINE_FIXME("NIY\n");
    }

    return 1;
}

/******************************************************************
 *		MACRO_CallVoidFunc
 *
 *
 */
static int MACRO_CallVoidFunc(void *fn, const char* args)
{
    void*       pa[6];
    int         idx = MACRO_CheckArgs(pa, sizeof(pa)/sizeof(pa[0]), args);

    if (idx < 0) return 0;
    if (!fn)     return 1;

    WINE_TRACE("calling %p with %u pmts\n", fn, idx);

    switch (strlen(args))
    {
    case 0:
    {
        void (WINAPI *func)(void) = fn;
        func();
        break;
    }
    case 1:
    {
        void (WINAPI *func)(void*) = fn;
        func( pa[0] );
        break;
    }
    case 2:
    {
        void (WINAPI *func)(void*,void*) = fn;
        func( pa[0], pa[1] );
        break;
    }
    case 3:
    {
        void (WINAPI *func)(void*,void*,void*) = fn;
        func( pa[0], pa[1], pa[2] );
        break;
    }
    case 4:
    {
        void (WINAPI *func)(void*,void*,void*,void*) = fn;
        func( pa[0], pa[1], pa[2], pa[3] );
        break;
    }
    case 5:
    {
        void (WINAPI *func)(void*,void*,void*,void*,void*) = fn;
        func( pa[0], pa[1], pa[2], pa[3], pa[4] );
        break;
    }
    case 6:
    {
        void (WINAPI *func)(void*,void*,void*,void*,void*,void*) = fn;
        func( pa[0], pa[1], pa[2], pa[3], pa[4], pa[5] );
        break;
    }
    default: WINE_FIXME("NIY\n");
    }

    return 1;
}

BOOL MACRO_ExecuteMacro(WINHELP_WINDOW* window, LPCSTR macro)
{
    struct lex_data     curr_lex_data, *prev_lex_data;
    BOOL ret = TRUE;
    int t;

    WINE_TRACE("%s\n", wine_dbgstr_a(macro));

    prev_lex_data = lex_data;
    lex_data = &curr_lex_data;

    memset(lex_data, 0, sizeof(*lex_data));
    lex_data->macroptr = macro;
    lex_data->window = WINHELP_GrabWindow(window);

    while ((t = yylex()) != EMPTY)
    {
        switch (t)
        {
        case VOID_FUNCTION:
            WINE_TRACE("got type void func(%s)\n", yylval.proto);
            MACRO_CallVoidFunc(yylval.function, yylval.proto);
            break;
        case BOOL_FUNCTION:
            WINE_WARN("got type bool func(%s)\n", yylval.proto);
            break;
        default:
            WINE_WARN("got unexpected type %s\n", ts(t));
            YY_FLUSH_BUFFER;
            ret = FALSE;
            goto done;
        }
        switch (t = yylex())
        {
        case EMPTY:     goto done;
        case ';':       break;
        default:        ret = FALSE; YY_FLUSH_BUFFER; goto done;
        }
    }

done:
    for (t = 0; t < lex_data->cache_used; t++)
        HeapFree(GetProcessHeap(), 0, lex_data->cache_string[t]);
    lex_data = prev_lex_data;
    WINHELP_ReleaseWindow(window);

    return ret;
}

WINHELP_WINDOW* MACRO_CurrentWindow(void)
{
    return lex_data ? lex_data->window : Globals.active_win;
}

#ifndef yywrap
int yywrap(void) { return 1; }
#endif