nandi/clonimpublic Fork
⑂ 8c9eb04
Commits
⬇ Clone ▾
git clone https://git.rickub.com/nandi/clonim.git
git clone ssh://git@rickub.com/nandi/clonim.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Download ZIP

feat: resolve host-provided namespaces

nandithebull committed 2026-09-21T00:38:10-07:00 Browse files
8c9eb04 parent: e5a8bce
modified src/namespaces.nim +24 -2
@@ -16,7 +16,7 @@ type
1616 defs, privateDefs, required: HashSet[string]
1717 Resolver = ref object
1818 roots: seq[string]
19- cores: HashSet[string]
19+ cores, hosts: HashSet[string]
2020 spaces: Table[string, Namespace]
2121 active: seq[string]
2222 loaded: HashSet[string]
@@ -64,6 +64,22 @@ proc registeredCoreNames(): HashSet[string] =
6464 finally:
6565 globals = saved
6666
67+proc registeredHostNames(): HashSet[string] =
68+ ## Runtime functions in named host namespaces (for example clojure.string)
69+ ## do not need a shadow source file merely to be required by user code.
70+ let saved = globals
71+ globals = initTable[string, VarCell]()
72+ try:
73+ registerCore()
74+ for name, cell in globals:
75+ if cell.bound: result.incl name
76+ finally:
77+ globals = saved
78+
79+proc hasHostNamespace(r: Resolver, name: string): bool =
80+ for candidate in r.hosts:
81+ if candidate.startsWith(name & "/"): return true
82+
6783 proc registerNamespaceCore*() =
6884 ## Call after registerCore in the generated program, before resolving cells.
6985 ## Both spellings share a cell, so rebinding remains coherent.
@@ -85,6 +101,7 @@ proc checkVar(r: Resolver, ns: Namespace, target, name: string,
85101 fail("no definition " & canonical)
86102 if target != ns.name and target notin ns.required:
87103 fail("namespace " & target & " is not required by " & ns.name)
104+ if canonical in r.hosts: return
88105 if not r.spaces.hasKey(target) or name notin r.spaces[target].defs:
89106 fail("no definition " & canonical)
90107 if target != ns.name and name in r.spaces[target].privateDefs:
@@ -299,6 +316,10 @@ proc process(r: Resolver, forms: seq[Value], expected = "")
299316 proc load(r: Resolver, name: string) =
300317 validNamespace(name)
301318 if name == "clojure.core": return
319+ if r.hasHostNamespace(name):
320+ discard r.space(name)
321+ r.loaded.incl name
322+ return
302323 if name in r.active: fail("dependency cycle: " & (r.active & @[name]).join(" -> "))
303324 if name in r.loaded: return
304325 let relative = name.replace('.', '/').replace('-', '_')
@@ -410,6 +431,7 @@ proc resolveSource*(src: string, sourceRoots: seq[string]): seq[Value] =
410431 ## Forward references require declare. Qualified references require a direct
411432 ## require, except for the current namespace and implicit clojure.core.
412433 ## Unknown and externally private vars are rejected during analysis.
413- let r = Resolver(roots: sourceRoots, cores: registeredCoreNames())
434+ let r = Resolver(roots: sourceRoots, cores: registeredCoreNames(),
435+ hosts: registeredHostNames())
414436 r.process(readAll(src))
415437 r.output
@@ -16,7 +16,7 @@ type
16 defs, privateDefs, required: HashSet[string]16 defs, privateDefs, required: HashSet[string]
17 Resolver = ref object17 Resolver = ref object
18 roots: seq[string]18 roots: seq[string]
19- cores: HashSet[string]19+ cores, hosts: HashSet[string]
20 spaces: Table[string, Namespace]20 spaces: Table[string, Namespace]
21 active: seq[string]21 active: seq[string]
22 loaded: HashSet[string]22 loaded: HashSet[string]
@@ -64,6 +64,22 @@ proc registeredCoreNames(): HashSet[string] =
64 finally:64 finally:
65 globals = saved65 globals = saved
66 66
67+proc registeredHostNames(): HashSet[string] =
68+ ## Runtime functions in named host namespaces (for example clojure.string)
69+ ## do not need a shadow source file merely to be required by user code.
70+ let saved = globals
71+ globals = initTable[string, VarCell]()
72+ try:
73+ registerCore()
74+ for name, cell in globals:
75+ if cell.bound: result.incl name
76+ finally:
77+ globals = saved
78+
79+proc hasHostNamespace(r: Resolver, name: string): bool =
80+ for candidate in r.hosts:
81+ if candidate.startsWith(name & "/"): return true
82+
67 proc registerNamespaceCore*() =83 proc registerNamespaceCore*() =
68 ## Call after registerCore in the generated program, before resolving cells.84 ## Call after registerCore in the generated program, before resolving cells.
69 ## Both spellings share a cell, so rebinding remains coherent.85 ## Both spellings share a cell, so rebinding remains coherent.
@@ -85,6 +101,7 @@ proc checkVar(r: Resolver, ns: Namespace, target, name: string,
85 fail("no definition " & canonical)101 fail("no definition " & canonical)
86 if target != ns.name and target notin ns.required:102 if target != ns.name and target notin ns.required:
87 fail("namespace " & target & " is not required by " & ns.name)103 fail("namespace " & target & " is not required by " & ns.name)
104+ if canonical in r.hosts: return
88 if not r.spaces.hasKey(target) or name notin r.spaces[target].defs:105 if not r.spaces.hasKey(target) or name notin r.spaces[target].defs:
89 fail("no definition " & canonical)106 fail("no definition " & canonical)
90 if target != ns.name and name in r.spaces[target].privateDefs:107 if target != ns.name and name in r.spaces[target].privateDefs:
@@ -299,6 +316,10 @@ proc process(r: Resolver, forms: seq[Value], expected = "")
299 proc load(r: Resolver, name: string) =316 proc load(r: Resolver, name: string) =
300 validNamespace(name)317 validNamespace(name)
301 if name == "clojure.core": return318 if name == "clojure.core": return
319+ if r.hasHostNamespace(name):
320+ discard r.space(name)
321+ r.loaded.incl name
322+ return
302 if name in r.active: fail("dependency cycle: " & (r.active & @[name]).join(" -> "))323 if name in r.active: fail("dependency cycle: " & (r.active & @[name]).join(" -> "))
303 if name in r.loaded: return324 if name in r.loaded: return
304 let relative = name.replace('.', '/').replace('-', '_')325 let relative = name.replace('.', '/').replace('-', '_')
@@ -410,6 +431,7 @@ proc resolveSource*(src: string, sourceRoots: seq[string]): seq[Value] =
410 ## Forward references require declare. Qualified references require a direct431 ## Forward references require declare. Qualified references require a direct
411 ## require, except for the current namespace and implicit clojure.core.432 ## require, except for the current namespace and implicit clojure.core.
412 ## Unknown and externally private vars are rejected during analysis.433 ## Unknown and externally private vars are rejected during analysis.
413- let r = Resolver(roots: sourceRoots, cores: registeredCoreNames())434+ let r = Resolver(roots: sourceRoots, cores: registeredCoreNames(),
435+ hosts: registeredHostNames())
414 r.process(readAll(src))436 r.process(readAll(src))
415 r.output437 r.output
modified tests/test_namespaces.nim +4 -0
@@ -28,6 +28,10 @@ suite "static namespace resolution":
2828 check forms[0].kind == kStr
2929 check forms[0].s == "(?i)^did:[a-z0-9]+:"
3030
31+ test "host namespaces resolve without placeholder source files":
32+ checkForms("(ns user (:require [clojure.string :as str])) (str/trim \" hi \")",
33+ "(clojure.string/trim \" hi \")")
34+
3135 test "definitions resolve sequentially, including core shadowing":
3236 checkForms("(inc 1) (def inc (fn [x] x)) (inc 2)",
3337 "(clojure.core/inc 1) (def user/inc (fn [x] x)) (user/inc 2)")
@@ -28,6 +28,10 @@ suite "static namespace resolution":
28 check forms[0].kind == kStr28 check forms[0].kind == kStr
29 check forms[0].s == "(?i)^did:[a-z0-9]+:"29 check forms[0].s == "(?i)^did:[a-z0-9]+:"
30 30
31+ test "host namespaces resolve without placeholder source files":
32+ checkForms("(ns user (:require [clojure.string :as str])) (str/trim \" hi \")",
33+ "(clojure.string/trim \" hi \")")
34+
31 test "definitions resolve sequentially, including core shadowing":35 test "definitions resolve sequentially, including core shadowing":
32 checkForms("(inc 1) (def inc (fn [x] x)) (inc 2)",36 checkForms("(inc 1) (def inc (fn [x] x)) (inc 2)",
33 "(clojure.core/inc 1) (def user/inc (fn [x] x)) (user/inc 2)")37 "(clojure.core/inc 1) (def user/inc (fn [x] x)) (user/inc 2)")