Lisp Basics

 

Lisp Data Types

AIS Lisp uses the standard Analytic Information Server data types, which are are listed in the following table.

Data Type Description
Word A 128 bit container capable of holding any one of the AIS data types shown below.
Void The empty or nil type.
Boolean Logical truth values, i.e. true  false. (Note: inside a Word only, Boolean values are stored as the 64 bit Integer values 0 or 1).
Character An 8 bit data field containing the standard ASCII character set. (Note: inside a Word only, Character values are stored as 64 bit Integer values).
CharPointer Pointer to an array of character data bytes in context memory
Complex A Heap object containing two IEEE 64 bit double precision real numbers, the real and the imaginary elements.
Date An IEEE double precision real number with a floating date/time scale capable of microsecond accuracy in the present era, and capable of daily accuracy in the 15,000,000,000BC and the 15,000,000,000AD eras.
FloatPointer Pointer to an array of 32 bit IEEE floating point data values in context memory
IntPointer Pointer to an array of integer data values in context memory
Integer A 64 bit integer value.
JumpPointer Pointer to a virtual machine instruction in the current Lambda's pcode vector
LongPointer Pointer to an array of long integer data values in context memory
Money An IEEE double precision real number representing U.S. currency values.
Number An IEEE 64 bit double precision real number.
NumPointer Pointer to an array of 64 bit IEEE floating point data values in context memory
ShortPointer Pointer to an array of short integer data values in context memory
String A Heap object containing zero or more 8 bit ASCII data items and terminated by a nul (0) value.
Text An immediate string of from zero to nine 8 bit ASCII data items followed by a nul (0) value. (Note: inside a Word only, Text values are stored in the 80 bit immediate area of the Word).
Symbol A unique Heap object containing one or more 8 bit ASCII data items and terminated by a nul (0) value, plus an additional global value component which is a 128 bit Word container. (Note: Symbol objects are forced to be unique. No two Symbol objects have the same ASCII string contents. Because they are unique, Symbol objects are used to store and reference global values, which are stored in the Word component of the Symbol object).
Vector A Heap object containing zero or more Word data items.
BitVector A Heap object containing zero or more one bit data items (i.e. 0 1).
Brick A Heap object containing zero or more bindings. Each binding is composed of any AIS data type (possibly repeating), and referenced by a Symbol key, allowing Brick field values to be referenced and modified by symbolic key. Brick key values must be references to Symbol objects. (Note: Brick key values are stored separate from the BrickS data body).
ByteVector A Heap object containing zero or more 8 bit data values. (Note: the ByteVector object is NOT restricted to ASCII data and may contain any configuration of 8 bit data values).
CpxVector A Heap object containing zero or more Complex number data items. Each Complex number data item is two IEEE 64 bit double precision real numbers, the real and the imaginary elements.
FltVector A Heap object containing zero or more IEEE 32 bit single precision real numbers.
IntVector A Heap object containing zero or more 64 bit integer values.
LongVector A Heap object containing zero or more 64 bit integer data values.
NumVector A Heap object containing zero or more IEEE 64 bit double precision real numbers.
ObjectRepository A Heap object containing zero or more bindings. Each binding is composed of an AIS Word data value and followed by an object reference key, allowing ObjectRepository values to be referenced and modified by object key. The ObjectRepository values are NOT stored in memory; instead, they are stored on disk. (Note: ObjectRepository key values are NOT restricted to Symbol objects, and may be references to any AIS heap object).
ObjVector A Heap object containing zero or more pointers to other Heap objects in the System. (Note: the empty or nil value is represented by a nil (0) pointer value).
PcodeVector A Heap object containing zero or more 64 bit DRM Virtual Machine instructions.
ShortVector A Heap object containing zero or more 16 bit integer data values.
Structure A Heap object containing zero or more bindings. Each binding is composed of an AIS Word data value and followed by a Symbol reference key, allowing Structure values to be referenced and modified by symbolic key. (Note: Structure key values must be references to Symbol objects).
Dictionary A Heap object containing zero or more bindings. Each binding is composed of an AIS Word data value and followed by an object reference key, allowing Dictionary values to be referenced and modified by object key. (Note: Dictionary key values are NOT restricted to Symbol objects, and may be references to any AIS heap object).
Directory A Heap object containing zero or more bindings. Each binding is composed of an AIS Word data value and followed by an AIS Word reference key, allowing Directory values to be referenced and modified by arbitrary keys. (Note: Directory key values are NOT restricted to object references, and may be any AIS data type).
Matrix A Heap object containing zero or more 128 bit AIS Word values, which are arranged in from one to three dimensions. (Note: Matrix objects may be dynamically arranged with one, two, or three dimensions).
NumMatrix A Heap object containing zero or more IEEE 64 bit double precision real numbers, which are arranged in from one to three dimensions. (Note: NumMatrix objects may be dynamically arranged with one, two, or three dimensions).

Overview of Data Types

AIS Lisp uses the standard Analytic Information Server data types, which are divided into four categories: Native Data Types (also known as Immediate types), Objects (heap objects), registers, and Repositories. The Native (immediate) types can be entirely contained within the immediate data of a single Virtual Machine Word. The Objects (heap objects) types are too large to be contained within a single Virtual Machine Word and require extra memory must be managed by the heap manager. Without exception, all of the Object types are identified by an object id. The object id identifies a block of memory, managed by the Analytic Information Server memory manager, in which the Object's data is stored.

Most of the memory space, set aside within the host application, for use by the Analytic Information Server subsystem is divided into a set of virtual machine words. Each virtual machine word begins with a type tag and is followed by an immediate data (Dynamic Typing). The contents of the tag inform the Analytic Information Server virtual machine about the type of data which follows.

The Heap contains memory resident data, which is of variable length or is too large to fit in AIS Words. The Analytic Information Server object Heap manager supports automated object resizing, garbage collection, and anti-fragmentation algorithms so that the user may concentrate on the analysis and modeling of data rather than on memory management.

Registers are very fast special locations that can be mapped directly to the microchip's operation registers. There are fifty arithmetic registers for use by the programmer.

Object Repositories (databases) contain persistent data of all sorts. Lambda Information Server supports repositories with multiple database volumes and multiple database schema?s including General Object Repositories, Text Repositories, and Spreadsheet Repositories.

AIS Word Format

The AIS Word is a 128-bit dynamically typed Word capable of holding any of the AIS native data types (shown above). Each Word begins with an 80-bit data area capable of holding up to ten bytes of null-terminated ASCII text, or any one of these other native AIS data types: Character, Boolean, Float, Integer, Long, Number, Object, or Short. Immediately following the Word's 80-bit data area, is the Word's tail (a 32-bit signed integer). The tail is a general purpose data field used for linking words to other words, keeping word counts, or any other purpose. Immediately following the Word's tail, is the Word's Declared Type (an 8-bit data type announcing the user's preferred data type for this Word). Immediately following the Word's Declared Type, is the Word's Current Type (an 8-bit data type announcing the type of data contained in the Word's 80-bit data area).

Object Data Types

The Analytic Information Server Object Types are stored in the Heap and are managed by the Heap manager. The Analytic Information Server Heap manager supports object resizing, garbage collection, and anti-fragmentation algorithms so that the user may concentrate on the analysis and modeling of data rather than on memory management. Without exception, all of the Object types are identified by an object id. The object id identifies a block of memory, managed by the Analytic Information Server memory manager, in which the Object's data is stored.

The Analytic Information Server Heap Object and Native Data types can be saved and loaded to and from persistent (disk file) storage at any time. Words with immediate data are saved on disk in fixed length records equal to the size of the Word. Words with Heap object references are saved in fixed length records, which are automatically expanded to include the contents of the Heap object, and any objects referenced by the Heap object, etc. This feature is called Object Closure Management and is automatic with every Lambda Information Server database save.

Analytic Information Server Words may be loaded from any database repository record at any time. If the data in the record is immediate, the database load fills the Word with the immediate data. If the data in the record is an object closure, the database load fills the Word with a Heap object reference, and all of the objects in the record are loaded back into the Heap with the same referential relationships they had when they were saved in the repository.

Register Data Types

AIS Lisp allows up to fifty register variables to be declared (see the regs special form). AIS Lisp register variables allow the fastest possible integer and floating point arithmetic operations carried out at microchip-level speeds. The pointer types allow the fastest possible indirect referencing of heap memory. Each register can be declared as one of the following types:

Integer 64 bit integer data depending upon the microchip
LongPointer Pointer to an array of long integer data values in context memory
Number 64 bit IEEE floating point data depending upon the microchip
CharPointer Pointer to an array of character data bytes in context memory
ShortPointer Pointer to an array of short integer data values in context memory
JumpPointer Pointer to a virtual machine instruction in the current Lambda's pcode vector
IntPointer Pointer to an array of integer data values in context memory
FloatPointer Pointer to an array of 32 bit IEEE floating point data values in context memory
NumPointer Pointer to an array of 64 bit IEEE floating point data values in context memory

Type Casting

AIS Lisp supports limited type casting inside expressions. Three type casting expressions are supported (Number: ...expression...) which casts the expression into a Number register (Integer: ...expression...) which casts the expression into an Integer register and (Word: ...expression...) which casts the expression into a Word temporary. No attempt is made to provide a comprehensive set of type casting services. It will always be the case that absolute programmer control of AIS Lisp is obtained via the explicit use of Lisp assembler statements. Some examples of AIS Lisp type casting expressions are given below.

Warning

There is a syntax conflict between the messaging short cut (msg: ...expression...) and the type casting inside expressions (Number: ...expression...). Wherever the meaning is unclear, AIS Lisp will always assume that a type cast was intended.

Lisp Constants

AIS Lisp has access to all of the Analytic Information Server data types, more built-in data types than are available to either Common Lisp or Scheme. AIS Lisp uses an extension of the standard Lisp constant syntax. The following is a table of all the AIS data types which have AIS Lisp constant formats.

Data Type Constant Examples
Void #void
Boolean true  false
Character #\G  #\a  #\"  #\2  #\newline  #\return  #\tab  #\,
Complex #ci  #c-i  #c34  #c34.5  #c4.5i  #c-4.5i  #c2.1-4.5i
Date #Jan,3,1984  #Feb,22,1492  #Aug,10,2002:11:34:01  #Jan,1,0:11:34:01  #Jan,23,1050BC
Integer 25  -1  10392756  -10392756  -10392756  -103  2756
Money $25.34  $-1.00  $1039.27  $-1.64  $-103.92  $-.10  $275.26
Number 25.34  -1.0  1039.2756  -1.6E-56  -103.92756  -.103  275.6
Percent 25.34%  -1.0%  1039.27%  -1.66%  -103.92%  10%  275%
String  or  Text "This is a string"   "I'm also a string"   "This is a quote \" character"   {I'm a string}   {##[This is also a string]##}
Symbol |This is a symbol|:  'myName  myName:  |myName|:  '|Hello there|
Vector #(a b 2)  #(1 . 2)  #(a (b c) d)  #(#(1 2) #(3 4) #(5 6))  #((1 2) a (5 6) . ( 3 4) )
BitVector #(bit| 1 0 1)  #(bit| 1 0 1 0 0 0 1 1 0 1)
CpxVector #(cpx| #c34.0 #i #3.4-2i)  #(cpx| #c-23.567i #-i #3.34+2.66i)
IntVector #(int| 2)  #(int| 1 . 2)  #(int| 1 34 2)  #(int| 1 -34 20163 -23)
FltVector #(float| 2.2)  #(float| -1.4 . 4.2)  #(float| 1.3 34 2.1)  #(float| 1.23 -3.4 2.0163 -2.3)
ObjVector #(obj| a b #(1 2 3))  #(obj| a)  #(obj| a bb (b c) d . sf)  #(obj| "Hello there" #(3 4) #(5 6))
Structure #{a 22 b #(1 2 3)}  #{a true}   #{a bb c (b c) d 1 . sf}   #{ as "Hello there" b #(3 4) myKey #(5 6)}   #{decl| (String:Name "Hello") Number:Salary Vector:x}
Dictionary #{dic| a 22 b #(1 2 3)}  #{dic| a true}  #{dic| a bb c (b c) d 1}  #{dic| as "Hello there" b #(3 4) myKey #(5 6)}
Directory #{dir| 22 a #(1 2 3) b}  #{dir| true a}  #{dir| a bb (b c) d c 1}  #{dir| "Hello there" as b #(3 4) myKey #(5 6)}
Matrix #(mat| c b 2 3)  #(mat[3]| c b 2)  #(mat[3 2]| c b 2 d (8 . d) 2)  #(mat[2 3 2]| c b 2 d (8 . d) 2 x "Hello" 2 d #(8 3 d) 2)
NumMatrix #(nummat| 1.1 2.4 3)  #(nummat[3]| 1 4 2)  #(nummat[3 2]| 2.56 -56 2.2 6 8.234 2)  #(nummat[2 3 2]| 2.3 -3 3.5 4.5 1000 2 -34 5.6 2 1 0 2)

Lexical Elements

Lisp supports the following compiler directives:

#LOCKGLOBALS# Locks current global symbols
#javaScript# Invokes the javaScript parser (must be first seven characters in source string).
#alice# Invokes the alice parser (must be first seven characters in source string).
#xml# Invokes the xml parser (must be first seven characters in source string).
#name# Invokes the name parser (must be first seven characters in source string).

White Space

Lisp uses white space to separate each of its symbols and operators. The Lisp white space characters include the comma, all the standard 8-bit ASCII control characters (less than 32 decimal), and the blank character (32 decimal).

, Comma
new line Line feed control character (10 decimal)
carraige return Carraige return control character (13 decimal)
right tab Right tab control character (9 decimal)
  Blank character (32 decimal)

The Lisp parser looks for white space or parentheses, brackets, or braces to determine each token. A token is either a symbol, a constant value, or operator. If white space is missing between an operator and a symbol, the Lisp interpreter cannot parse the statement correctly.

(+ 1 2 ) not (+1 2 )

Special Characters

Lisp uses the standard 8-bit ASCII character set. Some of the Lisp special characters serve to group a set of characters as a single unit (e.g. double quotes group characters to form a string constant). The remainder of the special characters serve to separate tokens (e.g. comma or blank) or prefix a constant (e.g. $ # ). The following are the Lisp special characters.

\ | ( ) [ ] { } # @
` ' , " : ; $ % . blank

String Constants

Lisp supports String constants. A String is any series of Lisp characters or symbols: (A) enclosed in double quote symbol paris (" "); (B) enclosed in left-right brace paris ({ }); or (C) enclosed in these special sequence paris ({##[ ]##}); For Example:

"This is a string \" constant" "This is a string constant"
{This is a string "A" constant} {This is a string constant}
{##[This is a string "A" constant]##} {##[This is a string {A} constant]##}

Comments

Lisp supports comment constants. A comment is any series of Lisp characters or symbols, preceded by a semicolon (;), and terminating at the end of the line on which the comment appears. For Example:

(+ 2 4) ; This is a comment after the semicolon
"This is not ; a comment" ; This is a comment
#\; ; The first semicolon does not start a comment

Object Identifier Notation

Lisp supports object identifier notation which acts as syntactical short hand for invoking the inspect function. The inspect function allows one to refer to an object by its object identifier only. The characters #< followed by a type name, an integer object identifier, and terminated with the > character, invokes object identifier notation. For Example:

   #<Vector: 3194>   is shorthand for   (inspect Vector: 3194)

Names

Naming Conventions

AIS Lisp uses the naming convention that single word names are always lowercase. For multiple word names, the first word is always lowercase while the first letter of each succeeding word is uppercase. Special system names always begin with an underscore character. For example:

Labels

Lisp supports labels of arbitrary length and containing a wide range of characters. A Lisp label is any valid symbol followed immediately by the : (colon) special character. In Lisp labels are used as keywords in many special forms and are useful in a variety of other situations. Furthermore, Lisp supports goto labels which are any valid symbol followed immediately by the :: (double colon) special character pair. goto labels can be used with the goto special form for unconditional branches.

    (defun foo(n) vars:(x) (setq x (+ n n))) ;; Here vars is a keyword

    (defun xyz(n)

        Here:: ;; Here is a goto label

        (setq n (sub1 n))

        (if (< n 10)

            (goto Here:)) ;; goto branches to Here (up 3 lines above)

        true)

 

Local Variables

AIS Lisp supports the declaration of local variables. There are five categories of local variables for each Lambda.

The vars, pvars, and cvars local variable allocations may include initialization specifications along with their declarations. The regs local variable allocations may include data type specifications along with their declarations.

    (defun foo(n) ;; Variable n is an argument variable.

        vars:(x (yx and y are temporary variables.

        pvars:((a 20)) ;; Variable a is a persistent variable initialized to 20.

        cvars:(b) ;; Variable b is a persistent variable uninitialized.

        regs:(Integer:myReg) ;; Variable myReg is a microchip register variable typed as Integer.

        true)

 

Global Variables

In AIS Lisp, referencing a symbol, which has NOT already been declared, automatically causes it to be declared as a global variable. Lisp programmers will recognize automatic variable declaration as a Lisp extension. This feature has been added to make Lisp more user-friendly. In standard Lisp terms, the following Lisp expressions are equivalent (the assumption is made that X has not already been referenced).

   (setq x 23)   is equivalent to   (define x 23)

In AIS Lisp, referencing a global symbol explicitly, automatically causes it to be declared as a global variable. There are two methods of explicitly specifying a symbol as a global variable reference. If name is the variable, then either ^name or |Gv:name| will automatically be considered global variable references. The |Gv:name| reference simply generates a global variable reference directly; while, the ^name reference generates a call to the getGlobalValue function.

   ^name   is equivalent to   (getGlobalValue name:)

Lisp global variables are valid during the whole life of the current context (see the _globals global symbol table variable). Global variables are not lost when the current function invocation returns control. Lisp global variables are referenced by specifying the symbol. In addition to user defined globals, Lisp global variables include all of the built-in functions such as + - * define lambda let, etc.

The Lisp language is specified as case-sensitive (most dialects of Lisp are case-insensitive). Therefore:

   define   is NOT equivalent to   Define

Creating Objects

AIS Lisp allocates memory by creating new data objects with the new function (the constant forms for some objects can also be used to allocate static memory). Once created, the new object can be stored into or retrieved from as befits the type of object created. When the Lisp program no longer needs the object, the garbage collector will reclaim the allocated memory. Automatic garbage collection is one of the nice features about Lisp. The following is a table of some AIS Lisp data objects being created with the new function.

Data Type Constant Examples
String (new String: "This is a string")  (new String: "I've got a string")  (new String: "This is a quote \" character")
Symbol (new Symbol: |This is a symbol|)  (new Symbol: 'myName)  (new Symbol: '|Hello there|)
Vector (new Vector:3 a: b: 2)  (new Vector:1 1 . 2)  (new Vector:1000)  (new Vector:1 (new Vector:100))
BitVector (new Vector: bit:3 1 0 1)  (new Vector: bit:10 1 0 1 0 0 0 1 1 0 1)
ByteVector (new Vector: byte:3 1 0 1)  (new Vector: byte:10000)
IntVector (new Vector: integer:2)  (new Vector: integer:1 1 . 2)  (new Vector: integer:3 1 -34 2)
FltVector (new Vector: float:1 2.2)  (new Vector: float:1 -1.4 . 4.2)  (new Vector: float:3 1.3 34 2.1)
ObjVector (new Vector: object:3 a b #(1 2 3))  (new Vector: object:1 a)  (new Vector: object:4 a bb (b c) d . sf)  (new Vector: object:3000)
Structure (new Structure: a: 22 b: #(1 2 3))  (new Structure: a: true)  (new Structure: a: bb: c: '(b c) d: 1 . sf)
Dictionary (new Dictionary: a: 22 b: #(1 2 3))  (new Dictionary: a: true)  (new Dictionary: a: bb c: '(b c) d: 1)  (new Dictionary: as: "Hello there" b: #(3 4))
Directory (new Directory: 22 a: #(1 2 3) b:)  (new Directory: true a:)  (new Directory: a: bb: '(b c) d: c: 1)
Matrix (new Matrix:1 4 c: b: 2 3)  (new Matrix:1 3 c: b: 2)  (new Matrix:2 3 2 c: b: 2 d: '(8 . d) 2)
NumMatrix (new Matrix: number:1 3 1.1 2.4 3)  (new Matrix: number:1 3 1 4 2)  (new Matrix: number:2 2 3 2.56 -56 2.2 6 8.234 2)

Bracket Operator []

The Bracket Operator [ ] supports both setting and referencing indexed elements within an object in AIS Lisp. The bracket operator, used for reference, is the equivalent of the ref built-in function. The bracket operator, used for assignment, is the equivalent of the setq special form.

 

Type:       Lexical

 

Syntax examples

 

       variable[index]

       (setq variable[index] newValue)

 

Equivalence


x[5] is identical to (ref x 5)
(setq x[2] "Hello") is identical to (setq (ref x 0) "Hello")

 

When To Use

Use the bracket operator when writing out the full equivalent setq or ref expressions would be too cumbersome or would make the Lisp program less readable.

 

Example 1

Here are just a few sample object retrievals and settings using the bracket operator.

 

    (setq x (new Vector:3 45 -6 78)) ;; Set the newly created vector object into the variable x.

    x[2]   Returns   78

    x[0]   Returns   45

    (setq x[0] 26)   Returns   #(26 -6 78)

    (setq x "Hello World") ;; Set the string "Hello World" into the variable x.

    x[1]   Returns   #\e

    x[6]   Returns   #\W

    (setq x[0] #\h)   Returns   "hello World"

 

Notes & Hints

Refer to the appropriate object data type reference guide to determine the effect of the bracket operator on that specific type or even what types of indices the bracket operator can take. For String objects refer to the String Object Reference Guide, For Vector objects refer to the Vector Object Reference Guide, For Dictionary objects refer to the Dictionary Object Reference Guide, etc.

Dot Operator .

The Dot Operator . supports both setting and referencing indexed elements within an object in AIS Lisp. The dot operator, used for reference, is the equivalent of the ref built-in function. The dot operator, used for assignment, is the equivalent of the setq special form.

 

Type:       Lexical

 

Syntax examples

 

       variable.indexSymbol

       (setq variable.indexSymbol newValue)

 

Equivalence


x.A is identical to (ref x A:)
(setq x.C "Hello") is identical to (setq (ref x C:) "Hello")

 

When To Use

Use the dot operator when writing out the full equivalent setq or ref expressions would be too cumbersome or would make the Lisp program less readable.

 

Example 1

Here are just a few sample object retrievals and settings using the dot operator.

 

    (setq x (new Structure: A: 45 B: -6 C: 78)) ;; Set the newly created Structure object into the variable x.

    x.C   Returns   78

    x.A   Returns   45

    (setq x.A 26)   Returns   #{A 26 B -6 C 78}

    (setq x.B 0)   Returns   #{A 26 B 0 C 78}

 

Notes & Hints

Refer to the appropriate object data type reference guide to determine the effect of the dot operator on that specific type or even what types of indices the bracket operator can take. For String objects refer to the String Object Reference Guide, For Vector objects refer to the Vector Object Reference Guide, For Dictionary objects refer to the Dictionary Object Reference Guide, etc.

Offset Addressing

The offset function returns the byte offset, from the start of the specified basis object basis, when computed using the specified constant indices index1, index2, index3. The offset function operates on a number of object types and can accept from one to three indices depending upon the type of basis object. The offset function is an important tool in supporting direct offset addressing using AIS Lisp registers addressing.

The offset function is called automatically, by the compiler, when an AIS Lisp register variable is indexed by a constant offset expression, using the bracket operator such as: register[:integer:], register[basis(name)], register[basis(index1)], register[basis(index1,index2)], or register[basis(index1,index2,index3)]. AIS Lisp treats each register offset expression as a single compound variable name. Therefore, there can be absolutely NO whitespace between any of the parts of the register offset expression. It should also be noted that AIS Lisp only supports register offsets of up to 32,767 bytes.

                vars:((employee #{decl| (Name "John Doe") (Number:Salary 23450.00)}))


                rvars:(NumPointer:ptr)


                (setq ptr employee)     ;; Loads register ptr with the address of the employee Structure.


                (+= ptr[employee(Salary)] 200.0)     ;; Adds 200 to Salary, using direct offset addressing from the ptr register.

 

Type:       Function

 

Syntax examples

 

       (offset basis index1)
       (offset basis index1 index2)
       (offset basis index1 index2 index3)
       register[:integer:]
       register[basis(name)]
       register[basis(index1)]
       register[basis(index1,index2)]
       register[basis(index1,index2,index3)]

 

Arguments


basis The specified basis object to be referenced via direct offset addressing.
name (Mandatory) The field name from which to compute the direct offset address.
Returns The direct offset address, from the start of the basis object, for the indices specified.

basis The specified basis object to be referenced via direct offset addressing.
index1 (Mandatory) The first numeric index from which to compute the direct offset address.
index2 (Optional) The second numeric index from which to compute the direct offset address.
index3 (Optional) The third numeric index from which to compute the direct offset address.
Returns The direct offset address, from the start of the basis object, for the indices specified.

 

When To Use

The offset function is an important tool in supporting direct offset addressing using AIS Lisp registers addressing.

The offset function operates on a number of object types and can accept a number of different constant indices, as follows:

 

Example 1

Here follows a simple example of AIS Lisp offset addressing in action.

      vars:((complex #{Real 2134.2 Imagine 23450.00}))
      rvars:(NumPointer:ptr)
      (setq ptr complex)     ;; Loads register ptr with the address of the complex Structure.
      (+= ptr[complex(Real)] 200.0)     ;; Adds 200 to the real part of complex.
      (+= ptr[complex(Imagine)] 1540.0)     ;; Adds 200 to the imaginary part of complex.

 

Example 2

Here follows a simple example of AIS Lisp offset addressing for Number Matrices.

      vars:((gauss #(NumMat[2 2 2]| 1.0 2.0 3.0 4.0 5.0 6.0 7.0 8.0)))
      rvars:(NumPointer:ptr)
      (setq ptr gauss)     ;; Loads register ptr with the address of the gauss Matrix.
      (setq ptr[gauss(1 0 1)] .45)     ;; Moves .45 into the [1 0 1]th element of the matrix.

 

Notes & Hints

The offset function is an important tool for supporting direct register offset addressing in AIS Lisp.

Carot Operator ^

The Carot Operator ^ preceding a symbol references a global variable value even in contexts where there are local variable naming conflicts. The AIS Lisp ^ operator uses the getGlobalValue function to return the global value of the specified symbol argument. The carot operator ^name is the equivalent of the (getGlobalValue name:) function. Also the carot operator ^name has a similar effect as the |Gv:name| symbolic form.

 

Type:       Lexical

 

Syntax examples

 

       ^variable

 

       |Gv:variable|

 

Equivalence


^name is identical to (getGlobalValue name:)
^name has a similar effect as |Gv:name|

 

When To Use

Use the carot operator when writing out the full equivalent (getGlobalValue name:) expression would be too cumbersome or would make the Lisp program less readable, or use the |Gv:name| symbolic form to achieve the same effect without a function call.

 

Example

Here are just a few sample globals retrievals using the carot operator.

 

    (setq x (new Structure: A: 45 B: -6 C: 78)) ;; Set the Structure object into the global variable x.

    ((lambda(x) ^x) 2)   Returns   #{A 45 B -6 C 78}

    ((lambda(x) |Gv:x|) 2)   Returns   #{A 45 B -6 C 78}

    ((lambda(x) x) 2)   Returns   2

 

Notes & Hints

Use the carot operator only as shorthand notation for the (getGlobalValue name) function, or use the |Gv:name| symbolic form to achieve the same effect without a function call.

Quote Operator '

The Quote Operator ' preceding a list or symbol converts the list or symbol into a list or symbol constant. The AIS Lisp ' is the short hand equivalent of the quote special form.

 

Type:       Lexical

 

Syntax examples

 

       'symbol

       '(...list...)

 

Equivalence


'thing is identical to (quote thing)

 

When To Use

Use the quote operator when writing out the full equivalent quote expression would be too cumbersome or would make the Lisp program less readable.

 

Example 1

Here are just a few examples of the quote operator in action.

 

    (defun foo(x) (length x))   Returns   #<Lambda 291>

    (setq x (new Vector: 3 1 2 3))   Returns   #(1 2 3)

    x   Returns   #(1 2 3)

    'x   Returns   x

    (foo x)   Returns   3

    (foo 'x)   Returns   1

    '(foo x)   Returns   (foo x)

 

Notes & Hints

Use the quote operator only as shorthand notation for the quote special form.

Colon Operator :

The Colon Operator : suffixing a symbol always converts the symbol into a symbolic constant. Also symbols enclosed in vertical bars |I am a compound symbol|: can be converted into symbolic constants using the colon operator. The AIS Lisp name: is the short hand equivalent of the (quote name) special form.

Using the colon operator on a symbol at the head of a list can be used as short hand to send a message to a class instance. The AIS Lisp (msg: self) is the short hand equivalent of the (send msg: self) method invocation.

 

Type:       Lexical

 

Syntax examples

 

       name:

       |I am a compound symbol|:

       (msg: self)

 

Equivalence


name: is identical to (quote name)
(msg: self) is identical to (send msg: self)

 

When To Use

Use the colon operator when writing out the full equivalent (quote name) or (send msg: self) expressions would be too cumbersome or would make the Lisp program less readable.

 

Example

Here are some examples of the colon operator in action.

 

    (defclass foo() svars:(Number:x Vector:y) (defun new(self) (setq y (new Vector: 3 1 2 3))) true)

    (defmethod foo:len(self) (length y))

    (setq x (new foo))

    (len: x)   Returns   3

    x:   Returns   x

    (length x)   Returns   2

    (length x:)   Returns   1

 

Notes & Hints

Use the colon operator as shorthand notation for the (quote name) special form, or use the colon operator as shorthand notation sending a message to a class instance.

At Operator @

The At Operator @ infixing a compound name, converts the compound name into a member macro invocation. The AIS Lisp parent@name is the short hand equivalent of the (refmacro parent name) special form. Using the @ Operator on a symbol at the head of a list can be used to invoke a member macro during compilation.

 

Type:       Lexical

 

Syntax examples

 

       (parent@name)

       (parent.child@name)

       (parent.child[2]@name)

 

Equivalence


parent@name is identical to (refmacro parent name)

 

When To Use

Use the @ operator when writing out the full equivalent (refmacro parent name) expression would be too cumbersome or would make the Lisp program less readable.

 

Example

Here is an example of the colon operator in action.

 

    (defun foo(x) pvars:(v len) (setq v x))

    (defmacro foo:len(self) (macroReplace self '(length %1)))

    (setq x (new Vector: 3 1 2 3))

    (foo@len x)   Returns   3

 

Functions

Lisp is the original functional programming language. Heavily influenced by Church's lambda calculus during its development phase, Lisp attempts to express all algorithms as a combination of function declarations and function calls. AIS Lisp continues that tradition; however, in AIS Lisp every function declaration creates an executable Lambda object (instead of a function object). This means that AIS Lisp function declarations can be but need not be much more than simple functions.

 

Example 1: Hello world Declaration

An example of a simple Lisp function declaration is the time honored Hello World function as follows (notice that Lisp functions are declared with the lambda keyword further showing the heavy influence of Church's lambda calculus on Lisp language design).

 

    (lambda() "Hello World")   Returns   #<Lambda 2940>

Notice that lambda is also a Lisp function which creates and returns other functions. In effect, lambda is like a function factory whose job is to produce other functions (except in AIS Lisp lambda returns an Lambda object which can behave like a function).

 

Example 2: Hello world Invocation

Enclosing lambda in parentheses invokes the lambda function which returns an Lambda object (albeit an Lambda object designed to act like a function). Enclosing lambda in parentheses twice, first invokes the lambda function, which returns the "Hello World" function, and then immediately invokes the just created function, which returns "Hello World" as follows.

 

    ((lambda() "Hello World"))   Returns   "Hello World"

 

 

Example 3: Named Functions

The define keyword supports the creation of globally named lambda values (functions) as follows.

 

    (define foo (lambda() "Hello World"))   Returns   #<Lambda 2940>

 

The defun keyword also supports the creation of globally named lambda values (functions) as follows.

 

    (defun foo() "Hello World")   Returns   #<Lambda 2940>

 

The newly defined foo function can be invoked as follows.

 

    (foo)   Returns   "Hello World"

 

Argument Variables

Argument values, passed to Lisp functions, increase the flexibility and power of the programming language. AIS Lisp supports the declaration of argument variables in the lambda, defun and other special forms.

Argument passing to functions is a central feature of Church's lambda calculus (which heavily influenced Lisp language design) as well as many other general purpose computer programming languages. AIS Lisp supports the declaration of up to twenty (20) argument variables. All Lisp arguments are passed as Words and cannot be initialized. However, each Lisp argument variable can be declared as having a preferred type (even though encapsulated in a Word). Each Lisp argument variable can be declared as one of the following types:

 

Example 1: Cube Declaration

An example of a simple Lisp function declaration is the time honored Cube function as follows (notice that Lisp functions are declared with the lambda keyword further showing the heavy influence of Church's lambda calculus on Lisp language design).

 

    (lambda(Number:x) (* x x x))   Returns   #<Lambda 2341>

Notice that lambda is also a Lisp function which creates and returns other functions. In effect, lambda is like a function factory whose job is to produce other functions (except in AIS Lisp lambda returns an Lambda object which can behave like a function).

Note: The x argument has been declared to expect a Number data type.

 

Example 2: Cube Invocation

Enclosing lambda in parentheses invokes the lambda function which returns an Lambda object (albeit an Lambda object designed to act like a function). Enclosing lambda in parentheses twice, allong with the argument 3, first invokes the lambda function, which returns the Cube function, and then immediately invokes the just created function, passing it the argument 3, which returns 27 (the cube of the argument 3) as follows.

 

    ((lambda(x) (* x x x)) 3)   Returns   27

Note: The x argument is undeclared and defaults to expect a Word data type.

 

Example 3: Named Functions

The define keyword supports the creation of globally named lambda values (functions) as follows.

 

    (define cube (lambda(x) (* x x x)))   Returns   #<Lambda 928>

 

The defun keyword also supports the creation of globally named lambda values (functions) as follows.

 

    (defun cube(x) (* x x x))   Returns   #<Lambda 340>

 

The newly defined cube function can be invoked and passed the argument 4 as follows.

 

    (cube 4)   Returns   64

 

Example 4: Arguments Visible in Lambda Object

AIS Lisp returns the cube function as an executable Lambda object. The argument x is visible in the Av Structure element of the cube Lambda as follows.

 

    cube.Av   Returns   #{x #void}

 

The cube Lambda knows that it needs an argument, upon invocation, to bind with the argument variable x. Attempting to invoke the cube Lambda with less than one or more than one argument will return an error as follows.

 

    (cube)   Returns   !arglist!

 

    (cube 23 6)   Returns   !arglist!

 

Temporary Variables

Temporary variables, for temporary storage in Lisp functions, increase the flexibility and power of the programming language. AIS Lisp supports the declaration of temporary variables in the lambda and defun special forms. Temporary variables in functions is a central feature of Lisp as well as many other general purpose computer programming languages. The vars keyword is used to declare persistent variables. Each AIS Lisp persistent variable is stored in a Word and may optionally be initialized to a constant value; also, each Lisp temporary variable can be declared as one of the following preferred types:

If a temporary variable is uninitialized, it will have a preferred type of Word.

 

Example 1: Making X upper case

An example of a simple Lisp function declaration, which uses temporary variables, is the following function which converts all lower case x characters into upper case X characters. The string to be altered s is passed as an argument. The temporary variables N and n are used to store the length of the string (N) and the character index into the string (n) respectively, as follows.

 

    (defun makeXUpperCase(s)

       vars:((Integer:n 0) N) ;; Declare temporary variables to help loop through each character in the string.

       (setq N (length s)) ;; Set the length of the string in N.

       (loop for n from 0 until N do ;; Loop through each character in the string s.

          ((if (= s[n] #\x) (setq s[n] #\X)) ;; Replace and lower case (x) chars with upper case (X) chars.

          ) ;; end s loop.

       s) ;; Return the altered string s.

 

Invoking the makeXUpperCase function with the argument "x marks the spot" will convert the lower case (x) character into an upper case (X) character as follows.

 

    (makeXUpperCase "x marks the spot")   Returns   "X marks the spot"

 

Example 2: Temporaries Visible in Lambda Object

AIS Lisp returns the makeXUpperCase function as an executable Lambda object. The temporary variables n and N are visible in the Tv Structure element of the makeXUpperCase Lambda as follows.

 

    makeXUpperCase.Tv   Returns   #{n 0 N #void}

 

The makeXUpperCase Lambda knows that it must allocate stack space for two temporary variables (n and N) upon each invocation. At the start of each invocation, the n temporary variable is initialized to 0, and the N temporary variable is initialized to #void.

 

Persistent Variables

Persistent variables, for permanent storage in Lisp functions, increase the flexibility and power of the programming language. Once persistent variables are declared in a Lisp function it starts to cross the line between behaving like a simple function and behaving like a simple Lambda. AIS Lisp supports the declaration of persistent variables in the lambda and defun special forms. Persistent variables in functions is a central feature of AIS Lisp as well as many other Lambda oriented computer programming languages. AIS Lisp supports the declaration of an unlimited number of persistent variables in any one Lambda.

The pvars keyword is used to declare persistent variables. Each AIS Lisp persistent variable is stored in a Word and may optionally be initialized to a constant value; also, each Lisp persistent variable can be declared as one of the following preferred types:

If a persistant variable is uninitialized, it will have a preferred type of Word.

 

Example 1: Keeping Score

An example of a simple Lisp function declaration, which uses persistent variables, is the following function which keeps a rolling total of all the numbers ever passed to it. The number to be tallied is passed as an argument. The persistent variable total is used to store the total of all prior numbers ever passed, as follows.

 

    (defun keepScore(Integer:n)

       pvars:((Integer:total 0)) ;; Declare a persistent variable to save the rolling total.

       (setq total (+ n total)) ;; Accumulate the rolling forward sum in total.

       total) ;; Return the rolling forward sum.

 

Invoking the keepScore function with the argument 2 will return the following results.

 

    (keepScore 2)   Returns   2

    (keepScore 2)   Returns   4

    (keepScore 2)   Returns   6

 

Example 2: Persistent Variables Visible in Lambda Object

AIS Lisp returns the keepScore function as an executable Lambda object. The persistent variable total is visible in the Pv Structure element of the keepScore Lambda as follows.

 

    keepScore.Pv   Returns   #{total 6}

    keepScore.Pv.total   Returns   6

 

Class Variables

Class variables, for permanent storage in Lisp functions, increase the flexibility and power of the programming language. Once class variables are declared in a Lisp function it starts to cross the line between behaving like a simple function and behaving like an object oriented Lambda. AIS Lisp supports the declaration of class variables in the lambda and defun special forms. Class variables in functions is a central feature of AIS Lisp as well as many other objected oriented computer programming languages. AIS Lisp supports the declaration of an unlimited number of class variables in any one Lambda.

The svars keyword is used to declare class variables. Each AIS Lisp class variable is stored in a Word and may optionally be initialized to a constant value; also, each Lisp class variable can be declared as one of the following preferred types:

If a class variable is uninitialized, it will have a preferred type of Word.

 

Example 1

Creating and initializing a new class of object.

(defclass employee()

          svars:(String:Name (Number:Salary 0.0))

          (defun new(self name salary)

              (setq Name (string name))

              (setq Salary (number salary))

              self) ;; end of new method

          true) ;; end of employee class

(setq x (new employee "John Doe" 24000.0))

x.Name     ==>     "John Doe"

x.Salary     ==>     24000.0

Note: The new function creates a new copy of the class object, containing all of the variables as declared in the svars:(). This newly minted copy of the class object is passed to the new method as the argument self. The Sv register is automatically set to address the self argument so any reference to the svars:() variables will effect the self object.

Constant Variables

Constant variables, for permanent storage in Lisp functions, increase the flexibility and power of the programming language. Once constant variables are declared in a Lisp function it starts to cross the line between behaving like a simple function and behaving like a simple Lambda. AIS Lisp supports the declaration of constant variables in the lambda and defun special forms. Constant variables in functions is a central feature of AIS Lisp as well as many other Lambda oriented computer programming languages. AIS Lisp supports the declaration of an unlimited number of constant variables in any one Lambda.

The cvars keyword is used to declare constant variables. Each AIS Lisp constant variable is stored in a Word and may optionally be initialized to a constant value; also, each Lisp constant variable can be declared as one of the following preferred types:

If a constant variable is uninitialized, it will have a preferred type of Word.

 

Example 1: Keeping Score

An example of a simple Lisp function declaration, which uses persistent variables, is the following function which keeps a rolling total of all the numbers ever passed to it. The number to be tallied is passed as an argument. The persistent variable total is used to store the total of all prior numbers ever passed, as follows.

 

    (defun keepScore(Integer:n)

       cvars:((Integer:total 0)) ;; Declare a constant variable to save the rolling total.

       (setq total (+ n total)) ;; Accumulate the rolling forward sum in total.

       total) ;; Return the rolling forward sum.

 

Invoking the keepScore function with the argument 2 will return the following results.

 

    (keepScore 2)   Returns   2

    (keepScore 2)   Returns   4

    (keepScore 2)   Returns   6

 

Example 2: Constant Variables Visible in Lambda Object

AIS Lisp returns the keepScore function as an executable Lambda object. The constant variable total is visible in the Cv Structure element of the keepScore Lambda as follows.

 

    keepScore.Cv   Returns   #{total 6}

    keepScore.Cv.total   Returns   6

 

Register Variables

AIS Lisp programmers use register variables when they wish to increase execution speed, and are willing to sacrifice flexibility. Register Variables, for fast temporary storage in Lisp functions, decrease the flexibility and increase the execution speed of the programming language. AIS Lisp supports the declaration of register variables in the lambda and defun special forms. Register variables in functions is a central feature of AIS Lisp as well as many other execution speed sensitive computer programming languages. AIS Lisp supports the declaration of up to fifty register variables in any one Lambda (see the regs special form). The regs keyword is used to declare persistent variables. Each AIS Lisp persistent variable is stored in a Word and may optionally be initialized to a constant value; also, each Lisp register variable can be declared as one of the following preferred types:

If a temporary variable has no declared type and is uninitialized, it will have a preferred type of Integer.

Register variables may be initialized to a Boolean constant, a Character constant, an Integer constant, or a Number constant. Obviously register variables should be declared to a preferred type compatible with any initial constants. If a Register variable is initialized to a Boolean constant, it is automatically declared as type Integer (all Boolean data is stored in Register variables as 64 bit integers). If a Register variable is initialized to a Character constant, it is automatically declared as type Integer. (all Character data is stored in Register variables as 64 bit integers). If a Register variable is initialized to an Integer constant, it is automatically declared as type Integer. If a Register variable is initialized to a Number constant, it is automatically declared as type Number. If a Register variable in both undeclared and uninitialized, then it automatically defaults to a preferred type of Integer.

 

Example 1: Making X upper case (flexibly)

An example of a simple Lisp function declaration, which uses temporary (not register) variables, is the following function which converts all lower case x characters into upper case X characters. The object to be altered s is passed as an argument. The temporary variables N and n are used to store the length of the object (N) and the index into the object (n) respectively, as follows.

 

    (defun makeXUpperCase(s)

       vars:((n 0) N) ;; Declare temporary variables to help loop through each character in the object.

       (setq N (length s)) ;; Set the length of the object in N.

       (loop for n from 0 until N do ;; Loop through each character in the object s.

          ((if (= s[n] #\x) (setq s[n] #\X)) ;; Replace and lower case (x) chars with upper case (X) chars.

          ) ;; end s loop.

       s) ;; Return the altered object s.

 

Invoking the makeXUpperCase function with the argument "x marks the spot" will convert the lower case (x) character into an upper case (X) character; but, makeXUpperCase is very flexible. It will also work if we send it a vector object, or a Structure object, as follows.

    (makeXUpperCase "x marks the spot")    Returns   "X marks the spot"

    (makeXUpperCase #(45 #\x 34.5 (8 . 6)))    Returns   #(45 #\X 34.5 (8 . 6))

    (makeXUpperCase #{A 45 B #\x C 34.5}    Returns   #{A 45 B #\X C 34.5}

 

Designing the makeXUpperCase function with dynamically typed temporary variables, makes it very general and flexible; however, there is a cost: execution speed.

 

 

Example 2: Making X upper case (fast)

Designing the makeXUpperCase function with register variables, and restricting it's flexibility so that it works only with String objects, will make it execute much faster, as follows.

 

    (defun makeXUpperCase(String:s)

       regs:(CharPointer:p (n 0) c N (CX #\X)) ;; Declare register variables for speed.

       (vmregObjLength s N) ;; Set the length of the string into the register N.

       (setq p s) ;; Set a pointer to the character data (in the string) into the register p.

       (loop for n from 0 until N do ;; Loop through each character in the string s.

          (setq c p[n]) ;; Load the next character into the register c.

          ((if (= c #\x) (setq p[n] CX)) ;; Replace any lower case (x) chars with upper case (X) chars.

          ) ;; end s loop.

       s) ;; Return the altered string s.

 

Invoking the makeXUpperCase function with the argument "x marks the spot" will convert the lower case (x) character into an upper case (X) character; and, the inflexible register version will execute very much faster than the more general version.

 

Vector Data Instructions

AIS Lisp programmers use vector data instructions when they wish to process data arrays at the fastest possible execution speeds. The AIS Lisp vector data instructions provide two internal Vector Processing Stacks (one for Integer data and one for Number data), three internal Vector Data Pointer Registers, three internal Vector Data Pointer Increment Registers, and one internal Vector Data Loop Counter Register. The AIS Lisp vector data instruction set is designed to use the latest highly parallel auxilliary, floating point, signal processing, and graphics microprocessor chips, and supports the fastest possible processing of the following types of vectored data:

The use of vector data instructions is appropriate for those applications requiring the fastest possible execution speeds, and willing to sacrifice flexibility. Vector data algorithms, for extremely fast processing of vectored data, decrease the flexibility and increase the execution speed of the programming language. Vector data algorithms must be composed of clustered vector data instuctions with no other intervening instruction types. Grouping each vector data algorithm together in a cluster of instructions allows the AIS Lisp Just-In-Time compiler to easily recognize the vector data algorithm and to execute the algorithm on the fastest available auxilliary, floating point, signal processing, or graphics microprocessor chip.

 

Example 1: Vector Dot Product

An example of a vector Dot Product computation, which uses vector data instructions, is the following function.

    (defun vectorDotProduct(NumVector:X NumVector:Y)

       regs:(NumPointer:px NumPointer:py Number:z n N) ;; Declare register variables for speed.

       (vmregObjPointer X px) ;; Set a pointer to the vector X.

       (vmregObjPointer Y py) ;; Set a pointer to the vector Y.

       (vmregObjLength X N) ;; Get the length of X (example assumes the Vectors are of equal length).

       (vmregMoveImmediate Number: n) ;; Set the byte size of a Number in n.

       (vmvecSetPointers px py py) ;; Declare the Vector Pointer Registers.

       (vmvecSetIncrements n n n) ;; Declare the Vector Increment Registers.

       (vmvecNumScalar dot: n z) ;; Perform the dot product on the two vectors.

       z) ;; Return the resulting scalar z.

 

Example 2: Vector Multiply with Cosine

An example of a simple Lisp vector multiply with cosine, which uses vector data instructions, is the following function.

    (defun vectorCosMultiply(FloatVector:X FloatVector:Y FloatVector:Z)

       regs:(FloatPointer:px FloatPointer:py FloatPointer:pz n N) ;; Declare register variables for speed.

       (vmregObjPointer X px) ;; Set a pointer to the vector X.

       (vmregObjPointer Y> py) ;; Set a pointer to the vector Y.

       (vmregObjPointer Z pz) ;; Set a pointer to the vector Z.

       (vmregObjLength X N) ;; Get the length of X (example assumes the Vectors are of equal length).

       (vmregMoveImmediate Float: n) ;; Set the byte size of a float in n.

       (vmvecSetPointers px py pz) ;; Declare the Vector Pointer Registers.

       (vmvecSetIncrements n n n) ;; Declare the Vector Increment Registers.

       (vmvecInitialize target: n) ;; Start the Vector data loop.

       (vmvecPush Float: argument:) ;; Load a Number from the X vector.

       (vmvecPush Float: source:) ;; Load a Number from the Y vector.

       (vmvecBinary mul:) ;; Multiply the X and Y Numbers together.

       (vmvecUnary cos:) ;; Compute the cosine of the product.

       (vmvecPop Float: target:) ;; Save the result in the Z vector.

       (vmvecLoop) ;; End the Vector data loop.

       Z) ;; Return the result vector Z.

 

In this example, the vector data algorithm begins with the first vmvecSetPointers instruction and ends with the last vmvecLoop instruction. The algorithm is composed of a clustered group ov vmvec instructions with no intervening other instruction types. The algorithm is readily identifyable by the AIS Lisp JIT which is free to execute the algorithm on the fastest available auxilliary, floating point, signal processing, or graphics microprocessor chip.

 

Example 3: Bubble Sort

An example of a simple Lisp Integer bubble sort, which uses vector data instructions, is the following function which sorts an Integer Vector.

    (defun bubbleSort(NumVector:V)

       regs:(NumPointer:pv Number:v i n N) ;; Declare register variables for speed.

       (vmregObjPointer V pv) ;; Set a pointer to the vector in pv.

       (vmregObjLength V N) ;; Set the length of the vector in N.

       (vmregMoveImmediate Integer: i) ;; Set the byte size of an Integer in i.

       (vmvecSetPointers pv pv pv) ;; Declare the Vector Pointer Registers.

       (vmvecSetIncrements i i i) ;; Declare the Vector Increment Registers.

       (loop for n from N until 0 by -1 do ;; Loop through each Integer in the vector.

          (vmvecRefNumber pv v) ;; Load the first integer in the bubble into a temporary register.

          (vmvecPushNumber v) ;; Load the first integer in the bubble.

          (vmvecInitialize argument: n) ;; Start looping through each integer in the bubble.

          (vmvecPush Number: argument:) ;; Load the next integer in the bubble.

          (vmvecSwap LT:) ;; Leave the larger number on top of the stack.

          (vmvecPop Number: argument:) ;; Save the larger number.

          (vmvecLoop) ;; End the Vector data loop.

          (vmvecPopNumber v) ;; Save the minimum number in a temporary register.

          (vmregSetNumber v pv) ;; Save the minimum number in the first position of this bubble.

          (vmregAddPointer i pv) ;; Promote the vector pointer to the next bubble.

          ) ;; end n loop.

       V) ;; Return the sorted vector.

 

Safe Coding Practice

AIS Lisp programmers are encouraged to use a specific style of coding as their Safe Coding Practice. This prototypical coding style obtains the most safety out of the AIS Lisp compiler while requiring the least engineering effort during the development process. There are only three simple rules for Safe Coding Practice, as follows:

 

Example: A straight forward example of AIS Lisp Safe Coding Practice is shown below.

 

    (defun foo(i)

       vars:(m M n N x)

       vars:(v1 v2)

       (setq N i)

       (setq M 1000)

       (setq v1 (new Vector: Number: M 0))

       (setq v2 (new Vector: Number: M 0))

       (loop for n from 0 until N do

          (loop for m from 0 until M do

             (setq x v1[m])

             (+= x 10.0)

             (*= x x)

             (/= x 10.0)

             (setq v2[m] x)

             ) ; end m loop

          ) ; end n loop

       v2) ; end of foo

Normal Coding Practice

AIS Lisp programmers are encouraged to use a specific style of coding as their Normal Coding Practice. This preferred coding style obtains the most efficiency out of the AIS Lisp compiler without requiring the extra effort of writing in Lisp Assembler. There are only five simple rules for Normal Coding Practice, as follows:

 

Example: A straight forward example of AIS Lisp Normal Coding Practice is shown below.

 

    (defun foo(Integer:i)

       regs:(m M n N Number:x (Number:cTen 10.0)) ;; Default type for regs is always Integer

       vars:(NumVector:v1 NumVector:v2) ;; Default type for vars, pvars, & cvars is always Word

       (setq N i)

       (setq M 1000)

       (setq v1 (new Vector: Number: M 0))

       (setq v2 (new Vector: Number: M 0))

       (loop for n from 0 until N do

          (loop for m from 0 until M do

             (setq x v1[m])

             (+= x cTen)

             (*= x x)

             (/= x cTen)

             (setq v2[m] x)

             ) ; end m loop

          ) ; end n loop

       v2) ; end of foo

Advanced Coding Practice

AIS Lisp programmers are encouraged to use a specific style of coding as their Advanced Coding Practice. This advanced coding style obtains the most efficiency out of the AIS Lisp compiler by adding the extra effort of writing in almost Lisp Assembler.

The mission of the Lisp compiler is to compile reasonably fast (but safe) Lambdas for prototyping complex algorithms. A secondary mission of the Lisp compiler is to provide the facility for reworking mature prototypes into very fast portable machine language algorithmic Lambdas. Implicit in the design of the Lisp compiler, is that the programmer will place extra engineering effort into those mission critical Lambdas needing rework; and, that the resulting reworked Lambdas will be very fast (but not necessarily safe). Therefore, the final rework stage is intended only for mission critical Lambdas where speed is essential; AND, it is assumed that the programmer will invest a level of personal attention so as to be satisfied with the speed and safety of the final generated code.

There are eight simple rules for Advanced Coding Practice, as follows:

 

Example: An example of AIS Lisp advanced coding practice is shown below.

 

    (defun foo(Integer:i)

       regs:(m M n N Number:x Number:y (Number:cTen 10.0))

       regs:(NumPointer:pv1 NumPointer:pv2)

       vars:(NumVector:v1 NumVector:v2)

       (setq N i)

       (setq M 1000)

       (setq v1 (new Vector: Number: M 0))(setq pv1 v1) ;; Create Vector and initialize pointer to Vector

       (setq v2 (new Vector: Number: M 0))(setq pv1 v1) ;; Create Vector and initialize pointer to Vector

       (vmregRunInHardware start:)

       (loop for n from 0 until N do

          (loop for m from 0 until M do

             (setq x pv1[m])

             (+= x cTen)

             (*= x x)

             (/= x cTen)

             (setq pv2[m] x)

             ) ; end m loop

          ) ; end n loop

       (vmregRunInHardware stop:)

       (vmregRunInHardware start:)

       (loop for m from 0 until M do

          (setq x pv1[m])

          (setq y pv2[m])

          (+= y x)

          (setq pv2[m] y)

          ) ; end m loop

       (vmregRunInHardware stop:)

       v2) ; end of foo

AIS Lisp compared with C/C++ Execution Speed

Normally AIS Lisp programmers are encouraged to use the safe style of coding for their prototype Lambdas, while C/C++ programmers are encouraged to use the existing class hierarchy and to write modular code for ease of reuse. In this section we will see that writing in Lisp safe mode and writing C++ best practices does not necessaruly translate into code which executes fast. Below is the console display after running the "Test_LispTime.sl" in the Tutorial_TestSuite folder. The test suite should be run with each new computer to give the programmer a feel for the relative execution speed of each class of instructions.

(runScript (append _path "/Test_LispTime.sl"))
*********************************************************************************************************
runSuite starting test script: Test_LispTime.sl with NativeCode = true
*********************************************************************************************************
****** Lisp Timing Test: ******

       An internal study of several large AIS Lisp programs compared to equivalent C programs discovered that Lisp ran approximately 1.75x slower than similar C programs.
       However both the large Lisp and large C programs had many modules which were unoptimized and generic - which seems to be a frequent situation with large programs.
       However the same internal study showed AIS Lisp programs were coded and debugged approximately 5x to 7x faster than their equivalent C programs.

       Normal safe mode (untyped) AIS Lisp programs run slower than similar optimized C programs.
       This is because of the many memory management services and prototyping safety features embedded in AIS Lisp, and will vary with the mix of instruction types.
       For instance Lisp garbage collection is quite powerful, can even manage cyclic self-referencing graphs, but it is not free.

       For functions, which must run fast, AIS Lisp supports strong typing, pointers, register mode, and Lisp assembler instructions.
       The difference in execution speed between safe mode Lisp and optimized Lisp is quite significant.

       In general:
       Machine register optimized Lisp programs involving: Integers, Numbers, Vectors, Matrices, or Strings run faster than C programs.
       Untyped Lisp programs involving: safe mode instructions, function calls, or garbage collection, run slower than C programs.

       This Lisp timing test is designed to inform you of the speed differences between Lisp and C on your current hardware for different register optimized and safe mode instructions.

****** Lisp Register Optimized (strongly typed, Lisp assembler) versus C speed differences: ******

****** A Register Optimized Lisp Integer Vector Loop runs 1.3722 times as fast as C 
****** A Register Optimized Lisp Number Vector Loop runs 3.4773 times as fast as C 
****** A Register Optimized Lisp String Vector Loop runs 1.9087 times as fast as C 
****** A Register Optimized Lisp Function Call Loop runs 0.6103 times as fast as C 
****** A Register Optimized Lisp Dot Product Loop runs 5.6304 times as fast as C 
****** A Register Optimized Lisp Number Vector Distance Loop runs 3.8971 times as fast as C 

****** Lisp Register Optimized versus Lisp Safe Mode speed differences (How much speed does safety cost?): ******

****** A Register Optimized Lisp Integer Vector Loop runs 16.8145 times as fast as Lisp in Safe Mode 
****** A Register Optimized Lisp Number Vector Loop runs 115.0667 times as fast as Lisp in Safe Mode 

****** Lisp Safe Mode versus C speed differences: ******

****** A Safe Mode Lisp Integer Vector Loop runs 0.0854 times as fast as C
****** A Safe Mode Lisp Number Vector Loop runs 0.0295 times as fast as C 
****** A Safe Mode Lisp String Vector Loop runs 0.4563 times as fast as C 
****** A Safe Mode Lisp Function Call Loop runs 0.6004 times as fast as C 

Lisp Timing Test completed in [60.30100000000001] Seconds

The reader is encouraged to study the timings in the above console display - especially the relative speed differences. It is clear from the above timing differences, that use of machine registers and in-line code is optimal for fast execution speed. Safe mode, function calls, and garbage collection are pessimal for fast execution speed. Safe mode offers many safety checks and other services which slow execution speed. Function calls cause a branch from the in-line instruction stream which is very costly in terms of execution speed in both C and Lisp. Lisp garbage collection allows memory management of cyclic self-referencing structures, but it does slow execution speed.

One of the informative results, is the superior execution speed of Lisp register optimized code over C code for Integer, and Number Vector and Matrix operations. Another informative result, is the superior execution speed of Lisp String optimized code over C code for Strings and String Vector operations. An expected result is the reduction in execution speed in programs containing excessive garbage collection (programs where Heap Objects are created and deleted over and over). A noteable result is the slowing of execution speed by inclusion of function calls. This is not just a problem isolated to Lisp. It is equally true in C/C++. Many C++ class libraries are implemented with operator overloaded function calls and/or deep class hierarchies. The first method call contains an embedded call to execute the parent's method with continued embedded calls up the inheritance chain. This is typical of many C++ class hierarchies and is extremely pessimal for execution speed.

Another very informative result, is the significant difference in execution speed between Lisp optimized code and Lisp safe mode for Vector and Matrix operations. This is entirely due to in-line register instructions with no embedded function calls.

A further very informative result shows the execution speed of C function calls is nearly two thirds faster than Lisp function calls on average. C/C++ functions require only the initialization of their local variables on the C stack. Lisp Lambdas must initialize their regs, vars, pvars, and cvars variables which, on average, is more time consuming than the initialization for C function calls.

In summary, safe mode, function calls, and garbage collection are pessimal for execution speed. In C/C++, try to use class hierarchies which have as many methods in-lined as possible. In Lisp, try to in-line as much register optimized code as possible without embedded function calls or operations requiring garbage collection. In internal studies, the usual mix of safe mode code and function calls tends to favor C/C++ in large applications. If an application can be written with in-line register optimized code, it will be difficult to write C code which executes faster than Lisp. If an application contains many safe mode instructions, embedded function calls, or much garbage collection, it will be difficult to write Lisp code which executes faster than C.

Generating Fast Machine Code

AIS Lisp programmers are encouraged to use the safe style of coding for their prototype Lambdas. AIS Lisp Lambdas, written in Safe Coding Practice, execute much slower than their Advance Coding Practice cousins; but, they are much safer and require much less engineering and dubugging attention during the development process.

For mission-critical Lambdas, where speed is essential, Safe Coding Practice Lambdas will require conversion to Normal or Advanced Coding Practice. AIS Lisp Programmers are encouraged to use care when converting from the Safe coding style to either Normal or Advanced Coding Practice. The advanced coding style obtains the most efficiency out of the AIS Lisp compiler by adding the extra effort of writing in almost Lisp Assembler. Implicit in the design of the Lisp compiler, is that the programmer will place extra engineering effort into those mission critical Lambdas needing rework; and, that the resulting reworked Lambdas will be very fast (but not necessarily safe). Therefore, the final rework stage is intended only for mission critical Lambdas where speed is essential; AND, it is assumed that the programmer will invest a level of personal attention so as to be satisfied with the speed and safety of the final generated code.

 

Example: foo in Safe Mode.

The example below shows a simple Lambda, named foo, using Safe Coding Practice, and its DRM virtual machine code generated by the compiler.

 

   (defun foo(x) vars:(n m y t xv) (setq y (sin (+ (* xv[n] x) (/ xv[m] 20.0)))))

       (disassemble foo) ==>

000000: (vmref    Tv[n]  Tv[xv]  Tv[__T7] )
000004: (vmmul    Av[x]  Tv[__T7]  Tv[__T6] )
000008: (vmref    Tv[m]  Tv[xv]  Tv[__T8] )
000012: (vmdiv    Cv[__C23334:"20.0"]  Tv[__T8]  Tv[__T7] )
000016: (vmadd    Tv[__T7]  Tv[__T6]  Tv[__T6] )
000020: (vmpush    Tv[__T6] )
000022: (vmcall    1  Gv[sin] Tv[y] )
000026: (vmreturn    Tv[y] )

 

This safe mode version of foo generates only safe mode VM instructions. There are no vmnat or vmreg instructions whatsoever. This program will execute slowly but it will be quite safe.

 

Example: foo with Strong Typing.

The example below shows foo, with strong typing, and its DRM virtual machine code generated by the compiler.

 

   (defun foo(Number:x) vars:(Integer:n Integer:m Number:y Number:t NumVector:xv) (setq y (sin (+ (* xv[n] x) (/ xv[m] 20.0)))))

       (disassemble foo) ==>

000000: (vmref    Tv[n]  Tv[xv]  Tv[__T7] )
000004: (vmmul    Av[x]  Tv[__T7]  Tv[__T6] )
000008: (vmref    Tv[m]  Tv[xv]  Tv[__T8] )
000012: (vmdiv    Cv[__C23340:"20.0"]  Tv[__T8]  Tv[__T7] )
000016: (vmadd    Tv[__T7]  Tv[__T6]  Tv[__T6] )
000020: (vmpush    Tv[__T6] )
000022: (vmcall    1  Gv[sin] Tv[y] )
000026: (vmreturn    Tv[y] )

 

This strongly typed version of foo still generates only safe mode VM instructions. There are no vmnat or vmreg instructions whatsoever.

 

Example: foo with simpler expressions.

The example below shows foo, with simpler expressions, and its DRM virtual machine code generated by the compiler.

 

   (defun foo(Number:x) vars:(Integer:n Integer:m Number:y Number:t NumVector:xv) (setq y xv[m]) (/= y 20.0) (setq t xv[n]) (*= t x) (+= t y) (setq y (sin t)))

       (disassemble foo) ==>

000000: (vmrefnumvector    Tv[m]  Tv[xv]  Tv[y] )
000004: (vmndiv    Cv[__C23344:"20.0"]  Tv[y]  Tv[y] )
000008: (vmrefnumvector    Tv[n]  Tv[xv]  Tv[t] )
000012: (vmnmul    Av[x]  Tv[t]  Tv[t] )
000016: (vmnadd    Tv[y]  Tv[t]  Tv[t] )
000020: (vmpush    Tv[t] )
000022: (vmcall    1  Gv[sin] Tv[y] )
000026: (vmreturn    Tv[y] )

 

This strongly typed version of foo, with simpler expressions, generates faster native mode VM instructions. The first five VM instructions are strongly typed native mode which execute faster.

 

Example: foo with register variables.

The example below shows foo, with register variables, and its DRM virtual machine code generated by the compiler.

 

   (defun foo(Number:x) regs:(Integer:n Integer:m Number:y Number:t (Number:c20 20.0)) vars:(NumVector:xv) (setq y xv[m]) (/= y c20) (setq t xv[n]) (*= t x) (+= t y) (setq y (sin t)))

       (disassemble foo) ==>

000000: (vmregObjPointer    Tv[xv]  __Rp  )
000002: (vmregRefXNumber    __Rp   m   y  )
000003: (vmregDivNumber    c20   y  )
000004: (vmregObjPointer    Tv[xv]  __Rp  )
000006: (vmregRefXNumber    __Rp   n   t  )
000007: (vmregLoadNumber    Av[x]  __RNx  )
000009: (vmregMulNumber    __RNx   t  )
000010: (vmregAddNumber    y   t  )
000011: (vmregSinNumber    t   y  )
000012: (vmreturn    y )

 

This register variable version of foo, with simpler expressions, generates very fast register mode VM instructions. All generated VM instructions are strongly typed register mode which execute much faster.

 

Example: Writing Fast Yet Safe Code.

Writing fast AIS Lisp code requires as much careful attention as writing in Assembler or C. Pointers, registers, and other low level machine tools are fast; but, not necessarily safe. Conversion of an AIS Lisp Lambda to Advanced Coding Practice, with registers and pointers, requires frequent use of the disassemble function as well as an understanding of the DRM virtual machine code generated by the compiler. The example below shows a simple Lambda, named foo, using Safe Coding Practice, and its DRM virtual machine code generated by the compiler.

 

   (defun foo() vars:(X) vars:((n 20) y) (setq X (new Vector: Integer:)) (setq X[n] y))

       (disassemble foo) ==>

       0000: (vmpush Cv[__C3553:"Vector"] Cv[__C3554:"Integer"] )

       0003: (vmcall 2 Gv[new] Tv[X] )

       0007: (vmset Tv[n] Tv[y] Tv[X] )

       0011: (vmreturn Tv[X] )

 

The vmpush followed by the vmcall instructions create an IntVector, of zero length, in the X variable. The vmset instruction notices that the IntVector does not have the capacity to store y at location 20 (n); so the vmset instruction extends the size of the IntVector before storing y at location 20 (n).

The example below shows the foo Lambda, converted to Normal Coding Practice, and its DRM virtual machine code generated by the compiler.

 

   (defun foo() vars:(IntVector:X) regs:((n 20) y) (setq X (new Vector: Integer:)) (setq X[n] y))

       (disassemble foo) ==>

       0000: (vmpush Cv[__C3581:"Vector"] Cv[__C3582:"Integer"] )

       0003: (vmcall 2 Gv[new] Tv[X] )

       0007: (vmregObjPointer Tv[X] __Rp )

       0009: (vmregSetXInteger y n __Rp )

       0010: (vmreturn Tv[X] )

 

Following this simple conversion from Safe to Normal Coding Practice, the foo Lambda was executed and crashed the AIS system. The crash occurs because, the vmpush followed by the vmcall instructions create an IntVector, of zero length, in the X variable. The vmregObjPointer instruction loads a pointer register (__Rp) with the address of the data portion of the IntVector X. Since X is of zero length, the pointer (__Rp) is loaded with the zero address. The vmregSetXInteger instruction attempts to store y at location __Rp plus 20 (n). This causes an illegal memory access error in the host computer chip, crashing the AIS system.

The example below shows the foo Lambda, converted to Normal Coding Practice, with the system crash fixed.

 

   (defun foo() vars:(IntVector:X) regs:((n 20) y) (setq X (new Vector: Integer: 21)) (setq X[n] y))

LISP Optimization

AIS LISP Programmers may encounter these issues when trying to lessen execution time and use lesser memory to improve performance and efficiency of LISP Lambdas.

Memory Allocation (Byte Vectors or Strings)

Like C, LISP provides for Static and Dynamic Memory Allocation.

1. Static Memory Allocation provides space for the object in the binary at compile time. An example is a command preallocating a fixed memory size of 1000 bytes to a vector.

(setq testByteVector (new Vector: Byte: 1000))

2. Dynamic Memory Allocation allows blocks of memory of arbitrary size to be requested at run-time. The Heap manager which supports dynamic memory allocation allows for object resizing, garbage collection, and anti-fragmentation algorithms.

A String is an example of an AIS Object Data type that uses dynamic memory allocation. The initial type and number of a sequence of characters in a String is determined by its size or it may be declared initially empty. When a character constant is modified or when a String is expanded or contracted, Analytic Information Server handles the transition dynamically as needed.

The use of register programming to manipulate pointers and store the characters being pointed into a Byte Vector of a fixed size is fast compared to the use of Strings. But this also depends on the nature of the data being processed. If the size of the data to be processed is more or less constant, the Static Memory Allocation approach is better and faster. But if the size of the data greatly varies (e.g. from 10 bytes to 100000 bytes) then Dynamic memory allocation through the use of string is faster and more efficient.

Register Programming

The use of pointers to traverse the contents of a Byte Vector or String is a fast alternative to other time and memory intensive functions such as: copy, append, etc. An example of this is:

(vmregRunInHardware start:) (setq charPointer1 charPointer2) (++ charPointer1) (setq cc charPointer1 [0]) (while (and (> cc 0) (<= cc 32)) (begin (++ start) (setq cc start[0]))) (vmregRunInHardware stop:)

This code traverses through all the characters in a document until it encounters a whitespace(ASCII Number 32). This register code is fast especially since it is enclosed in vmregRunInHardware commands.

Global variables

The presence of global or persistent variables may have a significant impact in a LISP program's performance. For instance, an unwanted persistent variable may cause data to be concatenated to it until the increase of variable size eats up memory and affects performance. This may cause memory overhead to increase per program run.

To show global variables accessed by a specific Lambda, issue the command:

(libraryMgr.showGlobals rootLambda)

where rootLambda is the name of the Lambda in memory.

Unwanted global references should either be deleted or converted from persistent variables to ordinary variables (pvars to vars).

Program Checking

LISP has various commands to check performance and memory status. Also included below are other suggested tips to check causes for longer execution time and more memory utilization.

1. inspect

This command return the statistical and system information concerning the specified Analytic Information Server environment.

2. systemCheck

The systemCheck function performs a system diagnostic check of the current memory manager and displays a system error message if any memory blocks are damaged and cannot be repaired The systemCheck function is useful for determining if there are memory leaks

3.getTickCount

This function is helpful to get timing results of a LISP program. Usually, the timing test code looks like this:

(setq startTime 0) (setq endTime 0) (setq startTime (getTickCount 0)) ... Program execution (setq endTime (getTickCount startTime))

4.sizeof and length

These functions are helpful to do a per variable checking within the Lambda. The length function checks if there are unwanted concatenated data in a variable. The sizeof function determines the object closure size of an object.

5.number of recursion or iterations

It is always helpful to check and see if the number of recursions or iterations is consistent with the program logic.