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).
This commit is contained in:
parent
2ec416db97
commit
f7ac2a1a43
5
able-script-test/iotest.able
Normal file
5
able-script-test/iotest.able
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
var data;
|
||||||
|
loop {
|
||||||
|
data read;
|
||||||
|
data print;
|
||||||
|
}
|
16
src/error.rs
16
src/error.rs
|
@ -1,14 +1,14 @@
|
||||||
use std::ops::Range;
|
use std::{io, ops::Range};
|
||||||
|
|
||||||
use crate::{brian::InterpretError, lexer::Token};
|
use crate::{brian::InterpretError, lexer::Token};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
pub kind: ErrorKind,
|
pub kind: ErrorKind,
|
||||||
pub span: Range<usize>,
|
pub span: Range<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
SyntaxError(String),
|
SyntaxError(String),
|
||||||
UnexpectedEof,
|
UnexpectedEof,
|
||||||
|
@ -22,6 +22,7 @@ pub enum ErrorKind {
|
||||||
BfInterpretError(InterpretError),
|
BfInterpretError(InterpretError),
|
||||||
MismatchedArgumentError,
|
MismatchedArgumentError,
|
||||||
MissingLhs,
|
MissingLhs,
|
||||||
|
IOError(io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
@ -35,3 +36,12 @@ impl Error {
|
||||||
Self::new(ErrorKind::UnexpectedEof, index..index)
|
Self::new(ErrorKind::UnexpectedEof, index..index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(e: io::Error) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ErrorKind::IOError(e),
|
||||||
|
span: 0..0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,12 +9,11 @@
|
||||||
#![deny(missing_docs)]
|
#![deny(missing_docs)]
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::HashMap,
|
collections::{HashMap, VecDeque},
|
||||||
io::{stdout, Write},
|
io::{stdin, stdout, Read, Write},
|
||||||
ops::Range,
|
ops::Range,
|
||||||
process::exit,
|
process::exit,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
usize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use rand::random;
|
use rand::random;
|
||||||
|
@ -32,6 +31,13 @@ pub struct ExecEnv {
|
||||||
/// top-most (newest) stack frame, and `stack[0]` is the
|
/// top-most (newest) stack frame, and `stack[0]` is the
|
||||||
/// bottom-most (oldest) stack frame.
|
/// bottom-most (oldest) stack frame.
|
||||||
stack: Vec<Scope>,
|
stack: Vec<Scope>,
|
||||||
|
|
||||||
|
/// 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<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of visible variable and function definitions in a single
|
/// A set of visible variable and function definitions in a single
|
||||||
|
@ -58,6 +64,10 @@ enum HaltStatus {
|
||||||
Hopback(Range<usize>),
|
Hopback(Range<usize>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The number of bits the `read` statement reads at once from
|
||||||
|
/// standard input.
|
||||||
|
pub const READ_BITS: u8 = 3;
|
||||||
|
|
||||||
impl ExecEnv {
|
impl ExecEnv {
|
||||||
/// Create a new Scope with no predefined variable definitions or
|
/// Create a new Scope with no predefined variable definitions or
|
||||||
/// other information.
|
/// other information.
|
||||||
|
@ -65,6 +75,7 @@ impl ExecEnv {
|
||||||
Self {
|
Self {
|
||||||
// We always need at least one stackframe.
|
// We always need at least one stackframe.
|
||||||
stack: vec![Default::default()],
|
stack: vec![Default::default()],
|
||||||
|
read_buf: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,13 +216,15 @@ impl ExecEnv {
|
||||||
|
|
||||||
self.decl_var(&iden.iden, init);
|
self.decl_var(&iden.iden, init);
|
||||||
}
|
}
|
||||||
StmtKind::Functio { iden, params, body } => self.decl_var(
|
StmtKind::Functio { iden, params, body } => {
|
||||||
&iden.iden,
|
self.decl_var(
|
||||||
Value::Functio(Functio::AbleFunctio {
|
&iden.iden,
|
||||||
params: params.iter().map(|iden| iden.iden.to_string()).collect(),
|
Value::Functio(Functio::AbleFunctio {
|
||||||
body: body.block.to_owned(),
|
params: params.iter().map(|iden| iden.iden.to_string()).collect(),
|
||||||
}),
|
body: body.block.to_owned(),
|
||||||
),
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
StmtKind::BfFunctio {
|
StmtKind::BfFunctio {
|
||||||
iden,
|
iden,
|
||||||
tape_len,
|
tape_len,
|
||||||
|
@ -280,7 +293,15 @@ impl ExecEnv {
|
||||||
.write_all(include_str!("rickroll").as_bytes())
|
.write_all(include_str!("rickroll").as_bytes())
|
||||||
.expect("Failed to write to stdout");
|
.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)
|
Ok(HaltStatus::Finished)
|
||||||
|
@ -356,6 +377,26 @@ impl ExecEnv {
|
||||||
Ok(())
|
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<bool, Error> {
|
||||||
|
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
|
/// Get the value of a variable. Throw an error if the variable is
|
||||||
/// inaccessible or banned.
|
/// inaccessible or banned.
|
||||||
fn get_var(&self, name: &Iden) -> Result<Value, Error> {
|
fn get_var(&self, name: &Iden) -> Result<Value, Error> {
|
||||||
|
|
Loading…
Reference in a new issue