Skip to main content

hara_native/core/
namespace.rs

1fn previously_failed_error(registry: &NamespaceRegistry<Value>, namespace: &str) -> String {
2    let mut message =
3        format!("Namespace load previously failed; use explicit reload to retry: {namespace}");
4    if let Some(detail) = registry.load_failure(namespace) {
5        message.push_str(&format!(" (initial failure: {detail})"));
6    }
7    message
8}
9
10fn eval_source_form(
11    namespace: &str,
12    form: &crate::kernel::SpannedForm,
13    env: &mut HashMap<String, Value>,
14) -> Result<Value, String> {
15    let site = ExceptionSite {
16        namespace: Some(namespace.to_owned()),
17        resource: None,
18        line: form.span.start.line,
19        column: form.span.start.column,
20    };
21    let form = attach_exception_sites(form);
22    with_exception_site(site, || eval(&form, env))
23}
24
25fn load_source_namespace(
26    name: &str,
27    source: &str,
28    registry: &NamespaceRegistry<Value>,
29    env: &mut HashMap<String, Value>,
30) -> Result<(), String> {
31    let forms = crate::kernel::read_forms(source).map_err(|error| error.to_string())?;
32    let mut start = 0;
33    if forms
34        .first()
35        .is_some_and(|form| top_level_namespace_form(&form.form))
36    {
37        eval_source_form(name, &forms[0], env)
38            .map_err(|error| format!("{name}: top-level form 1: {error}"))?;
39        start = 1;
40    }
41    let declarations = forms[start..]
42        .iter()
43        .filter_map(|form| top_level_definition_name(&form.form))
44        .map(|name| Form::Symbol(name.to_owned()))
45        .collect::<Vec<_>>();
46    if !declarations.is_empty() {
47        let declaration = Form::List(
48            std::iter::once(Form::Symbol("declare".into()))
49                .chain(declarations)
50                .collect(),
51        );
52        eval(&declaration, env)
53            .map_err(|error| format!("{name}: top-level predeclaration: {error}"))?;
54    }
55    for (index, form) in forms.into_iter().enumerate().skip(start) {
56        eval_source_form(name, &form, env)
57            .map_err(|error| format!("{name}: top-level form {}: {error}", index + 1))?;
58    }
59    if registry.find(name).is_none() {
60        return Err(format!(
61            "Namespace source did not define expected namespace: {name}"
62        ));
63    }
64    Ok(())
65}
66
67fn ensure_namespace(
68    registry: &NamespaceRegistry<Value>,
69    env: &mut HashMap<String, Value>,
70    name: &str,
71    reload: bool,
72) -> Result<(), String> {
73    // Resource replacement currently re-marks a namespace as `Unloaded`.
74    // Preserve the sticky-failure contract by treating an accompanying
75    // failure detail as `Failed` until an explicit reload succeeds.
76    let load_state = match registry.load_state(name) {
77        Some(NamespaceLoadState::Unloaded) if registry.load_failure(name).is_some() => {
78            Some(NamespaceLoadState::Failed)
79        }
80        state => state,
81    };
82    match load_state {
83        Some(NamespaceLoadState::Loaded) if !reload => return Ok(()),
84        Some(NamespaceLoadState::Loading) => {
85            return Err(format!("Cyclic namespace require: {name}"));
86        }
87        Some(NamespaceLoadState::Failed) if !reload => {
88            return Err(previously_failed_error(registry, name));
89        }
90        _ => {}
91    }
92
93    let catalog = package_catalog();
94    if let Some(coordinate) = catalog.coordinate_for_namespace(name) {
95        if catalog.state(&coordinate).as_deref() != Some("ready") {
96            return Err(format!(
97                "package/not-installed: namespace is locked but unavailable: {name}; call Package/ensure first"
98            ));
99        }
100    }
101
102    let requiring = registry.current().name().as_str().to_owned();
103    // A host resource replacement can mark a materialized namespace as
104    // `Unloaded` so the next require considers the new source.  The old
105    // namespace is still the rollback baseline, however, and a failed reload
106    // must restore it as `Loaded` rather than making the prior generation
107    // appear failed.
108    let previous_state = match load_state {
109        Some(NamespaceLoadState::Unloaded) if registry.find(name).is_some() => {
110            Some(NamespaceLoadState::Loaded)
111        }
112        Some(state) => Some(state),
113        None => registry.find(name).map(|_| NamespaceLoadState::Loaded),
114    };
115    let registry_before = registry.transaction_snapshot([requiring.as_str(), name]);
116    // Qualified and aliased bindings are a derived namespace view. Snapshot
117    // only unqualified bindings (including lexical locals), then rebuild the
118    // derived view on rollback instead of cloning the full cross-namespace
119    // environment before every successful require.
120    let environment_before = env
121        .iter()
122        .filter(|(binding, _)| !binding.contains('/'))
123        .map(|(binding, value)| (binding.clone(), value.clone()))
124        .collect::<HashMap<_, _>>();
125    let macros_before = ACTIVE_MACROS.with(|active| {
126        active
127            .borrow()
128            .as_ref()
129            .map(|macros| macros.borrow().clone())
130    });
131    registry.clear_module_dependencies(name);
132    registry.set_load_state(name, NamespaceLoadState::Loading);
133
134    let loaded = (|| {
135        let resource = NAMESPACE_SOURCE_PROVIDER
136            .with(|active| active.borrow().as_ref().and_then(|provider| provider(name)))
137            .ok_or_else(|| format!("Cannot require missing namespace: {name}"))?;
138        #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
139        let loaded_directly = if let Some(loader) = direct_native_namespace_loader() {
140            loader(name, resource.clone(), env)?;
141            true
142        } else {
143            false
144        };
145        #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
146        let loaded_directly = false;
147        if !loaded_directly {
148            let source = match &resource {
149                NamespaceResource::Source(source) => Some(source.clone()),
150                #[cfg(not(target_arch = "wasm32"))]
151                NamespaceResource::SourcePath(_) => {
152                    Some(crate::core::read_source_resource(&resource, name)?)
153                }
154                #[cfg(feature = "bytecode-vm")]
155                NamespaceResource::Bytecode { .. } => None,
156            };
157            if let Some(source) = source {
158                load_source_namespace(name, &source, registry, env)?;
159            } else {
160                #[cfg(feature = "bytecode-vm")]
161                if let NamespaceResource::Bytecode {
162                    namespace_form,
163                    artifact,
164                } = resource
165                {
166                    let forms = crate::kernel::read_forms(&namespace_form)
167                        .map_err(|error| error.to_string())?;
168                    for (index, form) in forms.into_iter().enumerate() {
169                        eval_source_form(name, &form, env).map_err(|error| {
170                            format!("{name}: namespace form {}: {error}", index + 1)
171                        })?;
172                    }
173                    let program = Rc::new(crate::vm::decode_program(&artifact)?);
174                    registry.set_current(name);
175                    crate::vm::execute_program_with_globals(program, registry)
176                        .map_err(|error| error.to_string())?;
177                }
178            }
179        }
180        if registry.find(name).is_none() {
181            return Err(format!(
182                "Namespace source did not define expected namespace: {name}"
183            ));
184        }
185        Ok(())
186    })();
187
188    select_namespace_environment(registry, env, &requiring);
189    if let Err(error) = loaded {
190        *env = environment_before;
191        registry.restore_transaction(registry_before);
192        refresh_namespace_environment(registry, env);
193        if previous_state == Some(NamespaceLoadState::Loaded) {
194            // A failed reload must restore the complete previously loaded
195            // boundary, including its observable load state and failure
196            // marker.  The transaction snapshot restores namespace values,
197            // but load state is maintained separately.
198            registry.set_load_state(name, NamespaceLoadState::Loaded);
199            registry.clear_load_failure(name);
200        } else {
201            registry.set_load_state(name, NamespaceLoadState::Failed);
202            registry.set_load_failure(name, error.clone());
203        }
204        if let Some(saved) = macros_before {
205            ACTIVE_MACROS.with(|active| {
206                if let Some(macros) = active.borrow().as_ref() {
207                    *macros.borrow_mut() = saved;
208                }
209            });
210        }
211        return Err(error);
212    }
213
214    registry.set_load_state(name, NamespaceLoadState::Loaded);
215    registry.clear_load_failure(name);
216    registry.commit_module_revision(name);
217    Ok(())
218}
219
220fn ensure_foundation_namespace_for_symbol(
221    registry: &NamespaceRegistry<Value>,
222    env: &mut HashMap<String, Value>,
223    symbol: &str,
224) -> Result<(), String> {
225    let Some((namespace, _)) = symbol.split_once('/') else {
226        return Ok(());
227    };
228    if namespace.starts_with("std.foundation.")
229        && registry.load_state(namespace) == Some(NamespaceLoadState::Unloaded)
230    {
231        ensure_namespace(registry, env, namespace, false)?;
232    }
233    Ok(())
234}
235
236fn top_level_namespace_form(form: &Form) -> bool {
237    matches!(form_without_metadata(form), Form::List(values)
238        if matches!(values.first(), Some(Form::Symbol(head)) if head == "ns" || head == "ns+"))
239}
240
241fn top_level_definition_name(form: &Form) -> Option<&str> {
242    let form = match form {
243        Form::Metadata(_, value) => value.as_ref(),
244        value => value,
245    };
246    let Form::List(values) = form else {
247        return None;
248    };
249    let head = match values.first()? {
250        Form::Symbol(head) => head.as_str(),
251        _ => return None,
252    };
253    if !matches!(head, "def" | "defonce" | "defn" | "defmacro") {
254        return None;
255    }
256    match values.get(1)? {
257        Form::Symbol(name) => Some(name),
258        Form::Metadata(_, value) => match value.as_ref() {
259            Form::Symbol(name) => Some(name),
260            _ => None,
261        },
262        _ => None,
263    }
264}
265
266pub fn require_namespace(
267    registry: &NamespaceRegistry<Value>,
268    env: &mut HashMap<String, Value>,
269    name: &str,
270) -> Result<(), String> {
271    ensure_namespace(registry, env, name, false)
272}
273
274fn eval_require_spec(
275    registry: &NamespaceRegistry<Value>,
276    env: &mut HashMap<String, Value>,
277    form: &Form,
278) -> Result<(), String> {
279    let (target, options) = match form {
280        Form::Vector(items) => {
281            let target = match items.first() {
282                Some(Form::Symbol(target)) => target.clone(),
283                _ => return Err("require namespace must be a symbol".into()),
284            };
285            (
286                crate::kernel::generated::normalize_namespace(&target).to_owned(),
287                &items[1..],
288            )
289        }
290        Form::List(items)
291            if items.len() == 2
292                && matches!(&items[0], Form::Symbol(q) if q == "quote")
293                && matches!(&items[1], Form::Symbol(_)) =>
294        {
295            let target = match &items[1] {
296                Form::Symbol(target) => target.clone(),
297                _ => unreachable!(),
298            };
299            (
300                crate::kernel::generated::normalize_namespace(&target).to_owned(),
301                &[][..],
302            )
303        }
304        _ => return Err("require expects vectors such as [chrome.api :as api]".into()),
305    };
306    if options.len() % 2 != 0 {
307        return Err(format!("Malformed require options for {target}"));
308    }
309    let lazy = options.chunks(2).any(|option| {
310        matches!(&option[0], Form::Keyword(keyword) if keyword.as_str() == "lazy")
311            && matches!(&option[1], Form::Bool(true))
312    });
313    let reload = options.chunks(2).any(|option| {
314        matches!(&option[0], Form::Keyword(keyword) if keyword.as_str() == "reload")
315            && matches!(&option[1], Form::Bool(true))
316    });
317    let excluded = options
318        .chunks(2)
319        .find_map(|option| {
320            matches!(&option[0], Form::Keyword(keyword) if keyword.as_str() == "exclude")
321                .then_some(&option[1])
322        })
323        .map(|value| match value {
324            Form::Vector(names) => names
325                .iter()
326                .map(|name| match name {
327                    Form::Symbol(name) if name == "/" || !name.contains('/') => Ok(name.clone()),
328                    _ => Err("require :exclude expects unqualified symbols".to_string()),
329                })
330                .collect::<Result<HashSet<_>, _>>(),
331            _ => Err("require :exclude expects a vector of symbols".into()),
332        })
333        .transpose()?
334        .unwrap_or_default();
335    if lazy {
336        let has_alias = options
337            .chunks(2)
338            .any(|option| matches!(&option[0], Form::Keyword(keyword) if keyword.as_str() == "as"));
339        if !has_alias {
340            return Err("require :lazy requires :as".into());
341        }
342        for option in options.chunks(2) {
343            match &option[0] {
344                Form::Keyword(keyword)
345                    if keyword.as_str() == "refer" || keyword.as_str() == "refer-macros" =>
346                {
347                    return Err(format!(
348                        "require :lazy cannot be combined with :{}",
349                        keyword
350                    ));
351                }
352                Form::Keyword(keyword)
353                    if keyword.as_str() == "lazy" && !matches!(&option[1], Form::Bool(true)) =>
354                {
355                    return Err("require :lazy expects true".into());
356                }
357                _ => {}
358            }
359        }
360    }
361    let deferred = lazy && !reload;
362    #[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
363    let direct_reload = !deferred
364        && direct_native_namespace_loader().is_some()
365        && namespace_has_interpreted_functions(registry, &target);
366    #[cfg(not(all(feature = "direct-native", not(target_arch = "wasm32"))))]
367    let direct_reload = false;
368    if deferred {
369        if registry.load_state(&target).is_none() {
370            registry.set_load_state(&target, NamespaceLoadState::Unloaded);
371        }
372    } else if !crate::kernel::generated::known_namespace(&target) || direct_reload {
373        ensure_namespace(registry, env, &target, reload || direct_reload)?;
374    }
375    let requiring = registry.current().name().as_str().to_owned();
376    if requiring != target && registry.load_state(&requiring) == Some(NamespaceLoadState::Loading) {
377        registry.record_module_dependency(&requiring, &target);
378    }
379    if !deferred {
380        let destination = registry.current();
381        for name in &excluded {
382            let local = crate::lang::data::Symbol::parse(name);
383            if destination
384                .resolve(&local)
385                .is_some_and(|var| var.symbol().get_namespace() == Some(target.as_str()))
386            {
387                destination.unmap(&local);
388                env.remove(name);
389            }
390        }
391    }
392    for option in options.chunks(2) {
393        let name = match &option[0] {
394            Form::Keyword(keyword) => keyword.as_str(),
395            _ => return Err("Malformed require options".into()),
396        };
397        match name {
398            "as" => {
399                let alias = match &option[1] {
400                    Form::Symbol(alias) if !alias.contains('/') => alias.clone(),
401                    _ => return Err("require :as expects an unqualified symbol".into()),
402                };
403                if alias == "-" {
404                    return Err("Namespace alias is reserved: -".into());
405                }
406                // Clear a stale materialized Foundation binding before an
407                // explicit alias claims the same local name.
408                let local = crate::lang::data::Symbol::parse(&alias);
409                if registry
410                    .current()
411                    .resolve(&local)
412                    .is_some_and(|var| var.symbol().get_namespace() == Some("std.foundation"))
413                {
414                    registry.current().unmap(&local);
415                    env.remove(&alias);
416                }
417                if deferred {
418                    registry.current().lazy_alias(alias, &target);
419                } else {
420                    let namespace = registry
421                        .find(&target)
422                        .ok_or_else(|| format!("Cannot require missing namespace: {target}"))?;
423                    registry.current().alias(alias, namespace);
424                }
425            }
426            "refer" => {
427                let source = registry
428                    .find(&target)
429                    .ok_or_else(|| format!("Cannot require missing namespace: {target}"))?;
430                let destination = registry.current();
431                let destination_name = destination.name().as_str().to_owned();
432                let names = match &option[1] {
433                    Form::Keyword(name) if name.as_str() == "all" => source
434                        .mappings()
435                        .into_iter()
436                        .map(|(name, _)| name.as_str().to_owned())
437                        .collect::<Vec<_>>(),
438                    Form::Vector(names) => names
439                        .iter()
440                        .map(|name| match name {
441                            Form::Symbol(name) if !name.contains('/') => Ok(name.clone()),
442                            _ => Err("require :refer expects unqualified symbols".to_string()),
443                        })
444                        .collect::<Result<Vec<_>, _>>()?,
445                    _ => return Err("require :refer expects a vector of symbols or :all".into()),
446                };
447                for name in names {
448                    if excluded.contains(&name) {
449                        continue;
450                    }
451                    let var = source
452                        .resolve(&crate::lang::data::Symbol::parse(&name))
453                        .ok_or_else(|| format!("Cannot refer missing Var: {target}/{name}"))?;
454                    destination.map_var(crate::lang::data::Symbol::parse(&name), var);
455                    ACTIVE_MACROS.with(|active| {
456                        if let Some(macros) = active.borrow().as_ref() {
457                            let mut macros = macros.borrow_mut();
458                            if let Some(function) =
459                                macros.get(&(target.clone(), name.clone())).cloned()
460                            {
461                                macros.insert((destination_name.clone(), name.clone()), function);
462                            }
463                        }
464                    });
465                }
466            }
467            "refer-macros" => {
468                let Form::Vector(names) = &option[1] else {
469                    return Err("require :refer-macros expects a vector of symbols".into());
470                };
471                let destination = registry.current().name().as_str().to_owned();
472                ACTIVE_MACROS.with(|active| -> Result<(), String> {
473                    let active = active.borrow();
474                    let macros = active
475                        .as_ref()
476                        .ok_or_else(|| "macro runtime is unavailable".to_string())?;
477                    let mut macros = macros.borrow_mut();
478                    for name in names {
479                        let Form::Symbol(name) = name else {
480                            return Err("require :refer-macros expects unqualified symbols".into());
481                        };
482                        if name.contains('/') {
483                            return Err("require :refer-macros expects unqualified symbols".into());
484                        }
485                        let macro_fn = macros
486                            .get(&(target.clone(), name.clone()))
487                            .cloned()
488                            .ok_or_else(|| {
489                                format!("Cannot refer missing macro: {target}/{name}")
490                            })?;
491                        macros.insert((destination.clone(), name.clone()), macro_fn);
492                    }
493                    Ok(())
494                })?;
495            }
496            "lazy" => {}
497            "reload" => {
498                if !matches!(&option[1], Form::Bool(true)) {
499                    return Err("require :reload expects true".into());
500                }
501            }
502            "exclude" => {}
503            other => return Err(format!("Unsupported require option: :{other}")),
504        }
505    }
506    Ok(())
507}
508
509fn eval_require_specs(
510    registry: &NamespaceRegistry<Value>,
511    env: &mut HashMap<String, Value>,
512    specs: &[Form],
513) -> Result<(), String> {
514    for spec in specs {
515        eval_require_spec(registry, env, spec)?;
516    }
517    refresh_namespace_environment(registry, env);
518    Ok(())
519}
520
521#[cfg(all(feature = "direct-native", not(target_arch = "wasm32")))]
522fn namespace_has_interpreted_functions(registry: &NamespaceRegistry<Value>, name: &str) -> bool {
523    registry.find(name).is_some_and(|namespace| {
524        namespace.mappings().into_iter().any(|(_, var)| {
525            matches!(var.deref_value(), Value::Function(function) if !is_direct_native_function(&function))
526        })
527    })
528}
529
530fn force_lazy_alias(
531    registry: &NamespaceRegistry<Value>,
532    env: &mut HashMap<String, Value>,
533    symbol: &str,
534) -> Result<(), String> {
535    let Some((alias, _)) = symbol.split_once('/') else {
536        return Ok(());
537    };
538    if registry.current().name().as_str() == alias {
539        return Ok(());
540    }
541    let target = registry.current().lazy_target(alias);
542    let Some(target) = target else {
543        return Ok(());
544    };
545    ensure_namespace(registry, env, target.as_str(), false)?;
546    let namespace = registry
547        .find(target.as_str())
548        .ok_or_else(|| format!("Cannot require missing namespace: {target}"))?;
549    registry.current().alias(alias, namespace);
550    refresh_namespace_environment(registry, env);
551    Ok(())
552}
553
554/// Handles the `ns`, `ns+`, and `require` special forms.
555///
556/// Kept out of line so the giant `eval` dispatch does not reserve stack for
557/// these locals on every recursive call (the native runtime recurses through
558/// `eval` and test threads run on small stacks).
559#[inline(never)]
560fn eval_namespace_form(fs: &[Form], env: &mut HashMap<String, Value>) -> Result<Value, String> {
561    let head = match &fs[0] {
562        Form::Symbol(head) => head.as_str(),
563        _ => unreachable!("ns/ns+/require dispatch guarantees a symbol head"),
564    };
565    if head == "require" {
566        let registry = namespace_registry()?;
567        eval_require_specs(&registry, env, &fs[1..])?;
568        return Ok(Value::Nil);
569    }
570    let registry = namespace_registry()?;
571    let (name, clauses) = if head == "ns+" {
572        if matches!(fs.get(1), Some(Form::Symbol(_))) {
573            return Err("ns+ does not accept a namespace name".into());
574        }
575        (registry.current().name().as_str().to_owned(), &fs[1..])
576    } else {
577        if fs.len() < 2 {
578            return Err("ns expects a namespace symbol".into());
579        }
580        let name = match &fs[1] {
581            Form::Symbol(name) if !name.contains('/') => name.clone(),
582            _ => return Err("ns expects a namespace symbol".into()),
583        };
584        (name, &fs[2..])
585    };
586    // Namespace configuration is normally consumed by the generated-runtime
587    // orchestration layer. The raw HTA evaluator executes forms directly in
588    // an EvalFiber, so the core special form must still honor namespace
589    // construction settings, including global aliases and imports. Foundation
590    // child-library aliases remain distinct from native runtime symbols.
591    let config = crate::kernel::GeneratedNamespaceConfig::configure_with(clauses, |_| true)?;
592    if let Some(alias) = config.global_alias() {
593        registry.register_global_alias(alias, &name)?;
594    }
595    for alias in config.declared_global_imports() {
596        let canonical =
597            crate::core::canonical_native_symbol(alias).unwrap_or_else(|| alias.clone());
598        registry.register_global_import(alias, canonical)?;
599    }
600    apply_global_aliases(&registry, &name);
601    crate::core::apply_global_imports(&registry, &name);
602    select_namespace_environment(&registry, env, &name);
603    let destination = registry.current();
604    destination.set_role(config.role());
605    destination.set_foundation_visibility(
606        config.exposed_foundation(),
607        config.excluded_foundation(),
608        config.blank(),
609    );
610    destination.set_native_flavor(config.native_flavor().map(str::to_owned));
611    for (local, module) in config.native_imports() {
612        destination.import(local, module.clone());
613    }
614    for (alias, _) in registry.global_aliases() {
615        destination.unalias(alias.as_str());
616    }
617    for (alias, target) in config.aliases() {
618        if !target.starts_with("std.foundation.") {
619            continue;
620        }
621        if let Some(namespace) = registry.find(&target) {
622            destination.alias(alias, namespace);
623        } else {
624            destination.lazy_alias(alias, target);
625        }
626    }
627    let omitted = match config.exposed_foundation() {
628        Some(exposed) => destination
629            .mappings()
630            .into_iter()
631            .filter(|(local, var)| {
632                var.symbol().get_namespace() == Some("std.foundation")
633                    && !exposed.contains(local.as_str())
634            })
635            .map(|(local, _)| local.as_str().to_owned())
636            .collect::<Vec<_>>(),
637        None => config.excluded_foundation().iter().cloned().collect(),
638    };
639    for overridden in omitted {
640        let destination = registry.current();
641        let local = crate::lang::data::Symbol::parse(&overridden);
642        if destination
643            .resolve(&local)
644            .is_some_and(|var| var.symbol().get_namespace() == Some("std.foundation"))
645        {
646            destination.unmap(&local);
647            env.remove(&overridden);
648        }
649        let destination_name = destination.name().as_str().to_owned();
650        ACTIVE_MACROS.with(|active| {
651            if let Some(macros) = active.borrow().as_ref() {
652                macros
653                    .borrow_mut()
654                    .remove(&(destination_name, overridden.clone()));
655            }
656        });
657    }
658    for (alias, target) in destination.aliases() {
659        let excluded = config
660            .excluded_foundation_libraries()
661            .iter()
662            .any(|library| target.name().as_str() == format!("std.foundation.{library}"));
663        if excluded {
664            destination.unalias(alias.as_str());
665        }
666    }
667    for (alias, target) in destination.lazy_aliases() {
668        let excluded = config
669            .excluded_foundation_libraries()
670            .iter()
671            .any(|library| target.as_str() == format!("std.foundation.{library}"));
672        if excluded {
673            destination.unalias(alias.as_str());
674        }
675    }
676    for library in config.excluded_foundation_libraries() {
677        if let Some(alias) = crate::kernel::generated::foundation_library_alias(library) {
678            destination.unalias(alias);
679        }
680    }
681    for clause in clauses {
682        match clause {
683            Form::List(clause_forms) if matches!(clause_forms.first(), Some(Form::Keyword(k)) if k == "require") =>
684            {
685                eval_require_specs(&registry, env, &clause_forms[1..])?;
686            }
687            Form::List(clause_forms) if matches!(clause_forms.first(), Some(Form::Keyword(k)) if k == "use") =>
688            {
689                let specs = clause_forms[1..]
690                    .iter()
691                    .map(|namespace| match namespace {
692                        Form::Symbol(name) if !name.contains('/') => Ok(Form::Vector(vec![
693                            Form::Symbol(name.clone()),
694                            Form::Keyword("refer".into()),
695                            Form::Keyword("all".into()),
696                        ])),
697                        _ => Err("ns :use expects namespace symbols".to_string()),
698                    })
699                    .collect::<Result<Vec<_>, _>>()?;
700                eval_require_specs(&registry, env, &specs)?;
701            }
702            Form::List(clause_forms) if matches!(clause_forms.first(), Some(Form::Keyword(k)) if k == "config") =>
703            {
704                // :config is processed by the generated-namespace machinery for
705                // top-level ns forms. For ns forms loaded from source files (e.g.
706                // runtime-library activation declarations), it is metadata-only
707                // and can be ignored here.
708            }
709            Form::List(clause_forms) if matches!(clause_forms.first(), Some(Form::Keyword(k)) if k == "flavor" || k == "import") =>
710                {}
711            _ => return Err("unsupported ns clause in evaluator".into()),
712        }
713    }
714    refresh_namespace_environment(&registry, env);
715    Ok(Value::Nil)
716}
717
718/// Applies a top-level namespace declaration before bytecode compilation.
719///
720/// Namespace selection and configuration affect how every later global is
721/// resolved, so the bytecode compiler performs this analysis-time step before
722/// it creates its compilation context. The emitted form still evaluates to
723/// nil; only the namespace registry is prepared here.
724pub(crate) fn prepare_namespace_form(form: &Form) -> Result<(), String> {
725    let Form::List(forms) = form_without_metadata(form) else {
726        return Err("namespace declaration must be a list".into());
727    };
728    if !matches!(forms.first(), Some(Form::Symbol(head)) if head == "ns" || head == "ns+") {
729        return Err("namespace declaration must start with ns or ns+".into());
730    }
731    eval_namespace_form(forms, &mut HashMap::new()).map(|_| ())
732}
733
734/// Applies a namespace-management form retained in a validated bytecode
735/// constant. `ns`, `ns+`, and `require` need the namespace registry's management
736/// semantics, but must not re-enter the tree evaluator when a VM or direct
737/// native program executes them.
738pub(crate) fn eval_bytecode_management(value: &Value) -> Result<Value, String> {
739    let registry = namespace_registry()?;
740    let mut environment = registry
741        .current()
742        .mappings()
743        .into_iter()
744        .map(|(name, var)| (name.as_str().to_owned(), Value::Var(var)))
745        .collect();
746    refresh_namespace_environment(&registry, &mut environment);
747    let result = eval_bytecode_management_in(value, &mut environment)?;
748    save_namespace_environment(&registry, &mut environment);
749    Ok(result)
750}
751
752/// Applies a validated namespace-management value against a caller-owned
753/// compatibility environment. Namespace loaders use this form so a direct
754/// native frame can prepare `ns`/`require` without entering the tree
755/// evaluator. The environment is intentionally borrowed: selecting a module
756/// must save the requiring namespace before switching to the loaded one.
757pub(crate) fn eval_bytecode_management_in(
758    value: &Value,
759    environment: &mut HashMap<String, Value>,
760) -> Result<Value, String> {
761    let form = value_to_form(value)?;
762    let Form::List(forms) = form_without_metadata(&form) else {
763        return Err("namespace-management instruction expects a list".into());
764    };
765    let Some(Form::Symbol(operator)) = forms.first() else {
766        return Err("namespace-management instruction expects a symbol operator".into());
767    };
768    if !matches!(operator.as_str(), "ns" | "ns+" | "require") {
769        return Err(format!(
770            "namespace-management instruction does not support {operator}"
771        ));
772    }
773    eval_namespace_form(forms, environment)
774}