From 3edbca51d5429416fd9b0cc8716e2db58fdb5b20 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 18 Jun 2021 20:28:53 +0200 Subject: [PATCH 1/2] Implemented read in Parser --- src/ast.rs | 1 + src/interpret.rs | 1 + src/lexer.rs | 4 ++++ src/parser.rs | 12 ++++++++++++ 4 files changed, 18 insertions(+) diff --git a/src/ast.rs b/src/ast.rs index 835e679..f84c47a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -73,6 +73,7 @@ pub enum StmtKind { args: Vec, }, Print(Expr), + Read(Iden), Melo(Iden), Rlyeh, Rickroll, diff --git a/src/interpret.rs b/src/interpret.rs index 43d48d9..29466ec 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -280,6 +280,7 @@ impl ExecEnv { .write_all(include_str!("rickroll").as_bytes()) .expect("Failed to write to stdout"); } + StmtKind::Read(_) => todo!(), } Ok(HaltStatus::Finished) diff --git a/src/lexer.rs b/src/lexer.rs index 3f686c0..7632386 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -86,6 +86,10 @@ pub enum Token { #[token("print")] Print, + /// Read input into preceding variable + #[token("read")] + Read, + /// Ban the following variable from ever being used again #[token("melo")] Melo, diff --git a/src/parser.rs b/src/parser.rs index 893f44b..90d86bc 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -318,6 +318,18 @@ impl<'source> Parser<'source> { }; } } + + // Read input + Token::Read => { + if let Some(Expr { + kind: ExprKind::Variable(iden), + span, + }) = buf + { + break self.semi_terminated(StmtKind::Read(Iden::new(iden, span)))?; + } + } + t => buf = Some(self.parse_expr(t, &mut buf)?), } }; From eee173060ce50275ecc5a6aa29a83587b4260f80 Mon Sep 17 00:00:00 2001 From: Alex Bethel Date: Fri, 18 Jun 2021 16:05:50 -0500 Subject: [PATCH 2/2] Implement `read` semantics in interpreter Also added a test file `iotest.able` to just read data and print it (the `cat` of AbleScript I guess). --- able-script-test/iotest.able | 5 +++ src/error.rs | 16 +++++++-- src/interpret.rs | 63 +++++++++++++++++++++++++++++------- 3 files changed, 70 insertions(+), 14 deletions(-) create mode 100644 able-script-test/iotest.able diff --git a/able-script-test/iotest.able b/able-script-test/iotest.able new file mode 100644 index 0000000..b0406df --- /dev/null +++ b/able-script-test/iotest.able @@ -0,0 +1,5 @@ +var data; +loop { + data read; + data print; +} diff --git a/src/error.rs b/src/error.rs index 7d1e489..940b479 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,14 +1,14 @@ -use std::ops::Range; +use std::{io, ops::Range}; use crate::{brian::InterpretError, lexer::Token}; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Error { pub kind: ErrorKind, pub span: Range, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum ErrorKind { SyntaxError(String), UnexpectedEof, @@ -22,6 +22,7 @@ pub enum ErrorKind { BfInterpretError(InterpretError), MismatchedArgumentError, MissingLhs, + IOError(io::Error), } impl Error { @@ -35,3 +36,12 @@ impl Error { Self::new(ErrorKind::UnexpectedEof, index..index) } } + +impl From for Error { + fn from(e: io::Error) -> Self { + Self { + kind: ErrorKind::IOError(e), + span: 0..0, + } + } +} diff --git a/src/interpret.rs b/src/interpret.rs index 29466ec..9d0bda9 100644 --- a/src/interpret.rs +++ b/src/interpret.rs @@ -9,12 +9,11 @@ #![deny(missing_docs)] use std::{ cell::RefCell, - collections::HashMap, - io::{stdout, Write}, + collections::{HashMap, VecDeque}, + io::{stdin, stdout, Read, Write}, ops::Range, process::exit, rc::Rc, - usize, }; use rand::random; @@ -32,6 +31,13 @@ pub struct ExecEnv { /// top-most (newest) stack frame, and `stack[0]` is the /// bottom-most (oldest) stack frame. stack: Vec, + + /// The `read` statement maintains a buffer of up to 7 bits, + /// because input comes from the operating system 8 bits at a time + /// (via stdin) but gets delivered to AbleScript 3 bits at a time + /// (via the `read` statement). We store each of those bits as + /// booleans to facilitate easy manipulation. + read_buf: VecDeque, } /// A set of visible variable and function definitions in a single @@ -58,6 +64,10 @@ enum HaltStatus { Hopback(Range), } +/// The number of bits the `read` statement reads at once from +/// standard input. +pub const READ_BITS: u8 = 3; + impl ExecEnv { /// Create a new Scope with no predefined variable definitions or /// other information. @@ -65,6 +75,7 @@ impl ExecEnv { Self { // We always need at least one stackframe. stack: vec![Default::default()], + read_buf: Default::default(), } } @@ -205,13 +216,15 @@ impl ExecEnv { self.decl_var(&iden.iden, init); } - StmtKind::Functio { iden, params, body } => self.decl_var( - &iden.iden, - Value::Functio(Functio::AbleFunctio { - params: params.iter().map(|iden| iden.iden.to_string()).collect(), - body: body.block.to_owned(), - }), - ), + StmtKind::Functio { iden, params, body } => { + self.decl_var( + &iden.iden, + Value::Functio(Functio::AbleFunctio { + params: params.iter().map(|iden| iden.iden.to_string()).collect(), + body: body.block.to_owned(), + }), + ); + } StmtKind::BfFunctio { iden, tape_len, @@ -280,7 +293,15 @@ impl ExecEnv { .write_all(include_str!("rickroll").as_bytes()) .expect("Failed to write to stdout"); } - StmtKind::Read(_) => todo!(), + StmtKind::Read(iden) => { + let mut value = 0; + for _ in 0..READ_BITS { + value <<= 1; + value += self.get_bit()? as i32; + } + + self.get_var_mut(&iden)?.value.replace(Value::Int(value)); + } } Ok(HaltStatus::Finished) @@ -356,6 +377,26 @@ impl ExecEnv { Ok(()) } + /// Get a single bit from the bit buffer, or refill it from + /// standard input if it is empty. + fn get_bit(&mut self) -> Result { + const BITS_PER_BYTE: u8 = 8; + + if self.read_buf.is_empty() { + let mut data = [0]; + stdin().read_exact(&mut data)?; + + for n in (0..BITS_PER_BYTE).rev() { + self.read_buf.push_back(((data[0] >> n) & 1) != 0); + } + } + + Ok(self + .read_buf + .pop_front() + .expect("We just pushed to the buffer if it was empty")) + } + /// Get the value of a variable. Throw an error if the variable is /// inaccessible or banned. fn get_var(&self, name: &Iden) -> Result {