1use std::collections::{BTreeMap, BTreeSet};
21
22use serde::{Deserialize, Serialize};
23
24use crate::{hash_def, verify_module, Def, Module, WeftError, WeftHash};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Package {
29 pub name: String,
31 pub exports: BTreeMap<String, WeftHash>,
33 pub defs: BTreeMap<WeftHash, Def>,
35}
36
37impl Package {
38 pub fn build(
40 name: &str,
41 defs: Vec<Def>,
42 exports: Vec<(&str, usize)>,
43 ) -> Result<Self, WeftError> {
44 let hashed: Vec<(WeftHash, Def)> = defs.into_iter().map(|d| (hash_def(&d), d)).collect();
45 let mut map = BTreeMap::new();
46 let mut ex = BTreeMap::new();
47 for (petname, idx) in exports {
48 let (h, _) = hashed.get(idx).ok_or(WeftError::UnknownEntry)?;
49 ex.insert(petname.to_string(), *h);
50 }
51 for (h, d) in hashed {
52 map.insert(h, d);
53 }
54 let pkg = Package {
55 name: name.to_string(),
56 exports: ex,
57 defs: map,
58 };
59 pkg.verify()?;
60 Ok(pkg)
61 }
62
63 pub fn verify(&self) -> Result<(), WeftError> {
67 for (h, d) in &self.defs {
68 if hash_def(d) != *h {
69 return Err(WeftError::Type(format!("def {h} does not hash to its key")));
70 }
71 }
72 for (name, h) in &self.exports {
73 if !self.defs.contains_key(h) {
74 return Err(WeftError::Type(format!(
75 "export '{name}' points outside the package"
76 )));
77 }
78 }
79 if let Some(first) = self
81 .exports
82 .values()
83 .next()
84 .or_else(|| self.defs.keys().next())
85 {
86 let m = Module {
87 defs: self.defs.clone(),
88 entry: *first,
89 };
90 verify_module(&m)?;
91 }
92 Ok(())
93 }
94
95 pub fn export(&self, petname: &str) -> Option<WeftHash> {
97 self.exports.get(petname).copied()
98 }
99}
100
101pub fn link(
105 packages: &[Package],
106 local_defs: Vec<Def>,
107 entry_index: usize,
108) -> Result<Module, WeftError> {
109 let mut defs: BTreeMap<WeftHash, Def> = BTreeMap::new();
110 for p in packages {
111 p.verify()?;
112 for (h, d) in &p.defs {
113 defs.insert(*h, d.clone());
114 }
115 }
116 let mut entry = None;
117 for (i, d) in local_defs.into_iter().enumerate() {
118 let h = hash_def(&d);
119 if i == entry_index {
120 entry = Some(h);
121 }
122 defs.insert(h, d);
123 }
124 let entry = entry.ok_or(WeftError::UnknownEntry)?;
125 let module = Module { defs, entry };
126 let mut keep: BTreeSet<WeftHash> = BTreeSet::new();
128 let mut stack = vec![entry];
129 while let Some(h) = stack.pop() {
130 if !keep.insert(h) {
131 continue;
132 }
133 if let Some(d) = module.defs.get(&h) {
134 for c in crate::callees_of(&d.body) {
135 stack.push(c);
136 }
137 }
138 }
139 let module = Module {
140 defs: module
141 .defs
142 .into_iter()
143 .filter(|(h, _)| keep.contains(h))
144 .collect(),
145 entry,
146 };
147 verify_module(&module)?;
148 Ok(module)
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154 use crate::{PrimOp, Term, Ty};
155
156 fn double_def() -> Def {
157 Def {
158 params: vec![Ty::Int],
159 ret: Ty::Int,
160 effects: BTreeSet::new(),
161 body: Term::Prim(PrimOp::Mul, vec![Term::Var(0), Term::Int(2)]),
162 pre: None,
163 post: None,
164 }
165 }
166
167 #[test]
168 fn packages_build_verify_and_link_by_hash() {
169 let pkg = Package::build("math-basics", vec![double_def()], vec![("double", 0)]).unwrap();
170 let double = pkg.export("double").expect("exported");
171 let entry = Def {
173 params: vec![],
174 ret: Ty::Int,
175 effects: BTreeSet::new(),
176 body: Term::Call(double, vec![Term::Int(21)]),
177 pre: None,
178 post: None,
179 };
180 let module = link(&[pkg], vec![entry], 0).expect("links + verifies");
181 let out = crate::eval_call(&module, module.entry, vec![], 10_000).unwrap();
182 assert_eq!(out.value, crate::Value::Int(42));
183 }
184
185 #[test]
186 fn linking_is_a_set_union_no_diamond_problem() {
187 let a = Package::build("a", vec![double_def()], vec![("dbl", 0)]).unwrap();
189 let b = Package::build("b", vec![double_def()], vec![("twice", 0)]).unwrap();
190 assert_eq!(
191 a.export("dbl"),
192 b.export("twice"),
193 "same bytes, same identity"
194 );
195 let entry = Def {
196 params: vec![],
197 ret: Ty::Int,
198 effects: BTreeSet::new(),
199 body: Term::Call(a.export("dbl").unwrap(), vec![Term::Int(3)]),
200 pre: None,
201 post: None,
202 };
203 let m = link(&[a, b], vec![entry], 0).unwrap();
204 assert_eq!(m.defs.len(), 2, "one shared def + the entry");
205 }
206
207 #[test]
208 fn tampered_defs_are_refused() {
209 let mut pkg = Package::build("m", vec![double_def()], vec![("double", 0)]).unwrap();
210 let key = *pkg.defs.keys().next().unwrap();
212 pkg.defs.get_mut(&key).unwrap().body = Term::Int(666);
213 assert!(pkg.verify().is_err(), "hash mismatch must be fatal");
214 }
215}