Tagged as Gambit internals
Written on 2020-09-07 10:56:36
If you have taken a look at Gambit Scheme implementation source code you should have noticed the occurrence of
'##namespace'
or 'namespace'
at the beginning of many of its implementation files. While the feature is still undocumented in Gambit's manual, hinting that the current version might still be far from the final one, I think that it's still useful to share what I have found so by delving into Gambit source code.
Gambit uses the character '#'
as the separator between the namespace part and the local part of a symbol with the '#'
that is considered part of the namespace. During the compilation stage the current namespace is prepended to every symbol that is compiled.There is no namespace table or any other data structure that associates a namespace with its symbols. In other words namespaces are just syntactic sugar to facilitate enforcing of strict naming conventions. The current namespace can be changed with (namespace (string-ending-in-#))
. This declaration affects the whole file when put at the top of the source file while otherwise its scope lasts until the end of the current let or lambda.
The empty namespace ""
is the exception to the rule: it is the default namespace when Gambit is started and therefore is the namespace where user code is usually entered. In addition to that many of Gambit non-internal symbols belong to this namespace. Another notable namespace is the "##"
namespace which is used for Gambit's internals. In particular most of the functions declared in the empty namespace are built by adding type checking and other safety controls on top of corresponding unsafe internal functions in the ##
namespace. This separation allows to easily redefine user level functions while at the same time avoiding any side effect due to the redefinition of internal pieces.
Given all of above it's not surprising that obtaining the current namespace and listing all the available namespaces requires a bit of an hack.
Let's look at the code:
(define (current-namespace)
(##symbol->string (eval (##quote (##caddr (##decompile (##lambda () ||)))))))
The current namespace is obtained by first compiling a lambda containing the empty symbol. The compiler will prepend the current namespace to the empty sybol which is the extracted from the decompilation step.
define (get-all-namespaces)
(let ((namespaces (make-table)))
(##global-var-table-foldl
(lambda (dummy var)
(let* ((sym (##global-var->identifier var))
(str (symbol->string sym))
(ns (let ((i (##namespace-separator-index str)))
(if (< i 0) "" (substring str 0 (+ i 1))))))
(table-set! namespaces ns ns)
dummy))
#f)
(list-sort! string<=? (map car (table->list namespaces)))))
This function builds the list of available namespaces by listing all identifiers and extracing all prefixes that end in a '#'
. The results that don't have a local name following a '#'
are filtered out as they represents header files (e.g foo#.scm
)