1use core::fmt;
2
3use crate::codec::{
4 read_def_id, read_f32, read_i32, read_u8, read_u16, read_u32, write_def_id, write_f32,
5 write_i32, write_u8, write_u16, write_u32,
6};
7use crate::id::DefinitionId;
8
9const PUSH_INT: u8 = 0x01;
13const PUSH_FLOAT: u8 = 0x02;
14const PUSH_BOOL: u8 = 0x03;
15const PUSH_STRING: u8 = 0x04;
16const PUSH_LIST: u8 = 0x05;
17const PUSH_DIVERT_TARGET: u8 = 0x06;
18const PUSH_NULL: u8 = 0x07;
19const POP: u8 = 0x08;
20const DUPLICATE: u8 = 0x09;
21
22const ADD: u8 = 0x10;
24const SUBTRACT: u8 = 0x11;
25const MULTIPLY: u8 = 0x12;
26const DIVIDE: u8 = 0x13;
27const MODULO: u8 = 0x14;
28const NEGATE: u8 = 0x15;
29
30const EQUAL: u8 = 0x20;
32const NOT_EQUAL: u8 = 0x21;
33const GREATER: u8 = 0x22;
34const GREATER_OR_EQUAL: u8 = 0x23;
35const LESS: u8 = 0x24;
36const LESS_OR_EQUAL: u8 = 0x25;
37
38const NOT: u8 = 0x28;
40const AND: u8 = 0x29;
41const OR: u8 = 0x2A;
42
43const GET_GLOBAL: u8 = 0x30;
45const SET_GLOBAL: u8 = 0x31;
46
47const DECLARE_TEMP: u8 = 0x34;
49const GET_TEMP: u8 = 0x35;
50const SET_TEMP: u8 = 0x36;
51const GET_TEMP_RAW: u8 = 0x37;
52
53const PUSH_VAR_POINTER: u8 = 0x38;
55const PUSH_TEMP_POINTER: u8 = 0x39;
56
57const JUMP: u8 = 0x40;
59const JUMP_IF_FALSE: u8 = 0x41;
60const GOTO: u8 = 0x42;
61const GOTO_IF: u8 = 0x43;
62const GOTO_VARIABLE: u8 = 0x44;
63
64const ENTER_CONTAINER: u8 = 0x48;
66const EXIT_CONTAINER: u8 = 0x49;
67
68const CALL: u8 = 0x50;
70const RETURN: u8 = 0x51;
71const TUNNEL_CALL: u8 = 0x52;
72const TUNNEL_RETURN: u8 = 0x53;
73const TUNNEL_CALL_VARIABLE: u8 = 0x54;
74const CALL_VARIABLE: u8 = 0x55;
75
76const THREAD_CALL: u8 = 0x57;
78const THREAD_START: u8 = 0x58;
79const THREAD_DONE: u8 = 0x59;
80
81const EMIT_LINE: u8 = 0x60;
83const EMIT_VALUE: u8 = 0x61;
84const EMIT_NEWLINE: u8 = 0x62;
85const SPRING: u8 = 0x67;
86const GLUE: u8 = 0x63;
87const BEGIN_TAG: u8 = 0x64;
88const END_TAG: u8 = 0x65;
89const EVAL_LINE: u8 = 0x66;
90const BEGIN_FRAGMENT: u8 = 0x68;
91const END_FRAGMENT: u8 = 0x69;
92
93const BEGIN_CHOICE: u8 = 0x72;
95const END_CHOICE: u8 = 0x73;
96const SEQUENCE: u8 = 0x78;
98const SEQUENCE_BRANCH: u8 = 0x79;
99
100const VISIT_COUNT: u8 = 0x80;
102const TURNS_SINCE: u8 = 0x81;
103const TURN_INDEX: u8 = 0x82;
104const CHOICE_COUNT: u8 = 0x83;
105const RANDOM: u8 = 0x84;
106const SEED_RANDOM: u8 = 0x85;
107const CURRENT_VISIT_COUNT: u8 = 0x86;
108
109const CAST_TO_INT: u8 = 0x90;
111const CAST_TO_FLOAT: u8 = 0x91;
112const FLOOR: u8 = 0x92;
113const CEILING: u8 = 0x93;
114const POW: u8 = 0x94;
115const MIN: u8 = 0x95;
116const MAX: u8 = 0x96;
117
118const CALL_EXTERNAL: u8 = 0xA0;
120
121const LIST_CONTAINS: u8 = 0xB0;
123const LIST_NOT_CONTAINS: u8 = 0xB1;
124const LIST_INTERSECT: u8 = 0xB2;
125const LIST_ALL: u8 = 0xB5;
126const LIST_INVERT: u8 = 0xB6;
127const LIST_COUNT: u8 = 0xB7;
128const LIST_MIN: u8 = 0xB8;
129const LIST_MAX: u8 = 0xB9;
130const LIST_VALUE: u8 = 0xBA;
131const LIST_RANGE: u8 = 0xBB;
132const LIST_FROM_INT: u8 = 0xBC;
133const LIST_RANDOM: u8 = 0xBD;
134
135const DONE: u8 = 0xF0;
137const YIELD: u8 = 0xF3;
138const END: u8 = 0xF1;
139const NOP: u8 = 0xF2;
140
141const BEGIN_STRING_EVAL: u8 = 0xE0;
143const END_STRING_EVAL: u8 = 0xE1;
144
145const SOURCE_LOCATION: u8 = 0xFE;
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
152pub enum SequenceKind {
153 Cycle,
154 Stopping,
155 OnceOnly,
156 Shuffle,
157}
158
159impl SequenceKind {
160 fn to_byte(self) -> u8 {
161 match self {
162 Self::Cycle => 0,
163 Self::Stopping => 1,
164 Self::OnceOnly => 2,
165 Self::Shuffle => 3,
166 }
167 }
168
169 fn from_byte(b: u8) -> Result<Self, DecodeError> {
170 match b {
171 0 => Ok(Self::Cycle),
172 1 => Ok(Self::Stopping),
173 2 => Ok(Self::OnceOnly),
174 3 => Ok(Self::Shuffle),
175 _ => Err(DecodeError::InvalidSequenceKind(b)),
176 }
177 }
178}
179
180#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
188#[expect(clippy::struct_excessive_bools)]
189pub struct ChoiceFlags {
190 pub has_condition: bool,
191 pub has_start_content: bool,
193 pub has_choice_only_content: bool,
196 pub once_only: bool,
197 pub is_invisible_default: bool,
198}
199
200impl ChoiceFlags {
201 fn to_byte(self) -> u8 {
202 let mut b = 0u8;
203 if self.has_condition {
204 b |= 0x01;
205 }
206 if self.has_start_content {
207 b |= 0x02;
208 }
209 if self.has_choice_only_content {
210 b |= 0x04;
211 }
212 if self.once_only {
213 b |= 0x08;
214 }
215 if self.is_invisible_default {
216 b |= 0x10;
217 }
218 b
219 }
220
221 fn from_byte(b: u8) -> Self {
222 Self {
223 has_condition: b & 0x01 != 0,
224 has_start_content: b & 0x02 != 0,
225 has_choice_only_content: b & 0x04 != 0,
226 once_only: b & 0x08 != 0,
227 is_invisible_default: b & 0x10 != 0,
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq, Eq)]
234pub enum DecodeError {
235 UnexpectedEof,
237 UnknownOpcode(u8),
239 InvalidDefinitionId(u64),
241 InvalidSequenceKind(u8),
243 BadMagic([u8; 4]),
245 UnsupportedVersion(u16),
247 InvalidUtf8,
249 InvalidValueType(u8),
251 InvalidSelectKey(u8),
253 InvalidLinePart(u8),
255 InvalidLineContent(u8),
257 InvalidPluralCategory(u8),
259 InvalidSectionKind(u8),
261 MissingSectionKind(u8),
263 FileSizeMismatch { expected: u32, actual: usize },
265 ChecksumMismatch { expected: u32, actual: u32 },
267 InvalidSectionOffset { kind: u8, offset: u32 },
269 BadInklMagic([u8; 4]),
271 UnsupportedInklVersion(u8),
273}
274
275impl fmt::Display for DecodeError {
276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277 match self {
278 Self::UnexpectedEof => write!(f, "unexpected end of bytecode"),
279 Self::UnknownOpcode(b) => write!(f, "unknown opcode: {b:#04x}"),
280 Self::InvalidDefinitionId(raw) => {
281 write!(f, "invalid definition id: {raw:#018x}")
282 }
283 Self::InvalidSequenceKind(b) => write!(f, "invalid sequence kind: {b}"),
284 Self::BadMagic(m) => write!(f, "bad magic: {m:02x?}"),
285 Self::UnsupportedVersion(v) => write!(f, "unsupported .inkb version: {v}"),
286 Self::InvalidUtf8 => write!(f, "invalid UTF-8 in string field"),
287 Self::InvalidValueType(b) => write!(f, "invalid value type: {b:#04x}"),
288 Self::InvalidSelectKey(b) => write!(f, "invalid select key: {b:#04x}"),
289 Self::InvalidLinePart(b) => write!(f, "invalid line part: {b:#04x}"),
290 Self::InvalidLineContent(b) => write!(f, "invalid line content: {b:#04x}"),
291 Self::InvalidPluralCategory(b) => write!(f, "invalid plural category: {b:#04x}"),
292 Self::InvalidSectionKind(b) => write!(f, "invalid section kind: {b:#04x}"),
293 Self::MissingSectionKind(b) => write!(f, "missing required section kind: {b:#04x}"),
294 Self::FileSizeMismatch { expected, actual } => {
295 write!(
296 f,
297 "file size mismatch: header says {expected}, actual {actual}"
298 )
299 }
300 Self::ChecksumMismatch { expected, actual } => {
301 write!(
302 f,
303 "checksum mismatch: header {expected:#010x}, computed {actual:#010x}"
304 )
305 }
306 Self::InvalidSectionOffset { kind, offset } => {
307 write!(
308 f,
309 "invalid section offset: kind {kind:#04x} at offset {offset}"
310 )
311 }
312 Self::BadInklMagic(m) => write!(f, "bad .inkl magic: {m:02x?}"),
313 Self::UnsupportedInklVersion(v) => write!(f, "unsupported .inkl version: {v}"),
314 }
315 }
316}
317
318impl std::error::Error for DecodeError {}
319
320#[derive(Debug, Clone, PartialEq)]
322pub enum Opcode {
323 PushInt(i32),
325 PushFloat(f32),
326 PushBool(bool),
327 PushString(u16),
328 PushList(u16),
329 PushDivertTarget(DefinitionId),
330 PushNull,
331 Pop,
332 Duplicate,
333
334 Add,
336 Subtract,
337 Multiply,
338 Divide,
339 Modulo,
340 Negate,
341
342 Equal,
344 NotEqual,
345 Greater,
346 GreaterOrEqual,
347 Less,
348 LessOrEqual,
349
350 Not,
352 And,
353 Or,
354
355 GetGlobal(DefinitionId),
357 SetGlobal(DefinitionId),
358
359 DeclareTemp(u16),
361 GetTemp(u16),
362 SetTemp(u16),
363 GetTempRaw(u16),
365
366 PushVarPointer(DefinitionId),
369 PushTempPointer(u16),
371
372 Jump(i32),
374 JumpIfFalse(i32),
375 Goto(DefinitionId),
376 GotoIf(DefinitionId),
377 GotoVariable,
378
379 EnterContainer(DefinitionId),
381 ExitContainer,
382
383 Call(DefinitionId),
385 Return,
386 TunnelCall(DefinitionId),
387 TunnelReturn,
388 TunnelCallVariable,
389 CallVariable,
390
391 ThreadCall(DefinitionId),
393 ThreadStart,
394 ThreadDone,
395
396 EmitLine(u16, u8),
398 EmitValue,
399 EmitNewline,
400 Spring,
402 Glue,
403 BeginTag,
404 EndTag,
405 EvalLine(u16, u8),
406 BeginFragment,
408 EndFragment,
410
411 BeginChoice(ChoiceFlags, DefinitionId),
413 EndChoice,
414
415 Sequence(SequenceKind, u8),
417 SequenceBranch(i32),
418
419 VisitCount,
422 CurrentVisitCount,
424 TurnsSince,
425 TurnIndex,
426 ChoiceCount,
427 Random,
428 SeedRandom,
429
430 CastToInt,
432 CastToFloat,
433 Floor,
434 Ceiling,
435 Pow,
436 Min,
437 Max,
438
439 CallExternal(DefinitionId, u8),
441
442 ListContains,
444 ListNotContains,
445 ListIntersect,
446 ListAll,
447 ListInvert,
448 ListCount,
449 ListMin,
450 ListMax,
451 ListValue,
452 ListRange,
453 ListFromInt,
454 ListRandom,
455
456 Done,
458 Yield,
462 End,
463 Nop,
464
465 BeginStringEval,
467 EndStringEval,
468
469 SourceLocation(u32, u32),
471}
472
473impl Opcode {
476 #[expect(clippy::too_many_lines)]
478 pub fn encode(&self, buf: &mut Vec<u8>) {
479 match *self {
480 Self::PushInt(v) => {
482 write_u8(buf, PUSH_INT);
483 write_i32(buf, v);
484 }
485 Self::PushFloat(v) => {
486 write_u8(buf, PUSH_FLOAT);
487 write_f32(buf, v);
488 }
489 Self::PushBool(v) => {
490 write_u8(buf, PUSH_BOOL);
491 write_u8(buf, u8::from(v));
492 }
493 Self::PushString(idx) => {
494 write_u8(buf, PUSH_STRING);
495 write_u16(buf, idx);
496 }
497 Self::PushList(idx) => {
498 write_u8(buf, PUSH_LIST);
499 write_u16(buf, idx);
500 }
501 Self::PushDivertTarget(id) => {
502 write_u8(buf, PUSH_DIVERT_TARGET);
503 write_def_id(buf, id);
504 }
505 Self::PushNull => write_u8(buf, PUSH_NULL),
506 Self::Pop => write_u8(buf, POP),
507 Self::Duplicate => write_u8(buf, DUPLICATE),
508
509 Self::Add => write_u8(buf, ADD),
511 Self::Subtract => write_u8(buf, SUBTRACT),
512 Self::Multiply => write_u8(buf, MULTIPLY),
513 Self::Divide => write_u8(buf, DIVIDE),
514 Self::Modulo => write_u8(buf, MODULO),
515 Self::Negate => write_u8(buf, NEGATE),
516
517 Self::Equal => write_u8(buf, EQUAL),
519 Self::NotEqual => write_u8(buf, NOT_EQUAL),
520 Self::Greater => write_u8(buf, GREATER),
521 Self::GreaterOrEqual => write_u8(buf, GREATER_OR_EQUAL),
522 Self::Less => write_u8(buf, LESS),
523 Self::LessOrEqual => write_u8(buf, LESS_OR_EQUAL),
524
525 Self::Not => write_u8(buf, NOT),
527 Self::And => write_u8(buf, AND),
528 Self::Or => write_u8(buf, OR),
529
530 Self::GetGlobal(id) => {
532 write_u8(buf, GET_GLOBAL);
533 write_def_id(buf, id);
534 }
535 Self::SetGlobal(id) => {
536 write_u8(buf, SET_GLOBAL);
537 write_def_id(buf, id);
538 }
539
540 Self::DeclareTemp(idx) => {
542 write_u8(buf, DECLARE_TEMP);
543 write_u16(buf, idx);
544 }
545 Self::GetTemp(idx) => {
546 write_u8(buf, GET_TEMP);
547 write_u16(buf, idx);
548 }
549 Self::SetTemp(idx) => {
550 write_u8(buf, SET_TEMP);
551 write_u16(buf, idx);
552 }
553 Self::GetTempRaw(idx) => {
554 write_u8(buf, GET_TEMP_RAW);
555 write_u16(buf, idx);
556 }
557
558 Self::PushVarPointer(id) => {
560 write_u8(buf, PUSH_VAR_POINTER);
561 write_def_id(buf, id);
562 }
563 Self::PushTempPointer(slot) => {
564 write_u8(buf, PUSH_TEMP_POINTER);
565 write_u16(buf, slot);
566 }
567
568 Self::Jump(offset) => {
570 write_u8(buf, JUMP);
571 write_i32(buf, offset);
572 }
573 Self::JumpIfFalse(offset) => {
574 write_u8(buf, JUMP_IF_FALSE);
575 write_i32(buf, offset);
576 }
577 Self::Goto(id) => {
578 write_u8(buf, GOTO);
579 write_def_id(buf, id);
580 }
581 Self::GotoIf(id) => {
582 write_u8(buf, GOTO_IF);
583 write_def_id(buf, id);
584 }
585 Self::GotoVariable => write_u8(buf, GOTO_VARIABLE),
586
587 Self::EnterContainer(id) => {
589 write_u8(buf, ENTER_CONTAINER);
590 write_def_id(buf, id);
591 }
592 Self::ExitContainer => write_u8(buf, EXIT_CONTAINER),
593
594 Self::Call(id) => {
596 write_u8(buf, CALL);
597 write_def_id(buf, id);
598 }
599 Self::Return => write_u8(buf, RETURN),
600 Self::TunnelCall(id) => {
601 write_u8(buf, TUNNEL_CALL);
602 write_def_id(buf, id);
603 }
604 Self::TunnelReturn => write_u8(buf, TUNNEL_RETURN),
605 Self::TunnelCallVariable => write_u8(buf, TUNNEL_CALL_VARIABLE),
606 Self::CallVariable => write_u8(buf, CALL_VARIABLE),
607
608 Self::ThreadCall(id) => {
610 write_u8(buf, THREAD_CALL);
611 write_def_id(buf, id);
612 }
613 Self::ThreadStart => write_u8(buf, THREAD_START),
614 Self::ThreadDone => write_u8(buf, THREAD_DONE),
615
616 Self::EmitLine(idx, slot_count) => {
618 write_u8(buf, EMIT_LINE);
619 write_u16(buf, idx);
620 write_u8(buf, slot_count);
621 }
622 Self::EmitValue => write_u8(buf, EMIT_VALUE),
623 Self::EmitNewline => write_u8(buf, EMIT_NEWLINE),
624 Self::Spring => write_u8(buf, SPRING),
625 Self::Glue => write_u8(buf, GLUE),
626 Self::BeginTag => write_u8(buf, BEGIN_TAG),
627 Self::EndTag => write_u8(buf, END_TAG),
628 Self::EvalLine(idx, slot_count) => {
629 write_u8(buf, EVAL_LINE);
630 write_u16(buf, idx);
631 write_u8(buf, slot_count);
632 }
633 Self::BeginFragment => write_u8(buf, BEGIN_FRAGMENT),
634 Self::EndFragment => write_u8(buf, END_FRAGMENT),
635
636 Self::BeginChoice(flags, target) => {
638 write_u8(buf, BEGIN_CHOICE);
639 write_u8(buf, flags.to_byte());
640 write_def_id(buf, target);
641 }
642 Self::EndChoice => write_u8(buf, END_CHOICE),
643
644 Self::Sequence(kind, count) => {
646 write_u8(buf, SEQUENCE);
647 write_u8(buf, kind.to_byte());
648 write_u8(buf, count);
649 }
650 Self::SequenceBranch(offset) => {
651 write_u8(buf, SEQUENCE_BRANCH);
652 write_i32(buf, offset);
653 }
654
655 Self::VisitCount => write_u8(buf, VISIT_COUNT),
657 Self::CurrentVisitCount => write_u8(buf, CURRENT_VISIT_COUNT),
658 Self::TurnsSince => write_u8(buf, TURNS_SINCE),
659 Self::TurnIndex => write_u8(buf, TURN_INDEX),
660 Self::ChoiceCount => write_u8(buf, CHOICE_COUNT),
661 Self::Random => write_u8(buf, RANDOM),
662 Self::SeedRandom => write_u8(buf, SEED_RANDOM),
663
664 Self::CastToInt => write_u8(buf, CAST_TO_INT),
666 Self::CastToFloat => write_u8(buf, CAST_TO_FLOAT),
667 Self::Floor => write_u8(buf, FLOOR),
668 Self::Ceiling => write_u8(buf, CEILING),
669 Self::Pow => write_u8(buf, POW),
670 Self::Min => write_u8(buf, MIN),
671 Self::Max => write_u8(buf, MAX),
672
673 Self::CallExternal(id, argc) => {
675 write_u8(buf, CALL_EXTERNAL);
676 write_def_id(buf, id);
677 write_u8(buf, argc);
678 }
679
680 Self::ListContains => write_u8(buf, LIST_CONTAINS),
682 Self::ListNotContains => write_u8(buf, LIST_NOT_CONTAINS),
683 Self::ListIntersect => write_u8(buf, LIST_INTERSECT),
684 Self::ListAll => write_u8(buf, LIST_ALL),
685 Self::ListInvert => write_u8(buf, LIST_INVERT),
686 Self::ListCount => write_u8(buf, LIST_COUNT),
687 Self::ListMin => write_u8(buf, LIST_MIN),
688 Self::ListMax => write_u8(buf, LIST_MAX),
689 Self::ListValue => write_u8(buf, LIST_VALUE),
690 Self::ListRange => write_u8(buf, LIST_RANGE),
691 Self::ListFromInt => write_u8(buf, LIST_FROM_INT),
692 Self::ListRandom => write_u8(buf, LIST_RANDOM),
693
694 Self::Done => write_u8(buf, DONE),
696 Self::Yield => write_u8(buf, YIELD),
697 Self::End => write_u8(buf, END),
698 Self::Nop => write_u8(buf, NOP),
699
700 Self::BeginStringEval => write_u8(buf, BEGIN_STRING_EVAL),
702 Self::EndStringEval => write_u8(buf, END_STRING_EVAL),
703
704 Self::SourceLocation(line, col) => {
706 write_u8(buf, SOURCE_LOCATION);
707 write_u32(buf, line);
708 write_u32(buf, col);
709 }
710 }
711 }
712
713 #[expect(clippy::too_many_lines)]
717 pub fn decode(buf: &[u8], offset: &mut usize) -> Result<Self, DecodeError> {
718 let disc = read_u8(buf, offset)?;
719
720 let op = match disc {
721 PUSH_INT => Self::PushInt(read_i32(buf, offset)?),
723 PUSH_FLOAT => Self::PushFloat(read_f32(buf, offset)?),
724 PUSH_BOOL => Self::PushBool(read_u8(buf, offset)? != 0),
725 PUSH_STRING => Self::PushString(read_u16(buf, offset)?),
726 PUSH_LIST => Self::PushList(read_u16(buf, offset)?),
727 PUSH_DIVERT_TARGET => Self::PushDivertTarget(read_def_id(buf, offset)?),
728 PUSH_NULL => Self::PushNull,
729 POP => Self::Pop,
730 DUPLICATE => Self::Duplicate,
731
732 ADD => Self::Add,
734 SUBTRACT => Self::Subtract,
735 MULTIPLY => Self::Multiply,
736 DIVIDE => Self::Divide,
737 MODULO => Self::Modulo,
738 NEGATE => Self::Negate,
739
740 EQUAL => Self::Equal,
742 NOT_EQUAL => Self::NotEqual,
743 GREATER => Self::Greater,
744 GREATER_OR_EQUAL => Self::GreaterOrEqual,
745 LESS => Self::Less,
746 LESS_OR_EQUAL => Self::LessOrEqual,
747
748 NOT => Self::Not,
750 AND => Self::And,
751 OR => Self::Or,
752
753 GET_GLOBAL => Self::GetGlobal(read_def_id(buf, offset)?),
755 SET_GLOBAL => Self::SetGlobal(read_def_id(buf, offset)?),
756
757 DECLARE_TEMP => Self::DeclareTemp(read_u16(buf, offset)?),
759 GET_TEMP => Self::GetTemp(read_u16(buf, offset)?),
760 SET_TEMP => Self::SetTemp(read_u16(buf, offset)?),
761 GET_TEMP_RAW => Self::GetTempRaw(read_u16(buf, offset)?),
762
763 PUSH_VAR_POINTER => Self::PushVarPointer(read_def_id(buf, offset)?),
765 PUSH_TEMP_POINTER => Self::PushTempPointer(read_u16(buf, offset)?),
766
767 JUMP => Self::Jump(read_i32(buf, offset)?),
769 JUMP_IF_FALSE => Self::JumpIfFalse(read_i32(buf, offset)?),
770 GOTO => Self::Goto(read_def_id(buf, offset)?),
771 GOTO_IF => Self::GotoIf(read_def_id(buf, offset)?),
772 GOTO_VARIABLE => Self::GotoVariable,
773
774 ENTER_CONTAINER => Self::EnterContainer(read_def_id(buf, offset)?),
776 EXIT_CONTAINER => Self::ExitContainer,
777
778 CALL => Self::Call(read_def_id(buf, offset)?),
780 RETURN => Self::Return,
781 TUNNEL_CALL => Self::TunnelCall(read_def_id(buf, offset)?),
782 TUNNEL_RETURN => Self::TunnelReturn,
783 TUNNEL_CALL_VARIABLE => Self::TunnelCallVariable,
784 CALL_VARIABLE => Self::CallVariable,
785
786 THREAD_CALL => Self::ThreadCall(read_def_id(buf, offset)?),
788 THREAD_START => Self::ThreadStart,
789 THREAD_DONE => Self::ThreadDone,
790
791 EMIT_LINE => {
793 let idx = read_u16(buf, offset)?;
794 let slot_count = read_u8(buf, offset)?;
795 Self::EmitLine(idx, slot_count)
796 }
797 EMIT_VALUE => Self::EmitValue,
798 EMIT_NEWLINE => Self::EmitNewline,
799 SPRING => Self::Spring,
800 GLUE => Self::Glue,
801 BEGIN_TAG => Self::BeginTag,
802 END_TAG => Self::EndTag,
803 EVAL_LINE => {
804 let idx = read_u16(buf, offset)?;
805 let slot_count = read_u8(buf, offset)?;
806 Self::EvalLine(idx, slot_count)
807 }
808 BEGIN_FRAGMENT => Self::BeginFragment,
809 END_FRAGMENT => Self::EndFragment,
810
811 BEGIN_CHOICE => {
813 let flags = ChoiceFlags::from_byte(read_u8(buf, offset)?);
814 let target = read_def_id(buf, offset)?;
815 Self::BeginChoice(flags, target)
816 }
817 END_CHOICE => Self::EndChoice,
818
819 SEQUENCE => {
821 let kind = SequenceKind::from_byte(read_u8(buf, offset)?)?;
822 let count = read_u8(buf, offset)?;
823 Self::Sequence(kind, count)
824 }
825 SEQUENCE_BRANCH => Self::SequenceBranch(read_i32(buf, offset)?),
826
827 VISIT_COUNT => Self::VisitCount,
829 CURRENT_VISIT_COUNT => Self::CurrentVisitCount,
830 TURNS_SINCE => Self::TurnsSince,
831 TURN_INDEX => Self::TurnIndex,
832 CHOICE_COUNT => Self::ChoiceCount,
833 RANDOM => Self::Random,
834 SEED_RANDOM => Self::SeedRandom,
835
836 CAST_TO_INT => Self::CastToInt,
838 CAST_TO_FLOAT => Self::CastToFloat,
839 FLOOR => Self::Floor,
840 CEILING => Self::Ceiling,
841 POW => Self::Pow,
842 MIN => Self::Min,
843 MAX => Self::Max,
844
845 CALL_EXTERNAL => {
847 let id = read_def_id(buf, offset)?;
848 let argc = read_u8(buf, offset)?;
849 Self::CallExternal(id, argc)
850 }
851
852 LIST_CONTAINS => Self::ListContains,
854 LIST_NOT_CONTAINS => Self::ListNotContains,
855 LIST_INTERSECT => Self::ListIntersect,
856 LIST_ALL => Self::ListAll,
857 LIST_INVERT => Self::ListInvert,
858 LIST_COUNT => Self::ListCount,
859 LIST_MIN => Self::ListMin,
860 LIST_MAX => Self::ListMax,
861 LIST_VALUE => Self::ListValue,
862 LIST_RANGE => Self::ListRange,
863 LIST_FROM_INT => Self::ListFromInt,
864 LIST_RANDOM => Self::ListRandom,
865
866 DONE => Self::Done,
868 YIELD => Self::Yield,
869 END => Self::End,
870 NOP => Self::Nop,
871
872 BEGIN_STRING_EVAL => Self::BeginStringEval,
874 END_STRING_EVAL => Self::EndStringEval,
875
876 SOURCE_LOCATION => {
878 let line = read_u32(buf, offset)?;
879 let col = read_u32(buf, offset)?;
880 Self::SourceLocation(line, col)
881 }
882
883 _ => return Err(DecodeError::UnknownOpcode(disc)),
884 };
885
886 Ok(op)
887 }
888}
889
890#[cfg(test)]
891mod tests {
892 use super::*;
893 use crate::id::DefinitionTag;
894
895 fn roundtrip(op: &Opcode) {
896 let mut buf = Vec::new();
897 op.encode(&mut buf);
898 let mut offset = 0;
899 let decoded = Opcode::decode(&buf, &mut offset).unwrap();
900 assert_eq!(*op, decoded, "roundtrip failed for {op:?}");
901 assert_eq!(offset, buf.len(), "not all bytes consumed for {op:?}");
902 }
903
904 fn test_id() -> DefinitionId {
905 DefinitionId::new(DefinitionTag::Address, 0xBEEF)
906 }
907
908 fn global_id() -> DefinitionId {
909 DefinitionId::new(DefinitionTag::GlobalVar, 42)
910 }
911
912 fn ext_id() -> DefinitionId {
913 DefinitionId::new(DefinitionTag::ExternalFn, 0xCAFE)
914 }
915
916 #[test]
917 fn roundtrip_stack_literals() {
918 roundtrip(&Opcode::PushInt(0));
919 roundtrip(&Opcode::PushInt(-1));
920 roundtrip(&Opcode::PushInt(i32::MAX));
921 roundtrip(&Opcode::PushInt(i32::MIN));
922 roundtrip(&Opcode::PushFloat(0.0));
923 roundtrip(&Opcode::PushFloat(3.125));
924 roundtrip(&Opcode::PushFloat(f32::NEG_INFINITY));
925 roundtrip(&Opcode::PushBool(true));
926 roundtrip(&Opcode::PushBool(false));
927 roundtrip(&Opcode::PushString(0));
928 roundtrip(&Opcode::PushString(u16::MAX));
929 roundtrip(&Opcode::PushList(7));
930 roundtrip(&Opcode::PushDivertTarget(test_id()));
931 roundtrip(&Opcode::PushNull);
932 roundtrip(&Opcode::Pop);
933 roundtrip(&Opcode::Duplicate);
934 }
935
936 #[test]
937 fn roundtrip_arithmetic() {
938 for op in [
939 Opcode::Add,
940 Opcode::Subtract,
941 Opcode::Multiply,
942 Opcode::Divide,
943 Opcode::Modulo,
944 Opcode::Negate,
945 ] {
946 roundtrip(&op);
947 }
948 }
949
950 #[test]
951 fn roundtrip_comparison() {
952 for op in [
953 Opcode::Equal,
954 Opcode::NotEqual,
955 Opcode::Greater,
956 Opcode::GreaterOrEqual,
957 Opcode::Less,
958 Opcode::LessOrEqual,
959 ] {
960 roundtrip(&op);
961 }
962 }
963
964 #[test]
965 fn roundtrip_logic() {
966 for op in [Opcode::Not, Opcode::And, Opcode::Or] {
967 roundtrip(&op);
968 }
969 }
970
971 #[test]
972 fn roundtrip_globals() {
973 roundtrip(&Opcode::GetGlobal(global_id()));
974 roundtrip(&Opcode::SetGlobal(global_id()));
975 }
976
977 #[test]
978 fn roundtrip_temps() {
979 roundtrip(&Opcode::DeclareTemp(0));
980 roundtrip(&Opcode::GetTemp(5));
981 roundtrip(&Opcode::SetTemp(u16::MAX));
982 roundtrip(&Opcode::GetTempRaw(3));
983 }
984
985 #[test]
986 fn roundtrip_var_pointer() {
987 roundtrip(&Opcode::PushVarPointer(global_id()));
988 roundtrip(&Opcode::PushTempPointer(0));
989 roundtrip(&Opcode::PushTempPointer(u16::MAX));
990 }
991
992 #[test]
993 fn roundtrip_control_flow() {
994 roundtrip(&Opcode::Jump(0));
995 roundtrip(&Opcode::Jump(-42));
996 roundtrip(&Opcode::JumpIfFalse(100));
997 roundtrip(&Opcode::Goto(test_id()));
998 roundtrip(&Opcode::GotoIf(test_id()));
999 roundtrip(&Opcode::GotoVariable);
1000 }
1001
1002 #[test]
1003 fn roundtrip_container_flow() {
1004 roundtrip(&Opcode::EnterContainer(test_id()));
1005 roundtrip(&Opcode::ExitContainer);
1006 }
1007
1008 #[test]
1009 fn roundtrip_functions_tunnels() {
1010 roundtrip(&Opcode::Call(test_id()));
1011 roundtrip(&Opcode::Return);
1012 roundtrip(&Opcode::TunnelCall(test_id()));
1013 roundtrip(&Opcode::TunnelReturn);
1014 roundtrip(&Opcode::TunnelCallVariable);
1015 roundtrip(&Opcode::CallVariable);
1016 }
1017
1018 #[test]
1019 fn roundtrip_threads() {
1020 roundtrip(&Opcode::ThreadCall(test_id()));
1021 roundtrip(&Opcode::ThreadStart);
1022 roundtrip(&Opcode::ThreadDone);
1023 }
1024
1025 #[test]
1026 fn roundtrip_output() {
1027 roundtrip(&Opcode::EmitLine(0, 0));
1028 roundtrip(&Opcode::EmitLine(999, 3));
1029 roundtrip(&Opcode::EmitValue);
1030 roundtrip(&Opcode::EmitNewline);
1031 roundtrip(&Opcode::Spring);
1032 roundtrip(&Opcode::Glue);
1033 roundtrip(&Opcode::BeginTag);
1034 roundtrip(&Opcode::EndTag);
1035 roundtrip(&Opcode::EvalLine(0, 0));
1036 roundtrip(&Opcode::EvalLine(42, 2));
1037 }
1038
1039 #[test]
1040 fn roundtrip_choices() {
1041 roundtrip(&Opcode::BeginChoice(
1042 ChoiceFlags {
1043 has_condition: true,
1044 has_start_content: false,
1045 has_choice_only_content: true,
1046 once_only: false,
1047 is_invisible_default: true,
1048 },
1049 test_id(),
1050 ));
1051 roundtrip(&Opcode::BeginChoice(
1052 ChoiceFlags {
1053 has_condition: false,
1054 has_start_content: true,
1055 has_choice_only_content: false,
1056 once_only: true,
1057 is_invisible_default: false,
1058 },
1059 test_id(),
1060 ));
1061 roundtrip(&Opcode::EndChoice);
1062 }
1063
1064 #[test]
1065 fn roundtrip_sequences() {
1066 for kind in [
1067 SequenceKind::Cycle,
1068 SequenceKind::Stopping,
1069 SequenceKind::OnceOnly,
1070 SequenceKind::Shuffle,
1071 ] {
1072 roundtrip(&Opcode::Sequence(kind, 5));
1073 }
1074 roundtrip(&Opcode::SequenceBranch(-10));
1075 roundtrip(&Opcode::SequenceBranch(0));
1076 }
1077
1078 #[test]
1079 fn roundtrip_intrinsics() {
1080 for op in [
1081 Opcode::VisitCount,
1082 Opcode::CurrentVisitCount,
1083 Opcode::TurnsSince,
1084 Opcode::TurnIndex,
1085 Opcode::ChoiceCount,
1086 Opcode::Random,
1087 Opcode::SeedRandom,
1088 ] {
1089 roundtrip(&op);
1090 }
1091 }
1092
1093 #[test]
1094 fn roundtrip_casts_math() {
1095 for op in [
1096 Opcode::CastToInt,
1097 Opcode::CastToFloat,
1098 Opcode::Floor,
1099 Opcode::Ceiling,
1100 Opcode::Pow,
1101 Opcode::Min,
1102 Opcode::Max,
1103 ] {
1104 roundtrip(&op);
1105 }
1106 }
1107
1108 #[test]
1109 fn roundtrip_call_external() {
1110 roundtrip(&Opcode::CallExternal(ext_id(), 3));
1111 roundtrip(&Opcode::CallExternal(ext_id(), 0));
1112 }
1113
1114 #[test]
1115 fn roundtrip_list_ops() {
1116 for op in [
1117 Opcode::ListContains,
1118 Opcode::ListNotContains,
1119 Opcode::ListIntersect,
1120 Opcode::ListAll,
1121 Opcode::ListInvert,
1122 Opcode::ListCount,
1123 Opcode::ListMin,
1124 Opcode::ListMax,
1125 Opcode::ListValue,
1126 Opcode::ListRange,
1127 Opcode::ListFromInt,
1128 Opcode::ListRandom,
1129 ] {
1130 roundtrip(&op);
1131 }
1132 }
1133
1134 #[test]
1135 fn roundtrip_lifecycle() {
1136 for op in [Opcode::Done, Opcode::Yield, Opcode::End, Opcode::Nop] {
1137 roundtrip(&op);
1138 }
1139 }
1140
1141 #[test]
1142 fn roundtrip_string_eval() {
1143 roundtrip(&Opcode::BeginStringEval);
1144 roundtrip(&Opcode::EndStringEval);
1145 }
1146
1147 #[test]
1148 fn roundtrip_debug() {
1149 roundtrip(&Opcode::SourceLocation(1, 0));
1150 roundtrip(&Opcode::SourceLocation(u32::MAX, u32::MAX));
1151 }
1152
1153 #[test]
1154 fn decode_unknown_opcode() {
1155 let buf = [0xFF];
1156 let mut offset = 0;
1157 let err = Opcode::decode(&buf, &mut offset).unwrap_err();
1158 assert_eq!(err, DecodeError::UnknownOpcode(0xFF));
1159 }
1160
1161 #[test]
1162 fn decode_unexpected_eof() {
1163 let buf = [PUSH_INT, 0x00];
1165 let mut offset = 0;
1166 let err = Opcode::decode(&buf, &mut offset).unwrap_err();
1167 assert_eq!(err, DecodeError::UnexpectedEof);
1168 }
1169
1170 #[test]
1171 fn decode_multiple_instructions() {
1172 let ops = vec![
1173 Opcode::PushInt(42),
1174 Opcode::PushBool(true),
1175 Opcode::Add,
1176 Opcode::Done,
1177 ];
1178 let mut buf = Vec::new();
1179 for op in &ops {
1180 op.encode(&mut buf);
1181 }
1182 let mut offset = 0;
1183 for expected in &ops {
1184 let decoded = Opcode::decode(&buf, &mut offset).unwrap();
1185 assert_eq!(*expected, decoded);
1186 }
1187 assert_eq!(offset, buf.len());
1188 }
1189
1190 #[test]
1191 fn choice_flags_roundtrip() {
1192 for bits in 0..32u8 {
1193 let flags = ChoiceFlags::from_byte(bits);
1194 assert_eq!(flags.to_byte(), bits);
1195 }
1196 }
1197}