C PROGRAMMING

Quincy's Debugger

Al Stevens

It's my anniversary again, and the annual C issue. Six years ago this month, I wrote my first "C Programming" column. It announced my intentions to use the column as a forum for C code that readers could use and study. Having at the time about three months worth of code in reserve, I wondered how soon I would exhaust my store of ideas. Not soon, I hope.

This month continues the Quincy project by describing the debugger. Quincy is a C interpreter with a D-Flat front end and an integrated C-language tutorial. It's the teaching environment for a book I'm writing and an ongoing project for this column. The program has undergone many changes since I started writing the book. Quincy has to properly interpret the book's exercises, which demonstrate most of Standard C. There were times when I was tempted to change an exercise to avoid a bug in Quincy, but I tried not to yield to those temptations. The result is a better interpreter. There are still bugs--a couple that I know about and some, I'm sure, that I haven't seen yet. I'll hear about them.

The Quincy Debugger

Quincy operates from within an integrated development environment (IDE) which integrates an editor, a debugger, and a translator. The editor is implemented as the D-Flat Editor class, which I built specifically for Quincy but made a part of D-Flat, since it is a general-purpose text-editor class. The translator is an interpreter that stands alone because I might want to use it by itself outside of the IDE or integrate it with a different user interface. The debugger is part of Quincy's application, driven by commands from the D-Flat CUA interface, and is mostly independent of the interpreter or the fact that it is debugging C-language source code. It has some D-Flat specific code, but if I ever port Quincy to another platform, most of the debugger code will go along. If I ever use the IDE for a different language, the debugger will port easily enough, too.

A debugger needs cooperation from the executing program, which, in this case, is the interpreted C program being executed by Quincy's interpreter. When you run the program outside of the debugger, the debugger must intrude as little as possible to minimize its overhead. When you are stepping through the program, the debugger must take control after the execution of every statement in the program to display the source-code line and let the programmer view the source code and watch variables. When breakpoints are set, the debugger observes every line of code as it is executed to see if it is in the breakpoint list. This means that the executing program reports the execution of each statement to the debugger, and the debugger associates statements in the executing program with lines of source code. All of which means that executable code needs line-number information in order to be debugged.

Quincy's preprocessor strips all of the excess white space from the source code. But it preserves the program's line-number information by inserting a line-number comment for each line that has code on it. Quincy's lexical scanner translates source code into interpretable tokens, and the line-number comments become line-number tokens. Quincy's interpreter calls a function to retrieve the next token in the stream. That function bypasses line-number tokens after storing the current line number globally. That way, the interpreter and debugger always know what the current line number is. The debugger needs it to step through the code and sense breakpoints. The interpreter uses it to report run-time errors.

The debugger consists of three logical parts: executing the program; managing breakpoints; and viewing, watching, and modifying variables. Execution is the focus this month. Listing One is debugger.h and Listing Two debugger.c. Debugger.h declares external and global functions and variables for all three parts of the debugger, and debugger.c contains the code that operates the interpreted program from the IDE.

When the user chooses the Run command from the IDE, the RunProgram function is called. This runs the program without stepping. You can use it to start the program after some Step commands, after a breakpoint, or after interrupting a running program with Ctrl+Break. There is a complicated relationship between these different contexts. If this function finds the Stepping flag set to be true, the program has already been running and has been interrupted by a step, breakpoint, or Ctrl+Break. The user has decided to Run uninterrupted from the current position in the program. The function turns off the Stepping flag and returns. This sequence finds its way back to the interpreter at the interrupted position.

If the interpreted program is not stepping, the RunProgram function sets some flags, hides the IDE so the program can use the screen, and calls StartProgram. When that function returns, the program has returned from main. If the Stepping variable is true, the user has chosen Run, the program has hit a breakpoint, or the user pressed Ctrl+Break and has stepped to the end of the program. If Stepping is not true, the program was run and terminated without interruption. In any case, the function displays the IDE.

The StepProgram function is called when the user chooses Step Over or Step From the IDE. If Stepping is not true, this is the first step into the program. It sets up the execution and calls StartProgram. If Stepping is true, this is a subsequent step. The function sets inIDE to false and returns, which finds its way back to the place in the debugger function where the current statement was interrupted to wait for the next step.

The StopProgram function is called when the user chooses the Stop command to stop a program that has not terminated normally. It clears some flags that manage the running context of the program and sets inIDE to false.

The StartProgram function starts a program running from the Run and Step commands when the program is not yet running. The function sets up command-line input/output redirection, builds the command-line parameters from what the user sets up, and calls qinterpmain to scan, translate, and interpret the source-code program. When that function returns, the interpreted program has terminated, either by returning from main, calling exit or abort, or as the result of the user choosing the Stop command from the IDE.

The interpreter calls the debugger function once for each statement in the interpreted program. The debugger function returns true to tell the interpreter to terminate the program or false to continue interpreting. If the user is not Stepping, no breakpoints are programmed, and the user has not pressed Ctrl+Break, then the debugger function gives control back to the interpreter. If the user is stepping, debugger looks at the current line number to see if it is the same as the last time the interpreter called in. Several C statements can be on the same line; the interpreter calls in for each statement. The debugger function, being interested in source-code lines rather than statements, returns if the statement has already been processed. Otherwise, it calls DisplaySourceCodeLine twice: first to turn off the step highlight cursor for the previous line, and then to turn it on for the current line. Then the debugger calls the RunIDE function. When the RunIDE function returns, the debugger function returns to the interpreter.

The RunIDE function calls UpdateWatches (to be discussed next month) to query watched variables and display their values. Then it goes into a D-Flat message-dispatching loop. This action runs the IDE until the user does something that clears the inIDE flag, at which time RunIDE returns.

There are several more small functions in debugger.c. Their comments document their purposes. At the bottom of the listing are functions to support the Function List and Function History commands. The user can view a list of the functions in the program and view the stack of functions as they have been called from main to the current running function in the interpreted program. The user can choose an entry from the list, and the IDE will page to the chosen function and display it in the editor window. Function List and Function History are implemented by a single dialog box. The difference between the two is that the function history comes from the run-time table of functions running, while the function list comes from the table of callable functions. Neither command is active when the program is not running. This is because the tables that build them are themselves built by the interpreter, and they are not available unless the source code has been scanned and translated.

Next month I'll explain how the debugger manages breakpoints and the Watch Window.

Cpp: Summation for the Defense

In the "Programmer's Bookshelf" column in this issue , I review Bjarne Stroustrup's new book, The Design and Evolution of C++. I enjoyed the book and recommend it to every C++ programmer, but I had a problem with Chapter 18, Bjarne's brief diatribe on the preprocessor. In the last four pages of the book, he becomes a bit of a language lawyer, something he usually avoids. His dislike for the preprocessor is clear. "I didn't like Cpp at all and I still don't like it._ I'd like to see Cpp abolished." No beating about the bush there. He is not alone. Many C++ programmers express the same sentiment. I understand it, but having recently written Quincy's preprocessor and having used the preprocessor effectively in other projects, I cannot agree to the extent that I might have otherwise. Understand that these are matters of opinion, and Bjarne makes it clear that Chapter 18 reflects his own. His opinions merit attention and carry weight because of the respect that his achievements have earned him. This response is my view of the same subject.

C++ has language features that obviate some of Cpp's preprocessing directives, but not all of them. Bjarne stated his intention to provide language alternatives for as much of Cpp as possible. In most cases he succeeded. However, there is no replacement for #if, #ifdef, and #ifndef because he hasn't come up with anything suitable. He has never seen a #pragma that he liked. I agree with that. The only #pragma that I ever used suppresses annoying C warning messages and is unnecessary because C++ permits unnamed parameters in a function header.

Bjarne proposes a C++ include directive that is more restrictive than #include. It would ignore multiple inclusions of the same header file and would restrict the effectiveness of macros to the file in which they are defined. That part of the proposal, which implicates macros, is inconsistent with the position that macros are replaced by inline functions. If they are unnecessary, why deal with their scope?

Bjarne's proposed include operator would disable a useful programming idiom if it ever replaced #include. Despite his good intentions, applications for #define exist that inline C++ functions do not support and that the proposed include operator would not support. Consider how D-Flat redefines the ClassDef macro to use the same initializer list for different purposes. Take a look at D-Flat's dialbox.h and dialogs.c. The preprocessor is the resource compiler, and a good one at that. These techniques apply macro definitions that transcend source files and include header files more than once in different compile-time conditional contexts. Without #include, these techniques would not work.

Bjarne prefers that you not have to use Cpp. In his opinion, Cpp does its job "pretty badly" although "adequately." Furthermore, he dislikes the potential for programmer abuse. He gives an extreme (and funny) example of a macro coded on the command line that converts sqrt to rand and return to abort. This is an effective, albeit unrealistic, point but it is also an inconsistent position. Bjarne dismisses other potential abuses of the language itself as being unimportant, saying that a good programming language cannot defend against intentional malice, and that in the spirit of C++, programmers are trusted to know what they are doing. Yet, Cpp is unworthy of the same tolerance, and you cannot be trusted to use it.

That's where I disagree. The preprocessor is only a tool, and, as with unions, casts, unbounded arrays, unchecked pointers, and every way that a programmer can subvert the protection mechanisms of the language, good programmers know better than to abuse it. Careless and irresponsible programmers can use #define to override the type system if they want to. They can burn down the data center, too.

Bjarne made his point about Cpp in certain terms, but you don't have to agree with his opinion. It is a moot point, in any event. Cpp is a source-code preprocessing program. It reads and emits text, reacting to preprocessing directives and making macro substitutions. It's output is compilable source code. It is just a program. They can't make it go away by committee decree. Even if they remove it from the C and C++ language definitions (which, I opine, will not happen soon), it remains widely available, and programmers can and will use it.

"C Programming" Column Source Code

Quincy, D-Flat, and D-Flat++ are available to download from the DDJ Forum on CompuServe and on the Internet by anonymous ftp. See page 3 for details. If you cannot get to one of the online sources, send a diskette and a stamped, addressed mailer to me at Dr. Dobb's Journal, 411 Borel, San Mateo, CA 94402. I'll send you a copy of the source code. It's free, but if you want to support my Careware charity, include a dollar for the Brevard County Food Bank.

Listing One


/* -------- debugger.h ------- */

#ifndef DEBUGGER_H
#define DEBUGGER_H

#define MAXBREAKS 25
#define SIZEWATCH 110
#define MAXARGS 20
#define WATCHHT 2
#define MAXWATCHHT 5
#define MAXWATCHES 25

extern BOOL Running;
extern BOOL Stepping;
extern BOOL CtrlBreaking;
extern BOOL Exiting;
extern WINDOW watchWnd;
extern DBOX ExamineDB;
extern DBOX FunctionStackDB;
extern char *errs[];
extern int LastStep;
extern int BreakpointCount;
extern int DupStdin;
extern int DupStdout;
extern int oldstdin;
extern int oldstdout;
extern char DupFileIn[65];
extern char DupFileOut[65];
extern char ErrorMsg[160];

int isBreakpoint(int);
void AdjustBreakpointsInserting(int, int);
void AdjustBreakpointsDeleting(int, int);
void SetBreakpointColor(WINDOW);
void SetBreakpointCursorColor(WINDOW);
void DisplaySourceLine(int);
void DisplayError(void);
void RunProgram(void);
void StepProgram(void);
void StepOverFunction(void);
void StopProgram(void);
void CommandLine(void);
void ExamineVariable(void);
void ToggleWatch(void);
void AddWatch(char *);
void FunctionStack(void);
void FunctionList(void);
void GetWatch(void);
void ShowWatches(void);
void DelWatch(void);
void DeleteAllWatches(void);
void UpdateWatches(void);
void unBreak(void);
void reBreak(void);

#endif


Listing Two


/* ---------- debugger.c ---------- */

#include "dflat.h"
#include "qnc.h"
#include "debugger.h"
#include "interp.h"

static BOOL StdoutOpened;
static BOOL inProgram;
static BOOL inIDE;
static BOOL Changed;
static BOOL Stopping;
static BOOL FuncStack;
static char CmdLine[61];
static char Commands[61];
static int argc;
static char *args[MAXARGS+1];
static int StuffKey;

BOOL Running;
BOOL Stepping;
int LastStep;
int SteppingOver;
int ErrorCode;
int currx, curry;
char ErrorMsg[160];

static char line[135];

static void CopyRedirectedFilename(char *, char **);
static int StartProgram(void);
static BOOL LineNoOnCurrentPage(int);
static void TurnPageToLineNo(int);
static void TestFocus(void);
static void BuildDisplayLine(int);
static BOOL SourceChanged(int);
static BOOL TestRestart(void);
static void TerminateMessage(int);
static void RunIDE(void);

int DupStdin = -1;
int DupStdout = -1;
int oldstdin;
int oldstdout;
char DupFileIn[65];
char DupFileOut[65];

/* --- run the program from Run command --- */
void RunProgram(void)
{
    int rtn = -1;
    TestFocus();
    if (SourceChanged(F9))
        return;
    Stopping = FALSE;
    if (Stepping)    {
        /* --- executed Run after Step or Breakpoint --- */
        OpenStdout();
        inIDE = FALSE;
        Stepping = FALSE;
        return;
    }
    /* --- Running program from start --- */
    StdoutOpened = FALSE;
    HideIDE();
    Changed = editWnd->TextChanged;
    editWnd->TextChanged = FALSE;
    inProgram = FALSE;

    rtn = StartProgram();    /* run the program */

    /* --- returned from running the program --- */
    editWnd->TextChanged |= Changed;

    if (Stepping)    {
        /* Stepping would be Run/Bkpt/Step, returning here on program exit */
        Stepping = FALSE;
        SteppingOver = FALSE;
        Stopping = FALSE;
        LastStep = 0;
        SendMessage(editWnd, PAINT, 0, 0); /* clear IP cursor */
    }
    else    {
        if (ErrorCode==0 && !Exiting && !Stopping && !StuffKey)
            PromptIDE();
        UnHideIDE();
    }
    if (ErrorCode)
        DisplayError();
    else if (!TestRestart())
        TerminateMessage(rtn);
}
/* --- step over the current statement --- */
void StepOverFunction(void)
{
    SteppingOver = 1;
    StepProgram();
}
/* --- execute one statement (Step) --- */
void StepProgram(void)
{
    TestFocus();
    if (SourceChanged(F7))
        return;
    if (!Stepping)    {
        /* ---- the first step ---- */
        int rtn = -1;
        Stepping = TRUE;
        Stopping = FALSE;
        StdoutOpened = FALSE;
        LastStep = 0;
        inProgram = FALSE;
        Changed = editWnd->TextChanged;
        editWnd->TextChanged = FALSE;
        rtn = StartProgram();     /* take the step */
        editWnd->TextChanged |= Changed;
        Stepping = FALSE;
        LastStep = 0;
        if (!isVisible(applWnd))    {
            UnHideIDE();        /* display the IDE */
            SteppingOver = FALSE;
        }
        if (ErrorCode)
            DisplayError();
        SendMessage(editWnd, PAINT, 0, 0);
        Stopping = FALSE;
        if (!TestRestart())
            TerminateMessage(rtn);
    }
    else
        inIDE = FALSE;
}
/* --- process Stop command --- */
void StopProgram(void)
{
    Stopping = TRUE;
    Stepping = FALSE;
    inProgram = FALSE;
    inIDE = FALSE;
    if (LastStep && LineNoOnCurrentPage(LastStep))    {
        WriteTextLine(editWnd, NULL, LastStep-1, FALSE);
        LastStep = 0;
    }
    TestFocus();
}
/* -- start running the program from Run or Step command -- */
static int StartProgram(void)
{
    int rtn = -1;
    char *cp = CmdLine;
    char *cm = Commands;
    argc = 0;
    args[argc++] = editWnd->extension;
    while (*cp && argc < MAXARGS)    {
        while (isspace(*cp))
            cp++;
        if (*cp == '>')    {
            CopyRedirectedFilename(DupFileOut, &cp);
            continue;
        }
        else if (*cp == '<')    {
            CopyRedirectedFilename(DupFileIn, &cp);
            continue;
        }
        args[argc++] = cm;
        while (*cp && !isspace(*cp))
            *cm++ = *cp++;
        *cm++ = '\0';
    }
    CtrlBreaking = FALSE;
    unBreak();
    if (ErrorCode == 0)    {
        Running = TRUE;
        rtn = qinterpmain(editWnd->text, argc, args);
        Running = FALSE;
    }
    reBreak();
    *DupFileIn = *DupFileOut = '\0';
    return rtn;
}
/* --- entry to debugger from interpreter --- */
BOOL debugger(void)
{
    if (SteppingOver)    {
        SteppingOver = 0;
        Stepping = TRUE;
        curr_cursor(&currx, &curry);
        UnHideIDE();
    }
    if (inProgram == FALSE)    {
        /* -- don't debug the first statement (main call) -- */
        inProgram = TRUE;
        return FALSE;
    }
    if ((CtrlBreaking || Stepping || BreakpointCount) && Ctx.CurrFileno == 0) {
        int lno = Ctx.CurrLineno;
        if (Stepping)    {
            if (lno != LastStep)    {
                if (LastStep)    {
                    int ln = LastStep;
                    LastStep = 0;
                    DisplaySourceLine(ln);
                }
                LastStep = lno;
                DisplaySourceLine(lno);
                SendMessage(editWnd, KEYBOARD_CURSOR, 0, lno-editWnd->wtop-1);
                /* --- let D-Flat run the IDE --- */
                reBreak();
                RunIDE();
                unBreak();
            }
        }
        else if (CtrlBreaking || isBreakpoint(lno))    {
            LastStep = lno;
            Stepping = TRUE;
            UnHideIDE();
            TurnPageToLineNo(lno);
            SendMessage(editWnd, KEYBOARD_CURSOR, 0, lno-1-editWnd->wtop);
            MessageBox(DFlatApplication,
              CtrlBreaking ? errs[CTRLBREAK-1] : "Breakpoint");
            CtrlBreaking = FALSE;
            /* --- let D-Flat run the IDE --- */
            reBreak();
            RunIDE();
            unBreak();
        }
    }
    return Stopping;
}
/* --- copy a redirected filename from command line --- */
static void CopyRedirectedFilename(char *fname, char **cp)
{
    (*cp)++;
    while (isspace(**cp))
        (*cp)++;
    while (**cp && !isspace(**cp))
        *fname++ = *((*cp)++);
    *fname = '\0';
}
/* -- display source code line with bkpt/cursor highlights - */
void DisplaySourceLine(int lno)
{
    TurnPageToLineNo(lno);
    BuildDisplayLine(lno);
    if (isBreakpoint(lno))    {
        if (Stepping && lno == LastStep)
            SetBreakpointCursorColor(editWnd);
        else
            SetBreakpointColor(editWnd);
    }
    else if (Stepping && lno == LastStep)
        SetReverseColor(editWnd);
    else
        SetStandardColor(editWnd);
    PutWindowLine(editWnd, line, 0, lno-1-editWnd->wtop);
}
/* --- build a display line of code ---- */
static void BuildDisplayLine(int lno)
{
    char *tx = TextLine(editWnd, lno-1);
    int wd = min(134, ClientWidth(editWnd));
    char *ln = line;
    char *cp1 = ln;
    char *cp2 = tx+editWnd->wleft;
    while (*cp2 != '\n' && cp1 < ln+wd)
        *cp1++ = *cp2++;
    while (cp1 < ln+wd)
        *cp1++ = ' ';
    *cp1 = '\0';
}
/* ---- prompt user on output screen ---- */
void PromptIDE(void)
{
    printf("\nPress any key to return to Quincy...");
    getkey();
    printf("\r                                    ");
}
/* --- test Run/Step command for source changed --- */
static BOOL SourceChanged(int key)
{
    if (Stepping && editWnd->TextChanged)    {
        MessageBox(DFlatApplication, "Source changed, must restart");
        StopProgram();
        StuffKey = key;
        return TRUE;
    }
    return FALSE;
}
/* --- test for restarting with Run or Step command --- */
static BOOL TestRestart(void)
{
    if (StuffKey)    {
        SendMessage(editWnd, PAINT, 0, 0);
        PostMessage(editWnd, KEYBOARD, StuffKey, 0);
        StuffKey = 0;
        return TRUE;
    }
    return FALSE;
}
/* --- run the IDE --- */
static void RunIDE(void)
{
    inIDE = TRUE;
    UpdateWatches();
    while (inIDE && dispatch_message())
        ;
}
/* ------------- set command line parameters ------------- */
void CommandLine(void)
{
    InputBox(applWnd, DFlatApplication, "Command line:", CmdLine, 128, 25);
}
/* ---- display program terminated message ---- */
static void TerminateMessage(int rtn)
{
    char msg[50];
    sprintf(msg, "Program terminated: code %d", rtn);
    MessageBox(DFlatApplication, msg);
}
/* --- test if source line is in view ---- */
static BOOL LineNoOnCurrentPage(int lno)
{
    return (lno-1 >= editWnd->wtop &&
            lno-1 < editWnd->wtop+ClientHeight(editWnd));
}
/* ---- display page with specified source code line --- */
static void TurnPageToLineNo(int lno)
{
    if (!LineNoOnCurrentPage(lno))    {
        if ((editWnd->wtop = lno-ClientHeight(editWnd)/2) < 0)
            editWnd->wtop = 0;
        SendMessage(editWnd, PAINT, 0, 0);
    }
}
/* --- make sure that edit window has the focus --- */
static void TestFocus(void)
{
    if (editWnd != inFocus)
        SendMessage(editWnd, SETFOCUS, TRUE, 0);
}
/* --- display an error message when program terminates --- */
void DisplayError(void)
{
    if (!Watching)    {
        if (Ctx.CurrFileno == 0)    {
            TestFocus();
            TurnPageToLineNo(Ctx.CurrLineno);
            SendMessage(editWnd, KEYBOARD_CURSOR, 
                                         0, Ctx.CurrLineno-1-editWnd->wtop);
        }
    }
    strcat(ErrorMsg, errs[ErrorCode-1]);
    beep();
    ErrorMessage(ErrorMsg);
    ErrorCode = 0;
}
/* --- hide IDE so program can use output screen --- */
void HideIDE(void)
{
    if (isVisible(applWnd))    {
        SendMessage(applWnd, HIDE_WINDOW, 0, 0);
        SendMessage(NULL, HIDE_MOUSE, 0, 0);
        cursor(currx, curry);
    }
}
/* -- display IDE when program is done with output screen -- */
void UnHideIDE(void)
{
    if (!isVisible(applWnd))    {
        curr_cursor(&currx, &curry);
        SendMessage(applWnd, SHOW_WINDOW, 0, 0);
        SendMessage(NULL, SHOW_MOUSE, 0, 0);
    }
}
/* --- interpreter needs output screen for stdout --- */
void OpenStdout(void)
{
    if (Stepping)
        HideIDE();
    if (!StdoutOpened)    {
        StdoutOpened = TRUE;
        putchar('\n');
    }
}
/* --- interpreter is done with output screen for stdout --- */
void CloseStdout(void)
{
    if (Stepping)
        if (!SteppingOver)
            UnHideIDE();
}
/* ---- window proc module for Function Stack DB ------- */
static int FunctionStackProc(WINDOW wnd, MESSAGE msg, PARAM p1, PARAM p2)
{
    FUNCRUNNING *fr = Ctx.Curfunc;
    FUNCTION *fun = FunctionMemory;
    int sel;
    CTLWINDOW *ct =
        FindCommand(wnd->extension,ID_FUNCSTACK,LISTBOX);
    WINDOW lwnd = ct ? ct->wnd : NULL;

    switch (msg)    {
        case INITIATE_DIALOG:
            Assert(lwnd != NULL);
            SendMessage(lwnd, CLEARTEXT, 0, 0);
            if (FuncStack)    {
                while (fr != NULL)    {
                    char *fn=FindSymbolName(fr->fvar->symbol);
                    if (fn)
                        SendMessage(lwnd,ADDTEXT,(PARAM)fn,0);
                    fr = fr->fprev;
                }
            }
            else    {
                while (fun < NextFunction)    {
                    if (fun->libcode == 0)    {
                        char *fn = FindSymbolName(fun->symbol);
                        if (fn)
                          SendMessage(lwnd,ADDTEXT,(PARAM)fn,0);
                    }
                    fun++;
                }
            }
            SendMessage(lwnd, PAINT, 0, 0);
            break;
        case COMMAND:
            Assert(lwnd != NULL);
            switch ((int) p1)    {
                case ID_FUNCSTACK:
                    if ((int) p2 == LB_CHOOSE)
                        SendMessage(wnd, COMMAND, ID_OK, 0);
                    break;
                case ID_OK:
                    if (p2)
                        break;
                    sel = SendMessage(lwnd,
                            LB_CURRENTSELECTION, 0, 0);
                    if (FuncStack)    {
                        while (fr != NULL && sel--)
                            fr = fr->fprev;
                        fun = fr->fvar;
                    }
                    else
                        while (fun < NextFunction)    {
                            if (fun->libcode == 0)
                                if (sel-- == 0)
                                    break;
                            fun++;
                        }
                    if (fun->fileno == 0)    {
                        TurnPageToLineNo(fun->lineno);
                        SendMessage(editWnd, KEYBOARD_CURSOR,0,
                            fun->lineno-1-editWnd->wtop);
                    }
                    break;
                default:
                    break;
            }
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}
/* ---- display the function stack ------- */
void FunctionStack(void)
{
    FuncStack = TRUE;
    FunctionStackDB.dwnd.title = "Function History";
    DialogBox(applWnd, &FunctionStackDB, TRUE, FunctionStackProc);
}
/* ---- display list of functions ------- */
void FunctionList(void)
{
    FuncStack = FALSE;
    FunctionStackDB.dwnd.title = "Function List";
    DialogBox(applWnd, &FunctionStackDB, TRUE, FunctionStackProc);
}

Copyright © 1994, Dr. Dobb's Journal