Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 44 additions & 15 deletions cranefack/src/backends/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,40 @@ use std::ops::Range;

use crate::errors::RuntimeError;
use crate::ir::ops::{LoopDecrement, Op, OpType};
use crate::limiters::{Limiter, LimiterResult, Unlimited};
use crate::parser::Program;

const MAX_HEAP_SIZE: usize = 16 * 1024 * 1024;

/// Interpreter to execute a program
pub struct Interpreter<R: Read, W: Write> {
pub struct Interpreter<R: Read, W: Write, L: Limiter> {
limiter: L,
max_heap_size: usize,
pub(crate) heap: Vec<u8>,
pointer: usize,
input: R,
output: W,
}

impl<R: Read, W: Write> Interpreter<R, W> {
impl<R: Read, W: Write> Interpreter<R, W, Unlimited> {
/// Create a default interpreter
pub fn new(input: R, output: W) -> Interpreter<R, W> {
pub fn new(input: R, output: W) -> Interpreter<R, W, Unlimited> {
Interpreter {
limiter: Unlimited,
max_heap_size: MAX_HEAP_SIZE,
heap: vec![0; 1024],
pointer: 0,
input,
output,
}
}
}

impl<R: Read, W: Write, L: Limiter> Interpreter<R, W, L> {
/// Create an interpreter with specific limiter
pub fn with_limiter(input: R, output: W, limiter: L) -> Interpreter<R, W, L> {
Interpreter {
limiter,
max_heap_size: MAX_HEAP_SIZE,
heap: vec![0; 1024],
pointer: 0,
Expand All @@ -31,10 +48,16 @@ impl<R: Read, W: Write> Interpreter<R, W> {

/// Execute program
pub fn execute(&mut self, program: &Program) -> Result<(), RuntimeError> {
self.execute_ops(&program.ops)
self.execute_ops(&program.ops, &(0..1))
}

fn execute_ops(&mut self, ops: &[Op]) -> Result<(), RuntimeError> {
fn execute_ops(&mut self, ops: &[Op], span: &Range<usize>) -> Result<(), RuntimeError> {
if let LimiterResult::Halt = self.limiter.execute_op() {
return Err(RuntimeError::LimiterTriggered {
span: span.clone(),
});
}
Comment on lines +55 to +59
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of returning LimiterResult it might be better if the limiter returns an Result<(), RuntimeError>.
this would allow the limiter to control the LimiterTriggered error. In addition i think that passing more informations to Limiter::execute_op would be usefull.
Maybe soming like
fn execute_op(&mut self, span: &Range<usize>, op: &Op, heap: &[u8], pointer: usize) -> Result<(), RuntimeError>


for op in ops {
self.execute_op(op)?;
}
Expand All @@ -43,6 +66,12 @@ impl<R: Read, W: Write> Interpreter<R, W> {
}

fn execute_op(&mut self, op: &Op) -> Result<(), RuntimeError> {
if let LimiterResult::Halt = self.limiter.execute_op() {
return Err(RuntimeError::LimiterTriggered {
span: op.span.clone(),
});
}

match &op.op_type {
OpType::Start => {
// ignore
Expand Down Expand Up @@ -125,14 +154,14 @@ impl<R: Read, W: Write> Interpreter<R, W> {
OpType::PutString(array) => self.put_string(&op.span, array)?,
OpType::DLoop(ops, _) => {
while *self.heap_value(&op.span)? > 0 {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
}
}
OpType::LLoop(ops, _) => {
let heap_pointer = self.pointer;

while *self.heap_value(&op.span)? > 0 {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
self.pointer = heap_pointer;
}
}
Expand All @@ -144,7 +173,7 @@ impl<R: Read, W: Write> Interpreter<R, W> {
while left > 0 {
left = left.wrapping_sub(*step);
*self.heap_value(&op.span)? = left;
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
self.pointer = heap_pointer;
}

Expand All @@ -155,7 +184,7 @@ impl<R: Read, W: Write> Interpreter<R, W> {

let mut left = *self.heap_value(&op.span)?;
while left > 0 {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
left = left.wrapping_sub(*step);
*self.heap_value(&op.span)? = left;
self.pointer = heap_pointer;
Expand All @@ -168,7 +197,7 @@ impl<R: Read, W: Write> Interpreter<R, W> {

let mut left = *self.heap_value(&op.span)?;
while left > 0 {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
left = left.wrapping_sub(*step);
self.pointer = heap_pointer;
}
Expand All @@ -185,7 +214,7 @@ impl<R: Read, W: Write> Interpreter<R, W> {
while left > 0 {
left = left.wrapping_sub(1);
*self.heap_value(&op.span)? = left;
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
self.pointer = heap_pointer;
}

Expand All @@ -197,7 +226,7 @@ impl<R: Read, W: Write> Interpreter<R, W> {
*self.heap_value(&op.span)? = *iterations;
let mut left = *self.heap_value(&op.span)?;
while left > 0 {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
self.pointer = heap_pointer;
left = left.wrapping_sub(1);
*self.heap_value(&op.span)? = left;
Expand All @@ -208,7 +237,7 @@ impl<R: Read, W: Write> Interpreter<R, W> {
LoopDecrement::Auto => {
let heap_pointer = self.pointer;
for _ in 0..*iterations {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
self.pointer = heap_pointer;
}
*self.heap_value(&op.span)? = 0;
Expand All @@ -218,15 +247,15 @@ impl<R: Read, W: Write> Interpreter<R, W> {
if *self.heap_value(&op.span)? != 0 {
let heap_pointer = self.pointer;

self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;

self.pointer = heap_pointer;
*self.heap_value(&op.span)? = 0;
}
}
OpType::DTNz(ops, _, _) => {
if *self.heap_value(&op.span)? > 0 {
self.execute_ops(ops)?;
self.execute_ops(ops, &op.span)?;
}
}
OpType::SearchZero(step, _) => {
Expand Down
7 changes: 7 additions & 0 deletions cranefack/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ impl CraneFackError for ParserError {
/// Runtime errors for interpreter invocations
#[derive(Debug)]
pub enum RuntimeError {
/// The interpreter's configured [`Limiter`][crate::limiters::Limiter] triggered
LimiterTriggered {
span: Range<usize>,
},
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a message: String to the variant would allow the Limiter to return more useful information


/// The program tries to use a heap cell beyond the maximu allowed size
MaxHeapSizeReached {
span: Range<usize>,
Expand All @@ -125,6 +130,7 @@ impl Error for RuntimeError {
impl Display for RuntimeError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
RuntimeError::LimiterTriggered { .. } => write!(f, "Max number of cycles reached"),
RuntimeError::MaxHeapSizeReached {
max_heap_size,
required,
Expand All @@ -142,6 +148,7 @@ impl Display for RuntimeError {
impl CraneFackError for RuntimeError {
fn get_message(&self) -> (Option<Range<usize>>, String, Option<String>) {
match self {
RuntimeError::LimiterTriggered { span } => (Some(span.clone()), self.to_string(), None),
RuntimeError::MaxHeapSizeReached { span, .. } => {
(Some(span.clone()), self.to_string(), None)
}
Expand Down
1 change: 1 addition & 0 deletions cranefack/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mod analyzer;
mod backends;
mod errors;
mod ir;
pub mod limiters;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of now all modules are implementation details and all types are explicitly exported.
Please remove the pub and add an explicit export to the pub use lines below.

mod optimizations;
mod parser;

Expand Down
66 changes: 66 additions & 0 deletions cranefack/src/limiters.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
pub enum LimiterResult {
/// Continue executing
Continue,
/// Halt execution and return [`LimiterTriggered`][crate::errors::RuntimeError::LimiterTriggered]
Halt,
}

/// Generic interface for determining whether to continue or stop execution.
pub trait Limiter {
fn execute_op(&mut self) -> LimiterResult;
}

/// Always returns [`Continue`][LimiterResult::Continue].
pub struct Unlimited;

impl Limiter for Unlimited {
fn execute_op(&mut self) -> LimiterResult {
LimiterResult::Continue
}
}

/// Limit execution to within the configured number of cycles.
pub struct CycleLimiter(usize);

impl CycleLimiter {
pub fn new(max_cycles: usize) -> Self {
CycleLimiter(max_cycles)
}
}

impl Limiter for CycleLimiter {
fn execute_op(&mut self) -> LimiterResult {
self.0 = self.0.saturating_sub(1);
if self.0 == 0 {
LimiterResult::Halt
} else {
LimiterResult::Continue
}
}
}

/// Limits execution to within a given [`Duration`](std::time::Duration) of time.
pub struct TimeLimiter {
max_duration: std::time::Duration,
started_at: Option<std::time::Instant>,
}

impl TimeLimiter {
pub fn new(max_duration: std::time::Duration) -> Self {
TimeLimiter {
max_duration,
started_at: None,
}
}
}

impl Limiter for TimeLimiter {
fn execute_op(&mut self) -> LimiterResult {
let started_at = self.started_at.get_or_insert(std::time::Instant::now());
if started_at.elapsed() > self.max_duration {
LimiterResult::Halt
} else {
LimiterResult::Continue
}
}
}