spans and tests

This commit is contained in:
nora 2022-04-12 21:49:59 +02:00
parent e82b14b09a
commit 5634330287
10 changed files with 300 additions and 112 deletions

View file

@ -1,8 +1,6 @@
use bumpalo::Bump; use bumpalo::Bump;
use criterion::{criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::fs;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::PathBuf;
struct MockReadWrite; struct MockReadWrite;
@ -23,32 +21,27 @@ impl Write for MockReadWrite {
} }
} }
fn get_bf(path_from_bench: impl AsRef<str>) -> 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) { fn run_bf_bench(bf: &str) {
let bump = Bump::new(); 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); let optimized = brainfuck::opts::optimize(&bump, &parsed);
brainfuck::ir_interpreter::run(&optimized, MockReadWrite, MockReadWrite); brainfuck::ir_interpreter::run(&optimized, MockReadWrite, MockReadWrite);
} }
fn optimized(c: &mut Criterion) { fn optimized(c: &mut Criterion) {
let fizzbuzz = get_bf("fizzbuzz.bf"); let fizzbuzz = include_str!("fizzbuzz.bf");
let hello_world = get_bf("hello.bf"); let hello_world = include_str!("hello.bf");
let bench = get_bf("bench.bf"); let bench = include_str!("bench.bf");
let loopremove = get_bf("loopremove.bf"); let loopremove = include_str!("loopremove.bf");
c.bench_function("fizzbuzz", |b| b.iter(|| run_bf_bench(&fizzbuzz))); c.bench_function("fizzbuzz", |b| b.iter(|| run_bf_bench(black_box(fizzbuzz))));
c.bench_function("hello_world", |b| b.iter(|| run_bf_bench(&hello_world))); c.bench_function("hello_world", |b| {
c.bench_function("bench", |b| b.iter(|| run_bf_bench(&bench))); b.iter(|| run_bf_bench(black_box(hello_world)))
c.bench_function("loopremove", |b| b.iter(|| run_bf_bench(&loopremove))); });
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); criterion_group!(benches, optimized);

7
rust2/src/fizzbuzz.bf Normal file
View file

@ -0,0 +1,7 @@
++++++++++[>++++++++++<-]>>++++++++++>->>>>>>>>>>>>>>>>-->+++++++[->++\n++++++++<]>[->+>+>+>+<<<<]+++>>+++>>>++++++++[-<
++++<++++<++++>>>]++++\n+[-<++++<++++>>]>>-->++++++[->+++++++++++<]>[->+>+>+>+<<<<]+++++>>+>++\n++++>++++++>++++++++[-<+
+++<++++<++++>>>]++++++[-<+++<+++<+++>>>]>>-->\n---+[-<+]-<[+[->+]-<<->>>+>[-]++[-->++]-->+++[---++[--<++]---->>-<+>[+\n
+++[----<++++]--[>]++[-->++]--<]>++[--+[-<+]->>[-]+++++[---->++++]-->[\n->+<]>>[.>]++[-->++]]-->+++]---+[-<+]->>-[+>>>+[
-<+]->>>++++++++++<<[-\n>+>-[>+>>]>[+[-<+>]>+>>]<<<<<<]>>[-]>>>++++++++++<[->-[>+>>]>[+[-<+>]>\n+>>]<<<<<]>[-]>>[>++++++
[-<++++++++>]<.<<+>+>[-]]<[<[->-<]++++++[->+++\n+++++<]>.[-]]<<++++++[-<++++++++>]<.[-]<<[-<+>]+[-<+]->>]+[-]<<<.>>>+[\n
-<+]-<<]

View file

@ -1,4 +1,4 @@
use crate::opts::Stmt; use crate::opts::{Ir, Stmt};
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::num::Wrapping; use std::num::Wrapping;
@ -6,7 +6,7 @@ const MEM_SIZE: usize = 32_000;
type Memory = [Wrapping<u8>; MEM_SIZE]; type Memory = [Wrapping<u8>; MEM_SIZE];
pub fn run<W, R>(instrs: &[Stmt<'_>], mut stdout: W, mut stdin: R) pub fn run<W, R>(instrs: &Ir<'_>, mut stdout: W, mut stdin: R)
where where
W: Write, W: Write,
R: Read, R: Read,
@ -14,7 +14,7 @@ where
let mut mem = [Wrapping(0u8); MEM_SIZE]; let mut mem = [Wrapping(0u8); MEM_SIZE];
let mut ptr = 0; 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<W, R>( fn execute<W, R>(
@ -61,7 +61,7 @@ fn execute<W, R>(
} }
Stmt::Loop(body) => { Stmt::Loop(body) => {
while mem[*ptr] != Wrapping(0) { while mem[*ptr] != Wrapping(0) {
execute(mem, ptr, body, stdout, stdin); execute(mem, ptr, &body.stmts, stdout, stdin);
} }
} }
Stmt::SetNull => { Stmt::SetNull => {

View file

@ -1,6 +1,45 @@
#![feature(allocator_api, let_else)] #![feature(allocator_api, let_else)]
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
use crate::parse::ParseError;
use bumpalo::Bump;
use std::io::{Read, Write};
pub mod ir_interpreter; pub mod ir_interpreter;
pub mod opts; pub mod opts;
pub mod parse; pub mod parse;
pub fn run<R, W>(bytes: impl Iterator<Item = u8>, 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));
}
}

View file

@ -1,11 +1,8 @@
#![feature(allocator_api, let_else)] #![feature(allocator_api, let_else)]
#![warn(rust_2018_idioms)] #![warn(rust_2018_idioms)]
use bumpalo::Bump;
use std::{env, fs, io, process}; use std::{env, fs, io, process};
use brainfuck::{ir_interpreter, opts, parse};
fn main() { fn main() {
let Some(path) = env::args().nth(1) else { let Some(path) = env::args().nth(1) else {
eprintln!("error: Provide a path as input."); eprintln!("error: Provide a path as input.");
@ -17,24 +14,13 @@ fn main() {
process::exit(1); 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 = io::stdout();
let stdout = stdout.lock(); let stdout = stdout.lock();
let stdin = io::stdin(); let stdin = io::stdin();
let stdin = stdin.lock(); 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);
});
} }

View file

@ -1,7 +1,11 @@
use crate::parse::Instr; use crate::parse::{Instr, Span};
use bumpalo::Bump; use bumpalo::Bump;
pub type Ir<'ir> = Vec<Stmt<'ir>, &'ir Bump>; #[derive(Debug)]
pub struct Ir<'ir> {
pub stmts: Vec<Stmt<'ir>, &'ir Bump>,
pub spans: Vec<Span, &'ir Bump>,
}
#[derive(Debug)] #[derive(Debug)]
pub enum Stmt<'ir> { pub enum Stmt<'ir> {
@ -15,23 +19,27 @@ pub enum Stmt<'ir> {
SetNull, 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); let mut ir = ast_to_ir(alloc, instrs);
pass_find_set_null(&mut ir); pass_find_set_null(&mut ir);
ir ir
} }
fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[Instr<'_>]) -> Ir<'ir> { fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[(Instr<'_>, Span)]) -> Ir<'ir> {
let mut ir = Vec::new_in(alloc); let mut stmts = Vec::new_in(alloc);
let mut spans = Vec::new_in(alloc);
let mut instr_iter = ast.iter(); 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; let mut count = 1;
for next in instr_iter { for (next, next_span) in instr_iter {
match last { match last {
Instr::Add | Instr::Sub | Instr::Right | Instr::Left if last == next => { Instr::Add | Instr::Sub | Instr::Right | Instr::Left if last == next => {
count += 1; count += 1;
@ -47,8 +55,10 @@ fn ast_to_ir<'ir>(alloc: &'ir Bump, ast: &[Instr<'_>]) -> Ir<'ir> {
Instr::In => Stmt::In, 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.until(*next_span));
last = next; last = next;
last_span = *next_span;
count = 1; 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::Left => Stmt::Left(count.into()),
Instr::Out => Stmt::Out, Instr::Out => Stmt::Out,
Instr::In => Stmt::In, 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<'_>) { 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::Loop(body) = stmt {
if let [Stmt::Sub(_)] = body.as_slice() { if let [Stmt::Sub(_)] = body.stmts.as_slice() {
*stmt = Stmt::SetNull; *stmt = Stmt::SetNull;
} else { } else {
pass_find_set_null(body); pass_find_set_null(body);

View file

@ -1,6 +1,35 @@
use bumpalo::Bump; use bumpalo::Bump;
pub type Instrs<'ast> = Vec<Instr<'ast>, &'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)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Instr<'ast> { pub enum Instr<'ast> {
@ -18,23 +47,23 @@ pub struct ParseError;
pub fn parse<I>(alloc: &Bump, mut src: I) -> Result<Instrs<'_>, ParseError> pub fn parse<I>(alloc: &Bump, mut src: I) -> Result<Instrs<'_>, ParseError>
where where
I: Iterator<Item = u8>, I: Iterator<Item = (usize, u8)>,
{ {
let mut instrs = Vec::new_in(alloc); let mut instrs = Vec::new_in(alloc);
loop { loop {
match src.next() { match src.next() {
Some(b'+') => instrs.push(Instr::Add), Some((idx, b'+')) => instrs.push((Instr::Add, Span::single(idx))),
Some(b'-') => instrs.push(Instr::Sub), Some((idx, b'-')) => instrs.push((Instr::Sub, Span::single(idx))),
Some(b'>') => instrs.push(Instr::Right), Some((idx, b'>')) => instrs.push((Instr::Right, Span::single(idx))),
Some(b'<') => instrs.push(Instr::Left), Some((idx, b'<')) => instrs.push((Instr::Left, Span::single(idx))),
Some(b'.') => instrs.push(Instr::Out), Some((idx, b'.')) => instrs.push((Instr::Out, Span::single(idx))),
Some(b',') => instrs.push(Instr::In), Some((idx, b',')) => instrs.push((Instr::In, Span::single(idx))),
Some(b'[') => { Some((idx, b'[')) => {
let loop_instrs = parse_loop(alloc, &mut src, 0)?; let (loop_instrs, span) = parse_loop(alloc, &mut src, 0, idx)?;
instrs.push(Instr::Loop(loop_instrs)); instrs.push((Instr::Loop(loop_instrs), span));
} }
Some(b']') => return Err(ParseError), Some((_, b']')) => return Err(ParseError),
Some(_) => {} // comment Some(_) => {} // comment
None => break, None => break,
} }
@ -47,9 +76,10 @@ fn parse_loop<'ast, I>(
alloc: &'ast Bump, alloc: &'ast Bump,
src: &mut I, src: &mut I,
depth: u16, depth: u16,
) -> Result<Instrs<'ast>, ParseError> start_idx: usize,
) -> Result<(Instrs<'ast>, Span), ParseError>
where where
I: Iterator<Item = u8>, I: Iterator<Item = (usize, u8)>,
{ {
const MAX_DEPTH: u16 = 1000; const MAX_DEPTH: u16 = 1000;
@ -59,25 +89,25 @@ where
let mut instrs = Vec::new_in(alloc); let mut instrs = Vec::new_in(alloc);
loop { let end_idx = loop {
match src.next() { match src.next() {
Some(b'+') => instrs.push(Instr::Add), Some((idx, b'+')) => instrs.push((Instr::Add, Span::single(idx))),
Some(b'-') => instrs.push(Instr::Sub), Some((idx, b'-')) => instrs.push((Instr::Sub, Span::single(idx))),
Some(b'>') => instrs.push(Instr::Right), Some((idx, b'>')) => instrs.push((Instr::Right, Span::single(idx))),
Some(b'<') => instrs.push(Instr::Left), Some((idx, b'<')) => instrs.push((Instr::Left, Span::single(idx))),
Some(b'.') => instrs.push(Instr::Out), Some((idx, b'.')) => instrs.push((Instr::Out, Span::single(idx))),
Some(b',') => instrs.push(Instr::In), Some((idx, b',')) => instrs.push((Instr::In, Span::single(idx))),
Some(b'[') => { Some((idx, b'[')) => {
let loop_instrs = parse_loop(alloc, src, depth + 1)?; let (loop_instrs, span) = parse_loop(alloc, src, depth + 1, idx)?;
instrs.push(Instr::Loop(loop_instrs)); instrs.push((Instr::Loop(loop_instrs), span));
} }
Some(b']') => break, Some((idx, b']')) => break idx,
Some(_) => {} // comment Some(_) => {} // comment
None => return Err(ParseError), None => return Err(ParseError),
} }
} };
Ok(instrs) Ok((instrs, Span::start_end(start_idx, end_idx)))
} }
#[cfg(test)] #[cfg(test)]
@ -89,7 +119,7 @@ mod tests {
let alloc = Bump::new(); let alloc = Bump::new();
let bf = ">+<++[-]."; let bf = ">+<++[-].";
let instrs = super::parse(&alloc, bf.bytes()); let instrs = super::parse(&alloc, bf.bytes().enumerate());
insta::assert_debug_snapshot!(instrs); insta::assert_debug_snapshot!(instrs);
} }
@ -98,7 +128,7 @@ mod tests {
let alloc = Bump::new(); let alloc = Bump::new();
let bf = "+[-[-[-]]+>>>]"; let bf = "+[-[-[-]]+>>>]";
let instrs = super::parse(&alloc, bf.bytes()); let instrs = super::parse(&alloc, bf.bytes().enumerate());
insta::assert_debug_snapshot!(instrs); insta::assert_debug_snapshot!(instrs);
} }
} }

View file

@ -1,29 +1,95 @@
--- ---
source: src/parse.rs source: src/parse.rs
assertion_line: 102 assertion_line: 132
expression: instrs expression: instrs
--- ---
Ok( Ok(
[ [
Add, (
Loop( Add,
[ Span {
Sub, start: 0,
Loop( len: 1,
[ },
),
(
Loop(
[
(
Sub, Sub,
Span {
start: 2,
len: 1,
},
),
(
Loop( Loop(
[ [
Sub, (
Sub,
Span {
start: 4,
len: 1,
},
),
(
Loop(
[
(
Sub,
Span {
start: 6,
len: 1,
},
),
],
),
Span {
start: 5,
len: 2,
},
),
], ],
), ),
], Span {
), start: 3,
Add, len: 5,
Right, },
Right, ),
Right, (
], 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,
},
), ),
], ],
) )

View file

@ -1,20 +1,68 @@
--- ---
source: src/parse.rs source: src/parse.rs
assertion_line: 93 assertion_line: 123
expression: instrs expression: instrs
--- ---
Ok( Ok(
[ [
Right, (
Add, Right,
Left, Span {
Add, start: 0,
Add, len: 1,
Loop( },
[ ),
Sub, (
], 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,
], ],
) )

View file

@ -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",
)