\ Author GPS
\ Written for Ficl 4.0.x (it uses the Johns-Hopkins locals)
\ This implements a string structure and various
\ words for manipulating and comparing string structures.

vocabulary string

also string definitions

: string->value ;
: string->value! ! ;
: string->value@ @ ;

: string->length 1 cells + ;
: string->length! string->length ! ;
: string->length@ string->length @ ;

: string-init ( string-bytes length ) { 1:s }
  dup s string->length!
  dup allocate if abort" out of memory for string->init" then
  dup s string->value!
  swap move 
  s ;

: string ( string-bytes length -- string-instance )
  2 cells allocate if abort" out of memory" then
  string-init ;

: string-free ( string-instance )
  dup string->value@
  dup if
    free if S" string-free value failed" type cr then 
  else 
    drop
  then
  free if S" string-free failed" type cr then ;

: string-index ( index -- char ) { 1:s }
  \ first check if the index is within the bounds
  dup 0 s string->length@ within 
  if
    chars s string->value@ + c@
  else
    abort" string-index out of range"  ( XXX should we use throw ? )
  then ;

: string-emit { 1:s }
  s string->value@
  s string->length@
  type ;

: string-compare { 1:a 1:b }
  a string->value@ a string->length@
  b string->value@ b string->length@
  compare ;

: string-equal ( a b -- flag )
  string-compare 0= ;

: string-first-from { 1:s 1:c 1:index }
  -1 ( the default result AKA not found )
  s string->value@
  index 0 s string->length@ within
  if
    index + ( account for the initial index offset )
    s string->length@ index do
      dup c@ c = if drop drop i unloop exit then 1 chars +
    loop
    drop
  else 
    abort" index out of range in string-first-from"
  then ;

: string-first ( string-instance char ) 
  0 string-first-from ;

: string-count { 1:s 1:c }
  0 ( the count )
  s string->value@
  s string->length@ 0 ?do
    dup c@ c = if swap 1 + swap then 1 chars +
  loop
  drop ;

: string-min-length ( a b ) 
  string->length@ swap string->length@ min ;

: string-char-equal ( a-addr b-addr -- flag )
  c@ swap c@ = ;

: string-prefix-equal { 1:a 1:b | 1:result }
  a string->value@ b string->value@
  a b string-min-length 
  dup 0 > if true to result else 0 to result then
  0 ?do 
    2dup string-char-equal 0 = if unloop 2drop 0 exit then
    1 chars + swap 1 chars + 
  loop 
  2drop
  result ;

previous

Code from the test suite:

S" hello" string 
variable mystring
mystring !

: test 
  mystring @ string->length@ 0 do
    i dup . cr mystring @ string-index 
  loop ;

test

S" passed test phase 1" type cr

mystring @ string-free

S" passed test phase 2" type cr

S" boingy boingy boingy" string
mystring !

S" string value: " type mystring @ dup string->value@ swap string->length@ type cr

S" passed test phase 3" type cr

mystring @ string-free

S" I like cheese!" string  mystring !

mystring @ string-emit cr

S" passed test phase 4" type cr

S" bella" string  S" bellb" string  string-compare -1 <> [if]
   abort" failed compare 1" [then]

S" bella" string  S" bella" string  string-compare 0 <> [if] 
   abort" failed compare 2" [then]

S" bellb" string  S" bella" string  string-compare 1 <> [if]
   abort" failed compare 3" [then]

S" abc" string  S" abc" string  string-equal 1 = [if]
   abort" failed string-equal" [then]

S" Hello Herbert" string char t 0 string-first-from 12 <> [if]
   abort" failed string-first-from 1" [then]

S" Hello Herbert" string char H 1 string-first-from 6 <> [if]
   abort" failed string-first-from 2" [then]

S" Hello Herbert" string char H 0 string-first-from 0 <> [if]
   abort" failed string-first-from 3" [then]

S" Hello Herbert" string char z string-first -1 <> [if]
   abort "failed string-first" [then]

S" passed test phase 5" type cr

S" passed all tests" type cr

Usage:

loading string.f 

ok> also string
ok> S" Hello" string
ok> 2 over string-index
ok> emit cr
l
ok> dup char l string-count .
2 ok> string-free