Skip to main content

hara_native/kernel/
halc.rs

1use super::Form;
2use num_bigint::BigInt;
3use num_traits::ToPrimitive;
4use sha2::{Digest, Sha256};
5use std::collections::{HashMap, HashSet};
6
7const MAGIC: &[u8] = b"HALC";
8const LEGACY_MAGIC: &[u8] = b"HIR\0";
9const FORMAT_VERSION: u16 = 1;
10const EXECUTABLE_FOUNDATION_FLAG: u16 = 1;
11const HASH_BYTES: usize = 32;
12const MAX_PAYLOAD_BYTES: usize = 64 * 1024 * 1024;
13const MAX_COLLECTION_ITEMS: i32 = 1_000_000;
14
15const NIL: u8 = 0;
16const FALSE: u8 = 1;
17const TRUE: u8 = 2;
18const LONG: u8 = 3;
19const DOUBLE: u8 = 4;
20const BIG_INTEGER: u8 = 5;
21const STRING: u8 = 6;
22const CHARACTER: u8 = 8;
23const SYMBOL: u8 = 9;
24const KEYWORD: u8 = 10;
25const LIST: u8 = 11;
26const VECTOR: u8 = 12;
27const MAP: u8 = 13;
28const SET: u8 = 14;
29const ORDERED_MAP: u8 = 15;
30const ORDERED_SET: u8 = 16;
31const REGEX: u8 = 17;
32
33#[derive(Debug, Clone)]
34pub struct HalcModule {
35    pub namespace: String,
36    pub resource: String,
37    pub source_hash: Vec<u8>,
38    pub forms: Vec<Form>,
39    pub schemas: HalcSchemaIndex,
40    pub origin: HalcOrigin,
41}
42
43/// The typed declarations recoverable from canonical HALC forms without
44/// evaluating the module. Keys are fully qualified Var names.
45#[derive(Debug, Clone, Default, PartialEq)]
46pub struct HalcSchemaIndex {
47    pub definitions: HashMap<String, Form>,
48    pub functions: HashMap<String, Form>,
49    pub definition_types: HashMap<String, super::SchemaType>,
50    pub function_types: HashMap<String, super::SchemaType>,
51}
52
53impl HalcSchemaIndex {
54    /// Resolves a function annotation through named-schema references while
55    /// preserving recursive graph edges as `Reference` nodes.
56    pub fn resolved_function_type(&self, qualified_var: &str) -> Option<&super::SchemaType> {
57        let schema = self.function_types.get(qualified_var)?;
58        match schema {
59            super::SchemaType::Reference(name) => self.definition_types.get(name).or(Some(schema)),
60            _ => Some(schema),
61        }
62    }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum HalcOrigin {
67    Halc,
68    LegacyHir,
69}
70
71pub fn decode_halc(bytes: &[u8]) -> Result<HalcModule, String> {
72    let (payload, origin) = decode_envelope(bytes)?;
73    let mut reader = ByteReader::new(&payload);
74    let namespace = reader.read_string()?;
75    let resource = reader.read_string()?;
76    let source_hash = reader.read_bytes(HASH_BYTES)?;
77    let form_count = reader.read_count()?;
78    let mut forms = Vec::with_capacity(form_count as usize);
79    for _ in 0..form_count {
80        forms.push(reader.read_value()?);
81    }
82    if !reader.is_empty() {
83        return Err("trailing payload bytes".into());
84    }
85    let forms = canonicalize_schema_references(&namespace, forms)?;
86    let schemas = build_schema_index(&namespace, &forms)?;
87    Ok(HalcModule {
88        namespace,
89        resource,
90        source_hash,
91        forms,
92        schemas,
93        origin,
94    })
95}
96
97fn decode_envelope(bytes: &[u8]) -> Result<(Vec<u8>, HalcOrigin), String> {
98    let mut reader = ByteReader::new(bytes);
99    let magic = reader.read_bytes(MAGIC.len())?;
100    let origin = if magic == MAGIC {
101        HalcOrigin::Halc
102    } else if magic == LEGACY_MAGIC {
103        HalcOrigin::LegacyHir
104    } else {
105        return Err("bad magic".into());
106    };
107    let version = reader.read_u16()?;
108    if version != FORMAT_VERSION {
109        return Err(format!("unsupported format version {version}"));
110    }
111    let flags = reader.read_u16()?;
112    if flags != EXECUTABLE_FOUNDATION_FLAG {
113        return Err(format!("unsupported flags {flags}"));
114    }
115    let payload_length = reader.read_u32()? as usize;
116    if payload_length > MAX_PAYLOAD_BYTES {
117        return Err(format!("invalid payload length {payload_length}"));
118    }
119    let expected_hash = reader.read_bytes(HASH_BYTES)?;
120    let payload = reader.read_bytes(payload_length)?;
121    if !reader.is_empty() {
122        return Err("trailing bytes".into());
123    }
124    let actual_hash = Sha256::digest(&payload);
125    if actual_hash[..] != expected_hash[..] {
126        return Err("payload checksum mismatch".into());
127    }
128    Ok((payload, origin))
129}
130
131struct ByteReader<'a> {
132    bytes: &'a [u8],
133    position: usize,
134}
135
136impl<'a> ByteReader<'a> {
137    fn new(bytes: &'a [u8]) -> Self {
138        Self { bytes, position: 0 }
139    }
140
141    fn is_empty(&self) -> bool {
142        self.position >= self.bytes.len()
143    }
144
145    fn remaining(&self) -> usize {
146        self.bytes.len().saturating_sub(self.position)
147    }
148
149    fn read_byte(&mut self) -> Result<u8, String> {
150        if self.position >= self.bytes.len() {
151            return Err("truncated artifact".into());
152        }
153        let byte = self.bytes[self.position];
154        self.position += 1;
155        Ok(byte)
156    }
157
158    fn read_bytes(&mut self, count: usize) -> Result<Vec<u8>, String> {
159        if self.remaining() < count {
160            return Err("truncated artifact".into());
161        }
162        let bytes = self.bytes[self.position..self.position + count].to_vec();
163        self.position += count;
164        Ok(bytes)
165    }
166
167    fn read_u16(&mut self) -> Result<u16, String> {
168        let bytes = self.read_bytes(2)?;
169        Ok(u16::from_be_bytes([bytes[0], bytes[1]]))
170    }
171
172    fn read_u32(&mut self) -> Result<u32, String> {
173        let bytes = self.read_bytes(4)?;
174        Ok(u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
175    }
176
177    fn read_i64(&mut self) -> Result<i64, String> {
178        let bytes = self.read_bytes(8)?;
179        Ok(i64::from_be_bytes([
180            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
181        ]))
182    }
183
184    fn read_f64(&mut self) -> Result<f64, String> {
185        let bytes = self.read_bytes(8)?;
186        let value = f64::from_be_bytes([
187            bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
188        ]);
189        if !value.is_finite() {
190            return Err("non-finite number".into());
191        }
192        Ok(value)
193    }
194
195    fn read_string(&mut self) -> Result<String, String> {
196        let length = self.read_u32()? as usize;
197        if length > MAX_PAYLOAD_BYTES {
198            return Err(format!("invalid string length {length}"));
199        }
200        let bytes = self.read_bytes(length)?;
201        String::from_utf8(bytes).map_err(|_| "invalid UTF-8 in string".to_string())
202    }
203
204    fn read_nullable_string(&mut self) -> Result<Option<String>, String> {
205        let present = self.read_byte()? != 0;
206        if present {
207            Ok(Some(self.read_string()?))
208        } else {
209            Ok(None)
210        }
211    }
212
213    fn read_count(&mut self) -> Result<i32, String> {
214        let count = self.read_u32()? as i32;
215        if count < 0 || count > MAX_COLLECTION_ITEMS {
216            return Err(format!("invalid collection count {count}"));
217        }
218        Ok(count)
219    }
220
221    fn read_metadata(&mut self) -> Result<Option<Form>, String> {
222        let present = self.read_byte()? != 0;
223        if present {
224            Ok(Some(self.read_value()?))
225        } else {
226            Ok(None)
227        }
228    }
229
230    fn read_value(&mut self) -> Result<Form, String> {
231        let opcode = self.read_byte()?;
232        match opcode {
233            NIL => Ok(Form::Nil),
234            FALSE => Ok(Form::Bool(false)),
235            TRUE => Ok(Form::Bool(true)),
236            LONG => Ok(Form::Number(self.read_i64()?)),
237            DOUBLE => Ok(Form::Float(self.read_f64()?)),
238            BIG_INTEGER => {
239                let text = self.read_string()?;
240                let value = BigInt::parse_bytes(text.as_bytes(), 10)
241                    .ok_or_else(|| "invalid big integer".to_string())?;
242                Ok(match value.to_i64() {
243                    Some(value) => Form::Number(value),
244                    None => Form::BigInteger(value),
245                })
246            }
247            STRING => Ok(Form::String(self.read_string()?)),
248            CHARACTER => Ok(Form::Character(
249                char::from_u32(self.read_u32()?).ok_or("invalid character code point")?,
250            )),
251            SYMBOL => {
252                let namespace = self.read_nullable_string()?;
253                let name = self.read_string()?;
254                Ok(with_metadata(
255                    Form::Symbol(namespaced(namespace, name)),
256                    self.read_metadata()?,
257                ))
258            }
259            KEYWORD => {
260                let namespace = self.read_nullable_string()?;
261                let name = self.read_string()?;
262                Ok(with_metadata(
263                    Form::Keyword(namespaced(namespace, name)),
264                    self.read_metadata()?,
265                ))
266            }
267            LIST => {
268                let count = self.read_count()?;
269                let items = self.read_values(count)?;
270                Ok(with_metadata(Form::List(items), self.read_metadata()?))
271            }
272            VECTOR => {
273                let count = self.read_count()?;
274                let items = self.read_values(count)?;
275                Ok(with_metadata(Form::Vector(items), self.read_metadata()?))
276            }
277            MAP | ORDERED_MAP => {
278                let count = self.read_count()?;
279                let mut entries = Vec::with_capacity(count as usize);
280                for _ in 0..count {
281                    let key = self.read_value()?;
282                    let value = self.read_value()?;
283                    entries.push((key, value));
284                }
285                Ok(with_metadata(Form::Map(entries), self.read_metadata()?))
286            }
287            SET | ORDERED_SET => {
288                let count = self.read_count()?;
289                let items = self.read_values(count)?;
290                Ok(with_metadata(Form::Set(items), self.read_metadata()?))
291            }
292            REGEX => Ok(Form::Regex(self.read_string()?)),
293            _ => Err(format!("unknown value opcode {opcode}")),
294        }
295    }
296
297    fn read_values(&mut self, count: i32) -> Result<Vec<Form>, String> {
298        let mut values = Vec::with_capacity(count as usize);
299        for _ in 0..count {
300            values.push(self.read_value()?);
301        }
302        Ok(values)
303    }
304}
305
306fn with_metadata(value: Form, metadata: Option<Form>) -> Form {
307    match metadata {
308        Some(metadata) => Form::Metadata(Box::new(metadata), Box::new(value)),
309        None => value,
310    }
311}
312
313fn namespaced(namespace: Option<String>, name: String) -> String {
314    match namespace {
315        Some(ns) => format!("{ns}/{name}"),
316        None => name,
317    }
318}
319
320#[cfg(any(test, feature = "halc-encoder"))]
321fn write_string(output: &mut Vec<u8>, value: &str) {
322    output.extend_from_slice(&(value.len() as u32).to_be_bytes());
323    output.extend_from_slice(value.as_bytes());
324}
325
326#[cfg(any(test, feature = "halc-encoder"))]
327fn write_count(output: &mut Vec<u8>, count: i32) {
328    output.extend_from_slice(&count.to_be_bytes());
329}
330
331#[cfg(any(test, feature = "halc-encoder"))]
332fn write_namespaced(output: &mut Vec<u8>, symbol: &str) {
333    if let Some((ns, name)) = symbol.rsplit_once('/') {
334        output.push(1);
335        write_string(output, ns);
336        write_string(output, name);
337    } else {
338        output.push(0);
339        write_string(output, symbol);
340    }
341}
342
343#[cfg(any(test, feature = "halc-encoder"))]
344fn write_values(output: &mut Vec<u8>, values: &[Form]) {
345    write_count(output, values.len() as i32);
346    for value in values {
347        write_value(output, value);
348    }
349}
350
351#[cfg(any(test, feature = "halc-encoder"))]
352fn encoded_value(form: &Form) -> Vec<u8> {
353    let mut output = Vec::new();
354    write_value(&mut output, form);
355    output
356}
357
358#[cfg(any(test, feature = "halc-encoder"))]
359fn write_canonical_map(output: &mut Vec<u8>, entries: &[(Form, Form)]) {
360    write_count(output, entries.len() as i32);
361    let mut encoded_entries = entries
362        .iter()
363        .map(|(key, value)| (encoded_value(key), encoded_value(value)))
364        .collect::<Vec<_>>();
365    encoded_entries.sort_by(|(left, _), (right, _)| left.cmp(right));
366    for (key, value) in encoded_entries {
367        output.extend_from_slice(&key);
368        output.extend_from_slice(&value);
369    }
370}
371
372#[cfg(any(test, feature = "halc-encoder"))]
373fn write_value(output: &mut Vec<u8>, form: &Form) {
374    match form {
375        Form::Metadata(metadata, value) => write_value_with_metadata(output, value, Some(metadata)),
376        _ => write_value_with_metadata(output, form, None),
377    }
378}
379
380#[cfg(any(test, feature = "halc-encoder"))]
381fn write_metadata(output: &mut Vec<u8>, metadata: Option<&Form>) {
382    match metadata {
383        Some(metadata) => {
384            output.push(1);
385            write_value(output, metadata);
386        }
387        None => output.push(0),
388    }
389}
390
391#[cfg(any(test, feature = "halc-encoder"))]
392fn write_value_with_metadata(output: &mut Vec<u8>, form: &Form, metadata: Option<&Form>) {
393    match form {
394        Form::Nil => output.push(NIL),
395        Form::Bool(false) => output.push(FALSE),
396        Form::Bool(true) => output.push(TRUE),
397        Form::Number(n) => {
398            output.push(LONG);
399            output.extend_from_slice(&n.to_be_bytes());
400        }
401        Form::Float(f) => {
402            assert!(f.is_finite(), "non-finite number");
403            output.push(DOUBLE);
404            output.extend_from_slice(&f.to_be_bytes());
405        }
406        Form::BigInteger(s) => {
407            output.push(BIG_INTEGER);
408            write_string(output, &s.to_string());
409        }
410        Form::String(s) => {
411            output.push(STRING);
412            write_string(output, s);
413        }
414        Form::Character(c) => {
415            output.push(CHARACTER);
416            output.extend_from_slice(&(*c as u32).to_be_bytes());
417        }
418        Form::Symbol(s) => {
419            output.push(SYMBOL);
420            write_namespaced(output, s);
421            write_metadata(output, metadata);
422        }
423        Form::Keyword(s) => {
424            output.push(KEYWORD);
425            write_namespaced(output, s);
426            write_metadata(output, metadata);
427        }
428        Form::List(items) => {
429            output.push(LIST);
430            write_values(output, items);
431            write_metadata(output, metadata);
432        }
433        Form::Vector(items) => {
434            output.push(VECTOR);
435            write_values(output, items);
436            write_metadata(output, metadata);
437        }
438        Form::Map(entries) => {
439            output.push(MAP);
440            write_canonical_map(output, entries);
441            write_metadata(output, metadata);
442        }
443        Form::Set(items) => {
444            output.push(ORDERED_SET);
445            write_values(output, items);
446            write_metadata(output, metadata);
447        }
448        Form::Regex(s) => {
449            output.push(REGEX);
450            write_string(output, s);
451        }
452        Form::Tagged(_, _) | Form::Metadata(_, _) => {
453            panic!("test encoder does not support tagged/metadata forms")
454        }
455    }
456}
457
458/// Encodes a canonical HALC artifact from parsed forms.
459///
460/// This mirrors the v1 format used by `decode_halc` so that integration tests
461/// can construct artifacts without depending on an external encoder.
462#[cfg(any(test, feature = "halc-encoder"))]
463pub fn encode_halc_module(
464    namespace: &str,
465    resource: &str,
466    source: &str,
467    forms: Vec<Form>,
468) -> Result<Vec<u8>, String> {
469    let forms = canonicalize_schema_references(namespace, forms)?;
470    for form in &forms {
471        validate_finite_form(form)?;
472    }
473    build_schema_index(namespace, &forms)?;
474    let mut payload = Vec::new();
475    write_string(&mut payload, namespace);
476    write_string(&mut payload, resource);
477    payload.extend_from_slice(&Sha256::digest(source.as_bytes()));
478    write_count(&mut payload, forms.len() as i32);
479    for form in forms {
480        write_value(&mut payload, &form);
481    }
482    let mut artifact = Vec::new();
483    artifact.extend_from_slice(MAGIC);
484    artifact.extend_from_slice(&FORMAT_VERSION.to_be_bytes());
485    artifact.extend_from_slice(&EXECUTABLE_FOUNDATION_FLAG.to_be_bytes());
486    artifact.extend_from_slice(&(payload.len() as u32).to_be_bytes());
487    artifact.extend_from_slice(&Sha256::digest(&payload));
488    artifact.extend_from_slice(&payload);
489    Ok(artifact)
490}
491
492#[cfg(any(test, feature = "halc-encoder"))]
493fn validate_finite_form(form: &Form) -> Result<(), String> {
494    match form {
495        Form::Float(value) if !value.is_finite() => Err("non-finite number".into()),
496        Form::Tagged(_, value) => validate_finite_form(value),
497        Form::Metadata(metadata, value) => {
498            validate_finite_form(metadata)?;
499            validate_finite_form(value)
500        }
501        Form::Map(entries) => {
502            for (key, value) in entries {
503                validate_finite_form(key)?;
504                validate_finite_form(value)?;
505            }
506            Ok(())
507        }
508        Form::Set(values) | Form::Vector(values) | Form::List(values) => {
509            for value in values {
510                validate_finite_form(value)?;
511            }
512            Ok(())
513        }
514        _ => Ok(()),
515    }
516}
517
518fn canonicalize_schema_references(
519    namespace: &str,
520    mut forms: Vec<Form>,
521) -> Result<Vec<Form>, String> {
522    let definitions: HashSet<String> = forms
523        .iter()
524        .filter_map(|form| {
525            let Form::List(items) = form else { return None };
526            let Form::Symbol(operator) = items.first()? else {
527                return None;
528            };
529            if !matches!(
530                operator.as_str(),
531                "def" | "defn" | "defmacro" | "defstruct" | "declare"
532            ) {
533                return None;
534            }
535            binding_name(items.get(1)?).map(str::to_owned)
536        })
537        .collect();
538    let schema_values: HashMap<String, usize> = forms
539        .iter()
540        .enumerate()
541        .filter_map(|(index, form)| {
542            let Form::List(items) = form else { return None };
543            if !matches!(items.first(), Some(Form::Symbol(operator)) if operator == "def") {
544                return None;
545            }
546            binding_name(items.get(1)?).map(|name| (name.to_owned(), index))
547        })
548        .collect();
549    let aliases = module_aliases(&forms);
550    let mut schema_roots = Vec::new();
551
552    for form in &mut forms {
553        let Form::List(items) = form else { continue };
554        let Some(Form::Symbol(operator)) = items.first() else {
555            continue;
556        };
557        if operator != "defn" {
558            continue;
559        }
560        let Some(Form::Metadata(metadata, _)) = items.get_mut(1) else {
561            continue;
562        };
563        let Form::Map(entries) = metadata.as_mut() else {
564            continue;
565        };
566        let Some((_, schema)) = entries
567            .iter_mut()
568            .find(|(key, _)| matches!(key, Form::Keyword(name) if name == "schema"))
569        else {
570            continue;
571        };
572        let Form::List(reference) = schema else {
573            continue;
574        };
575        if reference.len() != 2
576            || !matches!(&reference[0], Form::Symbol(operator) if operator == "var")
577        {
578            continue;
579        }
580        let Form::Symbol(target) = &reference[1] else {
581            continue;
582        };
583        let (qualifier, local) = target
584            .rsplit_once('/')
585            .map_or((None, target.as_str()), |(qualifier, local)| {
586                (Some(qualifier), local)
587            });
588        let target_namespace = match qualifier {
589            None | Some("-") => namespace,
590            Some(qualifier) => aliases.get(qualifier).map_or(qualifier, String::as_str),
591        };
592        if target_namespace == namespace && !definitions.contains(local) {
593            return Err(format!("schema Var does not exist: {target}"));
594        }
595        if target_namespace == namespace {
596            schema_roots.push(local.to_owned());
597        }
598        reference[1] = Form::Symbol(format!("{target_namespace}/{local}"));
599    }
600
601    let mut visited = HashSet::new();
602    while let Some(schema_name) = schema_roots.pop() {
603        if !visited.insert(schema_name.clone()) {
604            continue;
605        }
606        let Some(index) = schema_values.get(&schema_name).copied() else {
607            continue;
608        };
609        let Form::List(definition) = &mut forms[index] else {
610            continue;
611        };
612        let Some(schema_value) = definition.get_mut(2) else {
613            continue;
614        };
615        canonicalize_nested_schema_references(
616            schema_value,
617            namespace,
618            &aliases,
619            &definitions,
620            &mut schema_roots,
621        )?;
622    }
623    Ok(forms)
624}
625
626fn canonicalize_nested_schema_references(
627    form: &mut Form,
628    namespace: &str,
629    aliases: &HashMap<String, String>,
630    definitions: &HashSet<String>,
631    local_references: &mut Vec<String>,
632) -> Result<(), String> {
633    if let Form::List(reference) = form {
634        if reference.len() == 2
635            && matches!(&reference[0], Form::Symbol(operator) if operator == "var")
636        {
637            let Form::Symbol(target) = &reference[1] else {
638                return Ok(());
639            };
640            let original = target.clone();
641            let (qualifier, local) = original
642                .rsplit_once('/')
643                .map_or((None, original.as_str()), |(qualifier, local)| {
644                    (Some(qualifier), local)
645                });
646            let target_namespace = match qualifier {
647                None | Some("-") => namespace,
648                Some(qualifier) => aliases.get(qualifier).map_or(qualifier, String::as_str),
649            };
650            if target_namespace == namespace {
651                if !definitions.contains(local) {
652                    return Err(format!("schema Var does not exist: {original}"));
653                }
654                local_references.push(local.to_owned());
655            }
656            reference[1] = Form::Symbol(format!("{target_namespace}/{local}"));
657            return Ok(());
658        }
659    }
660
661    match form {
662        Form::Tagged(_, value) => canonicalize_nested_schema_references(
663            value,
664            namespace,
665            aliases,
666            definitions,
667            local_references,
668        ),
669        Form::Metadata(metadata, value) => {
670            canonicalize_nested_schema_references(
671                metadata,
672                namespace,
673                aliases,
674                definitions,
675                local_references,
676            )?;
677            canonicalize_nested_schema_references(
678                value,
679                namespace,
680                aliases,
681                definitions,
682                local_references,
683            )
684        }
685        Form::Map(entries) => {
686            for (key, value) in entries {
687                canonicalize_nested_schema_references(
688                    key,
689                    namespace,
690                    aliases,
691                    definitions,
692                    local_references,
693                )?;
694                canonicalize_nested_schema_references(
695                    value,
696                    namespace,
697                    aliases,
698                    definitions,
699                    local_references,
700                )?;
701            }
702            Ok(())
703        }
704        Form::Set(values) | Form::Vector(values) | Form::List(values) => {
705            for value in values {
706                canonicalize_nested_schema_references(
707                    value,
708                    namespace,
709                    aliases,
710                    definitions,
711                    local_references,
712                )?;
713            }
714            Ok(())
715        }
716        _ => Ok(()),
717    }
718}
719
720fn binding_name(form: &Form) -> Option<&str> {
721    match form {
722        Form::Symbol(name) => Some(name),
723        Form::Metadata(_, value) => binding_name(value),
724        _ => None,
725    }
726}
727
728fn module_aliases(forms: &[Form]) -> HashMap<String, String> {
729    let mut aliases = HashMap::new();
730    for form in forms {
731        let Form::List(declaration) = form else {
732            continue;
733        };
734        if !matches!(declaration.first(), Some(Form::Symbol(operator)) if operator == "ns") {
735            continue;
736        }
737        for clause in declaration.iter().skip(2) {
738            let Form::List(clause) = clause else { continue };
739            if !matches!(clause.first(), Some(Form::Keyword(keyword)) if keyword == "require") {
740                continue;
741            }
742            for spec in clause.iter().skip(1) {
743                let Form::Vector(spec) = spec else { continue };
744                let Some(Form::Symbol(target)) = spec.first() else {
745                    continue;
746                };
747                for option in spec[1..].chunks(2) {
748                    if let [Form::Keyword(key), Form::Symbol(alias)] = option {
749                        if key == "as" {
750                            aliases.insert(alias.clone(), target.clone());
751                        }
752                    }
753                }
754            }
755        }
756    }
757    aliases
758}
759
760fn build_schema_index(namespace: &str, forms: &[Form]) -> Result<HalcSchemaIndex, String> {
761    let mut index = HalcSchemaIndex::default();
762    let mut values = HashMap::new();
763    let mut roots = Vec::new();
764
765    for form in forms {
766        let Form::List(items) = form else { continue };
767        let Some(Form::Symbol(operator)) = items.first() else {
768            continue;
769        };
770        let Some(name) = items.get(1).and_then(binding_name) else {
771            continue;
772        };
773        let qualified_name = format!("{namespace}/{name}");
774        if operator == "def" {
775            if let Some(value) = items.get(2) {
776                values.insert(name.to_owned(), value.clone());
777            }
778            continue;
779        }
780        if operator != "defn" {
781            continue;
782        }
783        let Some(Form::Metadata(metadata, _)) = items.get(1) else {
784            continue;
785        };
786        let Form::Map(entries) = metadata.as_ref() else {
787            continue;
788        };
789        let Some(schema) = entries.iter().find_map(|(key, value)| {
790            matches!(key, Form::Keyword(name) if name == "schema").then_some(value)
791        }) else {
792            continue;
793        };
794        index.functions.insert(qualified_name, schema.clone());
795        collect_local_schema_references(schema, namespace, &mut roots);
796    }
797
798    let mut visited = HashSet::new();
799    while let Some(name) = roots.pop() {
800        if !visited.insert(name.clone()) {
801            continue;
802        }
803        let Some(value) = values.get(&name) else {
804            continue;
805        };
806        index
807            .definitions
808            .insert(format!("{namespace}/{name}"), value.clone());
809        collect_local_schema_references(value, namespace, &mut roots);
810    }
811    for (name, schema) in &index.definitions {
812        index.definition_types.insert(
813            name.clone(),
814            super::normalize_schema(schema)
815                .map_err(|error| format!("invalid schema {name}: {error}"))?,
816        );
817    }
818    for (name, schema) in &index.functions {
819        index.function_types.insert(
820            name.clone(),
821            super::normalize_schema(schema)
822                .map_err(|error| format!("invalid function schema {name}: {error}"))?,
823        );
824    }
825    Ok(index)
826}
827
828fn collect_local_schema_references(form: &Form, namespace: &str, output: &mut Vec<String>) {
829    if let Form::List(reference) = form {
830        if reference.len() == 2
831            && matches!(&reference[0], Form::Symbol(operator) if operator == "var")
832        {
833            if let Form::Symbol(target) = &reference[1] {
834                if let Some((qualifier, local)) = target.rsplit_once('/') {
835                    if qualifier == namespace {
836                        output.push(local.to_owned());
837                    }
838                }
839            }
840            return;
841        }
842    }
843    match form {
844        Form::Tagged(_, value) => collect_local_schema_references(value, namespace, output),
845        Form::Metadata(metadata, value) => {
846            collect_local_schema_references(metadata, namespace, output);
847            collect_local_schema_references(value, namespace, output);
848        }
849        Form::Map(entries) => {
850            for (key, value) in entries {
851                collect_local_schema_references(key, namespace, output);
852                collect_local_schema_references(value, namespace, output);
853            }
854        }
855        Form::Set(values) | Form::Vector(values) | Form::List(values) => {
856            for value in values {
857                collect_local_schema_references(value, namespace, output);
858            }
859        }
860        _ => {}
861    }
862}
863
864#[cfg(test)]
865mod tests {
866    use super::*;
867    use crate::kernel::parse;
868
869    fn artifact_payload(forms: Vec<Form>) -> Vec<u8> {
870        encode_halc_module("demo.ns", "demo.hal", "", forms).unwrap()
871    }
872
873    #[test]
874    fn round_trips_primitive_values() {
875        let cases = [
876            "nil",
877            "true",
878            "false",
879            "42",
880            "-7",
881            "3.14",
882            "\"hello\"",
883            "\\x",
884            ":key",
885            ":ns/key",
886            "symbol",
887            "ns/symbol",
888        ];
889        for source in cases {
890            let original = parse(source).unwrap();
891            let bytes = artifact_payload(vec![original.clone()]);
892            let decoded = decode_halc(&bytes).unwrap();
893            assert_eq!(decoded.forms.len(), 1);
894            assert_eq!(decoded.forms[0], original, "{source}");
895        }
896    }
897
898    #[test]
899    fn round_trips_collections() {
900        let original = parse("(do {:a [1 2] :b #{3 4}})").unwrap();
901        let bytes = artifact_payload(vec![original.clone()]);
902        let decoded = decode_halc(&bytes).unwrap();
903        assert_eq!(decoded.forms.len(), 1);
904        assert_eq!(decoded.forms[0], original);
905    }
906
907    #[test]
908    fn maps_use_canonical_member_order() {
909        let map_a = artifact_payload(vec![parse("{2 \"b\" 1 \"a\"}").unwrap()]);
910        let map_b = artifact_payload(vec![parse("{1 \"a\" 2 \"b\"}").unwrap()]);
911        assert_eq!(map_a, map_b);
912    }
913
914    #[test]
915    fn schema_var_references_are_checked_and_namespace_canonicalized() {
916        let source = "(ns demo.schema) \
917                      (def Customer [:map [:id :int]]) \
918                      (defn ^{:schema #'-/Customer} customer-id [customer] customer)";
919        let bytes = encode_halc_module(
920            "demo.schema",
921            "demo/schema.hal",
922            source,
923            crate::kernel::parse_forms(source).unwrap(),
924        )
925        .unwrap();
926        let module = decode_halc(&bytes).unwrap();
927        let Form::List(definition) = &module.forms[2] else {
928            panic!("expected defn");
929        };
930        let Form::Metadata(metadata, _) = &definition[1] else {
931            panic!("expected definition metadata");
932        };
933        let Form::Map(metadata) = metadata.as_ref() else {
934            panic!("expected metadata map");
935        };
936        let schema = metadata
937            .iter()
938            .find_map(|(key, value)| {
939                matches!(key, Form::Keyword(name) if name == "schema").then_some(value)
940            })
941            .unwrap();
942        assert_eq!(
943            schema,
944            &Form::List(vec![
945                Form::Symbol("var".into()),
946                Form::Symbol("demo.schema/Customer".into()),
947            ])
948        );
949
950        let missing = "(ns demo.schema) \
951                       (defn ^{:schema #'MissingSchema} invalid [value] value)";
952        assert_eq!(
953            encode_halc_module(
954                "demo.schema",
955                "demo/schema.hal",
956                missing,
957                crate::kernel::parse_forms(missing).unwrap(),
958            )
959            .unwrap_err(),
960            "schema Var does not exist: MissingSchema"
961        );
962    }
963
964    #[test]
965    fn nested_schema_var_references_are_canonicalized_and_checked() {
966        let source = "(ns demo.schema) \
967                      (def Address [:map [:street :str]]) \
968                      (def Customer [:map [:address #'-/Address]]) \
969                      (defn ^{:schema #'Customer} save [customer] customer)";
970        let bytes = encode_halc_module(
971            "demo.schema",
972            "demo/schema.hal",
973            source,
974            crate::kernel::parse_forms(source).unwrap(),
975        )
976        .unwrap();
977        let module = decode_halc(&bytes).unwrap();
978        assert!(module.forms[2]
979            .to_string()
980            .contains("(var demo.schema/Address)"));
981        assert_eq!(module.schemas.functions.len(), 1);
982        assert!(module.schemas.functions.contains_key("demo.schema/save"));
983        assert_eq!(module.schemas.definitions.len(), 2);
984        assert!(module
985            .schemas
986            .definitions
987            .contains_key("demo.schema/Address"));
988        assert!(module
989            .schemas
990            .definitions
991            .contains_key("demo.schema/Customer"));
992        assert!(matches!(
993            module.schemas.resolved_function_type("demo.schema/save"),
994            Some(super::super::SchemaType::Map(fields)) if fields.len() == 1
995        ));
996
997        let missing = "(ns demo.schema) \
998                       (def Customer [:map [:address #'MissingAddress]]) \
999                       (defn ^{:schema #'Customer} save [customer] customer)";
1000        assert_eq!(
1001            encode_halc_module(
1002                "demo.schema",
1003                "demo/schema.hal",
1004                missing,
1005                crate::kernel::parse_forms(missing).unwrap(),
1006            )
1007            .unwrap_err(),
1008            "schema Var does not exist: MissingAddress"
1009        );
1010
1011        let recursive = "(ns demo.schema) \
1012                         (def Node [:map [:children [:vector #'Node]]]) \
1013                         (defn ^{:schema #'Node} walk [node] node)";
1014        assert!(encode_halc_module(
1015            "demo.schema",
1016            "demo/schema.hal",
1017            recursive,
1018            crate::kernel::parse_forms(recursive).unwrap(),
1019        )
1020        .is_ok());
1021
1022        let malformed = "(ns demo.schema) \
1023                         (def Customer [:map [:name]]) \
1024                         (defn ^{:schema #'Customer} save [customer] customer)";
1025        assert_eq!(
1026            encode_halc_module(
1027                "demo.schema",
1028                "demo/schema.hal",
1029                malformed,
1030                crate::kernel::parse_forms(malformed).unwrap(),
1031            )
1032            .unwrap_err(),
1033            "invalid schema demo.schema/Customer: :map schema fields must be [name type] or [name properties type]"
1034        );
1035    }
1036
1037    #[test]
1038    fn round_trips_metadata() {
1039        let original = parse("^:dynamic *value*").unwrap();
1040        let bytes = artifact_payload(vec![original.clone()]);
1041        let decoded = decode_halc(&bytes).unwrap();
1042        assert_eq!(decoded.forms, vec![original]);
1043    }
1044
1045    #[test]
1046    fn rejects_bad_magic() {
1047        let mut bytes = artifact_payload(vec![Form::Nil]);
1048        bytes[0] = 0;
1049        assert!(decode_halc(&bytes).unwrap_err().contains("bad magic"));
1050    }
1051
1052    #[test]
1053    fn rejects_checksum_mismatch() {
1054        let mut bytes = artifact_payload(vec![Form::Nil]);
1055        let last = bytes.len() - 1;
1056        bytes[last] = bytes[last].wrapping_add(1);
1057        assert!(decode_halc(&bytes).unwrap_err().contains("checksum"));
1058    }
1059
1060    #[test]
1061    fn decodes_the_truffle_portable_format_golden_artifact() {
1062        // This is the canonical v1 artifact emitted by Truffle's
1063        // HalcArtifactTest.goldenBytesLockThePortableFormat. Keep this test
1064        // independent of Rust's test-only encoder: it is the cross-runtime
1065        // compatibility boundary, rather than a Rust encoder/decoder
1066        // round-trip.
1067        let bytes = hex_bytes(concat!(
1068            "48414c43000100010000013f57211e103028689092d59627fbba64015c289acd1bc5b2e7be27ec53d8bf4c35",
1069            "00000001740000000174e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
1070            "0000001100010203000000000000002a044004000000000000050000001e313233343536373839303132",
1071            "333435363738393031323334353637383930060000000668c3a172c3a008000000780901000000056d792e6e73",
1072            "000000066d792d73796d000a00000000026b77000b00000002030000000000000001060000000161000c00000002",
1073            "030000000000000001060000000161000d00000002030000000000000001060000000161030000000000000002",
1074            "060000000162000e00000002030000000000000001030000000000000002000f00000002030000000000000002",
1075            "060000000162030000000000000001060000000161001000000002030000000000000002030000000000000001",
1076            "001100000003612b62",
1077        ));
1078
1079        let module = decode_halc(&bytes).unwrap();
1080        assert_eq!(module.origin, HalcOrigin::Halc);
1081        assert_eq!(module.namespace, "t");
1082        assert_eq!(module.resource, "t");
1083        assert_eq!(module.forms.len(), 17);
1084        assert_eq!(module.forms[0], Form::Nil);
1085        assert_eq!(module.forms[3], Form::Number(42));
1086        assert_eq!(module.forms[6], Form::String("hárà".into()));
1087        assert_eq!(module.forms[7], Form::Character('x'));
1088        assert_eq!(module.forms[8], Form::Symbol("my.ns/my-sym".into()));
1089        assert_eq!(module.forms[9], Form::Keyword("kw".into()));
1090        assert_eq!(module.forms[16], Form::Regex("a+b".into()));
1091    }
1092
1093    #[test]
1094    fn legacy_hir_magic_decodes_but_encoding_always_uses_halc_magic() {
1095        let halc = artifact_payload(vec![Form::Number(42)]);
1096        let mut legacy = halc.clone();
1097        legacy[..4].copy_from_slice(LEGACY_MAGIC);
1098
1099        assert_eq!(decode_halc(&legacy).unwrap().origin, HalcOrigin::LegacyHir);
1100        assert_eq!(&halc[..4], MAGIC);
1101    }
1102
1103    #[test]
1104    fn shared_cross_runtime_goldens_decode() {
1105        let complete = std::fs::read(crate::spec_registry::require(
1106            "01-lang/009-halc/draft/conformance/golden/complete.halc",
1107        ))
1108        .expect("complete HALC golden is readable");
1109        let legacy = std::fs::read(crate::spec_registry::require(
1110            "01-lang/009-halc/draft/conformance/golden/legacy-v1.hir",
1111        ))
1112        .expect("legacy HIR golden is readable");
1113        let current = decode_halc(&complete).unwrap();
1114        assert_eq!(current.origin, HalcOrigin::Halc);
1115        assert_eq!(current.namespace, "halc.conformance.complete");
1116        assert_eq!(current.resource, "conformance/complete.hal");
1117        assert_eq!(decode_halc(&legacy).unwrap().origin, HalcOrigin::LegacyHir);
1118    }
1119
1120    #[test]
1121    fn registry_golden_matches_rust_encoding() {
1122        let source_path =
1123            crate::spec_registry::require("01-lang/009-halc/draft/conformance/complete.hal");
1124        let source = std::fs::read_to_string(source_path).expect("HALC source is readable");
1125        let forms = crate::kernel::parse_forms(&source).expect("HALC source parses");
1126        let encoded = encode_halc_module(
1127            "halc.conformance.complete",
1128            "conformance/complete.hal",
1129            &source,
1130            forms,
1131        )
1132        .expect("HALC source encodes");
1133        let expected = std::fs::read(crate::spec_registry::require(
1134            "01-lang/009-halc/draft/conformance/golden/complete.halc",
1135        ))
1136        .expect("HALC golden is readable");
1137        assert_eq!(expected, encoded);
1138    }
1139
1140    fn hex_bytes(hex: &str) -> Vec<u8> {
1141        (0..hex.len())
1142            .step_by(2)
1143            .map(|index| u8::from_str_radix(&hex[index..index + 2], 16).unwrap())
1144            .collect()
1145    }
1146}