Functions and Modules
Now that we have some basic tools to do calculations and store variables in GDLisp, it’s time to talk about how to write independent GDLisp source files.
Modules
This is one of the key places that GDLisp differs from GDScript, and it’s important to understand the distinction. In GDScript, every source file you write represents a GDScript resource, effectively a class in Python terminology. A GDLisp source file does not automatically produce an instantiable class. There are ways to declare a class, and we will discuss those in the next section. But a GDLisp source file has a one-to-one correspondence with a module. A module is a collection of named classes, functions, and constants that can be imported and used in other modules.
Functions
We’ve already seen how to declare local functions inside of a block of
code, either as a reified value with lambda or as a scoped local
name with flet or labels. At the top-level of a module, named
functions are declared with the defn declaration form.
(defn function-name (args ...)
body ...)
GDLisp functions are, by default, public to the module, which means
other modules are free to import and use the function. Private
module-level functions can be declared with the private modifier:
(defn function-name (args ...) private
body ...)
A function declared in this way can be used freely inside the current module but cannot be imported or used in another module.
To call a function, simply use its name as the first symbol of an S-expression:
(function-name arg1 arg2 arg3 ...)
The formal argument list of a function supports optional and variadic
arguments. To declare a function with optional arguments, use the
&opt directive:
(defn compare-strings (a b &opt case-sensitive)
(if case-sensitive
(a:casecmp_to b)
(a:nocasecmp_to b)))
This declares a function compare-strings that accepts two or three
arguments. If the third argument case-sensitive is not supplied,
it defaults to () (which is falsy).
Variadic arguments can be declared with &rest or &arr. The
former groups all extra arguments into a GDLisp list, and the latter
groups all extra arguments into a Godot array.
(defn make-array (&arr args)
args)
(make-array 1 2 3 4 5) ; Produces the array [1 2 3 4 5]
GDLisp standalone function calls always have their argument count validated at compile-time. If a function is called with the wrong number of arguments, the GDLisp compiler will emit an error.
Note that the same argument directives &opt, &rest, and
&arr can be applied to local functions declared with flet,
labels, or lambda. The only difference is that in the case of
lambda, the function is reified into an object, so the argument
validation happens at runtime rather than compile-time.
Constants
Constants are declared with defconst. Like functions, constants
are public by default but can be made private to the module.
Examples:
(defconst DAYS_IN_YEAR 365)
(defconst DEFAULT_PLAYER_NAME "Steve")
(defconst EPOCH 1970 private) ; Private constant
Important
Unlike GDScript, GDLisp does not use constants to load resources and other scripts. GDLisp has a dedicated import syntax for loading other files, whether those files are resources, source files, or packed scenes.
Enumerations
Enumerations are declared with defenum.
(defenum Color RED YELLOW BLUE)
This defines an enumeration constant called Color with three
elements: Color:RED, Color:GREEN, and Color:BLUE. Note
that we refer to the elements of an enumeration with a colon :, as
opposed to a dot . like we would in GDScript. We’ll see this
syntax again when we discuss classes.
Enum elements can optionally be given specific numerical values. For example:
(defenum NUMBERS
(ONE 1)
(TEN 10)
(ONE_HUNDRED 100))
As with constants and functions, an enumeration can be made private to the enclosing module.
(defenum NUMBERS private
(ONE 1)
(TEN 10)
(ONE_HUNDRED 100))
Importing Modules
Modules are of little use if they can’t be imported and reused in
other modules. In GDScript, we load other resources, including other
GDScript source files, with the preload function, as follows.
const MyScript = preload("res://MyScript.gd")
const MySprite = preload("res://MySprite.png")
const MyScene = preload("res://MyScene.tscn")
This is still technically possible to do in GDLisp, but it’s not
idiomatic, and it complicates macro expansion. GDLisp has a special,
and very versatile, use directive that’s designed for importing
data from other files.
Importing Resources
Importing non-GDLisp resources is simple.
(use "res://MySprite.png" as MySprite)
(use "res://MyScene.png" as MyScene)
After the keyword use, we write the path of the resource, using
the same res:// syntax as a GDScript preload. Then we specify
how we’d like to name the resource in the current scope. The above
snippet defines two constants in the current scope: MySprite and
MyScene.
Note that names imported into a module are not transitively
re-exported, so while our hypothetical module above has access to the
names MySprite and MyScene, other modules that import our
module cannot import those names from it.
If you don’t specify an alias for the import, one will be chosen for you based on the name of the resource. The following two lines are equivalent:
(use "res://Example/MySprite.png" as MySprite)
(use "res://Example/MySprite.png")
This is also how source files written in GDScript are imported into GDLisp. The entire GDScript resource is imported as a single name which the GDLisp module can access.
Importing Other GDLisp Modules
Importing GDLisp modules works a bit differently. A GDLisp module is a collection of functions and other names, not just a single runtime resource. If we use the same import syntax as above:
(use "res://MyModule.lisp" as MyModule)
Then every public name from the module MyModule will be imported
into the current module, with the prefix MyModule added.
Concretely, suppose MyModule.lisp contained these names:
(defn a-function () ...)
(defn another-function () ...)
(defconst MY_FIRST_CONSTANT 1)
(defconst MY_SECOND_CONSTANT 2 private)
Then the use directive given above would have the following
effects:
The function
a-functionis imported into the current scope, under the nameMyModule/a-function.The function
another-functionis imported into the current scope, under the nameMyModule/another-function.The constant
MY_FIRST_CONSTANTis imported into the current scope, under the nameMyModule/MY_FIRST_CONSTANT.The constant
MY_SECOND_CONSTANTis not imported, as the name is private and not visible to external modules.
As with non-GDLisp resources, if an alias is not specified, then one will be chosen for you based on the full path of the module.
Explicit Imports
Instead of providing a prefix to insert before all names from a module, you may instead specify explicitly which names you want to import (without a prefix) from the module.
(use "res://MyModule.lisp" (a-function MY_FIRST_CONSTANT))
In this example, the function a-function and the constant
MY_FIRST_CONSTANT will be imported as-is into the current scope
(with no prefix), and another-function will not be imported at
all.
Aliases can be provided, for any explicit imports.
(use "res://MyModule.lisp" ((a-function as an-external-function)
(MY_FIRST_CONSTANT as CONSTANT)))
Finally, if you want to import all of the (public) names from a
module, without any prefix, you may replace the explicit import list
with the word open.
(use "res://MyModule.lisp" open)
GDLisp.lisp
If you’re compiling your own GDLisp modules, there’s one more
dependency you need to know about. All of the functions and constants
that are defined in GDLisp are provided by a support library, called
GDLisp.lisp. This support library is included in the GDLisp
package you downloaded and is automatically compiled into
GDLisp.gd when you build the GDLisp compiler. GDLisp.gd must
be included as an autoloaded singleton (with the name GDLisp) in
any project that uses GDLisp code.
To include this in your project, simply copy the GDLisp.gd file
into your project’s root directory and add it in the autoloads list
under your project settings, ensuring that the name of the autoload is
GDLisp (this is case-sensitive). All GDLisp modules implicitly
assume that this autoload is available, so you must include it in
any project that includes GDLisp code.