home *** CD-ROM | disk | FTP | other *** search
- (10U
-
-
- Clipper Multi-dimensional Arrays
-
- by Roger Donnay
-
- A. Introduction
- B. The Evolution of the Multi-dimensional array
- C. Classes of Multi-dimensional arrays
- 1. Parallel symmetrical arrays
- 2. Ragged symmetrical arrays
- 3. Asymmetrical arrays
- D. Working with Linked arrays
- E. Browsing a Multi-dimensional array
- F. Saving and Restoring Multi-dimensional arrays
-
-
- ╔════════════════╗
- ║ INTRODUCTION ║
- ╚════════════════╝
-
-
- In my opinion, the single most powerful new feature of Clipper
- 5.0 is the multi-dimensional array system. Sometimes referred
- to as "ragged arrays", this incredibly versatile data type is
- almost unlimited in capability and opens the door to a new
- kind of programming just not possible in Summer 87.
-
- This seminar explores the use of ragged arrays from a practical
- and theoretical viewpoint and is directed to the Clipper
- programmer who is still discovering the power of Clipper 5.0.
- I don't usually like to use a lot of code snippets in my
- seminars, but teaching multi-dimensional arrays is simplified
- by using lots of example code, so feel free to use the code
- provided. Much of the code examples are extracted from the
- dCLIP libraries.
-
-
- ╔═══════════════════════════════════════════════╗
- ║ THE EVOLUTION OF THE MULTIDIMENSIONAL ARRAY ║
- ╚═══════════════════════════════════════════════╝
-
-
- Clipper Summer 87 supported "single-dimensional" arrays. The
- data structure allowed the programmer to store information
- in a structure called data "elements". Most languages which
- support arrays must first pre-define the length of the array
- (number of elements) and the length and type of each element
- of the array. This is because memory must be pre-allocated
- to allow for storage of data in the array elements. This
- memory allocation at compile/link time is a type of "early
- binding" to insure that memory will always be available
- during the running of the application.
-
- Clipper Summer 87 attempted to improve on this concept by
- eliminating the need to pre-define the type and size of each
- element of the array so the programmer could change any element
- at will. This new kind of "dynamic array" made programming
- much easier and more powerful, but the programmer now needed
- to consider the effects that this dynamic memory allocation
- may have on the use of the memory pool at run-time. Dynamic
- arrays that were not properly managed could easily run the
- program out of memory. Fortunately, this did not occur often
- in Summer 87 because even though any element of the array
- could be accessed via one (1) symbol declaration (the array
- name), the memory allocation and de-allocation for the elements
- was handled on an individual basis.
-
- Example of a single-dimensional array (S87 code):
-
- * make an array of 5 elements
- DECLARE myArray[5]
- * initialize each element as a null string value
- AFILL( myArray, "" )
- * make element 2 a date value
- myArray[2] = DATE()
- * make element 4 a numeric value
- myArray[4] = 234.56
-
- The array (myArray) will now look like this:
-
- myArray[1] = ""
- myArray[2] = 10/12/92
- myArray[3] = ""
- myArray[4] = 234.56
- myArray[5] = ""
-
- Probably the single most significant advantage to using arrays
- in Summer 87 was that any element of an array could be used
- in expressions exactly like any memory variable. Another
- significant advantage, but maybe not so obvious, was how using
- arrays instead of memvars reduced the memory requirements of
- the application. This is because an entire array uses only
- one symbol, whereas each memory variable uses a separate symbol.
-
- -- Why multi-dimensional arrays? --
-
- Arrays in Summer 87 made the programmer's life much simpler.
- Scatter-gather routines, get lists, pick-lists, browse-screens,
- menus, etc. became easier to program using arrays because the
- size of the array and each element could be determined during
- the actual running of the application. Arrays, like databases,
- are most effectively used and maintained when they are normalized.
- "Normalizing" a database is simply a term describing the careful
- structuring of a set of relational databases so that information
- can be stored in related groups (ie, invoices, customers, etc.)
- with little or no data redundancy. Good programming practices
- require that arrays also be structure in sets of related
- information. For example, creating an array-driven browse-system
- would require the use of many arrays for storing information
- like (1) screen-coordinates, (2) field-lists, (3) column-widths,
- (4) data-buffers, (5) relations, etc..etc. Summer 87 provided
- the ability to create many different arrays, but no easy
- mechanism to further relate these sets of arrays to each other.
- In the above example, each array had to be given a separate name.
- If you wished to save and restore a complete browse configuration,
- the process was difficult and impractical and required that the
- information be stored and retrieved from databases or kept alive
- as PUBLIC arrays and symbols.
-
- I attempted, with a good degree of success, to eliminate the
- separately defined arrays in my browse system and tie all the
- groups of information together into a single array which was a
- "psuedo" multi-dimensional array. I found I could eliminate
- the separate arrays and group everything together into one
- array by maintaining a group of offset pointers into one long
- array. For example, elements 1-4 would be the screen coordinates,
- 5-55 the field names, 56-106 the column order, etc. A sample of
- code that accessed an element of this "psuedo" multi-dimensional
- array would look like this:
-
- fieldname = aBrowse[ columns[ columnOffset + currentcol ] ] )
-
- A psuedo-multi-dimensional array would be treated like this:
-
- DECLARE myArray [ 4 + fcount()*2 ]
- fieldoffset = 4
- columnoffset = 4 + fieldoffset
-
- * put default screen coordinates into array
- myArray [1] = 0
- myArray [2] = 0
- myArray [3] = 24
- myArray [4] = 79
-
- * fill field name area of array
- for i = 1 to fcount()
- myArray[ fieldoffset + i ] = field(i)
- next
-
- * fill column pointer area of array with default column widths
- for i = 1 to fcount()
- myArray[ columnoffset + i ] = len(transform(field(i),'@')
- next
-
- Psuedo-multi-dimensional arrays in S87 were possible, but soon
- the code started becoming difficult to manage with every line of
- code making some reference to an array element "number" and
- offset "number" rather than an easy-to-remember symbol "name".
- Furthermore, it was impossible to dynamically re-size any
- sub-array without completely re-building the entire array.
- These limitations made it impractical to do multiple-window
- browsing, dynamic adding and deleting of columns, one-to-many
- browses, etc.
-
- Regardless of the limitations of the Summer 87 array system, it
- was still so powerful that Clipper applications grew in size and
- functionality at an alarming rate, thereby creating a whole new
- set of problems. The average Clipper programmer didn't want to
- invest in a deeper understanding of the memory ramifications of
- the heavy use of arrays, so he or she would work around the
- problem by using third-party DOS memory managers and overlay
- linkers that would give the application more DOS memory to start
- with. These third-party products were wonderful and extended
- the lifetime of S87, but eventually these applications will
- need to be upgraded to a language with a better memory manager.
-
-
-
- -- Clipper 5.0 arrays --
-
- Clipper 5.0 looks at arrays in an entirely new light. To
- overcome the difficulties that caused us to out-grow the S87
- array system, the 5.0 development team needed to treat arrays
- not as arrays in the traditional sense but as "linked lists".
- The need to grow arrays, shrink arrays, pass parameters in
- arrays, return arrays as values, and even store arrays and
- code-blocks in other arrays required developing a system to
- manage "pointers" and "pointers to pointers". The ultimate
- result of this architecture is a system that not only
- optimizes the use of symbols, but also the use of memory for
- the data elements. This automatic system of "optimizing"
- the data elements allows for creating incredibly large arrays
- that can be passed around the application for use by many
- routines and sub-routines by simply passing pointers rather
- than data. This concept vastly improves memory and speed
- performance and revolutionizes the future of data-driven
- programming.
-
- The development team took this concept even further and went as
- far as managing the pointers to the sub-elements so as to simply
- create a list of pointers to a "common" memory location for
- elements that contained identical data. For example, in Summer
- 87 or just about any other language, if you create an array of
- 1000 elements, and initialize each element to a length of 100
- characters, a memory block of 100,000 (100*1000) bytes would be
- required. Clipper 5.0 effectively handles this task by creating
- a system of logical pointers to the same area of memory until
- the information in an element is modified. This dynamic
- optimization will cause even a huge array to allocate very
- little memory when it is created, and from then on, only the
- absolute minimum amount needed as the array information changes.
-
- This concept, by itself, is a significant advancement in array
- architecture and memory management, but the development team
- went even further and created a Virtual Memory Manager (VMM)
- system that stores the array data in EMS memory.
-
- To see how effective this array optimization system really is,
- try this little test:
-
- for i := 1 to 25
- arrayname = 'array'+ALLTRIM(STR(i))
- &arrayname = array(1000)
- afill(&arrayname,space(1000))
- ? arrayname, memory(0), memory(4)
- next
-
- The above code will create 25 arrays of 1000 elements each and
- and 100 characters in each element. This is theoretically a
- memory requirement of 25 megabytes. In actuality the above
- code when run under dCLIP uses 0 bytes of free pool memory
- ( MEMORY(0) ) and only 340k of VMM ( MEMORY(4) ).
-
-
- ╔══════════════════════════════════════╗
- ║ CLASSES OF MULTIDIMENSIONAL ARRAYS ║
- ╚══════════════════════════════════════╝
-
- Clipper 5.0 provides a variety of methods to create and use
- multi-dimensional arrays. I am going to attempt to create
- a classification for types of multi-dimensional arrays. These
- classes are provided only to help define the different types
- of array programming and the advantages or disadvantages of
- each. Clipper does not make any distinction and treats all
- arrays the same way.
-
- 1. Parallel symmetrical arrays.
-
- Parallel arrays are the most common arrays in Clipper
- and are usually created using the ARRAY() function to
- predefine the length of the array. Parallel is a term
- defining the way data elements in the array are "grouped".
-
- An example of parallel arrays would be an array of
- 3 sub-arrays in which each sub-array has the same number
- of elements, and each element in each sub-array has a
- direct relationship with the corresponding element in
- every other sub-array. For example, a parallel array
- containing the structure of a database with 4 fields would
- look like this:
-
- Type Contents
-
- aStru[1] A Sub-array of field names
- aStru[1,1] C CUST_NAME
- aStru[1,2] C SHIP_DATE
- aStru[1,3] C BALANCE
- aStru[1,4] C PRINT_FLAG
- aStru[2] A Sub-array of field types
- aStru[2,1] C C
- aStru[2,2] C D
- aStru[2,3] C N
- aStru[2,4] C L
- aStru[3] A Sub-array of field lengths
- aStru[3,1] C 25
- aStru[3,2] C 8
- aStru[3,3] C 9
- aStru[3,4] C 1
- aStru[4] A Sub-array of field decimals
- aStru[4,1] N 0
- aStru[4,2] N 0
- aStru[4,3] N 2
- aStru[4,4] N 0
-
- This array could be created like this:
-
- aStru := Array( 4, Fcount() )
- FOR i := 1 TO Fcount()
- aStru[1,i] := Field(i)
- aStru[2,i] := Type(Field(i))
- aStru[3,i] := Len(Transform(&(aStru[1,i]),'@'))
- aStru[4,i] := Eval( {|n| n := At('.', ;
- Transform(&(astru[1,i]),'@')), ;
- Iif( n > 0, astru[3,i] - n , 0 ) } )
- NEXT
-
- Parallel arrays were very common in Summer 87 code, except
- that each array was assigned a different name or symbol,
- whereas parallel arrays in 5.0 can all exist under one
- array name and are addressed by sub-element number.
- Parallel arrays are most commonly used when you need a
- pick-list of items (using ACHOICE() or similar function)
- and then need to index into a set of parallel arrays to
- extract all related information based on a pointer returned
- by picking an item from the first parallel array.
-
-
- 2. Ragged symmetrical arrays.
-
- Ragged symmetrical arrays are a new concept introduced in
- Clipper 5.0 and are structured in such a way as to make the
- data in the array elements much easier to evaluate in a
- single expression with the AEVAL() function.
-
- An example of ragged symmetrical arrays would be an array
- of 4 sub-arrays in which each sub-array has the same number
- of elements (i.e. the same "structure") however, there is
- no direct relationship between the information in any one
- sub-array and any other sub-array. Instead, all the
- pertinent related information is contained in all the
- elements of a single sub-array. Let's create a ragged
- symmetrical array containing the structure of the same
- database we used in the parallel array example above. The
- array would look like this:
-
- Type Contents
-
- aStru[1] A Field 1 Information
- aStru[1,1] C CUST_NAME
- aStru[1,2] C C
- aStru[1,3] C 25
- aStru[1,4] N 0
- aStru[2] A Field 2 Information
- aStru[2,1] C SHIP_DATE
- aStru[2,2] C D
- aStru[2,3] C 8
- aStru[2,4] N 0
- aStru[3] A Field 3 Information
- aStru[3,1] C BALANCE
- aStru[3,2] C N
- aStru[3,3] C 9
- aStru[3,4] N 2
- aStru[4] A Field 4 Information
- aStru[4,1] C PRINT_FLAG
- aStru[4,2] C L
- aStru[4,3] C 1
- aStru[4, ] N 0
-
- This array could be created like this:
-
- aStru := {}
- FOR i := 1 TO Fcount()
- Aadd( aStru, Array(4) )
- aStru[i,1] := Field(i)
- aStru[i,2] := Type(Field(i))
- aStru[i,3] := Len(Transform(&(aStru[i,1]),'@'))
- aStru[i,4] := Eval( {|n| n := At('.', ;
- Transform(&(astru[i,1]),'@')), ;
- Iif( n > 0, astru[i,3] - n , 0 ) } )
- NEXT
-
- The main advantage of organizing your arrays in ragged
- symmetrical form is in the ease of evaluating the
- information in the array. For example, listing the
- structure of a database that has been loaded into a ragged
- symmetrical array would require only a single expression:
-
- Aeval( aStru, { |a| qout(pad(a[1],a[2],a[3],a[4]) } )
-
- Whereas trying to list the same information from parallel
- arrays would require more complex structural code:
-
- FOR i := 1 TO Fcount()
- qout(pad(aStru[1,i],10),aStru[2,i],aStru[3,i],aStru[4,i])
- NEXT
-
- The disadvantage, however of organizing data in ragged
- symmetrical form is that the array cannot be simply passed
- to a pick-list function such as ACHOICE(), so here's a
- handy function to convert one array type to another.
-
- FUNCTION dc_aconvert ( aInput )
- LOCAL i, j, aOutput := {}
- aOutput := Array( Len( aInput[1] ), Len( aInput ) )
- FOR i := 1 TO LEN( aInput )
- FOR j := 1 TO LEN( aInput[1] )
- aOutput[j,i] := aInput[i,j]
- NEXT
- NEXT
- RETURN aOutput
-
- You would use this function as follows:
-
- aRaggedDir := Directory()
- aParallelDir := DC_ACONVERT( aRaggedDir )
- nChoice := Achoice( 10,10,20,40, aParallelDir[1] )
- ? 'The size of '+aParallelDir[1,nChoice]+' is '+ ;
- Str( aParallelDir[2,nChoice] )
-
- 3. Asymmetrical arrays.
-
- An asymmetrical array is simply an array of information
- in which no element of the array has a "direct" indexed or
- ordinal relationship with any other element of the array
- yet all the information in all the elements make up a
- complete package of information. Basically, asymmetrical
- arrays can be used in place of memvars, in which each
- element of the array represents a specific piece of
- information. So why not use memvars instead of arrays?
- Mainly, because memvars cannot be easily grouped together
- into a complete "set" of information that can be easily
- stored and retrieved, whereas and entire array can be
- saved, restored, or even passed as a single parameter.
-
- An example of an asymmetrical array would be an array
- in which element 1 is another array which always
- represents the screen coordinates of a browse screen,
- element 2 is a character field with color of the screen,
- element 3 is a numeric field representing the work area,
- element 4 is a logical field, element 5 is a date field,
- etc., etc. In fact an asymmetrical array may even contain
- sub-arrays that are ragged symmetrical or parallel
- symmetrical. This entire multi-dimensional array could
- contain all the information necessary to repaint all
- browse screens for all work areas. This kind of array
- is effectively an "object", because it encapsulates all
- the necessary information about a program configuration
- into a nice, neat package.
-
- The advantage of working with large asymmetrical arrays
- is that only one symbol is used for the entire array,
- making it easy to save, restore and pass around an
- application. The disadvantage, is that the programmer
- must remember which element of which sub-array contains
- the desired data thereby making the source code very
- cryptic, unreadable, and difficult to maintain. In
- addition, if it became necessary to add new elements to
- the array, the ordinal position of each other element may
- change. This is where the pre-processor is not only very
- handy but an absolute necessity when working with large
- asymmetrical arrays. Assigning a "manifest constant" to
- an array element now allows the programmer to use multi-
- dimensional array elements and sub-elements in the same
- way he/she uses memory variables. It isn't necessary to
- remember which sub-element of an array contains a piece
- of information when a #define statement can be used to
- create any name desired. From then on, the programmer
- can write or read array information via an easy-to-remember
- set of variable names.
-
- There are several schools of thought when #defining
- manifest constants to represents array elements. Many
- programmers prefer to use the #define "only" for defining
- numeric values. Here is an example of this kind of array
- pre-processing:
-
- // BROWSE sub-array definitions
- #define MAIN 1
- #define COORDINATES 2
- #define FIELDS 4
-
- // COORDINATES definitions
- #define STARTROW 1
- #define STARTCOL 2
- #define ENDROW 3
- #define ENDCOL 4
-
- Defining array manifests in this manner requires that you
- use more than 1 variable name to access sub-array
- information. For example you would get the value of the
- start row of the display like this:
-
- nStartRow := BROWSE[ nWorkArea, COORDINATES, STARTROW ]
-
- Another method of #defining array pointers is by using one
- a "manifest symbol" rather than a "manifest constant".
- Here is an example of manifest symbols:
-
- // BROWSE sub-array definitions
- #define aMAIN 1
- #define aCOORDINATES 2
- #define aFIELDS 4
-
- // COORDINATES definitions
- #define nSTART_ROW BROWSE[ nWorkarea, 2, 1 ]
- #define nSTART_COLUMN BROWSE[ nWorkarea, 2, 2 ]
- #define nEND_ROW BROWSE[ nWorkarea, 2, 3 ]
- #define nEND_COLUMN BROWSE[ nWorkarea, 2, 4 ]
-
- Defining array manifests in this manner allows you to
- access multi-dimensional array information via shorter
- and simpler commands. For example you would get the value
- of the start row of the display like this:
-
- nStartRow := nSTART_ROW
-
- There currently is no "Hungarian notation" standard for
- manifest constants so you can use any name you wish, but
- I prefer the following format:
-
- xYYYYYYYYYY
- - --------
- | |
- | -------- The assigned name (in all capitals)
- ------------- The "type" of the data (in lower case)
- (a-Array, n-Numeric, d-Date, c-Character, etc)
-
- Defining manifest contstants in this manner makes them stand
- out in your program and not get confused with your memory
- variables or database field names.
-
-
- ╔══════════════════════════════╗
- ║ WORKING WITH LINKED ARRAYS ║
- ╚══════════════════════════════╝
-
- Although array elements in Clipper 5.0 can store the same types
- of data as memvars, it must be clearly understood that the 5.0
- linked array system creates new "pointers", NOT new "values".
- If this truth is forgotten or misunderstood you may not get the
- expected results in your program. Let's take the following
- code as an example:
-
- n1 := 1
- n2 := n1
- n2 := 2
- ? n1
- 1
-
- a1 := { 1, 2, 3 }
- a2 := a1
- a2[1] := 10
- ? a1[1]
- 10
-
- In the numeric memvar example above, n2 := n1 assigns the "value"
- of n1 to n2. There is no other relationship between n1 and n2.
- The value of n2 can now be changed to anything without affecting
- the value of n1.
-
- In the array example above, a2 := a1 assigns the "address" of
- a1 to a2. This now creates a direct relationship between a1 and
- a2 by creating a new set of pointers to the same information.
- Now if any of the elements of a2 are changed, the information in
- a1 will also be changed.
-
- If it desired to copy the information from an array to a new
- array and then work with the new array without affecting the
- original array, use the ACLONE() function.
-
- Actually, this "linked-list" type of array system is not a
- disadvantage, but in the contrary, is the reason why arrays in
- Clipper 5.0 are so powerful. When an array is passed as a
- parameter to another function or returned as a value from a
- function, only the pointer to the same information in memory
- is actually passed, not a set of values. This means that
- if the data in a LOCAL array that has been passed to a function
- is modified, the original passed array is also modified.
-
- Look at the following code:
-
- PROCEDURE TEST
- LOCAL a1 := {1,2,3}
- LOCAL a2
- a2 := TEST2 ( a1 )
- ? a1[1]
- 10
-
- STATIC FUNCTION TEST2 ( a3 )
- LOCAL a4 := a3
- a4[1] := 10
- a4[2] := 11
- a4[3] := 12
- RETURN a4
-
- In the above example, even though a4 in function TEST2 has been
- designated as a LOCAL memvar, the assigment of a3 to a4 will
- force the information in a1 of procedure TEST to change when
- it is changed in function TEST2.
-
-
- ╔═════════════════════════════════════╗
- ║ BROWSING A MULTIDIMENSIONAL ARRAY ║
- ╚═════════════════════════════════════╝
-
- I have seen lots of array browsers that don't give me the
- dimensional view of the array that I like. Most array browsers
- are written using Tbrowse, this one is not. This browser not
- only views the information in an array or object but also allows
- you to change the value and type of any array element or sub-
- element, or to grow or shrink any sub-array. Browsing an array
- in this manner requires building a different image of the
- array using macros and a Private memvar. This may not be a
- desirable programming practice to some, but it yields some good
- results. Note: This array browser can take a few seconds to
- build the Achoice image of the array if it is a large array.
-
- STATIC nLastKey := 0
- MEMVAR aNewArray
-
- FUNCTION abrowse ( aArray )
-
- LOCAL aArrayView, nSelect, nStart, cSaveScreen, cSaveScrn2,;
- cArrayElem, xValue, cType, nValueLoc, nRow, cElement, nLength,;
- cValue, cOldType, nOldLength, cSubName, nElement, lReBuild,;
- nLastElem, getlist := {}
-
- IF !(VALTYPE(aArray)$'AO')
- RETURN .f.
- ENDIF
- PRIVATE aNewArray := aArray
- aArrayView := ABROWSE3( {}, aNewArray, '', 0 )
- @ 0,2 TO 24,79 DOUBLE
- @ 24,7 SAY "┤ ENTER = Edit, ESCape = Exit, + = Add, "+;
- "DEL = Delete, INS = Insert ├"
- nSelect := 1
- nStart := 1
- DO WHILE .t.
- SET KEY 7 TO ABROWSE4
- SET KEY 22 TO ABROWSE4
- SET KEY 43 TO ABROWSE4
- nLastKey := 0
- nSelect := ACHOICE(1,3,23,78,aArrayView,,,nSelect,nStart)
- SET KEY 7 TO
- SET KEY 22 TO
- SET KEY 43 TO
- nStart := ROW()-1
- IF LASTKEY()=27
- EXIT
- ENDIF
- IF nSelect = 0
- LOOP
- ENDIF
- cArrayElem := aArrayView[nSelect]
- cType := SUBSTR( cArrayElem, AT( '],', cArrayElem)+2, 1 )
- nValueLoc := AT( '],', cArrayElem ) + 4
- cValue := SUBSTR( cArrayElem, nValueLoc )
- cElement := SUBSTR( cArrayElem, 1, nValueLoc-4 )
- nRow := IIF( ROW()>11, 2, 12 )
- cArrayElem := 'aNewArray' + cElement
- nLength := LEN(IIF(cType#'A',cValue,&cArrayElem))
- lReBuild := .f.
- FOR nLastElem := LEN(cElement) TO 1 STEP -1
- IF SUBSTR(cElement,nLastElem,1) $ '[,'
- EXIT
- ENDIF
- NEXT
- cSubName := 'aNewArray' + ALLTRIM(SUBSTR(cElement,1,nLastElem-1))+;
- IIF(SUBSTR(cElement,nLastElem,1)=',',']','')
- nElement := VAL(STRTRAN(SUBSTR(cElement,nLastElem+1),']',''))
- nOldLength := nLength
-
- DO CASE
-
- CASE nLastKey = 22 // Insert key inserts an element
- AINS( &cSubName, nElement)
- lReBuild := .t.
-
- CASE nLastKey = 7 // Delete key deletes an element
- ADEL( &cSubName, nElement)
- lReBuild := .t.
-
- CASE nLastKey = 43 // + key adds a new element
- AADD( &cSubName, nil )
- lReBuild := .t.
-
- CASE LASTKEY()=13
- cSaveScrn2 := SaveScreen( nRow,5,nRow+4,75 )
- @ nRow,5 CLEAR TO nRow+4,75
- @ nRow,5 TO nRow+4,75
- cOldType := cType
- @ nRow+1,7 SAY ' Type' GET cType PICT '!' VALID cType$'ACDNLU'
- READ
- DO CASE
- CASE cType = 'L'
- xValue := 'T'$cValue
- CASE cType = 'D'
- xValue := CTOD(cValue)
- CASE cType = 'N'
- xValue := VAL(cValue)
- CASE cType = 'U'
- xValue := nil
- CASE cType $ 'CA'
- @ nRow+2,8 SAY 'Length' GET nLength
- READ
- IF cType = 'C'
- xValue := PAD(cValue,nLength)
- ENDIF
- ENDCASE
- DO CASE
- CASE cType = 'N'
- @ nRow+3,8 SAY ' Value' GET xValue PICT ;
- '9999999999999.9999999'
- READ
- CASE !cType $ 'UA'
- @ nRow+3,8 SAY ' Value' GET xValue PICT '@S58'
- READ
- ENDCASE
- IF LASTKEY()#27
- IF cType#'A'
- IF cType='U'
- aArrayView[nSelect] := cElement + ','+cType+','
- ELSE
- aArrayView[nSelect] := cElement + ','+cType+','
- +TRANSFORM(xValue,'@A')
- ENDIF
- &cArrayElem := xValue
- ELSE
- IF TYPE(cArrayElem)#'A'
- &cArrayElem := {}
- ENDIF
- ASIZE(&cArrayElem,nLength)
- ENDIF
- lReBuild := (cOldType # cType .AND. ;
- ( cType='A' .OR. cOldType='A' )) ;
- .OR. (cType='A' .AND. cOldType='A' ;
- .AND. nLength # nOldLength)
- ENDIF
- RestScreen(nRow,5,nRow+4,75,cSaveScrn2)
- ENDCASE
- IF lReBuild
- aArrayView := ABROWSE3( {}, aNewArray, '', 0 )
- ENDIF
- ENDDO
- DC_IMPL(cSaveScreen)
- RETURN .t.
-
- // ----------------------------------------------------------- //
-
- STATIC FUNC aBrowse3 ( aArrayView, aArrayDisp, cElement, nRecurs )
-
- LOCAL nElement, cTextLine, cType, nArrayLen, cValue, cElement2
-
- nArrayLen := LEN(aArrayDisp)
- FOR nElement := 1 TO nArrayLen
- cType := VALTYPE(aArrayDisp[nElement])
- cTextLine := ''
- cElement2 := cElement+'['+ALLTRIM(STR(nElement,4))+']'
- cElement2 := STRTRAN( cElement2, '][', ',' )
- DO CASE
- CASE cType='C'
- cTextLine := aArrayDisp[nElement]
- CASE cType='N'
- cTextLine := STR(aArrayDisp[nElement])
- CASE cType='D'
- cTextLine := DTOC(aArrayDisp[nElement])
- CASE cType='L'
- cTextLine := IIF(aArrayDisp[nElement],'T','F')
- CASE cType$'AO'
- AADD( aArrayView, SPACE(nRecurs)+cElement2+','+cType+','+;
- cTextLine )
- aArrayView := ABROWSE3( aArrayView, aArrayDisp[nElement], ;
- cElement2, nRecurs+1 )
- LOOP
- ENDCASE
- AADD( aArrayView, SPACE(nRecurs)+cElement2+','+cType+','+;
- cTextLine )
- NEXT
- RETURN aArrayView
-
- // ---------------------------------------------------------- //
-
- STATIC PROC ABROWSE4
-
- nLastKey := LASTKEY()
- KEYBOARD CHR(13)
- RETURN
-
- // ---------------------------------------------------------- //
-
-
- ╔═══════════════════════════════╗
- ║ SAVING AND RESTORING ARRAYS ║
- ╚═══════════════════════════════╝
-
- Saving and restoring arrays to and from a disk file can offer
- the advantage of creating "persitent" objects. The following
- code will save and restore complete multi-dimensional arrays
- that DO NOT contain code blocks. Currently, there is no
- mechanism provided within Clipper to save/restore code blocks
- to disk, only to memvars. If you are using code blocks in
- arrays, you must store the code block as a string and macro-
- compile the string to restore the code block. The code
- provided here is 100% clipper code for compatability purposes
- only. The array saving/restoring mechanism I prefer is identical
- to the below code, except that the F_EOF and F_READLINE functions
- are replaced with assembly-level functions for speed.
-
-
- MEMVAR aOutArray
-
- FUNCTION arrayread ( cFileName )
-
- LOCAL cTextLine, cType, cElement, nArrayLen, nHandle,;
- nComma, cValue, cArray
- PRIVATE aOutArray := {}
-
- nHandle := FOPEN( cFileName )
- IF nHandle<=0
- RETURN nil
- ENDIF
- nArrayLen := 0
- DO WHILE !F_EOF(nHandle)
- cTextLine := F_READLINE(nHandle)
- IF LEFT(cTextLine,1) = '[' .OR. LEFT(cTextLine,1) = ','
- nComma := AT(',',cTextLine)
- cElement := TRIM(SUBSTR(cTextLine,1,nComma-1))
- cType := UPPER(SUBSTR(cTextLine,nComma+1,1))
- cValue := SUBSTR(cTextLine,nComma+3)
- cArray := 'aOutArray'+cElement
- ELSE
- cValue := cValue + cTextLine
- ENDIF
- DO CASE
- CASE cType='C'
- &cArray := STRTRAN(cValue,CHR(255),CHR(13)+CHR(10))
- CASE cType='N'
- &cArray := VAL(cValue)
- CASE cType='D'
- &cArray := CTOD(cValue)
- CASE cType='L'
- &cArray := IIF(cValue='Y',.t.,.f.)
- CASE cType='A'
- cArray := 'aOutArray'+cElement
- &cArray := {}
- ASIZE(&cArray,VAL(cValue))
- CASE cType='B'
- &cArray := &('"'+cValue+'"')
- ENDCASE
- ENDDO
- FCLOSE(nHandle)
- RETURN aOutArray
-
- // ------------------------------------------------------------ //
-
- FUNCTION arraywrite ( aArray, cFileName )
-
- LOCAL cTextLine, cType, nElement, nHandle
-
- IF VALTYPE(aArray)#'A'
- RETURN .F.
- ENDIF
- nHandle := FCREATE( cFileName )
- IF nHandle<=0
- RETURN .f.
- ENDIF
- _DCARRAY_W2 ( aArray, nHandle, '' )
- FCLOSE(nHandle)
- RETURN .T.
-
- // ------------------------------------------------------------- //
-
- STATIC FUNCTION _dcarray_w2 ( aArray , nHandle, cElement )
-
- LOCAL nElement, cTextLine, cType, nArrayLen, cValue
-
- nArrayLen := LEN(aArray)
- FWRITE(nHandle,cElement+',A,'+ALLTRIM(STR(nArrayLen))+;
- CHR(13)+CHR(10))
- FOR nElement := 1 TO nArrayLen
- IF VALTYPE(aArray[nElement])='U'
- LOOP
- ENDIF
- cValue := aArray[nElement]
- cType := VALTYPE(cValue)
- cTextLine := ''
- DO CASE
- CASE cType='C'
- cTextLine := STRTRAN(HARDCR(cValue),CHR(13)+;
- CHR(10),CHR(255))
- CASE cType='N'
- cTextLine := ALLTRIM(STR(cValue))
- CASE cType='D'
- cTextLine := DTOC(cValue)
- CASE cType='L'
- cTextLine := IIF(cValue,'Y','N')
- CASE cType='A'
- _DCARRAY_W2 ( aArray[nElement], nHandle, ;
- cElement+'['+ALLTRIM(STR(nElement,4))+']')
- LOOP
- ENDCASE
- FWRITE(nHandle,cElement+'['+ALLTRIM(STR(nElement,4))+']';
- +','+cType+','+cTextLine+CHR(13)+CHR(10))
- NEXT
- RETURN .T.
-
- // -------------------------------------------------------- //
-
- STATIC FUNCTION f_eof ( nHandle )
- LOCAL nCurrent, lEof
- nCurrent := FSEEK(nHandle,0,1)
- lEof := .f.
- IF nCurrent >= FSEEK(nHandle,0,2)
- lEof := .t.
- ENDIF
- FSEEK(nHandle,nCurrent,0)
- RETURN lEof
-
- // --------------------------------------------------------- //
-
- STATIC FUNCTION f_readline ( nHandle )
-
- LOCAL cString, nCurrent, nCr
- nCurrent := FSEEK(nHandle,0,1)
- cString := FREADSTR(nHandle,500)
- nCr := AT (CHR(13),cString)
- FSEEK(nHandle,nCurrent,0)
- FSEEK(nHandle,nCr+1,1)
- RETURN SUBSTR(cString,1,nCr-1)
-
-
-