M Cargo.lock => Cargo.lock +113 -0
@@ 1,5 1,118 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
+version = 3
+
[[package]]
name = "brainfucc"
version = "0.1.0"
+dependencies = [
+ "toasty_lc3_primitives",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
+dependencies = [
+ "thiserror",
+ "toml",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+
+[[package]]
+name = "syn"
+version = "1.0.98"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toasty_lc3_primitives"
+version = "0.2.0"
+dependencies = [
+ "num_enum",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
M Cargo.toml => Cargo.toml +10 -2
@@ 2,11 2,19 @@
name = "brainfucc"
description = "Brainfuck compiler and interpreter"
authors = ["tmpod <tom@tmpod.dev>"]
+readme = "README.md"
+repository = "https://git.sr.ht/~tmpod/brainfucc"
keywords = ["brainfuck", "compiler", "interpreter", "lc3"]
categories = ["compilers"]
version = "0.1.0"
edition = "2021"
-readme = "README.md"
-repository = "https://git.sr.ht/~tmpod/brainfucc"
+
+[features]
+default = ["lc3"]
+lc3 = ["dep:toasty_lc3_primitives"]
+
+[dependencies]
+# toasty_lc3_primitives = { git = "https://git.sr.ht/~tmpod/toasty-lc3-vm", optional = true }
+toasty_lc3_primitives = { path = "../toast-lc3/vm/primitives/", optional = true }
M src/interpreter.rs => src/interpreter.rs +3 -4
@@ 1,7 1,6 @@
use std::io::Read;
-use crate::compiler::Word;
-use crate::compiler::{BrainfuckIR, BrainfuckInstruction};
+use crate::ir::{BrainfuckIR, BrainfuckInstruction, Word};
const DEFAULT_TAPE_SIZE: usize = 30_000;
@@ 9,7 8,7 @@ type InputHook = fn() -> Word;
type OutputHook = fn(Word) -> ();
pub fn execute(
- compiled: BrainfuckIR,
+ compiled: &BrainfuckIR,
tape_size: usize,
input_hook: InputHook,
output_hook: OutputHook,
@@ 78,7 77,7 @@ pub fn default_output_hook(out: u8) {
print!("{}", out as char);
}
-pub fn execute_default(compiled: BrainfuckIR) {
+pub fn execute_default(compiled: &BrainfuckIR) {
execute(
compiled,
DEFAULT_TAPE_SIZE,
R src/compiler.rs => src/ir.rs +2 -2
@@ 35,8 35,8 @@ impl BrainfuckIR {
for c in source.chars() {
let inst = match c {
- '<' => RGT,
- '>' => LFT,
+ '<' => LFT,
+ '>' => RGT,
'+' => INC,
'-' => DEC,
'.' => OUT,
M src/lc3.rs => src/lc3.rs +54 -29
@@ 1,31 1,56 @@
/// LC3 backend
-use toast_lc3_vm::{
- instructions::{AddMode, Instruction as LC3Instruction},
+use std::iter;
+
+use toasty_lc3_primitives::{
+ condition_codes::ConditionCode,
+ instructions::{AddMode, Instruction as LC3Instruction, TrapVector},
registers::Register,
+ types::Word,
};
-use crate::compiler::{BrainfuckIR, BrainfuckInstruction as BfInstruction};
+use crate::ir::{BrainfuckIR, BrainfuckInstruction as BfInstruction};
+#[derive(Debug)]
pub struct LC3CompiledBrainfuck {
pub instructions: Vec<LC3Instruction>,
}
impl LC3CompiledBrainfuck {
+ const ORIGIN: Word = 0x3000;
+
+ // TODO: improve this to make use of negative bound being -16 and not -15
+ fn compile_add(r: Register, n: i16) -> impl Iterator<Item = LC3Instruction> {
+ let base = if n >= 0 { 0xF } else { 0x1F };
+ let repeats = n / 0xF;
+ let rem = n % 0xF;
+ iter::repeat(LC3Instruction::ADD(r, r, AddMode::Immediate(base)))
+ .take(repeats as usize)
+ .chain(iter::once(LC3Instruction::ADD(
+ r,
+ r,
+ AddMode::Immediate(rem as u16 & 0x1F),
+ )))
+ }
+
// Curent register usage:
// R0 -> temporary register for I/O and other operations
// R1 -> data pointer
// TODO: fix expected single-byte overflow not happening because LC3 words are 2-bytes wide.
// TODO: improve naive instruction emition to make use of registers for hot paths.
- fn compile(ir: &BrainfuckIR) -> Self {
- let ir_iter = ir.instructions.iter().peekable();
- let lc3_insts = Vec::new();
- let jump_stack = Vec::new();
+ // TODO: fix ADDs due to immediate mode only bearing 5 bits
+ pub fn compile(ir: &BrainfuckIR) -> Self {
+ let mut ir_iter = ir.instructions.iter().peekable();
+ let mut lc3_insts = Vec::new();
+ let mut jump_stack = Vec::new();
+
+ let mut value_mod = 0i16;
+ let mut pos_mod = 0i16;
- let mut value_mod = 0;
- let mut pos_mod = 0;
+ // Placeholder instruction to then be replaced by R1 init to tape beginning
+ lc3_insts.push(LC3Instruction::RES);
// One pass to do small optimizations and produce LC3 instructions
- for i in ir_iter {
+ while let Some(i) = ir_iter.next() {
match i {
BfInstruction::INC | BfInstruction::DEC => {
value_mod += match i {
@@ 37,11 62,7 @@ impl LC3CompiledBrainfuck {
Some(BfInstruction::INC) | Some(BfInstruction::DEC) => continue,
_ => {
lc3_insts.push(LC3Instruction::LDR(Register::R0, Register::R1, 0));
- lc3_insts.push(LC3Instruction::ADD(
- Register::R0,
- Register::R0,
- AddMode::Immediate(value_mod),
- ));
+ lc3_insts.extend(Self::compile_add(Register::R0, value_mod));
lc3_insts.push(LC3Instruction::STR(Register::R0, Register::R1, 0));
value_mod = 0;
}
@@ 56,11 77,7 @@ impl LC3CompiledBrainfuck {
match ir_iter.peek() {
Some(BfInstruction::RGT) | Some(BfInstruction::LFT) => continue,
_ => {
- lc3_insts.push(LC3Instruction::ADD(
- Register::R1,
- Register::R1,
- AddMode::Immediate(pos_mod),
- ));
+ lc3_insts.extend(Self::compile_add(Register::R1, pos_mod));
pos_mod = 0;
}
}
@@ 73,17 90,15 @@ impl LC3CompiledBrainfuck {
}
BfInstruction::RET => {
let opening_pc = jump_stack.pop().expect("Invalid IR");
- let closing_pc = lc3_insts.len();
+ let closing_pc = lc3_insts.len() as u16;
lc3_insts.push(LC3Instruction::LDR(Register::R0, Register::R1, 0));
lc3_insts.push(LC3Instruction::BR(
- ConditionCode::NEG.into() | ConditionCode::POS.into(),
- Register::R1,
- opening_pc - closing_pc + 2, // +2 because we wanna land on the inst after the [
+ ConditionCode::NEG as u16 | ConditionCode::POS as u16,
+ closing_pc - opening_pc as u16 + 2, // +2 because we wanna land on the inst after the [
));
// and fill in the jump instruction
- let open_inst = lc3_insts[opening_pc];
- lc3_insts[opening_pc] =
- LC3Instruction::BR(ConditionCode::ZER.into(), Register::R1, closing_pc + 2);
+ lc3_insts[opening_pc + 1] =
+ LC3Instruction::BR(ConditionCode::ZER.into(), closing_pc + 2);
}
BfInstruction::INP => {
lc3_insts.push(LC3Instruction::TRAP(TrapVector::GETC));
@@ 98,12 113,22 @@ impl LC3CompiledBrainfuck {
lc3_insts.push(LC3Instruction::TRAP(TrapVector::HALT));
+ // Complete R1 init
+ lc3_insts[0] = LC3Instruction::ADD(
+ Register::R1,
+ Register::R1,
+ AddMode::Immediate(lc3_insts.len() as Word + 1),
+ );
+
LC3CompiledBrainfuck {
instructions: lc3_insts,
}
}
- fn emit(&self) -> Vec<u8> {
- self.instructions.iter().map(|i| i.emit()).collect()
+ pub fn emit(&self) -> impl Iterator<Item = u8> + '_ {
+ let inst_words = self.instructions.iter().map(|i| i.emit());
+ iter::once(Self::ORIGIN)
+ .chain(inst_words)
+ .flat_map(|w| w.to_be_bytes())
}
}
M src/main.rs => src/main.rs +24 -8
@@ 1,21 1,37 @@
use std::env;
-use std::error::Error;
use std::fs::File;
-use std::io::Read;
+use std::io::{Read, Result, Write};
mod cli;
-mod compiler;
mod interpreter;
+mod ir;
+#[cfg(feature = "lc3")]
mod lc3;
-fn main() -> Result<(), Box<dyn Error>> {
- let mut src_file = File::open(env::args().skip(1).next().expect("Missing source filename"))?;
+fn main() -> Result<()> {
+ let src_filename = env::args().skip(1).next().expect("Missing source filename");
+ let mut out_filename = src_filename.clone();
+ let mut src_file = File::open(src_filename)?;
let mut src = String::new();
src_file.read_to_string(&mut src).unwrap();
- let compiled = crate::compiler::BrainfuckIR::parse(src.as_str());
- eprintln!("Compiled: {:?}", compiled);
- crate::interpreter::execute_default(compiled);
+ let ir = crate::ir::BrainfuckIR::parse(src.as_str());
+ // eprintln!("IR: {:?}", ir);
+ // crate::interpreter::execute_default(&ir);
+ let lc3_compiled = crate::lc3::LC3CompiledBrainfuck::compile(&ir);
+ eprintln!("LC3 compiled:");
+ for i in lc3_compiled.instructions.iter() {
+ eprintln!("{:?}", i);
+ }
+
+ // Write LC3 image
+ out_filename.push_str(".lc3.obj");
+ println!("Emitting to {}", out_filename);
+ let mut out_file = File::create(out_filename)?;
+ // TODO: implement output buffering
+ for c in lc3_compiled.emit() {
+ out_file.write(&[c])?;
+ }
Ok(())
}