DrawLisp is a dialect of the Lisp family of programming languages. Here is an example program.
; set the color of the background, and the color of the lines and filled areas
(let :clear-color black)
(let :stroke-color white)
(let :fill-color (color 255 50 30))
; opens a 400x400 canvas window
(create-window 400 400)
; draws a rectangle to the middle of the screen
(rect 150 150 100 100)
; draws two crossing lines
(line 150 150 250 250)
(line 150 250 250 150)
The expression (f arg1 arg2 arg3 [etc.])
calls the function f
with the given arguments.
You can exit the interactive shell by entering ;
.
Lexical settings are perhaps the most novel concept implemented in this project. They are certain variable names (currently :fill-color
, :stroke-color
, and :clear-color
) are read by the evaluator to determine how to draw a given shape. Importantly, these definitions are forgotten at the end of the block of code in which they are defined. Consider the following example:
(create-window)
(let :fill-color black)
(begin ; defines a new block of code
(let :fill-color white)
(rect 100 100 100 100)) ; draws a white square
(rect 300 300 100 100) ; draws a black square
These lexical settings replace traditional setter functions (like fill
and stroke
in Processing, for example). Importantly, this means that it's impossible for a function call to change any lexical settings in the current block.
These are notes for this tool which I don't want to leave undocumented but I also don't know how to explain to the uninitiated.
- In the interactive shell, you can omit the top level parens.
- DrawLisp supports
.
pair syntax. Cons pairs do not need to be well-formed lists. - DrawLisp is early-binding with variables.
- DrawLisp is lexically scoped.
- Recursion is vaguely supported but requires using a fixed-point combinator, but is not encouraged as it will eventually cause an unrecoverable stack overflow in the evaluator.
- Whether a builtin function is a lambda or a primitive is an implementation detail, and users should not depend on this difference.
List - Either ()
, or a value x
of type cons
such that (cdr x)
is a list.
true
: The truthy value.false
: The falsy value.white
: The color#ffffff
.black
: The color#000000
.
An argument surrounded by square brackets (e.g. [val]
) is optional. An Argument surrounded by parentheses (e.g. (val1 val2 val3)
) must be a list of that many elements. An argument prepended by two periods (e.g. ..vals
) is a list of zero or more arguments (a.k.a. variadic arguments).
(quote arg)
Returns arg
without evaluating it. The shorthand 'arg
is preferred.
(print value)
Prints the value to the interactive shell.
(color r g b)
Returns a value of type color
with the given red, green, and blue values, clamped to the range [0..255]
. r
, g
, and b
must be of type int
.
(create-window [width height])
Create a canvas window of a given width and height, destroying any previously created window. width
and height
default to 500
. width
and height
must be of type int
.
(draw)
Manually tells the canvas to rerender to display current changes. Only useful within computationally long loops.
(clear)
Clears the canvas with color :clear-color
.
(point x y)
Draws a pixel with color :stroke-color
at the given xy-position. x
and y
must be of type int
.
(point x1 y1 x2 y2)
Draws a line with color :stroke-color
between the two given xy-positions. x1
, y1
, x2
, and y2
must be of type int
.
(rect x y w h)
Draws a rectangle filled with color :fill-color
and bordered with color :stroke-color
. x
and y
give the xy-position of the top left corner of the rectangle, and w
and h
give the width and height of the rectangle. x
, y
, w
, and h
must be of type int
.
(destroy-window)
Close the current canvas window, if one exists.
(atom? x)
returnstrue
ifx
is not of typecons
.(nil? x)
returnstrue
ifx
is the value()
.(cons? x)
returnstrue
ifx
is of typecons
.(int? x)
returnstrue
ifx
is of typeint
.(bool? x)
returnstrue
ifx
is of typebool
.(symbol? x)
returnstrue
ifx
is of typesymbol
.(primitive? x)
returnstrue
ifx
is of typeprimitive
.(lambda? x)
returnstrue
ifx
is of typelambda
.(function? x)
returnstrue
ifx
is of typeprimitive
orlambda
.(color? x)
returnstrue
ifx
is of typecolor
.
(+ ..elements)
returns the sum of a list of numbers.(* ..elements)
returns the product of a list of numbers.(- n)
returns the negative value ofn
.(- n ..elements)
returns the difference betweenn
and the sum ofelements
.elements
must be non-empty.(/ n ..elements)
returns the quotient betweenn
andelements
, left-associative, rounding down.elements
must be non-empty. Division by zero is not allowed.(% n ..elements)
returns the modulo betweenn
andelements
, left-associative.elements
must be non-empty. Modulo by zero is not allowed.
(eq? x y)
Checks if x
and y
are equal. If x
and y
are of type cons
, this function instead returns false
.
(neq? x y)
Returns the negation of (eq? x y)
.
(not b)
Returns the negation of b
. b
must be of type bool
.
(bool->int b)
Returns 1 if b
is true
and 0 if b
is false
. b
must be of type bool
.
(int->bool n)
Returns false
if n
equals 0, and true
otherwise. n
must be of type int
.
(truthy? x)
Returns false
if x
is ()
, false
, or 0
, and true
otherwise.
(cond ..(condition ..body))
Evaluates each condition
in order. If a condition
yields true
, the corresponding body
will be evaluated and yielded. Otherwise, this function yields ()
. Each condition
must yield a value of type bool
, and each body
must be non-empty.
(if condition true-body [false-body])
If condition
evaluates to true
, this function evaluates and yields true-body
. Otherwise, it evaluates and yields false-body
. false-body
defaults to ()
.
(begin ..body)
Evaluates every element of body
. body
defaults to ()
.
(lambda args ..body)
Yields a function which will evaluate body
when called with the given args
. args
must either be an identifier or a list of identifiers. body
must be non-empty.
(let variable ..body)
Declares a new variable variable
with the yielded value of the evaluated body
. If an existing variable with the same name is already defined, it will be shadowed for the duration of the new variable's scope. variable
must be a symbol. body
must be non-empty.
(car x)
Returns the first element of x
. x
must be of type cons
.
(cdr x)
Returns the remaining elements of x
. x
must be of type cons
.
(cons a b)
Returns a value of type cons
such that (car (cons a b))
equals a
and (cdr (cons a b))
equals b
.
(range [start] end [step])
Returns a list of numbers between start
and end
at increments step
, not including end
. start
is defaulted to 0
and step
is defaulted to 1
. If start > end
, they will implicitly be swapped, e.g.
(range n)
wheren
is positive will yield a list of numbers in the range[0..n)
.(range n)
wheren
is negative will yield a list of numbers in the range[n..0)
.(range n m)
wheren < m
will yield a list of numbers in the range[n..m)
.(range n m)
wheren > m
will yield a list of numbers in the range[m..n)
.
start
, end
, and step
must be of type int
. step
must not be 0
unless start
and end
are equal.
(map fn list)
Returns a new list such that each element of the returned list is the value of function fn
applied to each element of list
, preserving order. fn
must be a function which can accept one argument. list
must be a list.
(filter fn list)
Returns a new list that only contains elements of list
where applying fn
to that element returns true
, preserving order. fn
must be a function which can accept one argument and returns a value of type bool
. list
must be a list.
(fold fn init list)
For every element x
in list
, sets init
to (fn init x)
, returning the final value of init
. fn
must be a function which can accept two arguments. list
must be a list.
(reduce fn list)
Like fold
, but uses the first element of list
as the initial value folded with the remaining elements of list
. Equivalent to (fold fn (car list) (cdr list))
. fn
must be a function which can accept two arguments. list
must be a non-empty list.