TwinLisp for lisp users

TwinLisp is a new way of programing in Common Lisp. The following is a code that will be executed by a Common Lisp machine:

def helloWorld {
    cout() << "Hello World!" << #\Newline }

More precisely, this code is first translated by TwinLisp into Common Lisp, which is then executed by a lisp machine.

This document describes TwinLisp's syntax, how it is translated into Common Lisp, what libraries are used, etc. It is assumed that the reader has some idea about Common Lisp.

Contents

TwinLisp Interpreter

You can get the latest version of a TwinLisp interpreter in a download area.

When interpreter is installed, interactive session can be started by typing 'tlisp' at the shell prompt:

$ tlisp
TwinLisp interpreter.
Typing "Ctrl-D" or "exit" quits interpreter.
>>>

Interpreter uses ">>>" prompt to indicate that it is waiting for commands:

>>> 1+2

3
>>>

When typing several lines, interpreter will use "..." prompt to indicate that expression hasn't been finished:

>>> progn {
...     a=2
...     a**5}

32
>>>

TwinLisp is a translator from TwinLisp code to Common Lisp code. So, execution of files and interpreter have to be changed to include translation phase before evalution in Common Lisp machine. TwinLisp interpreter executes TREPL -- translate-read-evaluate-print loop. You may check +, ++, and +++ usual variables to see the actual Common Lisp code -- an output from a reader that is fed into eval.

>>> progn {
...     a=2
...     a**5}

32
>>> &+

(PROGN (LET (A) (SETF A 2) (_**_ A 5)))
>>> 

Back to content

Identifiers and Operators

Unlike Common Lisp's syntax, TwinLisp uses regular binary and unary operators like + and - in the same way as they are used in languages like Python or Java. But, TwinLisp needs to use Common Lisp's functions, macros, and usually names in CL are something like *global-variable*, where non-alpha-numeric characters are legal inside of the identifier. In order to treat special characters inside of an identifier in CL's manner, TL requires &-character to be the first character of the identifier. For example:

>>> def &some-func(&a-var,&b-var) {
...     &a-var + &b-var }

SOME-FUNC
>>> &+

(DEFUN SOME-FUNC (A-VAR B-VAR) (_+_ A-VAR B-VAR))
>>>

&-ed identifier should be followed by a comma, by an opening or closing round bracket, by a semicolomn, by an end of line or an end of file. Other characters will be treated as if they are part of the identifier. Compare previous example with:

>>> &a-var+&b-var
ERROR: EVAL: variable A-VAR+&B-VAR has no value
>>>

Moreover, &-character can be used to create identifier that are identical in spelling to reserved TL words. For example:

>>> package = 1
SYNTAX ERROR:
Required 'NAME_ON_DIFF_LINE' part of a block structure is missing on line 1
>>> &package = 1

1
>>>

Back to content

Code expressions and blocks

TwinLisp syntax is different from that of Common Lisp. In fact, the main goal of TwinLisp is having an alternative syntax on top of an excelent CL runtime. Many people do not see treasures of CL behind its unpopular syntax. Syntax itself is a very thin layer that should be able to change to accommodate human preferences, which do not really matter at a time when program runs, but have an impact on a success of a development process.

TwinLisp uses operators like they are used in math and in languages like Python or Java. Function calls are much like those in Python or Java. There are special block structures, just like Python and Java. But there are differences.

The simpliest expressions of TwinLisp are atoms, like a symbol, or a number, or a string. Simple expressions can be compound by use of operators. Every expression is terminated by a comma, by a comment, by an end of line, by an end of file or by some closing bracket that encloses one or several expressions.

There are also blocks of code, that are enclosed in some brackets. Take a look at the following definition of a function:

>>> def foo(a,b) {
...     c = a+b
...     print(c)
...     c }

FOO

And compare it to Python

>>> def foo(a,b):
...     c = a+b
...     print c
...     return c
...

And to Java (assuming that this function is some class' method)

int foo(int a, int b) {
    int c = a+b;
    System.out.println(c);
    return c; }

TwinLisp's expressions are terminated by the end of the line, like they are in Python. This saves one character on each line in comparison to Java, where each line has a semicolomn at the end. On the other hand TwinLisp uses brackets to indicate a start and an end of a code block, like Java, and unlike Python. Python uses one colomn to mark a beginning of the block and a strict change of indentation to mark its end. Arguably, Python saves one character, but, besides having strict indentation rules, it misses one huge feature ever present in CL. Change of indentation requires a newline character, which also terminates an expression, of which a block can be a part. For example, the following seems impossible with Python's indentation rules:

>>> a = 3

3
>>> 7 + if (a>0) { -a }
...     else { a } - 1

3

Like it is in Python, if a line is too short for you, it can be continued by placing \ . And unlike Python, you may type anything after character \ , since it all will be discarded up to the end of a line:

>>> 12 + 7** \ what power should we insert ?
...         2 - 9

52

Back to content

Comments

For now, there are only one-line lisp comments available

>>> progn {
...     a = 0
...     ; this is a comment
...     a += 5 ; another comment
...     a**a }

3125

Back to content

Operator Methods

This section describes operators used in TwinLisp. Operators are translated into functions, macros or generic functions. When a generic function correspondes to an operator, new methods can be defined to provide new behaviour.

Order of operator application depends on type of an operator, precedence and associativity. There are three types of operators in TL: binary operators (those that act on two operands), unary operators (those that act on one operand only) and, for the lack of a word, "multary" operators (these act on many operands at once). Precedence is defined as a number, the lower its value, the higher the priority of an operator. Association determines an order, when two operators have the same precedence.

So, here are TwinLisp's operators:

Oper Type Prec Assoc Defined by
+ unary 2 Right &_unary+_(x) -- generic function
- unary 2 Right &_unary-_(x) -- generic function
! unary 2 Right _not_(x) -- generic function
not unary 2 Right _not_(x) -- generic function
** binary 3 Right &_**_(x,y) -- generic function
* binary 4 Left &_*_(x,y) -- generic function
/ binary 4 Left &_/_(x,y) -- generic function
% binary 4 Left &_%_(x,y) -- generic function
+ binary 5 Left &_+_(x,y) -- generic function
- binary 5 Left &_-_(x,y) -- generic function
< binary 7 Left &_<_(x,y) -- generic function
> binary 7 Left &_>_(x,y) -- generic function
<= binary 7 Left &_<=_(x,y) -- generic function
>= binary 7 Left &_>=_(x,y) -- generic function
== binary 8 Left &_==_(x,y) -- generic function
!= binary 8 Left &_!=_(x,y) -- generic function
& binary 9 Left _and_(x,y) -- generic function
and binary 9 Left _and_(x,y) -- generic function
^ binary 10 Left _xor_(x,y) -- generic function
xor binary 10 Left _xor_(x,y) -- generic function
| binary 11 Left _or_(x,y) -- generic function
or binary 11 Left _or_(x,y) -- generic function
<< binary 13 Left &_<<_(x,y) -- generic function
@ multary 14 n/a values(x,y,...) -- function
= binary 15 Right setf(x,y) -- macro
+= binary 15 Right &_+=_(x,y) -- macro
-= binary 15 Right &_-=_(x,y) -- macro
*= binary 15 Right &_*=_(x,y) -- macro
/= binary 15 Right &_/=_(x,y) -- macro

Let's look at how these operators are used:

Back to content

Scope of Variables

TwinLisp is a Common Lisp internally. As such there are both lexical and dynamical variables in it. All CL rules apply. But as a translator, TwinLisp saves you some typing be inserting "let" clauses in non-top level of code blocks. So, TwinLisp implicitly defines lexical variables in non-top levels. Take a look:

>>> progn {
...     a = 0
...     a+5 }

5
>>> &+

(PROGN (LET (A) (SETF A 0) (_+_ A 5)))

"let" construct is inserted, and it goes as far possible, i.e. till the end of a block.

"let" definition is used only for variables that are assigned to, and only if a variable hasn't been assigned before in the same or upper levels (with exception of function and macro definitions).

>>> progn {
...     a = 0
...     progn {
...         b = 1
...         a = b+2
...         a }}

3
>>> &+

(PROGN (LET (A) (SETF A 0) (PROGN (LET (B) (SETF B 1) (SETF A (_+_ B 2)) A))))

"let" construct is never used on variables that have a package name qualifier.

In a top level, no "let" constructs are inserted, and a result is governed by the behaviour of a setf macro, which should create a new special variable, if no proper variable exist.

Implicit addition of definition of lexical variables can be turned off and on (it is "on" be default) by use of a TwinLisp directive:

>>> lexscope explicit
>>> progn {
...     a = 0
...     a+5 }
SYNTAX ERROR:
Assignment to unknown variable 'a'. You have to use 'let'-type constructs, or declare variable global.
>>> let (a,b=0) {
...     a = 3
...     a+b }

3
>>> lexscope implicit
>>> progn {
...     a = 0
...     a+5 }

5

TwinLisp has a "let" construct, as you've seen it in above example, and "lets" construct, which is "let*" in CL.

In TwinLisp one can define variable, parameter and constant by use of macros var, param and const, which are the same as defvar, devparameter and defconstant, they are just shorter to spell.

One more important point is that, inside of a macro definition lexscope is explicit by default. So, the most probable place for use of "let" constructs will be inside of macro definitions.

Back to content

Defining Functions and Macros

Defining functions, macros and methods is a very common thing, so there are TwinLisp structures that aid this task. As it is in Common Lisp, in TwinLisp we have lambda:

>>> lambda (a,b) {
...     a+b }

#<CLOSURE :LAMBDA (A B) (_+_ A B)>

If a lambda list needs to be empty, it can be omitted:

>>> lambda { cout << "Hello, lambda-world!" }

#<CLOSURE :LAMBDA NIL (_<<_ COUT "Hello, lambda-world!")>

There can be optional parameters:

>>> lambda (a,b=2006) { a+b }

#<CLOSURE :LAMBDA (A &OPTIONAL (B 2006)) (_+_ A B)>
>>> lambda (a,b=2006=?bPresent) { a+b }

#<CLOSURE :LAMBDA (A &OPTIONAL (B 2006 BPRESENT)) (_+_ A B)>
>>> lambda (a,b=?bPresent) { a+b }

#<CLOSURE :LAMBDA (A &OPTIONAL (B NIL BPRESENT)) (_+_ A B)>
>>> lambda (a,&&optional b) { a+b }

#<CLOSURE :LAMBDA (A &OPTIONAL B) (_+_ A B)>

We can have key parameters:

>>> lambda (a,:b->b) { a+b }

#<CLOSURE :LAMBDA (A &KEY ((:B B))) (_+_ A B)>
>>> lambda (a,:b->b=2006) { a+b }

#<CLOSURE :LAMBDA (A &KEY ((:B B) 2006)) (_+_ A B)>
>>> lambda (a,:b->b=2006=?bPresent) { a+b }

#<CLOSURE :LAMBDA (A &KEY ((:B B) 2006 BPRESENT)) (_+_ A B)>
>>> lambda (a,:b->b=?bPresent) { a+b }

#<CLOSURE :LAMBDA (A &KEY ((:B B) NIL BPRESENT)) (_+_ A B)>
>>> lambda (a,&&key b) { a+b }

#<CLOSURE :LAMBDA (A &KEY B) (_+_ A B)>

We may have rest parameter:

>>> lambda (a,*b) {
...     for (elem,b) (a) { a += elem }}

#<CLOSURE :LAMBDA (A &REST B) (TL-FOR (ELEM B) (A) (_+=_ A ELEM))>
>>> lambda (a,&&rest b) {
...     for (elem,b) (a) { a += elem }}

#<CLOSURE :LAMBDA (A &REST B) (TL-FOR (ELEM B) (A) (_+=_ A ELEM))>

Lambda list will also admit &&allow-other-keys and &&aux parameters.

To define functions we use def structure (it does what defun does in CL):

>>> def foo (a,b=2006) { a+b }

FOO

All rules that we showed for lambda are applicable for def.

To define macros we use mac, which is much similar to def (due to similarity of defmacro and defun):

>>> mac boo (a,b) { `foo($a,$b) }

BOO

Macros' lambda lists are allowed to have inner lambda lists. To write an inner lambda list, so that its brackets are not confused with anything else, we use a dot in front of an inner list:

>>> mac boo (a, .(b,c)) { `(foo($a,$b)+$c) }

BOO
>>> &+

(DEFMACRO BOO (A (B C)) `(_+_ (FOO ,A ,B) ,C))

Macros' lambda lists may have other parameters than functions'. But one parameter is used more often then others, so that there is a special syntax for it:

>>> mac &infinite-loop (**body) {
...     `do () { $@body }}

INFINITE-LOOP
>>> &+

(DEFMACRO INFINITE-LOOP (&BODY BODY) `(DO NIL (NIL) ,@BODY))

Back to content

Calling Functions and Macros

Calling functions, macros and methods is done like in other languages:

>>> def foo (a,b=2006) { a+b }

FOO
>>> foo(330)

2336
>>> foo(2,2)

4
>>> def boo (a,:b->b=2006) { a+b }

BOO
>>> boo(330)

2336
>>> boo(2,:b=2)

4

Notice how value is assigned to key parameter using =. Under the surface, it is translated into:

>>> &+

(BOO 2 :B 2)

To call macros that require inner lambda lists, we need similar inner list in the call:

>>> mac boo (a, .(b,c)) { `(foo($a,$b)+$c) }

BOO
>>> boo(1,2,3)
ERROR: The macro BOO may not be called with 3 arguments: (BOO 1 2 3)
>>> boo(1,.(2,3))

6

Now, many macros define new control structures in which chunks of code should be placed. The round brackets for a function call will be inconvenient for it. Moreover, user defined control structures should look like official structures, where code is usually placed in braces ( {} ). This structures outline new code levels, which should be taken into account be the mechanism that takes care of implicit lexical variables. All of this begs for having a second complementary way to call macros, funcs, etc. It is done by using braces instead of round brackets. Let's illustrate it with what you might have thought to be a special TL structure:

>>> progn {
...     a=2
...     a**5 }

32
>>> &+

(PROGN (LET (A) (SETF A 2) (_**_ A 5)))

For TwinLisp progn is the same as foo above, i.e. it is not a special structure. So, previous numerous examples already illustrate this second way of calling functions (macros, methods).

The two ways can be combined by first using round brackets, and later -- braces. This combines use of = in round brackets with having a real code block in braces. This combination will fit into very common macro call:

>>> mac foo(a,**body) { `if ($a) { $@body }}

FOO
>>> foo(t){
...     a = 1
...     b = 3
...     5**b-a }

124
>>> foo(nil){
...     a = 1
...     b = 3
...     5**b-a }

NIL

Back to content

Calling Methods and Accessing Objects' Slots

Languages like Python and Java have objects, and they have a convenient way of calling objects' methods. For example in Python have:

>>> 4 . __str__()
'4'

Same can be done in TwinLisp, except that in Common Lisp methods do not belong to classes, and in Python (Java) methods belong to a class of an object before the dot. So, reasoning for having a dot notation in lisp should be a little different. Fortunately, the rescue idea comes from an analogy of how Python methods are written:

>>> class foo:
...     def showA(self):
...             return self.a
...

So, we say that if an object is followed by a dot and a function call, this object has to be the first argument of a call. Take a look:

>>> a = ~[1,2,3]

(1 2 3)
>>> a.cdr()

(2 3)
>>> &+

(CDR A)
>>> a.append("s")

NIL
>>> &+

(TL-APPEND A "s")

In this manner it will be convenient to call methods on objects.

TwinLisp uses the same dot notation to access objects' slots:

>>> class foo {
...     a {:initform="slot a"}},

#<STANDARD-CLASS FOO>
>>> obj = foo.new()

#<FOO #x203F95BE>
>>> obj.a

"slot a"
>>> obj.a = 2

2
>>> obj.a

2
>>> &+

(SLOT-VALUE OBJ 'A)

Back to content

Shortcuts

Common Lisp system uses the fact that forms can be treated as a code, which requires execution, or as a data. Transitions between code and data are simple in Common Lisp, because it has shortcuts like quote and a backquote. These are reader macros under the surface. But for our purposes, these are shortcuts, which act almost like some operators that are applied always to the following form.

TwinLisp is a translator, and it basically stands on the way of reader macros. So, if TwinLisp is unaware of a macro character, it will not be treated correctly, since reader macros use special characters, that may have a different sense for TL.

TL recognizes the following shortcuts, which are translated into corresponding CL shortcuts:

TL shortcut CL shortcut
' '
#' #'
` `
$ ,
$@ ,@
#. #.

The use of shortcuts is the same as in CL. For example,

>>> funcall( #'+ ,2,2)

4
>>> &+

(FUNCALL #'_+_ 2 2)
>>> a=~[1,2]

(1 2)
>>> 'car.funcall(a)

(FUNCALL CAR A)
>>> &+

'(FUNCALL CAR A)
>>> (#'car).funcall(a)

1
>>> &+

(FUNCALL #'CAR A)
>>> `list(a,$a)

(LIST A (1 2))
>>> &+

`(LIST A ,A)
>>> `list(a,$@a)

(LIST A 1 2)
>>> &+

`(LIST A ,@A)

The most common place for these shorcuts will be macro definitions. When I started writting TwinLisp, I thought that this syntax won't be as efficient as pure lisp for writting macros. To check it, I've compiled a file with comparisons of randomly chosen macros from well known sources with their analogs in TwinLisp. The result shows that writting a macro in TL is just as difficult (or simple) as it is in CL. One does not need brackets of a CL syntax to have macros, but one does need shortcuts. This means that languages like Python do not need a great change in syntax to accommodate true macros -- a small addition of shortcuts will do the trick.

Back to content

Common Lisp Forms (S-expressions)

TwinLisp can use all Common Lisp functions, macros, etc. But some CL macros should be called in a truely lisp way, using forms here and there. Syntax of these macros is created in such a way, that it is convenient to use them in only lisp syntax (s-expessions). For these cases it is convenient to have a syntax for lisp form.

Form starts with opening combination ~( and ends with ) .

>>> ~(list,1,2,3+4)

(1 2 7)
>>> &+

(LIST 1 2 (_+_ 3 4))

Round brackets are used in TL for groupping expression. So, a tild (~) tells when one has a CL form instead of a regular expression.

Back to content

Mixing with Common Lisp code

It is possible to insert Common Lisp code into TwinLisp code using "#t{" as an opening bracket and "#t}" as a closing one. Inserted in this way code is not touched at all by TL translator.

>>> 1 + #t{ (* 2 3) #t} - 5

2
>>> &+

(_-_ (_+_ 1 (* 2 3)) 5)

And you can insert TwinLisp code into Common Lisp code between "#t{" and "#t}". The reader's macro, dispatched on #t will translate all of TwinLisp code and insert it into one big progn.

Back to content

Standard Containers

TwinLisp follows Python example of using standard containers like list and dictionaries.

Python has tuples, lists and dictionaries. Tuples are like lists, but they are immutable. Unfortunately, they lack some list's search methods. Python lists are vectors in terms of implementation (if I am correct). Python dictionaries are hash-tables.

Being a lisp, TwinLisp has lists (true lists), vectors and hash-tables. Lists are essential for lisp, so TL has to have them. But in some cases it is more convenient to operate with adjustable vectors, so these have to be standard as well. Hash-tables should have a proper place in TL, too.

Now, to create a list, one can use standard CL function list(), or use ~[...] notation:

>>> list(1,2,"a",#\b)

(1 2 "a" #\b)
>>> &type-of(lst)

CONS
>>> lst = ~[1,2,"a",#\b ]

(1 2 "a" #\b)
>>> &type-of(lst)

CONS

To create an adjustable vector, one can use TL function &_make-vector_(), or use [...] notation:

>>> vec = &_make-vector_(:initContent=~[1,2,"a",#\b ]
...                      :elemType=t)

#(1 2 "a" #\b)
>>> &type-of(vec)

(VECTOR T 4)
>>> vec = [1,2,"a",#\b ]

#(1 2 "a" #\b)
>>> &type-of(vec)

(VECTOR T 4)

Notice that TL does not use round brackets to create containers. This removes possible confusion.

To create a hash-table, one can use TL function &_make-hash-table_(), or use {...} notation:

>>> ht = &_make-hash-table_ ("a",1,"b",2,"c",3)

#S(HASH-TABLE EQUAL ("c" . 3) ("b" . 2) ("a" . 1))
>>> ht = {"a"->1, "b"->2, "c"->3}

#S(HASH-TABLE EQUAL ("c" . 3) ("b" . 2) ("a" . 1))

Notice that a hash-table uses test equal.

To retrieve or set an element in a container by index or a key, TL uses Python [...] notation, which is translated into TL generic function _getitem_() or a setter _getitem_():

>>> lst[0]

1
>>> lst[2]

"a"
>>> vec[0]

1
>>> vec[0] = 78

78
>>> vec[0]

78
>>> ht["a"]

1  ;
T
>>> ht["xyz"] = 8776

8776
>>> ht["xyz"]

8776  ;
T

Notice that on a hash-table _getitem_() returns two values -- element or nil, and a success value of operation. The following are behaviours when a wrong index or a key is given:

>>> vec[7]
ERROR: Method _getitem_ is called on sequence with out-of-range integer index
>>> lst[7]
ERROR: Method _getitem_ is called on sequence with out-of-range integer index
>>> ht["no-such-key"]

NIL  ;
NIL

Let's get a type of an error that vectors generate:

>>> handle { vec[7] }
... cond error (er) { &type-of(er) },

INDEX-ERROR

With []'s one can do slicing:

>>> vec[->3]

#(78 2 "a")
>>> vec[1->3]

#(2 "a")
>>> vec[1->,2]

#(2 #\b)
>>> vec[1->4,2]

#(2 #\b)

The [...] notation is translated into _getitem_() call in two ways. If a combination -> is present, then forms in [] tell about slicing, and the format is then x[start->end,step], where at least one of start or end should be present. If there are no -> combination, then all elements in []'s are passed to _getitem_(). In this way, it is convenient to use multidimensional arrays:

>>> arr = &make-array('~(2,2)
...                   :&initial-contents ='~(~(1,2)
...                                          ~(3,4)))

#2A((1 2) (3 4))
>>> arr[0,1]

2
>>> arr[0,1] = 45

45
>>> arr[0,1]

45

TwinLisp defines some other common to containers methods:

One point should be mentioned here. Empty list is nil, which means nothing. Nothing has no structure. Destructive function mentioned above will not work on nil, since it has no inner structure to change.

>>> ~[].append(1,2)
ERROR: Method extend should not be called on null list

Back to content

Control Flow Structures

There are many control flow macros and functions in Common Lisp, such as do, loop, cond, etc. Some are used more often then others, and TwinLisp provides special constructs to ease the use of most common control flow manipulations.

When describing syntactic structures we will use square brackets with following meanings: [...] will indicate that whatever is in brackets may appear one or zero times, [...]* will indicate that whatever is in brackets may appear any number of times, and [...]+ will indicate that whatever is in brackets should appear one or more times. Character | will separate alternatives.

Back to content

IFs

The most used structures is if-elif-else:

   if (condition1) { body1 }
 [ elif (conditionN) { bodyN } ]*
 [ else { else-body } ]

When condition1 is true (t), then body1 is executed. If condition1 is nil, condition2 is checked, etc. If no condition is true, the else-body is executed. The return result is the value returned by the last expression from the executed body. If no body executed, nil is returned. Under the hood, if-elif-else structure is translated into cond.

A small modification of if-elif-else is case:

   case (expression)
 [ is (value-expression1N [,value-expressionMN]*) { bodyN } ]*
 [ else { else-body } ]

Expression is executed once and the result is compared (with TL ==) to results of value-expressions evaluations. When values are equal, corresponding body is executed. If no match is found, else-body is executed, if present. The return result is the value of the last expression inside of evaluated body. If no body executed, nil is returned. Under the hood, case is a macro over cond.

For cases when value-expressions do not have to be evaluated and comparison can be done with eql (this is a CL's case macro), one should use comcase (Common Lisp case):

   comcase (expression)
 [ is (value1N [,valueMN]*) { bodyN } ]*
 [ else { else-body } ]

Like CL, TwinLisp has typecase:

   typecase (obj)
 [ is (typeN) { bodyN } ]*

If the same body should correspond to several types, one should use, like in CL, &or(type1,type2,...) inside is-clause.

Back to content

Loops

TwinLisp uses do loop:

   do ( [var [= initValue [-> stepValue] ] ]* )
    [ ( [end-test [,result-expressions]* ] ) ]
      { [declaration]*
        [ tag | expression ]* }

The do loop is a Common Lisp do loop. Var is a variable used in the body of the loop with optional initValue and optional stepValue. When end-test is t (true), loop exists with the result of evaluation of result-expressions. If no result-expressions given, the return value is nil. A body of a do loop is a tagbody, so, tags can be used inside with go().

The do loop is also an implicit block named nil. To break out of this block one uses a break statement

   break [ from block-name ] [ result ]

This statement can be used with different blocks by use of option from. When from is absent, the block-name defaults to nil. Result is optional, and it also defaults to nil.

Common Lisp has a simple to use dolist loop. Python has a very similar for loop, but it can be used on other containers (tuples, etc.). In TwinLisp containers are expected to have iterators, which are implicitly used by a for loop, like in Python. But TL's loop allows definitions of other variables that might be used in iteration (similar to do loop's definitions):

   for ( var, container [,varN [= initValue [-> stepValue] ] ]* )
     [ ( [result-expressions]* ) ]
       { [declaration]*
         [ tag | expression ]* }

Unlike Python and Java, there is no continue statement to use in loops. The effect of a continue statement can be created by use of an end tag and go().

Very similar to for loop is times loop.

   times ( var, maxValue [,varN [= initValue [-> stepValue] ] ]* )
     [ ( [result-expressions]* ) ]
       { [declaration]*
         [ tag | expression ]* }

During the iteration var is bound to integer, starting from zero to the highest integer, which is less than maxValue. This loop is much like CL's dotimes loop.

Back to content

Block and Prog

Like Common Lisp, TwinLisp block structure:

   block [block-name] { body }

If no block-name is given, it will be named nil be default. An early lexical exit from a block is performed by break statement, mentioned before.

TwinLisp also provides a syntax structure for prog:

   prog [ ( [var [=initValue] ]* ) ] { body }

Back to content

Dynamic Exits

Common Lisp has to ways to perform dynamic exists. One way is via throw-ing and catch-ing some object. Another is by signal-ing conditions.

To throw an object, one should use statement throw:

   throw tag [ result ]

If result is omitted, it defaults to nil.

To execute expressions while anticipating to catch a tag, use catch structure:

   catch tag { body }

Signaling conditions and errors in TwinLisp is performed by usual CL functions error(), cerror() and signal(). To handle conditions that may arise during execution of some expressions, one should use handle structure:

   handle { body }
 [ cond condition-type [ (var) ] { condition-body } ]*

When execution of a body signals a condition, condition's type is checked against all given condition-types. When types match, an appropriate condition-body is executed with optional var bound to a signaled condition object.

Back to content

Structs and Classes

TwinLisp uses special syntax to define structures:

   struct name
       {  [slot [ {initValue [,option=value]* } ] ]*  }
    [ options { [option=value]* } ]

Struct is translated into defstruct. Options for each slot and for structure as a whole are exactly the same as the ones for defstruct.

Defining a class is very similar:

   class name [ ( [superclass]* ) ]
       {  [slot [ {initValue [,option=value]* } ] ]*  }
    [ options { [option=value]* } ]

Class is translated into defclass. Options for each slot and for a class as a whole are exactly the same as the ones for defclass.

Back to content

Definition and Use of Packages

Common Lisp uses packages to separate symbols, so that name clashes do not occur. Since TwinLisp is a translator on top of CL, it cannot do much to change the way package system works. But TwinLisp can minimize amount of typing.

To define a package, TwinLisp uses a syntactic structure "package":

   package name { [ option { [value]* } ]* }

For example:

>>> package foo {
...     :&use {"COMMON-LISP","TWINLISP"}
...     :nicknames {"BOO","BAR"}
...     :export { "A" }
...     :documentation {"foo package - example"}}

#<PACKAGE FOO>
>>> &+

(DEFPACKAGE FOO (:USE "COMMON-LISP" "TWINLISP") (:NICKNAMES "BOO" "BAR")
(:EXPORT "A") (:DOCUMENTATION "foo package - example"))

TL's package is translated into CL's defpackage, thus, options and their values in package are the same as those in defpackage. Notice that "use" is spelled with & in the beginning. This is due to TwinLisp having a special word "use", and & makes a distiction between the two (see Identifiers and Operators).

To switch between packages, use structure inside:

>>> inside foo

#<PACKAGE FOO>
>>> a = "a in foo"

"a in foo"
>>> b = "b" + " in foo"

"b in foo"
>>> inside "COMMON-LISP-USER"

#<PACKAGE COMMON-LISP-USER>
>>> &+

(IN-PACKAGE "COMMON-LISP-USER")

Suppose, you have defined a package foo with external symbol a and internal b (example above). To use these symbols in another package, you have to either always enter fully qualified names (foo:a and foo::b), or intern a needed symbol into the destination package. Interning a symbol into another package may again produce name clash. Writting a fully qualified name takes a lot of typing. TwinLisp may save you some typing when you use TL directive "use":

   use [ package[:] ] { [ symbol [= synonym] ]* }

This directive tells TwinLisp to substitute every occurance of synonym with package[:]:symbol. If synonym is not given, then every occurance of symbol will be substituted with package[:]:symbol. Let's try it with our example:

>>> use foo { a }
>>> a

"a in foo"
>>> &+

FOO:A
>>> use foo: { b }
>>> b

"b in foo"
>>> &+

FOO::B

Whatever synonyms you define with "use" directive, they will be used inside of the code level, in which it has been defined, and in all sub-levels. For example:

>>> progn {
...     use foo { a=x }
...     x }

"a in foo"
>>> x
ERROR: EVAL: variable X has no value

With the ability to create synonyms one can use shorter names without interning symbols, which leaves interning for some other better uses.

Back to content

TwinLisp's block structures and directives

Every TwinLisp block structure is translated directly into standard common lisp form (it can be a special form, macro or function), or into forms defined by TwinLisp. Since every form in lisp returns some value, all TwinLisp block structures return values. In this way TwinLisp can be used in functional style programing.

TwinLisp directives only adjust translator's behaviour. As such, they are not translated into lisp code, and they do not return anything. New translator's behaviour is applicable only on a current and lower code levels.

Describing TwinLisp syntactic structures, we will use square brackets, similarly to section "Control Flow Structures": [...] will indicate that whatever is in brackets may appear one or zero times, [...]* will indicate that whatever is in brackets may appear any number of times, and [...]+ will indicate that whatever is in brackets should appear one or more times. Character | will separate alternatives.

Back to content

block

   block [ name ] { body }

block is translated into CL block. If name is omitted, block will be named nil.

Back to content

break

   break [ from block-name ] [ return-value ]

break is translated into CL return-from. If block-name is omitted, nil is assumed. If return-value is omitted, nil is returned.

Back to content

case

   case (expression)
 [ is (value-expression1N [,value-expressionMN]*) { bodyN } ]*
 [ else { else-body } ]

case is translated into &tl-case macro. Expression is executed once and the result is compared (with TL ==) to results of value-expressions evaluations. When values are equal, corresponding body is executed. If no match is found, else-body is executed, if present. The return result is the value of the last expression inside of evaluated body. If no body executed, nil is returned.

Back to content

catch

   catch tag { body }

catch is translated into CL catch.

Back to content

class

   class name [ ( [superclass]* ) ]
       {  [slot [ {initValue [,option=value]* } ] ]*  }
    [ options {
         [:&default-initargs { [arg, value]* } ]
         [option=value]* } ]

class is translated into CL defclass. Options for each slot and for a class as a whole are exactly the same as the ones for defclass.

Back to content

comcase

   comcase (expression)
 [ is (value1N [,valueMN]*) { bodyN } ]*
 [ else { else-body } ]

comcase is translated into CL case.

Back to content

cond

   cond name [ ( [parent-type]* ) ]
       {  [slot [ {initValue [,option=value]* } ] ]*  }
    [ options {
         [:&default-initargs { [arg, value]* } ]
         [option=value]* } ]

cond is translated into CL define-condition. Options for each slot and for a condition as a whole are exactly the same as the ones for define-condition.

Back to content

def

   def [setter] func-name [ ( lambda-list ) ]
       { [ [declaration]* | documentation-string ]
         body }

where lambda-list is defined in lambda.

When "setter" is present, the actual name of the function created is "(setf func-name)".

Back to content

defgen

   defgen [setter] gfunc-name ( lambda-list )
    [ options { [option=value]*
                [declaration] } ]
    [ meth [method-qualifier]* ( specialized-lambda-list )
        { [ [declaration]* | documentation-string ]
            meth-body } ]*

where

   lambda-list := [var]*
                  [ &&optional [var]* ]
                  [ *var | &&rest [*]var ]
                  [ [keyword->var]* |
                    &&key [ [keyword->] var ]* [&&allow-other-keys] ]

defgen is translated into CL defgeneric. Generic function's options are the same as those in defgeneric. method-qualifier and specialized-lambda-list are the same as those in meth.

Back to content

do

   do ( [var [= initValue [-> stepValue] ] ]* )
    [ ( [end-test [,result-expressions]* ] ) ]
      { [declaration]*
        [ tag | expression ]* }

do is translated into a Common Lisp do loop. Var is a variable used in the body of the loop with optional initValue and optional stepValue. When end-test is t (true), loop exists with the result of evaluation of result-expressions. If no result-expressions given, the return value is nil. A body of a do loop is a tagbody, so, tags can be used inside with go().

The do loop is also an implicit block named nil. To break out of this block one uses a break statement.

Back to content

dos

   dos ( [var [= initValue [-> stepValue] ] ]* )
     [ ( [end-test [,result-expressions]* ] ) ]
       { [declaration]*
         [ tag | expression ]* }

dos is similar to do, except that vars are assigned sequentially. dos is translated into CL do*.

Back to content

flet

   flet [ [setter] func-name [ (lambda-list) ]
                    { [ [declaration]* | documentation-string ]
                        func-body } ]*
      { flet-body }

Syntax of function definitions inside flet is the same as in def

flet is translated into CL flet.

flet defines local functions, which can be used inside flet-body. New functions cannot call each other and they cannot form recursive groups. If that is desired, use labels.

Back to content

for

   for ( var, container [,varN [= initValue [-> stepValue] ] ]* )
     [ ( [result-expressions]* ) ]
       { [declaration]*
         [ tag | expression ]* }

for is translated into &tl-for macro.

for is a loop construct based on dos. Method iter() is implicitly called on a container, and on each iteration var is consequently bound to elements provided by an iterator. When there no more elements coming from an iterator, iteration is stopped, result-expressions are evaluated, and the value of the last result-expression is returned. If there are no result-expressions, nil is returned.

Back to content

gfun

   gfun ( lambda-list )
    [ options { [option=value]*
                [declaration] } ]
    [ meth [method-qualifier]* ( specialized-lambda-list )
        { [ [declaration]* | documentation-string ]
            meth-body } ]*

gfun is translated into CL generic-function. gfun is very similar to defgen, except that it defines an anonymous generic function.

Back to content

glabels

   glabels [  [setter] gfunc-name ( lambda-list )
            [ options { [option=value]*
                        [declaration] } ]
            [ meth [method-qualifier]* ( specialized-lambda-list )
                { [ [declaration]* | documentation-string ]
                    meth-body } ]* ]*
       { glabels-body }

glabels is translated into CL generic-labels.

glabels is essentially the same as glet, except that defined functions can call each other.

Back to content

glet

   glet [  [setter] gfunc-name ( lambda-list )
            [ options { [option=value]*
                        [declaration] } ]
            [ meth [method-qualifier]* ( specialized-lambda-list )
                { [ [declaration]* | documentation-string ]
                    meth-body } ]* ]*
       { glet-body }

glet is translated into CL generic-flet.

Function definitions inside glet are the same as those in defgen.

glet defines local generic functions, which can be used inside glet-body. These functions cannot use each other or form recursive groups (very similar to flet). If functions have to use each other, use glabels

Back to content

global

   global var

global is a TwinLisp's directive. It tells translator that a global variable var will be used, so that it shouldn't be introduced as an implicit lexical variable.

Back to content

handle

   handle { handle-body }
 [ cond condition-type [ (var) ] { [declaration]*
                                   condition-body } ]*
 [ else (lambda-list) { [declaration]*
                          else-body  } ]

handle is translated into CL handler-case.

If condition is signaled inside handle-body, it will be checked against condition-type's. When condition's type matches, appropriate condition-body is executed. An alternative var in cond clause will be bound to the signaled condition, and available inside condition-body. The result returned is the value of evalution of the last expression within condition-body.

If there are no conditions signaled, either the result of evalution of the last expression in handle-body is returned, or, if else clause is present, result of handle-body is passed into lambda-list (it is the same as in lambda), and else-body is evaluated as a lambda function, which result is returned. When else clause is present, one has to make sure that the number of returned results matches the number of parameters that lambda-list can take.

Back to content

if

   if (condition1) { body1 }
 [ elif (conditionN) { bodyN } ]*
 [ else { else-body } ]

if-elif-else structure is translated into CL cond. When condition1 is true (t), then body1 is executed. If condition1 is nil, condition2 is checked, etc. If no condition is true, the else-body is executed. The return result is the value returned by the last expression from the executed body. If no body executed, nil is returned.

Back to content

inside

   inside pack-name

inside is translated into CL in-package.

inside switches current package to pack-name. You get "inside" of this package.

Back to content

labels

   labels [ [setter] func-name [ (lambda-list) ]
                        { [ [declaration]* | documentation-string ]
                            func-body } ]*
       { labels-body }

labels is translated into CL labels.

labels is essentially the same as flet, except that functions can use each other and form recursive groups.

Back to content

lambda

   lambda [ ( lambda-list ) ]
       { [ [declaration]* | documentation-string ]
         body }

where

   lambda-list := [var]*
                  [ [var=initform [=?svar] ]* | [var [=initform] =?svar ]* |
                    &&optional [var [=initform] [=?svar] ]* ]
                  [ *rest | &&rest [*]rest ]
                  [ [ keyword->var [=initform] [=?svar] ]* |
                    &&key [ [keyword->] var [=initform] [=?svar] ]* 
                          [ &&allow-other-keys ] ]
                  [ &&aux [var [=initform] ]* ]

lambda is translated into CL lambda.

lambda creates an anonymous function.

Back to content

let

   let [ ( [var [=value] ]* ) ]
       { [declaration]*
         body }

let is translated into CL let.

let creates new lexical variables for use inside the body. Values assigned to new variables cannot use other new variables. If this is needed, use lets.

Back to content

lets

   lets [ ( [var [=value] ]* ) ]
        { [declaration]*
          body }

lets is translated into CL let*.

lets is essentially the same as let, except that values for new variables can be computed using other variables.

Back to content

lexscope

   lexscope explicit | lexscope implicit

lexscope is a TL's directive.

lexscope explicit turns off implicit introduction of new lexical variables, so that they have to be explicitly introduced by let, lets, or other ways.

lexscope implicit turns on implicit introduction of new lexical variables, i.e. its action is opposite to lexscope explicit.

Back to content

mac

   mac mac-name [ ( lambda-list ) ]
       { [ [declaration]* | documentation-string ]
         body }

mac is translated into CL defmacro.

lambda-list is like one in lambda with few additions. Options &&whole, &&environment and &&body are accepted. Instead of "&&body var", one may write shorter "**var".

mac defines macros.

Back to content

maclet

   maclet [ mac-name [ (lambda-list) ]
              { [ [declaration]* | documentation-string ]
                mac-body } ]*
      { maclet-body }

maclet is translated into CL macrolet.

maclet defines local macros, available for use inside maclet-body. Syntax for macro definition inside maclet is the same as one in mac.

Back to content

meth

   meth [setter] func-name [method-qualifier]* ( specialized-lambda-list )
       { [ [declaration]* | documentation-string ]
         body }

with

   specialized-lambda-list := [ var [==parameter-specializer-name] ]*
                              lambda-list

where lambda-list is like one for lambda, and parameter-specializer-name is either a symbol or eql(expression).

meth is translated into CL defmethod.

meth adds or modifies a method of a generic function. If a generic does not exist, yet, it will be created automatically.

Back to content

package

   package name { [ option { [value]* } ]* }

package is translated into CL's defpackage. Thus, options and their values in package are the same as those in defpackage.

package creates a new package.

Back to content

prog

   prog [ ( [var [=initValue] ]* ) ]
       { [declaration]*
         [ tag | expression ]* }

prog is translated into CL prog.

prog is simulatneously a let, block nil and a tagbody. It is possible to define local lexical variables for use in the prog's body. It is possible to exit prematurely with break. And it is possible to jump between tags with go(tag).

If exit from prog is performed via break, then return value is whatever is given to break. Otherwise, nil is returned.

Back to content

progs

   progs [ ( [var [=initValue] ]* ) ]
       { [declaration]*
         [ tag | expression ]* }

prog is translated into CL prog*.

progs is essentially the same as prog, except that variables are created like in lets.

Back to content

restart

   restart { body }
 [ at [name] [ (lambda-list) ]
       { [ :interactive, interactive-expression | :report, report-expression |
           :test, test-expression ]
          [declaration]*
          [expression]* } ]*

restart is translated into CL restart-case.

lambda-list is the same as one in lambda.

To create an anonymous restart, simply omit the name.

Back to content

return

   return [ from block-name ] [ return-value ]

return is translated into CL return-from. return is supposed to appear only in the bodies of functions or methods. If block-name is omitted, it defaults to the name of the function/method, where return appears. If return-value is omitted, it defaults to nil.

Back to content

struct

   struct name
       {  [slot [ {initValue [,option=value]* } ] ]*  }
    [ options { [option=value]* } ]

struct is translated into CL defstruct. Options for each slot and for structure as a whole are exactly the same as the ones for defstruct.

Back to content

throw

   throw tag [ result ]

throw is translated into CL throw. If result is omitted, it defaults to nil.

Back to content

times

   times ( var, maxValue [,varN [= initValue [-> stepValue] ] ]* )
     [ ( [result-expressions]* ) ]
       { [declaration]*
         [ tag | expression ]* }

times is translated into &tl-times macro.

On every iteration of times loop, var is bound to integers starting from 0, but less than maxValue. Other variables can be created and changed on every iteration, similar to for loop

Back to content

try

   try { try-body }
   finally { final-body }

try-finally is translated into CL unwind-protect.

Back to content

typecase

   typecase (obj)
 [ is (typeN) { bodyN } ]*

typecase is translated into CL typecase.

Back to content

use

   use [ package[:] ] { [ symbol [= synonym] ]* }

use is a TwinLisp's directive.

use directive tells TwinLisp to substitute every occurance of synonym with package[:]:symbol. If synonym is not given, then every occurance of symbol will be substituted with package[:]:symbol.

Back to content

Copyright (c) 2006 Mikalai Birukou.

Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation. Text of this license is provided in COPYING.txt file.

Valid XHTML 1.1! Valid CSS!