Classes and Objected-Oriented Programming
GDLisp, like GDScript, is an object-oriented programming language. GDLisp implements a similar object inheritance mechanism to GDScript, allowing you to define classes which inherit from exactly one superclass and to create instances of those classes.
Classes are declared at the top-level of a module as follows. Classes may not be nested within one another.
(defclass ClassName (ParentClassName)
body ...)
(defclass ClassName (ParentClassName) public-or-private
body ...)
The class name is a symbol literal. By convention, class names in
Godot are written in CamelCase, though this is not a requirement.
The parent class is a symbol literal whose value is a class known at
compile-time. The parent class name may optionally be followed by a
visibility modifier. Class names are
placed in the value namespace, similar to constants and enums.
The parent class name may be omitted by simply replacing it with the
empty list (). In this case, the parent class is assumed to be the
built-in type Reference. For example, the following class
implicitly has parent class Reference.
(defclass MyNewReferenceType ()
body ...)
After the class name, the class body consists of zero or more class declarations. These are somewhat similar to normal module-level declarations, but there are some forms that only make sense inside of classes, and there are some subtle differences between the two, so it’s best to treat the two scopes as distinct.
Macro expansion takes place inside of class declaration scopes, just like it does at the top level of a module.
Inside of classes, there are four valid types of declarations:
defsignal, defconst, defvar, and defn. Additionally,
progn forms can appear in class bodies and behave
identically to the same forms at the top-level.
Class Constants
defconst inside of classes works identically to the defconst
top-level form. It defines a constant inside the class scope, whose
value is known at compile-time. See Constants for more details
on how this declaration type works.
Class constants are considered “static”. That is, it is not necessary
to construct an instance in order to access a class constant. Class
constants can be accessed on the class directly or on an instance,
and the syntax to do so uses access-slot (or, more conveniently,
the : colon syntax sugar). Given the following class, both
Foo:A and (Foo:new):A will evaluate to 42.
(defclass Foo ()
(defconst A 42))
Class Signals
(defsignal signal-name)
(defsignal signal-name (args ...))
Signals are the only values defined in the signal namespace. A signal
is defined by defsignal, followed by the name of the signal, and
then followed by a simple lambda list. If
the argument list is omitted, then it defaults to ().
Instance Variables
(defvar var-name)
(defvar var-name initial-value)
Instance variables are defined using defvar. An instance variable
is a name that exists on instances of the class, not on the class
itself. GDLisp has no concept of “static” instance variables.
Instance variables can be accessed on instances of a class via the
access-slot syntax (more conveniently written using the infix
: operator). Given an instance my-player of type Player,
the health field on this instance can be accessed via
my-player:health (equivalently, (access-slot my-player
health)).
An instance variable may optionally be followed by its initial value.
If provided, the initial value will be set at the very beginning of
the class’ constructor, immediately after calling the superclass
constructor. If not provided, the initial value of the variable shall
be the null () object.
Note
GDLisp, like GDScript, has no notion of static instance variables. All instance variables are scoped to a particular instance.
Initialization Time
By default, instance variables for which an initial value is given are
initialized at object construction time, after the parent _init is
called but before the current class’ _init is executed. It is
often useful to initialize instance variables when a node is first
added to the scene tree. To this end, a defvar for which an
initial value is provided can optionally be succeeded by the
onready symbol.
(defvar var-name initial-value onready)
A variable indicated in this way will have its value set immediately
before the _ready method of the class is invoked, when the node is
added to the scene tree.
Class and Instance Functions
(defn function-name (args ...)
body ...)
Class-level functions are declared similarly to module-level
functions, using the defn keyword, followed by the function name,
then a list of formal arguments, and finally the function body.
Class-level functions take formal arguments as a simple lambda
list, which means functions inside of a class
do not support optional or variable arguments.
A function defined inside of a class is called on instances of the
class, using an access-slot form as the head of an S-expression in
an expression context. That is, given an object foo, the
expression (foo:bar 1 2 3) (or, written out in full,
((access-slot foo bar) 1 2 3)) will invoke the instance function
called bar on the object foo, calling it with three arguments:
1, 2, and 3.
Inside the body of an instance function, the argument names are bound
within a local scope, similar to a module function. Additionally, the
special variable name self is bound to the instance on which the
function was invoked. The body expressions of the function are
evaluated in order, and the final expression is returned. If the
function has no body, then the null () object is returned. Like
with module functions, instance functions can be exited early with the
return special form.
Static Functions
(defn function-name (args ...) static
body ...)
A function may be marked as static by placing the keyword static
keyword after the function’s formal argument list. A static function
can be invoked on either an instance or the class itself using the
: (equivalently, access-slot) forms to call the function. In
either case, a static function behaves like an instance function
except that self is never bound inside the function.
Constructor Functions
(defn _init (args ...)
body ...)
The function called _init is special. This is the function which
will be invoked when a new instance of the class should be constructed
via new. Any arguments passed to the class’ new function will
be forwarded onto _init.
_init takes a constructor lambda list, which permits a special
form of syntax unique to constructors, rather than a simple lambda
list. See Constructor Lambda Lists for details. _init can
never be static. Finally, _init never returns a value. The
return special form may still be used to exit the constructor
early, but its argument will be ignored.
The first expression in the body of a constructor function can be of
the form (super args ...), i.e. a proper list whose first element
is the literal name super. This will cause the constructor
function to invoke the parent class’ constructor with the arguments
given. This must be the first expression in a constructor function. If
an explicit super call is not supplied, then the parent class’
constructor will be called implicitly with no arguments.
Getter and Setter Functions
(defn (get field-name) ()
body ...)
(defn (set field-name) (arg)
body ...)
Rather than a literal symbol, the function name of a defn can be
one of the special forms (get field-name) or (set field-name)
where field-name is an arbitrary literal symbol. These define,
respectively, a getter and a setter function. Getters and setters can
never be static.
A getter function, defined with the name (get field-name), must
take zero arguments. It will be invoked when the corresponding field
(in the value namespace) is accessed on an instance of the class. That
is, a getter method (get health) will be invoked on a class
Player if we have an instance of the class my-player and
attempt to access the field my-player:health.
A setter function, defined with the name (set field-name), must
take exactly one argument. It will be invoked when the corresponding
field is assigned to with the set special form. That is, a
function (set health) will be invoked on a class Player if we
have an instance of the class my-player and write (set
my-player:health some-value). The sole argument to a setter function
is the right-hand side of the set special form. A setter function
never returns a value. Setters can be exited early with the return
special form, but the value returned will be ignored.
Setters and getters for the same field may be defined on the same
class. It is an error to define an instance variable (via defvar)
and a setter or a getter for the same field name on the same class.
Setters and getters are compatible with GDScript, in the sense that attempts to access or set the field from GDScript will also trigger the getter or setter, respectively.
Superclass Calls
Within a non-static instance function, a special form of syntax is available.
(super:method-name args ...)
Attempting to call a method on the literal symbol super will
invoke the method of the given name on the current instance, but
considering only functions defined in the parent class or above.
This syntax only makes sense inside of instance functions in a class.
The behavior is undefined if this super call syntax is used in a
setter, getter, or constructor. super is not a value in the
value namespace, and it is not permitted to assign super (on its
own) to a variable or use it in some way other than the syntax shown.
Main Classes
In GDScript, a single source file maps to a defined class. Functions on a source file are, unless marked static, functions on instances of that class. GDLisp works differently. A GDLisp source file is a module, and it may contain one or more classes, but it is not itself a class. These classes contained in a GDLisp source file will compile to inner classes in the resulting GDScript source file.
However, there are good reasons to have control over this “top-level” class in Godot. Packed scenes will always refer to a file’s top-level class, not to inner classes. So GDLisp provides a mechanism to define a particular class that should be treated as the “main” class.
(defclass ClassName (ParentClassName) main
body ...)
After the class’ parent name and before the class’ body, the symbol
main can be written to indicate that this class is the module’s
“main” class. If your class has a visibility modifier, then the
main modifier can be written before or after the visibility
modifier (though a private main class makes very little sense).
Designating a class as the “main” class does not change how you refer
to this class in GDLisp. It is still a class name defined on the
module in the value namespace, just like any other class, and it will
still be instantiated, imported, and used in the exact same way. The
main designator does affect how the class is compiled, though.
Rather than compiling to an inner class, the main class compiles to
the top-level class of the GDScript file.
There are several limitations.
There can be at most one
mainclass in a file.There must be no conflicts between names defined inside the
mainclass and names defined at the module level. That is, if a constant is defined at the module level, then there must be no constants or instance variables inside the class with the same name (up to normalization). Likewise, if a function or macro is defined at the top-level, then there must be no instance functions (static or otherwise) with the same name (again, up to normalization).
Visibility Inside a Class
A class name can, like most module declarations, be declared
public or private. However, the elements inside of a class
have no visibility modifiers. There is no way to define private fields
or instance functions in GDLisp. Everything defined inside of a class
is presumed public.
Name Normalization Within Classes
Name normalization works slightly differently inside of classes. The rules for how names are normalized within classes are the same as at the module level (see Name Normalization). However, the difference is in how names are resolved.
At the module level, a name must be referred to in the exact same way
as it was defined. That is, if you define a function called
foo-bar, then you must call it as (foo-bar ...). Even though,
at runtime, the resulting GDScript function will be called foo_bar
(with an underscore in place of the dash), GDLisp will not allow you
to call the function as (foo_bar ...).
Inside of classes, the rules are much more lenient, owing to Godot’s dynamic nature. You may access fields or call functions on classes and instances using any name that normalizes to the same name that was used to define the function or field.
As a consequence, you can call built-in GDScript instance functions
using the conventions of GDLisp, so you can get a child node from a
node by writing (parent-node:get-node "ChildNodeName"). This will
normalize to a function call to get_node, which is defined by
Godot.