# [ʞ] tool/api-compile.janet
#  ~ lexi hale <lexi@hale.su>
#  🄯 AGPLv3
#  ? gathers the core modules and assembles
#    them into a unified image that can be
#    loaded into gdjn.

(def- extern-table  @{})
(def  marshal-map   @{})
(def  unmarshal-map @{})

(defn- safe-to-export? [x]
	# only ref types and explicit exportscan be safely replaced;
	# otherwise e.g. any use of the string "release" will be
	# replaced by the value of janet/build when loaded
	# yes this is lunacy i don't know why they did it like that
	(or (function?  x)
		(cfunction? x)
		(table?     x)
		(array?     x)
		(buffer?    x)
		(has-key? extern-table x)))

(defn- register-env [env]
	(merge-into extern-table
		(tabseq [[sym data] :pairs env
				 :when (has-key?         data :value)
				 :when (safe-to-export? (data :value))]
						(data :value) sym)))


# provide symbol mapping for core values &
# external symbols provided by gdjn
(register-env root-env)

(def modules '[
	core prim json
])

# here we iterate over the core modules and load each
# in turn. the array *gd-api-map* is assembled at
# compile-time and then used by the init function at
# runtime to enumerate and install the core modules.
# when core modules use a primitive, they declare their
# own *gd-api-map* which maps gensyms to the name of
# the desired primitive.
(def *gd-api-map* @{})
(defn- install [name env]
	(put *gd-api-map* name env)
	(when (has-key? env '*gd-api-map*)
		(def gdmap (get-in env ['*gd-api-map* :value]))
		(loop [[val key] :pairs gdmap
			   :when (safe-to-export? val)]
			(def sym (symbol '-val- (bxor (hash name) (hash val))))
			(put   marshal-map val sym)
			(put unmarshal-map sym val))
		(merge-into extern-table gdmap)))

(defn- log [fmt & r]
	(:write stderr "(api-compile) "
			(string/format fmt ;r)))
(defn- install-mod [name]
	(log "loading library %s\n" name)
	(def env (require (string "/lib/" name)))
	(install (string name) env))

(each m modules (install-mod m))

# macro implementations can be provided directly in janet
# and incorporated into the API image without having to
# implement anything beyond the primitive API in C

# this function is exported as part of api.jimage.
# it will be called from vm.c after initializing
# the primitive bindings
(defn init []
	(print "beginning merge")
	(merge-into module/cache *gd-api-map*)
	(print "api loaded"))

# this is the build-time entry point, which does
# the work of assembling the modules and marshalling
# them out. it is not exported
(defn main [_ out-path]
	(def env @{})
	(each sym '[init marshal-map unmarshal-map]
		(put env sym ((curenv) sym)))
	(let [blob (marshal env extern-table)]
		(with [fd (file/open out-path :w)]
			(:write fd blob)))
	0)
