diff --git a/rust2/benches/opts.rs b/rust2/benches/opts.rs index 806d857..d332847 100644 --- a/rust2/benches/opts.rs +++ b/rust2/benches/opts.rs @@ -1,8 +1,6 @@ use bumpalo::Bump; -use criterion::{criterion_group, criterion_main, Criterion}; -use std::fs; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; use std::io::{Read, Write}; -use std::path::PathBuf; struct MockReadWrite; @@ -23,32 +21,27 @@ impl Write for MockReadWrite { } } -fn get_bf(path_from_bench: impl AsRef) -> String { - let file = PathBuf::from(file!()) - .parent() - .unwrap() - .join(path_from_bench.as_ref()); - - fs::read_to_string(file).unwrap() -} - fn run_bf_bench(bf: &str) { let bump = Bump::new(); - let parsed = brainfuck::parse::parse(&bump, bf.bytes()).unwrap(); + let parsed = brainfuck::parse::parse(&bump, bf.bytes().enumerate()).unwrap(); let optimized = brainfuck::opts::optimize(&bump, &parsed); brainfuck::ir_interpreter::run(&optimized, MockReadWrite, MockReadWrite); } fn optimized(c: &mut Criterion) { - let fizzbuzz = get_bf("fizzbuzz.bf"); - let hello_world = get_bf("hello.bf"); - let bench = get_bf("bench.bf"); - let loopremove = get_bf("loopremove.bf"); + let fizzbuzz = include_str!("fizzbuzz.bf"); + let hello_world = include_str!("hello.bf"); + let bench = include_str!("bench.bf"); + let loopremove = include_str!("loopremove.bf"); - c.bench_function("fizzbuzz", |b| b.iter(|| run_bf_bench(&fizzbuzz))); - c.bench_function("hello_world", |b| b.iter(|| run_bf_bench(&hello_world))); - c.bench_function("bench", |b| b.iter(|| run_bf_bench(&bench))); - c.bench_function("loopremove", |b| b.iter(|| run_bf_bench(&loopremove))); + c.bench_function("fizzbuzz", |b| b.iter(|| run_bf_bench(black_box(fizzbuzz)))); + c.bench_function("hello_world", |b| { + b.iter(|| run_bf_bench(black_box(hello_world))) + }); + c.bench_function("bench", |b| b.iter(|| run_bf_bench(black_box(bench)))); + c.bench_function("loopremove", |b| { + b.iter(|| run_bf_bench(black_box(loopremove))) + }); } criterion_group!(benches, optimized); diff --git a/rust2/src/fizzbuzz.bf b/rust2/src/fizzbuzz.bf new file mode 100644 index 0000000..cd3ce1a --- /dev/null +++ b/rust2/src/fizzbuzz.bf @@ -0,0 +1,7 @@ +++++++++++[>++++++++++<-]>>++++++++++>->>>>>>>>>>>>>>>>-->+++++++[->++\n++++++++<]>[->+>+>+>+<<<<]+++>>+++>>>++++++++[-< +++++<++++<++++>>>]++++\n+[-<++++<++++>>]>>-->++++++[->+++++++++++<]>[->+>+>+>+<<<<]+++++>>+>++\n++++>++++++>++++++++[-<+ ++++<++++<++++>>>]++++++[-<+++<+++<+++>>>]>>-->\n---+[-<+]-<[+[->+]-<<->>>+>[-]++[-->++]-->+++[---++[--<++]---->>-<+>[+\n ++++[----<++++]--[>]++[-->++]--<]>++[--+[-<+]->>[-]+++++[---->++++]-->[\n->+<]>>[.>]++[-->++]]-->+++]---+[-<+]->>-[+>>>+[ +-<+]->>>++++++++++<<[-\n>+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>\n+>>]<<<<<]>[-]>>[>++++++ +[-<++++++++>]<.<<+>+>[-]]<[<[->-<]++++++[->+++\n+++++<]>.[-]]<<++++++[-<++++++++>]<.[-]<<[-<+>]+[-<+]->>]+[-]<<<.>>>+[\n +-<+]-<<] diff --git a/rust2/src/ir_interpreter.rs b/rust2/src/ir_interpreter.rs index b8ee88e..419050a 100644 --- a/rust2/src/ir_interpreter.rs +++ b/rust2/src/ir_interpreter.rs @@ -1,4 +1,4 @@ -use crate::opts::Stmt; +use crate::opts::{Ir, Stmt}; use std::io::{Read, Write}; use std::num::Wrapping; @@ -6,7 +6,7 @@ const MEM_SIZE: usize = 32_000; type Memory = [Wrapping; MEM_SIZE]; -pub fn run(instrs: &[Stmt<'_>], mut stdout: W, mut stdin: R) +pub fn run(instrs: &Ir<'_>, mut stdout: W, mut stdin: R) where W: Write, R: Read, @@ -14,7 +14,7 @@ where let mut mem = [Wrapping(0u8); MEM_SIZE]; let mut ptr = 0; - execute(&mut mem, &mut ptr, instrs, &mut stdout, &mut stdin); + execute(&mut mem, &mut ptr, &instrs.stmts, &mut stdout, &mut stdin); } fn execute( @@ -61,7 +61,7 @@ fn execute( } Stmt::Loop(body) => { while mem[*ptr] != Wrapping(0) { - execute(mem, ptr, body, stdout, stdin); + execute(mem, ptr, &body.stmts, stdout, stdin); } } Stmt::SetNull => { diff --git a/rust2/src/lib.rs b/rust2/src/lib.rs index 4b53eec..f99e873 100644 --- a/rust2/src/lib.rs +++ b/rust2/src/lib.rs @@ -1,6 +1,45 @@ #![feature(allocator_api, let_else)] #![warn(rust_2018_idioms)] +use crate::parse::ParseError; +use bumpalo::Bump; +use std::io::{Read, Write}; + pub mod ir_interpreter; pub mod opts; pub mod parse; + +pub fn run(bytes: impl Iterator, stdout: W, stdin: R) -> Result<(), ParseError> +where + W: Write, + R: Read, +{ + let ast_alloc = Bump::new(); + + let parsed = parse::parse(&ast_alloc, bytes.enumerate())?; + + let ir_alloc = Bump::new(); + + let optimized_ir = opts::optimize(&ir_alloc, &parsed); + + drop(parsed); + drop(ast_alloc); + + ir_interpreter::run(&optimized_ir, stdout, stdin); + + Ok(()) +} + +#[cfg(test)] +mod tests { + #[test] + fn fizzbuzz() { + let str = include_str!("fizzbuzz.bf"); + let mut stdout = Vec::new(); + let stdin = []; + + super::run(str.bytes(), &mut stdout, stdin.as_slice()).unwrap(); + + insta::assert_debug_snapshot!(String::from_utf8(stdout)); + } +} diff --git a/rust2/src/main.rs b/rust2/src/main.rs index a3fae2a..2d42280 100644 --- a/rust2/src/main.rs +++ b/rust2/src/main.rs @@ -1,11 +1,8 @@ #![feature(allocator_api, let_else)] #![warn(rust_2018_idioms)] -use bumpalo::Bump; use std::{env, fs, io, process}; -use brainfuck::{ir_interpreter, opts, parse}; - fn main() { let Some(path) = env::args().nth(1) else { eprintln!("error: Provide a path as input."); @@ -17,24 +14,13 @@ fn main() { process::exit(1); }); - let ast_alloc = Bump::new(); - - let parsed = parse::parse(&ast_alloc, file.bytes()).unwrap_or_else(|_| { - eprintln!("Failed to parse brainfuck code."); - process::exit(1); - }); - - let ir_alloc = Bump::new(); - - let optimized_ir = opts::optimize(&ir_alloc, &parsed); - - drop(parsed); - drop(ast_alloc); - let stdout = io::stdout(); let stdout = stdout.lock(); let stdin = io::stdin(); let stdin = stdin.lock(); - ir_interpreter::run(&optimized_ir, stdout, stdin); + brainfuck::run(file.bytes(), stdout, stdin).unwrap_or_else(|_| { + eprintln!("error: Failed to parse brainfuck code"); + process::exit(1); + }); } diff --git a/rust2/src/opts.rs b/rust2/src/opts.rs index a41ae95..c5ab43c 100644 --- a/rust2/src/opts.rs +++ b/rust2/src/opts.rs @@ -1,7 +1,11 @@ -use crate::parse::Instr; +use crate::parse::{Instr, Span}; use bumpalo::Bump; -pub type Ir<'ir> = Vec, &'ir Bump>; +#[derive(Debug)] +pub struct Ir<'ir> { + pub stmts: Vec, &'ir Bump>, + pub spans: Vec, +} #[derive(Debug)] pub enum Stmt<'ir> { @@ -15,23 +19,27 @@ pub enum Stmt<'ir> { SetNull, } -pub fn optimize<'ir>(alloc: &'ir Bump, instrs: &[Instr<'_>]) -> Ir<'ir> { +pub fn optimize<'ir>(alloc: &'ir Bump, instrs: &[(Instr<'_>, Span)]) -> Ir<'ir> { let mut ir = ast_to_ir(alloc, instrs); pass_find_set_null(&mut ir); ir } -fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[Instr<'_>]) -> Ir<'ir> { - let mut ir = Vec::new_in(alloc); +fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[(Instr<'_>, Span)]) -> Ir<'ir> { + let mut stmts = Vec::new_in(alloc); + let mut spans = Vec::new_in(alloc); let mut instr_iter = ast.iter(); - let Some(first) = instr_iter.next() else { return ir; }; + let Some(first) = instr_iter.next() else { + return Ir { stmts, spans: Vec::new_in(alloc) }; + }; - let mut last = first; + let mut last = &first.0; + let mut last_span = first.1; let mut count = 1; - for next in instr_iter { + for (next, next_span) in instr_iter { match last { Instr::Add | Instr::Sub | Instr::Right | Instr::Left if last == next => { count += 1; @@ -47,8 +55,10 @@ fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[Instr<'_>]) -> Ir<'ir> { Instr::In => Stmt::In, Instr::Loop(body) => Stmt::Loop(ast_to_ir(alloc, body)), }; - ir.push(new_last); + stmts.push(new_last); + spans.push(last_span.until(*next_span)); last = next; + last_span = *next_span; count = 1; } } @@ -61,17 +71,18 @@ fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[Instr<'_>]) -> Ir<'ir> { Instr::Left => Stmt::Left(count.into()), Instr::Out => Stmt::Out, Instr::In => Stmt::In, - Instr::Loop(body) => Stmt::Loop(ast_to_ir(alloc, body)), + Instr::Loop(body) => Stmt::Loop(ast_to_ir(alloc, &body)), }; - ir.push(new_last); + stmts.push(new_last); + spans.push(last_span); - ir + Ir { stmts, spans } } fn pass_find_set_null(ir: &mut Ir<'_>) { - for stmt in ir { + for stmt in &mut ir.stmts { if let Stmt::Loop(body) = stmt { - if let [Stmt::Sub(_)] = body.as_slice() { + if let [Stmt::Sub(_)] = body.stmts.as_slice() { *stmt = Stmt::SetNull; } else { pass_find_set_null(body); diff --git a/rust2/src/parse.rs b/rust2/src/parse.rs index f31d2c0..2ec0a2e 100644 --- a/rust2/src/parse.rs +++ b/rust2/src/parse.rs @@ -1,6 +1,35 @@ use bumpalo::Bump; -pub type Instrs<'ast> = Vec, &'ast Bump>; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span { + pub start: u32, + pub len: u32, +} + +impl Span { + fn single(idx: usize) -> Self { + Self { + start: idx.try_into().unwrap(), + len: 1, + } + } + + fn start_end(start: usize, end: usize) -> Span { + Self { + start: start.try_into().unwrap(), + len: (end - start).try_into().unwrap(), + } + } + + pub fn until(&self, other: Self) -> Self { + Self { + start: self.start, + len: (other.start + other.len) - self.len, + } + } +} + +pub type Instrs<'ast> = Vec<(Instr<'ast>, Span), &'ast Bump>; #[derive(Debug, Clone, PartialEq, Eq)] pub enum Instr<'ast> { @@ -18,23 +47,23 @@ pub struct ParseError; pub fn parse(alloc: &Bump, mut src: I) -> Result, ParseError> where - I: Iterator, + I: Iterator, { let mut instrs = Vec::new_in(alloc); loop { match src.next() { - Some(b'+') => instrs.push(Instr::Add), - Some(b'-') => instrs.push(Instr::Sub), - Some(b'>') => instrs.push(Instr::Right), - Some(b'<') => instrs.push(Instr::Left), - Some(b'.') => instrs.push(Instr::Out), - Some(b',') => instrs.push(Instr::In), - Some(b'[') => { - let loop_instrs = parse_loop(alloc, &mut src, 0)?; - instrs.push(Instr::Loop(loop_instrs)); + Some((idx, b'+')) => instrs.push((Instr::Add, Span::single(idx))), + Some((idx, b'-')) => instrs.push((Instr::Sub, Span::single(idx))), + Some((idx, b'>')) => instrs.push((Instr::Right, Span::single(idx))), + Some((idx, b'<')) => instrs.push((Instr::Left, Span::single(idx))), + Some((idx, b'.')) => instrs.push((Instr::Out, Span::single(idx))), + Some((idx, b',')) => instrs.push((Instr::In, Span::single(idx))), + Some((idx, b'[')) => { + let (loop_instrs, span) = parse_loop(alloc, &mut src, 0, idx)?; + instrs.push((Instr::Loop(loop_instrs), span)); } - Some(b']') => return Err(ParseError), + Some((_, b']')) => return Err(ParseError), Some(_) => {} // comment None => break, } @@ -47,9 +76,10 @@ fn parse_loop<'ast, I>( alloc: &'ast Bump, src: &mut I, depth: u16, -) -> Result, ParseError> + start_idx: usize, +) -> Result<(Instrs<'ast>, Span), ParseError> where - I: Iterator, + I: Iterator, { const MAX_DEPTH: u16 = 1000; @@ -59,25 +89,25 @@ where let mut instrs = Vec::new_in(alloc); - loop { + let end_idx = loop { match src.next() { - Some(b'+') => instrs.push(Instr::Add), - Some(b'-') => instrs.push(Instr::Sub), - Some(b'>') => instrs.push(Instr::Right), - Some(b'<') => instrs.push(Instr::Left), - Some(b'.') => instrs.push(Instr::Out), - Some(b',') => instrs.push(Instr::In), - Some(b'[') => { - let loop_instrs = parse_loop(alloc, src, depth + 1)?; - instrs.push(Instr::Loop(loop_instrs)); + Some((idx, b'+')) => instrs.push((Instr::Add, Span::single(idx))), + Some((idx, b'-')) => instrs.push((Instr::Sub, Span::single(idx))), + Some((idx, b'>')) => instrs.push((Instr::Right, Span::single(idx))), + Some((idx, b'<')) => instrs.push((Instr::Left, Span::single(idx))), + Some((idx, b'.')) => instrs.push((Instr::Out, Span::single(idx))), + Some((idx, b',')) => instrs.push((Instr::In, Span::single(idx))), + Some((idx, b'[')) => { + let (loop_instrs, span) = parse_loop(alloc, src, depth + 1, idx)?; + instrs.push((Instr::Loop(loop_instrs), span)); } - Some(b']') => break, + Some((idx, b']')) => break idx, Some(_) => {} // comment None => return Err(ParseError), } - } + }; - Ok(instrs) + Ok((instrs, Span::start_end(start_idx, end_idx))) } #[cfg(test)] @@ -89,7 +119,7 @@ mod tests { let alloc = Bump::new(); let bf = ">+<++[-]."; - let instrs = super::parse(&alloc, bf.bytes()); + let instrs = super::parse(&alloc, bf.bytes().enumerate()); insta::assert_debug_snapshot!(instrs); } @@ -98,7 +128,7 @@ mod tests { let alloc = Bump::new(); let bf = "+[-[-[-]]+>>>]"; - let instrs = super::parse(&alloc, bf.bytes()); + let instrs = super::parse(&alloc, bf.bytes().enumerate()); insta::assert_debug_snapshot!(instrs); } } diff --git a/rust2/src/snapshots/brainfuck__parse__tests__nested_loop.snap b/rust2/src/snapshots/brainfuck__parse__tests__nested_loop.snap index f10f3e0..e2f734d 100644 --- a/rust2/src/snapshots/brainfuck__parse__tests__nested_loop.snap +++ b/rust2/src/snapshots/brainfuck__parse__tests__nested_loop.snap @@ -1,29 +1,95 @@ --- source: src/parse.rs -assertion_line: 102 +assertion_line: 132 expression: instrs --- Ok( [ - Add, - Loop( - [ - Sub, - Loop( - [ + ( + Add, + Span { + start: 0, + len: 1, + }, + ), + ( + Loop( + [ + ( Sub, + Span { + start: 2, + len: 1, + }, + ), + ( Loop( [ - Sub, + ( + Sub, + Span { + start: 4, + len: 1, + }, + ), + ( + Loop( + [ + ( + Sub, + Span { + start: 6, + len: 1, + }, + ), + ], + ), + Span { + start: 5, + len: 2, + }, + ), ], ), - ], - ), - Add, - Right, - Right, - Right, - ], + Span { + start: 3, + len: 5, + }, + ), + ( + Add, + Span { + start: 9, + len: 1, + }, + ), + ( + Right, + Span { + start: 10, + len: 1, + }, + ), + ( + Right, + Span { + start: 11, + len: 1, + }, + ), + ( + Right, + Span { + start: 12, + len: 1, + }, + ), + ], + ), + Span { + start: 1, + len: 12, + }, ), ], ) diff --git a/rust2/src/snapshots/brainfuck__parse__tests__simple.snap b/rust2/src/snapshots/brainfuck__parse__tests__simple.snap index 54ac4ca..4c32fe4 100644 --- a/rust2/src/snapshots/brainfuck__parse__tests__simple.snap +++ b/rust2/src/snapshots/brainfuck__parse__tests__simple.snap @@ -1,20 +1,68 @@ --- source: src/parse.rs -assertion_line: 93 +assertion_line: 123 expression: instrs --- Ok( [ - Right, - Add, - Left, - Add, - Add, - Loop( - [ - Sub, - ], + ( + Right, + Span { + start: 0, + len: 1, + }, + ), + ( + Add, + Span { + start: 1, + len: 1, + }, + ), + ( + Left, + Span { + start: 2, + len: 1, + }, + ), + ( + Add, + Span { + start: 3, + len: 1, + }, + ), + ( + Add, + Span { + start: 4, + len: 1, + }, + ), + ( + Loop( + [ + ( + Sub, + Span { + start: 6, + len: 1, + }, + ), + ], + ), + Span { + start: 5, + len: 2, + }, + ), + ( + Out, + Span { + start: 8, + len: 1, + }, ), - Out, ], ) diff --git a/rust2/src/snapshots/brainfuck__tests__fizzbuzz.snap b/rust2/src/snapshots/brainfuck__tests__fizzbuzz.snap new file mode 100644 index 0000000..6f1582c --- /dev/null +++ b/rust2/src/snapshots/brainfuck__tests__fizzbuzz.snap @@ -0,0 +1,8 @@ +--- +source: src/lib.rs +assertion_line: 43 +expression: "String::from_utf8(stdout)" +--- +Ok( + "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n16\n17\nFizz\n19\nBuzz\nFizz\n22\n23\nFizz\nBuzz\n26\nFizz\n28\n29\nFizzBuzz\n31\n32\nFizz\n34\nBuzz\nFizz\n37\n38\nFizz\nBuzz\n41\nFizz\n43\n44\nFizzBuzz\n46\n47\nFizz\n49\nBuzz\nFizz\n52\n53\nFizz\nBuzz\n56\nFizz\n58\n59\nFizzBuzz\n61\n62\nFizz\n64\nBuzz\nFizz\n67\n68\nFizz\nBuzz\n71\nFizz\n73\n74\nFizzBuzz\n76\n77\nFizz\n79\nBuzz\nFizz\n82\n83\nFizz\nBuzz\n86\nFizz\n88\n89\nFizzBuzz\n91\n92\nFizz\n94\nBuzz\nFizz\n97\n98\nFizz\nBuzz\n", +)