'===========================================================================
' Subject: SUPER EMS                          Date: 12/30/92 (00:00)       
'  Author: Quinn Tyler Jackson                Code: PDS, VBDOS             
'  Origin: SUPER,EMS                        Packet: MEMORY.ABC
'===========================================================================
' JackMack SuperArray Management Kit v1.0
' Released into the public domain on 30 December 1992
' Written by Quinn Tyler Jackson, JackMack Consulting & Development

' This array management tool is programmed for VBDOS 1.0, but may
' be fully compatible with BASIC PDS 7.x.  It uses advanced features
' not found in QuickBASIC 4.5, but these features MAY be worked out
' by enterprising programmers.  Words to look for in the source code
' include: PRESERVE.

' Features of this application:

' INTEGER and LONG numeric arrays can be stored in EMS memory, leaving
' space free for bigger and better things.  Arrays are referenced not by
' obscure numbers and handles, but by user assigned names that may include
' ANY character.  That means that an array COULD conceivably be called
' "This is my array."

' STRING arrays are stored to a to virtual memory file, and are variable
' length.  Only their pointers are stored in RAM, and even these are stored
' safely out of the way in EMS.  In short, as long as disk space allows,
' one could have a 300,000 element string array, each element being between
' one and 32000 some odd characters long, and it wouldn't take up any more
' of DGROUP or far string space than any other STRING JmArray.
' Also note that STRING arrays are compressed onto the virtual disk file
' if they do not contain high-ASCII characters, to conserve disk space.

' Some academic points illustrated by this program:

'   1) Pointer referencing,
'   2) End user modifiable array names,
'   3) "Handle-based" arrays,
'   4) Virtual memory.

' NOTE: To allow for INTEGER and LONG values to be passed back from the
'       same function that returns STRING values, all values are passed
'       back as STRING.  They must be converted thus:
'
'       ErrorCode = JmSET ("My array", 10, "100")
'       IntegerValue = VAL(JmGet ("My array",10))
'
'       This is unfortunate, but allows one function to return ALL types
'       of data, not just one per function.

' These seven routines are from Hanlin's PBCLONE 1.9 library.  Earlier
' versions of PBCLONE might work, too.

DECLARE FUNCTION IsASCII% (Ch$)
DECLARE FUNCTION StrSqu$ (St$)
DECLARE FUNCTION StrUnSq$ (St$)
DECLARE SUB EMSClose (BYVAL ArrayHandle%)
DECLARE SUB EMSOpen (Elements&, ElementType%, ArrayHandle%, ErrCode%)
DECLARE SUB EMSGet (BYVAL ArrayHandle%, ElementNr&, Value AS ANY)
DECLARE SUB EMSPut (BYVAL ArrayHandle%, ElementNr&, Value AS ANY)

' These routines are local to this particular program.
DECLARE FUNCTION IsAllASCII (Txt$) AS INTEGER
DECLARE FUNCTION JmDIM% (ArrayName$, Elements AS LONG, ArrayType%)
DECLARE FUNCTION JmWORD (InExpression$, Index%) AS STRING
DECLARE FUNCTION JmSET% (ArrayName$, Element AS LONG, Vlue AS STRING)
DECLARE FUNCTION JmERASE% (ArrayName$)

OPTION BASE 1 ' I prefer things to start at one.  Humans tend to count that
              ' way, don't you agree?

'Some system constants.
CONST BUFFER_MAX = 10           ' How many previously read strings to
buffer.
CONST VirtualFile = "JMVSA.$$$" ' Virtual string memory file.
CONST StartSize = 10            ' Initial size of pointer array.

'Ye olde tradional Boolean logic constants
CONST TRUE = (1 = 1)            ' I prefer (1=1) since it is compiler
                                ' independent, whereas -1 is specific to
                                ' MS BASICS.
CONST FALSE = NOT TRUE

' Array Types
CONST Array_Integer = 1
CONST Array_Long = 2
CONST Array_String = 3

' Errors that might happen
CONST Err_EMS_Allocation = -1
CONST Err_Bad_Subscript = -2
CONST Err_Array_Not_Dimensioned = -3
CONST Err_Overflow = -4
CONST Err_DOS_Error = -5

' PointerType for the array cross-reference table.
TYPE PointerType  Elements AS LONG  ' How many array elements array has.

    Handle AS INTEGER               ' EMS handle of either data or ptr table
                                    ' (String arrays use an EMS ptr table).

    ArrayType AS INTEGER            ' What type of array we're dealing with.

    Accesses AS LONG                ' How many times this array is accessed.
END TYPE

DEFINT A-Z
'$DYNAMIC arrays are going to be used so they can be redimensioned.

' PtrArray changes size and must be preserved when it does so. Therefore,
' QuickBASIC users might have to rethink the logic I have used throughout.
DIM SHARED PtrArray(StartSize) AS PointerType

REDIM SHARED AName$(StartSize)          ' Names of arrays.
DIM SHARED VirtualHandle AS INTEGER     ' Handle of virtual memory file.

' The simple sample application to show syntax follows here.  Normally, your
' program would go here....


CLS
' A 300,000 element string array!  Requires lots of EMS for pointers! A
' one million element array would require 4 Megs of free EMS, but wouldn't
' take up any more DGROUP or conventional memory than a two element array!

INPUT "This array can have any name you'd like: ", Array$
' Arrays can be named at the end-user level.  This is good for database
' applications and is a powerful feature.  The user is not forced to refer
' to his specific data by any contrived name other than the one he or she
' assigns!

nul = JmDIM(Array$, 1000, Array_String)
IF nul < 0 THEN PRINT "ERROR": END
PRINT nul
PRINT "Getting data from array '" + Array$ + "'."
nul = JmSET(Array$, 1000, "This is a test.  The test seems to have worked.")

PRINT JmGET(Array$, 1000, ErrCode)

nul = JmERASE("*") 'Be sure to do this to free EMS handles and memory!

FUNCTION IsAllASCII (Txt$) AS INTEGER
FOR scan = 1 TO LEN(Txt$)
    IF NOT IsASCII(MID$(Txt$, scan, 1)) THEN
        IsAllASCII = FALSE
        EXIT FUNCTION
    END IF
NEXT
IsAllASCII = TRUE
END FUNCTION

FUNCTION JmDIM (ArrayName$, Elements AS LONG, ArrayType) AS INTEGER
STATIC ArrayPtr AS INTEGER

' Get First Available spot in list.
FOR scan = 1 TO UBOUND(PtrArray)
    IF AName$(scan) = "" THEN
        ArrayPtr = scan
        Flag = TRUE
        EXIT FOR
    END IF
NEXT scan

IF NOT Flag THEN ' We have to make room for a new array, since no spots left.
    ArrayPtr = UBOUND(PtrArray) + 1
    REDIM PRESERVE PtrArray(ArrayPtr) AS PointerType
    REDIM PRESERVE AName$(ArrayPtr)
END IF


SELECT CASE ArrayType
    CASE Array_Integer, Array_Long
        AName$(ArrayPtr) = ArrayName$
        PtrArray(ArrayPtr).Elements = Elements
        PtrArray(ArrayPtr).ArrayType = ArrayType
        EMSOpen Elements, Array_Type, Handle, ErrCode
        PtrArray(ArrayPtr).Handle = Handle
        IF ErrCode THEN
            JmDIM = -1
            EXIT FUNCTION
        ELSE
            JmDIM = Handle
            EXIT FUNCTION
        END IF

    CASE Array_String, Array_Compressed
        AName$(ArrayPtr) = ArrayName$
        PtrArray(ArrayPtr).Elements = Elements
        PtrArray(ArrayPtr).ArrayType = ArrayType
        IF NOT VirtualHandle THEN 'we haven't opened the virtual file yet.
            VirtualHandle = FREEFILE
            OPEN VirtualFile FOR BINARY AS VirtualHandle
        END IF
        ' This EMS array is an array of POINTERS to file offsets.
        EMSOpen Elements, Array_Long, Handle, ErrCode
        PtrArray(ArrayPtr).Handle = Handle
        IF ErrCode THEN
            JmDIM = -1
            EXIT FUNCTION
        ELSE
            JmDIM = Handle
            EXIT FUNCTION
        END IF
END SELECT

END FUNCTION

FUNCTION JmERASE (ArrayName$)

IF ArrayName$ <> "*" THEN ' The asterix is intended to erase ALL JmArrays!!
    FOR scan = 1 TO UBOUND(PtrArray)
        IF ArrayName$ = AName$(scan) THEN
                'Release EMS being used by array.
                EMSClose PtrArray(scan).Handle
                'Show the name as blank so that it is freed for future use.
                AName$(scan) = ""
                Flag = TRUE
                EXIT FOR
        END IF
    NEXT scan
    IF NOT Flag THEN
        ' We tried to ERASE an array that didn't exist.  Names ARE
        ' case sensitive, so "Quinn" and "quinn" are different.
        JmERASE = Err_Array_Not_Dimensioned
        EXIT FUNCTION
    END IF
ELSE
    CLOSE VirtualHandle ' Close the virtual string file and
'    KILL VirtualFile    ' get rid of it.

    FOR scan = 1 TO UBOUND(PtrArray)
        IF AName$(scan) <> "" THEN
            'Release EMS used by array.
            EMSClose PtrArray(scan).Handle
        END IF
    NEXT scan

    REDIM PtrArray(1) AS PointerType
    REDIM AName$(1)
    VirtualHandle = 0
END IF
END FUNCTION

FUNCTION JmGET (ArrayName$, Element AS LONG, ErrCode AS INTEGER) AS STRING
STATIC BufferPtr

FOR scan = 1 TO UBOUND(PtrArray)
    IF ArrayName$ = AName$(scan) THEN
        IF Element > PtrArray(scan).Elements THEN
            ErrCode = Err_Bad_Subscript
            EXIT FUNCTION
        END IF
        SELECT CASE PtrArray(scan).ArrayType
            CASE Array_Integer
                EMSGet PtrArray(scan).Handle, Element, TempInt%
                JmGET = STR$(TempInt%)
                Flag = TRUE
                EXIT FOR

            CASE Array_Long
                EMSGet PtrArray(scan).Handle, Element, TempLong&
                JmGET = STR$(TempLong&)
                Flag = TRUE
                EXIT FOR

            CASE Array_String
                EMSGet PtrArray(scan).Handle, Element, EndPtr&

                ON LOCAL ERROR GOTO DOSErrorGet
                    ' First find the right spot in virtual file.
                    SEEK VirtualHandle, EndPtr&
                    ' Then find out how much data to read from file.
                    GET VirtualHandle, , Leng%
                    ' Then prepare an adequate buffer.
                    Buffer$ = SPACE$(ABS(Leng%))
                    ' And finally suck it in through the straw.
                    GET VirtualHandle, , Buffer$
                ON LOCAL ERROR GOTO 0

                ' Negative lengths indicate previous compression.
                IF Leng% < 0 THEN Buffer$ = StrUnSq(Buffer$)
                JmGET = Buffer$
                Flag = TRUE
                EXIT FOR
        END SELECT
    END IF
NEXT scan

IF NOT Flag THEN
    ErrCode = Err_Array_Not_Dimensioned
    EXIT FUNCTION
END IF

EXIT FUNCTION

DOSErrorGet:
' Something happened that had to be trapped.
ErrCode = Err_DOS_Error
EXIT FUNCTION

END FUNCTION

FUNCTION JmSET (ArrayName$, Element AS LONG, Vlue AS STRING)
FOR scan = 1 TO UBOUND(PtrArray)
    IF ArrayName$ = AName$(scan) THEN
        IF Element > PtrArray(scan).Elements THEN
            JmSET = Err_Bad_Subscript
            EXIT FUNCTION
        END IF
        SELECT CASE PtrArray(scan).ArrayType

            CASE Array_Integer
                TempInt& = VAL(Vlue)
                IF TempInt& > 32768 OR TempInt& < -32768 THEN
                    ' Someone forgot his BASIC basics.
                    JmSET = Err_Overflow
                    EXIT FUNCTION
                END IF
                TempInt% = TempInt&
                ' Stuff it up there in EMS land.
                EMSPut PtrArray(scan).Handle, Element, TempInt%
                Flag = TRUE
                EXIT FOR

            CASE Array_Long
                TempLong& = VAL(Vlue)
                EMSPut PtrArray(scan).Handle, Element, TempLong&
                Flag = TRUE
                EXIT FOR

            CASE Array_String

                ' New string assignments added to end of virtual file.
                EndPtr& = LOF(VirtualHandle) + 1
                EMSPut PtrArray(scan).Handle, Element, EndPtr&
                ON LOCAL ERROR GOTO DOSErrorSet
                    SEEK VirtualHandle, EndPtr&
                    ' Add the string length to the string for later use.
                    SELECT CASE IsAllASCII(Vlue)
                        CASE TRUE
                            'Compress string.
                            Vlue = StrSqu(Vlue)
                            ' Make it < 0 if compressed.
                            Vlue = MKI$(-LEN(Vlue)) + Vlue
                        CASE ELSE
                            Vlue = MKI$(LEN(Vlue)) + Vlue
                    END SELECT
                    PUT VirtualHandle, , Vlue
                ON LOCAL ERROR GOTO 0
                Flag = TRUE
                EXIT FOR

        END SELECT
    END IF
NEXT scan

IF NOT Flag THEN
    JmSET = Err_Bad_Array_Name
    EXIT FUNCTION
END IF

EXIT FUNCTION
DOSErrorSet:
    JmSET = Err_DOS_Error
    EXIT FUNCTION

END FUNCTION
