'===========================================================================
' Subject: PATTERN MATCHER                    Date: 06-11-99 (17:03)       
'  Author: Donald R. Darden                   Code: PB                     
'  Origin: oldefoxx@earthlink.net           Packet: PB.ABC
'===========================================================================
$if 0
PATTERNS.BAS   by Donald R. Darden, (c) 1999
Offered as FREEWARE SNIPPIT CODE  -- Sample Code that may be of value in
creating higher-level processes.  The format is:

        DEMO CODE - calls the related sub or function

        SUB OR FUNCTION - demonstrates a particular feature
        The CODE within a SUB or FUNCTION consists of one or two sections:

           Section One:  A PowerBASIC approach to the problem

           Section Two (if present):  An enhanced ASM solution as well

           Note:  if Section Two is present, there will be timing code
           to demonstrate the relative speed of the two approaches.
           Since only one section is actually needed, you can comment
           out the unneeded section or delete it.  The advantage of the
           PowerBASIC section is that it is easily customizable.  The
           ASM section usually has the advantage of speed, and it also
           demonstrates some of the constructs of creating inline code
           in PowerBASIC.

           *** OF SPECIAL NOTE: *** When adding ASM (Assembler) code to
           PowerBASIC where you will be exchanging values between the
           Assembler registers and PowerBASIC variables, you MUST DEFINE
           all interger variables with a DEFINT a-z (or whatever leading
           letters are used) as being the default type INTEGER.  This is
           because PowerBASIC's inline assembler does not recognize any
           type except itergers, and you will have difficulties in passing
           a value with a mov <var>,ax, or a mov ax,<var> command.  You
           CAN reference other types, but you will have to use an offset
           pointer to the memory location and possibly add a constant like
           so:  mov ax,word ptr a[0] for the lower two bytes, and mov ax,
           word ptr a[2] for the upper two bytes.  In this case we are
           assuming that a is a LONG variable.  But the inline assembler
           only recognizes the string ($) identifier, specifically for
           passing the PowerBASIC string allocated handle for use with
           internal procedures (use F1 for HELP on these procedures).
           You cannot include other types, such as a%, a?, a!, or a#, and
           have the assembler accept them.  SO MAKE SURE YOU INCLUDE A
           DEFINT A-Z (or specific leading letter group for the veriables
           you intend to use with the inline assembler) if you decide to
           include inline assembler code in your program.

Concerning the measurement of time:  The timer updates approximately 18.2
times a second, meaning that time measurements are relatively course using
timer references via any BASIC version. The integer portion of the time only
updates once every second.  Due to the slight inaccuracies in the timing
mechanism, most documentation indicates that the update is every 1/18ths of a
second and lets it go at that.  The timer is reset at midnight, or by a
restart of the computer.  So to measure time, you have to note its initial
setting, which is what the sub STARTTIME does, and then calculate the
difference when you stop, which is what the function MARKTIME does.

Most processes are far too fast on a modern PC to get effective measurements
this way, and consequently, most serious programmers look for some other
method to mark the passage of time.  There are other processes that
can be supported by drivers or TSRs.  Some of the most common are:

(1)  Speed up the timer. This can have a major impact on how frequently
other chained processes are polled, and how much time they regards as having
elapsed.  It would normally make your realtimeclock (RTC) reference be off
as well, because it would update too fast.

(2)  Install a driver to monitor the video card's horizontal or vertical
retrace signals.  The vertical retrace usually ranges from 60 Hz (cycles
per second) to as high as 90 Hz, depending upon the mode of operation and
the type of video card installed.  The horizontal rate is hundreds to times
higher than that, based on the vertical resolution of the screen and the
vertical retrace rate, as well as whether it is in interlace mode, or for
better quality monitors, non-interlace (NI) mode.  For a non-interlace
mode that supports a 640x400 (horizontal to vertical) resolution in non-
interlace mode with a refresh rate to 72 Hz, the horizontal rate would be
72*400, or 28,880 horizontal retraces per second.  For an interlace mode,
the horizontal refresh rate would be exactly half that rate.  This is fast
enough to mean that a relatively slow processor could not adequately
interface between retraces.  It is also probably a lot faster than any
application really requires, except for software timing loops to test the
efficiency of code.  So most often, this method is limited to use with the
vertical retrace signal, which is still fast enough to have a person "see"
continuous activity without evident pauses.

(3)  A third method is a variation on the first:  Speed up the clock, then
tick off how many times it attempts to update before allowing the ticks to
filter through to other processes.  For instance, raise the tick rate to
91 ticks a second (five times normal), then only allow every 5th tick
through to the downstream process (back to 18.2 ticks per second for their
processes).  You could do this for any multiple of 18.2, but you should keep
the rate near the low end to prevent the results from overstimulating the
background activity in your machine.  Processes like this steal CPU cycles
away from other activities, so you want to use some judgement here.

(4)  There are also other means, some very creative, for establishing a
faster timer function for game updates and real time activities, but they
are not covered here.  This is just to indicate that if you want tighter or
more accurate timing measurements than BASIC generally provides, you will
have to look at adopting such techniques yourself -- but be warned:  they
often call for a lot of research into different computer environments and
a comparison of fast time to normal tick time to establish a usable ratio
to keep activities from being either too fast or too slow on a given PC.

If you want to master tricks like this, Read a programmers' reference
book on DOS for more information on how to access and use the video port
and/or BIOS/DOS-supported interrupts.  Or get comfortable with real-time
game development software.  The INT86 library that comes on the PBXtra
CDROM is also a great reference.

$endif

DECLARE FUNCTION GetStrLoc(BYVAL AllocHandle%) AS LONG
defint a-z        'REQUIRED for including inline assembler code
dim marker as single                  'we use this for keeping time
q$=chr$(34)
color 15,1
cls
?
?"PATTERNS.BAS, by Donald R. Darden, offered as FREEWARE, 1999
?
?" There is a program devoted to giving you some nifty functions and subs that
?" can be used in your own program development.  Basically, these demonstrate
?" different ways in which you can prepare text to be scanned in a Pattern
?" Matching fashion.  These are processes you can do in any BASIC, but have
?" been optimized for use in PowerBASIC.  You can build your own applications
?" around the examples here.
?
?" If you are not familiar with the idea of pattern matching, then suppose
?" I show you a pattern of "q$"99:99:99"q$", and say that the 9's could be any
?" digit, what is this?  You should be able to say, "q$"Why, that probably shows
?" an hour, minute, and second, right?"q$", and of course you would be right.
?
?" Then if I should you a group that was "q$" Aaa 99, 9999 "q$", and said that the
?" capital A represents an upper case letter, and the small a represents a lower
?" case letter, and everything except the 9's is unchanged, what is this?, you
?" might say, "q$"It's a date, right?"q$".  Now you understand pattern matching.
?
?" It's a technique used to find information within text or fields using computer
?" software by not having to look for every combination of specific data -- we
?" don't need to search for all combinations of Jan, Feb, Mar, Apr, May, Jun
?" Jul, Aug, Sep, Oct, Nov, Dec, and every possible day combination from 1 to
?" 31, as well as every year from 1980 to 2020, to identify a date field now .
sleep
cls
?
?" Of course there are variations that you have to allow for, such as also being
?" able to match on "q$" 9:99 aa "q$" which might be 4:05 pm, or something else
?" entirely.  And a "q$" AAA 9, 99 "q$" could also be a date reference.  So
?" you may have to break up your pattern matching efforts into smaller groups
?" to allow for this type of problem.  But then other patterns become possible
?" as well.  For instance, you could write a pattern creator that allows only
?" certain changes to happen such as for a date, it would correspond to "
?" "q$" AAA 9, 99"q$".  This might mean "q$"One or more spaces, followed by three or more
?" letters, followed by one or more digits, followed by a comma, followed by one
?" or more spaces, followed by two or more digits"q$".
?
?" As it happens, I first thought to challenge someone else to do this, but it
?" looked interesting and useful enough that I took an hour to work it out
?" myself. So you now have the benefit of another useful function which I
?" named DeltaPattern$() in this package as well.
?
?" There are a lot of details and efforts to compare ways to modify and use
?" the supplied routines, as well as an effort to show just how fast they
?" really are.  Unfortunately, timer accuracy is not a strong suit for PCs,
?" DOS, or BASIC, so the times used to attempt to show relative speed of the
?" different routines may not do much (for more information on how to get
?" better timing done in your program, scan the comments in this program that
?" have been included for off-line viewing).
sleep
cls
?
?" If you still don't understand quite how to use a pattern process, this
?" may help:  You must actually change the original text using the pattern
?" process, but you must also keep a copy of the original text to go back to
?" after you find the pattern, so that you can use the original data from
?" it.  The last example (sequences of characters) is particularly hard to
?" do, cause the 1-to-1 correlation in character position is changed. So
?" you probably would need an array to track positional changes between the
?" different start positions in the original and the pattern version,
?
?" Well, now we are ready to run the Demo.  This just takes the DESPACE$()
?" functions that are provided and attemps to compares the time it takes for
?" each different approach to get the job done.  One way involves Assembler
?" code, and when written efficiently, this should always be the fastest and
?" smallest executable approach.  But as you will see, variations that just
?" use PowerBASIC statements are also very fast, meaning you don't have to be
?" an assembler programmer to make this stuff happen.  You just have to think
?" the problem through and find an effective way to do the task in hand.
?
?" After DESPACE (de-space) is run each way, the results of the last run are
?" shown to demonstrate what it does.  Now there are several other functions
?" in PATTERNS that will allow you to try other things, or combinations of
?" things.  And of course, you can reuse the routines elsewhere, or just
?" study them to help you in your own future development efforts.
sleep
cls
?" Now when you are ready, press any key and the Demo of DESPACE$() will run.
sleep
a$="  This  is    a   test of    DESPACE    "
b$=despace$(a$)
?
?"Original line surrounded with double-quotes:"
?chr$(34)a$chr$(34)
?"Resulting line surrounded with double-quotes:"
?chr$(34)b$chr$(34)
sleep
cls
?
?"DEMO version of PATTERNS by Donald R. Darden, (c) 1999
a$="This is a test string for the Pattern process begun on "+date$+" "+time$
?
?a$
?
b$=pattern$(a$)
?
?"Results of test:"
?a$
?b$
sleep
cls
a$="Jan and Marge, now 12 and 13, at 2112 Jay Court, will give a joint birthday party" + _
   " on September 23, 1999."
b$=" AAA 9, 99"
?
?"Now a more sophisticated pattern matching tool:  DeltaPattern()
?
?" Looking for a possible date and position in a string using a variable-
?" length pattern specified by a second string, using DeltaPattern():
?
?" String to be searched:"
?q$a$q$
?
?" Date pattern being sought:  "q$b$q$
?

a=deltapattern(1,a$,b$)
if a=0 then
  ?"Could not find a valid date format in "a$
  end
end if
?"DeltaPattern returns a pointer to where the specified pattern starts:
?
?q$a$q$
locate csrlin,a mod 80+1
?chr$(179)
?tab(a+1)chr$(212)" right here"
sleep
cls
?
?"Patterns recognition is incredably important when it comes to generalizing
?"theories from sample data.  Now it should be evident that reversing the
?"process allows us to use the general case to quickly find the specific
?"information involved.  And as we find more specific cases that do not fit,
?"we extend the pattern, whereas when all data fits, we can refine the
?"pattern.  This is known as getting smarter.
?
?"This task of using patterns is one that computers are well suited to, but
?"most computer languages are not.  Next would be to teach computers how to
?"relate specific data samplings to each other and find the best patterns to
?"fit.  Then you have computers that are able to begin building their own
?"languages and tools, and even to postulate their own theories.  However,
?"computers are not conscious, meaning I do not forsee an era where they,
?"of their own violation, become rivals to living things, including Man.
?
?"My definition of a proof:  The process of taking an abstract conjuecture
?"and sucessfully subjecting it to the rigors of deductive reasoning.
?
?"Please note:  98% of this program is just stuff I added - comments, print
?"statements, notations, things to try and put this whole thing into some
?"sort of perspective and make it more useful to you.  Strip that out, and
?"what's left will fit into your own code quite nicely.  Good Luck!
end

Function pattern(aa$)static as string 'Converts AlphaNumerics to A, a, or 9
$if 0

PATTERN converts a string into specific symbols to simplify groups-of-
characters recognition.  For instance, guess what this group might
represent:  99/99/99.  Or this group:  Aaa 9, 9999.  Or this group:
9-999-999-9999.  Or even this group:  999-99-9999.  If you guessed date,
date, phone number, and social security number, then you can see where
much of essential data in our lives correspond to some understood format.
That is what pattern helps you find.  PATTERN converts A through Z to a
capital "A", lowercase "a" through "z" to a lowercase "a", and digits
0 through 9 to a "9".  Everything else remains the same.

$endif

'Section One:  The PowerBASIC version...
starttime "Testing PATTERN using PowerBASIC REPLACE Command..."
a$="012345678BCDEFGHIJKLMNOPQRSTUVWXYZbcdefghijklmnopqrstuvwxyz"
b$="999999999AAAAAAAAAAAAAAAAAAAAAAAAAaaaaaaaaaaaaaaaaaaaaaaaaa"
c$=aa$
replace any a$ with b$ in c$
function=c$
'------------------------ end of section one --------------------------------
a!=marktime

'============== Section Two:  The enhanced ASM version: =====================

starttime "Testing PATTERN using ASM approach..."
'--------------------------- end of section two -----------------------------
b!=marktime
a$=AsmPattern$(aa$)
?"Pattern results";
if a$<>c$ then ?" DO NOT";
?" Match."
if a!>b! then
  ?"PowerBASIC took longer."
elseif a!<b! then
  ?"ASM took longer."
else
  ?"Times were the same within the limits of the measurements done."
end if
?"Inherantly, the ASM approach should be faster for very large strings or when
?"handling a large number of strings."
end function                   'and we're done

function AsmPattern(aa$)static as string
local a$
a$=aa$
! mov  ax,a$                   ;put PowerBASIC string handle for a$ in ax
! push ax                      ;put string handle on stack for GetStrLoc
! call GetStrLoc               ;PowerBASIC function to return DX:AX pointer
! or   cx,cx                   ;returned # chars in A$ in CX, check for nil
! jz   xpattern                ;if no chars, we're outta here!
! push ds                      ;work to do, hold ds on stack for later
! mov  ds,dx                   ;move DX -> DS for LODSB reads using DS:SI
! mov  si,ax                   ;move AX -> SI for LODSB reads using DS:SI
! mov  es,dx                   ;move DX -> ES for STOSB writes using ES:DI
! mov  di,ax                   ;move AX -> DI for STOSB writes using ES:DI
! cld                          ;clear direction flag so SI and DI increment
pattern1:                      'loop point to read/write all of a$ bytes
! lodsb                        ;load current DS:SI byte -> al, increment SI
! cmp  al,90                   ;compare al to code for "Z"
! jg   pattern3                ;if > "Z", go check for a-z instead
! mov  ah,65                   ;put code for "A" in ah
! cmp  al,ah                   ;compare to code for "A"
! jge  pattern4                ;code ranges from "A" to "Z", go replace
pattern2:                      'if < "A" so check for 0-9
! mov  ah,57                   ;put code for "9" in ah
! cmp  al,ah                   ;see if between "9" and "A"
! jg   pattern5                ;greater than"9", so go use original byte
! cmp  al,48                   ;compare to code for "0"
! jge  pattern4                ;if >= "0", go replace with code for "9"
! jmp short pattern5           ;jump to where we use the original code
pattern3:                      'here we check for "a" to "z"
! cmp  al,122                  ;compare to code for "z"
! jg   pattern5                ;if > "z", go use original code
! mov  ah,97                   ;put code for "a" in ah
! cmp  al,ah                   ;compare to code for "a"
! jl   pattern5                ;if < "a", go use original code
pattern4:                      'merge point to replace original code
! mov  al,ah                   ;copy replacement code into al
pattern5:                      'merge point to use original code
! stosb                        ;store al-> address at ES:DI, increment DI
! loop  pattern1               ;if CX<>0, decrement CX and jump to pattern1
! pop   ds                     ;restore ds for use by PowerBASIC
xpattern:                      'this is where we got outta here to
function=a$                    'we return the modified local string
end function

function deltapattern(offset,somestr$,dpattern$) static as integer
$if 0

DeltaPattern calls AsmPattern, which is actually a subpart of the PATTERN
function which represents an Asm code approach to doing pattern.  We just
stripped out the specific code and put it into AsmPattern$() to support its
use by this function as well.

DELTAPATTERN (Delta Pattern) is the most sophisticated and complicated
pattern matching routine included in PATTERNS.  In effect it uses sort of a
variable width pattern for alpha characters (which are all treated as
uppercase), for any groups of digits, and for "white" spaces (this term is
used to refer to all non-printable characters).  So if the pattern being
checked has an "A" in it, it will be satisfied by any size group of letters, from
"A" to "zed".  If there is a "9", than any group of digits from "0" to
"9999999999"  It also treats all "white" white spaces, so a carriage
return, line feed, tab, form feed, and other non printable characters tha
occur in text are covered by including as single space (" ").

Suppose you want to search for a valid date, and the pattern you elect to
use is " AAA 9, 99".  Try matching this to "Jan and Marge, now 12 and 13,
will give a joint birthday party on September 23, 1999.".  Typically, most
pattern matching process would have a difficult time not picking up Jan 12
1999, or picking up Mar 13, 1999 instead.  But deltapattern will zero right
in on the fact that the only pattern match here begins with the space
before September, even though we only indicated " AAA " at the start of our
pattern.  But this indicated one or more white spaces followed by 3 or
more letters grouped together followed by one or more white spaces, and so
on, until each group was identified in sequence.

Calling deltapattern is simple:

a$="Jan and Marge, now 12 and 13, will give a joint birthday party" + _
   " on September 23, 1999.""
b$=" AAA 9, 99"
a=deltapattern(1,a$,b$)
if a=0 then
  ?"Could not find a valid date format in "a$
  end
end if

Using datapattern results is also easy:

b$=ucase$(AsmPattern$(a$))
a=instr(a+1,b$,"A")
month$=mid$(a$,a,3)
select case month$
case "JAN","FEB","MAR","APR","MAY","JUN"
case "JUL","AUG","SEP","OCT","NOV","DEC"
case else
  ?"Month "month$" is invalid."
  end
end select
a=instr(a+1,b$,"9")
day=val(mid$(a$,a))
select case day
case 1 to 31
case else
  ?"The"day"is invalid."
  end
end select
a=instr(a+1,",")
a=instr(a+1,"9")
year=val(mid$(a$,a))
select case year
case 1980 to 2020
case else
  ?"The"year" is invalid."
  end
end select

Normally, in free form data, you have an issue with whether any specific
data can be found without analyzing every possible group as to having a
specific content.  If you found 3 or more characters, you would have to
check them to see if they matched to JAN, Jan, FEB, Feb, MAR, Mar, etc.
That is a lot of testing.  If you did it without skipping smartly over
spaces, you would scan something  like "... on September 23, 1999" the hard
way:

Scans using a 3-character window for "... on September 23, 1999"
# 1                                   [...]    - check for Sep, no match
# 2                                    [.. ]       "    "    "     "    "
# 3                                     [. o]      "    "    "     "    "
# 4                                      [ on]     "    "    "     "    "
# 5                                       [on ]    "    "    "     "    "
# 6                                        [n S]   "    "    "     "    "
# 7                                         [ Se]  "    "    "     "    "
# 8                                          [Sep]     - found Sep, get day
# 9                                           [ept]         - not a valid day
# 10                                           [pte]        - not a valid day
# 11                                            [tem]       - not a valid day
# 12                                             [emb]      - not a valid day
# 13                                              [mbe]     - not a valid day
# 14                                               [ber]    - not a valid day
# 15                                                [er ]   - not a valid day
# 16                                                 [r 2]  - not a valid day
# 17                                                  [ 23]    - possible day
# 18                                                   [23,]   - got day 23

if you had put in September 3, 1999 instead, the 18th step would have also
had to check for a match on [ 3,], or narrow its window to two characters.
And note that this approach would also have to check all other possibilities
as well:  [ 1,], [ 2,], [ 3,], [ 4,] ... [28,], [29,], [30,], and [31,].

So far, a lot of steps, and we haven't even gotten to the year yet.  And of
couse you wound not want to do this for every month, every day of every
month, and every valid valid month and day in every valid year -- we would
never get it all done this way!

Working smartly means you can greatly simplify a lot of tasks by first
turning the data into patterns and start with analysing those, rather than
trying to jump right in by focusing too soon on the specific data at hand.
We used datapattern() to identify where each field started, then we just
went to the start point of each unique part of the pattern for check for the
specific data there to see if it was valid.  So far so good, and it was
both quick and easy.  To do the job properly, we would then have to rule out
any invalid combinations.  For instance, there is no year 0, or February 30,
or September 31, and there is no February 29th except during leap years,
and not every 4th year is a leap year.  In fact, years evenly divisible by
4 are leap years unless they are also evenly divisible by 100 (most century
years (1700, 1800, 1900, etc.) are not leap years, unless again they are
evenly divisible by 400 (the century years 1200, 1600, 2000 are examples of
valid leap years).  This can be expressed with a simple boolean expression:

leapyear=((year mod 4)=0)-((year mod 100)>0)+((Year mod 400)=0)

leapyear will be -1 only if it is a valid leap year.

$endif

if dpattern$="" then
  function=0
  exit function
end if
aa$=AsmPattern$(somestr$)  'We work from an initial pattern of course
aa$=ucase$(aa$)    'add this if you want letters to match regardless of case
ab$=dpattern$
ac$=left$(ab$,1)
select case ac$
case "a","9"," ","A"    'remove "A" if you want 1-to-1 for any capitals
                   'remove "a" if you want 1-to-1 for any lower case letters
                   ' for instance, remove "A" and leave "a","9"," " would
                   ' then match for leading capital words like "Last" if you
                   ' used a pattern of "Aa"
  for a=2 to len(ab$)
    if mid$(ab$,a,1)<>ac$ then exit for
  next
  decr a
  ad$=left$(ab$,a)
case else
  ad$=ac$
end select
a=offset
do
  a=instr(a+1,aa$,ac$)
  if a=0 then exit do
  select case ac$
  case "A","9"," "
    for b=a+1 to len(aa$)
      if mid$(aa$,b,1)<>ac$ then exit for
      'change this to:  "if mid$(lcase$(aa$),b,1)<>ac$ then exit for" if
      'want to match on first groups that start with a capital, but which
      'may also have additional capitals in them, such as MacDougal, McCloud
      'or O'Brien.  Note that O'Brien won't match unless you classify the
      '(') as an alpha character.  You may also have to do this for the
      'hyphen (-) and period (.) when checking for patterns in names.  You
      'might encounter something like "MacDougal-Turner, Chas." someday.
      'You can easily replace one pattern symbol with another.  Use REPLACE
      'any "-." with "aa" in <pattern$>.  This does not disturb the original
      'text on which the pattern was based, which you can revert to later.
    next
   case else
    b=a+1
  end select
  ae$=mid$(aa$,b,1)
  c=len(ad$)+1
  if ae$=mid$(ab$,c,1)then
    'the suggestions given above can also be extended to the lines below
    'if you want to have upper and lower case letters mixed together in the
    'subsequent groups.  Not applying the same rules below means that you
    'will have one (or more) capitals as additional groups and one (or more)
    'lowercase letters seen as additional groups, even though they adjoin
    'each other.  For instance, without the same changes, "Keith R. D'angelo"
    'would appear to fit the following pattern group: "Aa A. A'a".  It would
    'not match the standard pattern for First M. Last, which would be
    '"Aa A. Aa".  You can of course employ multiple patterns, but then you
    'are getting back into the game of dealing with specific data.  But here
    'are a few patterns you could try, and one advantage is that since most
    'names follow the norm, it could be quite a bit faster going this way:
    '      "Aa, Aa A."        Norm pattern (Last, First M.)
    '      "A'A, Aa A."       for O'Brien, D'Angelo (not D'angelo) & company
    '      "A'a, Aa A."       for people who don't known when to capitalize
    '      "AaAa, Aa A."      for McCloud, LaFrance and MacDougal
    '      repeat the above without the period after A. for people having
    '                          middle initials but no middle names
    '      repeat the above without the " A." for people with no middle
    '                          initial or name
    '      "Aa AA, Aa A."     for JR, SR, II, IV, or VI after the last name
    '      "Aa AAA, Aa A."    for III after the last name
    'As you can see, this can begin to get very messy fast.  So adding a
    'bit of code at the core of the pattern process can really make life
    'a lot simpler at the higher levels of your program.

    do while c<=len(ab$)
      select case ae$
      case "a","A","9"," "
        for d=c to len(ab$)
          if mid$(ab$,d,1)<>ae$ then exit for
        next
        af$=mid$(ab$,c,d-c)
      case else
        af$=ae$
        d=c+1
      end select
      if mid$(aa$,b,len(af$))<>af$ then goto incra
      select case ae$
      case "A","9"," "
        for b=b+len(af$) to len(aa$)
          if mid$(aa$,b,1)<>ae$ then exit for
        next
      case else
        incr b
      end select
      c=d
      ae$=mid$(ab$,c,1)
    loop
    exit do
  end if
incra:
loop
function=a
end function


sub starttime(testname$) static
?"Beginning of "testname$"..."
shared marker!
marker!=timer
do
loop while marker!>=timer
marker!=timer
end sub

function marktime static as single
shared marker!
do
  aa!=timer-marker!
loop while aa!<0
ab$=using$("##:",aa#\3600 mod 24)+using$("##:",aa#\60 mod 60)+ _
    using$("##",aa# mod 60)+"."+using$("####",aa#*10000+.5 mod 10000)
replace " " with "0" in ab$
?"Lapsed Time: "ab$
function=aa#
end function

function despace(aa$)static as string
local a,b,a$,b$
$if 0

DESPACE converts TABs to SPACE codes and removes all duplicate spaces from
a string.  It does NOT remove the leading and trailing space that may be
in the string, since these may be important during the subsequent parsing
or combining of strings.  However, additional code is included to show
how to modify the function so that it either deletes trailing spaces,
or both leading and trailing spaces, as desired.

$endif
'================== Section One: PowerBASIC Methods: =======================
'Method 1:  Using simple REPLACE process: ----------------------------------
?
starttime "DESPACE PowerBASIC Method 1:"
a$=aa$
replace chr$(9) with " " in a$
do while instr(a$,"  ")
  replace "  " with " " in a$
loop
function=a$
marktime
'exit function            'un-comment in order to not use remaining methods
'end of method 1 ----------------------------------------------------------
'Method 2:  Improving REPLACE by adding an INSTR pointer: -----------------
?
starttime "DESPACE PowerBASIC Method 2:"
a$=aa$
replace chr$(9) with " " in a$
a=0
do
  a=instr(a+1,a$,"  ")
  if a=0 then exit do
  a$=left$(a$,a)+ltrim$(mid$(a$,a+1))
loop
function=a$
marktime
'exit function            'un-comment in order to not use remaining methods
'end of method 2: ---------------------------------------------------------

'Method 3:  Building a new String using INSTR pointers: -------------------
?
starttime "DESPACE PowerBASIC Method 3:"
a$=aa$
b$=""
a=0
replace chr$(9) with " " in a$
do
  b=instr(a+1,a$,"  ")
  if b then
    b$=b$+mid$(a$,a+1,b-a-1)
    a=b
  else
    b$=b$+mid$(a$,a+1)
  end if
loop while b
function=b$
marktime
'exit function            'un-comment in order to not use remaining methods
'end of method 3: ----------------------------------------------------------

'Method 4:  Building a new string at the left end of the old string: -------
?
starttime "DESPACE PowerBASIC Method 4:"
a$=aa$
replace chr$(9) with " " in a$
a=1
b=len(a$)
do
  a=instr(a,a$,"  ")
  if a=0 or a>=b then exit do
  mid$(a$,a)=mid$(a$,a+1)
  decr b
loop
if right$(a$,1)=" " then incr b  'remove this line to delete training space
function=left$(a$,b)
marktime
'exit function            'un-comment in order to not use remaining methods
'end of method 4: ----------------------------------------------------------

'Method 5: Building within string and using LTRIM$() for smart removal: ----
?
starttime "DESPACE PowerBASIC Method 5:"
a$=aa$
b=len(a$)
a=1
do
  a=instr(a,a$,"  ")
  if a=0 or a>=b then exit do
  incr a
  b$=ltrim$(mid$(a$,a))
  mid$(a$,a)=b$
  b=b+len(b$)+a-len(a$)-(b$>"")
loop while b$>""
if b$="" then decr b
function=left$(a$,b)
marktime
'exit function            'un-comment in order to not use remaining methods
'end of method 5: ---------------------------------------------------------
'-------------------------- end of section one ----------------------------

'==================== Section two: DESPACE using ASM code:=================
?
starttime "DESPACE Inline ASM Method:"
a$=aa$
a=0
! mov  ax,a$
! push ax
! call GetStrLoc
! or   cx,cx
! jz   xdespace
! push ds
! mov  ds,dx
! mov  si,ax
! mov  es,dx
! mov  di,ax
! xor  bx,bx
! xor  ah,ah                   ;change to mov ah,32 to remove leading spaces
! cld
despace1:
! lodsb
! cmp  al,9
! jnz  despace2
! mov  al,32
despace2:
! cmp  al,32
! jnz  despace3
! cmp  al,ah
! jz   despace4
despace3:
! mov  ah,al
! inc  bx
! stosb
despace4:
! loop despace1
'! cmp  al,32                   ;un-comment to keep a trailing space
'! jnz  despace5                ; "    "    "   "   "    "       "
'! dec  bx                      ; "    "    "   "   "    "       "
despace5:
! pop  ds
! mov  a,bx
xdespace:
function=left$(a$,a)
marktime
end function

sub pause (reason)static
$if 0
The process allows you to insert an effective STOP into code that takes
effect only if the program is running.  An example for calling if from
withing your program might be PAUSE 250.  The number could also be used
as a lookup code for an error table, or whatever.

In the IDE, it makes sence to put a breakpointon the ky$=inkey$ line.
This means that after pausing the execution, your next step will
automatically take you out of the loop and let you return to the point
in your program where you issued the pause 250 statement.  You can then
continue to single-step or run your program, which the STOP command does
not allow you to do.  This also overcomes an occasional "twitch" that
the IDE has where you can't continue properly after reaching the breakpoint,
or the breakpoint becomes dislodged by edits that happened to lines
previous to it.  I usually put the pause sub at the back end of my code to
make it easy to find, when I want to set or clear its breakpoint (just
<ctl><PgDn> to it), but you could also put it much higher and just use a
search to find SUB PAUSE or KY$=INKEY$.  That just takes a few more steps.

Try this with this routine:  Call it with the demo code to see how it
normally works.  Also change the call to PAUSE 0 to see how it eliminates
the message to the screen.  Then try it both ways with the KY$=INKEY$
line marked with a breakpoint.  You can also set the watch to then scope
variable changes at that point, or further changes as you single step
through the remainder of the process.

$endif
a!=timer+.125
if a!>timer then
  if a!>timer then
    b=1
    do
      a!=timer+.125
      ky$=inkey$
      if a!>timer then
        if b then
          ?"PAUSED, Reason ="reason
          b=0
        end if
      else
        b=1
      end if
    loop while ky$="" and b=0
  end if
end if
end sub
