gbf_core/opcode.rs
1#![deny(missing_docs)]
2
3use serde::{Deserialize, Serialize};
4use std::{fmt::Display, str::FromStr};
5use thiserror::Error;
6
7/// Error type for invalid opcodes.
8#[derive(Error, Debug, Serialize, Deserialize, Clone)]
9pub enum OpcodeError {
10 /// Error for when an invalid opcode is encountered.
11 #[error("Invalid opcode: {0}")]
12 InvalidOpcode(u8),
13
14 /// Error for when an invalid opcode string is encountered.
15 #[error("Invalid opcode: {0}")]
16 InvalidOpcodeString(String),
17}
18
19/// A macro to define opcodes as a `#[repr(u8)]` enum.
20///
21/// # Overview
22/// This macro simplifies defining opcodes by automatically generating:
23/// - An `Opcode` enum with associated `u8` values.
24/// - Utility methods like `from_byte` for safe conversion and `to_byte` for reverse mapping.
25macro_rules! define_opcodes {
26 (
27 $( $name:ident = $value:expr ),* $(,)?
28 ) => {
29 /// Enum representing opcodes for a bytecode system.
30 ///
31 /// Each variant corresponds to an opcode, with its numeric value
32 /// defined as a `u8`.
33 #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash)]
34 #[repr(u8)]
35 pub enum Opcode {
36 $(
37 #[doc = concat!("Represents the opcode ", stringify!($name))]
38 $name = $value,
39 )*
40 }
41
42 impl Opcode {
43 /// Converts a `u8` into an `Opcode`, if possible.
44 ///
45 /// # Arguments
46 /// - `byte`: The `u8` value to convert.
47 ///
48 /// # Returns
49 /// - `Some(Opcode)` if the value corresponds to a valid opcode.
50 /// - `None` if the value does not match any defined opcode.
51 ///
52 /// # Example
53 /// ```
54 /// use gbf_core::opcode::Opcode;
55 ///
56 /// let opcode = Opcode::from_byte(0x1).unwrap();
57 /// assert_eq!(opcode, Opcode::Jmp);
58 /// ```
59 pub fn from_byte(byte: u8) -> Result<Self, OpcodeError> {
60 match byte {
61 $(
62 $value => Ok(Opcode::$name),
63 )*
64 _ => Err(OpcodeError::InvalidOpcode(byte)),
65 }
66 }
67
68 /// Converts an `Opcode` to its `u8` value.
69 ///
70 /// # Returns
71 /// - The numeric value (`u8`) of the opcode.
72 ///
73 /// # Example
74 /// ```
75 /// use gbf_core::opcode::Opcode;
76 ///
77 /// let opcode = Opcode::Jmp;
78 /// assert_eq!(opcode.to_byte(), 0x1);
79 /// ```
80 pub fn to_byte(self) -> u8 {
81 self as u8
82 }
83
84 /// Get the number of defined opcodes.
85 ///
86 /// # Returns
87 /// - The number of opcodes.
88 ///
89 /// # Example
90 /// ```
91 /// use gbf_core::opcode::Opcode;
92 ///
93 /// let count = Opcode::count();
94 /// ```
95 pub fn count() -> usize {
96 0 $(+ { let _ = stringify!($name); 1 })*
97 }
98
99 /// Get a list of all defined opcodes.
100 ///
101 /// # Returns
102 /// - A vector containing all defined opcodes.
103 ///
104 /// # Example
105 /// ```
106 /// use gbf_core::opcode::Opcode;
107 ///
108 /// let opcodes = Opcode::all();
109 /// ```
110 pub fn all() -> &'static [Opcode] {
111 &[
112 $(
113 Opcode::$name,
114 )*
115 ]
116 }
117
118 /// If the opcode is a conditional jump instruction.
119 ///
120 /// # Returns
121 /// - `true` if the opcode is a conditional jump instruction.
122 /// - `false` otherwise.
123 ///
124 /// # Example
125 /// ```
126 /// use gbf_core::opcode::Opcode;
127 ///
128 /// let opcode = Opcode::Jeq;
129 /// assert!(opcode.is_conditional_jump());
130 /// ```
131 pub fn is_conditional_jump(self) -> bool {
132 return match self {
133 $(
134 Opcode::$name => {
135 matches!(self, Opcode::Jeq | Opcode::Jne)
136 },
137 )*
138 };
139 }
140
141 /// If the opcode is an unconditional jump instruction.
142 ///
143 /// # Returns
144 /// - `true` if the opcode is an unconditional jump instruction.
145 /// - `false` otherwise.
146 ///
147 /// # Example
148 /// ```
149 /// use gbf_core::opcode::Opcode;
150 ///
151 /// let opcode = Opcode::Jmp;
152 /// assert!(opcode.is_unconditional_jump());
153 /// ```
154 pub fn is_unconditional_jump(self) -> bool {
155 return match self {
156 $(
157 Opcode::$name => {
158 matches!(self, Opcode::Jmp)
159 },
160 )*
161 };
162 }
163
164 /// If this CFG-related opcode contains a branch with a fall-through path to the next instruction.
165 ///
166 /// # Returns
167 /// - `true` if the opcode is a block-starting opcode.
168 /// - `false` otherwise.
169 ///
170 /// # Example
171 /// ```
172 /// use gbf_core::opcode::Opcode;
173 ///
174 /// let opcode = Opcode::Jmp;
175 /// assert!(!opcode.has_fall_through());
176 /// ```
177 pub fn has_fall_through(self) -> bool {
178 return self.is_conditional_jump() || match self {
179 $(
180 Opcode::$name => {
181 matches!(self, Opcode::With | Opcode::ShortCircuitAnd | Opcode::ShortCircuitOr | Opcode::ForEach)
182 },
183 )*
184 };
185 }
186
187 /// If this CFG or non-CFG related opcode should connect to the next block if it is a terminal
188 /// opcode.
189 ///
190 /// # Returns
191 /// - `true` if the opcode has a fall-through path.
192 /// - `false` otherwise.
193 ///
194 /// # Example
195 /// ```
196 /// use gbf_core::opcode::Opcode;
197 ///
198 /// let opcode = Opcode::Jmp;
199 /// assert!(!opcode.connects_to_next_block());
200 ///
201 /// let opcode2 = Opcode::Ret;
202 /// assert!(!opcode2.connects_to_next_block());
203 /// ```
204 pub fn connects_to_next_block(self) -> bool {
205 !self.is_unconditional_jump() && !matches!(self, Opcode::Ret)
206 }
207
208 /// If this CFG-related opcode has a corresponding jump target as an operand.
209 ///
210 /// # Returns
211 /// - `true` if the opcode has a jump target.
212 /// - `false` otherwise.
213 ///
214 /// # Example
215 /// ```
216 /// use gbf_core::opcode::Opcode;
217 ///
218 /// let opcode = Opcode::Jmp;
219 /// assert!(opcode.has_jump_target());
220 /// ```
221 pub fn has_jump_target(self) -> bool {
222 return self.is_unconditional_jump() || self.has_fall_through();
223 }
224
225 /// If this CFG-related opcode should always be the last opcode in a block. If there is an
226 /// instruction that succeeds this opcode, it should be considered the start of a different block.
227 ///
228 /// # Returns
229 /// - `true` if the opcode is block-ending.
230 /// - `false` otherwise.
231 ///
232 /// # Example
233 /// ```
234 /// use gbf_core::opcode::Opcode;
235 ///
236 /// let opcode = Opcode::Ret;
237 /// assert!(opcode.is_block_end());
238 /// ```
239 pub fn is_block_end(self) -> bool {
240 return self.has_jump_target() || match self {
241 $(
242 Opcode::$name => {
243 matches!(self, Opcode::Ret | Opcode::ShortCircuitEnd | Opcode::WithEnd)
244 },
245 )*
246 };
247 }
248 }
249
250 impl Display for Opcode {
251 /// Convert an `Opcode` to a human-readable string.
252 ///
253 /// # Returns
254 /// - A string representation of the opcode.
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 write!(f, "{}", match self {
257 $(
258 Opcode::$name => stringify!($name),
259 )*
260 })
261 }
262 }
263
264 impl FromStr for Opcode {
265 type Err = OpcodeError;
266
267 /// Convert a string to an `Opcode`
268 ///
269 /// # Arguments
270 /// - `name`: The string to convert.
271 ///
272 /// # Returns
273 /// - `Ok(Opcode)` if the string corresponds to a valid opcode.
274 fn from_str(s: &str) -> Result<Self, Self::Err> {
275 match s {
276 $(
277 stringify!($name) => Ok(Opcode::$name),
278 )*
279 _ => Err(OpcodeError::InvalidOpcodeString(s.to_string())),
280 }
281 }
282 }
283 };
284}
285
286// Using the macro to define the opcodes
287define_opcodes! {
288 Jmp = 0x1,
289 Jeq = 0x2,
290 ShortCircuitOr = 0x3,
291 Jne = 0x4,
292 ShortCircuitAnd = 0x5,
293 Call = 0x6,
294 Ret = 0x7,
295 Sleep = 0x8,
296 IncreaseLoopCounter = 0x9,
297 FunctionStart = 0xa,
298 WaitFor = 0xb,
299 // Unknown values: 0xc - 0x13
300 PushNumber = 0x14,
301 PushString = 0x15,
302 PushVariable = 0x16,
303 PushArray = 0x17,
304 PushTrue = 0x18,
305 PushFalse = 0x19,
306 PushNull = 0x1a,
307 Pi = 0x1b,
308 // Unknown values: 0x1c - 0x1d
309 Copy = 0x1e,
310 Swap = 0x1f,
311 Pop = 0x20,
312 ConvertToFloat = 0x21,
313 ConvertToString = 0x22,
314 AccessMember = 0x23,
315 ConvertToObject = 0x24,
316 EndArray = 0x25,
317 NewUninitializedArray = 0x26,
318 SetArray = 0x27,
319 New = 0x28,
320 MakeVar = 0x29,
321 NewObject = 0x2a,
322 ConvertToVariable = 0x2b,
323 ShortCircuitEnd = 0x2c,
324 SetRegister = 0x2d,
325 GetRegister = 0x2e,
326 MarkRegisterVariable = 0x2f,
327 // Unknown values: 0x30 - 0x31
328 Assign = 0x32,
329 EndParams = 0x33,
330 Inc = 0x34,
331 Dec = 0x35,
332 // Unknown values: 0x36 - 0x3b
333 Add = 0x3c,
334 Subtract = 0x3d,
335 Multiply = 0x3e,
336 Divide = 0x3f,
337 Modulo = 0x40,
338 Power = 0x41,
339 // Unknown values: 0x42 - 0x43
340 LogicalNot = 0x44,
341 UnarySubtract = 0x45,
342 Equal = 0x46,
343 NotEqual = 0x47,
344 LessThan = 0x48,
345 GreaterThan = 0x49,
346 LessThanOrEqual = 0x4a,
347 GreaterThanOrEqual = 0x4b,
348 BitwiseOr = 0x4c,
349 BitwiseAnd = 0x4d,
350 BitwiseXor = 0x4e,
351 BitwiseInvert = 0x4f,
352 InRange = 0x50,
353 In = 0x51,
354 ObjIndex = 0x52,
355 ObjType = 0x53,
356 Format = 0x54,
357 Int = 0x55,
358 Abs = 0x56,
359 Random = 0x57,
360 Sin = 0x58,
361 Cos = 0x59,
362 ArcTan = 0x5a,
363 Exp = 0x5b,
364 Log = 0x5c,
365 Min = 0x5d,
366 Max = 0x5e,
367 GetAngle = 0x5f,
368 GetDir = 0x60,
369 VecX = 0x61,
370 VecY = 0x62,
371 ObjIndices = 0x63,
372 ObjLink = 0x64,
373 ShiftLeft = 0x65,
374 ShiftRight = 0x66,
375 Char = 0x67,
376 // Unknown values: 0x68 - 0x6d
377 ObjTrim = 0x6e,
378 ObjLength = 0x6f,
379 ObjPos = 0x70,
380 Join = 0x71,
381 ObjCharAt = 0x72,
382 ObjSubstring = 0x73,
383 ObjStarts = 0x74,
384 ObjEnds = 0x75,
385 ObjTokenize = 0x76,
386 GetTranslation = 0x77,
387 ObjPositions = 0x78,
388 // Unknown values: 0x78 - 0x81
389 ObjSize = 0x82,
390 ArrayAccess = 0x83,
391 AssignArray = 0x84,
392 AssignMultiDimensionalArrayIndex = 0x85,
393 AssignMultiDimensionalArray = 0x86,
394 ObjSubArray = 0x87,
395 ObjAddString = 0x88,
396 ObjDeleteString = 0x89,
397 ObjRemoveString = 0x8a,
398 ObjReplaceString = 0x8b,
399 ObjInsertString = 0x8c,
400 ObjClear = 0x8d,
401 MultiDimenArray = 0x8e,
402 // Unknown values: 0x8f - 0x95
403 With = 0x96,
404 WithEnd = 0x97,
405 // Unknown values: 0x98 - 0xa2
406 ForEach = 0xa3,
407 // Unknown values: 0xa4 - 0xb3
408 This = 0xb4,
409 ThisO = 0xb5,
410 Player = 0xb6,
411 PlayerO = 0xb7,
412 Level = 0xb8,
413 // Unknown values: 0xb9 - 0xbc
414 Temp = 0xbd,
415 Params = 0xbe,
416 // Unknown values: 0xbf - 0xef
417 ImmStringByte = 0xf0,
418 ImmStringShort = 0xf1,
419 ImmStringInt = 0xf2,
420 ImmByte = 0xf3,
421 ImmShort = 0xf4,
422 ImmInt = 0xf5,
423 ImmFloat = 0xf6,
424}
425
426#[cfg(test)]
427mod tests {
428 use super::*;
429
430 #[test]
431 fn test_valid_conversion() {
432 assert_eq!(Opcode::from_byte(0x1).unwrap(), Opcode::Jmp);
433 assert_eq!(Opcode::from_byte(0x21).unwrap(), Opcode::ConvertToFloat);
434 }
435
436 #[test]
437 fn test_invalid_conversion() {
438 assert!(Opcode::from_byte(0xFF).is_err());
439 assert!(Opcode::from_byte(0x68).is_err());
440 }
441
442 #[test]
443 fn test_to_byte() {
444 assert_eq!(Opcode::Jmp.to_byte(), 0x1);
445 assert_eq!(Opcode::ConvertToString.to_byte(), 0x22);
446 }
447
448 #[test]
449 fn test_fmt() {
450 assert_eq!(Opcode::Jmp.to_string(), "Jmp");
451 assert_eq!(Opcode::ConvertToString.to_string(), "ConvertToString");
452
453 let opcode = Opcode::from_byte(0x1).unwrap();
454 assert_eq!(format!("{}", opcode), "Jmp");
455
456 let opcode = Opcode::from_byte(0x22).unwrap();
457 assert_eq!(format!("{}", opcode), "ConvertToString");
458
459 // Test all opcodes
460 for opcode in Opcode::all() {
461 let str_val = format!("{}", opcode);
462 let from_str = Opcode::from_str(&str_val).unwrap();
463 assert_eq!(*opcode, from_str);
464 }
465 }
466
467 #[test]
468 fn test_from_str() {
469 assert_eq!(Opcode::from_str("Jmp").unwrap(), Opcode::Jmp);
470 assert_eq!(
471 Opcode::from_str("ConvertToString").unwrap(),
472 Opcode::ConvertToString
473 );
474 assert!(Opcode::from_str("Invalid").is_err());
475 }
476
477 #[test]
478 fn test_count() {
479 assert_eq!(Opcode::all().len(), Opcode::count());
480 }
481
482 #[test]
483 fn test_is_conditional_jump() {
484 assert!(Opcode::Jeq.is_conditional_jump());
485 assert!(Opcode::Jne.is_conditional_jump());
486 assert!(!Opcode::Jmp.is_conditional_jump());
487 }
488
489 #[test]
490 fn test_is_unconditional_jump() {
491 assert!(Opcode::Jmp.is_unconditional_jump());
492 assert!(!Opcode::Jeq.is_unconditional_jump());
493 assert!(!Opcode::Jne.is_unconditional_jump());
494 }
495
496 #[test]
497 fn test_has_jump_target() {
498 assert!(Opcode::Jmp.has_jump_target());
499 assert!(Opcode::Jeq.has_jump_target());
500 assert!(Opcode::Jne.has_jump_target());
501 assert!(Opcode::With.has_jump_target());
502 assert!(Opcode::ShortCircuitAnd.has_jump_target());
503 assert!(Opcode::ShortCircuitOr.has_jump_target());
504 assert!(Opcode::Jmp.has_jump_target());
505 }
506
507 #[test]
508 fn test_is_block_end() {
509 assert!(Opcode::Jmp.is_block_end());
510 assert!(Opcode::Ret.is_block_end());
511 assert!(Opcode::ShortCircuitEnd.is_block_end());
512 assert!(Opcode::WithEnd.is_block_end());
513 assert!(Opcode::Jeq.is_block_end());
514 assert!(Opcode::Jne.is_block_end());
515 assert!(Opcode::ShortCircuitAnd.is_block_end());
516 assert!(Opcode::ShortCircuitOr.is_block_end());
517 assert!(Opcode::ForEach.is_block_end());
518 assert!(Opcode::ShortCircuitEnd.is_block_end());
519 }
520}