gdjn  Diff

Differences From Artifact [a79a150ec2]:

To Artifact [5d3a9769d0]:


    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: ...