'=========================================================================== ' 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 ,ax, or a mov ax, 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! 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 . 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 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