/*----------------------------------------------------------------------------
    %%File: EMTEST.C
    %%Unit: Event Monitor (mntr)
    %%Contact: daleg

    Event Monitor Sample Application, Main Program.

    The purpose of this application is to demonstrate how to process events
    using the Event Monitor's Rule Compiler and rule engine.
----------------------------------------------------------------------------*/

//***   genem.c -- 'generic' evtmon client-side stuff
// DESCRIPTION
//  the client-side of an evtmon app has two parts:
//      - evtmon generic code (client-side)
//      - application-specific rules
//  this file is reusable (and semi-frozen) generic code.  it should be
//  #include'ed from the app.
// NOTES
//  WARNING: do *not* put app-specific code here.  put it in the
//  client (emclient/libem.c).  in fact in general, think twice before
//  modifying this file at all.

//***   YY_* -- generic rules support
//
#define YY_BASE     1   // base features (always needed)
#ifdef YY_BASE
// rulc.exe doesn't create all files if a given feature isn't used,
// so we can't unconditionally include these guys.  plus, we don't
// want the helper code if we don't need it.
// turn on these features here if you use them
#ifndef YY_DELAYED
#define YY_DELAYED  0   // delayed actions
#endif
#ifndef YY_CTOR
#define YY_CTOR     0   // action ctors
#endif
#ifndef YY_SEQCHECK
#define YY_SEQCHECK 0   // seq_check
#endif
#define YY_OTHER    0   // not sure what emactr.h is for
//#define YY_BRKPT()
#endif

//***   FEATURE_* -- domain-specific rules support
// NOTES
//  untested! not sure if these #if's are done right
#define FEATURE_DEMO    0   // demo code (e.g. wndproc test app)
#define FEATURE_SAMPLE  0   // sample code (e.g. joe-event firer)
#define FEATURE_OFFICE  0   // base office stuff
#define FEATURE_TEXT    0   // text parser
#define FEATURE_WORD    0   // format parser
#define FEATURE_DEAD    0   // unused/dead code
#define FEATURE_NYI     0   // things i don't understand

#include "mso.h"
#include "msoem.h"

DEBUGASSERTSZ


#include "msolex.h"
#include "emrule.h"
#include "emkwd.h"
#include "emact.h"

#if FEATURE_DEMO
#include "emtest.h"
#include "emres.h"
#endif

// { Files generated by Rule Compiler
#include "emdef.h"          // rules (#defines)
#if YY_CTOR // {
#include "emacts.h"         // ctor macro wrappers
#endif // }
#define DEBUG_RULE_POINTERS // Want ptrs to nodes
#include "emruli.h"         // rulebase
#if YY_SEQCHECK // seq_check
#include "emsqck.c_"        // sequences
#endif
// }


#if FEATURE_TEXT // {
// Constants
#define cbEditText  1024

#ifdef STATIC_INIT

#define grfLexFlagsEm    (MsoGrfLexFNoReset \
                        | MsoGrfLexFLookup \
                        | MsoGrfLexFLookupIntsAndSyms \
                        | MsoGrfLexFAllCapsAsFormat)

// EM data structures
MSORULTK rgrultkEmToken[200];                           // Text Token cache
MSORULTK rgrultkEmFormat[100];                          // Format Token cache
MSOLEXS vlexs =
    {
    &vkwtbEmTOKEN_KEYTABLE,                             // pkwtb
    {rgrultkEmToken, 200, },                            // rultkhToken
    {rgrultkEmFormat, 100, },                           // rultkhFormat
    fTrue,                                              // fInited
    isttblDefault,                                      // isttbl
    grfLexFlagsEm,                                      // grpfLexFlags
    _hObjectNil,                                        // hObjectNil
    MsoWchLexGetNextBufferDoc,                          // pfnlexbuf
    FGetNextLexRun,                                     // pfnlexrun
    FGetTokenTextObject,                                // pfnlextxt
    NULL,                                               // pfnlexfmt
    NULL,                                               // pfnlexrunDiscontig
    NULL,                                               // pfnlexrunForceCompl
    iNil,                                               // ichRun
    -1,                                                 // cchLookahead
    };
MSOLEXS *vplexs = &vlexs;                               // Global lexer state

#else /* !STATIC_INIT */

MSOLEXS     vlexs;                                      // Global lexer state
MSOLEXS    *vplexs;                                     // Global lexer state

#endif /* STATIC_INIT */
#endif // }

#if YY_BASE // {
// Global variables
RULS       *vlpruls = &vrulsEm;
#ifndef STATIC_LINK_EM
RULS      **_pvlprulsDLL = &vlpruls;                    // App's Global state
#endif /* !STATIC_LINK_EM */
#endif // }

#if FEATURE_DEMO // {
char        vszAppName[20];                             // Application name
HWND        vhInst;                                     // Instance
HWND        vhWndMain;                                  // Main window
HWND        vhWndDlg;                                   // Dialog window
char        vszEditText[cbEditText];                    // Text of edit control
int         vcchEditText;                               // #chs in edit control
static int  vcchPrev = 0;                               // Prev edit box len
static int  vcpIpPrev = 0;                              // Prev edit box ip
int         vfSettingDlgItem = fFalse;                  // Avoid Win recursion
#endif // }

#if YY_BASE // {
#if YY_DELAYED
#include "emactr.h"         // data tables
MSOACTTBL   vacttbl = {vrgacttrecEm};                   // Global action table
MSOACTTBL  *_pacttbl = &vacttbl;                        // Glob action tbl ptr
#endif
#endif // }

#if FEATURE_OFFICE // {
EMS         vems;                                       // EM global state
EMS        *vpems = &vems;                              // Ptr to global state
#endif // }

#if YY_BASE // {
#ifdef DEBUG
#ifndef STATIC_LINK_EM
int vwDebugLogFilter = fDebugFilterAll;
int vwDebugLogLvl = -2;
#endif // !STATIC_LINK_EM
// Pointers to global debug logging vars in Mso DLL
int *pvwDebugLogFilter = &vwDebugLogFilter;
int *pvwDebugLogLvl = &vwDebugLogLvl;
#endif /* DEBUG */
#endif // }


#if YY_BASE // {
#ifdef STATIC_INIT // {
/* F  I N I T  E M */
/*----------------------------------------------------------------------------
    %%Function: FInitEm
    %%Contact: daleg

    Initialize the static (compiled) Event Monitor rules.
----------------------------------------------------------------------------*/

int FInitEm(void)
{
#ifndef STATIC_LINK_EM
    /* Mirror global debug pointers in Mso97.dll */
    Debug(pvwDebugLogLvl = MsoPwDebugLogLvl(&pvwDebugLogFilter);)
#endif /* !STATIC_LINK_EM */
    Debug(*pvwDebugLogLvl = 7);

#ifndef STATIC_LINK_EM
    /* Mirror global pointers (vlpruls, etc) in Mso97.dll */
    _pvlprulsDLL = MsoPvlprulsMirror(&vlpruls);
    *_pvlprulsDLL = &vrulsEm;
#endif /* !STATIC_LINK_EM */

#ifdef DYN_RULES
    /* Load dynamic rules */
    FLoadDynEmRules();
#endif /* DYN_RULES */

#if FEATURE_OFFICE
    /* For performance reasons, action table is global */
    vacttbl.prultkh = &vplexs->rultkhToken;
#endif

#if FEATURE_DEMO
    /* Allow Event Drivers to run */
    EnableEM();
#endif

    /* Propagate the values through the rule network */
    MsoScheduleIrul(irul_YYSTD_INIT, fTrue);
#ifdef DYN_RULES
    MsoScheduleIrul(irul_YYSTD_LOADING_RULEBASE, fTrue);
#endif /* DYN_RULES */
    MsoEvaluateEvents(rulevtEm_YYSTD);

    return fTrue;
}
#else /* }{ !STATIC_INIT */
/* F  I N I T  E M */
/*----------------------------------------------------------------------------
    %%Function: FInitEm
    %%Contact: daleg

    Initialize the static (compiled) Event Monitor rules.
----------------------------------------------------------------------------*/

int FInitEm(void)
{
#ifndef STATIC_LINK_EM
    /* Mirror global debug pointers in Mso97.dll */
    Debug(pvwDebugLogLvl = MsoPwDebugLogLvl(&pvwDebugLogFilter);)
#endif /* !STATIC_LINK_EM */
    Debug(*pvwDebugLogLvl = 7);

    /* Initialize rule base, for performance reasons, rulebase is global */
    if (!MsoFInitStaticRuls(&vrulsEm, &vrulsEm))
        return fFalse;

#ifndef STATIC_LINK_EM
    /* Mirror global pointers (vlpruls, etc) in Mso97.dll */
    _pvlprulsDLL = MsoPvlprulsMirror(&vlpruls);
    *_pvlprulsDLL = &vrulsEm;
#endif /* !STATIC_LINK_EM */

#ifdef DYN_RULES
    /* Load dynamic rules */
    FLoadDynEmRules();
#endif /* DYN_RULES */

#if FEATURE_OFFICE
    /* Initialize the lexer to scan the doc */
    if (!(vplexs = MsoPlexsLexInitDoc
                        (&vlexs, _hObjectNil, FGetNextLexRun,
                        FGetTokenTextObject, NULL, NULL, 200, 100)))
        return fFalse;
    vplexs->pkwtb = &vkwtbEmTOKEN_KEYTABLE;

    /* For performance reasons, action table is global */
    vacttbl.prultkh = &vplexs->rultkhToken;
#endif

#if FEATURE_DEMO
    /* Allow Event Drivers to run */
    EnableEM();
#endif

    /* Propagate the values through the rule network */
    MsoScheduleIrul(irul_YYSTD_INIT, fTrue);
#ifdef DYN_RULES
    MsoScheduleIrul(irul_YYSTD_LOADING_RULEBASE, fTrue);
#endif /* DYN_RULES */
    MsoEvaluateEvents(rulevtEm_YYSTD);

    return fTrue;
}
#endif /* } STATIC_INIT */
#endif // }


#if YY_BASE // {
/* F  E V A L  E M  R U L E */
/*----------------------------------------------------------------------------
    %%Function: FEvalEmRule
    %%Contact: daleg

    Evaluate the rule associated with the given rule ID number.
    Return a boolean value for whether its value was TRUE or FALSE.
----------------------------------------------------------------------------*/

#include "emeval.c"
#endif // }




#if FEATURE_DEMO // {
/* W I N  M A I N */
/*----------------------------------------------------------------------------
    %%Function: WinMain
    %%Contact: daleg

    Main routine.
----------------------------------------------------------------------------*/

int CALLBACK WinMain(
    HANDLE              hInstance,
    HANDLE              hPrevInstance,
    LPSTR               lpszCmdLine,
    int                 nCmdShow
    )
{
    MSG                 msg;
    int                 nRc;
    char                szString[256];

    GdiSetBatchLimit(1);
    strcpy(vszAppName, "EMTEST");
    vhInst = hInstance;

    if (!hPrevInstance)
        {
        /* Register window classes if first instance of application */
        if ((nRc = nCwRegisterClasses()) == -1)
            {
            /* Put up msg if registering one of the windows failed */
            LoadString(vhInst, IDS_ERR_REGISTER_CLASS, szString,
                       sizeof(szString));
            MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
            return nRc;
            }
        }

    /* Create application's Main window */
    vhWndMain = CreateWindow(vszAppName,
                             NULL,
                             WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
                                | WS_MAXIMIZEBOX | WS_THICKFRAME
                                | WS_CLIPCHILDREN | WS_OVERLAPPED,
                             0, 0, 400, 400,
                             NULL, NULL, vhInst, NULL);

    /* If could not create main window, be nice before quitting */
    if (vhWndMain == NULL)
        {
        LoadString(vhInst, IDS_ERR_CREATE_WINDOW, szString, sizeof(szString));
        MessageBox(NULL, szString, NULL, MB_ICONEXCLAMATION);
        return IDS_ERR_CREATE_WINDOW;
        }

    /* Initialize the rulebase */
    if (!FInitEm())
        return 1;

    /* Display main window */
    ShowWindow(vhWndMain, nCmdShow);

    /* Until WM_QUIT message */
    while (GetMessage(&msg, NULL, 0, 0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }

    /* Do clean up before exiting from the application */
    CwUnRegisterClasses();

    return msg.wParam;
}


/* W N D  P R O C */
/*----------------------------------------------------------------------------
    %%Function: WndProc
    %%Contact: daleg

    Windows proc for main window.
----------------------------------------------------------------------------*/

LONG CALLBACK WndProc(
    HWND                hWnd,
    UINT                Message,
    WPARAM              wParam,
    LPARAM              lParam
    )
{
    HMENU               hMenu = 0;
    int                 nRc = 0;

    switch (Message)
        {
    case WM_COMMAND:
        FEmEvalDialog(wParam);

        switch (LOWORD(wParam))
            {
        case IDM_DIALOG:
            /* Respond to the menu item named "Dialog" */
            {
            FARPROC     lpfnDIALOGSMsgProc;

            lpfnDIALOGSMsgProc = MakeProcInstance((FARPROC) DIALOGSMsgProc,
                                                  vhInst);
            nRc = DialogBox(vhInst, MAKEINTRESOURCE(IDM_DIALOG), hWnd,
                            lpfnDIALOGSMsgProc);
            FreeProcInstance(lpfnDIALOGSMsgProc);
            }
            break;

        default:
            return DefWindowProc(hWnd, Message, wParam, lParam);
            }
        break;

    case WM_CLOSE:
        /* Destroy child windows, modeless dialogs, then, this window */
        DestroyWindow(hWnd);

        /* Quit the application */
        if (hWnd == vhWndMain)
            PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hWnd, Message, wParam, lParam);
        }
    return 0L;
}


/* D  I  A  L  O  G  S  M S G  P R O C */
/*----------------------------------------------------------------------------
    %%Function: DIALOGSMsgProc
    %%Contact: daleg

    Dialog proc for inner window.
----------------------------------------------------------------------------*/

BOOL CALLBACK DIALOGSMsgProc(
    HWND                hWndDlg,
    UINT                Message,
    WPARAM              wParam,
    LPARAM              lParam
    )
{
    switch (Message)
        {
    case WM_INITDIALOG:
        cwCenter(hWndDlg, 0);
        vcchPrev = 0;
        vcpIpPrev = 0;
        vhWndDlg = hWndDlg;
        break;

    case WM_CLOSE:
        break;

    case WM_COMMAND:
        switch (LOWORD(wParam))
            {
        case IDC_EDIT1:
            switch (HIWORD(wParam))
                {
            case EN_CHANGE:
                vcchEditText = GetDlgItemText(hWndDlg, IDC_EDIT1, vszEditText,
                                              cbEditText);
                if (!vfSettingDlgItem)
                    {
                    MSOCP   cpIp;
                    XCHAR   wch;

                    /* Get IP (Insertion Point) position */
                    SendDlgItemMessage(hWndDlg, IDC_EDIT1, EM_GETSEL, 0,
                                       (LPARAM)&cpIp);

                    /* Get character typed */
                    wch = (vcchEditText > vcchPrev
                            ? vszEditText[cpIp - 1]     // Char typed
                            : xchBackspace);            // Backspace

                    /* Notify Event Monitor CHAR driver */
                    FEmEvalChar(wch, cpIp, vszEditText, vcchEditText);

                    /* Track prev text positions for invalidation detect */
                    vcchPrev = vcchEditText;
                    vcpIpPrev = cpIp;
                    }
                break;
                }
            break;
        case IDC_BUTTON1:
        case IDC_BUTTON2:
        case IDC_START:
        case IDC_STOP:
        default:
            FEmEvalDialog(wParam);
            break;
        case IDOK:
            FEmEvalDialog(wParam);
            EndDialog(hWndDlg, FALSE);
            break;
            }
        break;

    default:
        return FALSE;
        }
    return TRUE;
}
#endif // }


#if FEATURE_SAMPLE // { joe event firer
/* F  E M  E V A L  D I A L O G */
/*----------------------------------------------------------------------------
    %%Function: FEmEvalDialog
    %%Contact: daleg

    Event Monitor Event Driver for DIALOG events.
----------------------------------------------------------------------------*/

int FEmEvalDialog(WPARAM wParam)
{
    short               idc = LOWORD(wParam);
    IRUL                irul;

#if FEATURE_DEMO
    /* Make sure we are enabled (e.g. macros not running) */
    if (FEmDisabled())
        return fFalse;
#endif

    /* Match the (sparse) dialog object to its associated (contig) event, if any */
    vlpruls->irulPrimaryEvent = irul
        = (IRUL) MsoPkwdlhLookupL(idc, &vkwtbEmIDC_KEYTABLE)->tk;

    debugEM1(2, "DIALOG EVENT: %-20.20s\n",
             LpchRulName(LprulFromIrul(irul)));

    /* Push event into appropriate queue (resets depth) */
    MsoScheduleIrul(irul, fTrue);
    MsoScheduleIrul(irulIDC_, idc);     // non-mapped event (autoclear global)

    /* Propagate the values through the rule network */
    MsoEvaluateEvents(rulevtEmDIALOG);
    // n.b. IDC_ is now autocleared

    /* Evaluate any pending actions */
    if (_pacttbl->pactPending)
        DoPendingActions();
}

/* F  E M  E V A L  C H A R */
/*----------------------------------------------------------------------------
    %%Function: FEmEvalChar
    %%Contact: daleg

    Event Driver for CHAR events in Event Monitor.
    Evaluate CHAR (KEYBOARD) events within the Event Monitor rulebase.
    Return whether or not it was handled.
----------------------------------------------------------------------------*/

int FEmEvalChar(
    XCHAR               wch,
    MSOCP               cpIp,
    XCHAR              *wz,
    int                 ichMac
    )
{
    IRUL                irul;

#if FEATURE_DEMO
    /* Make sure we are enabled (e.g. macros not running) */
    if (FEmDisabled())
        return fFalse;
#endif

    /* Primitive invalidation */
    if (FCheckIfMustReset(wch, ichMac, cpIp))
        InvalLex(vplexs);

    /* If our running state is still valid, adjust run variables */
    if (FInvalLex(vplexs))
        FResetEm((vplexs->cpIp = cpIp) - 1, wz, ichMac);

    /* Create a "lookahead" token to hold chars not yet scanned by lexer */
    vplexs->cchLookahead++;
    _CacheTkTextNext(vplexs);

    /* Look up character in keyword table */
    irul = (IRUL) MsoPkwdLookupName(&wch, 1, &vkwtbEmCHAR_KEYTABLE)->tk;

    debugEM3(2, "CHAR EVENT: %-20.20s for '%s' (0x%x)\n",
             LpchRulName(LprulFromIrul(irul)),
             MsoSzFromRgxchDebug(&wch, 1), wch);

    /* Push text token into appropriate queue (resets depth) */
    MsoScheduleIrul(irulCH_, wch);
    MsoScheduleIrul(vlpruls->irulPrimaryEvent = irul, fTrue);

    /* Propagate the values through the rule network */
    MsoEvaluateEvents(rulevtEmCHAR);

    /* Call TOKEN event driver to process any token events */
    FEmEvalToken(cpIp, wz, ichMac);
}

/* E M  E V A L  T K  I R U L */
/*----------------------------------------------------------------------------
    %%Function: EmEvalTkIrul
    %%Contact: daleg

    Evaluate a TOKEN event irul.
----------------------------------------------------------------------------*/

void EmEvalTkIrul(IRUL irul)
{
    MSORULTK           *prultk;

    debugEM2(2, "TOKEN EVENT: %-20.20s \"%.100s\"\n",
             LpchIrulName(irul), MsoSzLexTokenText(vplexs));

    /* Push pending token events into appropriate queues */
    prultk = PrultkFromTokenIrultk(vplexs, vplexs->rultkhToken.irultkMin);
    while (vplexs->rultkhToken.irultkMin != vplexs->rultkhToken.irultkLim)
        {
#ifdef DEBUG
        if (prultk->tk != irul)
            {
            debugEM2(4, "EXTRA TOKEN EVENT: %-20.20s %d\n",
                     LpchIrulName(prultk->tk), prultk->lValue);
            debugEM1(8, "  at CP %ld\n", prultk->cpFirst);
            }
#endif /* DEBUG */

        /* Push text token into appropriate queue (resets depth) */
        MsoSignalEventIrul(IrulFromTk(prultk->tk), prultk->lValue);

        /* Move to next cache record, wrapping around if necessary */
        IncrTokenPrultk(vplexs, &prultk, &vplexs->rultkhToken.irultkMin);
        }

    /* Push any applicable format tokens into appropriate queues */
    if (vplexs->rultkhFormat.irultkMin != vplexs->rultkhFormat.irultkLim)
        PushIrultkFormatPending();

    /* Push text token into appropriate queue (resets depth) */
    MsoScheduleIrul(irulTOKEN_, vlpruls->irulPrimaryEvent = irul);

    /* Propagate the values through the rule network */
    MsoEvaluateEvents(rulevtEmTOKEN);
}
#endif // }


#if FEATURE_SAMPLE // {
/* F  E M  E V A L  T O K E N */
/*----------------------------------------------------------------------------
    %%Function: FEmEvalToken
    %%Contact: daleg

    Event Monitor Event Driver for TOKEN events.
----------------------------------------------------------------------------*/

int FEmEvalToken(
    MSOCP               cpIp,
    XCHAR              *wz,
    int                 ichMac
    )
{
    IRUL                irul;

#if FEATURE_DEMO
    /* Make sure we are enabled (e.g. macros not running) */
    if (FEmDisabled())
        return fFalse;
#endif

    /* If our running state is still valid, adjust run variables */
    if (!FInvalLex(vplexs))
        {
        long            dwz = wz - vplexs->pxchBuffer;

        vplexs->pxchTkStart += dwz;
        vplexs->pxchNext += dwz;
        vplexs->pxchRun += dwz;
        vplexs->pxchBuffer += dwz;
        vplexs->cpIp = cpIp;
        vplexs->cpLim = ichMac;
        vplexs->cchRemain = vplexs->pxchBuffer + vplexs->cpLim
                - vplexs->pxchNext;
        }

    /* Else rescan back up to IP */
    else if (!FResetEm((vplexs->cpIp = cpIp) - 1, wz, ichMac))
        return fFalse;

    /* Inject and eval any complete tokens up to IP */
    while (FValidIrul(irul = IrulFromTk(MsoTkLexTextCpLim(vplexs, cpIp))))
        EmEvalTkIrul(irul);

    /* Evaluate any pending actions */
    if (_pacttbl->pactPending)
        DoPendingActions();

    /* All typed characters must now have been seen by the lexer */
    if (vplexs->cchLookahead > 0)
        vplexs->cchLookahead = 0;
}
#endif // }


#if FEATURE_TEXT // {
/* P U S H  I R U L T K  F O R M A T  P E N D I N G */
/*----------------------------------------------------------------------------
    %%Function: PushIrultkFormatPending
    %%Contact: daleg

    Push any pending format tokens into appropriate queues.
----------------------------------------------------------------------------*/

void PushIrultkFormatPending(void)
{
    MSORULTK           *prultk;

    debugEM1(8, "Checking for formatting before CP %ld\n",
             CpLexTokenFirst(vplexs));

    prultk = PrultkFormatFromIrultk(vplexs, vplexs->rultkhFormat.irultkMin);

    while (vplexs->rultkhFormat.irultkMin != vplexs->rultkhFormat.irultkLim
            &&  (prultk->cpFirst
                        < CpLexTokenFirst(vplexs) + DcpLexToken(vplexs)
                 ||  (DcpLexToken(vplexs) == 0
                        &&  prultk->cpFirst <= CpLexTokenFirst(vplexs))))
        {
        debugEM2(2, "FORMAT   : %-20.20s %ld\n",
                 LpchIrulName(prultk->tk), prultk->lValue);
        debugEM1(6, "  at CP %ld\n", prultk->cpFirst);

        /* Push format token into appropriate queue (resets depth) */
        MsoSignalEventIrul(IrulFromTk(prultk->tk), prultk->lValue);

        /* Move to next cache record, wrapping around if necessary */
        IncrFormatPrultk(vplexs, &prultk, &vplexs->rultkhFormat.irultkMin);
        }
}
#endif // }



#if FEATURE_TEXT // {
/* F  R E S E T  E M */
/*----------------------------------------------------------------------------
    %%Function: FResetEm
    %%Contact: daleg

    Reset the Event Monitor rules and lexer due to an IP (cursor) change,
    or due to some form of invalidation.
----------------------------------------------------------------------------*/

int FResetEm(
    MSOCP               cpIp,
    XCHAR              *wz,
    int                 ichMac
    )
{
    MSOCP               cpObject = 0;
    IRUL                irul;

    /* Initialize lexer to point to start of buffer */
    vplexs->pObjectIp = PObjectCur();
    vplexs->pxchBuffer = vplexs->pxchBufferIp = vplexs->pxchRun = wz;
    MsoLexSetPos(vplexs, cpObject, 0);

    debugEM0(0, "====================================================\n");
    debugEM3(0, "   FResetEm: RESETTING: pObjectIp %x cpLine %ld cp %ld\n",
             vplexs->pObjectIp, cpObject, cpIp);
    debugEM0(0, "====================================================\n");

    /* Reset rule base TOKEN state variables */
    MsoClearEventsForRulevts(rulevtEmTOKEN, drulevtToken,
                             (vpems->fInternalReset
                                ? rultPersistentRule | rultAlwaysPersist
                                : rultAlwaysPersist),
                             fTrue, fFalse);

    vplexs->wInterval = CIntervalsRulevt(rulevtEmTOKEN);

    /* Reset lexer base state */
    MsoResetLexState(vplexs, fTrue/*fFullReset*/);
    vplexs->cpLim = 0;
    vplexs->fInvalLexer = fFalse;

    /* Mark start of scan */
    MsoCacheTkText(vplexs, irulSTART, fTrue);
    MsoScheduleIrul(irulSTART, fTrue);

    /* Mark start of token cache, but not as events */
    MsoCacheTkText(vplexs, irulEND_OBJ, fTrue);
    vplexs->rultkhToken.irultkMin = vplexs->rultkhToken.irultkLim;

    /* Run only rules not marked as INTERACTIVE_ONLY */
    SetCurrRulg(rulgEmALWAYS);

    /* Inject and eval any complete tokens up to IP */
    while (FValidIrul(irul = IrulFromTk(MsoTkLexTextCpLim(vplexs, cpIp))))
        EmEvalTkIrul(irul);

    /* Run only rules not marked as INTERACTIVE_ONLY */
    SetCurrRulg(rulgEmINTERACTIVE_ONLY);

    /* Mark that we are synchronized */
    vplexs->cchLookahead = 0;

    return fTrue;
}


/* F  G E T  T O K E N  T E X T  O B J E C T */
/*----------------------------------------------------------------------------
    FGetTokenTextObject
    %%Contact: smueller

    Determine whether the text of the requested token needs to be fetched
    from the document.  If so, do so, leaving the results in ppxch and pcch,
    and return fTrue.  Otherwise, return fFalse.
----------------------------------------------------------------------------*/

int WIN_CALLBACK FGetTokenTextObject(
    MSORULTK           *prultk,
    const XCHAR       **ppxch,                          // RETURN
    int                *pcch,                           // RETURN
    struct _MSOLEXS    *plexs
    )
{
    if (plexs->pObject != NULL)
        {
        // NOT SUPPORTED YET
        return fTrue;
        }

    return fFalse;
}


/* F  G E T  N E X T  L E X  R U N */
/*----------------------------------------------------------------------------
    %%Function: FGetNextLexRun
    %%Contact: daleg

    Return next run of text within the current object , and set the lexer
    run-state variables.

    For this demo, there ain't any, but we will show a commented-out sample.
----------------------------------------------------------------------------*/

int WIN_CALLBACK FGetNextLexRun(MSOCP cpLim, MSOLEXS *plexs)
{
    int                 fStatus = fTrue;
    const XCHAR        *pxchPrevEnd = plexs->pxchNext;

    /* If prev run at end of current object, move to next object */
    if (FLexEndOfScan(vplexs))
        {
        /* If still have a pending token, complete it first */
        if (DcpLexCurr(vplexs) > 0)
            return fFalse;

        /* Create exactly one inter-object "created" tkEND_OBJ character */
        if (plexs->fCreateEndObjCh)
            {
            /* Make this run "created" text, i.e. no dcp */
            fStatus = fFalse;
            plexs->cpRun += plexs->ccpRun;
            plexs->ichRun += plexs->cchRun;
            plexs->cchRun = 1;
            plexs->ccpRun = 0;

#ifdef READY
            /* Mark run as "created", so dcp calcs will work properly */
            plexs->fAdjustTokenCps = fTrue;
            plexs->dcpCreated += 1;
            if (plexs->cpFirstCreated == 0L)
                plexs->cpFirstCreated = plexs->cpRun;
#endif /* READY */
            }

        /* Else get next object's text */
        else
            {
            if (!FGetNextLexObject(cpLim, plexs))
                return fFalse;

            /* If in object of IP has passed upper lim of scan, block lexer */
            if (plexs->pObject == plexs->pObjectIp  &&  cpLim != msocpMax
                    &&  cpLim <= 0 /* plexs->cpRun */)
                fStatus = fFalse;
            }
        }

    /* Else move to next run */
    else
        {
        plexs->cpRun += plexs->ccpRun;
        plexs->ichRun += plexs->cchRun;

#ifdef PORT_THIS
        plexs->cchRun = vcchEditText - plexs->ichRun;
#endif /* PORT_THIS */
        plexs->ccpRun = plexs->cchRun;

        // Reset buffer pointer if run in a different buffer
#ifdef PORT_THIS
        plexs->pxchBuffer = vszEditText;
#endif /* PORT_THIS */
        }

    /* Set Run length and pointer */
    plexs->cchRemain = plexs->cchRun;
    plexs->pxchNext = (plexs->pxchRun = plexs->pxchBuffer + plexs->ichRun);

    AssertSz0(pxchPrevEnd == plexs->pxchNext
                ||  pxchPrevEnd == plexs->pxchTkStart,
              "Discontiguous runs!");

    /* If run starts new text (line), reset non-cached tk start pointer */
    if (pxchPrevEnd == plexs->pxchTkStart)
        plexs->pxchTkStart = plexs->pxchNext;

    return fStatus;
}


/* F  G E T  N E X T  L E X  O B J E C T */
/*----------------------------------------------------------------------------
    %%Function: FGetNextLexObject
    %%Contact: daleg

    Set the "run" state variables to the first run of a new object, including
    the text buffer, and the run lengths.
----------------------------------------------------------------------------*/

int WIN_CALLBACK FGetNextLexObject(MSOCP cpLim, MSOLEXS *plexs)
{
    // If this is the first time thru, do initialization stuff
    if (plexs->ichRun == iNil)
        {
        // YOUR INITS HERE
        }

    // Fetch new object text
    plexs->pObject = PObjectCur();

    // Fetch new text (in our case, it is globally static)
    plexs->pxchBuffer = vszEditText;
    plexs->cchRun = plexs->cpLim = vcchEditText;
    plexs->pxchRun = plexs->pxchBuffer;

    // Set up other state variables
    plexs->ichRun = 0;
    plexs->cpRun = 0;
    plexs->cpObject = 0;
    plexs->ccpRun = plexs->cchRun;
    SetCpLexTokenFirst(vplexs, SetCpLexTokenNext(vplexs, 0));

    return fTrue;
}


/* F  L E X  F O R C E  C O M P L E T E */
/*----------------------------------------------------------------------------
    %%Function: FLexForceComplete
    %%Contact: daleg

    Force the current token to complete within the lexer.  This is a callback
    function that gets called when we wish to cause the lexer to finish a
    token without peeking at the next character.  This is generally when we
    are moving the IP out of a cell in a table, and wish to perform automatic
    actions anyway.

    Once we have completed the token, we clear the callback flag, to resume
    normal operation.

----------------------------------------------------------------------------*/

int WIN_CALLBACK FLexForceComplete(MSOCP cpLim, MSOLEXS *plexs)
{
    XCHAR               wch;

    /* If in the middle of a token complete it if next is EOO or delim */
    // REVIEW daleg: This should check for more than just spaces after
    if (plexs->iuState > 0
            &&  (cpLim == vplexs->cpLim
                    ||  (wch = *vplexs->pxchNext) == xchSpace))
        return fTrue;

    /* Do not call this routine next time */
    plexs->pfnlexrunForceComplete = NULL;

    /* Force an END_OBJ token (event) if we are at the cell boundary */
    return (cpLim == vplexs->cpLim);
}

#endif // }


#if YY_BASE // {

/* P A C T  P C A */
/*----------------------------------------------------------------------------
    %%Function: PactPca
    %%Contact: daleg

    Create a Delayed-Action record, using a MSOCA to define the edit range.
----------------------------------------------------------------------------*/

MSOACT *PactPca(
    MSOACTTBL          *pacttbl,
    int                 actt,
    MSOCA              *pca,
    ...
    )
{
    va_list             ap;
    MSOACT             *pact;

    /* Safety first: make sure we have an action structure */
    if (!pacttbl)
        return NULL;

    /* Start varargs */
    va_start(ap, pca);

    /* Create a new Delayed Action record, and push arg list into it */
    pact = MsoPactAp(pacttbl, actt,
                     (sizeof(MSOCA) + sizeof(long) - 1)/sizeof(long), ap);

    /* End varargs */
    va_end(ap);

    /* Calculate starting CP */
    pact->rec1_ca.ca = *pca;

    /* Insert the MSOACT record into the edit queue in CP sorted order */
    MsoInsertPact(pact, &pacttbl->pactPending);

    return pact;
}


#if YY_DELAYED // {
long WIN_CALLBACK DcpDoAct(
    MSOACT             *pact,
    MSOACTTBL          *pacttbl,
    long               *pdcp,
    MSOCA              *pca,
    MSOACT           **ppactNext,
    int                *pfDiscard
    );

/* D O  P E N D I N G  A C T I O N S */
/*----------------------------------------------------------------------------
    %%Function: DoPendingActions
    %%Contact: daleg

    Execute pending actions in delay action queue.
----------------------------------------------------------------------------*/

void DoPendingActions(void)
{
    _pacttbl->cpFirstEditPrev = -1;
    _pacttbl->dcpEditPrev = 0;
    _pacttbl->cpLimEdit = 0;
    MsoReversePact(&_pacttbl->pactPending);
    MsoDcpDoActs(&_pacttbl->pactPending, _pacttbl, 0, fTrue, -1, DcpDoAct);
}


/* D C P  D O  A C T */
/*----------------------------------------------------------------------------
    %%Function: DcpDoAct
    %%Contact: daleg

    Execute the action given by the MSOACT record.
----------------------------------------------------------------------------*/

long WIN_CALLBACK DcpDoAct(
    MSOACT             *pact,
    MSOACTTBL          *pacttbl,
    long               *pdcp,
    MSOCA              *pca,
    MSOACT           **ppactNext,
    int                *pfDiscard
    )
{
    switch (pact->rec1.actt)
        {
#include "emact.c_"
        }
    return 0;
}

#endif // }

#endif // }


#if FEATURE_DEMO // {
/* D C P  R E P L A C E  T E X T */
/*----------------------------------------------------------------------------
    %%Function: DcpReplaceText
    %%Contact: daleg

    Replace the text range given by the range of chars with the new string.
----------------------------------------------------------------------------*/

int DcpReplaceText(
    void               *pObject,
    int                 cpFirst,
    int                 cpLim,
    char               *sz
    )
{
    int                 cch = CchSz(sz);
    int                 dcp = cch - (cpLim - cpFirst);
    int                 cchOrig;
    char                rgch[cbEditText];
    MSOCP               cpIp;

    /* Get current edit control value */
    cchOrig = GetDlgItemText(vhWndDlg, IDC_EDIT1, rgch, cbEditText);

    /* Do not attempt to expand beyond control's capacity. */
    if (cchOrig + dcp >= cbEditText)
        return 0;

    /* Replace the string */
    CopyRgb(rgch + cpLim, rgch + cpLim + dcp, cchOrig - cpLim + 1);
    CopyRgbNo(sz, rgch + cpFirst, cch);

    /* Set the edit control value with the new string, preserving the IP */
    vfSettingDlgItem = fTrue;                           // Prevent recursion
    SendDlgItemMessage(vhWndDlg, IDC_EDIT1, EM_GETSEL, 0, (LPARAM)&cpIp);
    SendDlgItemMessage(vhWndDlg, IDC_EDIT1, WM_SETTEXT, 0, (LPARAM)&rgch[0]);
    SendDlgItemMessage(vhWndDlg, IDC_EDIT1, EM_SETSEL, cpIp + dcp, cpIp + dcp);
    vcchEditText += dcp;
    vfSettingDlgItem = fFalse;

    debugEM0(2, "Replaced text\n");

    return dcp;
}
#endif // }


#if FEATURE_TEXT // {
/* F  C H E C K  I F  M U S T  R E S E T */
/*----------------------------------------------------------------------------
    %%Function: FCheckIfMustReset
    %%Contact: daleg

    Do a primitive check to see if we should invalidate our running rulebase
    state, and do a full reset scan.

    THIS IS NOT A CANONICAL ROUTINE, but there should be something equivalent
    in each app.
----------------------------------------------------------------------------*/

int FCheckIfMustReset(XCHAR wch, int ichMac, MSOCP cpIp)
{
    if (wch == xchBackspace)
        return fTrue;

    /* If user typed a new character, emit character event */
    /* REVIEW: pasting will result in multiple new characters, of which we
       only notice the last */
    if (ichMac > vcchPrev)
        {
        Assert (cpIp > 0);

        /* Without notification of IP moves, there is no way to accurately
           identify when lexer needs invalidating; for safety, we could do so
           on every character.  For demonstrating the lexer, we'll do so only
           when the IP didn't move forward by exactly one character, which is
           a decent approximation of the right time. */
        /* REVIEW: find a more pleasant way to deal with this, like subclassing
           the edit control to get precise control over notifications. */

        if (cpIp != vcpIpPrev + 1)
            return fTrue;
        }

    return fFalse;
}
#endif // }


#if FEATURE_DEMO // {
/* N  C W  R E G I S T E R  C L A S S E S */
/*----------------------------------------------------------------------------
    %%Function: nCwRegisterClasses
    %%Contact: daleg

    Register window classes
----------------------------------------------------------------------------*/

int nCwRegisterClasses(void)
{
    WNDCLASS            wndclass;

    memset(&wndclass, 0x00, sizeof(WNDCLASS));

    /* load WNDCLASS with window's characteristics */
    wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW;
    wndclass.lpfnWndProc = WndProc;

    /* Extra storage for Class and Window objects */
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = vhInst;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);

    /* Create brush for erasing background */
    wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszMenuName = vszAppName;   /* Menu Name is App Name */
    wndclass.lpszClassName = vszAppName; /* Class Name is App Name */
    if (!RegisterClass(&wndclass))
        return -1;

    return (0);
}


/* C W  C E N T E R */
/*----------------------------------------------------------------------------
    %%Function: cwCenter
    %%Contact: daleg

    Center the main window.
----------------------------------------------------------------------------*/

void cwCenter(hWnd, top)
HWND hWnd;
int top;
{
    POINT               pt;
    RECT                swp;
    RECT                rParent;
    int                 iwidth;
    int                 iheight;

    /* get the rectangles for the parent and the child */
    GetWindowRect(hWnd, &swp);
    GetClientRect(vhWndMain, &rParent);

    /* calculate the height and width for MoveWindow */
    iwidth = swp.right - swp.left;
    iheight = swp.bottom - swp.top;

    /* find the center point and convert to screen coordinates */
    pt.x = (rParent.right - rParent.left) / 2;
    pt.y = (rParent.bottom - rParent.top) / 2;
    ClientToScreen(vhWndMain, &pt);

    /* calculate the new x, y starting point */
    pt.x = pt.x - (iwidth / 2);
    pt.y = pt.y - (iheight / 2);

    /* top will adjust the window position, up or down */
    if (top)
        pt.y = pt.y + top;

    /* move the window */
    MoveWindow(hWnd, pt.x, pt.y, iwidth, iheight, FALSE);
}


/* C W  U N  R E G I S T E R  C L A S S E S */
/*----------------------------------------------------------------------------
    %%Function: CwUnRegisterClasses
    %%Contact: daleg

    Un-register the windows classes.
----------------------------------------------------------------------------*/

void CwUnRegisterClasses(void)
{
    WNDCLASS            wndclass;

    memset(&wndclass, 0x00, sizeof(WNDCLASS));

    UnregisterClass(vszAppName, vhInst);
}
#endif // }


#if FEATURE_DEAD // {
/* A S S E R T  L S Z  P R O C */
/*----------------------------------------------------------------------------
    %%Function: AssertLszProc
    %%Contact: daleg

    Print assertion message, and prompt for whether to (f)ail, or (i)gnore.
----------------------------------------------------------------------------*/

int AssertLszProc(
    const char         *szExtra,
    const char         *szFile,
    int                 line
    )
{
    Fail("ASSERTION FAILED: %s IN %s line %d\n", szExtra, szFile, line);
    return 1;
}


/* F A I L */
/*----------------------------------------------------------------------------
    %%Function: Fail
    %%Contact: daleg

    Emit a failure message and exit.
----------------------------------------------------------------------------*/

void __cdecl Fail(const char *sz, ...)
{
    va_list             ap;
    char                szBuf[256];

    /* Start variable arglist */
    va_start(ap, sz);

    wvsprintf(szBuf, sz, ap);
    OutputDebugStringA("FATAL ERROR: ");
    OutputDebugStringA(szBuf);
    OutputDebugStringA("\n");

    /* End variable arglist */
    va_end(ap);

//  _asm int 3;
    exit(1);
}
#endif // }


#if FEATURE_DEAD // {
/* F  N E  N C  L P C H */
/*----------------------------------------------------------------------------
    %%Function: FNeNcLpch
    %%Contact: daleg

    Compare two strings, case insensitive.
----------------------------------------------------------------------------*/

BOOL FNeNcLpch(
    register const uchar       *pch1,
    register const uchar       *pch2,
    register int                cch
    )
{
    while (cch-- > 0)
        {
        if (ChUpper(*pch1++) != ChUpper(*pch2++))
            return fTrue;
        }
    return fFalse;
}
#endif // }