!
+-*/%
Ab
Ap
As
Ba
Ca
Cl
Co
Cu
De
Di
Do
En
Ex
Fi
Fl
Ga
Gl
In
La
Li
Ma
Mu
Net
New
Nt
Pa
Pr
Ra
Rea
Reg
Sea
Seq
Sl
St
Sy
Ti
Tr
Ut
Wr
newLISP focuses on the core components of Lisp: lists, symbols, and lambda expressions. To these, newLISP adds arrays, implicit indexing on lists and arrays, and dynamic and lexical scoping. Lexical scoping is implemented using separate namespaces called contexts.
The result is an easier-to-learn Lisp that is even smaller than most Scheme implementations, but which still has about 350 built-in functions. Not much over 200k in size on BSD systems, newLISP is built for high portability using only the most common Unix system C-libraries. It loads quickly and has a small memory footprint. newLISP is as fast or faster than other popular scripting languages and uses very few resources.
Both built-in and user-defined functions, along with variables, share the same global symbol tree and are manipulated by the same functions. Lambda expressions and user-defined functions can be handled like any other list expression.
newLISP is dynamically scoped inside lexically separated contexts (namespaces). Contexts in newLISP are used for multiple purposes. They allow (1) partitioning of programs into modules, (2) the definition of Classes in FOOP (Functional Object Oriented Programming), (3) the definition of functions with state and (4) the creation of Hash trees for associative key → value storage.
newLISP's efficient red-black tree implementation can handle millions of symbols in namespaces or hashes without degrading performance.
newLISP allocates and reclaims memory automatically, without using traditional asynchronous garbage collection. All objects — except for contexts, built-in primitives, and symbols — are passed by value and are referenced only once. Upon creation objects are scheduled for delayed deletion and Lisp cells are recycled for newly created objects. This results in predictable processing times without the pauses found in traditional garbage collection. newLISP's unique automatic memory management makes it the fastest interactive Lisp available. More than any other Lisp, it implements the data equals program paradigm and full self reflection.
Many of newLISP's built-in functions are polymorphic and accept a variety of data types and optional parameters. This greatly reduces the number of functions and syntactic forms necessary to learn and implement. High-level functions are available for string and list processing, financial math, statistics, and Artificial Intelligence applications.
newLISP has functions to modify, insert, or delete elements inside complex nested lists or multi-dimensional array structures.
Because strings can contain null characters in newLISP, they can be used to process binary data with most string manipulating functions.
newLISP can also be extended with a shared library interface to import functions that access data in foreign binary data structures. The distribution contains modules for importing popular C-library APIs.
newLISP's HTTP, TCP/IP, and UDP socket interfaces make it easy to write distributed networked applications. Its built-in XML interface, along with its text-processing features — Perl Compatible Regular Expressions (PCRE) and text-parsing functions — make newLISP a useful tool for CGI processing. The source distribution includes examples of HTML forms processing. newLISP can be run a as a CGI capable web server using its built-in http mode option.
newLISP has built-in support for distributed processing on networks and parallel, concurrent processing on the same CPU with one or more processing cores.
The source distribution can be compiled for Linux, macOS/Darwin, BSDs, many other Unix flavors and MS Windows. newLISP can be compiled as a 64-bit LP64 application for full 64-bit memory addressing.
Since version 10.5.7, newLISP also can be compiled to JavaScript and run in a web browser.
newLISP are licensed under version 3 of the GPL (General Public License). The newLISP documentation as well as other documentation packaged with newLISP are licensed under the GNU Free Documentation License.
Since version 10.3.0 newLISP can switch between IPv4 and IPv6 modes during run-time using the new net-ipv function. The -6 commandline option can be used to start newLISP in IPv6 mode. After transition to IPv6 the -6 commandline switch will be changed to -4 for starting up in IPv4 mode.
The old writing parse-date of date-parse is still recognized but deprecated since version 10.3.0. The old writing will be removed in a future version.
Since version 10.4.2 if-not is deprecated and will be removed in a future version.
Since version 10.4.6 newLISP has a built-in function json-parse for translating JSON data into S-expressions. The module file json.lsp is removed from the distribution.
Since version 10.4.8 newLISP has built-in support for unlimited precision integers. This makes the GNU GMP module gmp.lsp obsolete.
The best way to experience Lisp and experiment with it, is using interactive mode in a terminal window or operating system command shell. Since version 10.3, newLISP's read-eval-print-loop (REPL) accepts multi-line statements.
To enter a multi-line statement hit the [enter] key on an empty line after the system prompt. To exit multi-line mode, hit the [enter] key again on an empty line. In the following example computer output is shown in bold letters:
>
(define (foo x y)
(+ x y))
(lambda (x y) (+ x y))
> (foo 3 4)
7
>
Note, that multi-line mode is only possible in an OS command terminal window or command shell.
Interactive Lisp mode can accept operating system shell commands. To hit an OS command enter the '!' character right after the prompt, immediately followed by the shell command:
> !ls *.html CodePatterns.html MemoryManagement.html newLISPdoc.html ExpressionEvaluation.html manual_frame.html newlisp_index.html License.html newLISP-10.3-Release.html newlisp_manual.html >
In the example a ls shell command is entered to show HTML files in the current directory. On MS Windows a dir command could be used in the same fashion.
The mode can also be used to call an editor or any other program:
> !vi foo.lsp
The Vi editor will open to edit the program "foo.lsp". After leaving the editor the program could be run using a load statement:
> (load "foo.lsp")
The program foo.lsp is now run.
When using a Unix terminal or command shell, tab-expansion for built-in newLISP functions can be used:
> (pri print println primitive? > (pri
After entering the characters (pri hit the [tab] key once to show all the built-in functions starting with the same characters. When hitting [tab] twice before a function name has started, all built-in function names will be displayed.
On most Unix, parenthesis matching can be enabled on the commandline by including the following line in the file .inputrc in the home directory:
set blink-matching-paren on
Not all systems have a version of libreadline advanced enough for this to work.
When starting newLISP from the command-line several switches and options and source files can be specified. Executing:
newlisp -h
in a command shell will produce the following summary of options and switches:
-h this help (no init.lsp) -n no init.lsp (must be first) -x <source> <target> link (no init.lsp) -v version -s <stacksize> -m <max-mem-MB> cell memory -e <quoted lisp expression> -l <path-file> log connections -L <path-file> log all -w <working dir> -c no prompts, HTTP -C force prompts -t <usec-server-timeout> -p <port-no> -d <port-no> demon mode -http only -http-safe safe mode -6 IPv6 mode
Before or after the command-line switches, files to load and execute can be specified. If a newLISP executable program is followed by parameters, the program must finish with and (exit) statement, else newLISP will take command-line parameters as additional newLISP scripts to be loaded and executed.
On Linux and other Unix systems, a newlisp man page can be found:
man newlisp
This will display a man page in the Linux/Unix shell.
newLISP will load and execute files specified on the command-line. Files are specified with either their pathname or a file:// URL on the local file system or with a http:// URL on remote file systems running an HTTP server. That HTTP server can be newLISP running in HTTP server mode.
newlisp aprog.lsp bprog.lsp prog.lsp newlisp http://newlisp.org/example.lsp newlisp file:///usr/home/newlisp/demo.lsp
This option suppresses loading of any present initialization file init.lsp or .init.lsp. In order to work, this must be the first option specified:
newlisp -n
More about initialization files.
newlisp -s 4000 newlisp -s 100000 aprog bprog newlisp -s 6000 myprog newlisp -s 6000 http://asite.com/example.lsp
The above examples show starting newLISP with different stack sizes using the -s option, as well as loading one or more newLISP source files and loading files specified by an URL. When no stack size is specified, the stack defaults to 2048. Per stack position about 80 bytes of memory are preallocated.
newlisp -m 128
This example limits newLISP cell memory to 128 megabytes. In 32-bit newLISP, each Lisp cell consumes 16 bytes, so the argument 128 would represent a maximum of 8,388,608 newLISP cells. This information is returned by sys-info as the list's second element. Although Lisp cell memory is not the only memory consumed by newLISP, it is a good estimate of overall dynamic memory usage.
Small pieces of newLISP code can be executed directly from the command-line:
newlisp -e "(+ 3 4)" → 7 ; On MS Windows and Unix newlisp -e '(append "abc" "def")' → "abcdef" ; On Unix
The expression enclosed in quotation marks is evaluated, and the result is printed to standard out (STDOUT). In most Unix system shells, single quotes can also be used as command string delimiters. Note that there is a space between -e and the quoted command string.
In any mode, newLISP can write a log when started with the -l or -L option. Depending on the mode newLISP is running, different output is written to the log file. Both options always must specify the path of a log-file. The path may be a relative path and can be either attached or detached to the -l or -L option. If the file does not exist, it is created when the first logging output is written.
newlisp -l./logfile.txt -c newlisp -L /usr/home/www/log.txt -http -w /usr/home/www/htpdocs
The following table shows the items logged in different situations:
| logging mode | command-line and net-eval with -c | HTTP server with -http |
|---|---|---|
| newlisp -l | log only input and network connections | log only network connections |
| newlisp -L | log also newLISP output (w/o prompts) | log also HTTP requests |
All logging output is written to the file specified after the -l or -L option.
The -w option specifies the initial working directory for newLISP after startup:
newlisp -w /usr/home/newlisp
All file requests without a directory path will now be directed to the path specified with the -w option.
The command-line prompt and initial copyright banner can be suppressed:
newlisp -c
Listen and connection messages are suppressed if logging is not enabled. The -c option is useful when controlling newLISP from other programs; it is mandatory when setting it up as a net-eval server.
The -c option also enables newLISP server nodes to answer HTTP GET, PUT, POST and DELETE requests, as well as perform CGI processing. Using the -c option, together with the -w and -d options, newLISP can serve as a standalone httpd webserver:
newlisp -c -d 8080 -w /usr/home/www
When running newLISP as a inetd or xinetd enabled server on Unix machines, use:
newlisp -c -w /usr/home/www
In -c mode, newLISP processes command-line requests as well as HTTP and net-eval requests. Running newLISP in this mode is only recommended on a machine behind a firewall. This mode should not be run on machines open and accessible through the Internet. To suppress the processing of net-eval and command-line–like requests, use the safer -http option.
A capital C forces prompts when running newLISP in pipe I/O mode inside the Emacs editor:
newlisp -C
To suppress console output from return values from evaluations, use silent.
newlisp some.lsp -p 9090
This example shows how newLISP can listen for commands on a TCP/IP socket connection. In this case, standard I/O is redirected to the port specified with the -p option. some.lsp is an optional file loaded during startup, before listening for a connection begins.
The -p option is mainly used to control newLISP from another application, such as a newLISP GUI front-end or a program written in another language. As soon as the controlling client closes the connection, newLISP will exit.
A telnet application can be used to test running newLISP as a server. First enter:
newlisp -p 4711 &
The & indicates to a Unix shell to run the process in the background. On Windows, start the server process without the & in the foreground and open a second command window for the telnet application. Now connect with a telnet:
telnet localhost 4711
If connected, the newLISP sign-on banner and prompt appear. Instead of 4711, any other port number could be used.
When the client application closes the connection, newLISP will exit, too.
When the connection to the client is closed in -p mode, newLISP exits. To avoid this, use the -d option instead of the -p option:
newlisp -d 4711 &
This works like the -p option, but newLISP does not exit after a connection closes. Instead, it stays in memory, listening for a new connection and preserving its state. An exit issued from a client application closes the network connection, and the newLISP daemon remains resident, waiting for a new connection. Any port number could be used in place of 4711.
After each transaction, when a connection closes, newLISP will go through a reset process, reinitialize stack and signals and go to the MAIN context. Only the contents of program and variable symbols will be preserved when running a stateful server.
When running in -p or -d mode, the opening and closing tags [cmd] and [/cmd] must be used to enclose multiline statements. They must each appear on separate lines. This makes it possible to transfer larger portions of code from controlling applications.
The following variant of the -d mode is frequently used in a distributed computing environment, together with net-eval on the client side:
newlisp -c -d 4711 &
The -c spec suppresses prompts, making this mode suitable for receiving requests from the net-eval function.
newLISP server nodes running will also answer HTTP GET, PUT and DELETE requests. This can be used to retrieve and store files with get-url, put-url, delete-url, read-file, write-file and append-file, or to load and save programs using load and save from and to remote server nodes. See the chapters for the -c and -http options for more details.
newLISP can be limited to HTTP processing using the -http option. With this mode, a secure httpd web server daemon can be configured:
newlisp -http -d 8080 -w /usr/home/www
When running newLISP as an inetd or xinetd-enabled server on Unix machines, use:
newlisp -http -w /usr/home/www
To further enhance security and HTTP processing, load a program during startup when using this mode:
newlisp httpd-conf.lsp -http -w /usr/home/www
The file httpd-conf.lsp contains a command-event function configuring a user-defined function to analyze, filter and translate requests. See the reference for this function for a working example.
In the HTTP modes enabled by either -c or -http, the following file types are recognized, and a correctly formatted Content-Type: header is sent back:
| file extension | media type |
|---|---|
| .avi | video/x-msvideo |
| .css | text/css |
| .gif | image/gif |
| .htm | text/htm |
| .html | text/html |
| .jpg | image/jpg |
| .js | application/javascript |
| .mov | video/quicktime |
| .mp3 | audio/mpeg |
| .mpg | video/mpeg |
| application/pdf | |
| .png | image/png |
| .wav | audio/x-wav |
| .zip | application/zip |
| any other | text/plain |
To serve CGI, HTTP server mode needs a /tmp directory on Unix-like platforms or a C:\tmp directory on MS Windows. newLISP can process GET, PUT, POST and DELETE requests and create custom response headers. CGI files must have the extension .cgi and have executable permission on Unix. More information about CGI processing for newLISP server modes can be found in the document Code Patterns in newLISP.
In both server modes -c and -http the environment variables DOCUMENT_ROOT, HTTP_HOST, REMOTE_ADDR, REQUEST_METHOD, REQUEST_URI, SERVER_SOFTWARE and QUERY_STRING are set. The variables CONTENT_TYPE, CONTENT_LENGTH, HTTP_HOST, HTTP_USER_AGENT and HTTP_COOKIE are also set, if present in the HTTP header sent by the client. Environment variables can be read using the env function.
Instead of a port, a local domain Unix socket path can be specified in the -d or -p server modes.
newlisp -c -d /tmp/mysocket &
Test the server using another newLISP process:
newlisp -e '(net-eval "/tmp/mysocket" 0 "(symbols)")'
A list of all built-in symbols will be printed to the terminal
This mode will work together with local domain socket modes of net-connect, net-listen, and net-eval. Local domain sockets opened with net-connect and net-listen can be served using net-accept, net-receive, and net-send. Local domain socket connections can be monitored using net-peek and net-select.
Local domain socket connections are much faster than normal TCP/IP network connections and preferred for communications between processes on the same local file system in distributed applications. This mode is not available on MS Windows.
Specifies a connection timeout when running in -p or -d demon mode. A newLISP Server will disconnect when no further input is read after accepting a client connection. The timeout is specified in micro seconds:
newlisp -c -t 3000000 -d 4711 &
The example specifies a timeout of three seconds.
The inetd server running on virtually all Linux/Unix OSes can function as a proxy for newLISP. The server accepts TCP/IP or UDP connections and passes on requests via standard I/O to newLISP. inetd starts a newLISP process for each client connection. When a client disconnects, the connection is closed and the newLISP process exits.
inetd and newLISP together can handle multiple connections efficiently because of newLISP's small memory footprint, fast executable, and short program load times. When working with net-eval, this mode is preferred for efficiently handling multiple requests in a distributed computing environment.
Two files must be configured: services and inetd.conf. Both are ASCII-editable and can usually be found at /etc/services and /etc/inetd.conf.
Put one of the following lines into inetd.conf:
net-eval stream tcp nowait root /usr/local/bin/newlisp -c # as an alternative, a program can also be preloaded net-eval stream tcp nowait root /usr/local/bin/newlisp -c myprog.lsp
Instead of root, another user and optional group can be specified. For details, see the Unix man page for inetd.
The following line is put into the services file:
net-eval 4711/tcp # newLISP net-eval requests
On macOS and some Unix systems, xinetd can be used instead of inetd. Save the following to a file named net-eval in the /etc/xinetd.d/ directory:
service net-eval
{
socket_type = stream
wait = no
user = root
server = /usr/local/bin/newlisp
port = 4711
server_args = -c
only_from = localhost
}
For security reasons, root should be changed to a different user and file permissions of the www document directory adjusted accordingly. The only_from spec can be left out to permit remote access.
See the man pages for xinetd and xinetd.conf for other configuration options.
After configuring the daemon, inetd or xinetd must be restarted to allow the new or changed configuration files to be read:
kill -HUP <pid>
Replace <pid> with the process ID of the running xinetd process.
A number or network protocol other than 4711 or TCP can be specified.
newLISP handles everything as if the input were being entered on a newLISP command-line without a prompt. To test the inetd setup, the telnet program can be used:
telnet localhost 4711
newLISP expressions can now be entered, and inetd will automatically handle the startup and communications of a newLISP process. Multiline expressions can be entered by bracketing them with [cmd] and [/cmd] tags, each on separate lines.
newLISP server nodes answer HTTP GET and PUT requests. This can be used to retrieve and store files with get-url, put-url, read-file, write-file and append-file, or to load and save programs using load and save from and to remote server nodes.
Source code and the newLISP executable can be linked together to build a self-contained application by using the -x command line flag.
;; uppercase.lsp - Link example (println (upper-case (main-args 1))) (exit)
The program uppercase.lsp takes the first word on the command-line and converts it to uppercase.
To build this program as a self-contained executable, follow these steps:
# on OSX, Linux and other UNIX newlisp -x uppercase.lsp uppercase chmod 755 uppercase # give executable permission # on Windows the target needs .exe extension newlisp -x uppercase.lsp uppercase.exe
newLISP will find a newLISP executable in the execution path of the environment and link a copy of the source code.
uppercase "convert me to uppercase"
On Linux and other UNIX, if the current directory is not in the executable path:
./uppercase "convert me to uppercase"
The console should print:
CONVERT ME TO UPPERCASE
Note that neither one of the initialization files init.lsp nor .init.lsp is loaded during startup of linked programs.
During startup, newLISP sets the environment variable NEWLISPDIR, if it is not set already. On Linux, BSDs, macOS and other Unixes the variable is set to /usr/local/share/newlisp. On MS Windows the variable is set to %PROGRAMFILES%/newlisp. On most MS Windows systems %PROGRAMFILES% evaluates to the C:\Program Files (x86)\ directory.
The environment variable NEWLISPDIR is useful when loading files installed with newLISP:
(load (append (env "NEWLISPDIR") "/modules/mysql.lsp"))
A predefined function module can be used to shorten the second statement loading from the modules/ directory:
(module "mysql.lsp")
Before loading any files specified on the command-line, and before the banner and prompt are shown. newLISP tries to load a file .init.lsp from the home directory of the user starting newLISP. On macOS, Linux and other Unix the home directory is found in the HOME environment variable. On MS Windows the directory name is contained in the USERPROFILE or DOCUMENT_ROOT environment variable.
If a .init.lsp cannot be found in the home directory newLISP tries to load the file init.lsp from the directory found in the environment variable NEWLISPDIR.
When newLISP is run as a shared library, an initialization file is looked for in the environment variable NEWLISPLIB_INIT. The full path-name of the initialization file must be specified. If NEWLISPLIB_INIT is not defined, no initialization file will be loaded by the library module.
Although newLISP does not require init.lsp to run, it is convenient for defining functions and system-wide variables.
Note that neither one of the initialization files init.lsp nor .init.lsp is loaded during startup of linked programs or when one of the options -n, -h, -x is specified.
The directory /usr/local/share/newlisp/modules contains modules with useful functions POP3 mail, etc. The directory /usr/local/share/doc/newlisp/ contains documentation in HTML format.
On MS Windows systems, all files are installed in the default directory %PROGRAMFILES%\newlisp. PROGRAMFILES is a MS Windows environment variable that resolves to C:\Program files\newlisp\ in English language installations. The subdirectory %PROGRAMFILES%\newlisp\modules contains modules for interfacing to external libraries and sample programs.
Many shared libraries on Unix and MS Windows systems can be used to extend newLISP's functionality. Examples are libraries for writing graphical user interfaces, libraries for encryption or decryption and libraries for accessing databases.
The function import is used to import functions from external libraries. The function callback is used to register callback functions in external libraries. Other functions like pack, unpack, get-char, get-string, get-int and get-long exist to facilitate formatting input and output to and from imported library functions. The fucntion cpymem allows direct memory-to-memory copy specifying addresses.
Most of the functions used when writing APIs for share libraries can cause newLISP to segfault when not used correctly. The reference documentation marks these functions with a ⚠ character linking to this chapter.
See also the chapter 23. Extending newLISP in the Code Patterns in newLISP document.
newLISP can be compiled as a shared C library. On Linux, BSDs and other Unix flavors the library is called newlisp.so. On Windows it is called newlisp.dll and newlisp.dylib on macOS. A newLISP shared library is used like any other shared library. A newLISP shared library is only required for importing newLISP functionality into other programming languages.
The main function to import is newlispEvalStr. Like eval-string, this function takes a string containing a newLISP expression and stores the result in a string address. The result can be retrieved using get-string. The returned string is formatted like output from a command-line session. It contains terminating line-feed characters, but not the prompt string.
When calling newlispEvalStr, output normally directed to the console (e.g. return values or print statements) is returned in the form of an integer string pointer. The output can be accessed by passing this pointer to the get-string function. To silence the output from return values, use the silent function.
To enable stdio on the console, import the function newlispLibConsole and call it with a parameter of 1 for enabling I/O on the console with stdin and stdout.
Since v.10.3.3 callbacks can also be registered using newlispCallback. For more information read the chapter 24. newLISP compiled as a shared library in the Code Patterns in newLISP document.
Since version 10.5.7, newLISP can be compiled to JavaScript using the Emscripten toolset. The library can be used to run newLISP clientr-side in a web browser, just like JavaScript or HTML. An HTML page can host both, newLISP code and JavaScript code together. Both languages can call each other. For more information see the newlisp-js-x.x.x.zip distribution package which contains the library newlisp-js-lib.js, documentaion and example applications. A small newLISP development environment hosted in a browser can also be accessed here: newlisp-js The application contains links to another example application, documentation and a download link for the whole package.
newLISP compiled as a JavaScript library adds new functions linked from API for newLISP in a web browser.
The following is a short introduction to newLISP statement evaluation and the role of integer and floating point arithmetic in newLISP.
Top-level expressions are evaluated when using the load function or when entering expressions in console mode on the command-line.
Multiline expressions can be entered by entering an empty line first. Once in multiline mode, another empty line returns from entry mode and evaluates the statement(s) entered (ouput in boldface):
>
(define (foo x y)
(+ x y))
(lambda (x y) (+ x y))
> (foo 3 4)
7
> _
Entering multiline mode by hitting the enter key on an empty line suppresses the prompt. Entering another empty line will leave the multiline mode and evaluate expressions.
As an alternativo to entering empty lines, the [cmd] and [/cmd] tags are used, each entered on separate lines. This mode is used by some interactive IDEs controlling newLISP and internally by the net-eval function.
newLISP functions and operators accept integer and floating point numbers, converting them into the needed format. For example, a bit-manipulating operator converts a floating point number into an integer by omitting the fractional part. In the same fashion, a trigonometric function will internally convert an integer into a floating point number before performing its calculation.
The symbol operators (+ - * / % $ ~ | ^ << >>) return values of type integer. Functions and operators named with a word instead of a symbol (e.g., add rather than +) return floating point numbers. Integer operators truncate floating point numbers to integers, discarding the fractional parts.
newLISP has two types of basic arithmetic operators: integer (+ - * /) and floating point (add sub mul div). The arithmetic functions convert their arguments into types compatible with the function's own type: integer function arguments into integers, floating point function arguments into floating points. To make newLISP behave more like other scripting languages, the integer operators +, -, *, and / can be redefined to perform the floating point operators add, sub, mul, and div:
(constant '+ add) (constant '- sub) (constant '* mul) (constant '/ div) ;; or all 4 operators at once (constant '+ add '- sub '* mul '/ div)
Now the common arithmetic operators +, -, *, and / accept both integer and floating point numbers and return floating point results.
Care must be taken when importing from libraries that use functions expecting integers. After redefining +, -, *, and /, a double floating point number may be unintentionally passed to an imported function instead of an integer. In this case, floating point numbers can be converted into integers by using the function int. Likewise, integers can be transformed into floating point numbers using the float function:
(import "mylib.dll" "foo") ; importing int foo(int x) from C (foo (int x)) ; passed argument as integer (import "mylib.dll" "bar") ; importing C int bar(double y) (bar (float y)) ; force double float
Some of the modules shipping with newLISP are written assuming the default implementations of +, -, *, and /. This gives imported library functions maximum speed when performing address calculations.
The newLISP preference is to leave +, -, *, and / defined as integer operators and use add, sub, mul, and div when explicitly required. Since version 8.9.7, integer operations in newLISP are 64 bit operations, whereas 64 bit double floating point numbers offer only 52 bits of resolution in the integer part of the number.
The following operators, functions and predicates work on big integers:
| function | description |
|---|---|
| + - * / ++ -- % | arithmetic operators |
| < > = <= >= != | logical operators |
| abs | returns the absolute value of a number |
| gcd | calculates the greatest common divisor of a group of integers |
| even? | checks the parity of an integer number |
| odd? | checks the parity of an integer number |
| number? | checks if an expression is a float or an integer |
| zero? | checks if an expression is 0 or 0.0 |
If the first argument in any of these operators and functions is a big integer, the calculation performed will be in big integer mode. In the Function Reference section of this manual these are marked with a bigint suffix.
Literal integer values greater than 9223372036854775807 or smaller than -9223372036854775808, or integers with an appended letter L, will be converted and processed in big integer mode. The function bigint can be used to convert from integer, float or string format to big integer. The predicate bigint? checks for big integer type.
; first argument triggers big integer mode because it's big enough (+ 123456789012345678901234567890 12345) → 123456789012345678901234580235L ; first small literal put in big integer format by ; appending L to guarantee big integer mode (+ 12345L 123456789012345678901234567890) → 123456789012345678901234580235L (setq x 1234567890123456789012345) (* x x) → 1524157875323883675049533479957338669120562399025L ; conversion from bigint to float introduces rounding errors (bigint (float (* x x))) → 1524157875323883725344000000000000000000000000000L ; sequence itself does not take big integers, before using ; apply, the sequence is converted with bigint (apply * (map bigint (sequence 1 100))) ; calculate 100! → 93326215443944152681699238856266700490715968264381 62146859296389521759999322991560894146397615651828 62536979208272237582511852109168640000000000000000 00000000L ; only the first operand needs to be bigint for apply ; to work. The following gives the same result (apply * (cons 1L (sequence 2 100))) ; length on big integers returns the number of decimal digits (length (apply * (map bigint (sequence 1 100)))) → 158 ; decimal digits ; all fibonacci numbers up to 200, only the first number ; needs to be formatted as big integer, the rest follows ; automatically - when executed from the command line in ; a 120 char wide terminal, this shows a beautiful pattern (let (x 1L) (series x (fn (y) (+ x (swap y x))) 200))
When doing mixed integer / big integer arithmetic, the first argument should be a big integer to avoid erratic behaviour.
; because the first argument is 64-bit, no big integer arithmetic ; will be done, although the second argument is big integer (+ 123 12345L) → 12468 ; the second argument is recognized as a big integer ; and overflows the capacity of a 64-bit integer (+ 123 123453456735645634565463563546) → ERR: number overflows in function + ; now the first argument converts to big integer and the ; whole expression evaluates in big integer mode (+ 123L 123453456735645634565463563546) → 123453456735645634565463563669L
Under most circumstances mixing float, integers and big integers is transparent. Functions automatically do conversions when needed on the second argument. The overflow behavior when using normal integers and floats only, has not changed from newLISP versions previous to 10.5.0.
Evaluate expressions by entering and editing them on the command-line. More complicated programs can be entered using editors like Emacs and VI, which have modes to show matching parentheses while typing. Load a saved file back into a console session by using the load function.
A line comment begins with a ; (semicolon) or a # (number sign) and extends to the end of the line. newLISP ignores this line during evaluation. The # is useful when using newLISP as a scripting language in Linux/Unix environments, where the # is commonly used as a line comment in scripts and shells.
When evaluation occurs from the command-line, the result is printed to the console window.
The following examples can be entered on the command-line by typing the code to the left of the → symbol. The result that appears on the next line should match the code to the right of the → symbol.
nil and true are Boolean data types that evaluate to themselves:
nil → nil true → true
Integers, big integers and floating point numbers evaluate to themselves:
123 → 123 ; decimal integer 0xE8 → 232 ; hexadecimal prefixed by 0x 055 → 45 ; octal prefixed by 0 (zero) 0b101010 → 42 ; binary prefixed by 0b 1.23 → 1.23 ; float 123e-3 → 0.123 ; float in scientific notation 123456789012345678901234567890 → 123456789012345678901234567890L ; parses to big integer
Integers are 64-bit including the sign bit. Valid integers are numbers between -9,223,372,036,854,775,808 and +9,223,372,036,854,775,807. Larger numbers converted from floating point numbers are truncated to one of the two limits. Integers internal to newLISP, which are limited to 32-bit numbers, overflow to either +2,147,483,647 or -2,147,483,648.
Floating point numbers are IEEE 754 64-bit doubles. Unsigned numbers up to 18,446,744,073,709,551,615 can be displayed using special formatting characters for format.
Big integers are of unlimited precision and only limited in size by memory. The memory requirement of a big integer is:
bytes = 4 * ceil(digits / 9) + 4.
Where digits are decimal digits, bytes are 8 bits and ceil is the ceiling function rounding up to the next integer.
Strings may contain null characters and can have different delimiters. They evaluate to themselves.
"hello" →"hello" "\032\032\065\032" →" A " "\x20\x20\x41\x20" →" A " "\t\r\n" →"\t\r\n" "\x09\x0d\x0a" →"\t\r\n" ;; null characters are legal in strings: "\000\001\002" → "\000\001\002" {this "is" a string} → "this \"is\" a string" ;; use [text] tags for text longer than 2047 bytes: [text]this is a string, too[/text] → "this is a string, too"
Strings delimited by " (double quotes) will also process the following characters escaped with a \ (backslash):
| character | description |
|---|---|
| \" | for a double quote inside a quoted string |
| \n | for a line-feed character (ASCII 10) |
| \r | for a return character (ASCII 13) |
| \b | for a backspace BS character (ASCII 8) |
| \t | for a TAB character (ASCII 9) |
| \f | for a formfeed FF character (ASCII 12) |
| \nnn | for a three-digit ASCII number (nnn format between 000 and 255) |
| \xnn | for a two-digit-hex ASCII number (xnn format between x00 and xff) |
| \unnnn | for a unicode character encoded in the four nnnn hexadecimal digits. newLISP will translate this to a UTF8 character in the UTF8 enabled versions of newLISP. |
| \\ | for the backslash character (ASCII 92) itself |
Quoted strings cannot exceed 2,047 characters. Longer strings should use the [text] and [/text] tag delimiters. newLISP automatically uses these tags for string output longer than 2,047 characters.
The { (left curly bracket), } (right curly bracket), and [text], [/text] delimiters do not perform escape character processing.
Lambda and lambda-macro expressions evaluate to themselves:
(lambda (x) (* x x)) → (lambda (x) (* x x)) (lambda-macro (a b) (set (eval a) b)) → (lambda-macro (a b) (set (eval a) b)) (fn (x) (* x x)) → (lambda (x) (* x x)) ; an alternative syntax
Symbols evaluate to their contents:
(set 'something 123) → 123 something → 123
Contexts evaluate to themselves:
(context 'CTX) → CTX CTX → CTX
Built-in functions also evaluate to themselves:
add → add <B845770D> (eval (eval add)) → add <B845770D> (constant '+ add) → add <B845770D> + → add <B845770D>
In the above example, the number between the < > (angle brackets) is the hexadecimal memory address (machine-dependent) of the add function. It is displayed when printing a built-in primitive.
Quoted expressions lose one ' (single quote) when evaluated:
'something → something ''''any → '''any '(a b c d) → (a b c d)
A single quote is often used to protect an expression from evaluation (e.g., when referring to the symbol itself instead of its contents or to a list representing data instead of a function).
Lists are evaluated by first evaluating the first list element before the rest of the expression (as in Scheme). The result of the evaluation is applied to the remaining elements in the list and must be one of the following: a lambda expression, lambda-macro expression, or primitive (built-in) function.
(+ 1 2 3 4) → 10 (define (double x) (+ x x)) → (lambda (x) (+ x x))
or
(set 'double (lambda (x) (+ x x))) (double 20) → 40 ((lambda (x) (* x x)) 5) → 25
For a user-defined lambda expression, newLISP evaluates the arguments from left to right and binds the results to the parameters (also from left to right), before using the results in the body of the expression.
Like Scheme, newLISP evaluates the functor (function object) part of an expression before applying the result to its arguments. For example:
((if (> X 10) * +) X Y)
Depending on the value of X, this expression applies the * (product) or + (sum) function to X and Y.
Because their arguments are not evaluated, lambda-macro expressions are useful for extending the syntax of the language. Most built-in functions evaluate their arguments from left to right (as needed) when executed. Some exceptions to this rule are indicated in the reference section of this manual. Lisp functions that do not evaluate all or some of their arguments are called special forms.
Arrays evaluate to themselves:
(set 'A (array 2 2 '(1 2 3 4))) → ((1 2) (3 4)) (eval A) → ((1 2) (3 4))
Shell commands: If an ! (exclamation mark) is entered as the first character on the command-line followed by a shell command, the command will be executed. For example, !ls on Unix or !dir on MS Windows will display a listing of the present working directory. No spaces are permitted between the ! and the shell command. Symbols beginning with an ! are still allowed inside expressions or on the command-line when preceded by a space. Note: This mode only works when running in the shell and does not work when controlling newLISP from another application.
To exit the newLISP shell on Linux/Unix, press Ctrl-D; on MS Windows, type (exit) or Ctrl-C, then the x key.
Use the exec function to access shell commands from other applications or to pass results back to newLISP.
Lambda expressions in newLISP evaluate to themselves and can be treated just like regular lists:
(set 'double (lambda (x) (+ x x)))
(set 'double (fn (x) (+ x x))) ; alternative syntax
(last double) → (+ x x) ; treat lambda as a list
Note: No ' is necessary before the lambda expression because lambda expressions evaluate to themselves in newLISP.
The second line uses the keyword fn, an alternative syntax first suggested by Paul Graham for his Arc language project.
A lambda expression is a lambda list, a subtype of list, and its arguments can associate from left to right or right to left. When using append, for example, the arguments associate from left to right:
(append (lambda (x)) '((+ x x))) → (lambda (x) (+ x x))
cons, on the other hand, associates the arguments from right to left:
(cons '(x) (lambda (+ x x))) → (lambda (x) (+ x x))
Note that the lambda keyword is not a symbol in a list, but a designator of a special type of list: the lambda list.
(length (lambda (x) (+ x x))) → 2 (first (lambda (x) (+ x x))) → (x)
Lambda expressions can be mapped or applied onto arguments to work as user-defined, anonymous functions:
((lambda (x) (+ x x)) 123) → 246 (apply (lambda (x) (+ x x)) '(123)) → 246 (map (lambda (x) (+ x x)) '(1 2 3)) → (2 4 6)
A lambda expression can be assigned to a symbol, which in turn can be used as a function:
(set 'double (lambda (x) (+ x x))) → (lambda (x) (+ x x)) (double 123) → 246
The define function is just a shorter way of assigning a lambda expression to a symbol:
(define (double x) (+ x x))) → (lambda (x) (+ x x)) (double 123) → 246
In the above example, the expressions inside the lambda list are still accessible within double:
(set 'double (lambda (x) (+ x x))) → (lambda (x) (+ x x)) (last double) → (+ x x)
A lambda list can be manipulated as a first-class object using any function that operates on lists:
(setf (nth 1 double) '(mul 2 x)) → (lambda (x) (mul 2 x)) double → (lambda (x) (mul 2 x)) (double 123) → 246
All arguments are optional when applying lambda expressions and default to nil when not supplied by the user. This makes it possible to write functions with multiple parameter signatures.
In newLISP, nil and true represent both the symbols and the Boolean values false and true. Depending on their context, nil and true are treated differently. The following examples use nil, but they can be applied to true by simply reversing the logic.
Evaluation of nil yields a Boolean false and is treated as such inside flow control expressions such as if, unless, while, until, and not. Likewise, evaluating true yields true.
(set 'lst '(nil nil nil)) → (nil nil nil) (map symbol? lst) → (true true true)
In the above example, nil represents a symbol. In the following example, nil and true are evaluated and represent Boolean values:
(if nil "no" "yes") → "yes" (if true "yes" "no") → "yes" (map not lst) → (true true true)
In newLISP, nil and the empty list () are not the same as in some other Lisps. Only in conditional expressions are they treated as a Boolean false, as in and, or, if, while, unless, until, and cond.
Evaluation of (cons 'x '()) yields (x), but (cons 'x nil) yields (x nil) because nil is treated as a Boolean value when evaluated, not as an empty list. The cons of two atoms in newLISP does not yield a dotted pair, but rather a two-element list. The predicate atom? is true for nil, but false for the empty list. The empty list in newLISP is only an empty list and not equal to nil.
A list in newLISP is a newLISP cell of type list. It acts like a container for the linked list of elements making up the list cell's contents. There is no dotted pair in newLISP because the cdr (tail) part of a Lisp cell always points to another Lisp cell and never to a basic data type, such as a number or a symbol. Only the car (head) part may contain a basic data type. Early Lisp implementations used car and cdr for the names head and tail.
newLISP's arrays enable fast element access within large lists. New arrays can be constructed and initialized with the contents of an existing list using the function array. Lists can be converted into arrays, and vice versa. Most of the same functions used for modifying and accessing lists can be applied to arrays, as well. Arrays can hold any type of data or combination thereof.
In particular, the following functions can be used for creating, accessing, and modifying arrays:
| function | description |
|---|---|
| append | appends arrays |
| apply | apply a function or operator to a list of arguments. |
| array | creates and initializes an array with up to 16 dimensions |
| array-list | converts an array into a list |
| array? | checks if expression is an array |
| corr | calculates the product-moment correlation coefficient |
| det | returns the determinant of a matrix |
| dolist | evaluates once for each element in an array vector |
| first | returns the first row of an array |
| invert | returns the inversion of a matrix |
| last | returns the last row of an array |
| length | returns the number of rows in an array or elements in a vector |
| map | applies a function to vector(s) of arguments and returns results in a list. |
| mat | perform scalar operations on matrices |
| multiply | multiplies two matrices |
| nth | returns an element of and array |
| rest | returns all but the first row of an array |
| reverse | reverses the elements or rows in an array |
| setf | sets contents of an array reference |
| slice | returns a slice of an array |
| sort | sort the elements in an array |
| stats | calculates some basic statistics for a data vector |
| t-test | compares means of data samples using the Student's t statistic |
| transpose | transposes a matrix |
newLISP represents multidimensional arrays with an array of arrays (i.e., the elements of the array are themselves arrays).
When used interactively, newLISP prints and displays arrays as lists, with no way of distinguishing between them.
Use the source or save functions to serialize arrays (or the variables containing them). The array statement is included as part of the definition when serializing arrays.
Like lists, negative indices can be used to enumerate the elements of an array, starting from the last element.
An out-of-bounds index will cause an error message on an array or list.
Arrays can be non-rectangular, but they are made rectangular during serialization when using source or save. The array function always constructs arrays in rectangular form.
The matrix functions det, transpose, multiply, and invert can be used on matrices built with nested lists or arrays built with array.
For more details, see array, array?, and array-list in the reference section of this manual.
Some functions take array, list, or string elements (characters) specified by one or more int-index (integer index). The positive indices run 0, 1, …, N-2, N-1, where N is the number of elements in the list. If int-index is negative, the sequence is -N, -N+1, …, -2, -1. Adding N to the negative index of an element yields the positive index. Unless a function does otherwise, an index greater than N-1 or less then -N causes an out-of-bounds error in lists and arrays.
Implicit indexing can be used instead of nth to retrieve the elements of a list or array or the characters of a string:
(set 'lst '(a b c (d e) (f g))) (lst 0) → a ; same as (nth 0 lst) (lst 3) → (d e) (lst 3 1) → e ; same as (nth '(3 1) lst) (lst -1) → (f g) (set 'myarray (array 3 2 (sequence 1 6))) (myarray 1) → (3 4) (myarray 1 0) → 3 (myarray 0 -1) → 2 ; indexing ASCII strings ("newLISP" 3) → "L" ; indexing strings in UTF8 enabled versions ("我能吞下玻璃而不伤身体。" 3) → "下"
Indices may also be supplied from a list. In this way, implicit indexing works together with functions that take or produce index vectors, such as push, pop, ref and ref-all.
(lst '(3 1)) → e (set 'vec (ref 'e lst)) → (3 1) (lst vec) → e ; an empty index vector yields the original list or array (lst '()) → (set 'lst '(a b c (d e) (f g)))
Note that implicit indexing is not breaking newLISP syntax rules but is merely an expansion of existing rules to other data types in the functor position of an s-expression. In original Lisp, the first element in an s-expression list is applied as a function to the rest elements as arguments. In newLISP, a list in the functor position of an s-expression assumes self-indexing functionality using the index arguments following it.
Implicit indexing is faster than the explicit forms, but the explicit forms may be more readable depending on context.
Note that in the UTF-8–enabled version of newLISP, implicit indexing of strings or using the nth function work on character rather than single-byte boundaries.
The default functor is a functor inside a context with the same name as the context itself. See The context default function chapter. A default functor can be used together with implicit indexing to serve as a mechanism for referencing lists:
(set 'MyList:MyList '(a b c d e f g)) (MyList 0) → a (MyList 3) → d (MyList -1) → g (3 2 MyList) → (d e) (-3 MyList) → (e f g) (set 'aList MyList) (aList 3) → d
In this example, aList references MyList:MyList, not a copy of it. For more information about contexts, see Variables holding contexts.
The indexed default functor can also be used with setf as shown in the following example:
(set 'MyList:MyList '(a b c d e f g)) (setf (MyList 3) 999) → 999 (MyList 3) → 999 MyList:MyList → (a b c 999 e f g)
Implicit forms of rest and slice can be created by prepending a list with one or two numbers for offset and length. If the length is negative it counts from the end of the list or string:
(set 'lst '(a b c d e f g)) ; or as array (set 'lst (array 7 '(a b c d e f g))) (1 lst) → (b c d e f g) (2 lst) → (c d e f g) (2 3 lst) → (c d e) (-3 2 lst) → (e f) (2 -2 lst) → (c d e) ; resting and slicing is always on 8-bit char borders ; even on UTF8 enabled versions (set 'str "abcdefg") (1 str) → "bcdefg" (2 str) → "cdefg" (2 3 str) → "cde" (-3 2 str) → "ef" (2 -2 str) → "cde"
The functions rest, first and last work on multi-byte character boundaries in UTF-8 enabled versions of newLISP. But the implicit indexing forms for slicing and resting will always work on single-byte boundaries and can be used for binary content. Offset and length results from the regular expression functions find and regex are also in single-byte counts and can be further processed with slice or it's implicit form.
Parts in lists, arrays and strings referenced by indices can be modified using setf:
; lists (set 'lst '(a b c d (e f g))) (lst 1) → b (setf (lst 1) 'z) → z lst → (a z c d (e f g)) (setf (lst -1) '(E F G)) → (E F G) lst → (a z c d (E F G)) ; arrays (set 'myarray (array 2 3 (sequence 1 6))) → ((1 2 3) (4 5 6)) (setf (myarray 1 2) 66) → 66 myarray → ((1 2 3) (4 5 66)) ; strings (set 's "NewLISP") (setf (s 0) "n") → "n" s → "newLISP"
Note that only full elements or nested lists or arrays can be changed this way. Slices or rest parts of lists or arrays as used in implicit resting or slicing cannot be substituted at once using setf, but would have to be substituted element by element. In strings only one character can be replaced at a time, but that character can be replaced by a multi-character string.
Most of the primitives in newLISP are nondestructive (no side effects) and leave existing objects untouched, although they may create new ones. There are a few destructive functions, however, that do change the contents of a variable, list, array, or string:
| function | description |
|---|---|
| ++ | increments numbers in integer mode |
| -- | decrements numbers in integer mode |
| bind | binds variable associations in a list |
| constant | sets the contents of a variable and protects it |
| extend | extends a list or string |
| dec | decrements a number referenced by a variable, list or array |
| define | sets the contents of a variable |
| define-macro | sets the contents of a variable |
| inc | increments a number referenced by a variable, list or array |
| let | declares and initializes local variables |
| letn | initializes local variables incrementally, like nested lets |
| letex | expands local variables into an expression, then evaluates |
| net-receive | reads into a buffer variable |
| pop | pops an element from a list or string |
| pop-assoc | removes an association from an association list |
| push | pushes a new element onto a list or string |
| read | reads into a buffer variable |
| receive | receives a message from a parent or child process |
| replace | replaces elements in a list or string |
| reverse | reverses a list or string |
| rotate | rotates the elements of a list or characters of a string |
| set | sets the contents of a variable |
| setf setq | sets the contents of a variable, list, array or string |
| set-ref | searches for an element in a nested list and replaces it |
| set-ref-all | searches for an element in a nested list and replaces all instances |
| sort | sorts the elements of a list or array |
| swap | swaps two elements inside a list or string |
| write | write a string to a file or string buffer |
Some destructive functions can be made non-destructive by wrapping the target object into the copy function.
(set 'aList '(a b c d e f)) (replace 'c (copy aList)) → (a b d e f) aList → (a b c d e f)
The list in aList is left unchanged.
What follows are methods of interrupting the control flow inside both loops and the begin expression.
The looping functions dolist and dotimes can take optional conditional expressions to leave the loop early. catch and throw are a more general form to break out of a loop body and are also applicable to other forms or statement blocks.
Because newLISP is a functional language, it uses no break or return statements to exit functions or iterations. Instead, a block or function can be exited at any point using the functions catch and throw:
(define (foo x)
...
(if condition (throw 123))
...
456
)
;; if condition is true
(catch (foo p)) → 123
;; if condition is not true
(catch (foo p)) → 456
Breaking out of loops works in a similar way:
(catch
(dotimes (i N)
(if (= (foo i) 100) (throw i))))
→ value of i when foo(i) equals 100
The example shows how an iteration can be exited before executing N times.
Multiple points of return can be coded using throw:
(catch (begin
(foo1)
(foo2)
(if condition-A (throw 'x))
(foo3)
(if condition-B (throw 'y))
(foo4)
(foo5)))
If condition-A is true, x will be returned from the catch expression; if condition-B is true, the value returned is y. Otherwise, the result from foo5 will be used as the return value.
As an alternative to catch, the error-event function can be used to catch errors caused by faulty code or user-initiated exceptions.
The throw-error function may be used to throw user-defined errors.
Using the logical functions and and or, blocks of statements can be built that are exited depending on the Boolean result of the enclosed functions:
(and
(func-a)
(func-b)
(func-c)
(func-d))
The and expression will return as soon as one of the block's functions returns nil or an () (empty list). If none of the preceding functions causes an exit from the block, the result of the last function is returned.
or can be used in a similar fashion:
(or
(func-a)
(func-b)
(func-c)
(func-d))
The result of the or expression will be the first function that returns a value which is not nil or ().
newLISP uses dynamic scoping inside contexts. A context is a lexically closed namespace. In this way, parts of a newLISP program can live in different namespaces taking advantage of lexical scoping.
When the parameter symbols of a lambda expression are bound to its arguments, the old bindings are pushed onto a stack. newLISP automatically restores the original variable bindings when leaving the lambda function.
The following example illustrates the dynamic scoping mechanism. The text in bold is the output from newLISP:
> (set 'x 1) 1 > (define (f) x) (lambda () x) > (f) 1 > (define (g x) (f)) (lambda (x) (f)) > (g 0) 0 > (f) 1 > _
The variable x is first set to 1. But when (g 0) is called, x is bound to 0 and x is reported by (f) as 0 during execution of (g 0). After execution of (g 0), the call to (f) will report x as 1 again.
This is different from the lexical scoping mechanisms found in languages like C or Java, where the binding of local parameters occurs inside the function only. In lexically scoped languages like C, (f) would always print the global bindings of the symbol x with 1.
Be aware that passing quoted symbols to a user-defined function causes a name clash if the same variable name is used as a function parameter:
(define (inc-symbol x y) (inc (eval x) y)) (set 'y 200) (inc-symbol 'y 123) → 246 y → 200 ; y is still 200
Because the global y shares the same symbol as the function's second parameter, inc-symbol returns 246 (123 + 123), leaving the global y unaffected. Dynamic scoping's variable capture can be a disadvantage when passing symbol references to user-defined functions. newLISP offers several methods to avoid variable capture.
Contexts should be used to group related functions when creating interfaces or function libraries. This surrounds the functions with a lexical "fence", thus avoiding variable name clashes with the calling functions.
newLISP uses contexts for different forms of lexical scoping. See the chapters Contexts and default functors for more information.
In newLISP, symbols can be separated into namespaces called contexts. Each context has a private symbol table separate from all other contexts. Symbols known in one context are unknown in others, so the same name may be used in different contexts without conflict.
Contexts are used to build modules of isolated variable and function definitions. They also can be used to build dictionaries fo key values pairs. Contexts can be copied and dynamically assigned to variables or passed as arguments by reference. Because contexts in newLISP have lexically separated namespaces, they allow programming with lexical scoping and software object styles of programming.
Contexts are identified by symbols that are part of the root or MAIN context. Although context symbols are uppercased in this chapter, lowercase symbols may also be used.
In addition to context names, MAIN contains the symbols for built-in functions and special symbols such as true and nil. The MAIN context is created automatically each time newLISP is run. To see all the symbols in MAIN, enter the following expression after starting newLISP:
(symbols)
To see all symbols in MAIN pointing to contexts:
(filter context? (map eval (symbols)))
To seel all context symbols in MAIN when MAIN is not the current context:
(filter context? (map eval (symbols MAIN)))
The following rules should simplify the process of understanding contexts by identifying to which context the created symbols are being assigned.
newLISP first parses and translates each expression starting at the top level. All symbols are created during this phase. After the expression is translated, it gets evaluated.
A symbol is created when newLISP first sees it, while calling the load, sym, or eval-string functions. When newLISP reads a source file, symbols are created before evaluation occurs. The reader-event function can be used to inspect the expression after reading and translating but before evaluation. The read-expr function can be used to read and translate newLISP source without evaluation.
When an unknown symbol is encountered during code translation, a search for its definition begins inside the current context. Failing that, the search continues inside MAIN for a built-in function, context, or global symbol. If no definition is found, the symbol is created locally inside the current context.
Once a symbol is created and assigned to a specific context, it will belong to that context permanently or until it is deleted using the delete function.
When a user-defined function is evaluated, the context is switched to the name-space which owns that symbol.
A context switch only influences symbol creation during load, sym, or eval-string. load by default loads into MAIN except when context switches occur on the top level of the file loaded. For better style, the context should always be specified when the functions sym and eval-string are used. A context switch should normally only be made on the top level of a program, never inside a function.
Contexts can be created either by using the context function or via implicit creation. The first method is used when writing larger portions of code belonging to the same context:
(context 'FOO)
(set 'var 123)
(define (func x y z)
... )
(context MAIN)
If the context does not exist yet, the context symbol must be quoted. If the symbol is not quoted, newLISP assumes the symbol is a variable holding the symbol of the context to create. Because a context evaluates to itself, already existing contexts like MAIN do not require quoting.
When newLISP reads the above code, it will read, then evaluate the first statement: (context 'FOO). This causes newLISP to switch the namespace to FOO and the following symbols var, x, y and z will all be created in the FOO context when reading and evaluating the remaining expressions.
A context symbol is protected against change. Once a symbol refers to a context, it cannot be used for any other purpose, except when using delete.
To refer to var or func from anywhere else outside the FOO namespace, they need to be prefixed with the context name:
FOO:var → 123
(FOO:func p q r)
Note, that in the above example only func belongs to the FOO name space the symbols p q r all are part of the current context from which the FOO:func call is made.
The symbols function is used to show all symbols belonging to a context:
(symbols FOO) → (FOO:func FOO:var FOO:x FOO:y FOO:z) ; or from inside the context symbols are shown without context prefix (context FOO) → (func x y z) (sumbols)
A context is implicitly created when referring to one that does not yet exist. Unlike the context function, the context is not switched. The following statements are all executed inside the MAIN context:
> (set 'ACTX:var "hello") "hello" > ACTX:var "hello" > _
Note that only the symbols prefixed with their context name will be part of the context:
(define (ACTX:foo x y)
(+ x y))
When above code is loaded in MAIN only foo will be part of ACTX. The symbols x and y will still be part of MAIN. To make all locals of ACTX:foo members of the ACTX context, they would either have to be prefixed with ACTX, or the whole funtion must be preceded by a context switch satement at the top level:
(context 'ACTX)
(define (foo x y)
(+ x y)
(context MAIN
;; above same as
(define (ACTX:foo ACTX:x ACTX:y)
(+ ACTX:x ACTX:y))
When loading source files on the command-line with load, or when executing the functions eval-string or sym, the context function tells the newLISP source code reader in which namespace to put all of the symbols and definitions:
;;; file MY_PROG.LSP
;;
;; everything from here on goes into GRAPH
(context 'GRAPH)
(define (draw-triangle x y z)
(…))
(define (draw-circle)
(…))
;; show the runtime context, which is GRAPH
(define (foo)
(context))
;; switch back to MAIN
(context 'MAIN)
;; end of file
The draw-triangle and draw-circle functions — along with their x, y, and z parameters — are now part of the GRAPH context. These symbols are known only to GRAPH. To call these functions from another context, prefix them with GRAPH:
(GRAPH:draw-triangle 1 2 3)
(GRAPH:foo) → GRAPH
The last statement shows how the runtime context has changed to GRAPH (function foo's context).
A symbol's name and context are used when comparing symbols from different contexts. The term function can be used to extract the term part from a fully qualified symbol.
;; same symbol name, but in different context (= 'A:val 'B:val) → nil (= (term 'A:val) (term 'B:val)) → true (= (prefix 'A:val) (prefix 'B:val)) → nil
Note: The symbols in above example are quoted with a ' (single quote) because we are interested in the symbol itself, not in the contents of the symbol.
By default, only built-in functions and symbols like nil and true are visible inside contexts other than MAIN. To make a symbol visible to every context, use the global function:
(set 'aVar 123) → 123 (global 'aVar) → aVar (context 'FOO) → FOO aVar → 123
Without the global statement, the second aVar would have returned nil instead of 123. If FOO had a previously defined symbol (aVar in this example) that symbol's value — and not the global's — would be returned instead. Note that only symbols from the MAIN context can be made global.
Once it is made visible to contexts through the global function, a symbol cannot be hidden from them again.
By using the constant function, symbols can be both set and protected from change at the same time:
> (constant 'aVar 123) → 123 > (set 'aVar 999) ERR: symbol is protected in function set : aVar >_
A symbol needing to be both a constant and a global can be defined simultaneously:
(constant (global 'aVar) 123)
In the current context, symbols protected by constant can be overwritten by using the constant function again. This protects the symbols from being overwritten by code in other contexts.
Global and built-in function symbols can be overwritten inside a context by prefixing them with their own context symbol:
(context 'Account)
(define (Account:new …)
(…))
(context 'MAIN)
In this example, the built-in function new is overwritten by Account:new, a different function that is private to the Account context.
Variables can be used to refer to contexts:
(set 'FOO:x 123) (set 'ctx FOO) → FOO ctx:x → 123 (set 'ctx:x 999) → 999 FOO:x → 999
Context variables are useful when writing functions, which need to refer to different contexts during runtime or use contexts which do not exist during definition:
(define (update ctx val)
(set 'ctx:sum val)
(ctx:func 999)
)
(context 'FOO)
(define (func x)
(println "=>" x))
(context MAIN)
The following shows a terminal session using above definitions. The program output is shown in bold-face:
> (update FOO 123) => 999 > FOO:sum 123 >
The same one function update can display different behavior depending on the context passed as first parameter.
The sequence in which contexts are created or loaded can lead to unexpected results. Enter the following code into a file called demo:
;; demo - file for loading contexts (context 'FOO) (set 'ABC 123) (context MAIN) (context 'ABC) (set 'FOO 456) (context 'MAIN)
Now load the file into the newlisp shell:
> (load "demo")
ERR: symbol is protected in function set : FOO
> _
Loading the file causes an error message for FOO, but not for ABC. When the first context FOO is loaded, the context ABC does not exist yet, so a local variable FOO:ABC gets created. When ABC loads, FOO already exists as a global protected symbol and will be correctly flagged as protected.
FOO could still be used as a local variable in the ABC context by explicitly prefixing it, as in ABC:FOO.
Contexts in newLISP are mainly used for partitioning source into modules. Because each module lives in a different namespace, modules are lexically separated and the names of symbols cannot clash with identical names in other modules.
The modules, which are part of the newLISP distribution, are a good example of how to put related functions into a module file, and how to document modules using the newLISPdoc utility.
For best programming practice, a file should only contain one module and the filename should be similar if not identical to the context name used:
;; file db.lsp, commonly used database functions
(context 'db)
;; Variables used throughout this namespace
(define db:handle)
(define db:host "http://localhost")
;; Constants
(constant 'Max_N 1000000)
(constant 'Path "/usr/data/")
;; Functions
(define (db:open ... )
... )
(define (db:close ... )
... )
(define (db:update ... )
... )
The example shows a good practice of predefining variables, which are global inside the namespace, and defining as constants the variables that will not change.
If a file must contain more than one context, then the end of the context should be marked with a switch back to MAIN:
;; Multi context file multi.lsp (context 'A-ctx) ... (context MAIN) (context 'B-ctx) ... (context MAIN) (context 'C-ctx) ... (context MAIN)
In any case load will always switch back to the context from where it was called.
Contexts are frequently uses as data containers, e.g. for configuration data:
;; Config.lsp - configuration setup (context 'Config) (set 'user-name "admin") (set 'password "secret") (set 'db-name "/usr/data/db.lsp") ... ;; eof
Loading the Config namespace will now load a whole variable set into memory at once:
(load "Config.lsp") (set 'file (open Config:db-name "read")) ... ...
In a similar fashion a whole data set can be saved:
(save "Config.lsp" 'Config)
Read more about this in the section Serializing contexts.
Module files are loaded using the load function. If a programming project contains numerous modules that refer to each other, they can be pre-declared to avoid problems due to context forward references that can occur before the loading of that context.
;; pre-declaring contexts, finish with Main to return
(map context '(Utilities Config Acquisition Analysis SysLog MAIN))
;; loading context module files
(load "Utilities.lsp" "Acquisition.lsp")
(load "http://192.168.1.34/Config.lsp") ; load module from remote location
(load "Analysis.lsp" "SysLog.lsp")
(define (run)
... )
(run)
;; end of file
When pre-declaring and loading modules as shown in the example, the sequence of declaration or loading can be neglected. All forward references to variables and definitions in modules not loaded yet will be translated correctly. Wrong usage of a context symbol will result in an error message before that context is loaded.
Modules not starting with a context switch are always loaded into MAIN except when the load statement specifies a target context as the last parameter. The load function can take URLs to load modules from remote locations, via HTTP.
The current context after the load statement will always be the same as before the load.
Serialization makes a software object persistent by converting it into a character stream, which is then saved to a file or string in memory. In newLISP, anything referenced by a symbol can be serialized to a file by using the save function. Like other symbols, contexts are saved just by using their names:
(save "mycontext.lsp" 'MyCtx) ; save MyCtx to mycontext.lsp (load "mycontext.lsp") ; loads MyCtx into memory (save "mycontexts.lsp" 'Ctx1 'Ctx2 'Ctx3) ; save multiple contexts at once
For details, see the functions save (mentioned above) and source (for serializing to a newLISP string).
A default functor or default function is a symbol or user-defined function or macro with the same name as its namespace. When the context is used as the name of a function or in the functor position of an s-expression, newLISP executes the default function.
;; the default function
(define (Foo:Foo a b c) (+ a b c))
(Foo 1 2 3) → 6
If a default function is called from a context other than MAIN, the context must already exist or be declared with a forward declaration, which creates the context and the function symbol:
;; forward declaration of a default function
(define Fubar:Fubar)
(context 'Foo)
(define (Foo:Foo a b c)
…
(Fubar a b) ; forward reference
(…)) ; to default function
(context MAIN)
;; definition of previously declared default function
(context 'Fubar)
(define (Fubar:Fubar x y)
(…))
(context MAIN)
Default functions work like global functions, but they are lexically separate from the context in which they are called.
Like a lambda or lambda-macro function, default functions can be used with map or apply.
A default function can update the lexically isolated static variables contained inside its namespace:
;; a function with memory
(define (Gen:Gen x)
(if Gen:acc
(inc Gen:acc x)
(setq Gen:acc x)))
(Gen 1) → 1
(Gen 1) → 2
(Gen 2) → 4
(Gen 3) → 7
gen:acc → 7
The first time the Gen function is called, its accumulator is set to the value of the argument. Each successive call increments Gen's accumulator by the argument's value.
The definition of Gen:Gen shows, how a function is put in its own namespace without using the surrounding (context 'Gen) and (context MAIN) statements. In that case only symbols qualified by the namespace prefix will end up in the Gen context. In the above example the variable x is still part of MAIN.
There are several functions that can be used to place symbols into namespace contexts. When using dictionaries as simple hash-like collections of variable → value pairs, use the uninitialized default functor:
(define Myhash:Myhash) ; create namespace and default functor ; or as a safer alternative (new Tree 'Myhash) ; create from built-in template
Either method can be used to make the MyHash dictionary space and default functor. The second method is safer, as it will protect the default functor MyHash:MyHash from change. The default functor in a namespace must contain nil to be used as a dictionary. The string used for the symbol name is limited to 1022 characters and internally an underscore is prepended to the symbol name used in the context. Creating key-value pairs and retrieving a value is easy:
(Myhash "var" 123) ; create and set variable/value pair (Myhash "var") → 123 ; retrieve value ; keys can be integers and will be converted to strings internally (Myhash 456 "hello") (Myhash 456) → "hello" ; internally an underscore is prepended to the symbol name (symbols Myhash) → (Myhash:Myhash Myhash:_456 Myhash:_var)
Symbol variables created this way can contain spaces or other characters normally not allowed in newLISP symbol names:
(define Foo:Foo) ; or to protect the default functor from change ; (new Tree 'Foo) (Foo "John Doe" 123) → 123 (Foo "#1234" "hello world") → "hello world" (Foo "var" '(a b c d)) → (a b c d) (Foo "John Doe") → 123 (Foo "#1234") → "hello world" (Foo "var") → (a b c d)
An entry which doesn't exist will return nil:
(Foo "bar") → nil
Setting an entry to nil will effectively delete it from the namespace.
An association list can be generated from the contents of the namespace:
(Foo) → (("#1234" "hello world") ("John Doe" 123) ("var" (a b c d)))
Entries in the dictionary can also be created from a list:
(Foo '(("#1234" "hello world") ("John Doe" 123) ("var" (a b c d))) → Foo
The list can also be used to iterate through the sorted key -> value pairs:
(dolist (item (Foo)) (println (item 0) " -> " (item 1))) #1234 -> hello world John Doe -> 123 var -> (a b c d)
Like many built-in functions hash expressions return a reference to their content which can be modified directly:
(pop (Foo "var")) → a (Foo "var") → (b c d) (push 'z (Foo "var")) → (z b c d) (Foo "var") → (z b c d)
When setting hash values the anaphoric system variable $it can be used to refer to the old value when setting the new:
(Foo "bar" "hello world")
(Foo "bar" (upper-case $it))
(Foo "bar") → "HELLO WORLD"
Hash values also can be modified using setf:
(Foo "bar" 123) → 123 (setf (Foo "bar") 456) → 456 (Foo "bar") → 456
But supplying the value as a second parameter to the hash functions is shorter to write and faster.
Dictionaries can easily be saved to a file and reloaded later:
; save dictionary (save "Foo.lsp" 'Foo) ; load dictionary (load "Foo.lsp")
Internally the key strings are created and stored as symbols in the hash context. All key strings are prepended with an _ underscore character. This protects against overwriting the default symbol and symbols like set and sym, which are needed when loading a hash namespace from disk or over HTTP. Note the following difference:
(Foo) → (("#1234" "hello world") ("John Doe" 123) ("var" (a b c d))) (symbols Foo) → (Foo:Foo Foo:_#1234 Foo:_John Doe Foo:_var)
In the first line hash symbols are shown as strings without the preceding underscore characters. The second line shows the internal form of the symbols with prepended underscore characters.
For a more detailed introduction to namespaces, see the chapter on Contexts.
A default functor can also be used to hold data. If this data contains a list or string, the context name can be used as a reference to the data:
;; the default functor for holding data (define Mylist:Mylist '(a b c d e f g)) (Mylist 3) → d (setf (Mylist 3) 'D) → D Mylist:Mylist → (a b c D e f g) ;; access list or string data from a default functor (first Mylist) → a (reverse Mylist) → (g f e D c b a) (set 'Str:Str "acdefghijklmnop") (upper-case Str) → "ACDEFGHIJKLMNOP"
Most of the time, newLISP passes parameters by value copy. This poses a potential problem when passing large lists or strings to user-defined functions or macros. Strings and lists, which are packed in a namespace using default functors, are passed automatically by reference:
;; use a default functor to hold a list
(set 'Mydb:Mydb (sequence 1 100000))
(define (change-db obj idx value)
(setf (obj idx) value))
; pass by context reference
(change-db Mydb 1234 "abcdefg")
(Mydb 1234) → "abcdefg"
Any argument of a built-in function calling for either a list or a string — but no other data type — can receive data passed by reference. Any user-defined function can take either normal variables, or can take a context name for passing a reference to the default functor containing a list or string.
Note that on lists with less than about 100 elements or strings of less than about 50000 characters, the speed difference between reference and value passing is negligible. But on bigger data objects, differences in both speed and memory usage between reference and value passing can be significant.
Built-in and user-defined functions are suitable for both types of arguments, but when passing context names, data will be passed by reference.
Quoted symbols can also be used to pass data by reference, but this method has disadvantages:
(define (change-list aList) (push 999 (eval aList))) (set 'data '(1 2 3 4 5)) ; note the quote ' in front of data (change-list 'data) → (999 1 2 3 4 5) data → (999 1 2 3 4 5)
Although this method is simple to understand and use, it poses the potential problem of variable capture when passing the same symbol as used as a function parameter:
;; pass data by symbol reference
> (set 'aList '(a b c d))
(a b c d)
> (change-list 'aList)
ERR: list or string expected : (eval aList)
called from user defined function change-list
>
At the beginning of the chapter it was shown how to package data in a name-space using a default functor. Not only the default functor but any symbol in context can be used to hold data. The disadvantage is that the calling function must have knowledge about the symbol being used:
;; pass data by context reference
(set 'Mydb:data (sequence 1 100000))
(define (change-db obj idx value)
(setf (obj:data idx) value))
(change-db Mydb 1234 "abcdefg")
(nth 1234 Mydb:data) → "abcdefg"
; or
(Mydb:data 1234) → "abcdefg"
The function receives the namespace in the variable obj, but it must have the knowledge that the list to access is contained in the data symbol of that namespace (context).
Functional-object oriented programming (FOOP) is based on the following five principles:
Class attributes and methods are stored in the namespace of the object class.
The namespace default functor holds the object constructor method.
An object is constructed using a list, the first element of which is the context symbol describing the class of the object.
Polymorphism is implemented using the : (colon) operator, which selects the appropriate class from the object.
A target object inside a class-method function is accessed via the self function.
The following paragraphs are a short introduction to FOOP as designed by Michael Michaels from neglook.com.
Class attributes and methods are stored in the namespace of the object class. No object instance data is stored in this namespace/context. Data variables in the class namespace only describe the class of objects as a whole but don't contain any object specific information. A generic FOOP object constructor can be used as a template for specific object constructors when creating new object classes with new:
; built-in generic FOOP object constructor
(define (Class:Class)
(cons (context) (args)))
; create some new classes
(new Class 'Rectangle) → Rectangle
(new Class 'Circle) → Circle
; create some objects using the default constructor
(set 'rect (Rectangle 10 20)) → (Rectangle 10 20)
(set 'circ (Circle 10 10 20)) → (Circle 10 10 20)
; create a list of objects
; building the list using the list function instead of assigning
; a quoted list ensures that the object constructors are executed
(set 'shapes (list (Circle 5 8 12) (Rectangle 4 8) (Circle 7 7 15)))
→ ((Circle 5 8 12) (Rectangle 4 8) (Circle 7 7 15))
The generic FOOP constructor is already pre-defined, and FOOP code can start with (new Class ...) statements right away.
As a matter of style, new classes should only be created in the MAIN context. If creating a new class while in a different namespace, the new class name must be prefixed with MAIN and the statement should be on the top-level:
(context 'Geometry) (new Class 'MAIN:Rectangle) (new Class 'MAIN:Circle) ...
Creating the namespace classes using new reserves the class name as a context in newLISP and facilitates forward references. At the same time, a simple constructor is defined for the new class for instantiating new objects. As a convention, it is recommended to start class names in upper-case to signal that the name stands for a namespace.
In some cases, it may be useful to overwrite the simple constructor, that was created during class creation, with new:
; overwrite simple constructor
(define (Circle:Circle x y radius)
(list Circle x y radius))
A constructor can also specify defaults:
; constructor with defaults
(define (Circle:Circle (x 10) (y 10) (radius 3))