home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fred Fish Collection 1.5
/
ffcollection-1-5-1992-11.iso
/
ff_disks
/
200-299
/
ff239.lzh
/
JGoodies
/
Brunjes
/
StringPkg
< prev
next >
Wrap
Text File
|
1989-08-21
|
29KB
|
751 lines
\ StringPkg by Roy Brunjes
\ Public DOmain
Include? SpaceOrESC? SpaceOrEscape
Include? 0COUNT JF:Strings+
Include? { JU:Locals
Include? R< Utils
\ The debug options, when active, cost you about 4300 bytes of dict. space.
\ The debug code could be cleaned up (a few words created and called from
\ the other words) but I didn't want to bother with it for now.
.NEED String.Debug
Variable String.Debug
String.Debug ON \ By default this is TRUE (i.e. ACTIVE)
>newline ." Setting STRING.DEBUG to TRUE" CR
.ELSE
>newline ." Leaving STRING.DEBUG set to " String.Debug @ . CR
.THEN
.NEED User.Debug
Variable User.Debug
User.Debug ON \ By default this is TRUE (i.e. ACTIVE)
>newline ." Setting USER.DEBUG to TRUE" CR
.ELSE
>newline ." Leaving USER.DEBUG set to " USER.DEBUG @ . CR
.THEN
ANEW StringPkg
\ Functionality provided with this word set:
\
\ Defining Words CHAR and CHAR0 for defining counted character strings
\ and null-terminated character strings respectively.
\ SubString (Leftmost portion, Rightmost portion, Middle portion)
\ Concatenation of two strings of type CHAR and/or CHAR0.
\ Assignment of String Variable's value to another String Variable
\ Current and Maximum allowed lengths of a string (CHAR or CHAR0)
\ Keyboard input of values for CHAR and CHAR0 strings.
\
\ This wordset behaves somewhat differently based on the value of two
\ compile-time variables: STRING.DEBUG and USER.DEBUG. It is expected
\ that USER.DEBUG be a variable that has meaning outside of this String
\ Package. STRING.DEBUG is a debugging flag specifically for this package.
\ In either case, this code will be fatter and slower (but much more
\ tolerant of mistakes!) when either or both of these flags are TRUE
\ (which amounts to "non-FALSE" in Forth).
\ So, to have more forgiving code, issue a USER.DEBUG ON or a
\ STRING.DEBUG ON before compiling these words. Note that this is done
\ for you by default if either of the two variables are not present in the
\ dictionary at compile time. If you don't like the default, CHANGE IT!
\ Personally, I hate the GURU so I leave the debug options on until I am
\ SURE that my code is behaving!
\
\ Note that USER.DEBUG is intended to be used by other Forth code you write
\ or use. I couldn't count on anyone else defining it, so I defined it for
\ you. If you already have a USER.DEBUG variable, use it and remove my
\ creation of it. And then, set USER.DEBUG when you want ALL
\ "USER.DEBUG-aware" code you compile to be active, and set STRING.DEBUG ON
\ in those cases where you are confident that non-string code works but you
\ aren't sure about your string-related code. Enough comments! On with
\ the code!
Base @
Decimal
: ERASED ( n -- )
HERE OVER ERASE ALLOT ; \ Erase given # of bytes then allot them.
\ A note about how the following defining words set up shop in the
\ dictionary is in order:
\
\ Defining Word Dictionary Format
\ ------------- -----------------
\
\ CHAR0 First: Maximum Length (2 Bytes)
\ Second: The string itself
\
\ CHAR First: Current Length (1 Byte)
\ Second: Maximum Length (1 Byte)
\ Third: The string itself
: CHAR0
\ A defining word that defines a null-terminated string variable.
\
\ Compile-Time Behavior:
\
\ Usage: 20 char0 xyz0
\
\ This causes the definition of a 20 character string called xyz0 to be
\ compiled into the dictionary. It is intended to be a null-terminated
\ string. The first 2 bytes hold the max length (20 in this case).
\ The remaining bytes are for the string itself (with one extra for a
\ null terminator. These bytes are ALLOTted from the dictionary. In
\ the example, 2 bytes for the max length + 20 bytes for string + 1 for
\ null terminator = 23 bytes for this string. BUT, there appears to be
\ a bug in EXPECT, so this word reserves 4 extra bytes for a buggy
\ EXPECT. (See my comments further along in this file about EXPECT.)
\
\ Helpful Hint: Name all CHAR0 words xxxx0 to emphasize their
\ null terminators.
\
\ Run-Time Behavior:
\
\ Usage: xyz0 ( -- addr.of.first.char )
\
\ This causes the address of the first character in string xyz0 to be
\ placed on the stack.
( Likely way to print a CHAR0 string: stringvar dup length0 type )
( OR : stringvar dup 0 Smart.Length type )
\ Possible enhancements for this word:
\
\ Since 64K is a lot of string and would take up a lot of dictionary
\ space, consider allocating memory from the heap for these critters,
\ perhaps only if size > constant 0STRING.IN.HEAP .
\
\ Consider addition of 1 byte to both CHAR0 and CHAR that words using
\ these strings can look for during compilation. Then based on that,
\ they know by a tag byte whether this is a CHAR or CHAR0 type of string.
\ This would eliminate the need for separate wordsets for CHAR and CHAR0
\ types of strings. On the other hand, how many people will make heavy
\ use of null-terminated strings? If people are really just after longer
\ strings, they can change the CHAR strings' 1-byte count field to 2 bytes
\ and have really long strings that way. In that case, they too should
\ probably be allocated from the heap if their size gets bigger than a
\ magic constant. Also, use of PAD to get such long counted strings will
\ not be feasible. (PAD was used to bypass the EXPECT bug for counted
\ strings since they are currently limited to 255 bytes.)
[ String.Debug @
User.Debug @
OR ]
.IF
DEPTH 0=
IF \ TRUE if no data is on the stack
CR ." Fatal Error in CHAR0. No length supplied on stack."
CR ." This error checking just saved you a visit with the GURU!"
CR ." CHAR0 Abnormally Terminating." 0SP ABORT
THEN
DUP 65535 >
IF ( TRUE if user-specified max length is > 65535 )
cr ." Error in CHAR0 -- You can't define a CHAR0 string with a length"
cr ." greater than 65535 characters. If you really need a"
cr ." null-terminated string of > 65535 bytes, just change the
cr ." source code to CHAR0."
cr ." Length of string you tried to create = " .
cr ." CHAR0 Abnormally Terminating. " 0SP ABORT
THEN
.THEN
ALIGN
CREATE \ Creates a new dictionary entry (e.g. xyz)
DUP W, \ Stash the max length first
1+ \ One more byte for null at end.
4 + ERASED \ Zero-out this chunk of memory first, then allot.
\ Add one extra to hold null at end if string is full
\ length!
\ Add 4 extra bytes to cover a bug in EXPECT which
\ is very fond of adding 4 bytes of Hex 00's to the
\ end of the area it is storing the chars into.
ALIGN
DOES> \ Marks the end of the compile-time behavior of char0,
2+ \ and the beginning of the run-time behavior of char0.
\ Add 2 to jump over max length 2-byte value.
; \ End of definition for compiling word CHAR0.
: CHAR ( see CHAR0 above, this is the same except this is for counted )
( strings i.e. string length is stored in first byte of string )
( We have to make sure that no max lengths are > 255 since only 1 byte )
( of dictionary space is used to store the length of counted strings. )
( Likely way to print a CHAR string: stringvar dup length type )
( OR : stringvar dup 1 Smart.Length type )
[ String.Debug @
User.Debug @
OR ]
.IF
DEPTH 0=
IF \ TRUE if no data is on the stack
CR ." Fatal Error in CHAR. No length supplied on stack."
CR ." This error checking just saved you a visit with the GURU!"
CR ." CHAR Abnormally Terminating." 0SP ABORT
THEN
DUP 255 >
IF ( TRUE if user-specified max length is > 255 )
cr ." Error in CHAR -- You can't define a CHAR string with a length"
cr ." greater than 255 characters. If you need a longer string"
cr ." than this, use CHAR0 -- its max. length is 65535 bytes."
cr ." If you really need counted strings of > 256 bytes, just"
cr ." change the source code to CHAR."
cr ." Length of string you tried to create = " .
cr ." CHAR Abnormally Terminating. " 0SP ABORT
THEN
.THEN
ALIGN CREATE \ Create the string name in dictionary
DUP C, 0 C, \ Set max. length to n, Curr length to 0
ERASED \ Allot n bytes of memory & zero it out
ALIGN
DOES>
2+ ; \ Step over max length & curr length bytes and
\ return adr of 1st char in string
1 CONSTANT Value.Too.Small \ Internally used by Get.Number
2 CONSTANT Value.Too.Big \ Internally used by Get.Number
3 CONSTANT Not.A.Number \ Internally used by Get.Number
0 CONSTANT Null.String \ Useful for enhancing code readability.
\ e.g. xyz0 Null.String Smart.Length
\ is better than xyz0 0 Smart.Length
1 CONSTANT Count.String \ See comments for Null.String.
\ e.g. xyz Count.String Smart.Length
255 CHAR GetNumWorkStr \ Internally used by Get.Number
: Length ( Ptr.to.first.string.char -- length.of.string )
1- C@ ; ( Only good for counted strings )
: Max.Length ( Ptr.to.first.string.char -- max.string.length )
2- C@ ; ( Just before first char of string is max.length )
: 0COUNT-CR ( addr -- len )
\
\ "Zero Count Minus CR"
\
\ Returns length of a zero-terminated string, but does NOT count a final
\ CR at end of string (char just before the null) as part of the length.
\ If no CR is just before the null, then this function works just as
\ 0COUNT does.
\
0COUNT \ Raw String Length
DUP>R \ Stash current length on Return Stack
+ 1- \ Get addr of last character in the string.
C@ \ Put last char of string on TOS.
13 \ A CR is the last character.
= \ Is the last char in the string equal to a CR?
R< \ There's the length again ...
SWAP \ Put CR test flag on TOS
IF \ If it was a carriage return ...
1- \ Return one less than the 0COUNT
THEN ;
: Length0 ( Ptr.to.first.string.char -- length.of.string )
\ This word is for null-terminated strings. Return length of CHAR0 string.
0COUNT SWAP DROP ;
: Max.Length0 ( Ptr.to.first.string.char -- max.string.length )
\ Return max length of CHAR0 string.
2- W@ ;
: Get.String { addr charcount -- } \ Accept characters from keyboard until
\ you see a Carriage Return or charcount
\ chars have been entered. Then store
\ value into addr.
[ String.Debug @
User.Debug @
OR ]
.IF
addr charcount <
IF
CR ." Get.String Error. You probably switched the address and count"
CR ." on the stack. Proper order is: addr count <- top "
CR ." Values you passed on stack: " .S
CR ." Get.String aborting." 0SP
ABORT
THEN
addr Max.Length
charcount
< IF
CR
." Error in Get.String"
." -- Max. # of chars entered would cause string overflow." CR
." Number of characters asked for = " charcount . CR
." Maximum length allowed for string = " addr Max.Length . CR
." Get.String aborting." 0SP ( wipe out stack )
ABORT
THEN
.THEN
\ EXPECT puts 00 at end of memory (4 bytes worth). Why?
\ addr charcount should work, but EXPECT smashes 4 bytes at end of string.
\ Are you reading this Delta Research?
\ (YES, History EXPECT doesn't do this but old one does.)
PAD charcount
EXPECT \ Wait for count chars from terminal; store @ addr
PAD addr SPAN @
CMOVE \ Move string from PAD to String Variable's Address
SPAN @ addr 1- C! \ Store new length into current length byte
;
: Get.String0 { addr charcount -- } \ Accept characters from keyboard until
\ you see a Carriage Return or charcount
\ characters have been entered. Then
\ store value into addr. This routine
\ tacks a null (HEX 0) at the end of the
\ string.
[ String.Debug @
User.Debug @
OR ]
.IF
addr charcount <
IF
CR ." Get.String0 Error. You probably switched the address and count"
CR ." on the stack. Proper order is: addr count <- top "
CR ." Values you passed on stack: " .S
CR ." Get.String0 aborting." 0SP
ABORT
THEN
addr max.length0 charcount
< IF
CR
." Error in Get.String0"
." -- Max. # of chars entered would cause string overflow." CR
." Number of characters asked for = " charcount . CR
." Maximum length allowed for string = " addr max.length0 . CR
." Get.String0 aborting." 0SP
ABORT
THEN
.THEN
addr charcount
EXPECT
0 \ Here's the null to put at end of string
addr SPAN @ + \ Here's where to put the null in memory
C! \ Now we have a "safe" string
;
: Smart.Length ( addr.of.first.char flag -- length.of.string )
\ This length word can take either a counted string of type CHAR or a
\ null-terminated string of type CHAR0 and return its current length.
\ If FLAG is FALSE [aka ZERO], a null-terminated string is assumed, else
\ we assume that it's a counted string.
\ Consider renaming the length-words above so that this one is called
\ LENGTH and that would make it easier to user -- just one LENGTH word
\ regardless of type (of course, the flag will have to be on the stack!)
IF Length ELSE Length0 THEN ;
: Smart.Max.Length ( addr.of.first.char flag -- max.length.of.string )
\ This length word can take either a counted string of type CHAR or a
\ null-terminated string of type CHAR0 and return its maximum length.
\ If FLAG is FALSE [aka ZERO], a null-terminated string is assumed, else
\ we assume that it's a counted string.
\ Consider renaming the max. length-words above so that this one is called
\ Max.LENGTH and that would make it easier for the user -- just one
\ Max.LENGTH word regardless of type (of course, the flag will have to
\ be on the stack!)
IF Max.Length ELSE Max.Length0 THEN ;
: LEFT$ { first n flag -- ptr.to.1st.char n }
\ This word is trivial, but included for completeness. It returns a
\ pointer to the first character of the string and a count of characters
\ that makes up the leftmost n characters of the string. If flag is 0
\ (ZERO), it indicates that the string passed is a CHAR0 string. Any
\ other value for flag indicates that the string passed is a CHAR string.
\
\ Usage: stringvar 7 Count.String Left$ ( Works for CHAR & CHAR0 Vars )
\
\ Note: first = ptr to 1st char in string
\
( first check to see if he wants more chars than string has )
n first flag Smart.Length >
IF ( If TRUE, wants more chars than we have )
first DUP flag Smart.Length ( Return whole string )
ELSE ( User doesn't want whole string )
first n
THEN
;
: RIGHT$ { first n flag -- ptr.to.leftmost.char n }
\ This should a ptr to first character in string that begins a substring of
\ the rightmost n characters of the string. A flag value of zero indicates
\ that the string is a string of type CHAR0. A flag of any other value
\ indicates that the string passed is a CHAR string.
\
\ Usage: stringvar0 7 Null.String Right$ ( Works for CHAR & CHAR0 Vars )
\
\ Note: first = ptr to 1st char in string
\
( first check to see if he wants more chars than string has )
n first length >
IF ( If TRUE, he wants more chars than we have )
first DUP flag Smart.Length ( Return whole thing )
ELSE ( NOT asking for more chars than we have )
first DUP flag Smart.Length + 1- ( pointer to last char )
n - 1+ n
THEN
;
: MID$ { first start n flag -- substring.ptr.start n }
\
\ This word should return ptrs to the substring starting at start and
\ continuing for n chars. Flag of zero indicates that string is a
\ NULL-TERMINATED string (CHAR0). Flag of any other value indicates that
\ string passed is a COUNTED string (CHAR).
\
\ Usage: stringvar 8 4 Count.String MID$ ( Works for CHAR & CHAR0 Vars )
\
\ Note: first = ptr to 1st char in string
\ substring.ptr.start is a pointer to the first character in the
\ resulting substring.
\ start = index into string; the point at which you want to start
\ looking at the substring. In the example, start at 8th
\ char in string and continue for 4 characters. 8 is start.
\ 4 is n.
\
[ String.Debug @
User.Debug @
OR ]
.IF
start 0> NOT
IF ( user has passed us a non-positive starting position )
CR
." Error in MID$ -- Requested substring has start BEFORE beginning "
." of string." CR
." Starting value you passed to MID$ is: " start . CR
." MID$ Abnormally terminating. " 0SP
ABORT
THEN
n 0> NOT
IF ( user has passed us a non-positive character count )
CR
." Error in MID$ -- You cannot have a non-positive character count!" CR
." Character count you passed to MID$ is: " n . CR
." MID$ Abnormally terminating. " 0SP
ABORT
THEN
( Check to see if start is past end of string )
first flag Smart.Length start
< IF ( if TRUE, start IS past end of string )
\ 0 0 ( Return a null string )
\ Notice that the above line is commented out. That line shows the "true"
\ behavior of MID$ on most systems. I prefer to get an error message here
\ because to me it is too difficult to track down what goes wrong in a
\ program due to some wild value for a pointer or whatever. So this
\ version of the StringPkg stops you dead with an error here. Change it
\ if you like! Perhaps rewrite so that user.debug flag can have levels and
\ based on the level set by the user, this package in general will behave
\ strictly about such situations or ignore them all-together.
CR
." Error in MID$ -- Requested substring has start past end of "
." string." CR
." Current length of string = " first flag Smart.Length . CR
." You requested start of substring at character position: "
start . CR
." MID$ Abnormally terminating. " 0SP
ABORT
THEN
.THEN
( Start is NOT past end of string )
first start + 1- ( compute addr of start of substring )
first flag Smart.Length ( length of source string )
start - 1+ DUP ( # of chars from start to end of string )
n <=
IF ( Not n chars left in substring starting )
( at start. )
ELSE
DROP n \ There are at least n chars left
THEN ;
: Concat { ptr1 flag1 ptr2 flag2 -- }
\ Concatenate string2 to the end of string1. The resulting string is
\ stored in string1. Flag1 is 0 if the first string is NULL-TERMINATED
\ (CHAR0). A non-zero value of this flag implies that string1 is a COUNTED
\ string (CHAR). Flag2 likewise lets this word know what type of string
\ is represented there.
\
\ Usage: xyz Null.String abc Count.String Concat
\ Result: null-terminated string xyz becomes string xyz immediately followed
\ by the counted string value of abc. The null for xyz is placed
\ at the end of the new string.
\ Note: ptr1 = ptr to 1st char in string1
\ ptr2 = ptr to 1st char in string2
\
\ Note: If you want real speed and have counted strings only, try using
\ JForth's $APPEND. Be warned: It does NO ERROR CHECKING!!
\
[ String.Debug @
User.Debug @
OR ]
.IF
( First determine is string1 has a max. length big enough to hold result )
( of concatentation. )
ptr1 flag1 Smart.Max.Length ( Compute maximum length of string1 )
ptr1 flag1 Smart.Length ( Compute current length of string1 )
ptr2 flag2 Smart.Length ( Compute current length of string2 )
+ ( Compute concatenated length )
< IF ( TRUE if string1 too short )
cr
." Error in Concat -- Result of Concat would be too long." cr
." Maximum length of destination string is: "
ptr1 flag1 Smart.Max.Length . cr
." Proposed concatenation would require string of length: "
ptr1 flag1 Smart.Length ptr2 flag2 Smart.Length + . ." bytes." cr
." Concat Abnormally Terminating. " 0SP ABORT
THEN
.THEN
( We know that the destination string [string1] is long enough )
( Let's do the concatenation. )
ptr2 ( Addr of source of CMOVE coming up )
ptr1 dup flag1 Smart.Length + ( Get addr of last char + 1 in string )
( The CMOVE destination address )
ptr2 flag2 Smart.Length ( The # of chars to move )
CMOVE ( Move the data )
( Now we must fix up the little stuff concerning the new string )
( What we do depends on the type of string that string1 is )
flag1 IF ( True if string1 is COUNTED )
ptr1 flag1 Smart.Length
ptr2 flag2 Smart.Length
+ ( New length of string1 )
ptr1 1- ( Addr of current length of string1 )
C! ( Stash new length of string1 )
ELSE ( String1 is NULL-TERMINATED )
ptr1 flag1 Smart.Length
ptr2 flag2 Smart.Length
+ 0 C! ( Put NULL at end of new string1 )
THEN ;
: S! { source.ptr n dest.ptr flag -- }
\ This is S-Store. It will take a ptr to the first character of a string
\ and copy count bytes to the string pointed to by dest.ptr. It is a
\ string assignment word.
\
\ Usage: 45 CHAR abc
\ 0" This is my string" dup Length0 abc Count.String S!
\ OR a dup length abc Count.String S! ( where a already has a value )
\
\ Note: source.ptr = ptr to 1st char in source string
\ n = # of bytes to copy into destination string
\ dest.ptr = addr to store the first char of source string at
\ flag = type (0 --> null-term) of string var dest string is
\
[ String.Debug @
User.Debug @
OR ]
.IF
( First see if destination string can contain the source string )
dest.ptr flag Smart.Max.Length ( Max. Length of dest. string )
n ( Length of source string )
< IF ( TRUE if source string too long )
CR ." Error in S! -- Source string too long for destination string."
CR ." Maximum length of destination string is: "
dest.ptr flag Smart.Max.Length .
CR ." You requested to transfer " n . ." bytes of source string."
CR ." S! Abnormally Terminating. " 0SP ABORT
THEN
( Destination string is long enough )
source.ptr flag Smart.Length n <
IF
CR ." Error in S! -- Attempt to assign " n . ." characters from"
CR ." source string when source string has only "
source.ptr flag Smart.Length .
CR ." characters in it."
CR ." S! Abnormally Terminating. " 0SP ABORT
THEN
.THEN
source.ptr dest.ptr n CMOVE ( Perform the assignment )
( Now, cleanup time which depends on type of string of destination )
flag IF ( if TRUE, a COUNTED dest string )
n dest.ptr 1- C! ( Stash current length )
ELSE ( a NULL-TERMINATED dest string )
0 dest.ptr n + C! ( put null at end of dest string )
THEN ;
: Val ( addr -- d TRUE | FALSE )
\
\ addr is the address at which a count byte sits, with the byte after
\ that being the first character of the string.
\
\ Take that string and try to convert it to a number in the current base.
\ If successful, a TRUE is returned on top with a double length number
\ (the number that was represented by the string) under it.
\ If not successful, a FALSE is returned with nothing underneath.
\
\ Usage with CHAR strings: 30 CHAR MyString ( Create MyString )
\ MyString 20 Get.String ( Get a String )
\ MyString Val ( Convert to a number )
\ IF
\ you have a double length number to use here
\ ELSE
\ ." Bad number conversion in current base!"
\ ( But pick nicer wording than that! )
\ THEN
\
\ For those of you who don't need a double number to work with (you know
\ the value is < 2^32), just do a drop upon returning from VAL and that
\ will throw away 32 bits of the value 0, leaving a single length number
\ on the stack.
\
\ Cannot use with CHAR0 strings (no count byte on them). CHAR0 fans
\ should assign their string to a counted string and call VAL (or of
\ course, define your own equivalent of NUMBER? that works for CHAR0's.
\
\ To use with JForth native strings (i.e. counted strings, but not CHAR
\ type strings), you must get the "normal" JForth addr of the string
\ (which points to the count byte already), and do a 1+ before calling
\ this word. Easier is just to call NUMBER? in the case of JForth native
\ strings. That's really all this word is doing an it would save several
\ cycles of overhead.
\
1- NUMBER? ;
: Get.Number { 2 lo 2 hi -- d TRUE OR err# FALSE }
\
\ Get a signed number from user that is between lo and hi
\ inclusive.
\
\ If the user's number attempt is invalid, err# will have a code
\ indicating the nature of the problem with the user's input.
\
[ String.Debug @
User.Debug @
OR ]
.IF
hi lo D<
IF
>newline
." Error in Get.Number. Lo must be LESS than hi." cr
cr ." NOTE: If both lo and hi are negative, they should be"
." put on the stack with"
cr ." the MOST negative number UNDER the LEAST"
." negative number." cr
cr ." You sent lo = " lo d. ." and hi = " hi d.
cr ." Get.Number abnormally terminating." 0SP ABORT
THEN
.THEN
GetNumWorkStr
19 \ We want only 19 digits here since a 64-bit
\ value can be a max of 19 digits.
Get.String
GetNumWorkStr Val \ Try to convert to number in current base
IF \ TRUE if number is good so far
hi 1. D+
D< NOT
IF \ TRUE if value > hi
Value.Too.Big FALSE
ELSE \ We get here if value <= hi
GetNumWorkStr Val DROP
lo
D<
IF \ TRUE if value is below lo
Value.Too.Small
FALSE
ELSE \ We're OK. Value checks out.
GetNumWorkStr Val
DROP \ This HAS to succeed, it just did above
TRUE
THEN
THEN
ELSE \ We get here if not a number in current base
Not.A.Number FALSE
THEN ;
: Instr ( adr1 n1 adr2 n2 -- adr.in.str1 | 0 )
\ See doc in file ju:Match for information on this one.
\ It is faster to call match? . match? must use some relative branches
\ because it can't be inline.
\
\ This works pretty much like its BASIC counterpart.
match? ;
: S= ( adr1 adr2 #bytes -- 0 | 1 | -1 )
\
\ Compare string starting at adr1 to string at adr2.
COMPARE ;
: Lower.Case ( adr count -- )
\
\ Converts chars at adr to lower case for total of count characters.
\
0 DO
DUP I + DUP C@ 5 SET-BIT SWAP C!
LOOP DROP ;
: Upper.Case ( adr count -- )
\
\ Converts chars at adr to upper case for total of count characters.
\
0 DO
DUP I + DUP C@ 5 CLR-BIT SWAP C!
LOOP DROP ;
Base !