1use alloc::string::String;
4use alloc::vec::Vec;
5
6use crate::codec::{
7 crc32, write_def_id, write_i32, write_str, write_u8, write_u16, write_u32, write_u64,
8};
9use crate::definition::{
10 AddressDef, AddressPath, AliasEntry, CallAtom, CapabilityParam, ContainerDef, DirectEffects,
11 EffectRowEntry, ExternalFnDef, FrameShapeDef, GlobalVarDef, LineEntry, ListDef, ListItemDef,
12 ScopeLineTable, StructShapeDef,
13};
14use crate::id::DefinitionId;
15use crate::line::{LineContent, LinePart, PluralCategory, SelectKey};
16use crate::story::StoryData;
17use crate::value::{ListValue, MapKey, ProjSegment, Value, ValueType};
18
19use super::{
20 CAP_PARAM_ANY, CAT_FEW, CAT_MANY, CAT_ONE, CAT_OTHER, CAT_TWO, CAT_ZERO, HANDLE_PARAM_NONE,
21 HEADER_PREAMBLE, KEY_CARDINAL, KEY_EXACT, KEY_KEYWORD, KEY_ORDINAL, LINE_PLAIN, LINE_TEMPLATE,
22 MAGIC, PART_LITERAL, PART_SELECT, PART_SLOT, PART_SPAN, PROJ_SEG_INDEX, PROJ_SEG_KEY,
23 SECTION_COUNT, SECTION_ENTRY_SIZE, SectionKind, VAL_ARRAY, VAL_BOOL, VAL_CLOSURE,
24 VAL_DIVERT_TARGET, VAL_FLOAT, VAL_FN_REF, VAL_FRAGMENT_REF, VAL_HANDLE, VAL_INT, VAL_LIST,
25 VAL_MAP, VAL_MAT2, VAL_MAT3, VAL_MAT4, VAL_NULL, VAL_OPTION, VAL_PROJECTION, VAL_QUAT,
26 VAL_RANGE, VAL_RECORD, VAL_STRING, VAL_VAR_POINTER, VAL_VEC2, VAL_VEC3, VAL_VEC4, VAL_WEIGHTED,
27 VERSION,
28};
29
30#[expect(clippy::cast_possible_truncation)]
34pub fn write_inkb(story: &StoryData, buf: &mut Vec<u8>) {
35 let base = buf.len();
36
37 let has_visibility = !story.private_defs.is_empty();
44 let has_frame_shapes = !story.frame_shapes.is_empty();
49 let section_count =
50 SECTION_COUNT as usize + usize::from(has_visibility) + usize::from(has_frame_shapes);
51 let header_size = HEADER_PREAMBLE + section_count * SECTION_ENTRY_SIZE;
52
53 buf.resize(base + header_size, 0);
55
56 let mut sections: Vec<(SectionKind, u32)> = Vec::with_capacity(section_count);
60
61 macro_rules! section {
62 ($kind:expr, $write:expr) => {{
63 let offset = (buf.len() - base) as u32;
64 $write;
65 sections.push(($kind, offset));
66 }};
67 }
68
69 section!(
70 SectionKind::NameTable,
71 write_section_name_table(&story.name_table, buf)
72 );
73 section!(
74 SectionKind::Variables,
75 write_section_variables(&story.variables, buf)
76 );
77 section!(
78 SectionKind::ListDefs,
79 write_section_list_defs(&story.list_defs, buf)
80 );
81 section!(
82 SectionKind::ListItems,
83 write_section_list_items(&story.list_items, buf)
84 );
85 section!(
86 SectionKind::Externals,
87 write_section_externals(&story.externals, buf)
88 );
89 section!(
90 SectionKind::Containers,
91 write_section_containers(&story.containers, buf)
92 );
93 section!(
94 SectionKind::LineTables,
95 write_section_line_tables(&story.line_tables, buf)
96 );
97 section!(
98 SectionKind::Labels,
99 write_section_addresses(&story.addresses, buf)
100 );
101 section!(
102 SectionKind::ListLiterals,
103 write_section_list_literals(&story.list_literals, buf)
104 );
105 section!(
106 SectionKind::AddressPaths,
107 write_section_address_paths(&story.address_paths, buf)
108 );
109 section!(
110 SectionKind::LiteralPool,
111 write_section_literal_pool(&story.literal_pool, buf)
112 );
113 section!(
114 SectionKind::StructShapes,
115 write_section_struct_shapes(&story.struct_shapes, buf)
116 );
117 section!(
121 SectionKind::EffectRows,
122 write_section_effect_rows(&story.effect_rows, buf)
123 );
124 if has_visibility {
125 section!(
126 SectionKind::Visibility,
127 write_section_visibility(&story.private_defs, buf)
128 );
129 }
130 section!(
133 SectionKind::AliasTable,
134 write_section_alias_table(&story.alias_table, buf)
135 );
136 if has_frame_shapes {
140 section!(
141 SectionKind::FrameShapes,
142 write_section_frame_shapes(&story.frame_shapes, buf)
143 );
144 }
145
146 let file_size = (buf.len() - base) as u32;
147 let checksum = crc32(&buf[base + header_size..]);
148
149 let h = &mut buf[base..];
151 h[0..4].copy_from_slice(MAGIC);
152 h[4..6].copy_from_slice(&VERSION.to_le_bytes());
153 h[6] = section_count as u8;
154 h[7] = 0; h[8..12].copy_from_slice(&file_size.to_le_bytes());
156 h[12..16].copy_from_slice(&checksum.to_le_bytes());
157
158 for (i, (kind, offset)) in sections.iter().enumerate() {
159 let entry_base = HEADER_PREAMBLE + i * SECTION_ENTRY_SIZE;
160 h[entry_base] = *kind as u8;
161 h[entry_base + 1] = 0; h[entry_base + 2] = 0;
163 h[entry_base + 3] = 0;
164 h[entry_base + 4..entry_base + 8].copy_from_slice(&offset.to_le_bytes());
165 }
166}
167
168#[expect(clippy::cast_possible_truncation)]
175pub fn assemble_inkb(sections: &[(SectionKind, &[u8])], out: &mut Vec<u8>) {
176 let base = out.len();
177 let section_count = sections.len() as u8;
178 let header_size = HEADER_PREAMBLE + sections.len() * SECTION_ENTRY_SIZE;
179
180 out.resize(base + header_size, 0);
182
183 let mut entries: Vec<(SectionKind, u32)> = Vec::with_capacity(sections.len());
185 for (kind, data) in sections {
186 let offset = (out.len() - base) as u32;
187 entries.push((*kind, offset));
188 out.extend_from_slice(data);
189 }
190
191 let file_size = (out.len() - base) as u32;
192 let checksum = crc32(&out[base + header_size..]);
193
194 let h = &mut out[base..];
196 h[0..4].copy_from_slice(MAGIC);
197 h[4..6].copy_from_slice(&VERSION.to_le_bytes());
198 h[6] = section_count;
199 h[7] = 0;
200 h[8..12].copy_from_slice(&file_size.to_le_bytes());
201 h[12..16].copy_from_slice(&checksum.to_le_bytes());
202
203 for (i, (kind, offset)) in entries.iter().enumerate() {
204 let entry_base = HEADER_PREAMBLE + i * SECTION_ENTRY_SIZE;
205 h[entry_base] = *kind as u8;
206 h[entry_base + 1] = 0;
207 h[entry_base + 2] = 0;
208 h[entry_base + 3] = 0;
209 h[entry_base + 4..entry_base + 8].copy_from_slice(&offset.to_le_bytes());
210 }
211}
212
213#[expect(clippy::cast_possible_truncation)]
217pub fn write_section_name_table(names: &[String], buf: &mut Vec<u8>) {
218 write_u32(buf, names.len() as u32);
219 for name in names {
220 write_str(buf, name);
221 }
222}
223
224#[expect(clippy::cast_possible_truncation)]
226pub fn write_section_variables(variables: &[GlobalVarDef], buf: &mut Vec<u8>) {
227 write_u32(buf, variables.len() as u32);
228 for var in variables {
229 encode_global_var(var, buf);
230 }
231}
232
233#[expect(clippy::cast_possible_truncation)]
235pub fn write_section_list_defs(list_defs: &[ListDef], buf: &mut Vec<u8>) {
236 write_u32(buf, list_defs.len() as u32);
237 for ld in list_defs {
238 encode_list_def(ld, buf);
239 }
240}
241
242#[expect(clippy::cast_possible_truncation)]
244pub fn write_section_list_items(list_items: &[ListItemDef], buf: &mut Vec<u8>) {
245 write_u32(buf, list_items.len() as u32);
246 for li in list_items {
247 encode_list_item(li, buf);
248 }
249}
250
251#[expect(clippy::cast_possible_truncation)]
253pub fn write_section_externals(externals: &[ExternalFnDef], buf: &mut Vec<u8>) {
254 write_u32(buf, externals.len() as u32);
255 for ext in externals {
256 encode_external(ext, buf);
257 }
258}
259
260#[expect(clippy::cast_possible_truncation)]
262pub fn write_section_containers(containers: &[ContainerDef], buf: &mut Vec<u8>) {
263 write_u32(buf, containers.len() as u32);
264 for c in containers {
265 encode_container(c, buf);
266 }
267}
268
269#[expect(clippy::cast_possible_truncation)]
271pub fn write_section_addresses(addresses: &[AddressDef], buf: &mut Vec<u8>) {
272 write_u32(buf, addresses.len() as u32);
273 for addr in addresses {
274 write_def_id(buf, addr.id);
275 write_def_id(buf, addr.container_id);
276 write_u32(buf, addr.byte_offset);
277 }
278}
279
280#[expect(clippy::cast_possible_truncation)]
282pub fn write_section_address_paths(address_paths: &[AddressPath], buf: &mut Vec<u8>) {
283 write_u32(buf, address_paths.len() as u32);
284 for ap in address_paths {
285 write_u16(buf, ap.path.0);
286 write_def_id(buf, ap.target);
287 }
288}
289
290#[expect(clippy::cast_possible_truncation)]
294pub fn write_section_visibility(private_defs: &[DefinitionId], buf: &mut Vec<u8>) {
295 write_u32(buf, private_defs.len() as u32);
296 for id in private_defs {
297 write_def_id(buf, *id);
298 }
299}
300
301fn encode_global_var(v: &GlobalVarDef, buf: &mut Vec<u8>) {
304 write_def_id(buf, v.id);
305 write_u16(buf, v.name.0);
306 encode_value_type(v.value_type, buf);
307 encode_value(&v.default_value, buf);
308 write_u8(buf, u8::from(v.mutable));
309 write_u8(buf, u8::from(v.local));
310}
311
312fn encode_value_type(vt: ValueType, buf: &mut Vec<u8>) {
313 let tag = match vt {
314 ValueType::Int => VAL_INT,
315 ValueType::Float => VAL_FLOAT,
316 ValueType::Bool => VAL_BOOL,
317 ValueType::String => VAL_STRING,
318 ValueType::List => VAL_LIST,
319 ValueType::DivertTarget => VAL_DIVERT_TARGET,
320 ValueType::VariablePointer => VAL_VAR_POINTER,
321 ValueType::FragmentRef => VAL_FRAGMENT_REF,
323 ValueType::TempPointer | ValueType::Null => VAL_NULL,
324 ValueType::Array => VAL_ARRAY,
326 ValueType::Map => VAL_MAP,
327 ValueType::Record => VAL_RECORD,
329 ValueType::FnRef => VAL_FN_REF,
331 ValueType::Closure => VAL_CLOSURE,
332 ValueType::Handle => VAL_HANDLE,
334 ValueType::Projection => VAL_PROJECTION,
336 ValueType::Option => VAL_OPTION,
338 ValueType::Range => VAL_RANGE,
340 ValueType::Vec2 => VAL_VEC2,
342 ValueType::Vec3 => VAL_VEC3,
343 ValueType::Vec4 => VAL_VEC4,
344 ValueType::Quat => VAL_QUAT,
345 ValueType::Mat2 => VAL_MAT2,
346 ValueType::Mat3 => VAL_MAT3,
347 ValueType::Mat4 => VAL_MAT4,
348 ValueType::Weighted => VAL_WEIGHTED,
350 };
351 write_u8(buf, tag);
352}
353
354fn write_f32_lanes(buf: &mut Vec<u8>, lanes: &[f32]) {
360 for lane in lanes {
361 buf.extend_from_slice(&lane.to_le_bytes());
362 }
363}
364
365#[expect(clippy::cast_possible_truncation)]
366#[expect(
367 clippy::too_many_lines,
368 reason = "one match arm per value variant — the NS-A1 Option arm pushed this past 100"
369)]
370fn encode_value(v: &Value, buf: &mut Vec<u8>) {
371 match v {
372 Value::Int(n) => {
373 write_u8(buf, VAL_INT);
374 write_i32(buf, *n);
375 }
376 Value::Float(n) => {
377 write_u8(buf, VAL_FLOAT);
378 buf.extend_from_slice(&n.to_le_bytes());
379 }
380 Value::Bool(b) => {
381 write_u8(buf, VAL_BOOL);
382 write_u8(buf, u8::from(*b));
383 }
384 Value::String(s) => {
385 write_u8(buf, VAL_STRING);
386 write_str(buf, s);
387 }
388 Value::List(lv) => {
389 write_u8(buf, VAL_LIST);
390 write_u32(buf, lv.items.len() as u32);
391 for item in &lv.items {
392 write_def_id(buf, *item);
393 }
394 write_u32(buf, lv.origins.len() as u32);
395 for origin in &lv.origins {
396 write_def_id(buf, *origin);
397 }
398 }
399 Value::DivertTarget(id) => {
400 write_u8(buf, VAL_DIVERT_TARGET);
401 write_def_id(buf, *id);
402 }
403 Value::VariablePointer(id) => {
404 write_u8(buf, VAL_VAR_POINTER);
405 write_def_id(buf, *id);
406 }
407 Value::FragmentRef(idx) => {
408 write_u8(buf, VAL_FRAGMENT_REF);
409 write_u32(buf, *idx);
410 }
411 Value::TempPointer { .. } | Value::Null => {
413 write_u8(buf, VAL_NULL);
414 }
415 Value::Array(items) => {
419 write_u8(buf, VAL_ARRAY);
420 write_u32(buf, items.len() as u32);
421 for item in items.iter() {
422 encode_value(item, buf);
423 }
424 }
425 Value::Map(map) => {
426 write_u8(buf, VAL_MAP);
427 write_u32(buf, map.len() as u32);
428 for (key, val) in map.iter() {
430 encode_map_key(key, buf);
431 encode_value(val, buf);
432 }
433 }
434 Value::Record { shape, fields } => {
438 write_u8(buf, VAL_RECORD);
439 write_u32(buf, shape.0);
440 write_u32(buf, fields.len() as u32);
441 for field in fields.iter() {
442 encode_value(field, buf);
443 }
444 }
445 Value::FnRef(target) => {
450 write_u8(buf, VAL_FN_REF);
451 write_def_id(buf, *target);
452 }
453 Value::Closure(c) => {
454 write_u8(buf, VAL_CLOSURE);
455 write_def_id(buf, c.target);
456 write_u16(buf, c.env.len() as u16);
457 for entry in &c.env {
458 write_u16(buf, entry.name.0);
459 write_u8(buf, u8::from(entry.is_ref));
460 encode_value(&entry.payload, buf);
461 }
462 }
463 Value::Handle { kind, id } => {
469 write_u8(buf, VAL_HANDLE);
470 write_u16(buf, kind.0);
471 write_u64(buf, *id);
472 }
473 Value::Projection(p) => {
478 write_u8(buf, VAL_PROJECTION);
479 write_def_id(buf, p.cell);
480 write_u8(buf, p.segments.len() as u8);
481 for seg in &p.segments {
482 encode_proj_segment(seg, buf);
483 }
484 }
485 Value::OptionVal(inner) => {
493 write_u8(buf, VAL_OPTION);
494 match inner {
495 None => write_u8(buf, 0),
496 Some(v) => {
497 write_u8(buf, 1);
498 encode_value(v, buf);
499 }
500 }
501 }
502 Value::Range {
507 start,
508 end,
509 inclusive,
510 } => {
511 write_u8(buf, VAL_RANGE);
512 write_i32(buf, *start);
513 write_i32(buf, *end);
514 write_u8(buf, u8::from(*inclusive));
515 }
516 Value::Vec2(v) => {
525 write_u8(buf, VAL_VEC2);
526 write_f32_lanes(buf, &v.to_array());
527 }
528 Value::Vec3(v) => {
529 write_u8(buf, VAL_VEC3);
530 write_f32_lanes(buf, &v.to_array());
531 }
532 Value::Vec4(v) => {
533 write_u8(buf, VAL_VEC4);
534 write_f32_lanes(buf, &v.to_array());
535 }
536 Value::Quat(q) => {
537 write_u8(buf, VAL_QUAT);
538 write_f32_lanes(buf, &q.to_array());
539 }
540 Value::Mat2(m) => {
541 write_u8(buf, VAL_MAT2);
542 write_f32_lanes(buf, &m.to_cols_array());
543 }
544 Value::Mat3(m) => {
545 write_u8(buf, VAL_MAT3);
546 write_f32_lanes(buf, &m.to_cols_array());
547 }
548 Value::Mat4(m) => {
549 write_u8(buf, VAL_MAT4);
550 write_f32_lanes(buf, &m.to_cols_array());
551 }
552 Value::Weighted(w) => {
560 write_u8(buf, VAL_WEIGHTED);
561 write_u32(buf, w.entries.len() as u32);
562 for (weight, value) in &w.entries {
563 write_i32(buf, *weight);
564 encode_value(value, buf);
565 }
566 }
567 }
568}
569
570fn encode_proj_segment(seg: &ProjSegment, buf: &mut Vec<u8>) {
573 match seg {
574 ProjSegment::Index(n) => {
575 write_u8(buf, PROJ_SEG_INDEX);
576 write_i32(buf, *n);
577 }
578 ProjSegment::Key(v) => {
579 write_u8(buf, PROJ_SEG_KEY);
580 encode_value(v, buf);
581 }
582 }
583}
584
585fn encode_map_key(key: &MapKey, buf: &mut Vec<u8>) {
589 match key {
590 MapKey::Int(n) => {
591 write_u8(buf, VAL_INT);
592 write_i32(buf, *n);
593 }
594 MapKey::Str(s) => {
595 write_u8(buf, VAL_STRING);
596 write_str(buf, s);
597 }
598 MapKey::Bool(b) => {
599 write_u8(buf, VAL_BOOL);
600 write_u8(buf, u8::from(*b));
601 }
602 }
603}
604
605#[expect(clippy::cast_possible_truncation)]
606fn encode_list_def(ld: &ListDef, buf: &mut Vec<u8>) {
607 write_def_id(buf, ld.id);
608 write_u16(buf, ld.name.0);
609 write_u32(buf, ld.items.len() as u32);
610 for (name_id, ordinal) in &ld.items {
611 write_u16(buf, name_id.0);
612 write_i32(buf, *ordinal);
613 }
614}
615
616fn encode_list_item(li: &ListItemDef, buf: &mut Vec<u8>) {
617 write_def_id(buf, li.id);
618 write_def_id(buf, li.origin);
619 write_i32(buf, li.ordinal);
620 write_u16(buf, li.name.0);
621}
622
623#[expect(clippy::cast_possible_truncation)]
625pub fn write_section_list_literals(list_literals: &[ListValue], buf: &mut Vec<u8>) {
626 write_u32(buf, list_literals.len() as u32);
627 for lv in list_literals {
628 write_u32(buf, lv.items.len() as u32);
629 for item in &lv.items {
630 write_def_id(buf, *item);
631 }
632 write_u32(buf, lv.origins.len() as u32);
633 for origin in &lv.origins {
634 write_def_id(buf, *origin);
635 }
636 }
637}
638
639#[expect(clippy::cast_possible_truncation)]
645pub fn write_section_literal_pool(literal_pool: &[Value], buf: &mut Vec<u8>) {
646 write_u32(buf, literal_pool.len() as u32);
647 for v in literal_pool {
648 encode_value(v, buf);
649 }
650}
651
652#[expect(clippy::cast_possible_truncation)]
657pub fn write_section_struct_shapes(struct_shapes: &[StructShapeDef], buf: &mut Vec<u8>) {
658 write_u32(buf, struct_shapes.len() as u32);
659 for shape in struct_shapes {
660 write_u32(buf, shape.id.0);
661 write_u16(buf, shape.name.0);
662 write_u16(buf, shape.fields.len() as u16);
663 for field in &shape.fields {
664 write_u16(buf, field.0);
665 }
666 }
667}
668
669pub(crate) const ALIAS_TABLE_SECTION_VERSION: u8 = 1;
673
674#[expect(clippy::cast_possible_truncation)]
679pub fn write_section_alias_table(entries: &[AliasEntry], buf: &mut Vec<u8>) {
680 write_u8(buf, ALIAS_TABLE_SECTION_VERSION);
681 write_u32(buf, entries.len() as u32);
682 for entry in entries {
683 write_def_id(buf, entry.old);
684 write_def_id(buf, entry.new);
685 }
686}
687
688pub(crate) const FRAME_SHAPES_SECTION_VERSION: u8 = 1;
693
694#[expect(clippy::cast_possible_truncation)]
701pub fn write_section_frame_shapes(shapes: &[FrameShapeDef], buf: &mut Vec<u8>) {
702 write_u8(buf, FRAME_SHAPES_SECTION_VERSION);
703 write_u32(buf, shapes.len() as u32);
704 for shape in shapes {
705 write_def_id(buf, shape.site);
706 write_u32(buf, shape.slots.len() as u32);
707 for slot in &shape.slots {
708 write_u16(buf, slot.0);
709 }
710 }
711}
712
713pub(crate) const EFFECT_ROWS_SECTION_VERSION: u8 = 3;
726
727#[expect(clippy::cast_possible_truncation)]
733pub fn write_section_effect_rows(rows: &[EffectRowEntry], buf: &mut Vec<u8>) {
734 write_u8(buf, EFFECT_ROWS_SECTION_VERSION);
735 write_u32(buf, rows.len() as u32);
736 for row in rows {
737 write_def_id(buf, row.def);
738 write_u8(buf, u8::from(row.is_entry));
742 encode_direct_effects(&row.direct, buf);
743 write_u32(buf, row.dispatches.len() as u32);
746 for d in &row.dispatches {
747 write_def_id(buf, d.cell);
748 write_u8(buf, u8::from(d.narrowable));
749 encode_direct_effects(&d.fallback, buf);
750 }
751 }
752}
753
754#[expect(clippy::cast_possible_truncation)]
756fn encode_direct_effects(direct: &DirectEffects, buf: &mut Vec<u8>) {
757 write_u32(buf, direct.reads.len() as u32);
758 for id in &direct.reads {
759 write_def_id(buf, *id);
760 }
761 write_u32(buf, direct.writes.len() as u32);
762 for id in &direct.writes {
763 write_def_id(buf, *id);
764 }
765 write_u32(buf, direct.calls.len() as u32);
766 for atom in &direct.calls {
767 encode_call_atom(atom, buf);
768 }
769 write_u8(buf, u8::from(direct.opaque));
770 let mut dims = 0u8;
772 if direct.emits {
773 dims |= super::EFFECT_DIM_EMITS;
774 }
775 if direct.tags {
776 dims |= super::EFFECT_DIM_TAGS;
777 }
778 if direct.faults {
779 dims |= super::EFFECT_DIM_FAULTS;
780 }
781 write_u8(buf, dims);
782}
783
784fn encode_call_atom(atom: &CallAtom, buf: &mut Vec<u8>) {
789 write_u16(buf, atom.name.0);
790 let cap_tag = match atom.capability {
791 CapabilityParam::Any => CAP_PARAM_ANY,
792 };
793 write_u8(buf, cap_tag);
794 write_u8(buf, atom.handle_param.unwrap_or(HANDLE_PARAM_NONE));
797}
798
799fn encode_external(ext: &ExternalFnDef, buf: &mut Vec<u8>) {
800 write_def_id(buf, ext.id);
801 write_u16(buf, ext.name.0);
802 write_u8(buf, ext.arg_count);
803 match ext.fallback {
804 Some(fb) => {
805 write_u8(buf, 1);
806 write_def_id(buf, fb);
807 }
808 None => {
809 write_u8(buf, 0);
810 }
811 }
812}
813
814#[expect(clippy::cast_possible_truncation)]
815fn encode_container(c: &ContainerDef, buf: &mut Vec<u8>) {
816 write_def_id(buf, c.id);
817 write_def_id(buf, c.scope_id);
818 match c.name {
819 Some(name_id) => {
820 write_u8(buf, 1);
821 write_u16(buf, name_id.0);
822 }
823 None => {
824 write_u8(buf, 0);
825 }
826 }
827 write_u8(buf, c.counting_flags.bits());
828 write_i32(buf, c.path_hash);
829 write_u8(buf, c.param_count);
830 write_u8(buf, u8::from(c.local));
831 write_u16(buf, c.params.len() as u16);
834 for p in &c.params {
835 write_u16(buf, p.name.0);
836 write_u8(buf, u8::from(p.is_ref));
837 }
838 write_u32(buf, c.bytecode.len() as u32);
839 buf.extend_from_slice(&c.bytecode);
840}
841
842#[expect(clippy::cast_possible_truncation)]
844pub fn write_section_line_tables(line_tables: &[ScopeLineTable], buf: &mut Vec<u8>) {
845 write_u32(buf, line_tables.len() as u32);
846 for lt in line_tables {
847 encode_scope_line_table(lt, buf);
848 }
849}
850
851#[expect(clippy::cast_possible_truncation)]
852fn encode_scope_line_table(lt: &ScopeLineTable, buf: &mut Vec<u8>) {
853 write_def_id(buf, lt.scope_id);
854 write_u32(buf, lt.lines.len() as u32);
855 for entry in <.lines {
856 encode_line_entry(entry, buf);
857 }
858}
859
860fn encode_line_entry(entry: &LineEntry, buf: &mut Vec<u8>) {
861 encode_line_content(&entry.content, buf);
862 write_u64(buf, entry.source_hash);
863 match &entry.audio_ref {
864 Some(audio) => {
865 write_u8(buf, 1);
866 write_str(buf, audio);
867 }
868 None => {
869 write_u8(buf, 0);
870 }
871 }
872
873 #[expect(clippy::cast_possible_truncation)]
875 write_u8(buf, entry.slot_info.len() as u8);
876 for slot in &entry.slot_info {
877 write_u8(buf, slot.index);
878 write_str(buf, &slot.name);
879 }
880
881 match &entry.source_location {
883 Some(loc) => {
884 write_u8(buf, 1);
885 write_str(buf, &loc.file);
886 write_u32(buf, loc.range_start);
887 write_u32(buf, loc.range_end);
888 }
889 None => {
890 write_u8(buf, 0);
891 }
892 }
893}
894
895#[expect(clippy::cast_possible_truncation)]
896pub(crate) fn encode_line_content(content: &LineContent, buf: &mut Vec<u8>) {
897 match content {
898 LineContent::Plain(s) => {
899 write_u8(buf, LINE_PLAIN);
900 write_str(buf, s);
901 }
902 LineContent::Template(parts) => {
903 write_u8(buf, LINE_TEMPLATE);
904 write_u32(buf, parts.len() as u32);
905 for part in parts {
906 encode_line_part(part, buf);
907 }
908 }
909 }
910}
911
912#[expect(clippy::cast_possible_truncation)]
913fn encode_line_part(part: &LinePart, buf: &mut Vec<u8>) {
914 match part {
915 LinePart::Literal(s) => {
916 write_u8(buf, PART_LITERAL);
917 write_str(buf, s);
918 }
919 LinePart::Slot(idx) => {
920 write_u8(buf, PART_SLOT);
921 write_u8(buf, *idx);
922 }
923 LinePart::Select {
924 slot,
925 variants,
926 default,
927 } => {
928 write_u8(buf, PART_SELECT);
929 write_u8(buf, *slot);
930 write_u32(buf, variants.len() as u32);
931 for (key, text) in variants {
932 encode_select_key(key, buf);
933 write_str(buf, text);
934 }
935 write_str(buf, default);
936 }
937 LinePart::Span {
938 name,
939 attrs,
940 children,
941 } => {
942 write_u8(buf, PART_SPAN);
943 write_str(buf, name);
944 write_u32(buf, attrs.len() as u32);
945 for (k, v) in attrs {
946 write_str(buf, k);
947 write_str(buf, v);
948 }
949 write_u32(buf, children.len() as u32);
950 for child in children {
951 encode_line_part(child, buf);
952 }
953 }
954 }
955}
956
957fn encode_select_key(key: &SelectKey, buf: &mut Vec<u8>) {
958 match key {
959 SelectKey::Cardinal(cat) => {
960 write_u8(buf, KEY_CARDINAL);
961 encode_plural_category(*cat, buf);
962 }
963 SelectKey::Ordinal(cat) => {
964 write_u8(buf, KEY_ORDINAL);
965 encode_plural_category(*cat, buf);
966 }
967 SelectKey::Exact(n) => {
968 write_u8(buf, KEY_EXACT);
969 write_i32(buf, *n);
970 }
971 SelectKey::Keyword(k) => {
972 write_u8(buf, KEY_KEYWORD);
973 write_str(buf, k);
974 }
975 }
976}
977
978fn encode_plural_category(cat: PluralCategory, buf: &mut Vec<u8>) {
979 let tag = match cat {
980 PluralCategory::Zero => CAT_ZERO,
981 PluralCategory::One => CAT_ONE,
982 PluralCategory::Two => CAT_TWO,
983 PluralCategory::Few => CAT_FEW,
984 PluralCategory::Many => CAT_MANY,
985 PluralCategory::Other => CAT_OTHER,
986 };
987 write_u8(buf, tag);
988}