GDLisp
1 Overview
1.1 The GDLisp script
1.2 Bindings
or
and
if
when
class-name
extends
export
match
var
signal
func
static
class
..
:
:  =
#%gdscript
1.3 Caveats
1.3.1 Name mangling
8.12

GDLisp🔗ℹ

 (require gdlisp) package: gdlisp

GDLisp is a lisp dialect that compiles to GDScript.

#lang gdlisp
(print "Hello, world!")

Produces (when run):

print("Hello, world!")

1 Overview🔗ℹ

GDLisp is a way to write script files for the Godot engine without actually writing (directly) in the scripting language it provides.

The main benefits this provides is that you can write macros and (hopefully) enter into Lisp game jams. Also you don’t have to deal with semantic whitespace.

1.1 The GDLisp script🔗ℹ

A GDLisp script has the following grammar:

  program = top-level-stmt ...
     
  top-level-stmt = escape-stmt
  | top-class-stmt
     
  escape-stmt = (require require-spec ...)
  | (module module-body ...)
     
  top-class-stmt = (class-name name)
  | class-stmt
     
  class-stmt = (extends parent-name)
  | stmt

Where escape-stmts are put directly into the surrounding module, and top-class-stmts are interpreted as a GDLisp class.

Statements and expressions have the following grammar:

  stmt = (define maybe-var-prefix binding)
  | (var maybe-var-prefix binding)
  | 
(define maybe-static (name-id func-param ...)
  body-expr ...+)
  | 
(func maybe-static (name-id func-param ...)
  body-expr ...+)
  | 
(class name-id
  class-stmt ...)
  | (signal name-id)
  | (begin stmt ...)
  | stmt-expr
     
  func-param = name-id
  | [binding]
     
  maybe-var-prefix = 
  | export
  | (export export-arg ...)
     
  maybe-static = 
  | static
     
  binding = name-id
  | name-id : type-id
  | name-id : type-id default-expr
  | name-id := default-expr
     
  expr = (begin body-expr ...)
  | variable-id
  | (.-field-name target-expr)
  | (.method-name target-expr method-arg-expr ...)
  | (function-expr function-arg-expr ...)
  | literal-expr
  | special-expr
     
  literal-expr = [list-element-expr ...]
  | {kv-pair ...}
     
  special-expr = cond-expr
  | let-expr
  | recur-expr
  | match-expr
  | for-expr
     
  kv-pair = key-expr val-expr
     
  body-expr = (define binding)
  | expr
     
  cond-expr = 
(cond
  [pred-expr then-body-expr ...+] ...
  maybe-else-clause)
     
  maybe-else-clause = 
  | [else else-body-expr ...+]
     
  let-expr = 
(let maybe-let-name
    (binding ...)
  body-expr ...+)
     
  maybe-let-name = 
  | name-id
     
  recur-expr = (recur recur-target-id arg-expr ...)
     
  match-expr = 
(match target-expr
  [match-pattern body-expr ...+]
  ...)
     
  match-pattern = (or subpattern ...) ; where subpattern has no (var _) patterns
  | subpattern
     
  subpattern = constant-pattern
  | variable-id
  | _
  | (var name-id)
  | [subpattern ...]
  | [subpattern ... ..]
  | {kv-pattern ...}
  | {kv-pattern ... ..}
     
  kv-pattern = constant-pattern subpattern
     
  constant-pattern = constant-number
  | constant-string
  | constant-boolean
     
  for-expr = 
(for ([name-id target-expr])
  body-expr ...+)

Syntax transformers are expanded (roughly) as usual.

1.2 Bindings🔗ℹ

syntax

or

syntax

and

syntax

if

syntax

when

Analogous to the equivalent bindings from racket/base.

syntax

class-name

syntax

extends

syntax

export

syntax

match

syntax

var

syntax

signal

syntax

func

syntax

static

syntax

class

syntax

..

syntax

:

syntax

:=

syntax

#%gdscript

These have transformer bindings that prohibit them from being used outside of their context.

1.3 Caveats🔗ℹ

There are a few caveats and things to note about GDLisp.

1.3.1 Name mangling🔗ℹ

Racket identifiers used as variables are mangled before being emitted as GDScript, so they are valid GDScript identifiers. Most notably, hyphens ("-") are converted to underscores ("_"). Names that mangle to the same identifier will conflict. This does also mean that macros which introduce variable bindings are unhygienic. Use (gensym) if you need a name which doesn’t conflict.