Differences From
Artifact [a79a150ec2]:
54 54 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].
55 55
56 56 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.)
57 57
58 58 ##obj object model
59 59 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.
60 60
61 -the engine executes the "compile-time" phase of a janet script once it is finished loading. it is the responsibility of the compile-time thunk to set up the local environment to represent a class using def statements. the abstract serves as a proxy for method calls. when a lookup is performed against a class proxy, the proxy inspects its attached environment and returns an appropriate object.
61 +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.
62 62
63 -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 bindins are treated as static variables.
63 +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.
64 +
65 +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.
64 66
65 67 ~~~[janet] example bindings
66 68 (def name "Lisuan") # constant string
67 -(def name :field "Lisuan") # field of type string, initializes to "Lisuan"
68 -(def name {:field :variant} "Lisuan") field of type variant
69 +(def name :prop "Lisuan") # field of type string, initializes to "Lisuan"
70 +(def name {:prop :variant} "Lisuan") field of type variant
69 71 (def- secret "swordfish") # private constant
70 -(def- secret :field "swordfish") # private string field
72 +(def- secret :prop "swordfish") # private string field
71 73 (var count 0) # static variable of type int
72 74 (var- count 0) # private static variable of type int
73 75 ~~~
74 76
75 77 unadorned functions are treated as static functions.
76 78
77 79 private functions (those declared with [`def-]) are available only within the class implementation. they are not exported as methods.
78 80
79 81 functions with the annotation [":method] are treated as methods. when invoked, they are passed a [`self]-reference as their first argument.
80 82
81 -function type signatures can be specified with the annotations [":takes [...]] and [`:gives [$type]].
83 +function type signatures can be specified with the annotation ["{:method {:sig [ty name ...] :ret ret-type}}].
82 84
83 -tables with the annotation [":subclass] are treated as environment tables specifying inner classes. the macro [`subclass] should generally be used to maintain uniform syntax between outer and inner classes, e.g.
85 +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.
84 86
85 87 ~~~[janet]
86 -(use core)
87 88 (use-classes Object RefCounted)
88 89 (declare outerClass :is Object)
89 90 (def val 10)
90 -(prop v vec3)
91 +(prop t :float 0)
92 +(conf v :vec3 0 0 0)
93 +(data x :packed-byte-array [])
91 94 (defclass innerClass :is RefCounted
92 95 (def val 2))
93 96
94 97 # equivalent to
95 -(import prim)
98 +(use core) # implicit
96 99 (def Object (prim/load-class :Object))
97 100 (def RefCounted (prim/load-class :RefCounted))
98 -(def *class-name* :meta :outerClass)
99 -(def *class-inherit* :meta Object)
101 +(def @id :outerClass)
102 +(def @inherit Object)
100 103 (def val 10)
104 +(def t {:prop :float} 0)
105 +(def v {:prop :vector3} :export 0)
106 +(def x {:prop :packed-byte-array} :export-storage [])
101 107 (def innerClass (do
102 - (def *class-inherit* :meta RefCounted)
108 + (def @inherit RefCounted)
103 109 (let [env (table/setproto @{} (curenv))]
104 110 (eval '(do
105 111 (def val 2))
106 112 env)
107 113 env)))
108 114 ~~~
109 115
110 -since the annotations are somewhat verbose, macros are provided to automate the process.
116 +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.
111 117
112 118 + janet + gdscript
113 119 | ["(def count 10)] | ["const count := 10]
114 120 | ["(def count {:field int} 10)] | ["var count: int = 10]
115 121 | ["(defn open [path] ...)] | ["static func open(path: Variant) ...]
116 122 | ["(defn open {:takes [:string] :gives :int} [path] ...)] | ["static func open(path: String) -> int: ...]
117 123 | ["(defn close :method [me] ...)] | func close() -> void: ...