SP_SMALLWHEN

SMALLWHEN()

  Short:
  ------
  SMALLWHEN() Uses SMALLS() in a WHEN condition for a GET

  Returns:
  --------
  lValid  => logical value

  Syntax:
  -------
  SMALLWHEN([lShowOnUp],[lReturn],expDisplayString,[cTitle],;
       [expAlias],expReturn,[expStartRange,expEndRange],[bException])

  Description:
  ------------
  Uses SMALLS() in a WHEN condition for a GET. See
  SMALLS() for description of parameters <expDisplayString>
  through [bException]

  [lShowOnUp] If False (the default), smalls is not
  called when going backwards through the read (up arrow, etc), only when
  going forward.

  [lReturn]   This is simply the value to return. The
  default is False, which means the get is never 'edited'. A value
  of True means the smalls() lookup is done, and then the get is edited.

  Examples:
  ---------
   @3,0 GET V3 WHEN ;
       SMALLWHEN(.f.,.f.,"LASTNAME",nil,5,"LASTNAME")

   // This will pop up a smalls() lookup table when going forward
   // through the gets. If RETURN is pressed, the current GET is fed
   // or assigned the lookup value. (depends on value of <expReturn>)
   // A False is returned, so the GET is not actually edited. The new
   // value is displayed, and the next get is made active.

  Notes:
  -------
  See Smalls() for a complete parameter description.

  Source:
  -------
  S_SMGETS.PRG

 

SP_SMALLVALID

SMALLVALID()

  Short:
  ------
  SMALLVALID() Uses SMALLS() lookups in a VALID condition for a GET

  Returns:
  --------
  lValid  => logical value

  Syntax:
  -------
  SMALLVALID([bValid],expDisplayString,[cTitle],;
     [expAlias],expReturn,[expStartRange,expEndRange],[bException])

  Description:
  ------------
  Uses SMALLS() in a VALID condition for a GET. See
  SMALLS() for description of parameters <expDisplayString>
  through [bException]

  If the user selects a value from the SMALLS() lookup,
  a value of True is returned, allowing the next GET to be made
  active. Otherwise, a False is returned. The return value is also
  determined by [bValid]

  [bValid]  -  This is a code block that determines if
  the current get is already valid. If this is passed, it is first
  evaluated, and, only if the current get is NOT ALREADY VALID,
  SMALLS() is called with the SMALLS() parameters given. If this
  is not passed, SMALLS() is called automatically. IF this is
  passed and the GET is valid, a True is returned, allowing the
  next GET to be 'gotten'.

  Examples:
  ---------
   @3,0 GET V3 VALID  ;
       SMALLVALID({||!EMPTY(V3)},"LASTNAME",nil,5,"LASTNAME")

   // This will pop up a smalls() lookup table when attempting to exit
   // the get 'V3' where 'V3' is empty. A SMALLS() lookup is  called, with
   // smalls() parameters provided, and, if CR is pressed, the GET is fed
   // or assigned the lookup value. (depends on value of <expReturn>)
   // If 'V3' is already valid, a True is returned, and the next GET is made
   // active. If SMALLS() is called and CR is pressed while in SMALLS(),
   // a True is returned. Otherwise a False is returned.

  Notes:
  -------
  See Smalls() for a complete parameter description.

  Source:
  -------
  S_SMGETS.PRG

SP_SMALLS

SMALLS()

  Short:
  ------
  SMALLS() Lookup tables on dbf with optional hotkeys, code block

  Returns:
  --------
  lReturn   => True if CR pressed, False otherwise

  Syntax:
  -------
  Smalls(expDisplayString,[cTitle],[expAlias],[expReturn],;
      [expStartRange,expEndRange],[bException],[lForceCaps])

  Description:
  ------------
  Popup windowed lookup tables. Many options.

  The up/down/home/end/pgup/pgdn keys are active. If
  there is an controlling index key and the user types alphanumeric
  characters, it is assumed a keysearch is wanted. A read pops up
  allowing the user to complete the search key, and a seek is then
  done.

  Pressing ENTER or ESCAPE exits the lookup and closes
  the window.

  Depending on values contained in the optional
  parameters, values may be KEYBOARDED or ASSIGNED at that time.

  Other keys may be assigned actions, depending on the
  last parameter, described below.

  <expDisplayString>  is what appears for each row in
     the lookup table.

     Think of it as a column definition for Tbrowse.
       It can be either:

       1. A Characters string like
          "LAST+MI+LEFT(FIRST,1)"
          In which case it is macro expanded and made
          into a code block to display this expression

       2. A Codeblock like
          {||LAST+MI+LEFT(FIRST,1)}

       The resulting codeblock is then used as a
       Tbrowse column definition.

  [cTitle]    is an optional lookup box title string

  [expAlias]  Determines the area/dbf/ntx to use, can be
     (expC) alias name             or
     (expN) numeric work area      or
     (expC) dbf/ndx in the format
            "%dbfname%ndxname"

     If left as nil, the current DBF/NTX are
     used and left open when done at the last record
     pointer position.

  [expReturn] This determines what happens when CR is
     pressed. It need not be anything, but it can be:

     (expC) a character expression which is
            macro expanded and KEYBOARDed. Any valid character
            expression will do.

            (Bear in mind that it must be
              character. If you want to keyboard a value into a DATE
              field, for instance, your keyboard expression could be
              "DTOC(entrydate)"

     (expB) a code block which is simply
            evaluated. this can do
            anything you wish. It could assign
            multiple values
            from the lookup file into active
            gets, for instance.

  [expStartRange-expEndRange]
     These are valid for indexed files, and
     determine the beginning and ending key ranges. The
     values must match the type of the controlling index.

  [bException]
     If this codeblock is passed, any keys
     except up/down/enter/pgup/pgdn/home/end/escape  (and
     alphanumeric keys if indexed )  will cause the block to be
     evaluated like so:

       eval(block,lastkey())

     Some interesting ideas for this would be
     to assign a key to an update routine for updating the
     lookup dbf while doing a lookup. (yikes - you didn't hear
     that from me!)

  [lForceCaps]

     Means force capital letters when user is typing in a
     lookup key.  Allows case-insensitive search when index is on
     upper(field)

  Examples:
  ---------
   // lookup on "LNAME+' '+FNAME" in current area
   smalls("LNAME+' '+FNAME")

   // lookup on "LNAME+' '+FNAME" in current area
   // use "Name" as box title
   smalls("LNAME+' '+FNAME","Name")

   // lookup on "LNAME+' '+FNAME" in current area
   // use "Name" as box title
   // go to area 5 for the lookup
   // send LNAME to the keyboard if CR pressed
   smalls({||LNAME+''+FNAME},"Name",5,"LNAME")

   // lookup on "LNAME+' '+FNAME" in current area
   // use "Name" as box title
   // go to alias CUSTOMER for the lookup
   smalls("LNAME+''+FNAME","Name","CUSTOMER")

   // open customer.dbf and do a lookup on it
   // lookup on "LNAME+' '+FNAME"
   // use "Name" as box title
   smalls("LNAME+''+FNAME","Name","%CUSTOMER")

   // open customer.dbf and do a lookup on it
   // lookup on "LNAME+' '+FNAME"
   // use "Name" as box title
   // limit lookup to between "SMITH" and "ZEBRA"
   smalls("LNAME+''+FNAME","Name","%CUSTOMER%NAME",  ;
         "SMITH","ZEBRA")

   // open CUSTOMER.DBF with ENTRYDATE.NDX and do a lookup
   // lookup on "LNAME+' '+FNAME"
   // use "Name" as box title
   // limit lookup to the last year
   smalls("LNAME+''+FNAME","Name",  ;
         "%CUSTOMER%ENTRYDATE",date()365, date())

  Notes:
  -------
  see SMALLVALID(), SMALLWHEN() and SMALLKSET() for
  various get system interfaces to SMALLS().

  Source:
  -------
  S_SMAL.PRG

 

SP_SEARCHMRS

SEARCHMRS()

  Short:
  ------
  SEARCHMRS() Resets all SEARCHME() settings

  Returns:
  --------
  NIL

  Syntax:
  -------
  SEARCHMRS([)

  Description:
  ------------
  Resets all SEARCHME() static variables
    - sets found() flag to FALSE
    - sets locate block to nil
    - sets continue flag to .f.
    - sets ALIAS to nil

  For instance, if you change datafiles - you need to reset
  SEARCHME() with SEARMRS()

  Examples:
  ---------
  SEARCHMRS()

  Source:
  -------
  S_SEARCH.PRG

 

SP_SEARCHMFND

SEARCHMFND()

  Short:
  ------
  SEARCHMFND() Set/return last found() status from SEARCHME()

  Returns:
  --------
  <lFound> -> Last searchme found() status

  Syntax:
  -------
  SEARCHMFND([lSet])

  Description:
  ------------
  Sets/retrieves the last found() setting for SEARCHME()
  [lSet] optionally sets the found() flag.

  For instance, to find out what the last result of SEARCHME() was
  so you can take an action or not based on something being found,
  use SEARCHMFND() to retrieve the last result.

  Examples:
  ---------
  SEARCHMFND(.f.)

  Source:
  -------
  S_SEARCH.PRG

 

SP_SEARCHME

SEARCHME()

  Short:
  ------
  SEARCHME() Search a DBF with user-specified criteria

  Returns:
  --------
  <bSearch> -> The code block used to do the search

  Syntax:
  -------
  SEARCHME([aField,aFieldTypes,aFieldLengths],[aFieldDesc])

  Description:
  ------------
  Searches the current dbf with criteria given

  by the user.

       for :    Field Contents Match
                Query match  --->> calls QUERY()
                End of file
                Beginning of File
                Deleted Records
                Memo contents

  On subsequent calls, if the previous search resulted
  in a FOUND(), a box asking "Continue..", "New Search" will pop up.
  If CONTINUE is selected, a continuation of the last search is
  done.

  Three arrays may be passed for FIELDS [aFields],
  FIELD TYPES [aFieldTypes], and FIELD LENGTHS [aFieldLengths].

  An additional (optional) array of field descriptions may be passed

  Default is all fields, field names as descriptions.

  Searchme() actually returns the code block used to do the
  search. Normally you would want to ignore this. I use it in
  TAGIT() - if the code block is not NIL, then I can 'tag all
  matching' with it.

  Examples:
  ---------
  SEARCHME()

  Source:
  -------
  S_SEARCH.PRG

 

SP_GENVAL

GENVAL()

  Short:
  ------
  GENVAL() Generic VALID clause validation with message

  Returns:
  --------
  <lValid> => logical result of passed condition

  Syntax:
  -------
  GENVAL(expValid,expMessage)

  Description:
  ------------
  Evaluates <expValid>, which is either a bode block or
  a string to be macro expanded, as a logical value. If result is
  False, displays message <expMessage> and waits for a keypress.
  <expMessage> can be a single string, or an array of strings (see
  aMsg() ).

  Examples:
  ---------
   if genval("fcount() < 60",'Too many fields')
       COPY TO TEMP
   endif

   * ...or as a VALID CLAUSE macro'd

   @10,10 get lname VALID ;
     GENVAL("!empty(lname)","Need a last name")

   * ...or as a VALID CLAUSE eval'd

   @10,10 get fname VALID ;
     GENVAL( { || !empty(fname) },"Need a last name")

  Source:
  -------
  S_GENVA.PRG

 

SP_GENREADER

GENREADER()

  Short:
  ------
  GENREADER() Creates specialized user defined get reader block

  Returns:
  --------
  <bReader> => get reader block for GET

  Syntax:
  -------
  GENREADER(bBlock,lPass)

  Description:
  ------------
  Creates a get reader block that first passes control
  to code block <bBlock> for each keypress. <bBlock> is passed
  the following values:

       1. lastkey() value
       2. proc name
       3. var name
       4. current get value

  If <bBlock> returns any value but a Nil, the get is
  assigned this value. If a Nil is returned, and <lPass> is True,
  then the key is passed to the regular get handler.

  Implement by using the SEND keyword for your
  @Say..Get.. statements.

    @10,10 say blah get blahblah SEND reader:=GENREADER(bBlock,lPass)

  Or simply refer to the last get added with ATAIL(getlist)

    @10,10 say blah get blahlblah
    ATAIL(getlist):reader := GENREADER(bBlock,lPass)

  Examples:
  ---------
   // while in the get V1, you will be able to type 1, 2 or 3
   // to get a value from the array

   aValues := {"Section 1","Section 2","Section 3"}
   bBlock  := ;
        {  | k|  iif( (nAtk:=at(chr(k),"123")) >  0,aValues[nAtk],nil)  }

   v1 := "Section 1"
   @10,10 get v1 send reader := genreader(bBlock)
   READ

  Source:
  -------
  S_READRS.PRG

 

SP_AEXTRACT

AEXTRACT()

  Short:
  ------
  AEXTRACT() Extract and return a conditional set from an array

  Returns:
  --------
  <aSubset> => subset of the array

  Syntax:
  -------
  AEXTRACT(aSource,bCondition,[nElement])

  Description:
  ------------
  Extracts elements meeting <bCondition> from <aSource>
  and returns them as in an array.

  <bCondition> is a code block that, for each element
  of <aSource> is passed the element and the position. So for
  element 3 the codeblock would be called with

    EVAL(bCondition,aSource[3],3)

  [nElement]  if passed signals that <aSource> is an
  array of arrays, and that [nElement] is to be extracted from
  each subarray where <bCondition> is true.

  Examples:
  ---------
   aNames    := {"Fred","Wilma","Barney","Betty"}
   aRubbels  := aextract(aNames,{|e|left(e,1)=="B"} )

   aDir := directory()
   // extract for size > 100,000
   aBig := aextract(aDir,{|e,n|aDir[n,2]> 100000})
   // extract only the name element for size > 100,000
   aBigNames := aextract(aDir,{|e,n|aDir[n,2]> 100000},1)

  Source:
  -------
  S_AEXTRA.PRG

 

Coding Guidelines

Coding Guidelines

( by Greg Holmes )

Language Syntax 
The general rule of thumb is: built-in features in lowercase, and custom-written functions in mixed case. 
When specifying the complete syntax of a language element in documentation, the input items, parameters, and so on are referred to using the following symbols:

 Symbol  Description
< >  Indicates user input item
( )  Indicates function argument list
[ ]  Indicates optional item or list
{ }  Indicates code block or literal array
| |  Indicates code block argument list
–>  Indicates function return value
 Repeated elements if followed by a symbol
Intervening code if followed by a keyword
,  Item list separator
|  Indicates two or more mutually exclusive options
@  Indicates that an item must be passed by reference
*  Indicates a compatibility command or function

For example:

    len(<cString>|<aArray>) --> nLength

Metasymbols provide a place holder for syntax elements, and they describe the expected data types. A metasymbol consists of one or more lowercase data type designators followed by a mixed case description. This is known as Hungarian Notation.

 Designator  Description
a  Array
b  Code block
c  Character expression
d  Date expression
exp  Expression of any type
id  Literal identifier
l  Logical expression
m  Memo field
n  Numeric expression
o  Object
x  Extended expression

In this example, dnLower and dnUpper can be either date or numeric:

    @...get...range <dnLower>, <dnUpper>
Filenames and Aliases 
All filenames, in any context, are in upper case. Filenames follow DOS naming conventions (preferably limited to letters, numbers, and the underscore).

    use CUSTOMER
    nHandle := fopen('DATAFILE.DAT')

When referring to specific file types in documentation, include the period.
e.g. “A program is stored in a text file with a .PRG extension.” 
Alias names follow the same conventions as filenames, but are limited to A-Z, 0-9, and the underscore. If a filename begins with a number or contains unusual characters, an alias must be specified when the file is opened or an error will result. 
Note that CA-Clipper does not natively support Windows 95 long filenames, although third-party libraries are available to add the capability.

Fieldnames 
Fieldnames are all uppercase, and always include the alias of the table. Fieldnames may contain underscores, but should not begin with one (because the underscore is generally used to indicate an internal symbol).

    @ 10, 10 say BANKS->BRANCH
    nAge := CUSTOMER->CUST_AGE
Memory Variables 
Memory variables consist of a lowercase type designator followed by a mixed case description (see Hungarian Notation). Although CA-Clipper only recognizes the first 10 characters as unique, variable names may be longer.

    cString := "Hello World"
    nYearlyAverage := CalcYearAvg()

If you use Hungarian Notation for your memory variable names and include the table alias with fieldnames, there will be no conflict between the two.

Commands, Functions, and Keywords 
All built-in commands, functions, and keywords are lowercase. In documentation, the font should be Courier or a similar font. If fonts are not available, then bold or CAPITALIZE the word for emphasis. 
Never use abbreviations — this practice is not necessary with a compiler, although it was common in the early days of dBase (which was an interpreter). 
There should never be a space between the function name and the opening parenthesis. Also, note that the iif() function should never be spelled if().

    replace CUSTOMER->CUSTNAME with cCustName
    nKey := inkey(0)

When specifying commands that have clauses in documentation, separate the keywords with an ellipsis (...) and do not include the to clause, unless it is followed by the file,print, or screen keywords.

    copy...sdf
    set message...center
    @...say...get
Programmer-Defined Functions & Procedures 
These begin with an uppercase letter, followed by mixed case letters as appropriate.

    ? StripBlanks("Hello there, this will have no spaces.")

Function and procedure names may contain underscores, but should not begin with one (they may conflict with internal functions which often start with an underscore). There should be only one return statement per function or procedure, and it should not be indented.

    function SomeFunc (...)
      .
      . <statements>
      .
    return cResult

The return value of a function is not enclosed in parentheses, although parentheses may be used to clarify a complex expression.

    return nValue
    return (nCode * 47) + nAnswer
Preprocessor Directives 
Preprocessor directives are lowercase and are preceded by the # sign.

    #include 'INKEY.CH'

Optionally, you may use single quotes around header files that come with CA-Clipper and double quotes around your own. This convention is purely voluntary, but it helps to distinguish between the two. For example:

    #include 'INKEY.CH'
    #include "MY_APP.CH"

Manifest constants are uppercase.

    #define ESCAPE   27
    if lastkey() == ESCAPE

Pseudo-function names should also be uppercase.

    #define AREA(length, width)   ((length)*(width))
Declarations 
Local variables are grouped according to functionality, and may be declared on one or more lines. The declarations appear as the first code at the beginning of a function or procedure.

    procedure Main ( )
    local nTop, nLeft, nBottom, nRight
    local cOldScreen, cOldColor, nOldCursor

Variables may be declared one per line and accompanied by a description.

    local nCount        // Number of records found.
    local nTotal        // Sum of dollars.

The description can be omitted if better variable names are chosen.

    local nRecordCount
    local nDollarTotal

Variables can be initialized when they are declared, although it is often clearer (and safer) to initialize them immediately before they are used.

    local nRecordCount:=0
    local nDollarTotal:=0
Logicals 
The .T. and .F. are typed in uppercase.
Operators 
The in-line assignment operator (:=) is used for all assignments, and the exact comparison operator (==) is used for all comparisons.

    lContinue := .T.
    nOfficeTotal := nRegionTotal := 0
    lDuplicate := (CUSTFILE->CUSTNAME == cCustName)
    if nLineCount == 4  ...
    if left(PRODUCT->CODE, 3) == left(cProdCode, 3)  ...

Although the compound assignment operators (+=-=*=, etc.) are convenient, they should not be used if readability suffers.

    // The traditional way to accumulate:
    nTotal := nTotal + INVDETAIL->PRICE
    // A good use of a compound assignment operator:
    nTotal += INVDETAIL->PRICE
    // But what does this do?
    nVal **= 2

The increment (++) and decrement (--) operators are convenient, but can lead to obscure code because of the difference between prefix and postfix usage.

    nRecCount++
    nY := nX-- - --nX        // Huh?
Spacing 
Whenever a list of two or more items is separated by commas, the commas are followed by a space.

    MyFunc(nChoice, 10, 20, .T.)

Spaces may be used between successive parentheses.

    DoCalc( (nItem > nTotal), .F. )
    cNewStr := iif( empty(cStr), cNewStr, cStr + chr(13) )

Spaces should surround all operators for readability.

    nValue := 14 + 5 - (6 / 4)

In declarations, often spaces are not used around the assignment operator. This tends to make searching for the declaration of a variable easier.

    local lResult:=.F., nX:=0

Thus, searching for “nX :=” would find the lines where an assignment is made, while searching for “nX:=” would find the declaration line (such as the local above).

Indentation 
Indenting control structures is one of the easiest techniques, yet it improves the readability the most. 
Indent control structures and the code within functions and procedures 3 spaces.

    procedure SaySomething
       do while .T.
          if nTotal < 50
             ? "Less than 50."
          elseif nTotal > 50
             ? "Greater than 50."
          else
             ? "Equal to 50."
          endif
          ...
       enddo
    return

Case statements in a do…case structure are also indented 3 spaces.

    do case
       case nChoice == 1
          ? "Choice is 1"
       case ...
          ...
       otherwise
          ...
    endcase
Tabs 
Do not use tabs in source code — insert spaces instead. Tabs cause problems when printing or when moving from one editor to another, because of the lack of a standard tab width between editors and printers. Typically, printers expand tabs to 8 spaces which easily causes nested control structures to fall off the right-hand side of the page. Commonly, a source code editing program will insert the appropriate number of spaces when the <TAB> key is hit.
Line Continuation 
When a line of code approaches the 80th column, interrupt the code at an appropriate spot with a semicolon and continue on the next line. Indent the line so that it lines up in a readable manner.

    set filter to CUSTFILE->NAME  == 'John Smith  ';
            .and. CUSTFILE->STATE == 'OR'

To continue a character string, end the first line with a quote and a plus sign and place the remainder on the next line. Try to choose a logical place in the string to break it, either at a punctuation mark or after a space.

    @ 10, 10 say "The lazy brown fox tripped over " + ;
                 "the broken branch."
Quotes 
Use double quotes for text that needs to be translated (will appear on the screen), and single quotes for other strings.

    ? "Hello World!"
    cColor := 'W+/B'
    SelectArea('PROP')

This is a simple but extremely effective technique because translation departments often want to see the messages in context (in the source code), so the different quote types indicate which messages are to be translated and which should be left alone.

Comments 
Comments are structured just like English sentences, with a capital letter at the beginning and a period at the end.

    // Just like a sentence.
    /* This comment is longer. As you
       can see, it takes up two lines */

You may encounter old-style comment indicators if you maintain older (Summer’87 and earlier) code.

    && This is an older-style of comment indicator.
    *  The asterisk is also old.

For in-line comments, use the double slashes.

    use CUSTOMER            // Open the data file.
    goto bottom             // The last record.

Note that the ‘//‘ of in-line comments begins at column 40, if possible. This leaves enough room for a useful comment.

Source :  http://www.ghservices.com/gregh/clipper/guide.htm