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
- Identifiers and Operators
- Code Expressions and Blocks
- Comments
- Operator Methods
- Scope of Variables
- Defining Functions and Macros
- Calling Functions and Macros
- Calling Methods and Accessing Objects' Slots
- Shortcuts
- Common Lisp Forms (S-expressions)
- Mixing with Common Lisp code
- Standard Containers
- Control Flow Structures
- IFs
- Loops
- Block and Prog
- Dynamic Exits
- Structs and Classes
- Definition and Use of Packages
- TwinLisp's block structures and directives
- block
- break
- case
- catch
- class
- comcase
- cond
- def
- defgen
- do
- dos
- flet
- for
- gfun
- glabels
- glet
- global
- handle
- if
- inside
- labels
- lambda
- let
- lets
- lexscope
- mac
- maclet
- meth
- package
- prog
- progs
- restart
- return
- struct
- throw
- times
- try
- typecase
- use
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))) >>>
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 >>>
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
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
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:
- Unary + and - are defined for numbers and have a usual mathematical sense.
>>> +3 + -2 1
- not (!) acts like logical not, except when it is applied to a bit-vector. Then it acts as a bitwise not.
>>> not nil T >>> not #*001101 #*110010
- ** is an exponentiation operator for numbers. x**y is the same as expt(x,y).
>>> 2**8 256
- * and / are multiplication and division for numbers.
>>> 9*3 27 >>> 27 / 2.0 13.5
- % has a sense of whole division for integers
>>> 27 % 2 13
and it is used as a format operator for strings>>> "~D+~D=~D" % [2,2,4] "2+2=4" >>> "Year ~D" % 2006 "Year 2006"
- Binary + adds numbers
>>> 2+2 4
it also concatenates vectors, lists and strings (the resultant structure is not shared with initial ones)>>> [1,2,"a"] + ["b","c"] #(1 2 "a" "b" "c") >>> ~[1,2,"a"] + ~["b","c"] (1 2 "a" "b" "c") >>> "abc" + "def" "abcdef" >>> "abc" + #\D "abcD" >>> #\A + "bc" "Abc"
- Binary - subtractes numbers
>>> 9-5 4
- Comparison operators <, >, <= and >= are definted for numbers, characters and strings, and have exactly the same sense as corresponding comparisons in Common Lisp
>>> #c(1,0) < 1.1 T >>> #\a > #\b NIL >>> "ab" <= "bb" 0
- == is an equality operator. It compares numbers properly
>>> #c(1,0) == 1 T
it compares characters>>> #\a == #\A NIL
it compares strings in a case-sensitive manner>>> "abc" == "aBc" NIL
simple containers (vectors, lists and hash-tables) are compared by their content>>> [] == [] T >>> [1] == [2,3] NIL >>> ~[2,3,["a","b"]] == ~[2,3,["a","b"]] T >>> {"a"->1, "b"->2} == {"a"->1, "b"->2} T
in all other cases, equality is done as by Common Lisp function eq()>>> [] == ~[] NIL
- != is an inequality operator. For most of the cases it is a negation of an equality.
>>> #\a != #\A T
It could've been implemented as a macro, but, potentially, there might be cases, where it is simplier to write an inequality method and define equality as a negation of inequality. - and (&), xor (^) and or (|) are respective logical operations except when applied to bit-vectors, in which case they are bitwise operations
>>> 1 and nil NIL >>> 1 xor nil T >>> nil or 2 2 >>> #*000111 & #*001011 #*000011 >>> #*000111 ^ #*001011 #*001100 >>> #*000111 | #*001011 #*001111
- << is an insertion operator. It inserts its right hand side operand into its left hand side operand, and returns left hand side. This operation is defined on vectors, lists and streams.
>>> a=[] #() >>> eq((a << 1 << 2 << "a"),a) T >>> a #(1 2 "a") >>> eq(cout() << "string" << #\Space << #\A << #\Newline ... cout()) string A T
- @ is an operand for use in returning and accepting multiple values
>>> a @ b = floor(9,2) 4 ; 1 >>> "a=~D, b=~D" % [a,b] "a=4, b=1"
- = is an assignment operator. It is translated into macro setf. So, entire machinary of generalized variables is accessible through use of =.
>>> a = {"a"->0, "b"->0} #S(HASH-TABLE EQUAL ("b" . 0) ("a" . 0)) >>> a["a"] = 1 1 >>> a #S(HASH-TABLE EQUAL ("b" . 0) ("a" . 1))
- +=, -=, *= and /= are interpretered as macros that perform the first operation (+, -, * or /) between left and right operands, and assign result of the operation to the left side.
>>> a = 0 0 >>> a += 5 5 >>> a 5
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.
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))
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
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)
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.
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.
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.
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:
- index(sequence,obj) -- return an index of a first occurance of obj in a sequence. To get correct object, it uses TL's equality (==). nil is return if obj is not present in the sequence.
>>> lst.index("a") 2
- count(sequence,obj) -- returns a number of times obj appears in the sequence.
>>> lst.count("c") 0
- append(sequence,*elems) -- is defined for lists and vectors. Append destructively appends a given sequence with elems.
>>> lst (1 2 "a" #\b) >>> lst.append(34,78,~[],[]) NIL >>> lst (1 2 "a" #\b 34 78 NIL #())
- extend(sequence,*seqs) -- is defined for list and vectors. Extend destructively appends a given sequence with elements from given sequences (seqs).
>>> lst.extend(~[1,2],~[3,4],[5,6]) NIL >>> lst (1 2 "a" #\b 34 78 NIL #() 1 2 3 4 5 6)
- insert(sequence,index,obj) -- is defined for list and vectors. Destructively inserts obj into position given by index.
>>> lst.insert(5,"Bob") NIL >>> lst (1 2 "a" #\b 34 "Bob" 78 NIL #() 1 2 3 4 5 6)
- pop(container [,index]) -- is defined for list, vectors and hash-tables. Returns an element named by index and destructively removes it from a container.
>>> lst.pop(5) "Bob" >>> lst (1 2 "a" #\b 34 78 NIL #() 1 2 3 4 5 6) >>> lst.pop() 6 >>> lst (1 2 "a" #\b 34 78 NIL #() 1 2 3 4 5)
- remove(container,obj) -- is defined for list and vectors. Returns an index of a first occurance of an obj in the sequence and destructively removes it.
>>> lst.remove(nil) 6 >>> lst (1 2 "a" #\b 34 78 #() 1 2 3 4 5)
- iter(container [,step]) -- is defined for list, vectors and hash-tables. Returns an iterator. This method is implicitly used in the for loop.
>>> for (elem,lst) { cout() << "~A - " % ~[elem] } 1 - 2 - a - b - 34 - 78 - #() - 1 - 2 - 3 - 4 - 5 - NIL >>> for (elem,lst.iter(2)) { cout() << "~A - " % ~[elem] } 2 - b - 78 - 1 - 3 - 5 - NIL
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
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.
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.
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.
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 }
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.
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.
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.
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.
block
block [ name ] { body }
block is translated into CL block. If name is omitted, block will be named nil.
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.
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.
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.
comcase
comcase (expression) [ is (value1N [,valueMN]*) { bodyN } ]* [ else { else-body } ]
comcase is translated into CL case.
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.
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)".
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.
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.
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*.
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.
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.
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.
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.
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
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.
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.
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.
inside
inside pack-name
inside is translated into CL in-package.
inside switches current package to pack-name. You get "inside" of this package.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
throw
throw tag [ result ]
throw is translated into CL throw. If result is omitted, it defaults to nil.
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
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.