Copy Link
Add to Bookmark
Report

BASIC Softips Issue 01

eZine's profile picture
Published in 
BASIC Softips
 · 28 Dec 2019

  

(C)1990 Marquis Computing Inc., All rights reserved.
BASIC Softips(tm)
Entire contents copyrighted, 1990
Issue number 1
10/20/1990
5
TOC
TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TOC TO

Welcome to BASIC Softips for November 1990. Thank you downloading it and good
reading! Use the up/down arrows, pageup/pagedown, control pageup/pagedown
and home and end keys to navigate through an article or section. Press the
ALT key or click the mouse on the top line of the screen for a menu.

û Inside this issue get three FREE working programs!
û Tips to make your FOR...NEXT loops as much as 80% faster!
û Build a working, learning expert system
û Learn how to read Binary Coded Decimal
û Save 14K off an executable using PDS
û Explore the File Allocation Table & how DOS erases a file
û Use DOS interrupts to make your programs more powerful

This months segments include:

þ FORUM - introduction to BASIC Softips...why the author is C-ing red!
þ Q&A - a forum to answer your questions...How to make a program
run faster.
þ PROJECT OF THE MONTH - a multi-purpose input routine with commented
source code that can save you over 14K off the size of an EXE!
þ LONG TERM PROJECT - a working expert system, part one...Just what is
an expert system anyway? With source code for a working, learning
expert system!
þ The BASICS - how dos manages disk drives...File Allocation Tables,
FAT chains, DOS efficiency, erased files and more.
þ ADVANCED BASIC - part one...using interrupts to explore DOS. Includes
source code for program to read and display DOS boot sector & FAT using
BASIC.
þ BOOK OF THE MONTH - A review of ADVANCED MSDOS The Microsoft guide
for Assembly Language and C programmers.
þ SOFTWARE OF THE MONTH - A hands on review of A.J.S. Publishings dBASE
file support library, db/LIB.
END TOC
ADD 1
Your add could be here! Very, very competitive rates. Reach you're marketplace
using our unique, free and widely distributed computer based magazine.
Rates are as low as $5.00 per add! Contact Hank at 1.201.707.1316 for more
information. Call now!
Can you afford not to?
END ADD 1
ADD 2
This computer based magazine is free to over 400,000 thousand Bulletin Board
subscribers! As we are able to document downloads, our readership will
be determined, and then our add rates. But never fear! Our rates will always
be the best in any computer magazine!
BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC
END ADD 2
ADD 3
Marquis Computing Inc., Call 1.201.707.1316 and ask for Hank
þ Custom software programming
þ DOS, dBASE and file utilities
þ Training and education
þ Technical writing & editing
END ADD 3
ADD 4
To Subscribe to BASIC Softips for one year, send $12.00 with address to:
Marquis Computing, 135 Chestnut Street, Bridgewater NJ 08807. You will
receive your BASIC Softips on floppy diskette every month. Save downloading
charges! Subscribe today. With each issue get the latest READER too!
BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC Softips BASIC
END ADD 4
ADD 5
Do you want to write an article, author or sponsor a segment of BASIC Softips?
We are actively soliciting articles on topics of programming and computers.
Contact Marquis Computing, 135 Chestnut Street, Bridgewater NJ 08807. Or via
CompuServe at 76120,2413. Heres your chance!
END ADD 5
Q&A Q&A
Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A Q&A

The Q&A section is for any questions you have regarding anything, except
maybe the meaning of life.

Q: What can I do to optimize a program for speed?

A: We all want to know how to make our programs faster. There is no inherent
reason a BASIC program should run slower than any other program. Often
the reason our programs run slow is HOW we write them, not what language
we use. Look at the example below. A simple FOR...NEXT loop to search
through an array searching for a string.

FOR X# = 1 TO UBOUND(Array$) STEP 1
Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
IF INSTR(UCASE$(Array$), Search$) > 0 THEN
Element = X#
EXIT FOR
END IF
NEXT X#

Running this loop on my machine (a 80386 running with it's clock speed
slowed to 10MHZ) this loop averaged .69 seconds over 10 runs. Then I
began optimizing it. First, by moving the Search$ = UCASE$... line
outside of the loop. This line is always the same, so why leave it
inside the loop where we lose speed performing it each time? The code
below averaged .46 seconds over 10 runs. A savings of an 33%! Move all
non variant code to OUTSIDE of the loop.

Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
FOR X# = 1 TO UBOUND(Array$) STEP 1
IF INSTR(UCASE$(Array$), Search$) > 0 THEN
Element = X#
EXIT FOR
END IF
NEXT X#

Then I added a simple line, DEFINT A-Z, and changed the loop variable
X# to X. The code below ran in an amazing .08 seconds a whopping 83%
reduction in speed from the last change! What a difference changing the
loop variable made! Integers are the 'natural' numbers of BASIC - use
DEFINT A-Z at the top of each function, sub and module. And always use
an integer loop variable, if you can.

DEFINT A-Z

Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))
FOR X = 1 TO UBOUND(Array$) STEP 1
IF INSTR(UCASE$(Array$), Search$) > 0 THEN
Element = X
EXIT FOR
END IF
NEXT X

Next, I moved the UBOUND(Array$) out, as shown below. Interestingly
though, there was NO perceptible change in loop speed.

DEFINT A-Z

Count = UBOUND(Array$)
Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))

FOR X = 1 TO Count STEP 1
IF INSTR(UCASE$(Array$), Search$) > 0 THEN
Element = X
EXIT FOR
END IF
NEXT X

I removed the STEP 1 line, as we are incrementing by one, and that is
what BASIC FOR loops do by default. This brought about a .01 second
reduction in run time. Finally, I removed the X from the NEXT X line.
This brought about and additional .01 second reduction. I ended up
with the code shown below. It runs in an average of.06 seconds. All
the changed I made resulted in a loop running an incredible 82%
decrease in execution time.

DEFINT A-Z

Count = UBOUND(Array$)
Search$ = UCASE$(LTRIM$(RTRIM$(SearchText$)))

FOR X = 1 TO Count
IF INSTR(UCASE$(Array$), Search$) > 0 THEN
Element = X
EXIT FOR
END IF
NEXT

So, often it's the WAY we program, not WHAT we program in that gives us
slow programs. Imagine a whole application full of such loops or similar
WHILE...WEND, DO...LOOP functions. Take a close look at all those loops
to see if you can get some of these savings.


If any of you have similar experiments or questions, please send them in!

Please submit any questions, problems, corrections or
comments to :

Electronic (preferred):
Editor
BASIC Softips
CompuServe 76120, 2413

Paper (if you must):
Editor
BASIC Softips
135 Chestnut Street
Bridgewater NJ 08807


END Q&A Q&A
FORUM FORUM
FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM FORUM

C-ing red!

I am writing this newsletter because I feel slighted. Everywhere I look,
all I see is 'C'! People are always telling me (including on occasion MS
tech support) that certain things just can't be done in BASIC. Well, to me,
That's an open challenge! The whole concept of this magazine is to dispel
the vicious rumors that BASIC is a toy. To that end, over the next series
of these newsletters, we are going to blow the doors off of ANYONE who
thinks BASIC is a toy!

We will create a hyper text on-line help program, a multipurpose text entry
routine, a natural language interpreter, a file unerase utility-our own
version of NORTON utilities, a fully functional expert system, a database
engine capable of reading and writing dBASE and much, much more.

We will also cover, for newcomers, basic programming concepts such as
modules, structured programming, functions, making full use of the world of
DOS interrupts. You CAN do anything in BASIC that they can do in 'C'!

We will also explore the world of BASIC libraries available to us - AJS
Publishings dB/LIB, an excellent dBASE library. (By the way, how come no
dBASE magazine ever mentions the fact that there is a real alternative to
developing in dBASE or CLIPPER? I have written dBASE applications in
guess what - BASIC!) We will also examine Crescent softwares fine general
routine library QuickPac Professional and others.

My goal is nothing less than to make BASIC programmers stand up, and say
proudly "Yes, we used MS BASIC to develop that application." You see BASIC
is not a toy anymore. Recent revisions to BASIC make it on par with virtually
ant other language. The Professional Development Systems' inclusion on
overlay support, plus stub-files and using BASICs built-in runtime module
support allow you to create any application you can dream up. And in this
newsletter you will see how to do it!

Source Code you ask? Yes and lots of it. Example programs? Yes and lots of
them. The whole goal of this magazine is faster, smaller, more full featured
and more powerful programs - all using BASIC.

Your comments?
END FORUM FORUM
PROJECT OF THE MONTH PROJECT OF THE MONTH
PROJECT OF THE MONTH PROJECT OF THE MONTH PROJECT OF THE MONTH PROJECT OF THE

This months project is a multiple purpose text entry routine, written
entirely in BASIC. If will operate under QB or the PDS.

In general, this program allows you more flexibility than using BASICs
line input function. Although BASICs line input function offers full
string editing support, you can only exit it with a carriage return.
This program offers full editing support, but YOU define how to exit
the routine. In addition, you can specify if the input will be character
or numeric, if the string returned will be upper case or lower case and
the color of the input. In the future we are going to expand this routine
to give it a mask. This mask will let you return formatted strings from
the user, for example Social Security numbers, phone numbers and more.

As an added benefit, using this routine, and linking out full editing
support using the PDS stub file NOEDIT.OBJ results in a savings of
over 14,000 bytes of the size of a compiled executable! More powerful
and smaller. That's what it's all about!

Use the Cut segment command from the main utilities menu to save this
file to disk. Give it a name like EDITOR1.BAS so you can keep them
straight each month as we add features. When you load this into BASIC,
delete all of the text lines above. The program starts immediately below.

'Start of program-------------------------------------------------------
'
'(C)Copyright 1990 Marquis Computing Inc. All rights reserved.
'You may use this program for anything or any purpose including inclusion
'into programs you write BUT you cannot sell this source code. Written by
'Hank Marquis. revised 9/8/90.

DEFINT A-Z
DECLARE SUB LineInput (Msg$, code$, Caps, Num, row, Col, Fore, back)

'you program code here
' .
' .

Msg$ = "Enter your name:" 'prompt string
Code$ = CHR$(27) + CHR$(13) 'exit on escape or enter
Caps = 0 'do not force to capitals
Num = 0 'do not force numeric input
Row = 5 'print prompt on row 5
Col = 5 'start printing ar column 5
fore = 7 'use white for foreground
back = 0 'use black as background

LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back 'make the call

' .
' .
'process Msg$ returned from user here
'


END

DEFINT A-Z
SUB LineInput (Msg$, code$, Caps, Num, row, Col, Fore, back)

'This sub routine is used to write a prompt string, and then accept
' input. By setting variables which are defined below you have
' control over where the editing happens and how.
'
'All standard editing keys are supported :
' arrow left, arrow right = move one character left or right
' control arrow left, arrow right = move one word left or right
' home = jump to beginning of line
' end = jump to end of line
' backspace = delete the character to the left of the cursor
' delete = remove the character at the cursor
'
'You may choose if the user will enter numbers or letters or
' if the input string will be capitalized. By setting Msg$ to "",
' no prompt string is presented, allowing use of LineInput to get
' single characters.
'
'
'Msg$ = on input the prompt message to be displayed
' = on exit the user entered string
'
'Code$ = a concatenated string containing the key strokes to exit the
' sub routine. Example :
' Code$ = CHR$(13) + CHR$(27) 'sets exit code to the escape key or
' ' the enter key
'
'Caps = force capitalization flag. When Caps=1 the string is forced to
' all capitals
'
'Num = force numeric input flag. When Num=-1 the string is forced to
' be a numeric input. When Num is > 0, Num characters are input
' and then the routine is exited. Use this is determine the
' quantity of characters that a user can input
'
'Row = Row to position prompt string on
'Col = Column to position prompt string at
'
'
'here are some examples of using line input
'
' example #1 : print a prompt string and enter a string
'
' Msg$ = "Enter your name:" 'prompt string
' Code$ = CHR$(27) + CHR$(13) 'exit on escape or enter
' Caps = 0 'do not force to capitals
' Num = 0 'do not force numeric input
' Row = 5 'print prompt on row 5
' Col = 5 'start printing ar column 5
' fore = 7 'use white for foreground
' back = 0 'use black as background
' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
'
' example #2 : print a prompt string and a return a number
'
' Msg$ = "Enter birthday:" 'prompt string
' Code$ = CHR$(27) + CHR$(13) 'exit on escape or enter
' Caps = 0 'do not force to capitals
' Num = -1 'force numeric input
' Row = 5 'print prompt on row 5
' Col = 5 'start printing ar column 5
' fore = 7 'use white for foreground
' back = 0 'use black as background
' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
'
' example #3 : print a prompt and a return a string 4 characters long
'
' Msg$ = "Enter address:" 'prompt string
' Code$ = CHR$(27) + CHR$(13) 'exit on escape or enter
' Caps = 0 'do not force to capitals
' Num = -1 'force numeric input
' Row = 5 'print prompt on row 5
' Col = 5 'start printing ar column 5
' fore = 7 'use white for foreground
' back = 0 'use black as background
' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
'
' example #4 : suppress prompt and a return a single capital character
'
' Msg$ = "" 'prompt string
' Code$ = "" 'exit on any key
' Caps = 0 'do not force to capitals
' Num = 1 'exit on one character
' Row = 5 'print prompt on row 5
' Col = 5 'start printing ar column 5
' fore = 7 'use white for foreground
' back = 0 'use black as background
' LineInput Msg$, Code$, Caps, Num, Row, Col, Fore, Back
'
'
'Some things you could add are...
'1) you could add a mask to the call like this:
' LineInput (Mask$, Msg$, Code$, Caps, Num, Row, Col, Fore, Back)
' where mask is a template like ...-...-... for phones or
' ...-..-.... for Social Security numbers etc.. To do this you would
' need to add some code in the section where the string is printed - but
' don't forget to add some code in the editing sections to jump over the
' mask!
'
'2) You could have BASIC format the numeric string input by using the
' PRINT USING command. You could also put this in the printing section
' like this maybe..
'
' PrintString$ = ""
' FOR X = 1 TO LEN(text$)
' PrintString$ = PrintString$ + "#"
' IF X MOD 3 = 0 THEN PrintString$ = PrintString$ + ","
' NEXT
' PRINT USING PrintString; VAL(text$)
'
'-----------------------------------------------------------------------

'The main sub routine starts here.
' here we set up our variables and get ready to edit a string

COLOR Fore, back 'set the colors
Msg$ = LTRIM$(RTRIM$(Msg$)) 'use LRTRIM to remove any blanks from the
' left of the message string

blank$ = SPACE$(1) 'make a blank line for use later on

LOCATE row, Col, 1, 0, 7 'position the prompt string
PRINT Msg$; 'print the prompt string message

CurCol = LEN(Msg$) + 6 'set our position markers
Col = CurCol ' " " " "

StartScan = 7 'set for "insert cursor"
StopScan = 7 'set for "insert cursor"

ValidNums$ = "0123456789" 'build a string of valid numbers which
' can be entered

'------------
'here we do the actual editing of the string
'this do...loop is used until a key in the Code$ string is entered,
' then the loop is exited.
DO
Ky$ = INKEY$ 'get a key from BASIC
IF LEN(Ky$) > 0 THEN 'the user hit a key

IF Caps THEN Ky$ = UCASE$(Ky$) 'set to caps if Caps flag set to 1
'Below, we make the value negative for extended key strokes
' but you could make them something else. Here we convert the
' key into ASC code. This is needed because BASIC returns
' a keystroke of 1 or 2 characters. This is an easy way to process
' extended keys like arrows, pageup, function keys and ALT & CTRL
' keys.

IF LEN(Ky$) > 1 THEN 'this is an extended key code
Ky = ASC(RIGHT$(Ky$, 1)) * -1
ELSEIF LEN(Ky$) = 1 THEN 'this is a normal key
Ky = ASC(RIGHT$(Ky$, 1))
END IF 'end of...converting INKEY to ASC()

IF Ky > 0 AND Num = -1 THEN
IF INSTR(code$, CHR$(Ky)) = 0 AND INSTR(ValidNums$, Ky$) = 0 THEN
'if this key isn't an exit key AND
'we are checking for numbers AND
'if this is NOT a number THEN
BEEP 'user did not enter a number
Ky = 255 'set Ky to null so nothing happens
END IF 'end of...not a number
END IF 'end of...checking for numbers


'This select case block is where we determine the type of key
' entered, and then process it accordingly.

SELECT CASE Ky

CASE 8 'back space
'Back space removes the character to the LEFT of the
' cursor. Delete removes the current character.
IF LEN(LTRIM$(RTRIM$(text$))) THEN
CurCol = CurCol - 1
F$ = LEFT$(text$, (CurCol - Col))
L$ = RIGHT$(text$, LEN(text$) - (CurCol - Col) - 1)
text$ = F$ + L$
changed = 1
END IF 'if procesable text$

CASE 32 TO 125 ' the basic keyboard alphabet
'If we are inserting then break the sting into two sub
' strings called F$ - first and L$ - last. We break the string
' right at the current cursor position. Then, if we are inserting
' we add the new character Ky$ to the front off L$.
' for example :
' This is+sample string. (note the location of the cursor)
' F$ = This is
' L$ = sample string
' Ky$ = a
' L$ = "a" + "sample string" or "a sample string"
' Then we combine the parts,
' This is a sample string.
IF Inserting THEN
F$ = LEFT$(text$, (CurCol - Col))
L$ = Ky$ + RIGHT$(text$, LEN(text$) - (CurCol - Col))
text$ = F$ + L$
CurCol = CurCol + 1
changed = 1 'set the changed flag to print it
ELSE 'we are overstriking
text$ = text$ + SPACE$(1) 'make room for the next character
CurCol = CurCol + 1 'bump up the position in the string
MID$(text$, CurCol - Col) = Ky$ 'set the string to the character
changed = 1 'set the changed flag to print it
END IF 'insert/overstrike options

CASE -71 'home
CurCol = Col
LOCATE row, CurCol, 1, StartScan, StopScan

CASE -75 'left arrow
CurCol = POS(0) - 1
IF CurCol < Col THEN CurCol = LEN(text$) + Col
LOCATE row, CurCol, 1, StartScan, StopScan

CASE -77 'right arrow
'POS(0) returns from BASIC the current horizontal cursor position
' we add 1 to it to move the cursor position 1 to the right.
' Using LOCATE positions the cursor. If we had used the Changed
' variable to re-print the line, it would result in some
' flickering of the screen.
CurCol = POS(0) + 1
IF CurCol > LEN(text$) + Col THEN CurCol = Col
LOCATE row, CurCol, 1, StartScan, StopScan

CASE -79 'end
'The end of the editing box is the location of the start of
' the text string + the length of the text string$
CurCol = LEN(text$) + Col
LOCATE row, CurCol, 1, StartScan, StopScan

CASE -83 'delete
'Delete is similar to backspace but also different. Delete
' pulls the rest of the string toward the left as it removes
' the character at the cursor.

textwid = LEN(LTRIM$(RTRIM$(text$))) 'get length of text string

IF textwid AND LEN(text$) - (CurCol - Col) - 2 > -1 THEN
'if there is a character to the right of the cursor is indicated
' by LEN(text$) - (CurCol - Col) - 2 > -1
CurCol = CurCol - 1 'move cursor to the left 1 space
F$ = LEFT$(text$, (CurCol - Col) + 1) 'break the string in half
'note that below, we take LEN(text$)...-2. The -2 is to
' get rid of the character at the cursor PLUS pull the
' end of the rest of L$ to the left.
L$ = RIGHT$(text$, LEN(text$) - (CurCol - Col) - 2)
CurCol = CurCol + 1 'move the cursor to the new character
text$ = F$ + L$ 're-combine the string
changed = 1 'set to re-print the string

ELSE 'nothing to delete!
BEEP
END IF 'if processable text$

CASE -82 'insert
'This is a toggle between inserting and overstriking.
IF Inserting = 0 THEN
Inserting = 1
StartScan = 0 'this puts the cursor marker on the bottom line
ELSE
Inserting = 0
StartScan = 7 'this puts the cursor marker on the top line
END IF
changed = 1

CASE -115 'ctrl left
'start at the current cursor position and count BACK until the
' first space is found, then set the position of the space
' as the new current cursor position. Setting Changed = 1
' forces a re-print of the string, hence updating the display
FOR X = CurCol - 1 TO Col + 1 STEP -1
IF MID$(text$, X - Col, 1) = CHR$(32) THEN
CurCol = X
changed = 1
EXIT FOR
END IF
NEXT

CASE -116 'ctrl right
'start at the current cursor position and count UP until the
' first space is found, then set the position of the space
' as the new current cursor position. Setting Changed = 1
' forces a re-print of the string, hence updating the display
FOR X = (CurCol - Col) + 1 TO (LEN(text$) + Col)
IF MID$(text$, X, 1) = CHR$(32) THEN
CurCol = X + Col
changed = 1
EXIT FOR
END IF
NEXT

END SELECT
IF changed THEN
'Changed is used to indicate that we made some change in the string
' or the cursor position. If Changed = 1 then we want to re-print
' the string.
changed = 0 'reset changed flag to 0

LOCATE row, Col, 0 'locate where the text Starts
PRINT blank$; 'print a blank line to erase present line

LOCATE row, Col, 0 'locate where the text Starts
PRINT text$; 'print the text line
blank$ = SPACE$(LEN(text$)) 'reset the blank line for next time
LOCATE row, CurCol, 1, StartScan, StopScan 'position the cursor
END IF 'end of ...if changed

END IF 'end of ...user hit a key structure

IF Ky > 0 THEN
IF Num > 0 AND LEN(text$) >= Num OR INSTR(Code$, CHR$(Ky)) > 0 THEN
'if we have entered Num characters OR
' if an exit key was pressed
' then exit the loop
EXIT DO
END IF 'end of...if user hit exit key or length of string limit
END IF 'end of...if this is a normal key

LOOP

'------------
'this is the end of the subroutine

Msg$ = LTRIM$(RTRIM$(text$)) 'remove any leading or trailing blanks

code$ = Ky$ 'set the Code$ variable to the last key
' entered so that you can tell what key
' the user selected to exit the routine.

'This is handy when you give the user
' a choice of two inputs and you want to
' know which one was selected.

END SUB


END PROJECT OF THE MONTH PROJECT OF THE MONTH

LONG TERM LONG TERM
LONG TERM PROJECT LONG TERM PROJECT LONG TERM PROJECT LONG TERM PROJECT LONG T

Over the next few issues we will create a working, full featured expert
system. Our expert will be able to do anything we want it to. Given
a few examples it will be able to astound and amaze you and your friends.
Then you can start adding artificial intelligence to your programs - and
astound your users!
The expert we will build will be of a type known as a forward chaining,
parallel system. It will be a neural network. After we program it, we
will give it some examples, and it will actually learn from its mistakes
and get better and better as you use it. This wont be a "toy" either. Once
we get done with it, I'll show you how to build in hooks for dBASE or Lotus
so that you can give your expert applications real teeth! Sound interesting?
Lets go! But first we must cover some basics of expert systems. The following
article covers the basic operations and acronyms of what we are going to be
doing.

Just what is an Expert System?

An expert system is a program which mimics a human expert in a given field.
Expert systems usually focus in a narrow area of expertise. Their area of
knowledge is known as a Domain Of Enquiry. Specialized knowledge is captured
and stored in a Knowledge Base. In our expert, knowledge will be entered by
you as you build it. Most expert systems consists of stated Rules. A rule is
the chain of events or actions which will induce the expert to come to some
conclusion. In our expert, the rules will be stored in an array. The
Knowledge Base may shape these rules, allowing the expert system to make
deductions. These rules are typically not variable, except by a programming
effort referred to as Knowledge Engineering. Artificial Intelligence (AI)
refers to the entire field of study - like physics. An expert system applies
rules to its Knowledge Base and provides expert opinions. Well designed
expert systems give performance equal to or better than their human
counterparts. They do so in a manner which is easy, fast, inexpensive and
consistent. Well designed expert systems are also easy to use. They require
no specialized actions on the part of the user. They allow users the benefit
of not having to be an expert in the field that the expert is. Better still,
some even give an explanation as to why and how it came to a conclusion.

How do expert systems get to be an expert?

Most AI packages are "shells". A shell is a basic system which the user must
program with both it's expertise and user interface. The user designs the
layout of the system and then programs in the knowledge. Expert systems are
provided with their knowledge. This knowledge is stored in rules. Most expert
systems follow a rules based approach. In these systems the conclusions are
predefined in a series of nested rules statements. For example :

Rule 1:
IF : symptom is intermittent bad data
AND : facility is analog
AND : protocol is ASCII
AND : application is dial-up modem
THEN : problem is line noise hits

That looks a lot like BASIC and it is similar. Should the above rule prove
faulty with usage, it is changed. The Knowledge Engineer is the programmer
who would make the changes to the rules or modify the shell. Most expert
system shell systems have no method of directly modifying their own rules. If
there was a method for a program to modify itself, it could do so without
requiring the expense and time of a knowledge engineer. While, in fact, self
learning systems are somewhat rare, they do exist. A learning expert system
has a built in mechanism to modify its own rules based on its performance
feedback. That is the expert system can modify the manner it uses based on
wether the experts operation was right or wrong. If the expert was wrong, it
could modify the way it came to its deduction to reflect the real or desired
outcome it could learn from experience. A neural network is an expert system
which learns by example. You give it an outcome, along with the inputs which
should give that outcome, and the expert builds its own rules. It mimics the
actual process of the human mind - building its own pathways of connectivity.
By repetitively showing the neural network examples of the desired result,
the expert learns from experience. This is the process which our expert will
follow, learning by experience.

What is a good application for an expert system?

Good applications for an expert system are any operations which are
essentially a matching of multiple inputs to certain outputs or operations.
Obviously, the human expert operates this way. The human expert observes the
symptoms, the application or operation and then, based on experience,
deduces the most likely cause. And that is the most important aspect.
Human experts operate based on experience. Who would deny that a more
experienced technician or professional makes better, more accurate decision
than an inexperienced one? Likewise, for an expert system to be reliable,
there has to be some history of past occurrences. Expert systems do not
produce original thought. They simply relate one or more given conditions to
a given result. Virtually any operation where there are certain associated
inputs which typically result in the same output are good applications for
an expert system. Some past expert systems have included:

MYCIN - prescribing mediations,
PROSPECTOR - determining chance of finding precious metals,
DENDRAL - determining chemical structures and
PUFF - breathing disorders.

Notice how they all focus in some well defined area. Each also mimics an
expert which is able to deduce the outcome based on asking some questions or
observing some result. For example, PUFF, the expert system on breathing
disorders mimics a medical specialist in respiratory ailments. Observations
the Doctor might make are :
This is a female.
She is somewhat pale.
She is complaining of shortness of breath.
Based on these observations, questions the Doctor might ask are:
Does your chest feel tight?
Does you family have a history of asthma?
These observations, in conjunction with the questions asked, gather data and
build relationships which make an expert opinion, in this case a medical
diagnosis. Furthermore, this process could be for any field. The more complex
the item in question the larger the possible problems. The more complex the
field, the longer it takes to learn the reasoning process. Doctors go to
school for years to learn the Knowledge base of medical data then they learn
by applying that knowledge base in real situations. So good applications for
an expert system are those which:

1) Have an outcome which is deducible based on some inputs,
2) Have many variables and many possible results making for very complex
deductions,
3) Have many relationships between and among the input variables and
outputs,
4) Require years of study or experience to master,
5) Have fairly stable and consistent inputs or symptoms,
6) Posses an existent base of knowledge upon which to learn from and
7) Be an application valuable enough to warrant the benefits of an expert
system.

Ok. Enough about what an expert is, how do they work? Well, essentially, all
the expert does is match patterns. Our expert will take a various amount of
inputs and create a unique rule for each possible set of inputs. Then, when
actually processing, it will compare the inputs against all the possible
masks, choosing as the answer the closest match. This is referred to as fuzzy
logic and is a powerful tool. The expert can even guess - pretty accurately!

Out expert system, at this stage of evolution, will be menu driven. It
firsts prompts for the total number of variable and results. The variables
are the questions is will ask, the results are the answers it will give.
To use the expert, think of some group of things or some problem that
you will want the expert to differentiate between. This can by anything
from why your car wont start, to medical diagnosis to psychology. Keep
this in mind when entering the variables. Phrase each variable in the form
of an attribute - something the expected result would posses, which describes
it. For example, if one of your results is to be a CAR then a variable
might be TIRES or WINDSHIELD. Keep all you dialog similar.

After you select option 3, run expert, the expert system will prompt
for each variable whether it is true or false. You must have a desired
answer in mind when answering these prompts. This is because at this
early stage the expert knows nothing! You need to train it first. We
do this by you thinking of one of the results, then answering the
variable prompts for that response. Initially the expert will be wrong.
But as you give it examples of each response, the expert begins to learn.
Finally, when fully trained, it will have learned to tell each result
from the answers to the questions it asks. After each run, select option
1, display rules. What you will see is a matrix of the results to variables.
As you use the expert, and it learns, this matrix will change. You will
see positive numbers for true conditions and negative numbers for false
conditions.

While the expert in this stage of development is not directly usable in
another program, think about what it can do. Given some set of inputs and
outputs, and experience, the expert can accurately make deductions. And
if is wrong it can modify itself to induce corrective actions. Now think
about using such an expert in conjunction with a database or a spreadsheet.
You begin to understand the power. For example you could build a credit
risk assessment expert linked to a database, or a diagnostic system to
recommend repair activities. The sky is the limit.

Next month we are going to add a new feature known as NODES. This will
allow our expert to use it's deductions as inputs to another deduction.
This addition will allow our expert to analyze a situation, determine a
course of action and then recommend another course of action based upon
it's first deduction.

The code below is for a fully functional, working expert system. Take a
look at it. Each month we will add new parts, including a save to disk
option, and more. For now though it is fully functional and is able to learn
from its experience.
Use the Cut segment command from the main utilities menu to save this
file to disk. Give it a name like EXPERT1.BAS so you can keep them
straight each month. When you load this into BASIC, delete all of the
text lines above. The program starts immediately below.

' ------------------------------------------------------------------
'
'(C)Copyright 1990 Marquis Computing Inc. All rights reserved.
'You may use this program for anything or any purpose including inclusion
'into programs you write BUT you cannot sell this source code. Written by
'Hank Marquis.

DEFINT A-Z
CLS

' To make this a little easier for you, below I have stated the name &
' function of each array and variable .
'
' VARIABLE NAME FUNCTION
' -------- ---- --------
' MV NUMBER OF VARIABLES THE MAX NUMBER OF VARIABLES
' MR NUMBER OF RESULTS THE MAX NUMBER OF RESULTS
' HI HIGHEST RESULT POINTS TO THE MOST LIKELY NR()
' D HIGHEST RESULT HOLDS LAST BEST GUESS
'
' NR$(X) NUMBER OF RESULTS HOLDS THE CURRENT RESULT FOR
' R(X,Y) RULE ARRAY HOLDS THE RULE FOR EACH (X,Y)
' D(D) DECISION ARRAY HOLDS THE CURRENT BEST GUESS
' NV(X) NUMBER OF VARIABLES POINTS TO THE VARIABLE FOR CASE
' NV$(X) RESULT REGISTER FOR (X,Y) HOLDS THE ACTUAL
' RESULT.


'Setup expert definition-----
'
'To start use 4 variables
INPUT "How many variables "; MV

'To start use 3 results
INPUT "How many results "; MR
PRINT

REDIM NR$(MR), R(MV, MR), D(MR), NV(MV), NV$(MV)

'lets get the expert to learn to tell the difference between a plane
' a boat and a car. So for the 4 variables give the expert the following
' responses :
' Variable 1 name : wings
' Variable 2 name : sails
' Variable 3 name : tires
' Variable 4 name : motor
FOR I = 1 TO MV
PRINT "Variable"; I; "name : ";
LINE INPUT ; NV$(I)
PRINT
NEXT 'I
PRINT
'For the three responses give it
' Result 1 name : car
' Result 2 name : boat
' Result 3 name : plane

FOR I = 1 TO MR
PRINT "Result"; I; "name : ";
LINE INPUT ; NR$(I)
PRINT
NEXT 'I

'MENU SYSTEM -----

DO

'Select option 1 to display the rules the expert develops
' during its use
'Option 2 quits
'Option 3 starts the expert and asks you for the input regarding the
' four variables, then it makes its guess.

PRINT
PRINT " 1 - Display rules"
PRINT " 2 - Exit"
PRINT " 3 - Run expert"
X = VAL(UCASE$(INPUT$(1))) 'this is a quick way of getting one key
' stroke from BASIC

SELECT CASE X
CASE 1
'Prints a matrix of the relationship between inputs and outputs.
PRINT
FOR I = 1 TO MV
FOR J = 1 TO MR
PRINT R(I, J); " ";
NEXT 'J
PRINT
NEXT 'I

CASE 2
'as implies, ends program!
END
CASE 3
CLS
D = 0 'set last best guess to 0
'this FOR...NEXT loop resets the expert for each question
FOR I = 1 TO MR
D(I) = 0
NEXT 'I
GOSUB Engine 'run the expert system engine
END SELECT
LOOP

END


Engine: '------------------------------------------------
'
'This is the main expert system code - called the inference engine
' 1) we get the user input
' 2) build a mask or rule
' 3) make a guess
' 4) and if wrong learn

'Get User Input------------------------------

FOR I = 1 TO MV 'REPEAT SUB FOR "MV" MAXIMUM NUMBER OF VARIABLES

'ASK ABOUT THE CURRENT VARIABLE

DO
PRINT "Is variable"; I; "("; NV$(I); ") [T]rue or [F]alse?"
YN$ = UCASE$(INPUT$(1))

SELECT CASE YN$
CASE "T"
'If the variable is true then set the array NV(I) to a 1 indicating
' that the result possess this attribute of NV(I)
NV(I) = 1
EXIT DO
CASE "F"
'If the variable is false then set the rules array to a 0 indicating
' that the result does NOT possess this attribute of NV(I)
NV(I) = 0
EXIT DO
END SELECT

LOOP

NEXT 'I


'Now NV() holds a mask which represents the characteristics of the
' given item, boat, plane or car. For each attribute which is true
' NV(x) = 1 and for each attribute which is false, NV(x) = 0.
'
'for example if you had answered the prompts thinking of a car then NV()
' looks like this:
' NV(1) = 0 NO - wings
' NV(2) = 0 NO - sails
' NV(3) = 1 YES - tires
' NV(4) = 1 YES - motor
'
'We know have an attribute mask of '0011' for car. Now lets see if the
' expert can learn to tell them apart.

'BUILD A RULE-------------------------------

FOR I = 1 TO MV 'REPEAT FOR MAXIMUM NUMBER OF VARIABLES
FOR J = 1 TO MR 'REPEAT FOR MAXIMUM NUMBER OF RESULTS

'Here is where we build a rule for this attempt. We multiply the
' experts memory which is array R() times the mask held in NV().
' If the mask attribute is 0 then the value held in R() is not
' added to the decision array D() for this response. If NV() holds a 1
' then D() has the value of R() added to it. In this way we are
' actually testing each possible result against the mask array NV()
'

D(J) = D(J) + NV(I) * R(I, J) 'PERFORM RULE

NEXT 'J
NEXT 'I

' Now D() holds a value for each result. The value is greater for each
' attribute of the item for which NV() was a 1. The highest value of D()
' is the best, most likely answer for the given responses held in the
' mask array NV()

'MAKE AN EDUCATED GUESS! ----------------------------------------------

FOR I = 1 TO MR 'REPEAT FOR MAXIMUM NUMBER OF RESULTS

IF D(I) > D OR D(I) = D THEN 'IS D(X) = TO 1 OR -1 ?
D = D(I) 'IF 1 OR -1 THEN ASSIGN IT AS BEST GUESS
HI = I
END IF

NEXT 'I

'Now HI and D hold the number of the result which scored highest against
' the mask array NV(). We now have the experts guess as to which result
' is the answer - it is result NR$(HI)

'ASK IF IT'S A CORRECT ASSUMPTION -------------------------------------

PRINT
PRINT "Is the answer "; NR$(HI); "? [Y/N]" 'lets see if we are right
a$ = UCASE$(INPUT$(1)) 'get a Y/N from user
IF a$ = "Y" THEN RETURN 'bug out - we are right!
'IF IT IS NOT CORRECT ADJUST THE RULES (LEARN) ------------------------
'The user did not indicate that this was the right answer, so it
' must be wrong. Get the right answer from the user.
PRINT
PRINT "Which result was it? [select from 1 to"; STR$(MR); "]"
FOR I = 1 TO MR 'DISPLAY ALL THE POSSIBLE RESULTS
PRINT I; "- "; NR$(I)
NEXT 'I

B = VAL(INPUT$(1)) 'GET THE USERS RESPONSE

'B now holds the CORRECT result number, which we just got from the
' user.
'For all the possible results, if the value of D() calculated above
' is greater or equal to the best guess D, then we need to reduce the
' value of this rule, as it was wrong.

FOR I = 1 TO MR
IF D(I) > D OR D(I) = D AND I <> B THEN
FOR J = 1 TO MV

'Remove the value of NV() from the rule array R(I,J) for this
' result. This has the effect of further separating the
' masks - making the expert more accurate. This is the actual
' learning feedback process.

R(J, I) = R(J, I) - NV(J)

NEXT 'J
END IF

NEXT 'I

FOR J = 1 TO MV
'As an extra measure, lets add the value of NV() to the correct
' result, giving us now a clear separation between the right
' result and the wrongly guessed result. The expert has now learned.

R(J, B) = R(J, B) + NV(J)

NEXT 'J

'FINISH----------------------

RETURN

'END OF EXPERT SYSTEM CODE-----------------------
END LONG TERM LONG TERM
THE BASICS THE BASICS
THE BASICS THE BASICS THE BASICS THE BASICS THE BASICS THE BASICS THE BASICS

In this section we will cover topics of interest to new or intermediate
users of BASIC. This month, the topic is DOS Disk Files, an introduction
to how DOS manages files.

Sectors & Clusters
When a file is written to disk in DOS, it is divided into pieces,
called sectors. Each sector is typically 512 bytes long. A byte
refers to the grouping of eight bits - 10001101 is a byte.
Several sectors are then combined into a unit called a cluster.
The number of sectors per cluster depend upon the size of the
disk. When a disk is formatted, this information is stamped on
the disk in a special location, called the boot sector or BIOS
Parameter Base. Each cluster represents a given amount of disk
space. Depending on disk type (floppy or hard) and disk size
the number of sectors per cluster in different.

cluster
+---------------------------------------+
| | | | |
| | | | |
| | | | |
| | | | |
| | | | |
+---------------------------------------+
+-------------------------------------> SECTORS


file storage
When a file is written to a disk, it is done so in blocks of
bytes. In fact is written a sector at a time. Even if the file
doesn't fill up a sector, it still uses an entire sectors worth of
storage space. And, in turn, using one sector out of a cluster
uses that entire cluster. For example, if a disk used 512 byte
sectors (which is what most IBM disks use) and we have 4 sectors
per cluster (again, common on most hard drives) then the minimum
disk space needed for any file is 512 bytes X 4 sectors/cluster
or 2048 bytes! If the file was a very small batch file of only 30
bytes, it would really use 2048 bytes of disk space.

cluster
+---------------------------------------+
|This is |systems. |part of |.........|
|how data | |the |.........|
|is stored|If the |cluster |.........|<-- wasted or slack
|on most |file only|the rest |.........| area
|disk |uses a |is waste |.........|
+---------------------------------------+
+-------------------------------------> SECTORS


Now it isn't really using all that space, it's just allotted that much space
due to the way that DOS organizes disks. When you use the DOS DIR
command to display a file, you see the actual size of the file -
not how much space it is really using. The method which DOS uses
to add to a file on a hard of floppy disk is determined by the
version of DOS which is being used. Early versions of DOS (before
DOS version 3.0) used any next available cluster when a file was
appended or added to. Later versions of DOS try to allocate new
clusters which are contiguous - or together in a row. It is
important to understand that as a file grows, the clusters which
hold its data are often not one after the other. As shown above,
as a file grows new clusters are added. If there are no more available
clusters which are contiguous, DOS jumps to the next open cluster.
This process is referred to a fragmentation.

Keeping track of file allocations
DOS employs a simple system of tracking the clusters in use by a
file. This system is comprised of two parts - the File
Allocation Table (FAT) and the directory entry. First, the
directory entry. You see part of a DOS directory entry when you
use the DOS DIR command to display files. The entry contains the
file name, its size, its attribute and its starting cluster
number. The directory entry containing the file name also holds
the status of the file. If the file is erased, then the first
character in the file name is the Greek letter omega. If the first
character in a file name is omega then DOS doesn't display its data.
DOS uses the starting cluster number of a files directory entry to
identify the first cluster which holds this files data. The
starting cluster number is converted into an offset which points
to another special region of the disk, referred to as the File
Allocation Table or FAT. The FAT comes right after the BIOS
Parameter Base of boot sector of a disk. It is a matrix or table
of numbers, called Cells. Each number points to a cluster on the
disk. There may be more than one FAT and often there are two.


cluster 0
+---------------------+
| BIOS Parameter Base |
| or the BOOT sector | A DOS disk is laid out
| | like this.
|---------------------|
| File Allocation |
| Tables (FATS) |
| |
|---------------------|
| ROOT Directory |
| |
|---------------------|
| File space |
| |

cluster n

Right after the FAT table(s) there is the root directory space. It is a fixed
amount of disk space given the task of storing files and sub-directory
entries. You can only have a limited amount of files and sub directories
in the root because the root directory size if fixed. Other directories and
their entries are stored as files in other clusters, randomly around the disk.
Each FAT cell has a number, known by its position in the
table, which represents its cluster. FAT cell 1 is for cluster number 1, FAT
cell 2 for cluster 2 etc.. Every cluster has an entry in the FAT, and each
FAT cell points to a cluster. In addition, each FAT cell can hold a value.
These values are 0, the number of another FAT cell or the hexidecimal number
FFF for a floppy or FFFF for a hard disk. If the FAT cell for a cluster
is set to 0, then this FAT cells cluster is not in use. If the FAT cell
contains a number of a legal cluster number, then the number is the next
cluster containing data for this file. If the number is hex FFF or FFFF
then this FAT cell cluster is the last cluster in the file.

Example File Allocation chain for file SAMPLE.TXT

FAT CELL1 FAT CELL2 FAT CELL3 FAT CELL4
+---------------------------------------+
| | | | |
| 2 | 4 | FFFF | FFFF |
| | | | |
+---------------------------------------+
| | | |
| | | |
| | | |
| | | +>FAT cell 4 holds the value 65535
| | | or HEX FFFF. For hard disks this
| | | means the end of the file.
| | |
| | +> FAT cell 3 is used by another file
| |
| +>FAT cell 2 points to FAT cell 4
|
+>FAT cell 1 points to FAT cell 2 as the next cell in the chain
for our example file.


In the above diagram, the FAT chain for our file is 1 - 2 - 4.
Each FAT cell points to a cluster, depending on an algorithm to
convert FAT cell addresses to clusters. To read the file DOS goes
along the FAT chain, reading each FAT cell, converting the FAT cell
address into a cluster, and then reading that cluster.


Erased files
When DOS erases a file, it simply deletes the FAT chain by writing 0
to all the cells of the file. Then DOS adds the character omega as the
first character of the file name.

Erased file SAMPLE.TXT old FAT chain

FAT CELL1 FAT CELL2 FAT CELL3 FAT CELL4
+---------------------------------------+
| | | | |
| 0 | 0 | 0 | 0 |
| | | | |
+---------------------------------------+
Erased files directory entry

file name attributes create date create time size starting cluster
åAMPLE.TXT HIDDEN 01-01-1990 12:00:00 8723 1212
|
+> this is the omega character - when in the first character
position of a files name, it means the file is deleted

You can't see it or modify once this happens. A very important fact - the
data in the disk sectors that were this file it still there. DOS does NOT
remove the data when a file is deleted. It just erases it's FAT chain and
changes the directory entry as shown above. It is then possible to UNERASE
a deleted file! To unerase a file, you need to change the files directory
entry, and rebuild the files FAT cell chain. Unfortunately, this isn't
always so easy. Erased files may be recovered - we will build the routines
and a file unerase program to do this in another issue. But think of this
- how do we know which FAT cell is for which file if each erased files FAT
cell is a zeroed out and not pointing to another cell!? Well, that's a
story for another day...Hank Marquis
END THE BASICS THE BASICS
ADVANCED BASIC ADVANCED BASIC
ADVANCED BASIC ADVANCED BASIC ADVANCED BASIC ADVANCED BASIC ADVANCED BASIC ADV

This segment is dedicated to an in-depth study and application of an advanced
programming topic. This month we are going to take a look at using interrupts
in QuickBASIC to call low level DOS functions directly.

Using interrupts offers all the power of DOS to your programs. With such
power comes a responsibility - using the wrong interrupt or specifying
invalid or wrong pointers can lose data or damage your hard disk! I always
fully test ANY operation which writes to disk on a floppy BEFORE I try it
on a hard disk and even then I always back up by hard disks first.

What is a BASIC interrupt? Well, this function of BASIC lets us directly
communicate with the CPU and all of its registers and flags. We are able
to load registers with values, tell the CPU to execute to some DOS function
and then read the results. This is a very powerful operation. You have
access to every DOS system call. There is almost nothing that cant be done
using these DOS system calls in part or whole.

Read the segment in this months BASICS. It discusses the File Allocation
Table and how DOS manages disks. Well, now lets use the BASIC CALL Interrupt
function to actually read the BPB - BIOS Parameter Base - of a disk. As we
are only going to read from the disk, this is a safe use of interrupts.
For those souls seeking a higher level than I can provide here, I urge you
to get a copy of this months book review - ADVANCED MSDOS: The Microsoft
guide for Assembly language and C (there they go again...) programmers.
Advanced MS-DOS covers all the DOS functions and how they are called. It
also has passable explanations of the BPB and other interesting sections.
First, just what is the BPB? The BPB resides just after the OEM name and
version bytes, which in turn is just after the JUMP byte. The JUMP byte
holds a command which tells the computer that the disk is formatted and
points to a region of disk space located after the BPB. The BOOT Sector
contains all the above.

Item byte offset length
into sector 0 in bytes
+---------------------+
| JUMP INSTRUCTION | 0 1
|---------------------|
| OEM NAME & VERSION | 4 8
| |

Start of BPB +---------------------+
| bytes/sector | 12 2
|---------------------|
| sectors/cluster | 14 1
|---------------------|
| reserved sectors | 15 2
|---------------------|
| number of FATS | 17 1
|---------------------|
| max. root entries | 18 2
|---------------------|
| total sectors | 20 2
|---------------------|
| media descriptor | 22 1
|---------------------|
| sectors/FAT | 23 2
|---------------------|
| sectors/disk track | 25 2
|---------------------|
| number disk heads | 27 2
|---------------------|
End of BPB | hidden sectors | 29 2
| |

+---------------------+
| bootstrap code |


The boot Sector is logical sector 0. The boot sector holds much information
but here we are only looking at the BPB. The BPB contains the information
as the physical structure of the disk under use. It holds information
to allow FAT calculations and hence read or write any file to the disk.
We are now going to write a routine which will get the BPB from DOS, using
the BASIC Call Interrupt routine and take a look into our own BPB.
Following is a sub routine which reads the BPB, returning all the
information it contains. By itself it isn't to much use - more interesting
than useful. But as we go ahead in other issues, this sub routine becomes
critical in building our unerase program!

The call is :

drive$ = "C"
GetDOSBoot drive$, BPB, Regs, Status
drive$ = the drive letter (A,B,C,D etc) we want to read
BPB = the DOS Boot type array (DBType)
Regs = the CPU register type array
Status = a flag which, if not zero, indicates some sort of error
occurred during the call.

The code below will ONLY work under QuickBASIC. The PDS uses a different
string memory management technique referred to as 'far strings'. Under a
system using far strings all pointer contain to parts - a SEGMENT and an
OFFSET. The segment points to a 64KB block of memory, the offset points to
a distinct area within that segment. In the code below, the SADD function
of QuickBASIC version 4.5 is used. It points to the offset only - QB4.5 does
not use far strings - all strings are in one, known segment. To modify
this program for use with the PDS you will need to also use the InteruptX
routine, not Interrupt. Under InteruptX you will need to use the DS register
as follows :

DS = segment of string
BX = offset of string

After the program, we can display the results. A complete program is below.
Here is the complete working program. Cut this out and load it into
BASIC, then run it. You will need to load the quick library QB.QLB that
came with QB. To load a quick library, start QuickBASIC as follows

QB /L QB

Use the Cut segment command from the main utilities menu to save this
file to disk. Give it a name like DOSBOOT1.BAS so you can keep them
straight each month. When you load this into BASIC, delete all of the
text lines above. The program starts immediately below.

'start of program---------------------------------------------------------
'
'(C)Copyright 1990 Marquis Computing Inc. All rights reserved.
'You may use this program for anything or any purpose including inclusion
'into programs you write BUT you cannot sell this source code. Written by
'Hank Marquis. revised 9/8/90.

DEFINT A-Z

DECLARE SUB Interrupt (IntToCall, Regs AS ANY, Regs AS ANY)
DECLARE SUB GetDOSBoot (drive$, BPB AS ANY, Regs AS ANY, Status%)

'To use the CALL Interrupt routine we first must build some TYPE arrays.
'QuickBASIC supports a Interrupt and InteruptX call, I have used the
'Interrupt here. The difference is that InteruptX gives access to all the
'registers and flags. We don't need them all, but we can build a type
'array with them anyway - always better to think of the future.

'Sometimes you will see reference to AX or AH or AL. These are the
' same register! What they mean are which byte of the 16 bit register
' we are talking about - AX is all sixteen bits, AH is the high order
' 8 bits and AL is the low order 8 bits. The same is true for BX, BH
' BL and the others.
TYPE RegType
'This type defines all 80XXX type registers, pointers and
' flags. We are not going to use all of them in this
' program, but it doesn't hurt to be complete.
AX AS INTEGER
BX AS INTEGER
CX AS INTEGER
DX AS INTEGER
BP AS INTEGER
SI AS INTEGER
DI AS INTEGER
FLAGS AS INTEGER
ES AS INTEGER
DS AS INTEGER
END TYPE

'Next we need to build a file info type array to hold all the DOS boot
' sector or BPB information that our sub routine will return.

TYPE DBType
'Each item in the BPB will be returned using this type. The names of
' the type elements are accurate descriptions of the BPB data they
' return.
OEM AS STRING * 8
BytesPerSector AS INTEGER
SectorsPerCluster AS INTEGER
ReservedSectors AS INTEGER
NumberOfFats AS INTEGER
RootEntries AS INTEGER
Sectors AS LONG
Clusters AS LONG
MediaType AS STRING * 45
SectorsPerFAT AS INTEGER
SectorsPerTrack AS INTEGER
NumberOfHeads AS INTEGER
NumberHiddenSectors AS INTEGER
StartOFRoot AS INTEGER
StartOFFAT AS LONG
StartOfData AS INTEGER
SizeOfDir AS INTEGER
SizeOfFile AS DOUBLE
END TYPE

'Now lets DIM the type arrays so that we can use them.
DIM BPB AS DBType
DIM Regs AS RegType

'this program reads the DOS boot sector or more accurately sector 0 of a
'drive and determines the disk file allocation system & capacities - this
'part of the drive is called the BIOS Parameter Block or BPB

COLOR 7, 0
CLS
PRINT "Read & display the BIOS Parameter Base (boot sector)"
PRINT "Select letter of drive to display [ABCDE etc]"

drive$ = UCASE$(INPUT$(1))

  
'pick a drive, any drive...
GetDOSBoot drive$, BPB, Regs, Status 'Get the Boot Sector & parse
' it up

IF Status THEN 'something bad happened
END 'end program
END IF

'This block of print commands is optional. It is put here to let you
' see the information returned by the GetDOSBoot sub routine.

PRINT
PRINT "Boot sector information for drive "; drive$; ":\"
PRINT STRING$(60, "-")
PRINT "DOS OEM label "; BPB.OEM
PRINT "Bytes Per Sector "; BPB.BytesPerSector
PRINT "Sectors/Cluster "; BPB.SectorsPerCluster
PRINT "Clusters ";
PRINT USING "##,###"; BPB.Clusters
PRINT "Reserved Sectors "; BPB.ReservedSectors
PRINT "Number FATS "; BPB.NumberOfFats
PRINT "Root Dir Entries "; BPB.RootEntries; "(maximum)"
PRINT "Sectors ";
PRINT USING "##,###"; BPB.Sectors&
PRINT "Media Descriptor "; BPB.MediaType
PRINT "Sectors Per FAT "; BPB.SectorsPerFAT
PRINT "Hidden Sectors "; BPB.NumberHiddenSectors
PRINT "Start of FAT "; "sector #"; BPB.StartOFFAT
PRINT "Start of data "; "sector #"; BPB.StartOfData
PRINT "Drive capacity ";
PRINT USING "###,###,###"; BPB.BytesPerSector * BPB.Sectors&;
PRINT " (bytes)"
PRINT "Sectors/track "; BPB.SectorsPerTrack
PRINT "Drive heads "; BPB.NumberOfHeads

END

DEFINT A-Z
SUB GetDOSBoot (drive$, BPB AS DBType, Regs AS RegType, Status)

'This sub reads the DOS BIOS Parameter Base (BPB) and determines
' the basic disk configuration of sectors, clusters, FATs, disk
' type, root entries, disk size etc..,

'-Setup error trap-------------------------------------------------------
Status = 1 'set error flag for error - guilty until proven innocent

'Convert drive into a number for DOS-------------------------------------
drive$ = LEFT$(drive$, 1) 'drive$ should be A,B,C etc.,
drive = ASC(UCASE$(drive$)) - 65 'change drive to a number where
' A = 1, B = 2 etc.,

'Set disk sector size----------------------------------------------------
'An IBM disk sector size is - 512 bytes. But what if we didn't know
' that? There is a DOS function call which can return the number of
' bytes per sector. It is Int &21H function &H36.
'
'You use it as follows:
'
' Regs.AX = &H3600 'function to call
' Regs.DX = Drive 'drive where 0=A, 1=B etc.,
' Interrupt &H21, Regs, Regs 'use DOS general interrupt &H21
'
'This call returns the following information:
'
' Regs.AX = sectors per cluster
' Regs.BX = number of available clusters
' Regs.CX = bytes per sector
' Regs.DX = cluster per drive
'
'Now this information is also in it's raw form in the BPB, so we are
' going to use what we find in the BPB, even though this call could
' find out some of the same information. We are just using this call
' to get the number of bytes per sector.
'-------------------------------------------------------------------

Regs.AX = &H3600 'function &H36 - get free disk space
Regs.DX = Drive + 1 'use drive entered + 1 because
' this function uses a slightly
' different syntax than INT &H25. &H36
' uses 0 for default, 1 for A etc.,
' &H25 uses 0 for A, 1 for B etc.,

Interrupt &H21, Regs, Regs 'call &H21 - general DOS functions

BytesInSector = Regs.CX 'Regs.CX now holds bytes per sector

IF Regs.CX = 0 THEN EXIT SUB 'something bad happened so boogy...

'Now BytesInSector is the size of a sector. We will use this to make
' a string exactly one sector long to receive the BPB from the next
' DOS call we make.

'Read DOS Base Pointer (BPB)---------------------------------------------

'Most interrupts added in DOS 3.0 and up, though not all, use a null
' terminated string for these calls. We do that in BASIC by appending
' CHR$(0) to the end of the string, as shown below. This is referred to
' to as ASCIIZ
'
'DOS interrupt &H25 is the DOS absolute read function. It will read
' as many sectors as you ask it for. We are going to read the whole
' boot sector - which is BytesInSector long - from above. We need to
' pre-fill a string to recieve the sector data first. That is why
' we needed to determine BytesInSector BEFORE calling this routine.
'
'We call it with the following parameters under QB 4.5
' AX = drive to read (0 = A etc.,)
' BX = pointer to pre-filled ASCIIZ string to receive disk information
' CX = quantity of sectors to read
' DX = absolute disk sector number to start reading from to read
'
'To use this routine under QBX you will need to make some changes. This
' is due to QBX use of far strings. You need to pass the segment as
' well the offset. You will need to use the SSEG() function.
'
' DS = segment of string or Regs.DS = SSEG(string$)
'
'
'----------------------------------------------------------------------

DOSBOOT$ = SPACE$(BytesInSector) + CHR$(0) 'ASCIIZ to receive BPB data
' prefilled to correct
' length with spaces.

Regs.AX = drive 'Set drive to get from

Regs.BX = SADD(DOSBOOT$) 'set BX to point to location
' in memory of DOSBOOT$

'using SADD() above points to the memory location of the string. Here we
' are not actually working on the string itself - we are working on it
' indirectly using what is referred to as a "pointer"

Regs.CX = 1 'read '1' sector
Regs.DX = 0 'start reading at sector
' '0' boot sector
Interrupt &H25, Regs, Regs 'call DOS-absolute read

IF DOSBOOT$ = SPACE$(BytesInSector) + CHR$(0) THEN EXIT SUB
'the above line is true if there was an error - so if there was
' an error lets boogy... Note that Status was preset to indicate
' an error - so the calling program knows that this call was
' unsuccessful by default.

'If we got here then DOSBOOT$ now holds the DOS boot record!

'Parse BPB info------------------------------------------------------------

'If we got here, then we had a successful read. Lets begin to
' parse out the boot record information contained in the string DOSBOOT$
' which holds a copy of the boot sector. We will use the MID$ function of
' BASIC to retrieve sub strings out of the Boot record strings DOSBOOT$.
' As follows :
'
' SubString = MID$(DOSBOOT$, Offset into boot sector, Bytes to fetch)
'
'In most DOS applications numbers are converted into characters, then
' written to disk. This is called Binary coded decimal. For example
' dBASE does this also. To convert such a character string back into a
' number, first read the string. Then break it into sub strings of
' one character each. Then, depending on which character is which,
' convert into a number as follows. Leftmost character first, times
' decreasing powers of 16, as shown below:
'
' stringtoconvert$ = "ABCD"
'
' byte1$ = MID$(stringtoconvert$, 1, 1)
' byte2$ = MID$(stringtoconvert$, 2, 1)
' byte3$ = MID$(stringtoconvert$, 3, 1)
' byte4$ = MID$(stringtoconvert$, 4, 1)
'
' byte4 = ASCII value of byte4$ * 4,294,967,296
' or
' byte4& = ASC(byte4$) * 4,294,967,296
'
' byte3 = ASCII value of byte3$ * 65,536
' byte2 = ASCII value of byte2$ * 256
' byte1 = ASCII value of byte1$ * 1
'
' then add them up:
'
' value& = byte4& + byte3& + byte2& + byte1&
'
'---------------------------------------------------------------

'Here we start actually reading out the values of the BPB into
' the BPB type array

'get oem info...
BPB.OEM = MID$(DOSBOOT$, 4, 8)

'figure bytes/sector.
Work$ = MID$(DOSBOOT$, 12, 2) 'get sub string
Byte1& = ASC(LEFT$(Work$, 1)) 'convert each character into ASCII
Byte2& = ASC(RIGHT$(Work$, 1)) ' " " " " "
BPB.BytesPerSector = (Byte2& * 256) + Byte1& 'convert into number

'figure sectors/fat cell (allocation unit)
Work$ = MID$(DOSBOOT$, 14, 1)
Byte1& = ASC(LEFT$(Work$, 1))
BPB.SectorsPerCluster = Byte1&

'figure reserved sectors
Work$ = MID$(DOSBOOT$, 15, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.ReservedSectors = (Byte2& * 256) + Byte1& - 1
'the above subtraction of 1 from the last line is an offset for a DOS
' oddity because BPB.ReservedSectors also shows the boot sector itself

'figure number of fats
Work$ = MID$(DOSBOOT$, 17, 1)
Byte1& = ASC(LEFT$(Work$, 1))
BPB.NumberOfFats = Byte1&

'figure maximum root directory entries
Work$ = MID$(DOSBOOT$, 18, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.RootEntries = (Byte2& * 256) + Byte1&
'figure number of sectors
Work$ = MID$(DOSBOOT$, 20, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.Sectors& = (Byte2& * 256) + Byte1&
'figure media type where it is stored in HEX format, so first we
' read out its bytes, convert to a number, then into HEX
Work$ = MID$(DOSBOOT$, 22, 1)
MediaType$ = HEX$(ASC(LEFT$(Work$, 1)))
Base$ = "[" + HEX$(ASC(LEFT$(Work$, 1))) + "H]"

SELECT CASE MediaType$
CASE "F8"
MediaDesc$ = " hard disk drive"
CASE ELSE
MediaDesc$ = " floppy disk drive"
END SELECT

BPB.MediaType = Base$ + MediaDesc$

'figure number of sectors/fat
Work$ = MID$(DOSBOOT$, 23, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.SectorsPerFAT = (Byte2& * 256) + Byte1&

'figure clusters
BPB.Clusters = BPB.Sectors& \ BPB.SectorsPerCluster

'figure sectors/disk track
Work$ = MID$(DOSBOOT$, 25, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.SectorsPerTrack = (Byte2& * 256) + Byte1&

'figure number of disk heads
Work$ = MID$(DOSBOOT$, 27, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.NumberOfHeads = (Byte2& * 256) + Byte1&

'figure number (if any) of hidden sectors
Work$ = MID$(DOSBOOT$, 29, 2)
Byte1& = ASC(LEFT$(Work$, 1))
Byte2& = ASC(RIGHT$(Work$, 1))
BPB.NumberHiddenSectors = (Byte2& * 256) + Byte1&

'------------------get FAT start address-------------------
'the FAT starts right after (boot_sector + reserved_sectors)
'so start of FAT is...0 + BPB.ReservedSectors + 1
BPB.StartOFFAT = BPB.ReservedSectors + 1 '+1 to offset -1 from DOS
' reserved sectors count

'------------------get root start address-------------------
'After the boot sector and the FAT comes the root directory
'Add 1 to account for the boot sector itself
BPB.StartOFRoot = 1 + (BPB.NumberOfFats * BPB.SectorsPerFAT)
'------------------get disk data start address-------------------
'After the boot sector, the FAT and the root directory, starts
'the actual disk space that we can use...

BootSector = 1
StartOfData = BootSector + (BPB.NumberOfFats * BPB.SectorsPerFAT)
StartOfData = StartOfData + ((BPB.RootEntries * 32) \ BPB.BytesPerSector)

BPB.StartOfData = StartOfData

'------------------end of routine -------------------
'if we got here then we had no errors so set error flag Status
' to show no errors. The Type array BPB now holds all the DOS
' boot sector information for display
'
Status = 0 'we made with no errors, set error status to 0 or none

END SUB

END ADVANCED BASIC ADVANCED BASIC
THE BOOK OF THE MONTH THE BOOK OF THE MONTH
THE BOOK OF THE MONTH THE BOOK OF THE MONTH THE BOOK OF THE MONTH THE BOOK OF

In this segment we review a book that has to do with programing. For November
the book reviewed is ADVANCED MSDOS The Microsoft guide for Assembly Language
and C programmers.

Author : Ray Duncan
Publisher : Microsoft Press
Dated : 1986
Cost : $22.95
Available : this copy bought at Software Etc.,

I needed to understand how DOS actually operates to write a program utility.
I write in BASIC PDS because I like the flexibility, so I went forth into
the book world seeking an in-depth DOS book featuring BASIC. And guess what?
It seems that no such book exists. In desperation and despondence I bought
Advanced MS DOS, hoping to glean enough about DOS to piece together my own
code.

Well, I was successful. The book is well endowed with examples, as the name
would suggest though, they are almost all in assembly language. Luckily
for me these examples are fully commented and explained in the text.
Once upon a time when dinosaurs called the 6502 roamed the earth, and was
king of it, I had played with assembler. But I am by no means expert in it.
What I got was a good introduction to DOS and assembly language. Now this
isn't bad, but it is not what I wanted.
The author constantly refers to CP/M, as if we all already knew all about
CP/M and wrote in it everyday. I guess in fairness the book was written
for assembly language programers. Many of whom I guess used to write in
CP/M. (Control Program Microcode, the predecessor of DOS written by Digital
Research, the same folks who give us GEM Desktop and other programs.)
Unfortunately, I never did write in CP/M so all of the examples relating to
CP/M were useless to me! What I did learn from this study was that DOS is
quite a bit a kludge of past systems. Operations and functions are often
carried out in several ways to make them CP/M 'ish or "compatible" for
re-writing code. I guess I now know what they say DOS is showing it's age.
For the stout of heart and those interested in the evolution of DOS this is
good reading.

But to get back to my application for a minute. The books does explain
well such concepts as directory entries, file management and memory
management. Although I found myself reading and re-reading the "simplified"
block structure of several functions. The real benefit to this book to me
though is its complete indexing of all DOS functions, with examples and
explanations. I found this to be of most value. Often, the examples in the
book under each heading, while written for assembly language, are directly
useable in BASIC using the Call Interrupt routine. At last, I had found my
answer - I just couldn't figure it out without reading it 20 times!
But, I did ultimately figure it out, and that's the point.

In any event, the books is easier to read, more informative and holds more
examples than the DOS Technical Reference from IBM. In summary, I found the
book hard to read, it assumed the reader knew too much. The examples often
said things like ..."use the directory entry to find..." without
indicating how one might go about finding the directory entry and then
processing it. This is where the reading of each section 20 times comes in.

I did find the book interesting with is genealogy of DOS and very thorough
explanation of exactly how DOS does anything it does. Also, as stated
earlier, the complete indexing of functions and DOS calls in invaluable.
As a side note, going through the amply commented examples given in assembly
language you get a good understanding of how assembly language operates.
And that is a valuable thing in itself.

I recommend this book to anyone who writes in BASIC and is trying to get
the most from DOS. A must for any programming using the CALL Interrupt or
InterruptX routines or a BASIC programmer writing your own DOS functions.

Hank Marquis
END THE BOOK OF THE MONTH THE BOOK OF THE MONTH
SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH
SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH SOFTWARE

In this segment, we review a software program, utility or add-on for BASIC.
This month, we take a look at the A.J.S. Publishing package db/LIB. An add
on library for letting BASIC programmers read, write, modify, create and
use dBASE III and dBASE III+ style databases and indexes.

Publisher : A.J.S. Publishing
Version tested : 2.0
Dated : 1990
Cost : $145.00 ($495.00 LAN version)
Available : Contact A.J.S. publishing at:
A.J.S. Publishing, Inc.
P.O. Box 83220
Los Angeles, CA 90083
phone : 213.215.9145

What is it? db/LIB is a set of routines which you include into programs
you write. These sub routines let you do any virtually any database
management function. You can read and write dBASE files, create dBASE files
and indexes and an entire host of other database file and index functions.

These routines are all written in assembly language and they are fast. I
compared a program written using dBASE and one written in BASIC. On every
account, the BASIC program was faster than dBASE itself! db/LIB indexed
faster, searched faster and also resulted in a smaller program.

Included with the package are 12 already written BASIC programs. Each
amply demonstrating some needed database function. You are free to 'cut and
paste' from these demos into your own applications. That is a real time
saver. Also built into db/LIB is a full featured expression evaluator.
Expressions make selecting data from a database easier. You build a string
using BASIC syntax to select a record or record. For example, "EQUIPMENT
.EQ. 'CODEX'" Then pass this to the expression evaluator. A routine then
creates a macro, which you use on subsequent calls to check against each
record. If the record matches the macro, the expression evaluator returns
a "T", if is does not, it returns a "F". And it too is really fast. I had
never used one before, and now I wonder how I ever got by without one.

The book offers decent information, although at times the examples and
lacking in depth and the writing is a bit 'high'. That is you read the
calls two or three times until you understand it. My biggest complaint
is not with the program - it is with the book. I used db/LIB for a
recent database project (written, by the way, in QuickBASIC 4.5). As
I was using the book everyday, it began to fall apart. I spoke with a
printer friend who told me that the type of binding used on the db/LIB
book was prone to falling apart. So far I haven't lost any pages, but
the book is in tatters.

The technical support from A.J.S. is fine. I never was put on hold, although
once in a while I had to re-dial several times. But I always got though
and got clear answers to my questions.

From a technical note, I tried using db/LIB for another project. This one
in the PDS. It turns out that db/LIB is not compatible with the PDS use of
far strings. A.J.S. says they are working on an upgrade. That's not too bad
as many add-on library vendors have to upgrade their product for far
strings. Relatedly, I then tried using db/LIB with QB.45, to build
a custom runtime module. The application had about 12 executable modules,
each needing part or all of the A.J.S. library. A perfect application for
a custom BASIC runtime module if I ever saw one. After the module was
compiled, the application seized up every time. During an investigation I
found out the assembly language calls (which is what db/LIB is written in)
must be true FAR CALLS. One phone call to A.J.S. cleared it up. The db/LIB
current package does not use FAR CALLS. The package for the PDS will. So I
guess if you want to use a custom runtime module, you won't be using db/LIB.
In summary, db/LIB lets BASIC programmers access the world of real database
management. It is a no nonsense, fast, efficient and economical package.
It's bevy of pre-written programs, each well written and commented are a
real plus. The expression evaluator is a real gift - once you realize the
power it offers, you find yourself using everywhere. If you are a BASIC
programmer, writing in QuickBASIC and need to read/write dBASE files then
db/LIB is for you. If you are writing programs and want to use a file
structure that is an industry standard, then db/LIB is the choice. Even
though I am sitting on the edge of my seat waiting for the far strings
upgrade, I still highly recommend db/LIB from A.J.S. Publishing.


END SOFTWARE OF THE MONTH SOFTWARE OF THE MONTH

loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT