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