#top gdjn
allow use of Godot 4 with a [^civ civilized] scripting language, viz. [>janet Janet].

	janet: https://janet-lang.org
	godot: https://godotengine.org

%toc

@civ {
	to meet the bare minimum standards necessary to qualify as [!civilized] to my mind, a language must:
	* support compilation, whether to bytecode or machine code (excludes gdscript)
	* parse into an AST (excludes bash, php (or used to))
	* support AST-rewriting macros (excludes everything outside the lisp family except nim and maybe rust)
	* use real syntax, not whitespace-based kludges (excludes python, gdscript inter alia)
	* provide a proper module system (excludes C)
	* provide for hygienic namespacing (excludes C, gdscript)
	* provide simple, cheap aggregate types (excludes gdscript, java, maybe python depending on how you view tuples)
	so, janet and fennel are about the only game in town.
}


## building

###dep dependencies
* [*build-time]
** GNU make
** git 
** [>top.janet janet] (automatically downloaded and built in [`ext/janet] by default)
*** CLI binary [`janet] (used to run codegen scripts)
*** linked with static library [`libjanet.a] using header [`janet.h]
** [>top.godot godot] ≥ 4.3, or the following files generated by godot:
*** [`gen/extension_api.json]
*** [`gen/gdextension_interface.h]
* [*runtime]
** godot ≥ 4.3

## license
the content of this repository is made available under the terms of the [>agpl GNU AGPLv3] except where otherwise noted. the full legally-binding english-language text of this license may be found in the root of this repository in the file [`license.en].
	agpl: https://www.gnu.org/licenses/agpl-3.0.txt

## about
gdscript is a [>anti-gdscriptische-aktion gibbering aberration] from beyond the stars; this is known. however, as a poor fool determined to write a video game, using godot no less, i resolved to ameliorate my suffering by binding a better language to the engine. such is the depth of my madness.

"it'll be simple," i told myself. "i've poked at gdextension before; i know it's a nightmare from the sewers of R'lyeh but i've already paid the toll in blood and pain to wrest loose its secrets. i have pretty good sense of what i'm doing this time. janet is well suited to this. [!wgah'nagl fhtagn]. ｉｔ　ｗｉｌｌ　ｂｅ　ｅａｓｙ"

yes, i was on drugs when i made this decision. i still am

a week or so later, i found myself implementing a new domain-specific IDL from scratch because that was less work than directly using the GDExtension api. the C side of GDExtension, it must be understood, was not designed for humans to use. it was intended as a scaffolding for godot-cpp, and only exists because C++ does not have a [^abi standardized ABI] -- C++ binaries can only talk to other C++ binaries by using C as a gobetween. no, this is not as crazy as it sounds. it's [!crazier.]

	abi: okay yes in reality it absolutely fucking does, everyone uses the same gods-damned amd one, in actual materially existing reality there is no interop problem at least on x86, but because we haven't ritually enshrined this model at ISO and pacified its marauding [!aramitama] with burnt offerings of motor oil and obsolete DRAM chips, we cover our eyes and pretend we cannot see what is sitting right in front of us. i love too compute dont u

so GDExtension is designed (if i may use the word liberally) with certain notions fixed firmly in mind. chief among them is that the actual C-level calls will be thoroughly ensconced in a suffocating buffer of template hackery, which will employ dark arts and crafts to generate gobs and gobs of impenetrable C at compile time. in fact, GDExtension is so designed that it is basically impossible to use it directly without a code generation layer. unwilling as i was to use C++ (it's against my religion), i wrote my layer from scratch, in janet.

this was not as bad as it could have been. janet has something [^deficient magnificent] in its stdlib that every language should have: a PEG module. the resulting program, ["tool/class-compile.janet] is under 1k lines! just barely. this tool is fired off by the makefile for the ["src/*.gcd] files, generating a header file ["gen/*.h] and an object file ["out/*.o] that implements the class described in the file. the care and feeding of this file format is described in the [>gcd GCD section].

	deficient: it is deficient in one particular way: it only operates over bytestrings. so you can use a PEG to parse raw text, or you can use it to implement a lexer, but you can't have both a PEG lexer and parser, which is really fucking dumb and makes things like dealing with whitespace far more painful than it needs to be.)

##obj object model
godot uses a strongly javalike OOP system, which translates poorly to Janet's prototype-based OO. gdjn therefore uses the following convention to translate between the two.

class definitions are represented by janet environments. any environment with the proper definitions and annotations can be treated as a class, whether from janet code or from GDScript. environments that do not define a class are treated as simple janet modules; their functionality is exported to GDScript by representing a janet module as an abstract class with only static members.

the engine/editor executes the "compile-time" phase of a janet script once it is finished loading or saving. it is the responsibility of the compile-time thunk to define the runtime behavior of the class by populating the environment with the proper bindings. 

unadorned immutable bindings are treated as constants. values with the [":field type] annotation are treated as object fields. when a new instance is created, space is allocated for a field of the appropriate type, and initialized to the value of the binding. public [`var] mutable bindings are treated as static variables.

~~~[janet] example bindings
(def name "Lisuan") # constant string
(def name :prop "Lisuan") # field of type string, initializes to "Lisuan"
(def name {:prop :variant} "Lisuan") field of type variant
(def- secret "swordfish") # private constant
(def- secret :prop "swordfish") # private string field
(var count 0) # static variable of type int
(var- count 0) # private static variable of type int
~~~

unadorned functions are treated as static functions.

private functions (those declared with [`def-]) are available only within the class implementation. they are not exported as methods.

functions with the annotation [":method] are treated as methods. when invoked, they are passed a [`self]-reference as their first argument.

function type signatures can be specified with the annotation ["{:method {:sig [ty name ...] :ret ret-type}}].

tables with the annotation [":subclass] are treated as environment tables specifying inner classes. the macro [`defclass] should generally be used to maintain uniform syntax between outer and inner classes, e.g.

~~~[janet]
(use-classes Object RefCounted)
(declare outerClass :is Object)
(def val 10)
(prop t :float 0)
(conf v :vec3 0 0 0)
(data x :packed-byte-array [])
(defclass innerClass :is RefCounted
	(def val 2))

# equivalent to
(use core) # implicit
(def Object (prim/load-class :Object))
(def RefCounted (prim/load-class :RefCounted))
(def @id :outerClass)
(def @inherit Object)
(def val 10)
(def t {:prop :float} 0)
(def v {:prop :vector3} :export 0)
(def x {:prop :packed-byte-array} :export-storage [])
(def innerClass (do
	(def @inherit RefCounted)
	(let [env (table/setproto @{} (curenv))]
		(eval '(do
			(def val 2))
		env)
	env)))
~~~

since the annotations are somewhat verbose, macros are provided to automate the process. note that while the subclass mechanism looks exceptionally tricksy and aggressively dynamic, all of the class definition code is executed completely at compile-time, so there is no runtime cost to defining a subclass rather than a top-level class.

+ janet + gdscript
| ["(def count 10)] | ["const count := 10]
| ["(def count {:field int} 10)] | ["var count: int = 10]
| ["(defn open [path] ...)] | ["static func open(path: Variant) ...]
| ["(defn open {:takes [:string] :gives :int} [path] ...)] | ["static func open(path: String) -> int:  ...]
| ["(defn close :method [me] ...)] |  func close() -> void: ...


##gcd GCD language
GCD is a simple IDL which is translated into [^complex much more complicated C]. it's designed to make writing GDExtension classes as close to the native GDScript experience as possible, without the syntactic hanging offenses. you define the structure of the class using GCD, and write inline C to implement your functions. linemarkers are emitted properly so when you inevitably fuck it up, the compiler will be able to apportion the blame properly.

	complex: the generated implementation code is roughly 24x longer than the input file

the C "lexing" is pretty robust, and as long as you aren't doing horrible shit with the preprocessor (on the order of ["#define fi }]) it shouldn't choke on anything you write. if you [!are] doing horrible shit with the preprocessor however, [!you deserve everything you get].

["tool/class-compile.janet] accepts the following (nested) forms:

	block: \{ ··· [$[#1]] ··· }
	c-block:  {block inline C}
	scope: {block inner scope}

* [`(* ... *)] masks out a comment. these nest properly. normal C comments in a C block will be output with the rest of the text; gcd comments will be elided everywhere.
* [`(- ... -)] marks out inline documentation. this is not a comment! it is a syntactical element, allowed only in certain places (before a line item definition, after a return type, at the end of an argument specifier before the terminating comma/semicolon, or after an enum item). javadoc-style doc comments will be generated for the generated C, and it is my hope to also integrate this with godot's online API documentation system, which i will grudgingly acknowledge is pretty fricken neat.
* [`class [$name] (is|extends) [$base] {scope};] define a class [$name] inheriting from [$base]. use [`is] when inheriting from a godot native class; use [`extends] when inheriting from a gdextension class [!defined in your program] (inheriting from foreign gdextension classes will not work as expected either way). the necessity of this distinction is unfortunate; godot's virtual method implementation is hilariously broken so we have to manually hack around that particular cesspit of incompetence
* [`(fn | impl) [$name]([$arg-type] [$arg], ...) -> [$return-type] {c-block};] defines a function. use fn for normal static-dispatch functions; use impl for virtual overrides. these are implemented in bizarrely diverging ways but you don't have to care about that, because i did it for you. [!you owe me.]
** all arguments should be available by name as the proper godot type; e.g. [`string-name] becomes a [`gd_stringName].
** a pointer to the C-side storage struct for this class can be named by the token [`me]. if you need the godot object, write [`me -> self].
** for void functions, omit the [`-> [$return-type]] clause.
* [`var [$c-type] as [$gd-type]: [$v1], [$v2], ...;] defines a public class field (or global, at unit scope). [$c-type] is the actual storage type of the object; omit it to have a sensible choice picked for you based on [$gd-type]. [$gd-type] is the godot type that this field will be presented as.
* [`var [$c-type]: [$v1], [$v2], ...;] defines a private class field, accessible only to C.
** note that [*you cannot use intializers here] (yet, anyway); defining a reasonable syntax for a type that has to translate between C and the Godot type system is a decidedly nontrivial endeavor, and would probably have been more complicated than the rest of the syntax combined. for now, initialize your variables in [`new {c-block}]. 
* [`(import | use) <[$header]>;] transcludes a C system include into the unit. [`import] is the equivalent of using [`#include] in a header file; [`use] is the equivalent of using it in an implementation file.
* [`(import | use) \"[$header]\";] transcludes a local C file into the unit.
* [`(import | use) {c-block};] dumps a block of C code into the header or implementation. use this to e.g. define utility functions.
* [`new {c-block};] defines the class constructor
* [`del {c-block};] defines the class destructor. remember that in godot-land refcounting is used, so sane RAII is kind of out of the question unless you're imposing manual memory management on your poor end users
* [*godot types]: these are named using lisp-style tokens instead of the normal godot camelcase, both because i prefer it immensely and because it simplifies the more aggravating aspects of generating appropriate C from this trainwreck.
** primitive types: [`string], [`string-name], [`int], [`float], [`packed-float64-array] etc.
** container types: [`array], [`array\[[$prim-type]\]], [`dictionary], [`dictionary\[[$prim-key], [$prim-val]\]]
** reference types: these are prefixed with the [`ref] keyword, e.g [`ref node-3D], [`ref rich-text-label] etc. you can also write these as normal camelcase ([`ref RichTextLabel]) if you like, since i don't have to care about the internal structure of the identifier at this point.

note that [*semicolons are mandatory] after every statement. whitespace is irrelevant except as a token-separator.

the interface for each unit is written to ["gen/*.h]. be sure to call the unit load function at the proper place and time.

#anti-gdscriptische-aktion  manifesto against GDScript
> [!gdscript is a gibbering aberration from beyond the stars]

okay, this is, if anything, an understatement. it is possibly the single worst programming language i have yet (however begrudgingly) coded in. the tale of gdscript is one of fantastical hubris, of strife and horror and punishment handed down by the gods from on high like unto the sagas of old.

the creators of gdscript, you see (whom i shall henceforth address by the name of Abdul al-Hazred, for reasons i hope are readily apparent) originally wanted to use lua as godot's scripting language, and made an attempt to do so (this was sensible) but they concluded (incorrectly) that lua's facilities for threading were insufficient (i.e. they did not understand how to use the library correctly), so, having zero domain expertise or relevant experience, they resolved to devise their very own language from scratch. (this [>blunder was not]. i am put in mind of how Brendan Eich (piss be upon him) actually wanted to use a Scheme as Netscape's scripting language -- as i would have in his place! -- but the bleating idiot suits wanted something they could (however dishonestly) slap the Java branding on, so instead the entire modern web is infested by unending reams of [>wat ECMAScript]. more ECMAScript than there are stars. [!more ECMAScript than there is light])

so, knowing nothing of elementary PL theory, they hacked together a lurching screeching nightmare that is the antithesis to everything lua has ever been. shall i number the ways?

: lua has clean, consistent, unambiguous syntax that completely elides any need for syntactic handholding like brackets or (outside one niggling edge case) semicolons or line continuation sigils; gdscript reads the entrails of your indenting discipline and tries to infer structure therefrom (except for enums, for some unfathomable reason, which use C-style bracket blocks), which you inevitably have to hack your way around with the aforementioned semicolons and backslashes, and no matter what you do your code looks hideous. even this is inconsistent: the (woefully inadequate) match statement ([!"statement"], i will note, [!not] "expression"; the ML weenies in the audience just flinched, and rightly so) absolutely requires whitespace and newlines to delimit its cases.
: lua has a simple but extensible prototype-ish object model. godot has a full-blown class system with all the elegance of GObject, all the efficiency of Java, all the consistency of an American politician, and all the expressive power of MUMPS. single inheritance, no interfaces, no templates, no generics, no macros, no mixins. in a [!game engine]. [!lunacy, i tell you.]
: lua is cleverly designed to use tables to implement namespacing. in point of fact, namespacing is emergent; it's barely even part of the language. every lua chunk has its own clean namespace (or a shared global environment, an the host program will it so), and can choose what to bring in by binding modules to variables. gdscript barely supports namespacing at all, and in such a broken fucking way the native classes don't even try to use it. inner classes are defective, with reduced autocomplete metadata from other units, and cannot be used as resources. i honestly cannot fathom how they fucked this up so badly. every godot class you create pretty much pollutes a global namespace, and the situation is so bad that addon writers follow a convention of [!not naming their classes at all], using path-based imports instead.
: lua is a reasonable functional language. you can use any FP paradigm you want, with a bit of effort. closures are lightweight, simple, and fun to use. there is no difference between an ordinary function and a lambda. gdscript, on the other hand, is a procedural language with lambdas hacked awkwardly in, with syntax that will make you long for the purity of ruby. a closure is a completely different thing from functions, an object of class Callable, and can only be invoked by using [`Callable.call(args)]. and of course closures are completely untyped.
: lua has a straightforward dynamic type system. not great, but it's bearable. gdscript has a bizarre and spiritually unclean gradual typing system (i have always been suspicious of this concept and i am now firmly of the opinion that gradual typing is categorically a bug, not a feature.) it is not even internally consistent. "typed arrays" are not a type at all, but bizarre syntactic sugar for an array-specific runtime type tagging system. typed dictionaries [!were not even available] until 4.4, which at the time of writing is still in pre-release. there are no generics, no templates. just [`Variant], and special-casing as far as the eye can see. this is almost the worst part, because to use godot at all, even from another language, you have to interact with this abortion somehow. i am going to have to wrap some kind of elaborate blast shield around it to make it usable from janet and i do not eagerly anticipate the process.
: lua compiles down to bytecode. the reference implementation is not terribly impressive speed-wise, but luajit (an ideological variant based on pre-5.3 lua, not an implementation as it is often called) has performance characteristics bordering on C itself. gdscript does not compile at all; at best, it is pre-tokenized. yes, the entire damn thing is interpreted, at runtime. [*this alone should be enough to rule it out summarily as a game scripting language.] i mean, [!for shame]. fucking [!ruby] is probably more efficient at this point (not that i suggest anyone pull an RPGMaker XP here).

this is not exhaustive. this isn't even getting into the rat fuck screaming lunacy that is the underlying implementation. this is just the particular set of aggravations that came most immediately to mind when i set my cyberquill to electric paper. this is just a fucking taste, a faint flickering preview of how bad it gets. [!and people use this language for serious work.] they claim to [!like] it. the godot team blithely encourages people to learn gdscript, enthusing on its many virtues, as if they should not be too ashamed to show their faces where unsullied souls might see. the absolute mindless [!gall] is beyond all reckoning. i would say that this is the fucking PHP fractal-of-bad-design fiasco all over again, except, like, I've used PHP. [!i prefer PHP to this monstrosity.] yes, even ye-olde-worlde PHP, with [`register_globals] and all. i think it is fair to say that gdscript is to lua as modern American English is to [>toki Toki Pona] or Japanese.
in broad conclusion, gdscript is not a programming language. it is barely a scripting language. it is a genus of tyranid. it is the diseased, misshapen, gurgling spawn of the unclean things that gnaw the roots of Yggdrasil in the depths beyond depths where gods and titans fear to tread, and sing searing nightmares into the sleeping souls of mortals. it is a thing demonstrative of the Kali Yuga, and foretells in near time the merciful descent of Shiva's cleansing blade. it is the most intricate work of raw seething blasphemy yet wrought by mortal hands, if indeed mere mortals had anything to do with it.
you maybe have some idea of exactly [!how angry this thing makes me], simply from the fact that i was willing to endure the process of binding a whole new language to godot out of sheer fulminating [!spite].
- - - -
i would like to make one final point, and if you take anything away from the above rant, [!wote it be this]: programming language design is an art unto itself, and being a programmer -- even a very good one -- gives you fuck all credibility as a language designer. i don't care how many languages you know, i don't care how 10x your full stack is, i don't care if you have 700 years of experience at Current Thing, and i don't care how sharp your suit is. none of that has any bearing on your competence as a language designer. as an [!implementer], maybe. but leave the high concept stuff to your betters. do not under any circumstances design Yet Another Language, and work actively to shame those who do.

yes, i am being unironically elitist here: like making art and making war, language design is an [!intrinsically aristocratic pastime], and no amount of raw labor power or professional experience can suffice in the absence of a suitably disciplined, cultured, and talented mind. comrade stalin (peace be upon him) learned this the hard way about war, and if anything we moderns seem to be learning the same lesson even more slowly about the technical arts.

i myself am capable of reasonably competent language design, and that was a skill i developed over more than a decade of research and experiment and, mostly, plain old omphaloskepsis, things i would not have had time or energy for if i weren't broadly sheltered from the material conditions of the 21st century by my status as a(n intermittently starving) artist, paid by human patrons rather than bugman employers. i do not even hold a candle to [>fennel the] [>lua true] [>bakpakin masters] in this arena.

	toki: https://en.wikipedia.org/wiki/Toki_Pona
	blunder: https://www.youtube.com/watch?v=WjvsYi2DkOs
	fennel: https://github.com/bakpakin/Fennel
	lua: https://lua.org
	bakpakin: https://github.com/bakpakin
	wat: https://www.destroyallsoftware.com/talks/wat
