! ---------------------------------------------------------------------------- !
!   Stars.h
!       original  1.0 Nov00 by Roger Firth (roger@firthworks.com) for Inform 6
!       CAUTION:  This version has not been extensively tested.
!
! ---------------------------------------------------------------------------- !
!   Installation: add the line:
!
!       Include "Stars";
!
!   just about anywhere.
! ---------------------------------------------------------------------------- !
!   Stars implements a set of string-handling routines, with a slightly
!   BASIC-like feel; you might also consider Istring.h, which replicates
!   the ANSI C string functions.
!
!   The basic Z-machine supports some very limited string handling. Strings are
!   static -- encrypted and packed into high memory by the compiler -- such that
!   the only thing you can do with a string at run-time is to print it,
!   or unpack it to a set of bytes.
!
!   This package introduces the concept of a STring ARray: a 'star'. A star is
!   an array in which the first byte defines the star's maximum size, the second
!   byte specifies its current size, and the remaining bytes hold the individual
!   characters. The largest possible star can hold up to 254 characters.
!   As an example, here's a star with a capacity of eight characters which
!   currently holds the string "CLOAK":
!
!       +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
!       |  9  |  5  |  C  |  L  |  O  |  A  |  K  |  ?  |  ?  |  ?  |
!       +-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
!
!       Byte  0:   the maximum character capacity PLUS ONE.
!       Byte  1:   the current number of characters.
!       Bytes 2-6: the current characters.
!       Bytes 7-9: undefined.
!
!   To declare and initialize this star, you could use either of:
!
!       Array MyFirstStar ->  9  5 'C' 'L' 'O' 'A' 'K' 0 0 0;
!       Array MyFirstStar string 5 'C' 'L' 'O' 'A' 'K' 0 0 0;
!
!   However, this is pretty clumsy unless the star is very short, so instead
!   it's easier to declare an empty star of the right size, and then somewhere
!   else -- maybe in your Initialize() routine -- load the characters into it.
!
!       Array MyFirstStar string 9;     ! Maximum capacity is EIGHT characters
!       ...
!       LoadStar(MyFirstStar, "CLOAK");
!
!   Once you've declared your stars, you've got routines for assignment and
!   concatenation, input and output, comparison and subsetting. Note, however,
!   that the package /doesn't/ implement any form of dynamic memory management,
!   so you need to ensure that your intermediate and final results are always
!   being written somewhere sensible -- you can't blithely construct
!   arbitrarily-complex string expressions and expect them to work in all cases.
!
! ---------------------------------------------------------------------------- !
!   The supplied routines are:
!
!       toStar = LoadStar( [toStar], character_or_string )
!       toStar = SetStar( [toStar], fromStar1, [fromStar2, ... fromStar6] )
!
!       toStar = LeftStar( [toStar], fromStar, number )
!       toStar = RightStar( [toStar], fromStar, number )
!       toStar = MidStar( [toStar], fromStar, position, [number] )
!       toStar = LcStar( [toStar], fromStar )
!       toStar = UcStar( [toStar], fromStar )
!
!       toStar = InputStar( [toStar] )
!       toStar = InputStar2( [toStar] )
!       toStar = InputStar3( [toStar] )
!
!       number = InStar( subStar, fromStar, [position] )
!       number = CompStar( thisStar, thatStar )
!
!       number = PrintStar( star )
!       number = NumericStar( star )
!       buffer = ParseStar( star )
!
!       number = MaxStar( star )
!       number = LenStar( star )
!
!   All <position> parameters treat "0" as the first character.
!
! ---------------------------------------------------------------------------- !
!   Here's an example which uses several of the routines:
!
!   Array   FirstName string 21;
!   Array   LastName  string 21;
!   Array   FullName  string 41;
!
!   [ PromptForName
!       x y;
!       print "Testing the stars.h package.^";
!       do {
!           print "Please type your first and last names (eg John Smith).^";
!           x = InputStar();
!           y = ParseStar(x);
!           } until (y->1 == 2);
!       SetStar(FirstName, MidStar(0,x,y->5,y->4));
!       SetStar(LastName,  MidStar(0,x,y->9,y->8));
!       SetStar(FullName,  FirstName, LoadStar(0,' '), LastName);
!       if (CompStar(UcStar(0,FullName), LoadStar(0,"JOHN SMITH")) == 0)
!           "Hi there, John!";
!       else
!           print_ret "Hello, ", (PrintStar) FullName, ".";
!       ];
!
! ---------------------------------------------------------------------------- !
!   Working storage.
!
System_file;

Array   _Temp1Star string 255;  ! used by LoadStar()
Array   _Temp2Star string 255;  ! used by SetStar()
Array   _Temp3Star string 255;  ! used by LeftStar(),RightStar(),MidStar()
Array   _Temp4Star string 255;  ! used by LcStar(),UcStar()
Array   _Temp5Star string 255;  ! used by InputStar(),InputStar2(),InputStar3()
Array   _Temp6Star -> 50;       ! used by ParseStar()

! ---------------------------------------------------------------------------- !
!   Utility routine: Copy a sub-string of <fromStar> and append it to <toStar>.
!   <fromPos> is the start position in <fromStar> (default 0 is the start of <fromStar>).
!   <fromNum> is the sub-string length (default is <fromPos> to the end of <fromStar>).
!
[ AppendStar toStar fromStar fromPos fromNum
        toLen fromLen x;
        if (toStar == 0 || fromStar == 0) {
            #ifdef DEBUG; print "*** Star address not supplied. ***^"; #endif;
            rfalse;
            }
        @loadb fromStar 1 ->fromLen;
        if (fromPos < 0 || fromPos > 255 || fromNum < 0 || fromNum > 255) {
            #ifdef DEBUG; print "*** Source parameters out-of-range. ***^"; #endif;
            rfalse;
            }
        if (fromNum == 0) fromNum = fromLen - fromPos;
        if (fromPos+fromNum > fromLen) {
            #ifdef DEBUG; print "*** Source string too short. ***^"; #endif;
            rfalse;
            }
        @loadb toStar 1 ->toLen;
        x = toLen + fromNum;
        if (x > (toStar->0)-1) {
            #ifdef DEBUG; print "*** Destination string too short. ***^"; #endif;
            rfalse;
            }
        @storeb toStar 1 x;
        x = toStar;
        fromStar = fromStar + 2 + fromPos;
        toStar = toStar + 2 + toLen;
        @copy_table fromStar toStar fromNum;
        return x;
        ];

! ---------------------------------------------------------------------------- !
!   Accept as <zstr> a character constant 'z', a Z-code string "zzz",
!   or a routine returning either of those.
!   Return the converted result in <toStar> (default is _Temp1Star).
!
[ LoadStar toStar zstr
        x tmp; tmp = _Temp1Star;
        if (toStar == 0)  { toStar = tmp; @storeb toStar 0 255; }
        if (metaclass(zstr) == Routine) zstr = zstr();
        if (zstr < 128) { @storeb toStar 1 1; @storeb toStar 2 zstr; }
        else {
            @loadb toStar 0 ->x;              ! Preserve max length
            zstr.print_to_array(toStar);
            @storeb toStar 0 x;               ! And then restore it
            }
        return toStar;
        ];

! ---------------------------------------------------------------------------- !
!   Concatenate <fromStar1> ... <fromStar6>.
!   Return the result in <toStar> (default is _Temp2Star).
!
[ SetStar toStar fromStar1 fromStar2 fromStar3 fromStar4 fromStar5 fromStar6
        x tmp; tmp = _Temp2Star; @storeb tmp 0 255; @storeb tmp 1 0;
        if (fromStar1)      x = AppendStar(tmp, fromStar1);
        if (fromStar2 && x) x = AppendStar(tmp, fromStar2);
        if (fromStar3 && x) x = AppendStar(tmp, fromStar3);
        if (fromStar4 && x) x = AppendStar(tmp, fromStar4);
        if (fromStar5 && x) x = AppendStar(tmp, fromStar5);
        if (fromStar6 && x) x = AppendStar(tmp, fromStar6);
        if (x == 0) rfalse;
        if (toStar == 0) return tmp;
        @storeb toStar 1 0;
        return AppendStar(toStar, tmp);
        ];

! ---------------------------------------------------------------------------- !
!   Extract the leftmost <fromNum> characters of <fromStar>.
!   Return the result in <toStar> (default is _Temp5Star).
!
[ LeftStar toStar fromStar fromNum
        tmp; tmp = _Temp3Star; @storeb tmp 0 255; @storeb tmp 1 0;
        if (AppendStar(tmp, fromStar, 0, fromNum) == 0) rfalse;
        if (toStar == 0) return tmp;
        @storeb toStar 1 0;
        return AppendStar(toStar, tmp);
        ];

! ---------------------------------------------------------------------------- !
!   Extract the rightmost <fromNum> characters of <fromStar>.
!   Return the result in <toStar> (default is _Temp5Star).
!
[ RightStar toStar fromStar fromNum
        tmp; tmp = _Temp3Star; @storeb tmp 0 255; @storeb tmp 1 0;
        if (AppendStar(tmp, fromStar, (fromStar->1)-fromNum, fromNum) == 0) rfalse;
        if (toStar == 0) return tmp;
        @storeb toStar 1 0;
        return AppendStar(toStar, tmp);
        ];

! ---------------------------------------------------------------------------- !
!   Extract <fromNum> characters of <fromStar> starting at <fromPos>.
!   Return the result in <toStar> (default is _Temp5Star).
!
[ MidStar toStar fromStar fromPos fromNum
        tmp; tmp = _Temp3Star; @storeb tmp 0 255; @storeb tmp 1 0;
        if (AppendStar(tmp, fromStar, fromPos, fromNum) == 0) rfalse;
        if (toStar == 0) return tmp;
        @storeb toStar 1 0;
        return AppendStar(toStar, tmp);
        ];

! ---------------------------------------------------------------------------- !
!   Convert <fromStar> to lowercase.
!   Return the result in <toStar> (default is _Temp4Star).
!
[ LcStar toStar fromStar
        i tmp; tmp = _Temp4Star;
        if (fromStar == 0) rfalse;
        if (toStar ~= fromStar) {
            if (toStar == 0) { toStar = tmp; @storeb toStar 0 255; }
            @storeb toStar 1 0;
            if (AppendStar(toStar, fromStar) == 0) rfalse;
            }
        for (i=2 : i<2+(toStar->1) : i++)
            if (toStar->i >= 'A' && toStar->i <= 'Z')
                toStar->i = toStar->i -'A' + 'a';
        return toStar;
        ];

! ---------------------------------------------------------------------------- !
!   Convert <fromStar> to uppercase.
!   Return the result in <toStar> (default is _Temp4Star).
!
[ UcStar toStar fromStar
        i tmp; tmp = _Temp4Star;
        if (fromStar == 0) rfalse;
        if (toStar ~= fromStar) {
            if (toStar == 0) { toStar = tmp; @storeb toStar 0 255; }
            @storeb toStar 1 0;
            if (AppendStar(toStar, fromStar) == 0) rfalse;
            }
        for (i=2 : i<2+(toStar->1) : i++)
            if (toStar->i >= 'a' && toStar->i <= 'z')
                toStar->i = toStar->i -'a' + 'A';
        return toStar;
        ];

! ---------------------------------------------------------------------------- !
!   Locate sub-string <subStar> within <fromStar>, and return its start position.
!   Optionally, start scanning at <fromPos>. Return -1 if not found.
!
[ InStar subStar fromStar fromPos
        subLen fromLen i j;
        @loadb subStar 1 ->subLen;
        @loadb fromStar 1 ->fromLen;
        for (i=fromPos : i<=fromLen-subLen : i++) {
            for (j=2 : j<2+subLen : j++)
                if (subStar->(j) ~= fromStar->(i+j)) jump NextInStar;
            return i;
           .NextInStar;
            }
        return -1;
        ];

! ---------------------------------------------------------------------------- !
!   Compare <thisStar> against <thatStar>, returning -1, 0 or +1.
!
[ CompStar thisStar thatStar
        thisLen thatLen len i;
        @loadb thisStar 1 ->thisLen;
        @loadb thatStar 1 ->thatLen;
        if (thisLen > thatLen) len = thatLen; else len = thisLen;
        for (i=2 : i<2+len : i++)
            if (thisStar->i < thatStar->i) return -1;
            else if (thisStar->i > thatStar->i) return 1;
        if (thisLen < thatLen) return -1;
        if (thisLen > thatLen) return 1;
        return 0;
        ];

! ---------------------------------------------------------------------------- !
!   Output <star>.
!
[ PrintStar star
        i;
        for (i=2 : i<(star->1)+2 : i++) print (char) star->i;
        return star->1;
        ];

! ---------------------------------------------------------------------------- !
!   Input <toStar> from keyboard.
!   Disadvantage: uses status line.
!
[ InputStar toStar
        c i j max len row col csr tmp; tmp = _Temp5Star;
        if (toStar == 0) { toStar = tmp; @storeb toStar 0 255; }
        @set_window 1;   style reverse;
    .ReInput;
        @set_cursor 1 1; spaces (0->33);
        @set_cursor 1 1; print ">> ";
        row = 1; col = csr = 4;             ! Home cursor position
        max = (toStar->0)-1; len = 0;
        for (::) {
            @set_cursor row col;
            for (i=2 : i<2+len : i++) print (char) toStar->i;
            if (csr > col+max-1) csr = col+max-1;
            j = csr - col;                  ! Current position in toStar
            @set_cursor row csr;
            @read_char 1 c;
            switch (c) {
            32 to 126:                      ! Printable
                if (len < max) len++;
                for (i=max+1 : i>2+j : i--) toStar->i = toStar->(i-1);
                toStar->(2+j) = c;
                csr++;
            8:                              ! BS
                if (len == 0) continue;
                i = col+len-1; @set_cursor row i; @print_char ' ';
                if ((len < max) || (1+j < max)) { csr--; j--; }
                for (i=2+j : i<max+1 : i++) toStar->i = toStar->(i+1);
                len--;
            129,130:                        ! Cursor up,down
                jump ReInput;
            131:                            ! Cursor left
                if (j > 0) csr--;
            132:                            ! Cursor right
                if (j < len) csr++;
            10, 13, 27:                     ! LF, CR, ESC
                @set_window 0; style roman;
                @storeb toStar 1 len;
                return toStar;
                }
            }
        ];

! ---------------------------------------------------------------------------- !
!   Input <toStar> from keyboard.
!   Disadvantage: converts to lowercase.
!
[ InputStar2 toStar
        x tmp; tmp = _Temp5Star;
        if (toStar == 0) { toStar = tmp; @storeb toStar 0 255; }
        print ">> ";
        @loadb toStar 0 ->x; x--; @storeb toStar 0 x;
        read toStar 0;
        @loadb toStar 0 ->x; x++; @storeb toStar 0 x;
        return toStar;
        ];

! ---------------------------------------------------------------------------- !
!   Input <toStar> from keyboard.
!   Disadvantage: doesn't handle BACKSPACE properly.
!
[ InputStar3 toStar
        c max len tmp; tmp = _Temp5Star;
        if (toStar == 0) { toStar = tmp; @storeb toStar 0 255; }
        print ">> ";
        max = (toStar->0)-1; len = 0;
        for (::) {
            @read_char 1 0 0 ->c;
            switch (c) {
            32 to 126:                      ! Printable
                if (len == max) continue;
                len++;
                toStar->(1+len) = c;
                @print_char c;
            8:                              ! BS
                if (len == 0) continue;
                len--;
                @print_char '<';
            10, 13, 27:                     ! LF, CR, ESC
                @storeb toStar 1 len;
                new_line;
                return toStar;
                }
            }
        ];

! ---------------------------------------------------------------------------- !
!   Parse <star> as a number.
!   If non-numeric, return -32768 ($8000).
!
[ NumericStar star
        c i num sign;
        for (i=2 : i<(star->1)+2 : i++) {
            c = star->i;
            if (c == ' ' or ',' or '.') continue;
            if (c == '-' or '+')
                if (num == 0) { sign = c; continue; } else return $8000;
            if (c < '0' || c > '9') return $8000;
            if (num > 3200) return $8000;
            num = (num * 10) + c - '0';
            }
        if (sign == '-') num = 0 - num;
        return num;
        ];

! ---------------------------------------------------------------------------- !
!   Parse <star> into words, returning address of parse buffer. In the buffer:
!   Byte 0 is limit on input words, byte 1 is actual number of words found.
!   These are followed by four bytes for each word, in which:
!       first and second bytes are address of 'word' in dictionary, or zero.
!       third byte is number of characters in 'word'.
!       fourth byte is <position> in <star> of first character of 'word'.
!
[ ParseStar star
        i tmp; tmp = _Temp6Star;
        @storeb tmp 0 12;               ! Maximum number of input words
        @tokenise star tmp;
        for (i=tmp->1 : i>0 : i--) tmp->(4*i+1) = tmp->(4*i+1) - 2;
        return tmp;
        ];

! ---------------------------------------------------------------------------- !
!   Return the maximum length of <star>.
!
[ MaxStar star;
        if (star && star->0) return (star->0)-1; else rfalse;
        ];

! ---------------------------------------------------------------------------- !
!   Return the current length of <star>.
!
[ LenStar star;
        if (star) return star->1; else rfalse;
        ];

! ---------------------------------------------------------------------------- !


