8.16
GDLisp is a lisp dialect that compiles to GDScript.
Produces (when run):
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.
Analogous to the equivalent bindings from racket/base.
These have transformer bindings that prohibit them from being used outside of their context.
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.