use serde::{Deserialize, Serialize};
pub(crate) mod cloud;
pub(crate) mod lifter;
const CALL_OPS: &[&str] = &["m_call", "m_icall"];
#[derive(Serialize, Deserialize, Debug)]
pub struct Op {
op: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
out: Option<String>,
inputs: Vec<String>,
}
impl crate::Op for Op {
fn mnemonic(&self) -> &str {
&self.op
}
fn inputs(&self) -> &[String] {
&self.inputs
}
fn ret(&self) -> Option<&str> {
self.out.as_deref()
}
fn is_call(&self) -> bool {
CALL_OPS.contains(&self.op.as_str())
}
fn symbol_key(&self) -> Option<String> {
if !self.is_call() {
return None;
}
self.inputs.first().cloned()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Op as _;
fn op(name: &str, inputs: &[&str]) -> Op {
Op {
op: name.to_string(),
out: None,
inputs: inputs.iter().map(ToString::to_string).collect(),
}
}
#[test]
fn test_both_spellings_of_a_call_are_calls() {
let direct = op("m_call", &["$_printf"]);
let indirect = op("m_icall", &["r0"]);
assert!(direct.is_call());
assert!(indirect.is_call());
assert_eq!(direct.symbol_key().as_deref(), Some("$_printf"));
}
#[test]
fn test_an_operation_that_is_not_a_call_names_no_symbol() {
let mov = op("m_mov", &["r0", "$_printf"]);
assert!(!mov.is_call());
assert_eq!(mov.symbol_key(), None);
}
#[test]
fn test_a_call_without_operands_yields_nothing() {
let bare = op("m_call", &[]);
assert!(bare.is_call());
assert_eq!(bare.symbol_key(), None);
}
#[test]
fn test_the_operands_and_the_destination_are_what_was_read() {
let mut mov = op("m_mov", &["r1", "r0"]);
assert_eq!(mov.inputs(), ["r1".to_string(), "r0".to_string()]);
assert_eq!(mov.ret(), None);
mov.out = Some("r0".to_string());
assert_eq!(mov.ret(), Some("r0"));
assert_eq!(mov.mnemonic(), "m_mov");
}
}