1use std::collections::{BTreeMap, BTreeSet};
55use std::sync::Arc;
56
57use beck_diag::{FileId, Span};
58use serde::{Deserialize, Serialize};
59
60use crate::command;
61use crate::core::{Arm, Const, Core, CoreKind, Pattern, Prim, VarId};
62use crate::split::Placed;
63use crate::ty::{Tier, Ty};
64
65pub const FORMAT: u32 = 1;
69
70#[derive(Clone, Debug)]
72pub struct Bundle {
73 pub component: Arc<str>,
76 pub wire_id: String,
80 pub view: Core,
82 pub validate: Core,
84 pub fold: Core,
86 pub init: Core,
89 pub defs: BTreeMap<Arc<str>, Core>,
91 pub command: command::Schema,
94 pub optimistic: bool,
98}
99
100#[derive(Clone, Debug, PartialEq, Eq)]
102pub enum BadBundle {
103 Format {
105 found: u32,
106 expected: u32,
107 },
108 Shape {
110 found: String,
111 expected: String,
112 },
113 Malformed(String),
114}
115
116impl std::fmt::Display for BadBundle {
117 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118 match self {
119 BadBundle::Format { found, expected } => write!(
120 f,
121 "this bundle is format {found} and this kernel reads format {expected}"
122 ),
123 BadBundle::Shape { found, expected } => write!(
124 f,
125 "this bundle was compiled by a different compiler \
126 (primitives {found}, this kernel {expected})"
127 ),
128 BadBundle::Malformed(why) => write!(f, "this bundle is malformed: {why}"),
129 }
130 }
131}
132
133impl std::error::Error for BadBundle {}
134
135pub fn shape_id() -> String {
139 let mut hasher = blake3::Hasher::new();
140 for (_, prim, _) in crate::prelude::prims() {
141 hasher.update(prim.name().as_bytes());
142 hasher.update(b"=");
143 hasher.update((prim as u32).to_le_bytes().as_slice());
144 hasher.update(b";");
145 }
146 hasher.finalize().to_hex()[..16].to_string()
147}
148
149impl Bundle {
150 pub fn of(placed: &Placed) -> Bundle {
152 let roles = &placed.roles;
153 let mut defs = BTreeMap::new();
154 let mut seen = BTreeSet::new();
155 for role in [&roles.view, &roles.validate, &roles.fold, &roles.init] {
156 reachable(role, placed, &mut seen, &mut defs);
157 }
158 Bundle {
159 component: roles.page_name.clone(),
160 wire_id: placed.wire_id.clone(),
161 view: roles.view.clone(),
162 validate: roles.validate.clone(),
163 fold: roles.fold.clone(),
164 init: roles.init.clone(),
165 defs,
166 command: command::Schema::of(placed),
167 optimistic: placed.render.optimistic,
170 }
171 }
172
173 pub fn to_bytes(&self) -> Vec<u8> {
174 postcard::to_allocvec(&Wire::of(self)).expect("a bundle is encodable")
177 }
178
179 pub fn from_bytes(bytes: &[u8]) -> Result<Bundle, BadBundle> {
180 let wire: Wire =
181 postcard::from_bytes(bytes).map_err(|e| BadBundle::Malformed(e.to_string()))?;
182 if wire.format != FORMAT {
183 return Err(BadBundle::Format {
184 found: wire.format,
185 expected: FORMAT,
186 });
187 }
188 let expected = shape_id();
189 if wire.shape != expected {
190 return Err(BadBundle::Shape {
191 found: wire.shape,
192 expected,
193 });
194 }
195 Ok(wire.to_bundle())
196 }
197
198 pub fn nodes(&self) -> usize {
201 let mut n = 0;
202 for code in [&self.view, &self.validate, &self.fold, &self.init]
203 .into_iter()
204 .chain(self.defs.values())
205 {
206 count(code, &mut n);
207 }
208 n
209 }
210}
211
212fn reachable(
214 code: &Core,
215 placed: &Placed,
216 seen: &mut BTreeSet<Arc<str>>,
217 defs: &mut BTreeMap<Arc<str>, Core>,
218) {
219 let mut names = Vec::new();
220 globals(code, &mut names);
221 for name in names {
222 if !seen.insert(name.clone()) {
223 continue;
224 }
225 let Some(def) = placed.program.defs.get(&name) else {
229 continue;
230 };
231 defs.insert(name, def.body.clone());
232 reachable(&def.body, placed, seen, defs);
233 }
234}
235
236fn globals(code: &Core, out: &mut Vec<Arc<str>>) {
237 if let CoreKind::Global(name) = &code.kind {
238 out.push(name.clone());
239 }
240 walk(code, &mut |c| globals(c, out));
241}
242
243fn count(code: &Core, n: &mut usize) {
244 *n += 1;
245 walk(code, &mut |c| count(c, n));
246}
247
248fn walk(code: &Core, f: &mut dyn FnMut(&Core)) {
254 match &code.kind {
255 CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
256 CoreKind::Lam { body, .. } => f(body),
257 CoreKind::App { func, args } => {
258 f(func);
259 args.iter().for_each(&mut *f);
260 }
261 CoreKind::Prim { args, .. } => args.iter().for_each(&mut *f),
262 CoreKind::Let { value, body, .. } => {
263 f(value);
264 f(body);
265 }
266 CoreKind::If { cond, then, alt } => {
267 f(cond);
268 f(then);
269 f(alt);
270 }
271 CoreKind::Match { scrutinee, arms } => {
272 f(scrutinee);
273 for arm in arms {
274 arm.exprs().for_each(&mut *f);
275 }
276 }
277 CoreKind::Make { fields, .. } => fields.iter().for_each(|(_, c)| f(c)),
278 CoreKind::Field { base, .. } => f(base),
279 CoreKind::With { base, fields } => {
280 f(base);
281 fields.iter().for_each(|(_, c)| f(c));
282 }
283 CoreKind::ListLit(xs) => xs.iter().for_each(&mut *f),
284 CoreKind::MapLit(kvs) => kvs.iter().for_each(|(k, v)| {
285 f(k);
286 f(v);
287 }),
288 }
289}
290
291#[derive(Serialize, Deserialize)]
294struct Wire {
295 format: u32,
296 shape: String,
297 component: String,
298 wire_id: String,
299 view: WCore,
300 validate: WCore,
301 fold: WCore,
302 init: WCore,
303 defs: Vec<(String, WCore)>,
304 command: command::Schema,
305 optimistic: bool,
306}
307
308impl Wire {
309 fn of(b: &Bundle) -> Wire {
310 Wire {
311 format: FORMAT,
312 shape: shape_id(),
313 component: b.component.to_string(),
314 wire_id: b.wire_id.clone(),
315 view: WCore::of(&b.view),
316 validate: WCore::of(&b.validate),
317 fold: WCore::of(&b.fold),
318 init: WCore::of(&b.init),
319 defs: b
320 .defs
321 .iter()
322 .map(|(name, code)| (name.to_string(), WCore::of(code)))
323 .collect(),
324 command: b.command.clone(),
325 optimistic: b.optimistic,
326 }
327 }
328
329 fn to_bundle(&self) -> Bundle {
330 Bundle {
331 component: Arc::from(self.component.as_str()),
332 wire_id: self.wire_id.clone(),
333 view: self.view.to_core(),
334 validate: self.validate.to_core(),
335 fold: self.fold.to_core(),
336 init: self.init.to_core(),
337 defs: self
338 .defs
339 .iter()
340 .map(|(name, code)| (Arc::from(name.as_str()), code.to_core()))
341 .collect(),
342 command: self.command.clone(),
343 optimistic: self.optimistic,
344 }
345 }
346}
347
348#[derive(Serialize, Deserialize)]
350struct WCore {
351 kind: WKind,
352 span: (u32, u32, u32),
354 tier: u8,
355 last_use: bool,
359 order: u32,
360 locals: u32,
361}
362
363#[derive(Serialize, Deserialize)]
364enum WKind {
365 Const(WConst),
366 Var(VarId),
367 Global(String),
368 Lam {
369 params: Vec<VarId>,
370 body: Box<WCore>,
371 },
372 App {
373 func: Box<WCore>,
374 args: Vec<WCore>,
375 },
376 Prim {
377 op: WPrim,
378 args: Vec<WCore>,
379 },
380 Let {
381 var: VarId,
382 value: Box<WCore>,
383 body: Box<WCore>,
384 },
385 If {
386 cond: Box<WCore>,
387 then: Box<WCore>,
388 alt: Box<WCore>,
389 },
390 Match {
391 scrutinee: Box<WCore>,
392 arms: Vec<WArm>,
393 },
394 Make {
395 ty: String,
396 variant: Option<String>,
397 fields: Vec<(String, WCore)>,
398 },
399 Field {
400 base: Box<WCore>,
401 name: String,
402 },
403 With {
404 base: Box<WCore>,
405 fields: Vec<(String, WCore)>,
406 },
407 ListLit(Vec<WCore>),
408 MapLit(Vec<(WCore, WCore)>),
409}
410
411#[derive(Serialize, Deserialize)]
412enum WConst {
413 Unit,
414 Bool(bool),
415 Int(i64),
416 Float(u64),
418 Str(String),
419}
420
421#[derive(Serialize, Deserialize)]
422struct WArm {
423 pattern: WPattern,
424 guard: Option<WCore>,
425 body: WCore,
426 span: (u32, u32, u32),
427}
428
429#[derive(Serialize, Deserialize)]
430enum WPattern {
431 Wildcard,
432 Bind(VarId),
433 Const(WConst),
434 Ctor {
435 variant: String,
436 binds: Vec<(String, WPattern)>,
437 },
438 At {
439 var: VarId,
440 inner: Box<WPattern>,
441 },
442 Or(Vec<WPattern>),
443 List {
444 items: Vec<WPattern>,
445 rest: Option<Option<VarId>>,
446 },
447}
448
449fn span_of(s: Span) -> (u32, u32, u32) {
450 (s.file.0, s.start, s.end)
451}
452
453fn to_span(s: (u32, u32, u32)) -> Span {
454 Span {
455 file: FileId(s.0),
456 start: s.1,
457 end: s.2,
458 }
459}
460
461impl WConst {
462 fn of(c: &Const) -> WConst {
463 match c {
464 Const::Unit => WConst::Unit,
465 Const::Bool(b) => WConst::Bool(*b),
466 Const::Int(i) => WConst::Int(*i),
467 Const::Float(f) => WConst::Float(f.to_bits()),
468 Const::Str(s) => WConst::Str(s.to_string()),
469 }
470 }
471
472 fn to_const(&self) -> Const {
473 match self {
474 WConst::Unit => Const::Unit,
475 WConst::Bool(b) => Const::Bool(*b),
476 WConst::Int(i) => Const::Int(*i),
477 WConst::Float(bits) => Const::Float(f64::from_bits(*bits)),
478 WConst::Str(s) => Const::Str(Arc::from(s.as_str())),
479 }
480 }
481}
482
483impl WPattern {
484 fn of(p: &Pattern) -> WPattern {
485 match p {
486 Pattern::Wildcard => WPattern::Wildcard,
487 Pattern::Bind(v) => WPattern::Bind(*v),
488 Pattern::Const(c) => WPattern::Const(WConst::of(c)),
489 Pattern::Ctor { variant, binds } => WPattern::Ctor {
490 variant: variant.to_string(),
491 binds: binds
492 .iter()
493 .map(|(f, p)| (f.to_string(), WPattern::of(p)))
494 .collect(),
495 },
496 Pattern::At { var, inner } => WPattern::At {
497 var: *var,
498 inner: Box::new(WPattern::of(inner)),
499 },
500 Pattern::Or(alts) => WPattern::Or(alts.iter().map(WPattern::of).collect()),
501 Pattern::List { items, rest } => WPattern::List {
502 items: items.iter().map(WPattern::of).collect(),
503 rest: *rest,
504 },
505 }
506 }
507
508 fn to_pattern(&self) -> Pattern {
509 match self {
510 WPattern::Wildcard => Pattern::Wildcard,
511 WPattern::Bind(v) => Pattern::Bind(*v),
512 WPattern::Const(c) => Pattern::Const(c.to_const()),
513 WPattern::Ctor { variant, binds } => Pattern::Ctor {
514 variant: Arc::from(variant.as_str()),
515 binds: binds
516 .iter()
517 .map(|(f, p)| (Arc::from(f.as_str()), p.to_pattern()))
518 .collect(),
519 },
520 WPattern::At { var, inner } => Pattern::At {
521 var: *var,
522 inner: Box::new(inner.to_pattern()),
523 },
524 WPattern::Or(alts) => Pattern::Or(alts.iter().map(WPattern::to_pattern).collect()),
525 WPattern::List { items, rest } => Pattern::List {
526 items: items.iter().map(WPattern::to_pattern).collect(),
527 rest: *rest,
528 },
529 }
530 }
531}
532
533impl WCore {
534 fn of(c: &Core) -> WCore {
535 WCore {
536 kind: WKind::of(&c.kind),
537 span: span_of(c.span),
538 tier: c.tier as u8,
539 last_use: c.last_use,
540 order: c.order,
541 locals: c.locals,
542 }
543 }
544
545 fn to_core(&self) -> Core {
546 let mut core = Core::new(self.kind.to_kind(), Ty::unit(), to_span(self.span));
550 core.tier = tier_of(self.tier);
551 core.last_use = self.last_use;
552 core.order = self.order;
553 core.locals = self.locals;
554 core
555 }
556}
557
558fn tier_of(byte: u8) -> Tier {
559 match byte {
563 b if b == Tier::Client as u8 => Tier::Client,
564 b if b == Tier::Server as u8 => Tier::Server,
565 b if b == Tier::Data as u8 => Tier::Data,
566 _ => Tier::Any,
567 }
568}
569
570impl WKind {
571 fn of(k: &CoreKind) -> WKind {
572 let fields = |fs: &Vec<(Arc<str>, Core)>| {
573 fs.iter()
574 .map(|(n, c)| (n.to_string(), WCore::of(c)))
575 .collect()
576 };
577 match k {
578 CoreKind::Const(c) => WKind::Const(WConst::of(c)),
579 CoreKind::Var(v) => WKind::Var(*v),
580 CoreKind::Global(name) => WKind::Global(name.to_string()),
581 CoreKind::Lam { params, body } => WKind::Lam {
582 params: params.to_vec(),
583 body: Box::new(WCore::of(body)),
584 },
585 CoreKind::App { func, args } => WKind::App {
586 func: Box::new(WCore::of(func)),
587 args: args.iter().map(WCore::of).collect(),
588 },
589 CoreKind::Prim { op, args } => WKind::Prim {
590 op: WPrim(*op),
591 args: args.iter().map(WCore::of).collect(),
592 },
593 CoreKind::Let { var, value, body } => WKind::Let {
594 var: *var,
595 value: Box::new(WCore::of(value)),
596 body: Box::new(WCore::of(body)),
597 },
598 CoreKind::If { cond, then, alt } => WKind::If {
599 cond: Box::new(WCore::of(cond)),
600 then: Box::new(WCore::of(then)),
601 alt: Box::new(WCore::of(alt)),
602 },
603 CoreKind::Match { scrutinee, arms } => WKind::Match {
604 scrutinee: Box::new(WCore::of(scrutinee)),
605 arms: arms
606 .iter()
607 .map(|a| WArm {
608 pattern: WPattern::of(&a.pattern),
609 guard: a.guard.as_ref().map(WCore::of),
610 body: WCore::of(&a.body),
611 span: span_of(a.span),
612 })
613 .collect(),
614 },
615 CoreKind::Make {
616 ty,
617 variant,
618 fields: fs,
619 } => WKind::Make {
620 ty: ty.to_string(),
621 variant: variant.as_ref().map(|v| v.to_string()),
622 fields: fields(fs),
623 },
624 CoreKind::Field { base, name } => WKind::Field {
625 base: Box::new(WCore::of(base)),
626 name: name.to_string(),
627 },
628 CoreKind::With { base, fields: fs } => WKind::With {
629 base: Box::new(WCore::of(base)),
630 fields: fields(fs),
631 },
632 CoreKind::ListLit(xs) => WKind::ListLit(xs.iter().map(WCore::of).collect()),
633 CoreKind::MapLit(kvs) => WKind::MapLit(
634 kvs.iter()
635 .map(|(k, v)| (WCore::of(k), WCore::of(v)))
636 .collect(),
637 ),
638 }
639 }
640
641 fn to_kind(&self) -> CoreKind {
642 let fields = |fs: &Vec<(String, WCore)>| {
643 fs.iter()
644 .map(|(n, c)| (Arc::from(n.as_str()), c.to_core()))
645 .collect()
646 };
647 match self {
648 WKind::Const(c) => CoreKind::Const(c.to_const()),
649 WKind::Var(v) => CoreKind::Var(*v),
650 WKind::Global(name) => CoreKind::Global(Arc::from(name.as_str())),
651 WKind::Lam { params, body } => CoreKind::Lam {
652 params: params.as_slice().into(),
653 body: Arc::new(body.to_core()),
654 },
655 WKind::App { func, args } => CoreKind::App {
656 func: Box::new(func.to_core()),
657 args: args.iter().map(WCore::to_core).collect(),
658 },
659 WKind::Prim { op, args } => CoreKind::Prim {
660 op: op.0,
661 args: args.iter().map(WCore::to_core).collect(),
662 },
663 WKind::Let { var, value, body } => CoreKind::Let {
664 var: *var,
665 value: Box::new(value.to_core()),
666 body: Box::new(body.to_core()),
667 },
668 WKind::If { cond, then, alt } => CoreKind::If {
669 cond: Box::new(cond.to_core()),
670 then: Box::new(then.to_core()),
671 alt: Box::new(alt.to_core()),
672 },
673 WKind::Match { scrutinee, arms } => CoreKind::Match {
674 scrutinee: Box::new(scrutinee.to_core()),
675 arms: arms
676 .iter()
677 .map(|a| Arm {
678 pattern: a.pattern.to_pattern(),
679 guard: a.guard.as_ref().map(WCore::to_core),
680 body: a.body.to_core(),
681 span: to_span(a.span),
682 })
683 .collect(),
684 },
685 WKind::Make {
686 ty,
687 variant,
688 fields: fs,
689 } => CoreKind::Make {
690 ty: Arc::from(ty.as_str()),
691 variant: variant.as_ref().map(|v| Arc::from(v.as_str())),
692 fields: fields(fs),
693 },
694 WKind::Field { base, name } => CoreKind::Field {
695 base: Box::new(base.to_core()),
696 name: Arc::from(name.as_str()),
697 },
698 WKind::With { base, fields: fs } => CoreKind::With {
699 base: Box::new(base.to_core()),
700 fields: fields(fs),
701 },
702 WKind::ListLit(xs) => CoreKind::ListLit(xs.iter().map(WCore::to_core).collect()),
703 WKind::MapLit(kvs) => CoreKind::MapLit(
704 kvs.iter()
705 .map(|(k, v)| (k.to_core(), v.to_core()))
706 .collect(),
707 ),
708 }
709 }
710}
711
712#[derive(Clone, Copy)]
718struct WPrim(Prim);
719
720impl Serialize for WPrim {
721 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
722 s.serialize_u32(self.0 as u32)
723 }
724}
725
726impl<'de> Deserialize<'de> for WPrim {
727 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<WPrim, D::Error> {
728 let n = u32::deserialize(d)?;
729 table()
730 .get(n as usize)
731 .copied()
732 .flatten()
733 .map(WPrim)
734 .ok_or_else(|| serde::de::Error::custom(format!("no primitive is numbered {n}")))
735 }
736}
737
738fn table() -> &'static [Option<Prim>] {
744 static TABLE: std::sync::OnceLock<Vec<Option<Prim>>> = std::sync::OnceLock::new();
745 TABLE.get_or_init(|| {
746 let prims: Vec<Prim> = crate::prelude::prims()
747 .into_iter()
748 .map(|(_, p, _)| p)
749 .collect();
750 let width = prims.iter().map(|p| *p as usize).max().map_or(0, |m| m + 1);
751 let mut table = vec![None; width];
752 for p in prims {
753 table[p as usize] = Some(p);
754 }
755 table
756 })
757}
758
759#[cfg(test)]
760mod tests {
761 use super::*;
762
763 fn placed(src: &str) -> Placed {
764 let (placed, diags, map) = crate::compile_str("t.beck", src);
765 assert!(!diags.has_errors(), "{}", diags.render(&map));
766 placed.expect("compiles")
767 }
768
769 const TODO: &str = r#"
770model Todo:
771 id: Str
772 text: Str
773 done: Bool
774
775model State:
776 todos: list[Todo]
777
778union Command:
779 Add(id: Str, text: Str)
780 Toggle(id: Str)
781
782union Event:
783 Added(id: Str, text: Str)
784 Toggled(id: Str)
785
786union Rejection:
787 Blank
788
789def apply_event(s: State, env: Envelope[Event]) -> State:
790 match env.body:
791 case Added(id, text):
792 return s.with(todos=list_append(s.todos, Todo(id=id, text=text, done=False)))
793 case Toggled(id):
794 return s
795
796def validate(s: State, p: Proposal) -> Result[list[Event], Rejection]:
797 match p.command:
798 case Add(id, text):
799 if str_is_empty(text):
800 return Err(error=Blank)
801 return Ok(value=[Added(id=id, text=text)])
802 case Toggle(id):
803 return Ok(value=[Toggled(id=id)])
804
805def label(t: Todo) -> Str:
806 return t.text
807
808def render(s: State) -> Html:
809 return ui:
810 ul:
811 for t in s.todos:
812 li: label(t)
813
814@on(server)
815proposals: Stream[Proposal] = merge_clients()
816
817@on(server)
818events: Stream[Event] = decide(proposals, todos, validate)
819
820@on(data)
821todos: Signal[State] = durable(fold(apply_event, State(todos=[]), events))
822
823@on(client)
824page: Signal[Html] = signal_map(todos, render)
825"#;
826
827 #[test]
828 fn a_bundle_round_trips_through_its_bytes() {
829 let placed = placed(TODO);
830 let bundle = Bundle::of(&placed);
831 let bytes = bundle.to_bytes();
832 let back = Bundle::from_bytes(&bytes).expect("reads back");
833
834 assert_eq!(back.component, bundle.component);
835 assert_eq!(back.wire_id, bundle.wire_id);
836 assert_eq!(back.optimistic, bundle.optimistic);
837 assert_eq!(back.nodes(), bundle.nodes());
838 assert_eq!(
839 back.defs.keys().collect::<Vec<_>>(),
840 bundle.defs.keys().collect::<Vec<_>>()
841 );
842 }
843
844 #[test]
845 fn a_bundle_carries_what_its_roles_reach_and_not_the_rest() {
846 let placed = placed(TODO);
847 let bundle = Bundle::of(&placed);
848 assert!(
850 bundle.defs.contains_key("label"),
851 "{:?}",
852 bundle.defs.keys()
853 );
854 assert!(!bundle.defs.contains_key("page"));
856 }
857
858 #[test]
859 fn a_bundle_from_a_differently_numbered_compiler_is_refused() {
860 let placed = placed(TODO);
861 let bytes = Bundle::of(&placed).to_bytes();
862 let mut wire: Wire = postcard::from_bytes(&bytes).expect("decodes");
863 wire.shape = "0000000000000000".to_string();
864 let forged = postcard::to_allocvec(&wire).expect("encodes");
865
866 match Bundle::from_bytes(&forged) {
867 Err(BadBundle::Shape { found, .. }) => assert_eq!(found, "0000000000000000"),
868 other => panic!("expected a shape refusal, got {other:?}"),
869 }
870 }
871
872 #[test]
873 fn a_bundle_from_a_later_format_is_refused() {
874 let placed = placed(TODO);
875 let bytes = Bundle::of(&placed).to_bytes();
876 let mut wire: Wire = postcard::from_bytes(&bytes).expect("decodes");
877 wire.format = FORMAT + 1;
878 let forged = postcard::to_allocvec(&wire).expect("encodes");
879
880 assert!(matches!(
881 Bundle::from_bytes(&forged),
882 Err(BadBundle::Format { .. })
883 ));
884 }
885}