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 >
Text File  |  1989-08-21  |  29KB  |  751 lines

  1. \ StringPkg by Roy Brunjes
  2. \ Public DOmain
  3.  
  4. Include? SpaceOrESC? SpaceOrEscape
  5. Include? 0COUNT      JF:Strings+
  6. Include? {           JU:Locals
  7. Include? R<          Utils
  8.  
  9. \ The debug options, when active, cost you about 4300 bytes of dict. space.
  10. \ The debug code could be cleaned up (a few words created and called from
  11. \ the other words) but I didn't want to bother with it for now.
  12.  
  13. .NEED String.Debug
  14.     Variable String.Debug
  15.     String.Debug ON           \ By default this is TRUE (i.e. ACTIVE)
  16.  
  17.     >newline ." Setting STRING.DEBUG to TRUE" CR
  18. .ELSE
  19.     >newline ." Leaving STRING.DEBUG set to " String.Debug @ . CR
  20. .THEN
  21.  
  22.  
  23. .NEED User.Debug
  24.       Variable User.Debug
  25.       User.Debug ON           \ By default this is TRUE (i.e. ACTIVE)
  26.  
  27.     >newline ." Setting USER.DEBUG   to TRUE" CR
  28. .ELSE
  29.     >newline ." Leaving USER.DEBUG   set to " USER.DEBUG @ . CR
  30. .THEN
  31.  
  32.  
  33. ANEW StringPkg
  34.  
  35. \  Functionality provided with this word set:
  36. \
  37. \      Defining Words CHAR and CHAR0 for defining counted character strings
  38. \           and null-terminated character strings respectively.
  39. \      SubString (Leftmost portion, Rightmost portion, Middle portion)
  40. \      Concatenation of two strings of type CHAR and/or CHAR0.
  41. \      Assignment of String Variable's value to another String Variable
  42. \      Current and Maximum allowed lengths of a string (CHAR or CHAR0)
  43. \      Keyboard input of values for CHAR and CHAR0 strings.
  44. \
  45. \  This wordset behaves somewhat differently based on the value of two
  46. \  compile-time variables:  STRING.DEBUG and USER.DEBUG.  It is expected
  47. \  that USER.DEBUG be a variable that has meaning outside of this String
  48. \  Package.  STRING.DEBUG is a debugging flag specifically for this package.
  49. \  In either case, this code will be fatter and slower (but much more
  50. \  tolerant of mistakes!) when either or both of these flags are TRUE
  51. \  (which amounts to "non-FALSE" in Forth).
  52. \  So, to have more forgiving code, issue a USER.DEBUG ON or a
  53. \  STRING.DEBUG ON before compiling these words.  Note that this is done
  54. \  for you by default if either of the two variables are not present in the
  55. \  dictionary at compile time.  If you don't like the default, CHANGE IT!
  56. \  Personally, I hate the GURU so I leave the debug options on until I am
  57. \  SURE that my code is behaving!
  58. \
  59. \  Note that USER.DEBUG is intended to be used by other Forth code you write
  60. \  or use.  I couldn't count on anyone else defining it, so I defined it for
  61. \  you.  If you already have a USER.DEBUG variable, use it and remove my
  62. \  creation of it.  And then, set USER.DEBUG when you want ALL
  63. \  "USER.DEBUG-aware" code you compile to be active, and set STRING.DEBUG ON
  64. \  in those cases where you are confident that non-string code works but you
  65. \  aren't sure about your string-related code.  Enough comments!  On with
  66. \  the code!
  67.  
  68. Base @
  69. Decimal
  70.  
  71. : ERASED ( n -- )
  72.     HERE OVER ERASE ALLOT ;  \ Erase given # of bytes then allot them.
  73.  
  74. \  A note about how the following defining words set up shop in the
  75. \  dictionary is in order:
  76. \
  77. \  Defining Word         Dictionary Format
  78. \  -------------         -----------------
  79. \
  80. \  CHAR0                 First:  Maximum Length (2 Bytes)
  81. \                        Second: The string itself
  82. \
  83. \  CHAR                  First:  Current Length (1 Byte)
  84. \                        Second: Maximum Length (1 Byte)
  85. \                        Third:  The string itself
  86.  
  87. : CHAR0
  88.  
  89.   \ A defining word that defines a null-terminated string variable.
  90.   \
  91.   \  Compile-Time Behavior:
  92.   \
  93.   \  Usage: 20 char0 xyz0
  94.   \
  95.   \  This causes the definition of a 20 character string called xyz0 to be
  96.   \  compiled into the dictionary.  It is intended to be a null-terminated
  97.   \  string.  The first 2 bytes hold the max length (20 in this case).
  98.   \  The remaining bytes are for the string itself (with one extra for a
  99.   \  null terminator.  These bytes are ALLOTted from the dictionary.  In
  100.   \  the example, 2 bytes for the max length + 20 bytes for string + 1 for
  101.   \  null terminator = 23 bytes for this string.  BUT, there appears to be
  102.   \  a bug in EXPECT, so this word reserves 4 extra bytes for a buggy
  103.   \  EXPECT. (See my comments further along in this file about EXPECT.)
  104.   \
  105.   \  Helpful Hint:  Name all CHAR0 words xxxx0 to emphasize their
  106.   \                 null terminators.
  107.   \
  108.   \  Run-Time Behavior:
  109.   \
  110.   \  Usage: xyz0      ( -- addr.of.first.char )
  111.   \
  112.   \  This causes the address of the first character in string xyz0 to be
  113.   \  placed on the stack.
  114.  
  115.   ( Likely way to print a CHAR0 string:  stringvar dup length0 type       )
  116.   (                               OR  :  stringvar dup 0 Smart.Length type )
  117.  
  118. \ Possible enhancements for this word:
  119. \
  120. \   Since 64K is a lot of string and would take up a lot of dictionary
  121. \   space, consider allocating memory from the heap for these critters,
  122. \   perhaps only if size > constant 0STRING.IN.HEAP .
  123. \
  124. \   Consider addition of 1 byte to both CHAR0 and CHAR that words using
  125. \   these strings can look for during compilation.  Then based on that,
  126. \   they know by a tag byte whether this is a CHAR or CHAR0 type of string.
  127. \   This would eliminate the need for separate wordsets for CHAR and CHAR0
  128. \   types of strings.  On the other hand, how many people will make heavy
  129. \   use of null-terminated strings?  If people are really just after longer
  130. \   strings, they can change the CHAR strings' 1-byte count field to 2 bytes
  131. \   and have really long strings that way.  In that case, they too should
  132. \   probably be allocated from the heap if their size gets bigger than a
  133. \   magic constant.  Also, use of PAD to get such long counted strings will
  134. \   not be feasible.  (PAD was used to bypass the EXPECT bug for counted
  135. \   strings since they are currently limited to 255 bytes.)
  136.  
  137. [  String.Debug @
  138.    User.Debug   @
  139.    OR           ]
  140. .IF
  141.   DEPTH 0=
  142.   IF                    \ TRUE if no data is on the stack
  143.     CR ." Fatal Error in CHAR0.  No length supplied on stack."
  144.     CR ." This error checking just saved you a visit with the GURU!"
  145.     CR ." CHAR0 Abnormally Terminating."  0SP  ABORT
  146.   THEN
  147.  
  148.   DUP 65535 >
  149.   IF              ( TRUE if user-specified max length is > 65535 )
  150.     cr ." Error in CHAR0 -- You can't define a CHAR0 string with a length"
  151.     cr ."    greater than 65535 characters.  If you really need a"
  152.     cr ."    null-terminated string of > 65535 bytes, just change the
  153.     cr ."    source code to CHAR0."
  154.     cr ." Length of string you tried to create = " . 
  155.     cr ." CHAR0 Abnormally Terminating.   " 0SP  ABORT
  156.   THEN
  157.  
  158. .THEN
  159.  
  160.   ALIGN
  161.   CREATE             \ Creates a new dictionary entry (e.g. xyz)
  162.   DUP W,             \ Stash the max length first
  163.   1+                 \ One more byte for null at end.
  164.   4 + ERASED         \ Zero-out this chunk of memory first, then allot.
  165.                      \ Add one extra to hold null at end if string is full
  166.                      \ length!
  167.                      \ Add 4 extra bytes to cover a bug in EXPECT which
  168.                      \ is very fond of adding 4 bytes of Hex 00's to the
  169.                      \ end of the area it is storing the chars into.
  170.   ALIGN
  171.   DOES>              \ Marks the end of the compile-time behavior of char0,
  172.   2+                 \ and the beginning of the run-time behavior of char0.
  173.                      \ Add 2 to jump over max length 2-byte value.
  174.   ;                  \ End of definition for compiling word CHAR0.
  175.  
  176.  
  177. : CHAR ( see CHAR0 above, this is the same except this is for counted )
  178.        ( strings i.e. string length is stored in first byte of string )
  179.  
  180.   ( We have to make sure that no max lengths are > 255 since only 1 byte )
  181.   ( of dictionary space is used to store the length of counted strings.  )
  182.   ( Likely way to print a CHAR string: stringvar dup length type         )
  183.   (                             OR   : stringvar dup 1 Smart.Length type )
  184.  
  185. [  String.Debug @
  186.    User.Debug   @
  187.    OR           ]
  188. .IF
  189.   DEPTH 0=
  190.   IF                    \ TRUE if no data is on the stack
  191.     CR ." Fatal Error in CHAR.  No length supplied on stack."
  192.     CR ." This error checking just saved you a visit with the GURU!"
  193.     CR ." CHAR Abnormally Terminating."  0SP  ABORT
  194.   THEN
  195.  
  196.   DUP 255 >
  197.   IF              ( TRUE if user-specified max length is > 255 )
  198.     cr ." Error in CHAR -- You can't define a CHAR string with a length"
  199.     cr ."    greater than 255 characters.  If you need a longer string"
  200.     cr ."    than this, use CHAR0 -- its max. length is 65535 bytes."
  201.     cr ."    If you really need counted strings of > 256 bytes, just"
  202.     cr ."    change the source code to CHAR."
  203.     cr ." Length of string you tried to create = " . 
  204.     cr ." CHAR Abnormally Terminating.   " 0SP ABORT
  205.   THEN
  206.  
  207. .THEN
  208.  
  209.   ALIGN CREATE             \ Create the string name in dictionary
  210.   DUP C, 0 C,              \ Set max. length to n, Curr length to 0
  211.   ERASED                   \ Allot n bytes of memory & zero it out
  212.   ALIGN
  213.   DOES>  
  214.   2+ ;                     \ Step over max length & curr length bytes and
  215.                            \ return adr of 1st char in string
  216.  
  217.  
  218.   1 CONSTANT  Value.Too.Small    \ Internally used by Get.Number
  219.   2 CONSTANT  Value.Too.Big      \ Internally used by Get.Number
  220.   3 CONSTANT  Not.A.Number       \ Internally used by Get.Number
  221.  
  222.   0 CONSTANT  Null.String        \ Useful for enhancing code readability.
  223.                                  \ e.g.  xyz0  Null.String  Smart.Length
  224.                                  \ is better than xyz0  0  Smart.Length
  225.  
  226.   1 CONSTANT  Count.String       \ See comments for Null.String.
  227.                                  \ e.g.  xyz   Count.String  Smart.Length
  228.  
  229. 255 CHAR      GetNumWorkStr      \ Internally used by Get.Number
  230.  
  231.  
  232. : Length        ( Ptr.to.first.string.char -- length.of.string )
  233.   1- C@ ;       ( Only good for counted strings )
  234.  
  235. : Max.Length    ( Ptr.to.first.string.char --  max.string.length )
  236.   2- C@ ;       ( Just before first char of string is max.length )
  237.  
  238.  
  239. : 0COUNT-CR ( addr -- len )
  240.     \
  241.     \ "Zero Count Minus CR"
  242.     \
  243.     \ Returns length of a zero-terminated string, but does NOT count a final
  244.     \ CR at end of string (char just before the null) as part of the length.
  245.     \ If no CR is just before the null, then this function works just as
  246.     \ 0COUNT does.
  247.     \
  248.  
  249.     0COUNT                   \ Raw String Length
  250.     DUP>R                    \ Stash current length on Return Stack
  251.     + 1-                     \ Get addr of last character in the string.
  252.     C@                       \ Put last char of string on TOS.
  253.     13                       \ A CR is the last character.
  254.     =                        \ Is the last char in the string equal to a CR?
  255.     R<                       \ There's the length again ...
  256.     SWAP                     \ Put CR test flag on TOS
  257.     IF                       \ If it was a carriage return ...
  258.       1-                     \ Return one less than the 0COUNT
  259.     THEN ;
  260.  
  261.  
  262. : Length0       ( Ptr.to.first.string.char -- length.of.string )
  263. \ This word is for null-terminated strings. Return length of CHAR0 string.
  264.   0COUNT SWAP DROP ;
  265.  
  266.  
  267. : Max.Length0   ( Ptr.to.first.string.char -- max.string.length )
  268. \ Return max length of CHAR0 string.
  269.   2- W@ ;
  270.  
  271.  
  272. : Get.String { addr charcount -- } \ Accept characters from keyboard until
  273.                                    \ you see a Carriage Return or charcount
  274.                                    \ chars have been entered.  Then store
  275.                                    \ value into addr.
  276.  
  277. [  String.Debug @
  278.    User.Debug   @
  279.    OR           ]
  280. .IF
  281.     addr charcount <
  282.     IF
  283.       CR ." Get.String Error.  You probably switched the address and count"
  284.       CR ." on the stack.  Proper order is:  addr count  <- top  "
  285.       CR ." Values you passed on stack: " .S
  286.       CR ." Get.String aborting."  0SP
  287.       ABORT
  288.     THEN
  289.  
  290.     addr Max.Length
  291.     charcount
  292.     < IF
  293.         CR
  294.         ." Error in Get.String"
  295.         ."  -- Max. # of chars entered would cause string overflow." CR
  296.         ." Number of characters asked for = " charcount . CR
  297.         ." Maximum length allowed for string = " addr Max.Length . CR
  298.         ." Get.String aborting."   0SP  ( wipe out stack )
  299.         ABORT
  300.     THEN
  301.  
  302. .THEN
  303.  
  304. \ EXPECT puts 00 at end of memory (4 bytes worth). Why?
  305. \ addr charcount should work, but EXPECT smashes 4 bytes at end of string.
  306. \ Are you reading this Delta Research?
  307. \ (YES, History EXPECT doesn't do this but old one does.)
  308.  
  309.     PAD charcount
  310.     EXPECT             \ Wait for count chars from terminal; store @ addr
  311.     PAD addr SPAN @
  312.     CMOVE              \ Move string from PAD to String Variable's Address
  313.     SPAN @ addr 1- C!  \ Store new length into current length byte
  314. ;
  315.  
  316.  
  317. : Get.String0 { addr charcount -- } \ Accept characters from keyboard until
  318.                                     \ you see a Carriage Return or charcount
  319.                                     \ characters have been entered. Then
  320.                                     \ store value into addr.  This routine
  321.                                     \ tacks a null (HEX 0) at the end of the
  322.                                     \ string.
  323. [  String.Debug @
  324.    User.Debug   @
  325.    OR           ]
  326. .IF
  327.     addr charcount <
  328.     IF
  329.       CR ." Get.String0 Error.  You probably switched the address and count"
  330.       CR ." on the stack.  Proper order is:  addr count  <- top  "
  331.       CR ." Values you passed on stack: " .S
  332.       CR ." Get.String0 aborting."  0SP
  333.       ABORT
  334.     THEN
  335.  
  336.     addr max.length0 charcount 
  337.     < IF
  338.         CR
  339.         ." Error in Get.String0"
  340.         ."  -- Max. # of chars entered would cause string overflow." CR
  341.         ." Number of characters asked for = " charcount . CR
  342.         ." Maximum length allowed for string = " addr max.length0 . CR
  343.         ." Get.String0 aborting."  0SP
  344.         ABORT
  345.     THEN
  346.  
  347. .THEN
  348.  
  349.     addr charcount
  350.     EXPECT
  351.     0                      \ Here's the null to put at end of string
  352.     addr SPAN @ +          \ Here's where to put the null in memory
  353.     C!                     \ Now we have a "safe" string
  354. ;
  355.  
  356.  
  357. : Smart.Length ( addr.of.first.char  flag -- length.of.string )
  358.  
  359. \ This length word can take either a counted string of type CHAR or a
  360. \ null-terminated string of type CHAR0 and return its current length.
  361. \ If FLAG is FALSE [aka ZERO], a null-terminated string is assumed, else
  362. \ we assume that it's a counted string.
  363. \ Consider renaming the length-words above so that this one is called
  364. \ LENGTH and that would make it easier to user -- just one LENGTH word
  365. \ regardless of type (of course, the flag will have to be on the stack!)
  366.  
  367.     IF Length ELSE Length0 THEN ;
  368.  
  369. : Smart.Max.Length ( addr.of.first.char  flag -- max.length.of.string )
  370.  
  371. \ This length word can take either a counted string of type CHAR or a
  372. \ null-terminated string of type CHAR0 and return its maximum length.
  373. \ If FLAG is FALSE [aka ZERO], a null-terminated string is assumed, else
  374. \ we assume that it's a counted string.
  375. \ Consider renaming the max. length-words above so that this one is called
  376. \ Max.LENGTH and that would make it easier for the user -- just one
  377. \ Max.LENGTH word regardless of type (of course, the flag will have to
  378. \ be on the stack!)
  379.  
  380.     IF Max.Length ELSE Max.Length0 THEN ;
  381.  
  382. : LEFT$ { first n flag  --  ptr.to.1st.char  n }
  383.  
  384. \ This word is trivial, but included for completeness.  It returns a
  385. \ pointer to the first character of the string and a count of characters
  386. \ that makes up the leftmost n characters of the string.  If flag is 0
  387. \ (ZERO), it indicates that the string passed is a CHAR0 string.  Any
  388. \ other value for flag indicates that the string passed is a CHAR string.
  389. \
  390. \ Usage:  stringvar 7 Count.String Left$  ( Works for CHAR & CHAR0 Vars )
  391. \
  392. \ Note: first = ptr to 1st char in string
  393. \
  394.     ( first check to see if he wants more chars than string has )
  395.  
  396.     n first flag Smart.Length >
  397.     IF                            ( If TRUE, wants more chars than we have )
  398.       first DUP flag Smart.Length ( Return whole string )
  399.     ELSE                          ( User doesn't want whole string )
  400.       first n
  401.     THEN
  402. ;
  403.  
  404. : RIGHT$ { first n flag -- ptr.to.leftmost.char n }
  405.  
  406. \ This should a ptr to first character in string that begins a substring of
  407. \ the rightmost n characters of the string.  A flag value of zero indicates
  408. \ that the string is a string of type CHAR0.  A flag of any other value
  409. \ indicates that the string passed is a CHAR string.
  410. \
  411. \ Usage:  stringvar0 7 Null.String Right$ ( Works for CHAR & CHAR0 Vars )
  412. \
  413. \ Note:   first = ptr to 1st char in string
  414. \
  415.     ( first check to see if he wants more chars than string has )
  416.  
  417.     n first length >
  418.     IF                   ( If TRUE, he wants more chars than we have )
  419.       first DUP flag Smart.Length           ( Return whole thing )
  420.     ELSE                 ( NOT asking for more chars than we have )
  421.       first DUP flag Smart.Length + 1-      ( pointer to last char )
  422.       n - 1+ n
  423.     THEN
  424. ;
  425.  
  426. : MID$ { first start n flag -- substring.ptr.start n }
  427. \
  428. \ This word should return ptrs to the substring starting at start and
  429. \ continuing for n chars.  Flag of zero indicates that string is a
  430. \ NULL-TERMINATED string (CHAR0).  Flag of any other value indicates that
  431. \ string passed is a COUNTED string (CHAR).
  432. \
  433. \ Usage: stringvar 8 4 Count.String MID$  ( Works for CHAR & CHAR0 Vars )
  434. \
  435. \ Note:  first = ptr to 1st char in string
  436. \        substring.ptr.start is a pointer to the first character in the
  437. \                            resulting substring.
  438. \        start = index into string; the point at which you want to start
  439. \                looking at the substring.  In the example, start at 8th
  440. \                char in string and continue for 4 characters.  8 is start.
  441. \                4 is n.
  442. \
  443.  
  444. [  String.Debug @
  445.    User.Debug   @
  446.    OR           ]
  447. .IF
  448.     start 0> NOT
  449.     IF          ( user has passed us a non-positive starting position )
  450.       CR
  451.       ." Error in MID$ -- Requested substring has start BEFORE beginning "
  452.       ." of string." CR
  453.       ." Starting value you passed to MID$ is: " start . CR
  454.       ." MID$ Abnormally terminating.   "   0SP
  455.       ABORT
  456.     THEN
  457.  
  458.     n 0> NOT
  459.     IF          ( user has passed us a non-positive character count )
  460.       CR
  461.       ." Error in MID$ -- You cannot have a non-positive character count!" CR
  462.       ." Character count you passed to MID$ is: " n . CR
  463.       ." MID$ Abnormally terminating.   "   0SP
  464.       ABORT
  465.     THEN
  466.  
  467.     ( Check to see if start is past end of string )
  468.  
  469.     first flag Smart.Length    start
  470.     < IF       ( if TRUE, start IS past end of string )
  471.  
  472. \       0 0    ( Return a null string )
  473. \ Notice that the above line is commented out.  That line shows the "true"
  474. \ behavior of MID$ on most systems.  I prefer to get an error message here
  475. \ because to me it is too difficult to track down what goes wrong in a
  476. \ program due to some wild value for a pointer or whatever.  So this
  477. \ version of the StringPkg stops you dead with an error here.  Change it
  478. \ if you like!  Perhaps rewrite so that user.debug flag can have levels and
  479. \ based on the level set by the user, this package in general will behave
  480. \ strictly about such situations or ignore them all-together.
  481.  
  482.         CR
  483.         ." Error in MID$ -- Requested substring has start past end of "
  484.         ." string." CR
  485.         ." Current length of string = " first flag Smart.Length . CR
  486.         ." You requested start of substring at character position: "
  487.         start . CR
  488.         ." MID$ Abnormally terminating.   "  0SP
  489.         ABORT
  490.     THEN
  491. .THEN
  492.  
  493.     ( Start is NOT past end of string )
  494.     first start + 1-          ( compute addr of start of substring )
  495.     first flag Smart.Length   ( length of source string )
  496.     start - 1+ DUP            ( # of chars from start to end of string )
  497.     n <=
  498.     IF                        ( Not n chars left in substring starting )
  499.                               ( at start.                              )
  500.     ELSE
  501.        DROP n                       \ There are at least n chars left
  502.     THEN  ;
  503.  
  504.  
  505. : Concat { ptr1  flag1  ptr2  flag2 -- }
  506.  
  507. \ Concatenate string2 to the end of string1.  The resulting string is
  508. \ stored in string1.  Flag1 is 0 if the first string is NULL-TERMINATED
  509. \ (CHAR0).  A non-zero value of this flag implies that string1 is a COUNTED
  510. \ string (CHAR).  Flag2 likewise lets this word know what type of string
  511. \ is represented there.
  512. \
  513. \ Usage:  xyz Null.String abc Count.String Concat
  514. \ Result: null-terminated string xyz becomes string xyz immediately followed
  515. \         by the counted string value of abc.  The null for xyz is placed
  516. \         at the end of the new string.
  517. \ Note:   ptr1 = ptr to 1st char in string1
  518. \         ptr2 = ptr to 1st char in string2
  519. \
  520. \ Note: If you want real speed and have counted strings only, try using
  521. \       JForth's $APPEND.  Be warned:  It does NO ERROR CHECKING!!
  522. \
  523.  
  524. [  String.Debug @
  525.    User.Debug   @
  526.    OR           ]
  527. .IF
  528.   ( First determine is string1 has a max. length big enough to hold result )
  529.   ( of concatentation. )
  530.   ptr1 flag1 Smart.Max.Length      ( Compute maximum length of string1 )
  531.   ptr1 flag1 Smart.Length          ( Compute current length of string1 )
  532.   ptr2 flag2 Smart.Length          ( Compute current length of string2 )
  533.   +                                ( Compute concatenated length       )
  534.   < IF                             ( TRUE if string1 too short         )
  535.       cr
  536.       ." Error in Concat -- Result of Concat would be too long." cr
  537.       ." Maximum length of destination string is: "
  538.       ptr1 flag1 Smart.Max.Length . cr
  539.       ." Proposed concatenation would require string of length: "
  540.       ptr1 flag1 Smart.Length   ptr2 flag2 Smart.Length + . ." bytes." cr
  541.       ." Concat Abnormally Terminating.   " 0SP  ABORT
  542.     THEN
  543. .THEN
  544.  
  545.   ( We know that the destination string [string1] is long enough )
  546.   ( Let's do the concatenation. )
  547.  
  548.   ptr2                             ( Addr of source of CMOVE coming up )
  549.   ptr1 dup flag1 Smart.Length +    ( Get addr of last char + 1 in string )
  550.                                    ( The CMOVE destination address )
  551.   ptr2 flag2 Smart.Length          ( The # of chars to move )
  552.   CMOVE                            ( Move the data )
  553.  
  554.   ( Now we must fix up the little stuff concerning the new string )
  555.   ( What we do depends on the type of string that string1 is )
  556.  
  557.   flag1 IF                         ( True if string1 is COUNTED )
  558.           ptr1 flag1 Smart.Length
  559.           ptr2 flag2 Smart.Length
  560.           +                        ( New length of string1 )
  561.           ptr1 1-                  ( Addr of current length of string1 )
  562.           C!                       ( Stash new length of string1 )
  563.         ELSE                       ( String1 is NULL-TERMINATED )
  564.           ptr1 flag1 Smart.Length
  565.           ptr2 flag2 Smart.Length
  566.           + 0 C!                   ( Put NULL at end of new string1 )
  567.         THEN  ;
  568.  
  569.  
  570. : S! { source.ptr  n  dest.ptr  flag  -- }
  571.  
  572. \ This is S-Store.  It will take a ptr to the first character of a string
  573. \ and copy count bytes to the string pointed to by dest.ptr.  It is a
  574. \ string assignment word.
  575. \
  576. \ Usage:    45 CHAR abc
  577. \           0" This is my string" dup Length0 abc Count.String S!
  578. \    OR     a dup length abc Count.String S! ( where a already has a value )
  579. \
  580. \ Note:     source.ptr = ptr to 1st char in source string
  581. \           n          = # of bytes to copy into destination string
  582. \           dest.ptr   = addr to store the first char of source string at
  583. \           flag       = type (0 --> null-term) of string var dest string is
  584. \
  585.  
  586. [  String.Debug @
  587.    User.Debug   @
  588.    OR           ]
  589. .IF
  590.  
  591.   ( First see if destination string can contain the source string )
  592.   dest.ptr flag Smart.Max.Length        ( Max. Length of dest. string )
  593.   n                                     ( Length of source string )
  594.   < IF                                  ( TRUE if source string too long )
  595.       CR ." Error in S! -- Source string too long for destination string."
  596.       CR ." Maximum length of destination string is: "
  597.       dest.ptr flag Smart.Max.Length .
  598.       CR ." You requested to transfer " n . ." bytes of source string."
  599.       CR ." S! Abnormally Terminating.   " 0SP  ABORT
  600.     THEN
  601.   ( Destination string is long enough )
  602.  
  603.   source.ptr flag Smart.Length n <
  604.   IF
  605.       CR ." Error in S! -- Attempt to assign " n . ."  characters from"
  606.       CR ."                source string when source string has only "
  607.       source.ptr flag Smart.Length .
  608.       CR ."                characters in it."
  609.       CR ." S! Abnormally Terminating.   " 0SP  ABORT
  610.   THEN
  611. .THEN
  612.  
  613.   source.ptr dest.ptr n CMOVE           ( Perform the assignment )
  614.  
  615.   ( Now, cleanup time which depends on type of string of destination )
  616.  
  617.   flag IF                               ( if TRUE, a COUNTED dest string )
  618.          n dest.ptr 1- C!               ( Stash current length )
  619.        ELSE                             ( a NULL-TERMINATED dest string )
  620.          0 dest.ptr n + C!              ( put null at end of dest string )
  621.        THEN  ;
  622.  
  623.  
  624. : Val ( addr -- d TRUE | FALSE )
  625. \
  626. \   addr is the address at which a count byte sits, with the byte after
  627. \   that being the first character of the string.
  628. \
  629. \   Take that string and try to convert it to a number in the current base.
  630. \   If successful, a TRUE is returned on top with a double length number
  631. \   (the number that was represented by the string) under it.
  632. \   If not successful, a FALSE is returned with nothing underneath.
  633. \
  634. \   Usage with CHAR strings:  30 CHAR MyString       ( Create MyString )
  635. \                             MyString 20 Get.String ( Get a String )
  636. \                             MyString Val           ( Convert to a number )
  637. \                             IF
  638. \                                you have a double length number to use here
  639. \                             ELSE
  640. \                                ." Bad number conversion in current base!"
  641. \                                ( But pick nicer wording than that! )
  642. \                             THEN
  643. \
  644. \   For those of you who don't need a double number to work with (you know
  645. \   the value is < 2^32), just do a drop upon returning from VAL and that
  646. \   will throw away 32 bits of the value 0, leaving a single length number
  647. \   on the stack.
  648. \
  649. \   Cannot use with CHAR0 strings (no count byte on them).  CHAR0 fans
  650. \   should assign their string to a counted string and call VAL (or of
  651. \   course, define your own equivalent of NUMBER? that works for CHAR0's.
  652. \
  653. \   To use with JForth native strings (i.e. counted strings, but not CHAR
  654. \   type strings), you must get the "normal" JForth addr of the string
  655. \   (which points to the count byte already), and do a 1+ before calling
  656. \   this word.  Easier is just to call NUMBER? in the case of JForth native
  657. \   strings.  That's really all this word is doing an it would save several
  658. \   cycles of overhead.
  659. \
  660.     1- NUMBER?  ;
  661.  
  662.  
  663. : Get.Number { 2 lo 2 hi -- d TRUE OR err# FALSE }
  664. \
  665. \   Get a signed number from user that is between lo and hi
  666. \   inclusive.
  667. \
  668. \   If the user's number attempt is invalid, err# will have a code
  669. \   indicating the nature of the problem with the user's input.
  670. \
  671.  
  672. [  String.Debug @
  673.    User.Debug   @
  674.    OR           ]
  675. .IF
  676.     hi lo D<
  677.     IF
  678.        >newline
  679.           ." Error in Get.Number.  Lo must be LESS than hi." cr
  680.        cr ." NOTE:  If both lo and hi are negative, they should be"
  681.           ." put on the stack with"
  682.        cr ."        the MOST negative number UNDER the LEAST"
  683.           ."  negative number." cr
  684.        cr ." You sent lo = " lo d. ."  and hi = " hi d.
  685.        cr ." Get.Number abnormally terminating."  0SP  ABORT
  686.     THEN
  687. .THEN
  688.  
  689.     GetNumWorkStr
  690.     19                         \ We want only 19 digits here since a 64-bit
  691.                                \ value can be a max of 19 digits.
  692.     Get.String
  693.     GetNumWorkStr Val          \ Try to convert to number in current base
  694.     IF                         \ TRUE if number is good so far
  695.        hi 1. D+
  696.        D< NOT
  697.        IF                      \ TRUE if value > hi
  698.           Value.Too.Big FALSE
  699.        ELSE                    \ We get here if value <= hi
  700.           GetNumWorkStr Val DROP
  701.           lo
  702.           D<
  703.           IF                   \ TRUE if value is below lo
  704.              Value.Too.Small
  705.              FALSE
  706.           ELSE                 \ We're OK.  Value checks out.
  707.              GetNumWorkStr Val
  708.              DROP              \ This HAS to succeed, it just did above
  709.              TRUE
  710.           THEN
  711.        THEN
  712.     ELSE                       \ We get here if not a number in current base
  713.        Not.A.Number FALSE
  714.     THEN ;
  715.  
  716.  
  717. : Instr ( adr1 n1 adr2 n2 -- adr.in.str1 | 0 )
  718. \   See doc in file ju:Match for information on this one.
  719. \   It is faster to call match? .  match? must use some relative branches
  720. \   because it can't be inline.
  721. \
  722. \   This works pretty much like its BASIC counterpart.
  723.     match? ;
  724.  
  725.  
  726. : S=    ( adr1 adr2 #bytes -- 0 | 1 | -1 )
  727. \
  728. \   Compare string starting at adr1 to string at adr2.
  729.     COMPARE ;
  730.  
  731.  
  732. : Lower.Case ( adr count -- )
  733. \
  734. \   Converts chars at adr to lower case for total of count characters.
  735. \
  736.     0 DO
  737.         DUP I +  DUP  C@  5  SET-BIT  SWAP  C!
  738.       LOOP DROP  ;
  739.  
  740.  
  741. : Upper.Case ( adr count -- )
  742. \
  743. \   Converts chars at adr to upper case for total of count characters.
  744. \
  745.     0 DO
  746.         DUP I + DUP  C@  5  CLR-BIT  SWAP C!
  747.       LOOP DROP  ;
  748.  
  749. Base !
  750.  
  751.