diff --git a/src/interpret.rs b/src/interpret.rs index 38887eaa..a10ec6f6 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -205,10 +205,9 @@ impl ExecEnv { Value::Functio(func) => { match func { Functio::BfFunctio(body) => { - use crate::variables::BfWriter; let mut input: Vec = vec![]; for arg in args { - input.write_value(&self.eval_expr(arg)?); + self.eval_expr(arg)?.bf_write(&mut input); } println!("input = {:?}", input); let mut output = vec![]; diff --git a/src/variables.rs b/src/variables.rs index 0ef0e14e..a6abee9e 100644 --- a/src/variables.rs +++ b/src/variables.rs @@ -50,6 +50,63 @@ pub enum Value { Functio(Functio), } +impl Value { + /// Writes an AbleScript value to a Brainfuck input stream. This + /// should generally only be called on `Write`rs that cannot fail, + /// e.g., `Vec`, because any IO errors will cause a panic. + /// + /// The mapping from values to encodings is as follows, where all + /// multi-byte integers are little-endian: + /// + /// | AbleScript representation | Brainfuck representation | + /// |---------------------------|-------------------------------------------------------------| + /// | Nul | `00` | + /// | Str | `01` [length, 4 bytes] [string, \[LENGTH\] bytes, as UTF-8] | + /// | Int | `02` [value, 4 bytes] | + /// | Bool | `03` `00` false, `03` `01` true. | + /// | Abool | `04` `00` never, `04` `01` always, `04` `02` sometimes. | + /// | Brainfuck Functio | `05` `00` [length, 4 bytes] [source code, \[LENGTH\] bytes] | + /// | AbleScript Functio | `05` `01` (todo, not yet finalized or implemented) | + /// + /// The existing mappings should never change, as they are + /// directly visible from Brainfuck code and modifying them would + /// break a significant amount of AbleScript code. If more types + /// are added in the future, they should be assigned the remaining + /// discriminant bytes from 06..FF. + pub fn bf_write(&mut self, stream: &mut impl Write) { + match self { + Value::Nul => stream.write_all(&[0]), + Value::Str(s) => stream + .write_all(&[1]) + .and_then(|_| stream.write_all(&(s.len() as u32).to_le_bytes())) + .and_then(|_| stream.write_all(&s.as_bytes())), + Value::Int(v) => stream + .write_all(&[2]) + .and_then(|_| stream.write_all(&v.to_le_bytes())), + Value::Bool(b) => stream + .write_all(&[3]) + .and_then(|_| stream.write_all(&[*b as _])), + Value::Abool(a) => stream.write_all(&[4]).and_then(|_| { + stream.write_all(&[match *a { + Abool::Never => 0, + Abool::Sometimes => 2, + Abool::Always => 1, + }]) + }), + Value::Functio(f) => stream.write_all(&[5]).and_then(|_| match f { + Functio::BfFunctio(f) => stream + .write_all(&[0]) + .and_then(|_| stream.write_all(&(f.len() as u32).to_le_bytes())) + .and_then(|_| stream.write_all(&f)), + Functio::AbleFunctio(_) => { + todo!() + } + }), + } + .expect("Failed to write to Brainfuck input"); + } +} + impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -119,70 +176,6 @@ impl From for bool { } } -/// Allows writing AbleScript values to Brainfuck. -/// -/// This trait is blanket implemented for all `Write`rs, but should -/// typically only be used for `Write`rs that cannot fail, e.g., -/// `Vec`, because any IO errors will cause a panic. -/// -/// The mapping from values to encodings is as follows, where all -/// multi-byte integers are little-endian: -/// -/// | AbleScript representation | Brainfuck representation | -/// |---------------------------|-----------------------------------------------------------| -/// | Nul | 00 | -/// | Str | 01 [length, 4 bytes] [string, \[LENGTH\] bytes, as UTF-8] | -/// | Int | 02 [value, 4 bytes] | -/// | Bool | 03 00 false, 03 01 true. | -/// | Abool | 04 00 never, 04 01 always, 04 02 sometimes. | -/// | Brainfuck Functio | 05 00 [length, 4 bytes] [source code, \[LENGTH\] bytes] | -/// | AbleScript Functio | 05 01 (todo, not yet finalized or implemented) | -/// -/// The existing mappings should never change, as they are directly -/// visible from Brainfuck code and modifying them would break a -/// significant amount of AbleScript code. If more types are added in -/// the future, they should be assigned the remaining discriminant -/// bytes from 06..FF. -pub trait BfWriter { - /// Write a value. Panic if writing fails for any reason. - fn write_value(&mut self, value: &Value); -} - -impl BfWriter for T { - fn write_value(&mut self, value: &Value) { - match value { - Value::Nul => self.write_all(&[0]), - Value::Str(s) => self - .write_all(&[1]) - .and_then(|_| self.write_all(&(s.len() as u32).to_le_bytes())) - .and_then(|_| self.write_all(&s.as_bytes())), - Value::Int(v) => self - .write_all(&[2]) - .and_then(|_| self.write_all(&v.to_le_bytes())), - Value::Bool(b) => self - .write_all(&[3]) - .and_then(|_| self.write_all(&[*b as _])), - Value::Abool(a) => self.write_all(&[4]).and_then(|_| { - self.write_all(&[match *a { - Abool::Never => 0, - Abool::Sometimes => 2, - Abool::Always => 1, - }]) - }), - Value::Functio(f) => self.write_all(&[5]).and_then(|_| match f { - Functio::BfFunctio(f) => self - .write_all(&[0]) - .and_then(|_| self.write_all(&(f.len() as u32).to_le_bytes())) - .and_then(|_| self.write_all(&f)), - Functio::AbleFunctio(_) => { - todo!() - } - }), - } - .expect("Failed to write to Brainfuck input"); - } -} - #[derive(Debug)] pub struct Variable { pub melo: bool,