|
|
@ -1,7 +1,10 @@ |
|
|
|
|
|
|
|
#![allow(clippy::from_str_radix_10)] |
|
|
|
|
|
|
|
use std::cmp::Ordering; |
|
|
|
|
|
|
|
use std::collections::HashMap; |
|
|
|
use std::fs::File; |
|
|
|
use std::fs::File; |
|
|
|
use std::io::Write; |
|
|
|
use std::io::Write; |
|
|
|
|
|
|
|
|
|
|
|
use anyhow::{Context, Result}; |
|
|
|
use anyhow::Result; |
|
|
|
use string_builder::Builder; |
|
|
|
use string_builder::Builder; |
|
|
|
use structopt::StructOpt; |
|
|
|
use structopt::StructOpt; |
|
|
|
|
|
|
|
|
|
|
@ -16,6 +19,23 @@ use pest::Parser; |
|
|
|
#[grammar = "grammars/mblf.pest"] |
|
|
|
#[grammar = "grammars/mblf.pest"] |
|
|
|
struct MblfParser; |
|
|
|
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<u32>, |
|
|
|
|
|
|
|
mem_pointer: u32, |
|
|
|
|
|
|
|
variables: HashMap<String, MemCell>, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[derive(StructOpt)] |
|
|
|
#[derive(StructOpt)] |
|
|
|
struct Cli { |
|
|
|
struct Cli { |
|
|
|
#[structopt(parse(from_os_str))] |
|
|
|
#[structopt(parse(from_os_str))] |
|
|
@ -36,137 +56,187 @@ fn extract_operand(statement: Pair<Rule>) -> &str { |
|
|
|
line |
|
|
|
line |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn parse_constant(text: &str) -> Result<i32, std::num::ParseIntError> { |
|
|
|
fn parse_constant(text: &str) -> Result<u32, std::num::ParseIntError> { |
|
|
|
if text.starts_with("\"") { |
|
|
|
if text.starts_with('\"') { |
|
|
|
let c = text.chars().nth(1).unwrap(); |
|
|
|
let c = text.chars().nth(1).unwrap(); |
|
|
|
Ok(c as i32) |
|
|
|
Ok(c as u32) |
|
|
|
} else if text.starts_with("0x") { |
|
|
|
} else if text.starts_with("0x") { |
|
|
|
let without_prefix = text.trim_start_matches("0x"); |
|
|
|
let without_prefix = text.trim_start_matches("0x"); |
|
|
|
i32::from_str_radix(without_prefix, 16) |
|
|
|
u32::from_str_radix(without_prefix, 16) |
|
|
|
} else { |
|
|
|
} else { |
|
|
|
i32::from_str_radix(text, 10) |
|
|
|
u32::from_str_radix(text, 10) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn instruct(statement: Pair<Rule>, out: &mut Builder) { |
|
|
|
fn to_bf(rule: Rule, operand: &str, state: &mut State, out: &mut Builder) { |
|
|
|
match statement.as_rule() { |
|
|
|
match rule { |
|
|
|
Rule::include => { |
|
|
|
|
|
|
|
let file_path = extract_operand(statement); |
|
|
|
|
|
|
|
println!("Including {} into this src file", file_path); |
|
|
|
|
|
|
|
out.append("#include\n"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Rule::var => { |
|
|
|
Rule::var => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Creation of variable '{}'", variable_name); |
|
|
|
|
|
|
|
out.append("var\n"); |
|
|
|
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 => { |
|
|
|
Rule::delvar => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Deletion of variable '{}'", variable_name); |
|
|
|
let memcell = state |
|
|
|
out.append("delvar\n"); |
|
|
|
.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 => { |
|
|
|
Rule::point => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Pointing to variable '{}'", variable_name); |
|
|
|
let address = state |
|
|
|
out.append("point\n"); |
|
|
|
.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 => { |
|
|
|
Rule::pointm => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Pointing back to marker variable {}", variable_name); |
|
|
|
let address = state |
|
|
|
out.append("pointm\n"); |
|
|
|
.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 => { |
|
|
|
Rule::add => { |
|
|
|
let constant = extract_operand(statement); |
|
|
|
let constant = operand; |
|
|
|
let constant_parsed = parse_constant(constant).unwrap(); |
|
|
|
let constant_parsed = parse_constant(constant).unwrap(); |
|
|
|
println!( |
|
|
|
out.append("+".repeat(constant_parsed as usize)); |
|
|
|
"Addition of '{}', decimal value is {}", |
|
|
|
|
|
|
|
constant, constant_parsed |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
out.append("add\n"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Rule::addb => { |
|
|
|
|
|
|
|
let constant = extract_operand(statement); |
|
|
|
|
|
|
|
let constant_parsed = parse_constant(constant).unwrap(); |
|
|
|
|
|
|
|
println!( |
|
|
|
|
|
|
|
"Big Addition of '{}', decimal value is {}", |
|
|
|
|
|
|
|
constant, constant_parsed |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
out.append("addb\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::addv => { |
|
|
|
Rule::addv => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Addition to variable '{}'", variable_name); |
|
|
|
let source_address = state.mem_pointer.to_string(); |
|
|
|
out.append("addv\n"); |
|
|
|
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 => { |
|
|
|
Rule::sub => { |
|
|
|
let constant = extract_operand(statement); |
|
|
|
let constant = operand; |
|
|
|
let constant_parsed = parse_constant(constant).unwrap(); |
|
|
|
let constant_parsed = parse_constant(constant).unwrap(); |
|
|
|
println!( |
|
|
|
out.append("-".repeat(constant_parsed as usize)); |
|
|
|
"Subtraction of '{}', decimal value is {}", |
|
|
|
|
|
|
|
constant, constant_parsed |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
out.append("sub\n"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Rule::subb => { |
|
|
|
|
|
|
|
let constant = extract_operand(statement); |
|
|
|
|
|
|
|
let constant_parsed = parse_constant(constant).unwrap(); |
|
|
|
|
|
|
|
println!( |
|
|
|
|
|
|
|
"Big Subtraction of '{}', decimal value is {}", |
|
|
|
|
|
|
|
constant, constant_parsed |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
out.append("subb\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::subv => { |
|
|
|
Rule::subv => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Subtraction from variable '{}'", variable_name); |
|
|
|
let source_address = state.mem_pointer.to_string(); |
|
|
|
out.append("subv\n"); |
|
|
|
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 => { |
|
|
|
Rule::copy => { |
|
|
|
let variable_name = extract_operand(statement); |
|
|
|
let variable_name = operand; |
|
|
|
println!("Copy to variable '{}'", variable_name); |
|
|
|
let source_address = state.mem_pointer.to_string(); |
|
|
|
out.append("copy\n"); |
|
|
|
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 => { |
|
|
|
Rule::setz => { |
|
|
|
println!("Set current variable to zero"); |
|
|
|
out.append("[-]"); |
|
|
|
out.append("setz\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::getchr => { |
|
|
|
Rule::getchr => { |
|
|
|
println!("Reading char from user input into current variable"); |
|
|
|
out.append(","); |
|
|
|
out.append("getchr\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::print => { |
|
|
|
Rule::print => { |
|
|
|
println!("Printing current variable"); |
|
|
|
out.append("."); |
|
|
|
out.append("print\n"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Rule::instruction => { |
|
|
|
|
|
|
|
out.append("\n"); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Rule::loopBlock => { |
|
|
|
|
|
|
|
for nested_statement in statement.into_inner() { |
|
|
|
|
|
|
|
instruct(nested_statement, out); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::loopBlockStart => { |
|
|
|
Rule::loopBlockStart => { |
|
|
|
println!("Start of loopBlock"); |
|
|
|
out.append("["); |
|
|
|
out.append("loopBlockStart\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::loopBlockEnd => { |
|
|
|
Rule::loopBlockEnd => { |
|
|
|
println!("End of loopBlock"); |
|
|
|
out.append("]"); |
|
|
|
out.append("loopBlockEnd\n"); |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
Rule::EOI => { |
|
|
|
Rule::EOI => { |
|
|
|
println!("End of Input"); |
|
|
|
|
|
|
|
out.append("\n"); |
|
|
|
out.append("\n"); |
|
|
|
} |
|
|
|
} |
|
|
|
_ => unreachable!(), |
|
|
|
_ => unreachable!(), |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn instruct(statement: Pair<Rule>, 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<dyn std::error::Error>> { |
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> { |
|
|
|
let args = Cli::from_args(); |
|
|
|
let args = Cli::from_args(); |
|
|
|
|
|
|
|
|
|
|
|
let content = std::fs::read_to_string(&args.input_file) |
|
|
|
let content = std::fs::read_to_string(&args.input_file)?; |
|
|
|
.with_context(|| format!("could not read source file {:?}", args.input_file))?; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut builder = Builder::default(); |
|
|
|
let mut builder = Builder::default(); |
|
|
|
|
|
|
|
|
|
|
@ -175,15 +245,71 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
|
|
|
.next() |
|
|
|
.next() |
|
|
|
.unwrap(); |
|
|
|
.unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
for statement in parsed_file.into_inner() { |
|
|
|
let mut state = State { |
|
|
|
instruct(statement, &mut builder); |
|
|
|
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 bf = builder.string().unwrap(); |
|
|
|
|
|
|
|
|
|
|
|
let mut out = File::create(args.output_file)?; |
|
|
|
let mut out = File::create(args.output_file)?; |
|
|
|
out.write(bf.as_bytes())?; |
|
|
|
out.write_all(bf.as_bytes())?; |
|
|
|
out.sync_all()?; |
|
|
|
out.sync_all()?; |
|
|
|
|
|
|
|
|
|
|
|
Ok(()) |
|
|
|
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" |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|