String Functions
Updated: Aug 28, 2008

Return string result r$ based on applicable numeric or string arguments.
Arguments are described as "string", "number" (any type), "integer" 
(INTEGER, LONG, DWORD) 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 

ANSI$     8-bit encoded string from wide 16-bit encoded string

          r$ = ANSI$(MyWideString)

ARRAYREF$ +
          Reads string array value based on a pointer to the array object
          and the element subscripts.

          r$ = ARRAYREF$(lpArrayObject, subscripts)

  where lpArrayObject is obtained with the OBJPTR() Numeric Function and
  subscripts is a comma-delimited list of numeric values as might be used in
  reading a conventional array value.

  DIM A(99,99) As STRING*32
  DEFINT Aptr = OBJPTR(A)
  DEFINT i, j
  'code to set values for i and j  
  r$ = ARRAYREF$(Aptr,i,j)  'same as r$ = A(i,j)

  Notice you can pass an OBJPTR() value as an argument to a procedure which
  may read/write array values based on a pointer, not the dimensioned name of
  the array.

  Please see the ARRAYREF$() statement in Advanced Techniques.

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

BYREF$    Alias for VARPTR$ described below

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)  'get 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;

          r$ = DELETE$(string, i, n)  'i and n are integer expressions

          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).

  Example: Attribute mask &H27 returns ordinary files only.

  Linux:  DIR$() does not use a "file mask" string, only a path.  Example:

  $IFDEF LINUX
    $define slash "/"  'Linux
    $define DIR_FLAG &H4000
  $ELSE
    $define slash "\"  'Windows
    $define DIR_FLAG &H10
  $ENDIF
  DEFINT IsDir
 
  'code

  IF p$[p$.Length]<>slash THEN p$.append slash  'path has trailing slash
  $IFNDEF LINUX
  p$.append "*.*"  'use file mask only on Windows
  $ENDIF
  r$ = Dir$(p$,0)

  Notice "*.*" is used only in the Windows version; Linux version uses the
  path only with trailing slash.  We can filter files and sub-directories:

  next_dir_item:
  IF r$.Length then
    IF r$[r$.Length]="." THEN Goto next_dir_item  'reject these
    IF FILEREC.ATTR AND DIR_FLAG THEN
      r$.append slash: IsDir=1
    ELSE
      IsDir=zero
    END IF
  'etc
  END IF

  determines if the item is a file (IsDir = 0) or directory (IsDir = 1).
  
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.

GETGTK$   Gets string value for property of FORM object by handle

          r$ = GETGTK$(handle, string property)

          Linux GTK-mode only; example:  r$ = GETGTK$(myEdit,"text")

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: There is an IIF numeric function (please see Numeric Functions).

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$ 

  Note: for keyboard input of SQL commands which might contain embedded ","
  characters, use the INPUT$ function rather than the INPUT statement which
  parses the input line as a comma-delimited list of values.

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) 'returns n bytes of string starting at p
          r$ = MID$(string, p)    'returns remainder of string starting at p

  Note: can be used to extract fields from STRING or MEMORY binary data.       

NULL      Null string of zero length (""); r$ = NULL

  Note: IF r$ <> NULL THEN ...  'Note IF r$.len THEN ... is faster

PATH$     Path of application including trailing "\", derived from COMMAND$(0).
          CHDIR PATH$  'set application directory as working directory.
          Same as APP.Path 

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

VARIANTREF$
          Accesses VARIANT data by pointer; r$ = VARIANTREF$(integer)
          where integer is a pointer to a VARIANT.

  VariantRef$() reads VARIANT data by reference, useful when functions in other
  modules may provide a result as a pointer to a VARIANT.

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

WIDE$     16-bit encoded string from 8-bit encoded string

          r$ = WIDE$(My8BitString)

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-2007 James J Keene PhD
Original Publication: Oct 8, 2003
