String Functions
Updated: June 3, 2005

Return string result r$ based on applicable numeric or string arguments.
Arguments are described as "string", "number" (any type), "integer" 
(INTEGER, LONG, DWORD, VARIANT) and "immediate" number like 0, 1, etc.

STRING functions can be used on binary data.  Please see notes below.

NUMERIC functions with string arguments include: ASC, DIREXISTS,
FILEEXISTS, HEX2DW, INSTR, LEN, MESSAGEBOX, TALLY and VAL.

Function  Arguments, Comments, Examples
~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~     
[]        Square brackets are used to get a single byte from a string.

          r$ = MyString[i]  'i = 1 to MyString.Length 

BIN$      String of 1s and 0s for integer; r$ = BIN$(integer)

CHR$      One byte ASCII string for integer 0 - 255; r$ = CHR$(integer)

COMMA     Comma string; same as ","; r$ = COMMA: PRINT s$;COMMA;

COMMAND$  Command line item; r$ = COMMAND$(1) is first argument;
          r$ = COMMAND$(0) is application pathname.

  The command line may contain arguments as quoted strings with embedded
  spaces like "C:\Program Files\".

COMMANDLINE$
          Command line; r$ = COMMANDLINE$ 'entire command line

CRLF      CR and LF chars = CHR$(13)+CHR$(10) but faster; r$ = s$ + CRLF

CURDIR$   Current working directory; r$ = CURDIR$

DATE$     Date as string; r$ = DATE$

DELETE$   Deletes n characters starting at position i in string;
          n and i integers; r$ = DELETE$(string, i, n)
          If string.Length < i, then r$ = string

DIR$      First file with pathname mask string and attributes integer;
          r$ = DIR$(string, integer)
             
  r$ = Dir$("*.wav",0) 'gets first .wav file in current directory
  Goto DoWavFile
  NextWavFile:
  r$ = Dir$  'gets next .wav file if any
  DoWavFile:
  IF r$.Length THEN Call ProcessThisFile: Goto NextWavFile

  Note: With attributes integer 0 in the example above, all entries
  are returned including those with no attribute bits set.
  The attributes argument can be used to get volume label (&H1000),
  subdirectories (&10), read only (&H1), hidden (&H2),
  system (&H4), archive (&H20), normal (&H80), temporary (&H100),
  compressed (&H800) or any combination (by summing the hex flags).

ENVIRON$  Gets environment variable value, r$ = ENVIRON$(string)

  IF ENVIRON$("REQUEST_METHOD")="POST" THEN 
    k=VAL(ENVIRON$("CONTENT_LENGTH"))
    IF k<=0 THEN PRINT "Status: 400 No Content Posted": PRINT: Goto ProgEnd
    IF k>4096 THEN k=4096  'to protect from excessive content length
    q$=GET$(k)  
  ELSE
    q$=ENVIRON$("QUERY_STRING")  'if you expect one
  END IF

FIELD$    Gets field integer n (1 to ??) of string1 using delimiter string2 

  r$ = Field$(string1, string2, n) 'r$ = "" if field n does not exist

  DIM s$ As LIST: r$ = Field$(s$, CRLF, 5) 'same as r$ = s$.Item(4)

  Note: FIELD$ can be used on binary data as long as string2 uniquely
  marks fields.  E.g., if "NEXTIMAGE" never occurs in an image, then
  let's have some fun to show the kinds of things that can be done.

  DIM bmpsize As LONG, newbmp As STRING
  DIM bmp As STRING, imagelist As STRING 'or As MEMORY
  imagelist.LoadFromFile("MyGame.wad")   'MyGame soon to be top video game!
  bmp = Field$(imagelist, "NEXTIMAGE", 10) 'retrieves image 10 to bmp 
  bmpsize = bmp.Length  'just in case we want to know how many bytes it is
  IF bmpsize THEN PRINT "Yes, there is an image 10 there; our hero jumping."
  newbmp.LoadFromFile("new.bmp")
  IF INSTR(newbmp, "NEXTIMAGE") THEN PRINT "Problems! Delimiter in image!"
  imagelist = imagelist + newbmp + "NEXTIMAGE" 'add an image to the list

GET$      Get integer n characters from StdIn (or keyboard) without echo
          to StdOut (or screen); r$ = GET$(integer).

  Note: CGI programs use GET$.  GET$ is blocking; that is, control returns
  to program after n characters are received.  For CGI applications, if the
  server is worth its salt; it will send the characters.  CGI programs
  should not ask for more characters than the server indicates are available 
  with CONTENT_LENGTH.

HEX$      Hex string for integer number; r$ = HEX$(integer)

IIF +     Evaluates expression and returns either string1 (true) or
          string2 (false).  r$ = IFF(expression, string1, string2)

          r$ = IIF(2 > 1, "TRUE", "FALSE")  'returns "TRUE"
          r$ = IIF(a$ > b$, f1$, f2$)

  Note: IIF may also be used as a numeric function (please see hotnum).

INKEY$    Check for keyboard character and return immediately; r$ = INKEY$

  DO
  'code
  SLEEP 0.1 'You use 100% CPU time without the sleep; don't push your luck!
  r$ = INKEY$
  LOOP UNTIL r$ <> NULL

INPUT$    Input a keyboard string without a prompt; r$ = INPUT$ 

INSERT$   Inserts string2 into string1 at integer position n;
          r$ = INSERT$(string1, string2, n)
          If string1.Length < n, then string2 is appended to string1.

LCASE$    Lower case; r$ = LCASE$(string)

LEFT$     First integer n characters; r$ = LEFT$(string, n)

LTRIM$    Remove leading spaces; r$ = LTRIM$(string) 

MID$      Extract substring at integer position p of optional integer length n;
          r$ = MID$(string, p, n) or r$ = MID$(string, p)

  Note: can be used to extract fields from binary data STRINGs or MEMORYs.       

NULL      Null string of zero length (""); r$ = NULL: IF r$ <> NULL THEN ...

QUOTE     Double quote string; r$ = QUOTE + text$ + QUOTE

REPLACE$  In string1, replaces data with string2 at integer position n;
          r$ = REPLACE$(string1, string2, n)
          If string1.Length < n, then r$ = string1

REPLACESUBSTR$
          In string1, replaces all instances of string2 with string3;
          r$ = REPLACESUBSTR$(string1, string2, string3)

  Let's say you want multi-line text items in a LIST but CRLF is the delimiter
  used by the LIST object.  Try,

  DIM MayBeMultiLine As STRING, MyList As LIST
  'code to get data
  r$ = REPLACESUBSTR$(MayBeMultiLine, CRLF, "#")
  'I like "#" but CHR$(0) is flawless!
  MyList.AddItems r$
  'later in code
  r$ = MyList.Item(n)  'now we need this item
  r$ = REPLACESUBSTR$(r$, "#", CRLF)  'Now r$ is back to multi-line text
  
REVERSE$  Reverse order of string bytes; r$ = REVERSE$(string)

  'implement RINSTR like this from hotreg.bas:
  IF key$<>NULL THEN
    r$=REVERSE$(key$): i=INSTR(r$,"\")
    IF i THEN
      j=key$.length: key$=LEFT$(key$,j-i)
    ELSE
      key$=NULL
    END IF
  END IF

  Note:  The RINSTR function is in HotInclude as RINSTR.inc.

RIGHT$    Last integer n characters; r$ = RIGHT$(string, n)

RTRIM$    Remove trailing spaces; r$ = RTRIM$(string) 

SPACE     Space character = " "; r$ = SPACE: PRINT x; SPACE;

SPACE$    String of integer n space characters; r$ = SPACE$(n)

STR$      String representation of number; r$ = STR$(number): PRINT r$

  STR$ is 3 different functions for unsigned non-float, signed non-float
  and float.  If a variable is the argument, its type selects which
  conversion mode is used.  If the argument is an expression, the compiler
  may choose float mode (and you may get "0.999999..." instead of "1").

  If and only if the number is floating and the FPU is in an error state,
  then "ERROR" is returned by STR$.  Thus, prior to use of STR$ with a 
  floating argument, one can use FPU.Init or IF FPU.Error THEN FPU.Init.

  For signed and unsigned non-floating variables, the FPU is not used and
  a valid string conversion should always be returned by STR$.

STRING$   String of integer n characters of string; r$ = STRING$(n, string)

          r$ = STRING$(20, ".") 'twenty dots
          r$ = STRING$(256, CHR$(0)) '256 character buffer of 0 bytes

TAB       Tab string; same as CHR$(9); r$ = TAB: PRINT s$; TAB; t$

TIME$     Local time; r$ = TIME$

TRIM$     RTRIM$ and LTRIM$; r$=TRIM$(r$) 'remove leading and trailing spaces

UCASE$    Upper case; r$ = UCASE$(string)

  Note: RapidQ appears to use "atom" APIs for LISTs and comparisons for 
  duplicates are not case-sensitive.  In HotBasic, LIST lookups are
  case-sensitive (HotBasic does all its own LIST functions).  Thus, for
  non-case-sensitive LIST lookups, try

  DIM MyList As LIST
  MyList = UCASE$(MyList)

  'Now let's avoid duplicates by a lookup before adding items
  IF MyList.IndexOf(r$) < 0 THEN MyList.AddItems r$ '.IndexOf = -1 if r$ absent

VARPTR$   String at integer address; r$ = VARPTR$(integer)

WINDIR$   Windows directory; r$ = WINDIR$

WINDOW +  Gets FORM object string property by object handle.

  r$ = WINDOW(handle).Caption  'get property

  Syntax:  WINDOW(handle[,qualified_type]).property

  The optional qualified_type argument is needed in special cases where
  the compiler needs this information to generate special code in your
  application.  If handle alone does not work, add the qualified type.

  r$ = WINDOW(handle, GRID).Cell(i,j)


#############

Generally, the string functions are not "text dependent".  That is, fields in
binary data can be extracted (MID$), inserted, deleted, replaced, etc, with
string functions.  E.g.,

  DIM M As MEMORY, i As DWORD
  M = MID$(binary$,8,4): M.Position = 0: i = M.ReadNum(4)

extracts 4 bytes of binary data at position 8 from binary$ and assigns the
value to DWORD variable i.  For a single byte, i = ASC(binary$[j])
where j = offset + 1.

The foregoing may be cute, but is it sexy?  To be a HotBasic stud or babe,
try this one-liner:

  MEMCPY @i, @binary$+8, 4  'extracts 4 bytes at offset 8 to DWORD var i

Sounds like the kind of thing one might do in extracting fields from raw
network packets.  For network protocols, packet data may consist of values
of different length, identified only by position.  So an ARRAY with fixed
item length is troublesome.  MID$, however, does it all.

The other string functions can also be looked at in this light for perhaps
novel uses, taking advantage of the "low level" treatment of string data
built into HotBasic.

For situations like this, one can also define a TYPE where the "fields" in a
binary string are listed.  Then, for example,

  TYPE IP_HEADER
    'ip header items named and associated with a data length: byte, word, etc
    DestIP As DWORD
    'etc
  END IP_HEADER

  Dim header as IP_HEADER, sock as SOCKET, recv$ As STRING
  'set up other items to read a data packet into recv$, then,
  header = LEFT$(recv$,20) 'assuming header length of 20 bytes
  DestIP$ = sock.AddrToStr(header.DestIP)
  'etc ... well, you get the idea.


#############

COMMAND$(), COMMANDLINE$, DATE$, ENVIRON$, TIME$, etc.  win32.hlp states that
string pointers obtained from the OS do not have permanence and that the data
should be copied.  HotBasic copies such data to application temporary buffers.
However, such strings still do not have permanence for use as arguments in
other functions which may use the same buffers.  Thus, for complete
reliability, strings from the OS should be assigned to user-dimensioned
variables.  E.g., MyDate$=DATE$.  Then, use MyDate$ as an argument.


+ Penthouse (registered) version

Copyright 2003-2005 James J Keene PhD
Original Publication: Oct 8, 2003
