#![allow(clippy::from_str_radix_10)] use std::cmp::Ordering; use std::collections::HashMap; use std::fs::File; use std::io::Write; use anyhow::Result; use string_builder::Builder; use structopt::StructOpt; extern crate pest; #[macro_use] extern crate pest_derive; use pest::iterators::Pair; use pest::Parser; #[derive(Parser)] #[grammar = "grammars/mblf.pest"] struct MblfParser; struct MemCell { address: u32, } impl MemCell { pub fn allocate_to(address: u32) -> Self { Self { address } } } struct State { alloc_cnt: u32, free_list: Vec, mem_pointer: u32, variables: HashMap, } #[derive(StructOpt)] struct Cli { #[structopt(parse(from_os_str))] input_file: std::path::PathBuf, #[structopt(parse(from_os_str))] output_file: std::path::PathBuf, } fn extract_operand(statement: Pair) -> &str { let mut line = statement.as_str(); while let Some(c) = line.chars().next() { line = &line[c.len_utf8()..]; if c == ' ' { break; } } line } fn parse_constant(text: &str) -> Result { if text.starts_with('\"') { let c = text.chars().nth(1).unwrap(); Ok(c as u32) } else if text.starts_with("0x") { let without_prefix = text.trim_start_matches("0x"); u32::from_str_radix(without_prefix, 16) } else { u32::from_str_radix(text, 10) } } fn to_bf(rule: Rule, operand: &str, state: &mut State, out: &mut Builder) { match rule { Rule::var => { let variable_name = operand; let mem_extension: u32; let new_address: u32; if !state.free_list.is_empty() { new_address = state.free_list.pop().unwrap(); mem_extension = 0; } else { new_address = state.alloc_cnt; mem_extension = 1; } if let Some(_v) = state.variables.insert( String::from(variable_name), MemCell::allocate_to(new_address), ) { panic!("Variable '{}' already exists", variable_name); } state.alloc_cnt += mem_extension; } Rule::delvar => { let variable_name = operand; let memcell = state .variables .get(variable_name) .ok_or_else(|| format!("Variable '{}' did not exists", variable_name)) .unwrap(); state.free_list.push(memcell.address); state.variables.remove(&String::from(variable_name)); } Rule::point => { let variable_name = operand; let address = state .variables .get(variable_name) .unwrap_or_else(|| panic!("Variable '{}' did not exists", variable_name)) .address .to_string(); to_bf(Rule::pointa, &address, state, out) } Rule::pointa => { let address = operand; let address_parsed = parse_constant(address).unwrap(); match address_parsed.cmp(&state.mem_pointer) { Ordering::Less => { out.append("<".repeat((state.mem_pointer - address_parsed) as usize)) } Ordering::Greater => { out.append(">".repeat((address_parsed - state.mem_pointer) as usize)) } Ordering::Equal => (), } state.mem_pointer = address_parsed; } Rule::pointm => { let variable_name = operand; let address = state .variables .get(variable_name) .unwrap_or_else(|| panic!("Marker variable '{}' did not exists", variable_name)) .address; // thank you mixtela out.append("<+[-<+]-"); state.mem_pointer = address; } Rule::add => { let constant = operand; let constant_parsed = parse_constant(constant).unwrap(); out.append("+".repeat(constant_parsed as usize)); } Rule::addv => { let variable_name = operand; let source_address = state.mem_pointer.to_string(); out.append("["); to_bf(Rule::sub, "1", state, out); to_bf(Rule::point, variable_name, state, out); to_bf(Rule::add, "1", state, out); to_bf(Rule::pointa, &source_address, state, out); out.append("]"); } Rule::sub => { let constant = operand; let constant_parsed = parse_constant(constant).unwrap(); out.append("-".repeat(constant_parsed as usize)); } Rule::subv => { let variable_name = operand; let source_address = state.mem_pointer.to_string(); out.append("["); to_bf(Rule::sub, "1", state, out); to_bf(Rule::point, variable_name, state, out); to_bf(Rule::sub, "1", state, out); to_bf(Rule::pointa, &source_address, state, out); out.append("]"); } Rule::copy => { let variable_name = operand; let source_address = state.mem_pointer.to_string(); to_bf(Rule::var, "__temp", state, out); out.append("["); to_bf(Rule::sub, "1", state, out); to_bf(Rule::point, variable_name, state, out); to_bf(Rule::add, "1", state, out); to_bf(Rule::point, "__temp", state, out); to_bf(Rule::add, "1", state, out); to_bf(Rule::pointa, &source_address, state, out); out.append("]"); to_bf(Rule::point, "__temp", state, out); out.append("["); to_bf(Rule::sub, "1", state, out); to_bf(Rule::pointa, &source_address, state, out); to_bf(Rule::add, "1", state, out); to_bf(Rule::point, "__temp", state, out); out.append("]"); to_bf(Rule::delvar, "__temp", state, out); } Rule::setz => { out.append("[-]"); } Rule::getchr => { out.append(","); } Rule::print => { out.append("."); } Rule::loopBlockStart => { out.append("["); } Rule::loopBlockEnd => { out.append("]"); } Rule::EOI => { out.append("\n"); } _ => unreachable!(), } } fn instruct(statement: Pair, state: &mut State, out: &mut Builder) { match statement.as_rule() { Rule::include => { let file_path_raw = extract_operand(statement); let file_path = &file_path_raw[1..file_path_raw.len() - 1]; let content = std::fs::read_to_string(&file_path).unwrap(); let parsed_file = MblfParser::parse(Rule::file, &content) .expect("Parse Error") .next() .unwrap(); for statement in parsed_file.into_inner() { instruct(statement, state, out); } } Rule::loopBlock => { for nested_statement in statement.into_inner() { instruct(nested_statement, state, out); } } _ => to_bf(statement.as_rule(), extract_operand(statement), state, out), } } fn main() -> Result<(), Box> { let args = Cli::from_args(); let content = std::fs::read_to_string(&args.input_file)?; let mut builder = Builder::default(); let parsed_file = MblfParser::parse(Rule::file, &content) .expect("Parse Error") .next() .unwrap(); let mut state = State { alloc_cnt: 0, free_list: Vec::new(), mem_pointer: 0, variables: HashMap::new(), }; for stmt in parsed_file.into_inner() { instruct(stmt, &mut state, &mut builder); } let bf = builder.string().unwrap(); let mut out = File::create(args.output_file)?; out.write_all(bf.as_bytes())?; out.sync_all()?; Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parse_constant() { assert_eq!(parse_constant(&"42"), Ok(42)); assert_eq!(parse_constant(&"0x2A"), Ok(42)); assert_eq!(parse_constant(&"\"*\""), Ok(42)); assert!( matches!( parse_constant(&"\'*\'"), Err(std::num::ParseIntError { .. }) ), "Char literals are declared with \"_\", not \'_\'" ); } #[test] fn test_reuse_of_free_memcells() { let mut builder = Builder::default(); let mut state = State { alloc_cnt: 0, free_list: Vec::new(), mem_pointer: 0, variables: HashMap::new(), }; to_bf(Rule::var, "abc", &mut state, &mut builder); assert_eq!(state.alloc_cnt, 1); to_bf(Rule::delvar, "abc", &mut state, &mut builder); assert_eq!( state.alloc_cnt, 1, "allocation should not be shrunken, that is too costly" ); to_bf(Rule::var, "def", &mut state, &mut builder); assert_ne!( state.alloc_cnt, 2, "the just de-allocated memorycell should be re-used" ); assert_eq!( state.alloc_cnt, 1, "alloc, dealloc, alloc has a max usage of one memorycell" ); } }