use convert_case::{Case, Casing}; use std::{collections::BTreeMap, path::PathBuf}; use crate::{generate::utils, stationpedia}; pub fn generate_instructions( stationpedia: &stationpedia::Stationpedia, workspace: &std::path::Path, ) -> color_eyre::Result> { let instructions_path = workspace .join("ic10emu") .join("src") .join("vm") .join("instructions"); if !instructions_path.exists() { std::fs::create_dir(&instructions_path)?; } let mut writer = std::io::BufWriter::new(std::fs::File::create(instructions_path.join("enums.rs"))?); write_instructions_enum(&mut writer, &stationpedia.script_commands)?; let mut writer = std::io::BufWriter::new(std::fs::File::create(instructions_path.join("traits.rs"))?); write_instruction_trait_use(&mut writer)?; for (typ, info) in &stationpedia.script_commands { write_instruction_trait(&mut writer, (typ, info))?; } write_instruction_super_trait(&mut writer, &stationpedia.script_commands)?; Ok(vec![ instructions_path.join("enums.rs"), instructions_path.join("traits.rs"), ]) } fn write_instructions_enum( writer: &mut T, instructions: &BTreeMap, ) -> color_eyre::Result<()> { eprintln!("Writing instruction Listings ..."); let mut instructions = instructions.clone(); for (_, ref mut info) in instructions.iter_mut() { info.example = utils::strip_color(&info.example); } write!( writer, "use serde_derive::{{Deserialize, Serialize}};\n\ use strum::{{\n \ Display, EnumIter, EnumProperty, EnumString, FromRepr,\n\ }};\n use crate::vm::object::traits::Programmable;\n\ " )?; write!( writer, "#[derive(Debug, Display, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)]\n\ #[derive(EnumIter, EnumString, EnumProperty, FromRepr)]\n\ #[strum(use_phf, serialize_all = \"lowercase\")]\n\ #[serde(rename_all = \"lowercase\")]\n\ pub enum InstructionOp {{\n\ " )?; writeln!(writer, " Nop,")?; for (name, info) in &instructions { let props_str = format!( "props( example = \"{}\", desc = \"{}\", operands = \"{}\" )", &info.example, &info.desc, count_operands(&info.example) ); writeln!( writer, " #[strum({props_str})] {},", name.to_case(Case::Pascal) )?; } writeln!(writer, "}}")?; write!( writer, "impl InstructionOp {{\n \ pub fn num_operands(&self) -> usize {{\n \ self.get_str(\"operands\").expect(\"instruction without operand property\").parse::().expect(\"invalid instruction operand property\")\n \ }}\n\ \n \ pub fn execute(\n \ &self,\n \ ic: &mut T,\n \ operands: &[crate::vm::instructions::operands::Operand],\n \ ) -> Result<(), crate::errors::ICError>\n \ where\n \ T: Programmable,\n\ {{\n \ let num_operands = self.num_operands();\n \ if operands.len() != num_operands {{\n \ return Err(crate::errors::ICError::mismatch_operands(operands.len(), num_operands as u32));\n \ }}\n \ match self {{\n \ Self::Nop => Ok(()),\n \ " )?; for (name, info) in instructions { let num_operands = count_operands(&info.example); let operands = (0..num_operands) .map(|i| format!("&operands[{}]", i)) .collect::>() .join(", "); let trait_name = name.to_case(Case::Pascal); writeln!( writer, " Self::{trait_name} => ic.execute_{name}({operands}),", )?; } write!( writer, " }}\ }}\n\ }} " )?; Ok(()) } fn write_instruction_trait( writer: &mut T, instruction: (&str, &stationpedia::Command), ) -> color_eyre::Result<()> { let (name, info) = instruction; let op_name = name.to_case(Case::Pascal); let trait_name = format!("{op_name}Instruction"); let operands = operand_names(&info.example) .iter() .map(|name| { let mut n: &str = name; if n == "str" { n = "string"; } format!( "{}: &crate::vm::instructions::operands::Operand", n.to_case(Case::Snake) ) }) .collect::>() .join(", "); let operands_inner = operand_names(&info.example) .iter() .map(|name| { let mut n: &str = name; if n == "str" { n = "string"; } format!( "{}: &crate::vm::instructions::operands::InstOperand", n.to_case(Case::Snake) ) }) .collect::>() .join(", "); let operand_call = operand_names(&info.example) .iter() .enumerate() .map(|(index, name)| { let mut n: &str = name; if n == "str" { n = "string"; } format!( "&crate::vm::instructions::operands::InstOperand::new({}, InstructionOp::{op_name}, {index})", n.to_case(Case::Snake) ) }) .collect::>() .join(", "); let example = utils::strip_color(&info.example); write!( writer, "pub trait {trait_name}: IntegratedCircuit {{\n \ /// {example} \n \ fn execute_{name}(&mut self, {operands}) -> Result<(), crate::errors::ICError> {{\n \ {trait_name}::execute_inner(self, {operand_call})\n \ }}\n \ /// {example} \n \ fn execute_inner(&mut self, {operands_inner}) -> Result<(), crate::errors::ICError>;\n\ }}" )?; Ok(()) } fn count_operands(example: &str) -> usize { example.split(' ').count() - 1 } fn operand_names(example: &str) -> Vec { utils::strip_color(example) .split(' ') .skip(1) .map(|name| name.split(['?', '(']).next().unwrap().to_string()) .collect() } fn write_instruction_trait_use(writer: &mut T) -> color_eyre::Result<()> { write!( writer, "\ use crate::vm::object::traits::IntegratedCircuit;\n\ use crate::vm::instructions::enums::InstructionOp;\n\ " )?; Ok(()) } fn write_instruction_super_trait( writer: &mut T, instructions: &BTreeMap, ) -> color_eyre::Result<()> { let traits = instructions .keys() .map(|name| format!("{}Instruction", name.to_case(Case::Pascal))) .collect::>() .join(" + "); write!( writer, "\ pub trait ICInstructable: {traits} {{}}\n\ impl ICInstructable for T where T: {traits} {{}} " )?; Ok(()) }