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

Tools

Examining the PowerBASIC Developer Kit


MAR95: Examining the PowerBASIC Developer Kit

Examining the PowerBASIC Developer Kit

Vocabulary-frequency analysis moves from DOS to Windows

Raymond J. Schneider

Ray is the director of engineering at ComSonics and a doctoral candidate at George Mason University. He can be reached at [email protected].


Language--the most significant of human inventions--is to a great extent the medium of thought. Through the development of language we enlarge the scope of that which is thinkable. The act of composing computer programs is not unlike the use of natural language, though more formal and less flexible. The history of software engineering is the history of computer languages, beginning with Fortran, Cobol, and Lisp, and continuing to more recent languages such as Prolog, Smalltalk, C++, and others.

With this in mind, while working on my doctoral research at George Mason University in software-engineering methodologies, I explored the vocabulary used by authors writing professional papers in the software-engineering field. In the process, I developed some relatively simple tools to perform vocabulary-frequency analysis. As originally written, these programs run in a typical command-line environment under DOS. In a recent encounter with the PowerBASIC Developer Kit (PBDK), I dusted off these routines, extending and migrating them to Windows using the PBDK. The complete source code to both versions is available electronically; see "Availability," page 3.

English Riches

English has the largest vocabulary of any human language. Among the reasons for this are that it springs from a relatively rich core vocabulary drawn from Anglo-Saxon, Latin, and Greek. Furthermore, English has adopted interesting words from all other languages, so-called "loan" words. The simplest structure of an English sentence is a noun phrase (NP), followed by a verb phrase (VP) (also known as the "subject" followed by a "predicate"). Thus, this simple structure could be expressed as NP+VP.

In the sentence, "The programmer wrote the application," The programmer is the NP (subject) and wrote the application is the VP (predicate). In her book, Understanding English Grammar (McMillan, 1982), Martha Kolln distinguishes "_ten sentence patterns which account for the underlying skeletal structure of almost all the possible grammatical sentences in English. All are elaborations on this simple underlying pattern."

To understand English with a computer, it is necessary to incorporate both a large dictionary of words and rules for word transformation, sentence composition, and some linkage into machine-based semantics to allow some degree of understanding. According to Bill Bryson in The Mother Tongue (William Morrow, 1990), Webster's Third New International Dictionary lists 450,000 words, while the revised Oxford English Dictionary has 615,000. If we include all the scientific and technical terms and all the transformational words created by various formation rules, we easily get into the range of many millions of words. This is well beyond any reasonable tabular scheme, so obviously we must devise some rules.

Many Prolog implementations incorporate definite clause grammar (DCG), a scheme for parsing sentences. The DCG is basically a set of rewrite rules which use the infix operator -->. Thus our simple sentence definition, NP+VP would be written sentence --> noun-phrase, verb-phrase.

Vocabulary and Frequency

Words embody ideas. To study a subject, you need to learn the meaning of the words which are used and the way they are combined. This is true not only of natural language but of formal languages such as mathematics and computer programming. The importance of a concept expressed in a particular fragment of text may be measured by the frequency with which the words are used. Of course, some words occur with great frequency because they form part of the syntactic structure that holds the linguistic forms together. Reporting on a 1923 study by lexicographer G.H. McKnight, Cullen Murphy lists the infamous nine which account for one-quarter of all spoken words: and, be, have, it, of, the, to, will, and you ("The Big Nine," Atlantic, March 1988). Once we are past these and two score or more glue and structure words, we encounter the vocabulary that holds the ideas and relations which are the core of the communication.

Figure 1 illustrates the system structure of a small set of programs I wrote to collect frequency data on text fragments. The structure centers on text and alpha files. The text file is assumed to be a set of lines encoded in ASCII. An alpha file is a comma-delimited file whose first field is an ASCII string (normalized to all capital letters) and whose second field is the integer number of times that the word appears in the text fragment. The four functions shown in Figure 2 make up the system.

The system is implemented using PowerBASIC, the current embodiment of Borland's Turbo BASIC, written by Bob Zale. Like most Basics, PowerBASIC has powerful string-handling facilities. It is a fast native-code compiler, and the resulting programs are also fast. Additionally, PowerBASIC includes a variety of enhancements not found in many other Basics. I found the REPLACE statement, VERIFY function, and ARRAY SORT statement particularly helpful. The REPLACE statement replaces all occurrences of a given string with a new string which is specified; the VERIFY function determines whether each character of a string is present in another string; and ARRAY SORT allows you to sort all or part of an array into ascending or descending order. You can also include a TAGARRAY, an array associated and sorted with the main array. Thus, to sort the alpha files into alphabetical order, sort with the words using the frequencies as a TAGARRAY and vice versa when sorting in frequency order.

DOS and the COUNT Algorithm

The COUNT algorithm (see Listing One) simply processes lines of ASCII text one line at a time. COUNT isolates the words in the line by replacing all nonalpha characters (numbers, punctuation, brackets, and so on), except hyphens and apostrophes, with blanks using the VERIFY command. (Note that in Listing One, I first used the REPLACE command to replace numbers, brackets, and punctuation.) The VERIFY is open ended and cleans up anything that is not an alpha character or a hyphen or an apostrophe. Then the words are isolated as blank, separated character substrings, and any trailing or isolated hyphens or apostrophes are eliminated. Thus, a word is defined as a character string with embedded hyphens and apostrophes. This allows contractions such as "can't" and hyphenated words such as "high-brow" to get through the system whole.

As written, lines terminating in a hyphen are handled improperly. Also, some word structures fairly common in computer-science literature--"dot" notations referring to structures and the common use of underscore characters in variable names, for example--will be split. While these limitations are acceptable for my needs, this might be a good place to start when considering extensions to the code.

After getting all the words into an array, the array is sorted and scanned. Words that are spelled the same are adjacent in the sorted array, so the scan is performed, the frequency is accumulated, and the word array is reduced to an alpha array of words and their associated frequencies. This array is written out as a file with the same DOS name as the original, but with the .CNT extension. The rest of the programs--SORTA, MERGEA, and STATA--are available electronically (see "Availability," page 3). The programs parse the command line, read in the input file or files, perform the operation and write an output file, or in the case of STATA, the word counts are simply printed to standard output.

The process is a bit trickier in the case of MERGEA. The command line can include up to five files to be merged, as well as the name that the output file is to take. The alpha lines are simply appended, and the file is sorted with the frequency numbers as a TAGARRAY. Then the file is scanned in a fashion similar to COUNT except that alpha lines are scanned and the frequency counts of repeated words between files are accumulated.

Inside the PBDK

PowerBASIC recently added the PowerBASIC Developer Kit (PBDK) to its family of support tools. PBDK is a shell over the Windows API that simplifies Windows programming dramatically. Simple but complete access to Windows, dialog boxes, menus, DDE, clipboard, DLLs, and preemptive multitasking are all provided and relatively easy to use. Since it is not intrinsically event driven like Visual Basic, the PBDK provides a more graceful introduction to the Windows environment without the "sink-or-swim" feeling. The PBDK also provides PowerBASIC developers with a migration path to Windows that does not require a major rewrite of DOS application code.

The PBDK run time consists of three files: the DVSERVER.EXE server, DVDLL.DLL, and Windows virtual driver VDV.386. The DLL and driver are contained within DVSERVER.EXE, which will extract them transparently at run time and do so only once. The server shields you from much of the complexity of the Windows environment. From PowerBASIC, the server simply looks like a standard library.

Although Windows is message based, the PBDK handles all the low-level messages, leaving you to handle only those messages that are application dependent. The components of a Windows application as implemented by PBDK include the session, the working area of a PBDK application where it will open other windows and where any menus will be installed, and then various user-defined dialogs containing the controls the application will use. To make the application operate you must use event loops. The event loop uses the GetMessage() function to read the message queue maintained by the DV server. Figure 3 shows the steps required to create an application.

Migrating to Windows

Implementing the application required a bit of thought. The functions were generally independent in the DOS implementation. Creating a Windows implementation, however, implies a higher degree of integration. My first thought was simply to create a menu structure whose elements (Count, Sort, Statistics, and Merge) corresponded to one of the DOS commands. The implementation would just get the command parameters, and the only change necessary to the original DOS code would be to change from parsing a COMMAND$ line argument to passing the string to a subroutine for parsing.

However, it quickly became obvious that this simple approach would not be satisfactory by itself. The problem is that Windows is a very visual environment, while the DOS command line is a very nonvisual environment. Moreover, many capabilities available in the DOS environment--file management, browsers, and editors, for example--are not as immediately available under Windows. Thus, the application that I collectively call "Fun With Words" would have to incorporate visual elements that are not part of the DOS implementation.

Figure 4 shows the Windows version of the application. There is the Session window containing the menu bar. Note that the additional menu item Get has been added to allow access to a .CNT file directly. Within the Session window is a dialog containing a variety of controls used to add visual elements to Fun With Words. The controls are two edit boxes and two list boxes together with three buttons labeled OK, GO, and End Merge. During the development I found this arrangement fairly intuitive. However, I suspect that users will find it a bit confusing since the dialog is used in more than one way.

Figure 5 shows the application with annotations showing how the menus and controls operate. Both Count and Get open the OpenFileName common dialog. With OpenFileName, users can select a filename which is then passed to the edit box with the label Active Word File, which holds an alpha filename. The list box is loaded with the alpha file lines, generally in alpha order. Selecting Sort causes the alpha file to be put in frequency order in the list box. Statistics opens a message box with the total word count and the distinct word count for the file in the Active Word File edit box. Selecting Merge makes the dialog box modal and puts it in merge mode; see Figure 4. In merge mode, the Active Word File can be used as a source of alpha filenames by putting the appropriate wild cards in the edit box. The list box is loaded when the OK button is clicked. Double-clicking on an alpha filename causes it to appear in the Merge File Selection list box. Adding an output filename in the associated edit box and selecting Go causes the files to be merged and written to the output file. Clicking on End Merge clears the edit and list boxes and reactivates the main menu.

Coding with the PBDK

The OpenSession() subroutine in Listing Two initializes the session, sets the opening size of the window and the Txt$="Fun With Words" in the title bar, and returns the session handle, hSession%. The menu is defined in a resource file created with the Symantex Resource Toolkit included with the PBDK. You can also create menus directly in the code, but it's more cumbersome.

The menu is loaded with the LoadResources() subroutine and set with SetMenu(). The main dialog illustrated in Figure 4 is loaded with LoadResources() and then the various buttons and boxes are loaded with the GetDlgItem() subroutine. Once all the menus, dialog, and controls are in place, the program launches the main event loop. This is a straightforward loop which can be implemented in a number of ways. In Listing Two, a WHILE loop is used.

The key to handling PBDK messages is to set up appropriate conditions in the event loop. You can go a long way without getting too fancy. The parameters in the GetMessage() subroutine call provide you with all the information necessary to interpret the messages. hMsgWnd% is the handle of the object sending the message. Msg% is the message number denoting the kind of message. wP% is the main parameter of the message, which depends on the message number. XCursor%, YCursor% is the position of the cursor. XParam%() is a parameter array whose meaning is dependent on Msg%. Cmd$ is a string with additional information.

All controls (buttons, list, edit boxes, and so on) send the value Msg%=WMCOMMAND% and the ID of the control in the variable wP%. Numerical values in XParam%(2) give notification codes such as LBNDBLCLK% (List Box Notification Double Click), which is used to transfer filenames from the left list box to the right list box.

In Fun With Words, the global event loop looks for menu selections. With each selection, it does what is indicated (Count, Get, and so on). With Merge, it sets the main dialog box modal and sets off a local event loop to handle the Merge functionality. Clicking on the button End Merge causes the program to clear the boxes and return to the menu loop.

Pitfalls

Most of the problems in coding Fun With Words resulted from weak PBDK documentation in certain areas. The examples tend to be somewhat superficial, something not particularly surprising in a new toolkit. The two-volume manual covers a lot of territory, but Windows is a big topic. The PBDK is now being shipped with improved documentation.

I found that the GetOpenFileName() subroutine to use the OpenFileName common dialog was bombing the application when the Cancel button was clicked--my window would just collapse. After struggling and failing to find out why, I called PowerBASIC technical support. It turns out that you must execute a ReadErrorNumber() after calling GetOpenFileName() and test for error conditions. In my case, ReadError-Number() returned a value of 3. This is not exactly intuitive and I'm sure many more of these little tidbits of information are not clearly documented in the manuals.

Another problem I encountered was with the file buffers in many of the DV subroutines. Buffers had to be initialized with a command like FileName$=SPACE$(32), providing a 32-character buffer. When a string was returned, it was left-justified and space filled to the extent of the buffer. This caused a few problems when I simplistically concatenated filenames for the merge command. The parse instructions just weren't expecting really big strings. To find the problem, I scattered line numbers through the merge routine and created a MessageBox to tell me where I was blowing up. I left these in the code as warnings to stray passers-by. I also put a MessageBox out for every filename to establish that it was parsed correctly.

Conclusion

My initial experience using the PowerBASIC Developer Kit was positive and the effort, very productive. Although there were some difficulties and false starts, the product shields the programmer from many of Windows programming details, while allowing very functional use of the Windows environment. A strong point was the ability to easily port code created in the DOS environment to Windows with minimal change. I found the use of event loops somewhat more forgiving for a programmer unfamiliar with the Windows environment than a full-blown, event-driven interface would be. I was slightly disappointed that PBDK does not currently support the use of Visual Basic VBX-custom controls, nor does it support OLE 2. Bob Zale, PowerBASIC's author, assured me that VBX support is coming in the next release, with OLE 2 support to follow.

For More Information

PowerBASIC Developer Kit

PowerBASIC Inc.

316 Mid Valley Center

Carmel, CA 93923

800-780-7707

$299.00

Figure 1 Structure of the command-line word-frequency system.

Figure 2: Functions that make up the command-line word-frequency system.

COUNT    count <filename.ext> -> <filename.cnt>, 
         where <filename.ext> is an ASCII text file
         in free format and <filename.cnt> is
         an alpha file, comma delimited in the
         form <word>, <frequency>. The count
         function preserves hyphens and
         apostrophes in words and produces
         an alpha file in alphabetical order.
MERGEA   mergea <file1> <file2> _ / <fileout>. Input 
         files are alpha files; output is a combined 
         alpha file that adds together the frequencies 
         of the same words and produces a single, 
         merged alpha file. The alpha input files may
         be ordered in any fashion; the output file is 
         in alphabetical order.
SORTA    sorta <filename> [/A, /F] //. Accepts an 
         alpha file of unknown order and puts it in 

         either alphabetical or frequency order. If no 
         flag is provided, /F is assumed, and it will 
         be put into frequency order.
STATA    stata <filename>. Prints the total number of 
         words and the number of distinct words on the 
         screen. 

Figure 3: Steps to creating a Windows application using the PBDK.

1. Initialize PowerBASIC to link to the DVSERVER.
2. Initialize the DVSERVER.
3. Open the session.
4. Attach menus.
5. Add dialogs and controls.
6. Create global event loop.
7. Incorporate message handling and minor event loops as
   needed to implement application.
8. Disconnect server when finished: END.

Figure 4 Windows version of the vocabulary-frequency-analysis application. Figure 5 Annotated version of the vocabulary-frequency-analysis application.

Listing One


$ERROR ALL
InputFileName$=COMMAND$
CALL Mergea(InputFileName$)
END
'Program to read files and isolate and count occurances of words
' file should have the extension .TXT however the program will
' attempt to read any file so long as the user insists.

'OUTLINE
' 1. READ A LINE -- Drop if it is a comment line (i.e preceeded with ')
' 2. PARSE INTO WORDS
' 3. ADD WORDS TO AN ARRAY
' 4. WHEN LAST LINE, GO BACK AND...
' 5. ANALYZE ARRAY FOR REPEATED OCCURANCES OF WORDS
' 6. INITIALLY BY SORTING THE CONTENTS OF THE ARRAY AND
' 7. THEN GOING THROUGH THE ARRAY AND ACCUMULATING REPEATED WORDS.
' 8. WHEN FINISHED PRINT OUT TOTAL WORDS AND DISTINCT WORDS TO SCREEN.
' 9. WRITE THE ALPHA FILE <WORD>,<# OCCURANCES> IN SORTED ALPHA ORDER
'10. BUT THIS IS NOT AN ASSUMPTION ABOUT ALPHA FILES, THEY CAN BE IN ANY
'    ORDER Alpha File line::= <word>,<frequency>
'******************************************
'6/5/94 Added LinkBack: to drop comment lines so that input files could
'contain embedded comments without affecting the word counts
'6/7/94 Dropping word print outs and adding a stats line at end of run
'******************************************
 SUB Mergea (InputFileName$)
 'OPEN FILE
 $DYNAMIC
 MaxWords%=20000
 MaxWordsOnLine%=500
 DIM Word$(MaxWordsOnLine%)  'Assumes no more than MaxWordsOnLine%
 DIM AllWord$(MaxWords%), Vocab$(MaxWords%), VFreq%(MaxWords%)
 'String Constants Required
 UpperCase$="ABCDEFGHIJKLMNOPQRSTUVWXYZ"
 LowerCase$="abcdefghijklmnopqrstuvwxyz"
 Punctuation$=",.:;?"
 Brackets$="<>{}[]()"
 Digits$="0123456789"

 'Now create OutputFileName$ by parsing COMMAND$
 FileRootName$=EXTRACT$(InputFileName$,".")
 '********* Note that this Output FileName creation may be Error Prone
 OutputFileName$=FileRootName$+".CNT"
 'Ready to Open input file
 ON ERROR GOTO ErrorHandler
 OPEN InputFileName$ for input as #1
 WordCount=0 'This is the overall wordcount for the Input File
 WHILE NOT EOF(1)
 '*******************Get a Line$ and Parse it into words ************
 'Read Line In
LinkBack:
  LINE INPUT #1, Lin$
  'Parse Line Into Words
'First check line for apostrophe ... and skip lines with apostrophes
IF LEFT$(Lin$,1)="'" THEN Goto LinkBack 'this drops comment lines
'Then Eliminate non-alpha characters replacing them with spaces
  REPLACE ANY ",.:;?<>{}[]()0123456789" WITH "                       " IN Lin$
Again:
  L=VERIFY(Lin$,UpperCase$+LowerCase$+" "+"'"+"-") 'note apostrophe so as to 
                                                   '    capture contractions
  'Note this process will accept hyphens as a word and we want only
  'internal hyphens -- see below at EXTRACT$
  IF L=0 THEN Goto Success
  'Otherwise L points to a non alpha non space character
  'replace it with a space and continue
  MID$(Lin$,L)=" "
  Goto Again
Success:
  Lin$=LTRIM$(Lin$)
  Lin$=RTRIM$(Lin$)
  'Now spaces are stripped from front and back of string
  i=0 'Word Counter for # for Word(s) in Line$
KeepGoing:
  IF Len(Lin$)=0 THEN Goto Done
  Word$(i)=EXTRACT$(Lin$," ") 'Finds ith word but it may just be hyphens
  Lin$=LTRIM$(Lin$,Word$(i)) 'Strips found word from string
  Lin$=LTRIM$(Lin$) 'Strips leading blacks
  Word$(i)=LTRIM$(Word$(i),ANY "-'")
  Word$(i)=RTRIM$(Word$(i),ANY "-'")
  IF Len(Word$(i))=0 THEN Goto KeepGoing
  i=i+1 'Increment counter
  Goto KeepGoing 'this will loop collecting the words in Lin$
Done:
'****************** End of Line$ Parse *************************
  'i enters Done as the # of words collected indexed from 0 to n-1
  'Note that this is done with a line not the whole file so now we have
  'to add the words to the AllWord$() array
  'Now we must put the indicated # of words in an array which is
  ' large enough to hold all the words in the file.
'************** Assemble the Line$ words into the AllWord$ Array *******
  For j=0+WordCount to i-1+WordCount
  AllWord$(j)= Word$(j-WordCount)
  Next j
  WordCount=WordCount + i 'Increments after assigning all Word$(s) from line
WEND
CLOSE #1
'*********************************Word Collection Complete ************
' **** SORT the AllWord$(0 to WordCount-1) Array **********************
ARRAY SORT AllWord$(0) FOR WordCount,COLLATE UCASE
IF WordCount>0 THEN GOTO Proceed
Print "No Words Found"
END
Proceed:
'Variables are NumWords which counts the Vocabulary Additions AllWordPointer 
' which indexes through the AllWord$() array WordCount which holds the number 
' of words in the AllWord$() array indexed from 0 to WordCount-1
'Vocab$() the Vocabulary Array and VFreq%() the frequency array
NumWords=0
AllWordPointer=0 'Initialize NumWords and AllWordPointer
Vocab$(NumWords)=AllWord$(AllWordPointer) 'Initializes 1st word
VFreq%(NumWords)=1 'One occurrance of the 1st word
VocabAgain:
  AllWordPointer=AllWordPointer+1 'Now has # of words scanned
  IF UCASE$(AllWord$(AllWordPointer))=UCASE$(AllWord$(AllWordPointer-1)) THEN
     'Word is repeated so
     VFreq%(NumWords)=VFreq%(NumWords)+1
     IF AllWordPointer=WordCount-1 THEN Goto VDone
     Goto VocabAgain
  ELSE
     NumWords=NumWords+1
     Vocab$(NumWords)=AllWord$(AllWordPointer)
     VFreq%(NumWords)=1
     IF AllWordPointer=WordCount-1 THEN Goto VDone
     Goto VocabAgain
  END IF
VDone:
OPEN OutputFileName$ for output as #1
For j=0 to NumWords
PRINT #1,UCASE$(Vocab$(j));",";STR$(VFreq%(j))
next j
CLOSE #1
Print "Total Number of Words= ";WordCount
Print "Number of Different Words is= ";NumWords+1
END SUB
'********************************************************
' SUBROUTINES
'********************************************************
 ErrorHandler:
 E=ERRTEST
 IF E=53 then Print "Input File Not Found":END
 Print "Error";E;" Occurred"


Listing Two


$ERROR ALL
'Fun With Words -- an example of the conversion of DOS BASIC programs to 
' Windows using the PowerBASIC Development Kit -- Copyright R.Schneider 1994

$INCLUDE "PB3DV.PB3"
CALL InitExecution( "FWW", RESETAPP% )
CALL ReadErrorNumber( DVError% )
IF DVError% > 0 THEN END

'*****
'Creation of a Session, required to use Menus.
'*****
Txt$  =  "Fun With Words"
CALL OpenSession( Txt$, 0, 0, 639, 479, hSession% )

'Open Menu Resource
MenuResName$="WFMENU.RC"
CALL LoadResources(MenuResName$,hMenuRes%)
MenuName$="MainMenu"
CALL OpenMenuRes(hMenuRes%,0,MenuName$,hMenu%)
' Setting the new Menu in the current Session. The Menu is now
' displayed, and it starts emitting messages.
CALL SetMenu(hMenu%)

'Load MainDialog Box
ResName$="wfmndlg.rc"
CALL LoadResources(ResName$,hDlg%)
DlgName$="MAINDLG"
CALL OpenDlgRes(hDlg%,DlgName$,hWnd%)
CALL SetWindowModal(hWnd%,NOTMODAL%)

'Open Resources in Main Dialog Box
hEditBox%=101
CALL GetDlgItem(hWnd%,hEditBox%,hEdit%) 'hEdit% set by Windows
hListBox%=103
CALL GetDlgItem(hWnd%,hListBox%,hList%)
hGOButton%=107
CALL GetDlgItem(hWnd%,hGOButton%,hGO%)
hEndMergeButton%=110
CALL GetDlgItem(hWnd%,hEndMergeButton%,hEndMerge%)
hMergeFileEdit%=108
CALL GetDlgItem(hWnd%,hMergeFileEdit%,hMergeEdit%)
hMergeListBox%=109
CALL GetDlgItem(hWnd%,hMergeListBox%,hMergeList%)
hOKButton%=111
CALL GetDlgItem(hWnd%,hOKButton%,hOK%)
' Event Loop
DIM XParam%(4)
GetMsg%  =  1
WHILE GetMsg% <> 0
CALL GetMessage( hMsgWnd%, Msg%, wP%, XCursor%,_YCursor%, XParam%(1), Cmd$)
' When no message is available, GetMessage returns a Msg=0 Message.
  IF Msg% <> 0 THEN
  ' The Msg%=WMCOMMAND% messages come from the Menu.
  ' wP% contains the ID of the clicked Item.

    IF Msg% = WMCOMMAND% THEN
     IF wP%= 100 THEN 'Menu Count was selected
      Flags&=0
      Filter$="Filter 1 $.TXT $.BAK |*.TXT *.BAK | 
                                     Filter 2 $.C $.DOC $.BAK|*.C *.DOC *.BAK"
      CustomFilter$="CFilter 1 $.TXT $.BAK |*.TXT *.BAK"
      FileTitle$=""
      Title$="Get ASCII Test File to Count"
      FileName$=SPACE$(32)
      Directory$="C\"
      DExt$=""
      CALL GetOpenFileName(0,Filter$,CustomFilter$,0,_
           FileName$,64,_
           FileTitle$,Directory$,Title$,Flags&,_
           FileOffset%,FileExt%,DExt$,xError&)
           CALL ReadErrorNumber(ErrNum%)
         IF ErrNum%=0 THEN
           'Load Edit Box and Read .CNT file into ListBox
           DistinctWords%=0
           CALL Count(FileName$,hEdit%,OutFileName$,DistinctWords%)
           'Now load ListBox
           CALL ResetListBox(hList%)
           Open OutFileName$ for input as #1
           lnum%=0
           for num%=1 to DistinctWords%
           Line Input #1, WordF$
           CALL AddStringListBox(hList%,WordF$,lnum%)
           next num%
           close #1
         END IF
         IF ErrNum%=3 THEN
         END IF 'do nothing
           END IF 'menu 100 COUNT
           IF wP%=200 THEN
           ' Sort File Program Calls etc. Need to check for sort 
               ' variable /F or /A and check that edit box has a file in it.
          IF OutFileName$<>"" THEN
            Path$=OutFileName$
            CALL SetSelEdit(hEdit%,0,32)'Don-t like absolute #s
            CALL ReplaceSelEdit(hEdit%,Path$)
            CALL SortIt(OutFileName$,DWords%)
            CALL ResetListBox(hList%)
            Open OutFileName$ for input as #1
            lnum%=0
            FOR num%=1 to DWords%
            Line Input #1, WordF$
            CALL AddStringListBox(hList%,WordF$,lnum%)
            NEXT num%
            CLOSE #1
          END IF 'OutFileName
        END IF 'menu 200 SORT
        IF wP%=300 THEN
        'Statistics Program
          Print OutFileName$
          'Note should check EditBox for contents and use that!
          IF OutFileName$="" THEN
          ELSE
            CALL Statistics(OutFileName$)
          END IF 'OutFileName
          END IF 'menu 300 STATISTICS
          IF wP%=400 THEN
          'Merge Code
            CALL SetWindowModal(hWnd%,TASKMODAL%)
            CALL ResetListBox(hList%)
            CALL ResetListBox(hMergeList%)
            CALL SetSelEdit(hMergeEdit%,0,32)
            'Set Edit Box to Default Value, *.CNT
            Path$="*.CNT"
            CALL SetSelEdit(hEdit%,0,32)
            CALL ReplaceSelEdit(hEdit%,Path$)
            'Get directory based on DefaultValue
            CALL AddDirListBox(hList%,Path$,DDLREADWRITE%)
            'Setup Local Event Loop
            Cmd$=SPACE$(32)
            DIM Xp%(4)
            GetLocalMsg%=1
            WHILE GetLocalMsg%<>0
              Call GetMessage(hLMsgWnd%, LMsg%, LwP%, Xc%, Yc%, Xp%(1), Cmd$)
              IF LMsg%=0 THEN
            CALL ReleaseTimeSlice
              ELSE 'LMsg% is not equal to zero
            IF LMsg%=WMCOMMAND% THEN
               IF LwP%=hEndMergeButton% THEN
                  GetLocalMsg%=0
               END IF  'hEndMergeButton
               IF LwP%=hListBox% THEN
                  Notification%=Xp%(2)
                  IF Notification%=LBNDBLCLK% THEN
                  'Get the selected item from ListBox
                  'first get # lines in list box
                CALL StatusListBox(hList%,l%,top%,w%,height%,
                                  _MaxWidth%,Index0%,NbLines%,
                                  _SelectedLine%,selcnt%)
                'SelectedLine% is the index of the selected 
                                ' line or it is negative
                ThisLine$=SPACE$(32)
                CALL GetTextListBox(hList%,SelectedLine%,ThisLine$,32)
                Print ThisLine$
                'then look for selected line. put selected line
                'in merge listbox. get # of items in listbox
                CALL StatusListBox(hMergeList%,l%,top%,w%,
                                       height%,_MaxWidth%,Index0%,
                                       NbLines%,_SelectedLine%,selcnt%)
                IF NbLines%<5 THEN
                    CALL AddStringListBox(hMergeList%,ThisLine$,Rank%)
                    Print "Got to NbLine%<5 IF Statement"
                ELSE
                    'List Box has 5 items in it now! No more 
                    'will be accepted. Put up a message box.
                    Txt$  =  "Only 5 files can be merged at a time!"
                    Caption$  =  "Informative Message"
                    CALL MessageBox( 0, Txt$, Caption$,
                                                  MBOK% OR MBTASKMODAL%, Code%)
                END IF 'NbLines<5
                  END IF 'Notification
                END IF 'hListBox
                IF LwP%=hGOButton% THEN
                  'Enter a Merge File Name
                Size%=MAXSTR%
                MergeFileName$=SPACE$(MAXSTR%)
                CALL GetCTLText(hMergeEdit%,MergeFileName$,Size%)
                MergeFileName$=RTRIM$(MergeFileName$)
                MergeFileName$=LTRIM$(MergeFileName$)
                IF MergeFileName$="" THEN GOTO NoGood
                CALL StatusListBox(hMergeList%,l%,top%,w%,
                                   height%,_MaxWidth%,Index0%,
                                   NbLines%,_SelectedLine%,selcnt%)
                CtlString$=""
                for i%=Index0% to Index0%+NbLines%-1
                Size%=MAXSTR%
                Text$=SPACE$(MAXSTR%)
                CALL GetTextListBox(hMergeList%,i%,Text$,Size%)
                Text$=RTRIM$(Text$)
                CtlString$=CtlString$+" "+Text$
                next i%
                CtlString$=CtlString$+" /"+MergeFileName$
                CALL Merge(CtlString$)
                 Print CtlString$
                NoGood:
                END IF 'hGOButton%
                IF LwP%=hMergeListBox% THEN
                  'if doubleclick on filename. then delete filename
                  'if no filename, then do nothing
                  Notification%=Xp%(2)
                  IF Notification%=LBNDBLCLK% THEN
                'Get the selected item from ListBox
                'first get # lines in list box
                CALL StatusListBox(hMergeList%,l%,top%,w%,
                                    height%,_MaxWidth%,Index0%,
                                    NbLines%,_SelectedLine%,selcnt%)
                'SelectedLine% is the index of the selected 
                                'line or it is negative
                ThisLine$=SPACE$(32)
                CALL GetTextListBox(hMergeList%,SelectedLine%,ThisLine$,32)
                Print ThisLine$
                'then look for selected line
                'delete the selected line in merge list box
                IF ThisLine$ <> ""  THEN
                  CALL DeleteStringListBox(hMergeList%,SelectedLine%)
                END IF 'ThisLIne
                  END IF 'Notification
                END IF 'hMergeListBox -- to delete entries

                IF LwP%=hOKButton% THEN
                  'a file name has been entered
                  ' in the edit box with wildcards
                Size%=MAXSTR%
                EditFileName$=SPACE$(MAXSTR%)
                CALL GetCTLText(hEdit%,EditFileName$,Size%)
                PRINT EditFileName$
                'reset the edit box and then
                CALL ResetListBox(hList%)
                'add the directory or file name
                CALL AddDirListBox(hList%,EditFileName$,DDLREADWRITE%)
                END IF   'hOKButtion%
               END IF 'WMCOMMAND
            END IF 'LMsg%=0
              WEND 'Local Event Loop
              'Clear Everything
              CALL SetWindowModal(hWnd%,NOTMODAL%)
              CALL SetFocus(hSession%)
              CALL ResetListBox(hList%)
              CALL ResetListBox(hMergeList%)
              CALL SetCTLText(hEdit%,SPACE$(32))
              CALL SetCTLText(hMergeEdit%,SPACE$(32))
            END IF 'Merge Menu Item #400
            IF wP%=500 THEN Msg%=WMSYSCOMMAND%:wP%=SCCLOSE%
            IF wP%=600 THEN
             'Get an alphafile name and display the file in listbox--
             ' use same strategy as with the Count menu item without 
                     ' calling the count function
             Flags&=0
             Filter$="Filter 1 $.CNT $.BNT |*.CNT *.BNT"
             CustomFilter$="CFilter 1 $.CNT $.BNT |*.CNT *.BNT"
             FileTitle$=""
             Title$="Get Alpha File"
             FileName$=SPACE$(32)
             Directory$="C\"
             DExt$=""
             CALL GetOpenFileName(0,Filter$,CustomFilter$,0,_
              FileName$,64,_
              FileTitle$,Directory$,Title$,Flags&,_
              FileOffset%,FileExt%,DExt$,xError&)
              CALL ReadErrorNumber(ErrNum%)
             IF ErrNum%=0 THEN
              'Load Edit Box and Read .CNT file into ListBox
              Path$=FileName$
              CALL  SetSelEdit(hEdit%,0,32)'Don-t like absolute #s
              CALL ReplaceSelEdit(hEdit%,Path$)
              OPEN FileName$ for INPUT as #1
              LinCount%=0
              CALL ResetListBox(hList%)
              WHILE NOT EOF(1)
               LINE INPUT #1, Lin$
               CALL AddStringListBox(hList%,Lin$,lnum%)
               LinCount%=LinCount% + 1
              WEND
              CLOSE #1
              OutFileName$=FileName$
              CALL Statistics(OutFileName$)
             END IF
             IF ErrNum%=3 THEN
             END IF 'do nothing
            END IF 'Menu 600 GET
        END IF 'Msg WMCOMMAND
        ' The Msg=WMSYSCOMMAND messages come from the System Menu of 
        ' the current Session. wP contains the ID of the clicked Item.
        ' Event Loop ends when System Menu CLOSE Item is activated.
        IF Msg% = WMSYSCOMMAND% THEN
            IF wP% = SCCLOSE% THEN
            ' Opening a Message Box to ask the user if he really 
                        ' wants to end the program.
            Txt$  =  "Do you really want to end Fun With Words?"
            Caption$  =  "Goodbye Message"
            CALL MessageBox( 0, Txt$, Caption$, MBYESNO% OR MBTASKMODAL%, Code%)
            ' Code receives a value related to the user answer:
            ' IDYES%, or IDNO% in this case.
            IF Code%  =  IDYES% THEN GetMsg%  =  0
            END IF 'SCCLOSE
        END IF 'WMSYSCOMMAND
    ELSE
        ' If no message is available, the application releases its Time
        ' Slice to let other applications run.
        CALL ReleaseTimeSlice
    END IF
WEND 'Main Event Loop
CALL EndExecution
END
'******************* EXTERNAL SUBROUTINES ***************
'***** Slightly Modified Original DOS Code **************
$INCLUDE "merge.bas"
$INCLUDE "cnt.bas"
$INCLUDE "sortwin.bas"
$INCLUDE "statist.bas"
'********************************************************
' SUBROUTINES
'********************************************************
 ErrorHandler:
 E=ERRTEST
 'IF E=53 then Print "Input File Not Found":END
 Print "Error";E;" Occurred"
 Txt$  =  "Error="+STR$(E)+" Occurred at Line "+STR$(ERL)
 Caption$  =  "Error Box"
 CALL MessageBox( 0, Txt$, Caption$,MBYESNO% OR MBTASKMODAL%, Code%)
            ' Code receives a value related to the user answer:
            ' IDYES%, or IDNO% in this case.
            IF Code%  =  IDYES% THEN END
 END


Copyright © 1995, 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.