diff --git a/.gitignore b/.gitignore index de87e25..fa86d38 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ r7rs.pdf .#* .idea *.iml +.envrc diff --git a/Makefile b/Makefile index a431af0..fd4b3b1 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ install: build $(CARGO) install run: - $(CARGO) run --bin bracesi + $(CARGO) run --bin bracesi -- run $(RUN_FILE) debug_run: $(CARGO) run --features debug_vm --features debug_code --bin bracesi -- run $(RUN_FILE) diff --git a/examples/io.scm b/examples/io.scm new file mode 100644 index 0000000..06ca8ca --- /dev/null +++ b/examples/io.scm @@ -0,0 +1 @@ + (write-string "Hello world") diff --git a/src/cmd/run.rs b/src/cmd/run.rs index 5f50004..5b07043 100644 --- a/src/cmd/run.rs +++ b/src/cmd/run.rs @@ -21,7 +21,7 @@ impl Command { clap::Command::new("run") .alias("r") .about("run the specified file") - .arg(arg!([INPUT])) + .arg(arg!(input: [INPUT])) } pub fn run(&self) -> anyhow::Result<()> { diff --git a/src/compiler/frontend/reader/datum/number.rs b/src/compiler/frontend/reader/datum/number.rs index dfdd049..de08b3a 100644 --- a/src/compiler/frontend/reader/datum/number.rs +++ b/src/compiler/frontend/reader/datum/number.rs @@ -197,7 +197,7 @@ fn parse_decimal_short(input: Input) -> ParseResult { fn apply_exponent(num: f64, exp: Option) -> f64 { match exp { Some(e) => (num as f64) * (f64::powi(10.0, e) as f64), - _ => (num as f64), + _ => num as f64, } } diff --git a/src/repl.rs b/src/repl.rs index 96f92ed..a2adddb 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -177,11 +177,14 @@ impl Repl { self.command_counter += 1; match compiler.compile(&mut source) { - Ok(unit) => match self.vm.interpret(unit) { - Ok(Value::Unspecified) => (), - Ok(v) => println!("{}", self.vm.write(&v)), - Err(e) => self.vm.print_error(&e, &compiler), - }, + Ok(unit) => { + match self.vm.interpret(unit) { + Ok(Value::Unspecified) => (), + Ok(v) => println!("{}", self.vm.write(&v)), + Err(e) => self.vm.print_error(&e, &compiler), + }; + } + Err(e) => compiler.print_error(&e), } diff --git a/src/vm.rs b/src/vm.rs index 83e1c62..5d04f34 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -10,6 +10,7 @@ use scheme::writer::Writer; pub use settings::{Setting, Settings}; use value::Value; +use self::value::port::IORegistry; use self::value::procedure::foreign; use self::value::procedure::native; use crate::compiler::frontend::reader::datum::Datum; @@ -34,21 +35,31 @@ pub mod value; pub type Result = std::result::Result; -#[derive(Debug)] pub struct VM { stack_size: usize, pub values: value::Factory, pub top_level: TopLevel, + pub io_resources: IORegistry, writer: Writer, pub settings: Settings, } +impl std::fmt::Debug for VM { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("VM") + .field("settings", &self.settings) + .field("stack_size", &self.stack_size) + .finish() + } +} + impl VM { pub fn new(stack_size: usize) -> VM { VM { stack_size, values: value::Factory::default(), top_level: TopLevel::new(), + io_resources: IORegistry::new(), writer: Writer::new(), settings: Settings::default(), } @@ -88,7 +99,13 @@ impl VM { debug_mode: self.settings.is_enabled(&Setting::Debug), }; - Instance::interpret(unit.closure, &mut self.top_level, &mut self.values, options) + Instance::interpret( + unit.closure, + &mut self.top_level, + &mut self.values, + &mut self.io_resources, + options, + ) } pub fn interpret_expander( @@ -106,6 +123,7 @@ impl VM { &form_value, arguments, &mut self.top_level, + &mut self.io_resources, &mut self.values, )?, location, diff --git a/src/vm/error/reporting.rs b/src/vm/error/reporting.rs index a2f94d7..e6e8504 100644 --- a/src/vm/error/reporting.rs +++ b/src/vm/error/reporting.rs @@ -59,6 +59,9 @@ impl<'a> ErrorReporter<'a> { RuntimeError::OutOfBoundError(idx, accepted) => { format!("OutOfBoudError: can't access element at index `{}`. The allowed ranged is ({}..{})", idx, accepted.start, accepted.end) } + RuntimeError::IOError(e) => { + format!("IOError: {}", e) + } RuntimeError::SyntaxError(msg) => { format!("Error during macro expansion: {}", msg) } diff --git a/src/vm/instance.rs b/src/vm/instance.rs index 173a356..f04d978 100644 --- a/src/vm/instance.rs +++ b/src/vm/instance.rs @@ -16,6 +16,7 @@ /// ``` /// use braces::vm::instance::{Instance, Options}; /// use braces::vm::{value, global::TopLevel, VM}; +/// use braces::vm::value::port::IORegistry; /// use braces::compiler::{source::StringSource, Compiler}; /// let mut source = StringSource::new("(define (id x) x) (id #t)"); /// let mut compiler = Compiler::new(); @@ -23,7 +24,8 @@ /// // Now interpret the unit /// let mut top_level = TopLevel::new(); /// let mut values = value::Factory::default(); -/// let result = Instance::interpret(unit.closure, &mut top_level, &mut values, Options::default()).unwrap(); +/// let mut io_resources = IORegistry::new(); +/// let result = Instance::interpret(unit.closure, &mut top_level, &mut values, &mut io_resources, Options::default()).unwrap(); /// println!("{:#?}", result); /// ``` /// @@ -44,6 +46,7 @@ use super::stack_trace::StackTrace; use super::value; use super::value::closure::Closure; use super::value::error; +use super::value::port::IORegistry; use super::value::procedure::{self, Arity}; use super::value::symbol::Symbol; use super::value::Value; @@ -79,6 +82,8 @@ pub struct Instance<'a> { pub(crate) values: &'a mut value::Factory, // top level environment which can be shared between individual instance runs top_level: &'a mut TopLevel, + + pub(crate) io_resources: &'a mut IORegistry, // a simple stack to manage intermediate values and locals stack: ValueStack, // manage all live functions @@ -98,9 +103,10 @@ impl<'a> Instance<'a> { initial_closure: value::closure::Closure, top_level: &'a mut TopLevel, values: &'a mut value::Factory, + io_resources: &'a mut IORegistry, options: Options, ) -> Self { - let mut vm = Self::vanilla(top_level, values, options); + let mut vm = Self::vanilla(top_level, values, io_resources, options); vm.push(Value::Closure(initial_closure.clone())).unwrap(); vm.push_frame(initial_closure, 0).unwrap(); vm @@ -109,6 +115,7 @@ impl<'a> Instance<'a> { pub fn vanilla( top_level: &'a mut TopLevel, values: &'a mut value::Factory, + io_resources: &'a mut IORegistry, settings: Options, ) -> Self { let stack = ValueStack::new(settings.stack_size * 255); @@ -121,6 +128,7 @@ impl<'a> Instance<'a> { call_stack, top_level, active_frame: std::ptr::null_mut(), + io_resources, open_up_values, settings, } @@ -130,9 +138,10 @@ impl<'a> Instance<'a> { initial_closure: value::closure::Closure, top_level: &'a mut TopLevel, values: &'a mut value::Factory, + io_resources: &'a mut IORegistry, options: Options, ) -> Result { - let mut instance = Self::new(initial_closure, top_level, values, options); + let mut instance = Self::new(initial_closure, top_level, values, io_resources, options); instance.run() } @@ -141,9 +150,10 @@ impl<'a> Instance<'a> { syntax: &Value, arguments: &[Value], top_level: &'a mut TopLevel, + io_resources: &'a mut IORegistry, values: &'a mut value::Factory, ) -> Result { - let mut vm = Self::vanilla(top_level, values, Options::default()); + let mut vm = Self::vanilla(top_level, values, io_resources, Options::default()); let is_native = expander.is_native(); vm.push(Value::Procedure(expander))?; @@ -697,6 +707,7 @@ impl<'a> Instance<'a> { arg_count: usize, ) -> Result<()> { self.check_arity(&proc.arity, arg_count)?; + let arg_count = self.bind_arguments(&proc.arity, arg_count)?; let arguments = self .pop_n(arg_count) .iter() @@ -1087,6 +1098,7 @@ impl<'a> Instance<'a> { #[cfg(test)] mod tests { + use super::value::port::IORegistry; use crate::vm::global::TopLevel; use crate::vm::instance::{Instance, Options}; use crate::vm::value::access::{Access, Reference}; @@ -1097,8 +1109,10 @@ mod tests { fn test_bind_arguments_exactly_n() -> super::Result<()> { let mut top_level = TopLevel::new(); let mut values = Factory::default(); + let mut io_resources = IORegistry::new(); let settings = Options::default(); - let mut instance = Instance::vanilla(&mut top_level, &mut values, settings); + let mut instance = + Instance::vanilla(&mut top_level, &mut values, &mut io_resources, settings); instance.push(Access::ByVal(Value::Bool(true)))?; instance.push(Access::ByVal(Value::Bool(false)))?; @@ -1120,9 +1134,15 @@ mod tests { fn test_bind_arguments_rest_args() -> super::Result<()> { let mut top_level = TopLevel::new(); let mut values = Factory::default(); + let mut io_resources = IORegistry::new(); let expected_rest_args = values.proper_list(vec![Value::Bool(false), Value::Bool(false)]); - let mut instance = Instance::vanilla(&mut top_level, &mut values, Options::default()); + let mut instance = Instance::vanilla( + &mut top_level, + &mut values, + &mut io_resources, + Options::default(), + ); instance.push(Access::ByVal(Value::Bool(true)))?; instance.push(Access::ByVal(Value::Bool(false)))?; diff --git a/src/vm/scheme/core.rs b/src/vm/scheme/core.rs index 85e65b7..cd5b2be 100644 --- a/src/vm/scheme/core.rs +++ b/src/vm/scheme/core.rs @@ -1,4 +1,5 @@ pub mod numbers; +pub mod ports; use super::ffi::*; use crate::vm::instance::Instance; use crate::vm::value::access::{Access, Reference}; @@ -47,6 +48,7 @@ pub fn register(vm: &mut VM) { register_core!(vm, "gensym", gensym, Arity::Exactly(0)); numbers::register(vm); + ports::register(vm); } pub fn load_file(vm: &mut Instance, args: Vec) -> FunctionResult> { diff --git a/src/vm/scheme/core/numbers.rs b/src/vm/scheme/core/numbers.rs index 9280683..f5c6885 100644 --- a/src/vm/scheme/core/numbers.rs +++ b/src/vm/scheme/core/numbers.rs @@ -71,10 +71,14 @@ macro_rules! define_fold { pub fn $func(_vm: &mut Instance, args: Vec) -> FunctionResult> { let mut result = number::Number::fixnum($identity); - for n in args { - match n { - Value::Number(n) => result = result.$op(n)?, - v => return Err(error::argument_error(v.clone(), "is not a number")), + match rest_procedure(&args)? { + numbers => { + for n in numbers { + match n.to_owned() { + Value::Number(n) => result = result.$op(n)?, + v => return Err(error::argument_error(v, "is not a number")), + } + } } } @@ -89,17 +93,22 @@ define_fold!(mul, 1, mul); macro_rules! define_reduction { ($func:ident, $op:ident) => { pub fn $func(_vm: &mut Instance, args: Vec) -> FunctionResult> { - if let Value::Number(mut result) = args[0].clone() { - for n in args[1..].iter().cloned() { - match n { - Value::Number(n) => result = result.$op(n)?, - v => return Err(error::argument_error(v.clone(), "is not a number")), + match positional_and_rest_procedure1(&args)? { + (num @ Value::Number(_), rest) if rest.is_empty() => Ok(num.clone().into()), + + (Value::Number(init), rest) => { + let mut result = init.clone(); + + for n in rest { + match n.to_owned() { + Value::Number(n) => result = result.$op(n)?, + v => return Err(error::argument_error(v.clone(), "is not a number")), + } } - } - Ok(Value::Number(result).into()) - } else { - return Err(error::argument_error(args[0].clone(), "is not a number")); + Ok(Value::Number(result).into()) + } + (other, _) => Err(error::argument_error(other.clone(), "Expected number")), } } }; @@ -113,13 +122,18 @@ macro_rules! define_ordering { fn $func(vm: &mut Instance, args: Vec) -> FunctionResult> { let mut result = true; - for n in 0..args.len() { - if n == 0 { - result = true; - } else { - result = as_number(vm, &args[n - 1])? $op as_number(vm, &args[n])?; + match rest_procedure(&args)? { + rest => { + for n in 0..rest.len() { + if n == 0 { + result = true; + } else { + result = as_number(vm, &rest[n - 1].get_inner_ref())? $op as_number(vm, &rest[n].get_inner_ref())?; + } + } } - } + }; + Ok(Value::Bool(result).into()) } diff --git a/src/vm/scheme/core/ports.rs b/src/vm/scheme/core/ports.rs new file mode 100644 index 0000000..f92bd42 --- /dev/null +++ b/src/vm/scheme/core/ports.rs @@ -0,0 +1,105 @@ +use crate::vm::scheme::ffi::*; +use crate::vm::value::access::Access; +use crate::vm::value::port::Port; +use crate::vm::value::procedure::foreign; +use crate::vm::value::procedure::Arity; +use crate::vm::value::{error, Value}; +use crate::vm::Instance; +use crate::vm::VM; +use std::io::Write; + +pub fn register(vm: &mut VM) { + super::register_core!(vm, "port?", port_p, Arity::Exactly(1)); + super::register_core!( + vm, + "current-input-port", + current_input_port, + Arity::Exactly(0) + ); + super::register_core!( + vm, + "current-output-port", + current_output_port, + Arity::Exactly(0) + ); + + super::register_core!( + vm, + "current-error-port", + current_error_port, + Arity::Exactly(0) + ); + + super::register_core!(vm, "write-char", write_char, Arity::AtLeast(1)); + super::register_core!(vm, "write-string", write_string, Arity::AtLeast(1)); + super::register_core!(vm, "flush-output-port", flush_port, Arity::AtLeast(0)); +} + +fn port_p(_vm: &mut Instance, args: Vec) -> FunctionResult> { + match unary_procedure(&args)? { + Value::Port(_) => Ok(Value::Bool(true).into()), + _ => Ok(Value::Bool(false).into()), + } +} + +// TODO: return srfi-parameter value +fn current_input_port(_vm: &mut Instance, args: Vec) -> FunctionResult> { + if args.len() != 0 { + return Err(error::arity_mismatch(Arity::Exactly(0), args.len())); + } + + Ok(Value::Port(Port::stdin()).into()) +} + +fn current_output_port(_vm: &mut Instance, args: Vec) -> FunctionResult> { + if args.len() != 0 { + return Err(error::arity_mismatch(Arity::Exactly(0), args.len())); + } + + Ok(Value::Port(Port::stdout()).into()) +} + +fn current_error_port(_vm: &mut Instance, args: Vec) -> FunctionResult> { + if args.len() != 0 { + return Err(error::arity_mismatch(Arity::Exactly(0), args.len())); + } + + todo!() +} + +fn write_char(vm: &mut Instance, args: Vec) -> FunctionResult> { + match positional_and_rest_procedure1(&args)? { + (Value::Char(c), r) if r.is_empty() => { + let mut stdout = vm.io_resources.stdout().borrow_mut(); + stdout.write_all(c.encode_utf8(&mut [0; 4]).as_bytes())?; + Ok(Value::Unspecified.into()) + } + (Value::Char(c), r) => todo!(), + other => { + println!("other: {:?}", other); + Err(error::arity_mismatch(Arity::AtLeast(1), args.len())) + } + } +} + +fn write_string(vm: &mut Instance, args: Vec) -> FunctionResult> { + match positional_and_rest_procedure1(&args)? { + (Value::String(s), r) if r.is_empty() => { + let mut stdout = vm.io_resources.stdout().borrow_mut(); + stdout.write_all((&s.as_ref()).as_bytes())?; + Ok(Value::Unspecified.into()) + } + (Value::String(_s), _r) => todo!(), + (other, _) => Err(error::argument_error(other.clone(), "Expected String")), + } +} + +fn flush_port(vm: &mut Instance, args: Vec) -> FunctionResult> { + match optional_unary_procedure(&args)? { + None => { + vm.io_resources.stdout().borrow_mut().flush()?; + Ok(Value::Unspecified.into()) + } + Some(_v) => todo!(), + } +} diff --git a/src/vm/scheme/ffi.rs b/src/vm/scheme/ffi.rs index 282ac26..a128989 100644 --- a/src/vm/scheme/ffi.rs +++ b/src/vm/scheme/ffi.rs @@ -1,3 +1,4 @@ +use crate::vm::value::access::Reference; use crate::vm::value::Value; use crate::vm::value::{error, procedure::Arity}; use thiserror::Error; @@ -45,3 +46,33 @@ pub fn unary_procedure(args: &Vec) -> FunctionResult<&Value> { _ => Err(error::arity_mismatch(Arity::Exactly(1), args.len())), } } + +pub fn optional_unary_procedure(args: &Vec) -> FunctionResult>> { + match &args[..] { + [Value::ProperList(rest)] => Ok(rest.to_vector().get(0).cloned()), + other => { + println!("other: {:?}", other); + Err(error::arity_mismatch(Arity::AtLeast(0), args.len())) + } + } +} + +pub fn positional_and_rest_procedure1( + args: &Vec, +) -> FunctionResult<(&Value, Vec>)> { + match &args[..] { + [first, Value::ProperList(rest)] => Ok((first, rest.to_vector())), + + other => { + println!("other: {:?}", other); + Err(error::arity_mismatch(Arity::AtLeast(1), args.len())) + } + } +} + +pub fn rest_procedure(args: &Vec) -> FunctionResult>> { + match &args[..] { + [Value::ProperList(rest_args)] => Ok(rest_args.to_vector()), + _ => Err(error::arity_mismatch(Arity::Many, args.len())), + } +} diff --git a/src/vm/scheme/writer.rs b/src/vm/scheme/writer.rs index 3522787..35cde53 100644 --- a/src/vm/scheme/writer.rs +++ b/src/vm/scheme/writer.rs @@ -77,6 +77,8 @@ impl Writer { format!("({} . {})", head_body.join(" "), tail_body) } Value::Unspecified => "#".to_string(), + Value::Port(p) => format!("#", p), + Value::EofObject => "#".to_string(), } } diff --git a/src/vm/value.rs b/src/vm/value.rs index 6613585..016f5cc 100644 --- a/src/vm/value.rs +++ b/src/vm/value.rs @@ -25,6 +25,7 @@ pub mod equality; pub mod error; pub mod list; pub mod number; +pub mod port; pub mod procedure; pub mod string; pub mod symbol; @@ -52,6 +53,8 @@ pub enum Value { ImproperList(list::List, Reference), Procedure(procedure::Procedure), Closure(closure::Closure), + Port(port::Port), + EofObject, Unspecified, } diff --git a/src/vm/value/error.rs b/src/vm/value/error.rs index d1c2fce..1a74980 100644 --- a/src/vm/value/error.rs +++ b/src/vm/value/error.rs @@ -20,6 +20,8 @@ pub enum RuntimeError { SyntaxError(String), #[error("LoadError")] LoadError(std::path::PathBuf, Box), + #[error(transparent)] + IOError(#[from] std::io::Error), } pub fn load_error(file: std::path::PathBuf, e: crate::vm::Error) -> RuntimeError { diff --git a/src/vm/value/list.rs b/src/vm/value/list.rs index 5c5665a..1510601 100644 --- a/src/vm/value/list.rs +++ b/src/vm/value/list.rs @@ -33,6 +33,13 @@ impl List { } } + pub fn to_vector(&self) -> Vec> { + match self { + Self::Nil => Vec::new(), + Self::Cons(inner) => inner.iter().cloned().collect(), + } + } + // create a fresh list by concateneting the two supplied lists pub fn append(lhs: &List, rhs: &List) -> List { match (lhs, rhs) { diff --git a/src/vm/value/port.rs b/src/vm/value/port.rs new file mode 100644 index 0000000..cd4b311 --- /dev/null +++ b/src/vm/value/port.rs @@ -0,0 +1,125 @@ +use rustc_hash::FxHashMap; + +use super::{equality::SchemeEqual, error}; +use std::{ + cell::RefCell, + io::{Stderr, Stdin, Stdout}, + rc::Rc, +}; + +pub type Result = std::result::Result; + +#[derive(Clone, Debug, PartialEq)] +pub enum PortType { + Binary, + Textual, +} + +#[repr(transparent)] +pub struct IORegistry(FxHashMap); + +impl IORegistry { + pub fn new() -> Self { + let mut map = FxHashMap::default(); + + map.insert(IOKey::Stdin, IOEntry::stdin()); + map.insert(IOKey::Stdout, IOEntry::stdout()); + map.insert(IOKey::Stderr, IOEntry::stderr()); + + Self(map) + } + + pub fn stdout(&mut self) -> &mut Rc> { + match self.0.get_mut(&IOKey::Stdout).unwrap() { + IOEntry::Stdout(inner) => inner, + _ => unreachable!(), + } + } + + pub fn stderr(&mut self) -> &mut Rc> { + match self.0.get_mut(&IOKey::Stderr).unwrap() { + IOEntry::Stderr(inner) => inner, + _ => unreachable!(), + } + } + + pub fn stdin(&mut self) -> &mut Rc> { + match self.0.get_mut(&IOKey::Stdin).unwrap() { + IOEntry::Stdin(inner) => inner, + _ => unreachable!(), + } + } +} + +pub enum IOEntry { + Stdout(Rc>), + Stdin(Rc>), + Stderr(Rc>), +} + +impl IOEntry { + pub fn stdin() -> Self { + Self::Stdin(Rc::new(RefCell::new(std::io::stdin()))) + } + + pub fn stdout() -> Self { + Self::Stdout(Rc::new(RefCell::new(std::io::stdout()))) + } + + pub fn stderr() -> Self { + Self::Stderr(Rc::new(RefCell::new(std::io::stderr()))) + } +} + +#[derive(PartialEq, Eq, Debug, Clone, Hash)] +pub enum IOKey { + Stdin, + Stdout, + Stderr, + Uri(String), +} + +#[derive(Clone, PartialEq)] +#[repr(transparent)] +pub struct Port(IOKey); + +impl Port { + pub fn stdin() -> Self { + Self(IOKey::Stdin) + } + + pub fn stdout() -> Self { + Self(IOKey::Stdout) + } + + pub fn stderr() -> Self { + Self(IOKey::Stderr) + } +} + +impl std::fmt::Debug for Port { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let struct_name = match &self.0 { + IOKey::Stdin => "stdin".to_string(), + IOKey::Stdout => "stdout".to_string(), + IOKey::Stderr => "stderr".to_string(), + IOKey::Uri(uri) => format!("{}", uri), + }; + + f.debug_struct(&struct_name).finish() + } +} + +impl SchemeEqual for Port { + fn is_eq(&self, other: &Port) -> bool { + self == other + } + + fn is_eqv(&self, other: &Port) -> bool { + self == other + } + + fn is_equal(&self, other: &Port) -> bool { + self == other + } +} diff --git a/src/vm/value/vector.rs b/src/vm/value/vector.rs index f70c79b..d241667 100644 --- a/src/vm/value/vector.rs +++ b/src/vm/value/vector.rs @@ -27,6 +27,10 @@ impl Vector { Vector::from(new_vec) } + pub fn slice(&self) -> &[Reference] { + &self.0 + } + #[inline] pub fn len(&self) -> usize { self.0.len()