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 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<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) {
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);

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::num::Wrapping;
@ -6,7 +6,7 @@ const MEM_SIZE: usize = 32_000;
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
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<W, R>(
@ -61,7 +61,7 @@ fn execute<W, R>(
}
Stmt::Loop(body) => {
while mem[*ptr] != Wrapping(0) {
execute(mem, ptr, body, stdout, stdin);
execute(mem, ptr, &body.stmts, stdout, stdin);
}
}
Stmt::SetNull => {

View file

@ -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<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)]
#![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);
});
}

View file

@ -1,7 +1,11 @@
use crate::parse::Instr;
use crate::parse::{Instr, Span};
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)]
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);

View file

@ -1,6 +1,35 @@
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)]
pub enum Instr<'ast> {
@ -18,23 +47,23 @@ pub struct ParseError;
pub fn parse<I>(alloc: &Bump, mut src: I) -> Result<Instrs<'_>, ParseError>
where
I: Iterator<Item = u8>,
I: Iterator<Item = (usize, u8)>,
{
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<Instrs<'ast>, ParseError>
start_idx: usize,
) -> Result<(Instrs<'ast>, Span), ParseError>
where
I: Iterator<Item = u8>,
I: Iterator<Item = (usize, u8)>,
{
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);
}
}

View file

@ -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,
},
),
],
)

View file

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

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