-
Notifications
You must be signed in to change notification settings - Fork 30
Home
GLISP is a dialect of LISP designed as an embedded extension language for Go. It is implemented in pure Go as a bytecode interpreter. As a result, the interpreter can be compiled or cross-compiled for any platform Go runs on.
GLISP has six types of Atoms: ints, floats, strings, chars, bools, and symbols. The following are different kinds of literal syntax for these atoms.
3 ; an int
-21 ; a negative int
0x41 ; int in hexadecimal
0o755 ; int in octal
0b1110 ; int in binary
4.1 ; a float
-2.3 ; a negative float
1.3e20 ; a float in scientific notation
#c ; the character 'c'
#\n ; the newline character
"asdfsd" ; a string
asdfsd ; a symbol
true ; the "true" boolean
false ; the "false" boolean
Note that semicolons are used for single-line comments. The syntax for symbols
is quite flexible. Any non-whitespace character other than '
or #
can be
used in a symbol.
Lists are just cons-cell lists like in other LISP dialects and are delimited by parentheses
(a-function arg1 arg2
(another-function arg1 arg2))
You can also just describe plain pairs (cons-cells in which the tail is not necessarily a list) using the dotted syntax.
(a . b)
Arrays correspond to Go slices and are delimited by square braces.
[1 2 3 4]
The quote symbol '
indicates that the following expression should be
interpreted literally. This is useful for declaring symbols and lists.
'(1 2 3 4)
'a-symbol
An anonymous function can be declared in GLISP like so
(fn [a b] (+ a b))
A function can be declared with a name using defn
.
(defn add3 [a] (+ a 3))
Note that like in Clojure, the argument list is given in an array instead of a list.
A binding can be added in the current scope using def
. You can also create
a new scope and declare bindings in it using let
or let*
.
(def a 3)
(let [a 3
b 4]
(* a b))
; returns 12
(let* [a 2
b (+ a 1)]
(+ a b))
; returns 5
The difference between let
and let*
is that let
creates bindings all at
once, so you will not be able to access earlier bindings in later bindings.
The let*
form creates bindings one by one, so each binding can access
bindings declared before it.
Functions can be called in the regular way.
(defn add3 [a] (+ a 3))
(add3 2) ; returns 5
They can also be called indirectly using apply.
(apply + [1 2 3]) ; returns 6
(apply + '(1 2 3)) ; same as above
This works exactly the same with anonymous functions
((fn [a b] a) 2 3) ; returns 2
(apply (fn [a b] a) [2 3]) ; same as above
GLISP has only a single conditional statement, cond
.
The syntax is as follows.
(cond
first-condition first-expression
second-condition second-expression
...
default-expression)
The cond
statement will check the conditions in order. If the condition is
true, it will return the result of the corresponding expression. If not, it
will move on the next condition. If none of the conditions are true, it will
return the result of the default expression. The default expression is the
only required portion of this statement. The way to think of this is that the
first condition/expression pair is an if
statement, the second is an
else if
statement, and the default is the else
statement.
GLISP also provides the short-circuit boolean operators and
and or
.
The and
expression will return the first "falsy" sub-expression or, if all
sub-expressions are "truthy", the last sub-expression is returned. The or
expression is the opposite, returning the first "truthy" expression or the
last expression.
The boolean false
, the null value (empty list), the integer 0, and the null
character are considered "falsy". All other values are considered "truthy".
The begin
statement is used to sequence expressions. It will run all
sub-expressions and return the result of the final expression. The top-level,
function bodies, and let-statement bodies have implicit begin
statements.
The following builtin functions are provided by the language runtime.
-
sll
(shift-left logical) -
sra
(shift-right arithmetic) -
srl
(shift-right logical)
bit-and
bit-or
bit-xor
-
bit-not
(one's complement)
not
+
-
*
/
-
mod
(modulo)
<
>
<=
>=
=
not=
zero?
null?
list?
array?
-
number?
(int, char, or float) int?
float?
char?
symbol?
println
print
The array
function can be used to construct an array. It is identical to
the square brace literal syntax.
The make-array
function creates an array of a given length. By default, the
items in the array are intialized to null.
(make-array 3) ; => [() () ()]
(make-array 3 0) ; => [0 0 0]
The aget
function indexes into an array.
(aget [0 1 2] 1) ; returns 1
The aset!
function modifies the value in the array at the given index
(def arr [0 1 2])
(aset! arr 1 3)
; arr should now be [0 3 2]
So yes, arrays are mutable in GLISP.
The list
, cons
, first
, and rest
functions operate the same as in
other LISP dialects. Note, however, that first
and rest
can also work on
arrays.
The sget
function is similar to the aget
function, except it operates on
characters of a string instead of elements of a list.
The append
function can append an expression to the end of an array or
a character onto the end of a list.
(append [0 1] 2) ; => [0 1 2]
(append "ab" #c) ; => "abc"
The concat
function can concatenate two arrays, two strings, or two lists
(concat [0 1] [2 3]) ; => [0 1 2 3]
(concat "ab" "cd") ; => "abcd"
(concat '(1 2) '(3 4)) ; => (1 2 3 4)
The len
function returns the number of elements in an array or number of
characters in a string.