use anyhow::{bail, Result}; use indexmap::IndexSet; use itertools::Itertools; use std::collections::HashMap; use crate::parser; pub mod instruction; pub mod templates; pub use instruction::Instruction; pub trait Compilable { fn compile(&self, data: &T) -> Result; } #[derive(Debug)] pub struct Word { pub id: usize, pub instructions: Vec, pub times_used: usize, } #[derive(Debug, PartialEq, Eq, Hash)] pub struct Condition { pub if_instructions: Vec, pub else_instructions: Vec, } impl Condition { pub fn only_if(&self) -> bool { self.else_instructions.is_empty() } } #[derive(Debug)] pub enum CallableId { Word(String), Condition(usize), } #[derive(Debug)] pub struct Compiler { pub strings: IndexSet, pub words: HashMap, pub conditions: Vec, } impl Compiler { pub fn default() -> Self { Self { strings: IndexSet::new(), words: HashMap::new(), conditions: vec![], } } pub fn generate_instructions( &mut self, body: parser::ast::Body, optimize: bool, ) -> Result> { let mut instructions: Vec = vec![]; let mut iter = body.into_iter().peekable(); while let Some(node) = iter.next() { match node { _ if optimize && iter.next_if_eq(&node).is_some() => { let count = iter.by_ref().peeking_take_while(|n| *n == node).count() + 2; instructions.push(Instruction::Multiple { instruction: Box::new( self.generate_instructions(vec![node], optimize)? .into_iter() .next() .unwrap(), ), count, }); } parser::ast::Node::Comment(_) => {} parser::ast::Node::String { mode, string } => { instructions.push(match mode.as_str() { "." => { let id = self.strings.insert_full(string).0; Instruction::StringPrint(id) } "r" => { let id = self.strings.insert_full(string).0; Instruction::StringReference(id) } "asm" => Instruction::AsmQuote(string), _ => bail!("Unknown string mode: {}", mode), }); } parser::ast::Node::Number(x) => { instructions.push(instruction::Instruction::Push(x)); } parser::ast::Node::WordDefinition { name, stack: _, body, } => { if Instruction::from_word(&name).is_some() { bail!("Word already exists as compiler instruction: {}", name); } else if self.words.contains_key(&name) { bail!("Word already exists as user word definition: {}", name); } let instructions = self.generate_instructions(body, optimize)?; self.words.insert( name.to_string(), Word { id: self.words.len(), instructions, times_used: 0, }, ); } parser::ast::Node::Condition { if_body, else_body } => { let if_instructions = self.generate_instructions(if_body, optimize)?; let else_instructions = self.generate_instructions(else_body, optimize)?; let id = self.conditions.len(); self.conditions.push(Condition { if_instructions: if_instructions.clone(), else_instructions: else_instructions.clone(), }); instructions.push(Instruction::Condition(id)); } parser::ast::Node::Word(x) => { if let Some(ins) = Instruction::from_word(&x) { instructions.push(ins); } else if let Some(w) = self.words.get_mut(&x) { w.times_used += 1; instructions.push(Instruction::Call(x)); } else { bail!("Word does not exist: {}", x); } } } } Ok(instructions) } pub fn embed(&self, body: hence::parser::ast::Body) -> Result { let strings = self .strings .iter() .enumerate() .map(|(id, s)| templates::IdLike { id, data: s.to_owned(), }) .collect(); let conditions = self .conditions .iter() .enumerate() .map(|(id, c)| { Ok(templates::IdLike { id, data: ( c.if_instructions .iter() .map(|ins| ins.compile(self)) .collect::>>()? .into_iter() .flatten() .collect(), c.else_instructions .iter() .map(|ins| ins.compile(self)) .collect::>>()? .into_iter() .flatten() .collect(), ), }) }) .collect::>>()?; let words = self .words .iter() .filter(|(_, w)| w.times_used > 1) .sorted_by(|a, b| Ord::cmp(&a.1.id, &b.1.id)) .map(|(name, w)| { Ok(templates::IdLike { id: w.id, data: ( name.to_owned(), w.instructions .iter() .map(|ins| ins.compile(self)) .collect::>>()? .into_iter() .flatten() .collect(), ), }) }) .collect::>>()?; Ok(templates::DefaultTemplate { strings, conditions, words, main: body, } .to_string()) } } pub fn compile(ast: parser::ast::AST, optimize: bool) -> Result { let mut compiler = Compiler::default(); let instructions = compiler.generate_instructions(ast.body, optimize)?; Ok(compiler.embed( instructions .iter() .map(|ins| ins.compile(&compiler)) .collect::>>()? .into_iter() .flatten() .collect(), )?) }