Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

C Programming


MAY94: C PROGRAMMING

Quincy: A C Interpreter Project

This month launches a new "C Programming" column project, a C-language interpreter with a D-Flat integrated development environment (IDE). The program is named "Quincy," and its origins go back to the mid-eighties. It was then that I downloaded a shareware program called the "Small C Interpreter" (SCI) from a BBS. You can still find SCI among C-language shareware tools. SCI is a K&R subset interpreter with a command-line user interface that resembles early CP/M and MS-DOS Basic interpreters. SCI is the brainchild of Bob Brodt, the author of BAWK and other widely used utility programs. I called Bob, and we agreed to collaborate on a commercial upgrade to SCI. He would add some features to the language interpreter, and I would rewrite the user interface with a full-screen editor, debugger, help screens, and such. We named it "QC," booked some ads in DDJ and Computer Language, and sat back to wait for the revenues to come pouring in.

The first sale, surprisingly, was to Microsoft. Their purchase order included a letter saying that they like to keep abreast of new language products, particularly in the C arena. I guess they wanted to see what a product named QC looked like. We didn't know it when we named the product, but Microsoft was about to introduce QuickC, which was bound to be nicknamed QC. Maybe they were worried about the name conflict. They stopped worrying when they saw the package. It was small potatoes, not all that impressive. (QuickC 1.0 wasn't much better, though.)

Another call came from someone who was upset about the QC moniker and wanted it changed. I forget his name, but he owned a C compiler also named QC and he had a sabre to rattle. He was familiar with name problems. His company had originally advertised as the CodeWorks, and another company, The Austin Code Works, rattled some sabres of their own and made them change the name. Now The Austin Code Works was marketing the CodeWorks QC compiler, and he didn't want our product out there confusing the marketplace. He was right, of course, and I learned a lesson. Do plenty of product-name research before you spend any money that commits to a particular choice.

Not wanting a trademark fight, I was fretting about how to change the name of the program without losing whatever product identification our advertising efforts had yielded. Just then my daughter Wendy's white cat, Quincy (named after Jack Klugman's TV character), strolled by. Problem solved, the program was rechristened Quincy, close enough to QC to preserve the recognition and far enough away to hold the lawyers at bay.

We had sold a few, a very few, copies of Quincy when I tired of the mail-order software business. I was making more money playing the piano in a saloon. Remembering what the QC guy told me, I called The Austin Code Works, and Scott Guthery, who was to become a famous curmudgeon in these pages, agreed to handle Quincy if we would allow him to include source code in the package. He still carries the product and even sells one every now and then. Its proceeds go to the Brevard County Food Bank.

While integrating Bob's interpreter code into the IDE, I had seen similarities between it and the Tiny-C interpreter published in a book. Bob told me that he used the Tiny-C implementation to learn about C interpreters. Scott Guthery wrote the Tiny-C book. The cycle was complete.

Where is all this leading? Recently, I undertook a book project for an introduction to programming in C. Not quite C for Dodos, but along those lines. I wanted to include a compiler or interpreter on the companion diskette after the fashion of other language tutorials. Contemporary C-compiler products, as wonderful as they are, are disk-hungry behemoths with a heavy emphasis on C++. Some vendors will supply limited-feature compilers to go along with books about their languages and products, but they usually expect royalties. Not wanting to give up royalties, I resurrected Quincy and looked it over. Although it had promise, there were two problems. First, the language implementation was a K&R subset with none of the ANSI improvements. Second, the IDE did not comply with CUA, the user interface that everyone expects to see in new applications. I set aside some time and completely rewrote the program, using D-Flat for the IDE and putting as much of ANSI C as I could into the interpreter. Not much of the original Quincy code survived, but I kept the name in memory of that little white cat.

Quincy will be the tutorial environment in my new C book. Eventually, it might serve as the basis for a book about interpreter design. And starting this month, this column will run a series of articles about Quincy itself. As with all of my projects, you can download or send for the source code.

Quincy Design Goals

Quincy (the interpreter) is meant to be used to learn C. It will include a script-driven tutorial that uses D-Flat help windows to walk a student through the exercises and explain the lesson being demonstrated. Quincy is not a wall-to-wall implementation of ANSI C, although it is a healthy subset. The language implementation lacks some features. Quincy does not support typedef, multiple-dimension arrays, variable argument lists, goto, or concatenated string literals. If needed, I'll add those features, but in its current condition, the interpreter suffices to help teach C to a novice.

Quincy will not link multiple source-code files. It is an interpreter that loads and executes a source-code file using Run and Debug commands. Just as the original Small C Interpreter resembled MBasic in the user interface, Quincy resembles QBasic in this respect. You can use the #include preprocessor directive to load multiple source-code files into one program. The preprocessor does not implement all of the ANSI C additions, such as "stringizing."

I broke Quincy down into several logical parts. The IDE, which is implemented in D-Flat, is one. The preprocessor is another. The debugger and its interface to the interpreter are yet another. The C-language interpreter is the last part. The purpose is to isolate the code that would change if I decide to install Quincy into a different user interface, such as Windows, or use the Quincy environment with an altogether different language interpreter.

The first version of Quincy is 4.0, reflecting its ancestry. There were three versions of the K&R edition.

Quincy is relevant to this column not because DDJ readers need a C tutorial, but because the program is a study in interpreter design and implementation. It is also a study in the use of D-Flat to develop an application. You have to build your own configuration-file definition, command codes, menus, and dialog boxes in addition to or in place of the D-Flat defaults.

Quincy is also useful for developing small C programs that you can compile later. It fits on and will run from a 360K diskette, so you can take it with you no matter what the size of your road machine. (In 1990, I used Quincy Version 2 to develop the Data Encryption Standard programs that I published in this column. I was on the road, and my laptop was a T1000 4.77-MHz 8088 with one 720K diskette drive and no hard disk. Now that you feel sorry for me and my Spartan roots, let's return to the present.)

Quincy's IDE

Listing One, page 144, is interp.h, the header file that defines the Quincy menu and command codes, some prototypes, and some external variables. The command codes are in addition to the ones that the D-Flat Application window class uses. There are other header files for the debugger and interpreter. I'll discuss them in later columns.

Listing Two, page 144, is qnc.c, the source file that implements Quincy's IDE. It is a direct knock-off from D-Flat's example Memopad program. Quincy is not a multiple-document-interface application like the Memopad, though.

The main function initializes D-Flat, loads the configuration and help files, creates the application window and its child editor window, and loads a source-code file if one is named on the command line. The main function also calls reBreak and unBreak. These functions manage DOS's contrary Ctrl+Break and Ctrl+C operations. While the IDE is running, the interrupts for these keys are directed to an empty interrupt-service routine to prevent the user from exiting the program by pressing one of the keys. Such exits are inhospitable, at least. They are untimely, too, because the D-Flat environment has other interrupts hooked. An uncontrolled exit leaves DOS in an unstable state with interrupt vectors pointing into the vapor. While the interpreter is running a program, the break keys are set up to let the user interrupt the process to get out of a dead loop or terminate an errant program.

The QuincyProc function is the window-processing module for Quincy's application window. It intercepts D-Flat messages and processes them in cases where they need other than the routine application procedures. The CREATE_

WINDOW message turns on the scroll-bar option if a mouse is installed. The SIZE message adjusts the sizes of the application's child windows, which consist of an editor control and a list-box control to display programmed watch variables. The SHOW_WINDOW message deletes and adds scroll bars when the user has made that selection from a menu. It also observes when the screen size and the configured window height disagree and sends a SIZE message to compensate. The application window intercepts attempts to give it the focus and passes the focus on to the editor window. Each of the COMMAND messages calls an associated command function. Some of them, such as the ones to run and step through the program, are in the debugger. Others, such as the commands to load and save source code files, are processed here in qnc.c.

The QncEditorProc module is the window-processing module for the Editor window class. It intercepts its SCROLL and PAINT messages to highlight source-code lines that have breakpoints set and the one that represents the current program step position. If the user adds or deletes source-code lines with the keyboard or by cutting and pasting to and from the clipboard, the program adjusts the breakpoint table accordingly. The functions that make those adjustments are in the debugger.

qnc.c includes the command function to display the output screen while the user is viewing the IDE. Another command function displays a dialog box with some user-configurable memory parameters.

Quincy uses D-Flat Version 18 or later. That version adds an Editor class to deal with the problems of source code, such as expanding and collapsing tabs. It also replaces the File Open and Save As dialog boxes with improved versions and fixes a few small bugs. I built a special version of the D-Flat object library, too, to include only those D-Flat features that Quincy needs. All the MDI stuff is out, for example, which significantly reduces the size of the executable.

"C Programming" Column Source Code

Quincy, D-Flat, and D-Flat++ can be downloaded from the DDJ Forum on CompuServe or 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 Ave., San Mateo, CA 94402. I'll send you a copy of the source code. It's free, but if you want to support the Careware charity, include a dollar for the Brevard County Food Bank. They help hungry and homeless citizens.

Template Functions: A Reader Responds

In the February column, I groused about how Borland C++'s template implementation made building min/max template functions difficult. In March, I pointed out that the Watcom C++ compiler did not exhibit the same behavior and worked exactly the way I wanted it to. A reader, who asked to remain anonymous, responded. He uses Borland compilers and follows the activities of the ANSI C++ committee. I edited his communications to protect his identity and to cast our e-mail exchanges into a conversation.

Reader: Regarding your comments about your min/max template functions, please consider the following, taken from the ANSI C++ WP:

3 A template function may be overloaded either by (other) functions of its name or by (other) template functions of that same name. Overloading resolution for template functions and other functions of the same name is done in three steps:

[1] Look for an exact match (f13.2) on functions; if found, call it.

[2] Look for a function template from which a function that can be called with an exact match can be generated; if found, call it.

[3] Try ordinary overloading resolution (f13.2) for the functions; if a function is found, call it.

If no match is found the call is an error. In each case, if there is more than one alternative in the first step that finds a match, the call is ambiguous and is an error.

4 A match on a template (step [2]) implies that a specific template function with arguments that exactly matches the types of the arguments will be generated (f14.5). Not even trivial conversions (f13.2) will be applied in this case.

Please note the last sentence of the paragraph; I think it is pretty explicit about not allowing const int -> int or int -> const int. I have asked a C++ authority about your example, and he has confirmed that Borland C++ and Symantec C++ are correct in rejecting it, since it is not currently legal.

Having said that, though, I have to point out that the next ANSI meeting is to consider relaxing the matching rules, such that your example would become legal, and it is very likely that this proposed change will be accepted.

Stevens: According to a literal translation of the WP, the template in Example 1(a), which I copied from the Borland C++ 4.0 stdlib.h, would be crippled. This template would work only with const types.

Reader: This is absolutely true, and unfortunately, that happens to be precisely what the language definition implies, as of today.

I am not saying that the rules should stay this way; I am merely stating what they are. It's always a difficult process to decide how faithfully to follow the (evolving) standard, where it just plain doesn't make sense.

Stevens: The Borland compiler does not really work the way a literal interpretation would indicate, however. Example 1(b) works fine.

The Watcom compiler works the other way. The arguments to a template function can be any mix of const and non-const. I really don't see any overwhelming problem with that implementation, and it solves the problems that I pointed out in the column.

Reader: The above program is correctly flagged by Borland C++ 4.0 as an error, but only in strict ANSI mode, so I still have to argue that Borland C++ implements these rules pretty consistently. Borland requires the --A switch for complete compliance, and I am not aware of any options on Watcom that would make the compiler correctly flag the illegal code above. Of course, chances are that with the next ANSI meeting over, they will be the ones correctly implementing this feature.

On this particular point, there is no ambiguity--the language here is basically broken (but unambiguously so). And the C++ authority that I consulted agrees that the code is definitely illegal, as far as the language stands today.

I guess Borland did anticipate ANSI a little bit here by allowing what I just described without --A, similarly to Watcom, only Borland didn't go quite as far as Watcom did. With respect to the Watcom behavior, as long as ANSI actually makes this legal, I'd agree; otherwise, I'd have to argue that by taking advantage of an extension, you'd be writing nonportable code.

The reader's points are valid. I said in my original complaint that I did not know which implementation is correct. Now that I know, I am happy to hear that they are at least considering a repair to what, in my opinion, is a flawed design.

The ANSI meeting that decides this issue has not taken place, as I write this, although it should be history by the time you read these words. I won't guess at the outcome, but remember that my original comments were not so much about the problem itself, but about C++ issues of style. To circumvent the problem I ignored the C++ homily that discourages #define and lapsed into classic C. We do what we have to do until the language makers are finished. Then we do what we have to do some more.

Example 1: Examining C++ template functions.

<b>(a)</b>
template <class T> inline const T&
   min( const T& t1, const T& t2 )
{
   return t1>t2 ? t2 : t1;
}

<b>(b)</b>
#include <stdlib.h>
main()
{
    int a = 123;
    int b = 234;
    int c;
    c = min(a,b);
}

[LISTING ONE]



/* ------- interp.h -------- */

#ifndef INTERP_H
#define INTERP_H

#include "dflat.h"

#define QUINCY "Quincy"

/* ----- Menu and dialog box command codes ------ */
enum    {
    ID_OUTPUT = 200,
    ID_SCROLLBARS,
    ID_RUN,
    ID_STEP,
    ID_STOP,
    ID_BREAKPOINT,
    ID_WATCH,
    ID_DELBREAKPOINTS,
    ID_COMMANDLINE,
    ID_EXAMINE,
    ID_CHANGE,
    ID_VALUE,
    ID_STEPOVER,
    ID_ADDWATCH,
    ID_SWITCHWINDOW,
    ID_MEMORY,
    ID_PROGSIZE,
    ID_DATASPACE,
    ID_VARIABLES,
    ID_STACK,
    ID_JUMP
};
void qinterpmain(unsigned char *source, int argc, char *argv[]);
void HideIDE(void);
void UnHideIDE(void);
void PrintSourceCode(void);
void unBreak(void);
void reBreak(void);
int PrintSetupProc(WINDOW, MESSAGE, PARAM, PARAM);

extern WINDOW editWnd;
extern WINDOW applWnd;
extern int currx, curry;
extern DBOX Display;
extern DBOX PrintSetup;
extern DBOX MemoryDB;

#endif


[LISTING TWO]



/* --------------- qnc.c ----------- */
#include <process.h>
#include <alloc.h>
#include "dflat.h"
#include "qnc.h"
#include "interp.h"
#include "debugger.h"

char DFlatApplication[] = QUINCY;
static char Untitled[] = "Untitled";
char **Argv;

WINDOW editWnd;
WINDOW applWnd;
WINDOW watchWnd;
BOOL Exiting;
BOOL CtrlBreaking;

static char *qhdr = QUINCY " " QVERSION;
extern unsigned _stklen = 16384;
static void interrupt (*oldbreak)(void);
static void interrupt (*oldctrlc)(void);
static BOOL brkct;
static int QuincyProc(WINDOW, MESSAGE, PARAM, PARAM);
static void SelectFile(void);
static void OpenSourceCode(char *);
static void LoadFile(void);
static void SaveFile(int);
void ToggleBreakpoint(void);
void DeleteBreakpoints(void);
static int QncEditorProc(WINDOW, MESSAGE, PARAM, PARAM);
static char *NameComponent(char *);
static void ChangeTabs(void);
static void FixTabMenu(void);
static void CloseSourceCode(void);
static void OutputScreen(void);
static void Memory(void);
static void ChangeQncTitle(void);

void main(int argc, char *argv[])
{
    int hasscr = 0;
    if (!init_messages())
        return;
    curr_cursor(&currx, &curry);
    setcbrk(0);
    reBreak();
    Argv = argv;
    if (!LoadConfig())
        cfg.ScreenLines = SCREENHEIGHT;
    applWnd = CreateWindow(APPLICATION, qhdr, 0, 0, -1, -1, &MainMenu,
                        NULL, QuincyProc, HASSTATUSBAR );
    ClearAttribute(applWnd, CONTROLBOX);
    LoadHelpFile();
    if (SendMessage(NULL, MOUSE_INSTALLED, 0, 0))
        hasscr = VSCROLLBAR | HSCROLLBAR;
    editWnd = CreateWindow(EDITOR, NULL, GetClientLeft(applWnd),
                GetClientTop(applWnd), ClientHeight(applWnd),
                ClientWidth(applWnd), NULL, NULL, QncEditorProc,
                hasscr | HASBORDER | MULTILINE);
    ChangeQncTitle();
    SendMessage(editWnd, SETFOCUS, TRUE, 0);
    if (argc > 1)
        OpenSourceCode(argv[1]);
    while (dispatch_message())
        ;
    unBreak();
    cursor(currx, curry);
}
/* -- interception and management of Ctrl+C and Ctrl+Break -- */
static void interrupt newbreak(void)
{
    return;
}
int CBreak(void)
{
    if (!Stepping)    {
        CtrlBreaking = TRUE;
        if (inSystem)
            longjmp(BreakJmp, 1);
    }
    return 1;
}
void unBreak(void)
{
    if (brkct)    {
        setvect(0x1b, oldbreak);
        setvect(0x23, oldctrlc);
        ctrlbrk(CBreak);
        brkct = FALSE;
    }
}
void reBreak(void)
{
    if (!brkct)    {
        oldctrlc = getvect(0x23);
        oldbreak = getvect(0x1b);
        setvect(0x23, newbreak);
        setvect(0x1b, newbreak);
        brkct = TRUE;
    }
}
/* --- change application window title to show filename --- */
static void ChangeQncTitle(void)
{
    char *ttl;
    char *cp = Untitled;
    char *cp1 = editWnd->extension;
    int len = 13;

    if (cp1 && *cp1)    {
        cp = strrchr(cp1, '\\');
        if (cp == NULL)
            cp = strchr(cp1, ':');
        if (cp == NULL)
            cp = cp1;
        else
            cp++;
        len = strlen(cp) + 3;
    }
    ttl = DFmalloc(strlen(qhdr) + len);
    strcpy(ttl, qhdr);
    strcat(ttl, ": ");
    strcat(ttl, cp);
    AddTitle(applWnd, ttl);
    free(ttl);
}
/* --- window processing module for Quincy application --- */
static int QuincyProc(WINDOW wnd,MESSAGE msg,PARAM p1,PARAM p2)
{
    int rtn;
    static BOOL InterceptingShow = FALSE;
    BOOL ThisSbarSetting;
    static BOOL PrevSbarSetting;
    static int PrevScreenLines;
    switch (msg)    {
        case CREATE_WINDOW:
            rtn = DefaultWndProc(wnd, msg, p1, p2);
            if (SendMessage(NULL, MOUSE_INSTALLED, 0, 0))
                SetCheckBox(&Display, ID_SCROLLBARS);
            else
                ClearCheckBox(&Display, ID_SCROLLBARS);
            FixTabMenu();
            return rtn;
        case SIZE:
        {
            int dif = (int) p2 - GetBottom(wnd);
            int EditBottom = p2 - BottomBorderAdj(wnd);
            if (watchWnd != NULL &&
                    TestAttribute(watchWnd, VISIBLE))
                EditBottom -= WindowHeight(watchWnd);
            if (dif > 0)    {
                /* --- getting bigger--- */
                rtn = DefaultWndProc(wnd, msg, p1, p2);
                SendMessage(watchWnd, MOVE, GetLeft(watchWnd),
                                        GetTop(watchWnd)+dif);
                SendMessage(editWnd, SIZE, GetClientRight(wnd), EditBottom);
            }
            else    {
                /* --- getting smaller --- */
                SendMessage(editWnd,
                    SIZE, GetClientRight(wnd), EditBottom);
                SendMessage(watchWnd, MOVE, GetLeft(watchWnd),
                                        GetTop(watchWnd)+dif);
                rtn = DefaultWndProc(wnd, msg, p1, p2);
            }
            return rtn;
        }
        case SHOW_WINDOW:
            ThisSbarSetting =
                CheckBoxSetting(&Display, ID_SCROLLBARS);
            if (InterceptingShow)    {
                if (ThisSbarSetting != PrevSbarSetting)    {
                    if (ThisSbarSetting)
                        AddAttribute(editWnd,
                            VSCROLLBAR | HSCROLLBAR);
                    else     {
                        ClearAttribute(editWnd, VSCROLLBAR);
                        ClearAttribute(editWnd, HSCROLLBAR);
                    }
                }
                if (PrevScreenLines != cfg.ScreenLines)
                    SendMessage(wnd, SIZE, GetRight(wnd), cfg.ScreenLines-1);
            }
            break;
        case CLOSE_WINDOW:
            SendMessage(editWnd, CLOSE_WINDOW, 0, 0);
            break;
        case SETFOCUS:
            if (p1 && editWnd)    {
                SendMessage(editWnd, msg, p1, p2);
                return TRUE;
            }
            break;
        case COMMAND:
            switch ((int)p1)    {
                case ID_NEW:
                    OpenSourceCode(Untitled);
                    return TRUE;
                case ID_OPEN:
                    SelectFile();
                    return TRUE;
                case ID_SAVE:
                    SaveFile(FALSE);
                    return TRUE;
                case ID_SAVEAS:
                    SaveFile(TRUE);
                    return TRUE;
                case ID_PRINTSETUP:
                    DialogBox(wnd, &PrintSetup, TRUE, PrintSetupProc);
                    return TRUE;
                case ID_PRINT:
                    PrintSourceCode();
                    return TRUE;
                case ID_OUTPUT:
                    OutputScreen();
                    return TRUE;
                case ID_RUN:
                    RunProgram();
                    if (Exiting)
                        PostMessage(wnd, CLOSE_WINDOW, 0, 0);
                    return TRUE;
                case ID_STOP:
                    StopProgram();
                    return TRUE;
                case ID_EXAMINE:
                    ExamineVariable();
                    return TRUE;
                case ID_WATCH:
                    ToggleWatch();
                    return TRUE;
                case ID_ADDWATCH:
                    AddWatch();
                    return TRUE;
                case ID_SWITCHWINDOW:
                    SetNextFocus();
                    return TRUE;
                case ID_STEPOVER:
                    StepOverFunction();
                    return TRUE;
                case ID_STEP:
                    StepProgram();
                    if (Exiting)
                        PostMessage(wnd, CLOSE_WINDOW, 0, 0);
                    return TRUE;
                case ID_BREAKPOINT:
                    ToggleBreakpoint();
                    return TRUE;
                case ID_DELBREAKPOINTS:
                    DeleteBreakpoints();
                    return TRUE;
                case ID_COMMANDLINE:
                    CommandLine();
                    return TRUE;
                case ID_MEMORY:
                    Memory();
                    return TRUE;
                case ID_TAB2:
                    cfg.Tabs = 2;
                    ChangeTabs();
                    return TRUE;
                case ID_TAB4:
                    cfg.Tabs = 4;
                    ChangeTabs();
                    return TRUE;
                case ID_TAB6:
                    cfg.Tabs = 6;
                    ChangeTabs();
                    return TRUE;
                case ID_TAB8:
                    cfg.Tabs = 8;
                    ChangeTabs();
                    return TRUE;
                case ID_DISPLAY:
                    InterceptingShow = TRUE;
                    PrevSbarSetting =
                        CheckBoxSetting(&Display, ID_SCROLLBARS);
                    PrevScreenLines = cfg.ScreenLines;
                    rtn = DefaultWndProc(wnd, msg, p1, p2);
                    InterceptingShow = FALSE;
                    return rtn;
                case ID_EXIT:
                case ID_SYSCLOSE:
                    if (Stepping)    {
                        Exiting = TRUE;
                        StopProgram();
                        return TRUE;
                    }
                    break;
                case ID_ABOUT:
                    MessageBox
                    (
                         "About Quincy",
                        "                          \n"
                        "                          \n"
                        "                          \n"
                        "                          \n"
                        "                          \n"
                        "    \n"
                        "    The Quincy C Interpreter\n"
                        "          Version 4.0\n"
                        " Copyright (c) 1994 Al Stevens\n"
                        "       All Rights Reserved"
                    );
                    return TRUE;
                default:
                    break;
            }
            break;
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}
/* --- The Open... command. Select a file  --- */
static void SelectFile(void)
{
    char FileName[64];
    if (OpenFileDialogBox("*.c", FileName))
        /* --- see if the document is already in --- */
        if (stricmp(FileName, editWnd->extension) != 0)
            OpenSourceCode(FileName);
}
/* --- open a document window and load a file --- */
static void OpenSourceCode(char *FileName)
{
    WINDOW wwnd;
    struct stat sb;
    char *ermsg;

    CloseSourceCode();
    wwnd = WatchIcon();
    if (strcmp(FileName, Untitled))    {
        editWnd->extension = DFmalloc(strlen(FileName)+1);
        strcpy(editWnd->extension, FileName);
        LoadFile();
    }
    ChangeQncTitle();
    SendMessage(wwnd, CLOSE_WINDOW, 0, 0);
    SendMessage(editWnd, PAINT, 0, 0);
}
/* --- Load source code file into editor text buffer --- */
static void LoadFile(void)
{
    FILE *fp;
    if ((fp = fopen(editWnd->extension, "rt")) != NULL)    {
        char *Buf;
        struct stat sb;

        stat(editWnd->extension, &sb);
        Buf = DFcalloc(1, sb.st_size+1);

        /* --- read the source file --- */
        fread(Buf, sb.st_size, 1, fp);
        fclose(fp);
        SendMessage(editWnd, SETTEXT, (PARAM) Buf, 0);
        free(Buf);
    }
}
/* ---------- save a file to disk ------------ */
static void SaveFile(int Saveas)
{
    FILE *fp;
    if (editWnd->extension == NULL || Saveas)    {
        char FileName[64];
        if (SaveAsDialogBox("*.c", FileName))    {
            if (editWnd->extension != NULL)
                free(editWnd->extension);
            editWnd->extension = DFmalloc(strlen(FileName)+1);
            strcpy(editWnd->extension, FileName);
        }
        else
            return;
    }
    if (editWnd->extension != NULL)    {
        WINDOW mwnd = MomentaryMessage("Saving the file");
        if ((fp = fopen(editWnd->extension, "wt")) != NULL)    {
            CollapseTabs(editWnd);
            fwrite(GetText(editWnd), strlen(GetText(editWnd)), 1, fp);
            fclose(fp);
            ExpandTabs(editWnd);
        }
        SendMessage(mwnd, CLOSE_WINDOW, 0, 0);
        ChangeQncTitle();
        SendMessage(editWnd, SETFOCUS, TRUE, 0);
    }
}
/* ------ display the row and column in the statusbar ------ */
static void ShowPosition(WINDOW wnd)
{
    char status[60];
    sprintf(status, "Line:%4d  Column: %2d", wnd->CurrLine+1, wnd->CurrCol+1);
    SendMessage(GetParent(wnd), ADDSTATUS, (PARAM) status, 0);
}
/* ----- close and save the source code -------- */
static void CloseSourceCode(void)
{
    if (editWnd->TextChanged)
        if (YesNoBox("Text changed. Save it?"))
            SendMessage(applWnd, COMMAND, ID_SAVE, 0);
    SendMessage(editWnd, CLEARTEXT, 0, 0);
    SendMessage(editWnd, PAINT, 0, 0);
    if (editWnd->extension != NULL)    {
        free(editWnd->extension);
        editWnd->extension = NULL;
    }
    DeleteAllWatches();
    DeleteBreakpoints();
    StopProgram();
}
/* ---- count the newlines in a block of text --- */
static int CountNewLines(char *beg, char *end)
{
    int ct = 0;
    while (beg <= end)
        if (*beg++ == '\n')
            ct++;
    return ct;
}
/* ---- count the newlines in a block of editor text --- */
static int CountBlockNewLines(WINDOW wnd)
{
    return TextBlockMarked(wnd) ?
            CountNewLines(TextBlockBegin(wnd), TextBlockEnd(wnd)) : 0;
}
/* ---- count the newlines in clipboard text --- */
static int CountClipboardNewLines(WINDOW wnd)
{
    return ClipboardLength ?
            CountNewLines(Clipboard, Clipboard+ClipboardLength-1) : 0;
}
/* ----- window processing module for the editor ----- */
static int QncEditorProc(WINDOW wnd,MESSAGE msg, PARAM p1,PARAM p2)
{
    int rtn;
    switch (msg)    {
        case SETFOCUS:
            rtn = DefaultWndProc(wnd, msg, p1, p2);
            if ((int)p1 == FALSE)    {
                SendMessage(GetParent(wnd), ADDSTATUS, 0, 0);
                if (ErrorCode == 0)
                    SendMessage(NULL, HIDE_CURSOR, 0, 0);
            }
            else
                ShowPosition(wnd);
            return rtn;
        case SCROLL:
        case PAINT:
        {
            int lno;
            rtn = DefaultWndProc(wnd, msg, p1, p2);
            /* ---- update source line pointer and breakpoint displays ----- */
            for (lno = wnd->wtop+1;
                    lno <= wnd->wtop+ClientHeight(wnd); lno++)
                if ((Stepping && lno == LastStep) ||
                        isBreakpoint(lno))
                    DisplaySourceLine(lno);
            return rtn;
        }
        case KEYBOARD:
            /* --- if user adds/deletes lines,
                    adjust breakpoint table in debugger --- */
            if ((int) p1 == '\r')
                AdjustBreakpointsInserting(wnd->CurrLine+1, 1);
            else if ((int) p1 == DEL && *CurrChar == '\n')
                AdjustBreakpointsDeleting(wnd->CurrLine+2, 1);
            break;
        case KEYBOARD_CURSOR:
            rtn = DefaultWndProc(wnd, msg, p1, p2);
            SendMessage(NULL, SHOW_CURSOR, 0, 0);
            ShowPosition(wnd);
            return rtn;
        case COMMAND:
            switch ((int) p1)    {
                case ID_SEARCH:
                    SearchText(wnd);
                    return TRUE;
                case ID_REPLACE:
                    ReplaceText(wnd);
                    return TRUE;
                case ID_SEARCHNEXT:
                    SearchNext(wnd);
                    return TRUE;
                case ID_CUT:
                    CopyToClipboard(wnd);
                    SendMessage(wnd, COMMAND, ID_DELETETEXT, 0);
                    SendMessage(wnd, PAINT, 0, 0);
                    return TRUE;
                case ID_COPY:
                    CopyToClipboard(wnd);
                    ClearTextBlock(wnd);
                    SendMessage(wnd, PAINT, 0, 0);
                    return TRUE;
                case ID_PASTE:
                    /* --- if user pastes lines,
                        adjust breakpoint table in debugger --- */
                    AdjustBreakpointsInserting(wnd->CurrLine+1,
                            CountClipboardNewLines(wnd));
                    PasteFromClipboard(wnd);
                    SendMessage(wnd, PAINT, 0, 0);
                    return TRUE;
                case ID_DELETETEXT:
                    /* --- if user deletes lines,
                        adjust breakpoint table in debugger --- */
                    AdjustBreakpointsDeleting(wnd->BlkBegLine+1,
                            CountBlockNewLines(wnd));
                    rtn = DefaultWndProc(wnd, msg, p1, p2);
                    SendMessage(wnd, PAINT, 0, 0);
                    return rtn;
                case ID_HELP:
                    DisplayHelp(wnd, "QUINCY");
                    return TRUE;
                default:
                    break;
            }
            break;
        case CLOSE_WINDOW:
            CloseSourceCode();
            break;
        default:
            break;
    }
    return DefaultWndProc(wnd, msg, p1, p2);
}
/* -- point to the name component of a file specification -- */
static char *NameComponent(char *FileName)
{
    char *Fname;
    if ((Fname = strrchr(FileName, '\\')) == NULL)
        if ((Fname = strrchr(FileName, ':')) == NULL)
            Fname = FileName-1;
    return Fname + 1;
}
/* ---- rebuild display when user changes tab sizes ---- */
static void ChangeTabs(void)
{
    FixTabMenu();
    CollapseTabs(editWnd);
    ExpandTabs(editWnd);
}
/* ---- update the tab menu when user changes tabs ---- */
static void FixTabMenu(void)
{
    char *cp = GetCommandText(&MainMenu, ID_TABS);
    if (cp != NULL)    {
        cp = strchr(cp, '(');
        if (cp != NULL)    {
            *(cp+1) = cfg.Tabs + '0';
            if (GetClass(inFocus) == POPDOWNMENU)
                SendMessage(inFocus, PAINT, 0, 0);
        }
    }
}
/* ------- display the program's output screen ----- */
static void OutputScreen(void)
{
    SendMessage(NULL, HIDE_CURSOR, 0, 0);
    HideIDE();
    getkey();
    UnHideIDE();
    SendMessage(NULL, SHOW_CURSOR, 0, 0);
}
/* ---- the user may change certain interpreter memory parameters --- */
static void Memory(void)
{
    char text[20], *tx;
    int i;
    static struct prm {
        unsigned *max;
        unsigned id;
    } parms[] = {
        { &MaxProgram,   ID_PROGSIZE }, { &MaxDataSpace, ID_DATASPACE },
        { &MaxVariables, ID_VARIABLES }, { &MaxStack,     ID_STACK },
        { &MaxJumps,     ID_JUMP }
    };
    for (i = 0; i < (sizeof parms / sizeof(struct prm)); i++) {
        sprintf(text, "%u", *parms[i].max);
        SetEditBoxText(&MemoryDB, parms[i].id, text);
    }
    if (DialogBox(applWnd, &MemoryDB, TRUE, NULL))    {
        for (i = 0; i < (sizeof parms/sizeof(struct prm)); i++)
            *parms[i].max = atoi(GetEditBoxText(&MemoryDB, parms[i].id));
    }
}


Copyright © 1994, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.