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#[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 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#[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 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}