Add basic io, port integration tests
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
(print (+ (car *args*) ":"))
|
||||
(setq handle (fopen (car *args*)))
|
||||
|
||||
(loop
|
||||
(let (data (fread handle))
|
||||
(if data
|
||||
(fwrite *stdout* data)
|
||||
(break)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -589,7 +589,7 @@ impl FunctionBlock {
|
||||
};
|
||||
let local = &self.locals[index];
|
||||
if local.depth == -1 {
|
||||
todo!("Own init");
|
||||
return Err(CompileError::UndefinedLocalReference(name.into()));
|
||||
}
|
||||
|
||||
Ok(Some(index.try_into().unwrap()))
|
||||
|
||||
@@ -133,6 +133,7 @@ mod tests {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Call(CallExpression {
|
||||
callee: Rc::new(Expression::Lambda(LambdaExpression {
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["y".into()],
|
||||
optional_arguments: vec![],
|
||||
@@ -156,23 +157,24 @@ mod tests {
|
||||
let (cx, _v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert_eq!(cx.function_blocks.len(), 2);
|
||||
let root_function = cx.function_blocks[0].to_bytecode();
|
||||
let lambda_function = cx.function_blocks[1].to_bytecode();
|
||||
let root_function = cx.function_blocks[0].to_bytecode().unwrap();
|
||||
let lambda_function = cx.function_blocks[1].to_bytecode().unwrap();
|
||||
|
||||
// lambda
|
||||
assert_eq!(
|
||||
lambda_function.instructions.as_ref(),
|
||||
&[
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::GetUpvalue.into(),
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::Add.into(),
|
||||
2,
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
);
|
||||
assert_eq!(lambda_function.arity, 1);
|
||||
assert_eq!(lambda_function.min_arity, 1);
|
||||
assert_eq!(lambda_function.max_arity, 1);
|
||||
assert_eq!(
|
||||
lambda_function.upvalues.as_ref(),
|
||||
&[UpvalueDef {
|
||||
@@ -214,6 +216,7 @@ mod tests {
|
||||
#[test]
|
||||
fn test_lambda_compile() {
|
||||
let e = Expression::Lambda(LambdaExpression {
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec![],
|
||||
@@ -230,7 +233,7 @@ mod tests {
|
||||
let CompileValue::LocalFunction(index) = v else {
|
||||
panic!("lambda did not compile to a function value")
|
||||
};
|
||||
let lambda_function = cx.function_blocks[index].to_bytecode();
|
||||
let lambda_function = cx.function_blocks[index].to_bytecode().unwrap();
|
||||
assert_eq!(
|
||||
lambda_function.instructions.as_ref(),
|
||||
&[
|
||||
@@ -246,6 +249,7 @@ mod tests {
|
||||
fn test_compile_defun() {
|
||||
let e = Expression::Defun(DefunExpression {
|
||||
name: "my-function".into(),
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec![],
|
||||
@@ -259,8 +263,8 @@ mod tests {
|
||||
let (cx, _v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert_eq!(cx.function_blocks.len(), 2);
|
||||
let root_function = cx.function_blocks[0].to_bytecode();
|
||||
let defun_function = cx.function_blocks[1].to_bytecode();
|
||||
let root_function = cx.function_blocks[0].to_bytecode().unwrap();
|
||||
let defun_function = cx.function_blocks[1].to_bytecode().unwrap();
|
||||
// identifier + function
|
||||
assert_eq!(root_function.constants.len(), 2);
|
||||
assert_eq!(
|
||||
@@ -279,7 +283,8 @@ mod tests {
|
||||
);
|
||||
// inner function
|
||||
assert!(defun_function.constants.is_empty());
|
||||
assert_eq!(defun_function.arity, 1);
|
||||
assert_eq!(defun_function.min_arity, 1);
|
||||
assert_eq!(defun_function.max_arity, 1);
|
||||
assert_eq!(
|
||||
defun_function.instructions.as_ref(),
|
||||
&[
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#![coverage(off)]
|
||||
|
||||
use crate::compile::syntax::ParseError;
|
||||
use crate::{compile::syntax::ParseError, vm::value::IdentifierValue};
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum CompileError {
|
||||
@@ -16,8 +16,10 @@ pub enum CompileError {
|
||||
BreakOutsideOfLoop,
|
||||
#[error("(continue) used outside of a loop")]
|
||||
ContinueOutsideOfLoop,
|
||||
#[error("Cannot emit branch instruction from {0} to {1}")]
|
||||
#[error("cannot emit branch instruction from {0} to {1}")]
|
||||
CannotEmitBranch(usize, usize),
|
||||
#[error("undefined local value reference: {0}")]
|
||||
UndefinedLocalReference(IdentifierValue),
|
||||
}
|
||||
|
||||
impl From<Vec<ParseError>> for CompileError {
|
||||
|
||||
@@ -326,6 +326,7 @@ mod tests {
|
||||
assert_eq!(
|
||||
expr.as_ref(),
|
||||
&Expression::Lambda(LambdaExpression {
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec!["b".into()],
|
||||
|
||||
@@ -124,6 +124,7 @@ impl Expression {
|
||||
Value::Closure(_) => todo!("Error here"),
|
||||
Value::Function(_) => todo!("Error here"),
|
||||
Value::NativeFunction(_) => todo!("Error here"),
|
||||
Value::NativeValue(_) => todo!("Error here"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-5
@@ -678,10 +678,12 @@ mod tests {
|
||||
upvalues: vec![],
|
||||
function: Rc::new(BytecodeFunction {
|
||||
identifier: Some("test-script".into()),
|
||||
docstring: None,
|
||||
instructions: instructions.into(),
|
||||
constants: constants.into(),
|
||||
upvalues: [].into(),
|
||||
arity: 0,
|
||||
min_arity: 0,
|
||||
max_arity: 0,
|
||||
}),
|
||||
};
|
||||
let mut machine = Machine::default();
|
||||
@@ -734,15 +736,16 @@ mod tests {
|
||||
// (lambda (y) (let (x 123) (+ x y)))
|
||||
let lambda_function = Rc::new(BytecodeFunction {
|
||||
identifier: None,
|
||||
docstring: None,
|
||||
instructions: [
|
||||
// x 123
|
||||
Instruction::PushInteger.into(),
|
||||
123,
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
1,
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
1,
|
||||
Instruction::Add.into(),
|
||||
2,
|
||||
Instruction::SetTemp.into(),
|
||||
@@ -753,7 +756,8 @@ mod tests {
|
||||
.into(),
|
||||
constants: [].into(),
|
||||
upvalues: [].into(),
|
||||
arity: 2,
|
||||
min_arity: 1,
|
||||
max_arity: 1,
|
||||
});
|
||||
let (m, r) = eval0(
|
||||
&mut env,
|
||||
@@ -780,6 +784,7 @@ mod tests {
|
||||
// (lambda (x y) (+ x y))
|
||||
let lambda_function = Rc::new(BytecodeFunction {
|
||||
identifier: None,
|
||||
docstring: None,
|
||||
instructions: [
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
@@ -792,7 +797,8 @@ mod tests {
|
||||
.into(),
|
||||
constants: [].into(),
|
||||
upvalues: [].into(),
|
||||
arity: 2,
|
||||
min_arity: 2,
|
||||
max_arity: 2,
|
||||
});
|
||||
let (m, r) = eval0(
|
||||
&mut env,
|
||||
|
||||
@@ -38,6 +38,7 @@ impl MacroExpand for Value {
|
||||
| Self::Closure(_)
|
||||
| Self::Function(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Vector(_)
|
||||
| Self::Unquote(_) => Ok(self.clone()),
|
||||
// | Self::NativeFunction(_) => Ok(self.clone()),
|
||||
|
||||
@@ -40,6 +40,7 @@ pub fn load(env: &mut Environment) {
|
||||
function.docstring()
|
||||
)
|
||||
}
|
||||
Value::NativeValue(_) => "a native value".into(),
|
||||
};
|
||||
Ok(Value::String(explanation.into()))
|
||||
},
|
||||
|
||||
@@ -21,17 +21,6 @@ pub fn load(env: &mut Environment) {
|
||||
};
|
||||
Ok(outcome)
|
||||
});
|
||||
// env.defun_native("apply", |vm, env, args| {
|
||||
// let [f, xs] = args else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let f = AnyFunction::try_from_value(f).map_err(|e| vm.error_at_ip(e))?;
|
||||
// let args = xs
|
||||
// .proper_iter(vm.error_at_ip(MachineErrorKind::InvalidArgument))
|
||||
// .map(|x| x.cloned())
|
||||
// .collect::<Result<Vec<_>, _>>()?;
|
||||
// f.invoke(vm, env, &args[..])
|
||||
// });
|
||||
env.defun_native(
|
||||
"read",
|
||||
"Reads a form from standard input",
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
use crate::{error::MachineError, vm::env::Environment};
|
||||
use crate::{
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
env::Environment,
|
||||
value::convert::{AnyFunction, TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn load(env: &mut Environment) {
|
||||
// env.defun_native("map", |vm, env, args| {
|
||||
@@ -24,6 +30,24 @@ pub fn load(env: &mut Environment) {
|
||||
// }))?;
|
||||
// Ok(out)
|
||||
// });
|
||||
env.defun_native(
|
||||
"apply",
|
||||
"Applies the function to a given argument list",
|
||||
|vm, env, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let args = xs
|
||||
.proper_iter(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "proper list".into(),
|
||||
got: xs.clone(),
|
||||
}))
|
||||
.map(|x| x.cloned())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
f.invoke(vm, env, &args[..])
|
||||
},
|
||||
);
|
||||
env.defun_native("identity", "Returns the argument as is", |_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
fs::File,
|
||||
io::{Read, Write, stdin, stdout},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{NativeObject, NativeValue, StringValue, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Stream {
|
||||
Stdin,
|
||||
Stdout,
|
||||
File(RefCell<Option<File>>),
|
||||
}
|
||||
|
||||
impl NativeObject for Stream {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Stream {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("stream")
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
pub fn read(&self, amount: usize) -> Option<Value> {
|
||||
let mut output = vec![];
|
||||
let mut remaining = amount;
|
||||
let mut buffer = [0; 4096];
|
||||
|
||||
while remaining != 0 {
|
||||
let want = remaining.min(buffer.len());
|
||||
let len = match self {
|
||||
Self::Stdin => stdin().read(&mut buffer[..want]).ok()?,
|
||||
Self::File(cell) => match &mut *cell.borrow_mut() {
|
||||
Some(file) => file.read(&mut buffer[..want]).ok()?,
|
||||
None => return None,
|
||||
},
|
||||
Self::Stdout => return None,
|
||||
};
|
||||
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
output.extend_from_slice(&buffer[..len]);
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
Some(output.into())
|
||||
}
|
||||
|
||||
fn write(&self, bytes: &[u8]) -> Option<Value> {
|
||||
let len = match self {
|
||||
Self::Stdin => return None,
|
||||
Self::Stdout => stdout().write(bytes).ok()?,
|
||||
Self::File(cell) => match &mut *cell.borrow_mut() {
|
||||
Some(file) => file.write(bytes).ok()?,
|
||||
None => return None,
|
||||
},
|
||||
};
|
||||
Some(Value::Number(len.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(env: &mut Environment) {
|
||||
let stdin: Rc<dyn NativeObject> = Rc::new(Stream::Stdin);
|
||||
let stdout: Rc<dyn NativeObject> = Rc::new(Stream::Stdout);
|
||||
|
||||
env.set_global_value("*stdin*", NativeValue::from(stdin));
|
||||
env.set_global_value("*stdout*", NativeValue::from(stdout));
|
||||
|
||||
env.defun_native("fopen", "Opens a file", |_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
match File::open(&*path) {
|
||||
Ok(file) => {
|
||||
let object: Rc<dyn NativeObject> = Rc::new(Stream::File(RefCell::new(Some(file))));
|
||||
Ok(Value::NativeValue(object.into()))
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("TODO: not sure whether to use result/exceptions");
|
||||
eprintln!("{path}: {error}");
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
env.defun_native(
|
||||
"fread",
|
||||
"Reads data as a byte vector from the stream",
|
||||
|_, _, args| {
|
||||
let (stream, amount) = match args {
|
||||
// TODO read to end instead
|
||||
[stream] => (stream.as_native::<Stream>()?, 4096),
|
||||
[stream, amount] => (
|
||||
stream.as_native::<Stream>()?,
|
||||
usize::try_from_value(amount)?,
|
||||
),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
match stream.read(amount) {
|
||||
Some(value) => Ok(value),
|
||||
None => todo!(),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native("fwrite", "Writes data to the stream", |_, _, args| {
|
||||
let [stream, data] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let stream = stream.as_native::<Stream>()?;
|
||||
let result = match data {
|
||||
Value::Vector(value) if let Some(bytes) = value.as_bytes() => stream.write(&bytes[..]),
|
||||
Value::String(value) => stream.write(value.as_bytes()),
|
||||
_ => {
|
||||
return Err(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "byte vector or string".into(),
|
||||
got: data.clone(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Some(value) => Ok(value),
|
||||
None => todo!(),
|
||||
}
|
||||
});
|
||||
env.defun_native("fclose", "Closes the stream", |_, _, args| {
|
||||
let [stream] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let stream = stream.as_native::<Stream>()?;
|
||||
if let Stream::File(cell) = stream.as_ref() {
|
||||
cell.replace(None);
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod convert;
|
||||
mod debug;
|
||||
mod eval;
|
||||
mod functional;
|
||||
mod io;
|
||||
mod math;
|
||||
|
||||
pub(crate) use math::*;
|
||||
@@ -16,4 +17,5 @@ pub fn load(env: &mut Environment) {
|
||||
collections::load(env);
|
||||
convert::load(env);
|
||||
debug::load(env);
|
||||
io::load(env);
|
||||
}
|
||||
|
||||
+65
-3
@@ -1,10 +1,15 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::ValueConversionError,
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
Value,
|
||||
value::{BooleanValue, ConsCell, NumberValue, StringValue, Vector},
|
||||
env::Environment,
|
||||
machine::Machine,
|
||||
value::{
|
||||
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, NativeFunction, NativeValue,
|
||||
NumberValue, StringValue, Vector,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,6 +17,12 @@ pub trait TryFromValue<'a>: Sized {
|
||||
fn try_from_value(value: &'a Value) -> Result<Self, ValueConversionError>;
|
||||
}
|
||||
|
||||
pub enum AnyFunction {
|
||||
Native(NativeFunction),
|
||||
Function(Rc<BytecodeFunction>),
|
||||
Closure(ClosureValue),
|
||||
}
|
||||
|
||||
macro_rules! impl_integer {
|
||||
($($ty:ty : $bits:literal),+ $(,)?) => {
|
||||
$(
|
||||
@@ -103,9 +114,60 @@ impl TryFromValue<'_> for StringValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NativeValue> for Value {
|
||||
fn from(value: NativeValue) -> Self {
|
||||
Self::NativeValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Value {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Value::Vector(Rc::new(Vector::from_iter(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for AnyFunction {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::NativeFunction(function) => Ok(Self::Native(function.clone())),
|
||||
Value::Closure(closure) => Ok(Self::Closure(closure.clone())),
|
||||
Value::Function(function) => Ok(Self::Function(function.clone())),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "function".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyFunction {
|
||||
pub fn invoke(
|
||||
&self,
|
||||
vm: &mut Machine,
|
||||
env: &mut Environment,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
match self {
|
||||
Self::Native(function) => function.invoke(vm, env, args),
|
||||
Self::Closure(closure) => vm
|
||||
.evaluate_closure_args(env, closure.clone(), args)
|
||||
.map_err(|error| error.error),
|
||||
Self::Function(function) => {
|
||||
let closure = ClosureValue {
|
||||
function: function.clone(),
|
||||
upvalues: vec![],
|
||||
};
|
||||
vm.evaluate_closure_args(env, closure, args)
|
||||
.map_err(|error| error.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_integer!(
|
||||
i8: 8,
|
||||
i16: 16,
|
||||
i32: 32,
|
||||
i64: 64
|
||||
i64: 64,
|
||||
usize: 64,
|
||||
);
|
||||
|
||||
@@ -52,6 +52,6 @@ impl_keyword! {
|
||||
Break => "break",
|
||||
Continue => "continue",
|
||||
Declare => "declare",
|
||||
Cons => "cons",
|
||||
// Cons => "cons",
|
||||
}
|
||||
}
|
||||
|
||||
+15
-1
@@ -23,7 +23,7 @@ pub use cons::ConsCell;
|
||||
pub use function::BytecodeFunction;
|
||||
pub use identifier::IdentifierValue;
|
||||
pub use keyword::Keyword;
|
||||
pub use native::{NativeFunction, OpaqueValue};
|
||||
pub use native::{NativeFunction, NativeObject, NativeValue};
|
||||
pub use number::NumberValue;
|
||||
pub use string::StringValue;
|
||||
pub use vector::Vector;
|
||||
@@ -54,6 +54,7 @@ pub enum Value {
|
||||
Function(Rc<BytecodeFunction>),
|
||||
// Native
|
||||
NativeFunction(NativeFunction),
|
||||
NativeValue(NativeValue),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
@@ -100,6 +101,7 @@ impl Value {
|
||||
Self::Vector(_) => Self::Identifier("vector".into()),
|
||||
Self::Keyword(_) => Self::Identifier("keyword".into()),
|
||||
Self::String(_) => Self::Identifier("string".into()),
|
||||
Self::NativeValue(_) => Self::Identifier("native".into()),
|
||||
Self::NativeFunction(_) | Self::Function(_) | Self::Closure(_) => {
|
||||
Self::Identifier("function".into())
|
||||
}
|
||||
@@ -121,6 +123,7 @@ impl Value {
|
||||
| Self::Unquote(_)
|
||||
| Self::Closure(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Function(_) => true,
|
||||
}
|
||||
}
|
||||
@@ -175,6 +178,16 @@ impl Value {
|
||||
.collect::<Result<_, _>>()
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn as_native<T: NativeObject>(&self) -> Result<Rc<T>, ValueConversionError> {
|
||||
match self {
|
||||
Self::NativeValue(value) if let Some(value) = value.cast_rc() => Ok(value),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "native value".into(),
|
||||
got: self.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
@@ -210,6 +223,7 @@ impl fmt::Display for Value {
|
||||
Self::Boolean(value) => fmt::Display::fmt(value, f),
|
||||
Self::Closure(value) => fmt::Display::fmt(value, f),
|
||||
Self::Function(value) => fmt::Display::fmt(value, f),
|
||||
Self::NativeValue(value) => fmt::Display::fmt(value, f),
|
||||
Self::NativeFunction(value) => fmt::Display::fmt(value, f),
|
||||
}
|
||||
}
|
||||
|
||||
+29
-44
@@ -14,11 +14,13 @@ use crate::{
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct OpaqueValue {
|
||||
inner: Rc<dyn Any>,
|
||||
pub trait NativeObject: Any + fmt::Debug + fmt::Display {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NativeValue(Rc<dyn NativeObject>);
|
||||
|
||||
pub type NativeFunctionImpl =
|
||||
Rc<dyn Fn(&mut Machine, &mut Environment, &[Value]) -> Result<Value, MachineError> + 'static>;
|
||||
|
||||
@@ -29,47 +31,6 @@ pub struct NativeFunction {
|
||||
docstring: StringValue,
|
||||
}
|
||||
|
||||
impl OpaqueValue {
|
||||
pub fn cast<T: 'static>(&self) -> Result<&T, MachineError> {
|
||||
todo!()
|
||||
// self.inner
|
||||
// .downcast_ref()
|
||||
// .ok_or(MachineError::InvalidArgument)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for OpaqueValue {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
(&raw const *self.inner.as_ref()).addr().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OpaqueValue {}
|
||||
|
||||
impl fmt::Debug for OpaqueValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("OpaqueValue")
|
||||
.field_with(|f| write!(f, "{:p}", self.inner))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl PartialEq for OpaqueValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<dyn Any>> for OpaqueValue {
|
||||
fn from(value: Rc<dyn Any>) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
impl fmt::Display for OpaqueValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<opaque {:p}>", self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeFunction {
|
||||
pub fn new<S, D, F>(name: S, docstring: D, function: F) -> Self
|
||||
where
|
||||
@@ -136,3 +97,27 @@ impl fmt::Display for NativeFunction {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeValue {
|
||||
pub fn cast_rc<T: NativeObject>(&self) -> Option<Rc<T>> {
|
||||
Rc::downcast(self.0.clone()).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<dyn NativeObject + 'static>> for NativeValue {
|
||||
fn from(value: Rc<dyn NativeObject>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NativeValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NativeValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<native {}>", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +189,15 @@ impl From<i64> for NumberValue {
|
||||
Self::Int(value)
|
||||
}
|
||||
}
|
||||
impl From<usize> for NumberValue {
|
||||
fn from(value: usize) -> Self {
|
||||
if let Ok(value) = i64::try_from(value) {
|
||||
Self::Int(value)
|
||||
} else {
|
||||
todo!("TODO: promote to bigint")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for i8 {
|
||||
type Error = NumberConvertError;
|
||||
@@ -231,6 +240,17 @@ impl TryFrom<NumberValue> for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for usize {
|
||||
type Error = NumberConvertError;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(value),
|
||||
_ => Err(NumberConvertError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NumberValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
|
||||
+63
-55
@@ -1,8 +1,6 @@
|
||||
#![allow(irrefutable_let_patterns)]
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
fmt,
|
||||
fmt, mem,
|
||||
};
|
||||
|
||||
use crate::vm::value::{NumberValue, Value};
|
||||
@@ -38,6 +36,16 @@ impl Vector {
|
||||
pub fn set_value_at(&self, index: usize, value: Value) {
|
||||
self.0.borrow_mut().set_value_at(index, value);
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Option<Ref<'_, [u8]>> {
|
||||
Ref::filter_map(self.borrow(), |r| match r {
|
||||
VectorStorage::I8(values) => {
|
||||
Some(unsafe { mem::transmute::<&[i8], &[u8]>(&values[..]) })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Vector {
|
||||
@@ -300,55 +308,55 @@ impl_primitive_from_iter!(i16, u16 => I16);
|
||||
impl_primitive_from_iter!(i32, u32 => I32);
|
||||
impl_primitive_from_iter!(i64, u64 => I64);
|
||||
|
||||
// #[cfg(test)]
|
||||
// mod tests {
|
||||
// use crate::vm::value::{Value, vector::VectorStorage};
|
||||
//
|
||||
// #[test]
|
||||
// fn test_vector_storage_from_iter() {
|
||||
// let v = VectorStorage::from_iter([1i8, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1u8, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1i16, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1u16, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1i32, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1u32, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1i64, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// let v = VectorStorage::from_iter([1u64, 2, 3, 4, 5, 6, 7]);
|
||||
// assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_vector_unspecialize() {
|
||||
// let mut v = VectorStorage::from_iter([1i8, 2, 3]);
|
||||
// assert_eq!(VectorStorage::I8(vec![1, 2, 3]), v);
|
||||
// v.push(Value::Integer(1234));
|
||||
// assert_eq!(VectorStorage::I16(vec![1, 2, 3, 1234]), v);
|
||||
// v.push(Value::Integer(12341234));
|
||||
// assert_eq!(VectorStorage::I32(vec![1, 2, 3, 1234, 12341234]), v);
|
||||
// v.push(Value::Integer(1234123412341234));
|
||||
// assert_eq!(
|
||||
// VectorStorage::I64(vec![1, 2, 3, 1234, 12341234, 1234123412341234]),
|
||||
// v
|
||||
// );
|
||||
// v.push(Value::String("a".into()));
|
||||
// assert_eq!(
|
||||
// VectorStorage::Any(vec![
|
||||
// Value::Integer(1),
|
||||
// Value::Integer(2),
|
||||
// Value::Integer(3),
|
||||
// Value::Integer(1234),
|
||||
// Value::Integer(12341234),
|
||||
// Value::Integer(1234123412341234),
|
||||
// Value::String("a".into())
|
||||
// ]),
|
||||
// v
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::vm::value::{Value, vector::VectorStorage};
|
||||
|
||||
#[test]
|
||||
fn test_vector_storage_from_iter() {
|
||||
let v = VectorStorage::from_iter([1i8, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u8, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1i16, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u16, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1i32, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u32, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1i64, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u64, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vector_unspecialize() {
|
||||
let mut v = VectorStorage::from_iter([1i8, 2, 3]);
|
||||
assert_eq!(VectorStorage::I8(vec![1, 2, 3]), v);
|
||||
v.push(Value::Number(1234.into()));
|
||||
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 1234]), v);
|
||||
v.push(Value::Number(12341234.into()));
|
||||
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 1234, 12341234]), v);
|
||||
v.push(Value::Number(1234123412341234i64.into()));
|
||||
assert_eq!(
|
||||
VectorStorage::I64(vec![1, 2, 3, 1234, 12341234, 1234123412341234]),
|
||||
v
|
||||
);
|
||||
v.push(Value::String("a".into()));
|
||||
assert_eq!(
|
||||
VectorStorage::Any(vec![
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
Value::Number(1234.into()),
|
||||
Value::Number(12341234.into()),
|
||||
Value::Number(1234123412341234i64.into()),
|
||||
Value::String("a".into())
|
||||
]),
|
||||
v
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+185
-174
@@ -1,174 +1,185 @@
|
||||
// use std::io::{self, BufReader, Read};
|
||||
//
|
||||
// use lysp::{
|
||||
// error::{EvalError, MachineError, MachineErrorKind},
|
||||
// read::{FileReader, read},
|
||||
// vm::{env::Environment, machine::Machine, prelude, value::Value},
|
||||
// };
|
||||
//
|
||||
// struct SliceReader<'a>(&'a [u8]);
|
||||
//
|
||||
// impl Read for SliceReader<'_> {
|
||||
// fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
// let count = self.0.len().min(buf.len());
|
||||
// buf[..count].copy_from_slice(&self.0[..count]);
|
||||
// self.0 = &self.0[count..];
|
||||
// Ok(count)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// fn eval_str_in(code: &str, env: &mut Environment) -> Result<Value, EvalError> {
|
||||
// let mut machine = Machine::default();
|
||||
// let reader = BufReader::new(SliceReader(code.as_bytes()));
|
||||
// let mut reader = FileReader::new(reader);
|
||||
//
|
||||
// let mut last_value = None;
|
||||
// loop {
|
||||
// let value = match read(&mut reader, &mut machine, env) {
|
||||
// Ok(Some(value)) => value,
|
||||
// Ok(None) => break,
|
||||
// Err(error) => panic!("{error}"),
|
||||
// };
|
||||
//
|
||||
// last_value = Some(machine.eval_value(Default::default(), env, value, false));
|
||||
// }
|
||||
//
|
||||
// last_value.expect("no expressions evaluated")
|
||||
// }
|
||||
//
|
||||
// fn eval_str(code: &str) -> Value {
|
||||
// let mut env = Environment::default();
|
||||
// prelude::load(&mut env);
|
||||
// eval_str_in(code, &mut env).expect("expression evaluation failed")
|
||||
// }
|
||||
//
|
||||
// fn eval_str_err(code: &str) -> EvalError {
|
||||
// let mut env = Environment::default();
|
||||
// prelude::load(&mut env);
|
||||
// eval_str_in(code, &mut env).expect_err("expression was expected to fail")
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_math() {
|
||||
// // math
|
||||
// assert_eq!(eval_str("(+ 1 2 3)"), Value::Integer(6));
|
||||
// assert_eq!(eval_str("(- 3 2 1)"), Value::Integer(0));
|
||||
// assert_eq!(eval_str("(* 2 3 4)"), Value::Integer(24));
|
||||
// assert_eq!(eval_str("(/ 16 4 2)"), Value::Integer(2));
|
||||
// assert_eq!(eval_str("(% 35 16)"), Value::Integer(3));
|
||||
// assert_eq!(eval_str("(| 1 2 4)"), Value::Integer(7));
|
||||
// assert_eq!(eval_str("(& 1 2 4)"), Value::Integer(0));
|
||||
// assert_eq!(eval_str("(& 1 3 7)"), Value::Integer(1));
|
||||
// assert_eq!(eval_str("(^ 1 3 8)"), Value::Integer(10));
|
||||
// // comparison
|
||||
// assert_eq!(eval_str("(< 1 2 3 4 5)"), Value::Boolean(true));
|
||||
// assert_eq!(eval_str("(< 1 2 3 3 4 5)"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(> 1 2 3 4 5)"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(>= 5 5 4 3 2 1)"), Value::Boolean(true));
|
||||
// assert_eq!(eval_str("(> 5 5 4 3 2 1)"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(<= 1 2 3 3 3 4)"), Value::Boolean(true));
|
||||
// assert_eq!(eval_str("(/= 1 2 3 4)"), Value::Boolean(true));
|
||||
// assert_eq!(eval_str("(/= 1 2 2 4)"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(= 1 2 3 4)"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(= 1 1 1 1)"), Value::Boolean(true));
|
||||
// // logic
|
||||
// assert_eq!(eval_str("(&& 1 0)"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(&& #t 1 \"yes\")"), Value::Boolean(true));
|
||||
// assert_eq!(eval_str("(|| #f NIL \"\" ())"), Value::Boolean(false));
|
||||
// assert_eq!(eval_str("(|| #f '(1 2 3) \"\" ())"), Value::Boolean(true));
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_abort() {
|
||||
// let err = eval_str_err("(assert (= 1 (+ 1 1)))");
|
||||
// let EvalError::Machine(MachineError {
|
||||
// error: MachineErrorKind::Aborted(message),
|
||||
// ..
|
||||
// }) = err
|
||||
// else {
|
||||
// panic!("Invalid error returned")
|
||||
// };
|
||||
// assert_eq!(&message, "<undefined>: assertion failed: (= 1 (+ 1 1))");
|
||||
//
|
||||
// let err = eval_str_err("(abort 1234)");
|
||||
// let EvalError::Machine(MachineError {
|
||||
// error: MachineErrorKind::Aborted(message),
|
||||
// ..
|
||||
// }) = err
|
||||
// else {
|
||||
// panic!("Invalid error returned")
|
||||
// };
|
||||
// assert_eq!(&message, "1234");
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_lambda() {
|
||||
// assert_eq!(eval_str("((lambda (x) (+ x 1)) 1)"), Value::Integer(2));
|
||||
// assert_eq!(
|
||||
// eval_str("((lambda (x) (+ ((lambda () 2)) 1)) 1)"),
|
||||
// Value::Integer(3)
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_math_behaves_the_same_inside_apply() {
|
||||
// assert_eq!(eval_str("(apply + '(1 2 3 4))"), eval_str("(+ 1 2 3 4)"));
|
||||
// assert_eq!(eval_str("(apply * '(1 2 3 4))"), eval_str("(* 1 2 3 4)"));
|
||||
// assert_eq!(eval_str("(apply - '(1 2 3 4))"), eval_str("(- 1 2 3 4)"));
|
||||
// assert_eq!(eval_str("(apply / '(100 25 2))"), eval_str("(/ 100 25 2)"));
|
||||
// assert_eq!(eval_str("(apply % '(90 37))"), eval_str("(% 90 37)"));
|
||||
// assert_eq!(eval_str("(apply | '(1 2 4))"), eval_str("(| 1 2 4)"));
|
||||
// assert_eq!(eval_str("(apply & '(1 3 7))"), eval_str("(& 1 3 7)"));
|
||||
// assert_eq!(eval_str("(apply ^ '(1 3 7))"), eval_str("(^ 1 3 7)"));
|
||||
// assert_eq!(eval_str("(apply < '(1 2 3 4))"), eval_str("(< 1 2 3 4)"));
|
||||
// assert_eq!(eval_str("(apply > '(1 2 3 4))"), eval_str("(> 1 2 3 4)"));
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_setq() {
|
||||
// assert_eq!(eval_str("(setq a 1234) a\n"), Value::Integer(1234));
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_let() {
|
||||
// // Should fail
|
||||
// eval_str_err("(let (a 1234 b (+ a 1)) NIL)");
|
||||
//
|
||||
// assert_eq!(
|
||||
// eval_str("(let (a 1234 b 4321) (+ a b))"),
|
||||
// Value::Integer(5555)
|
||||
// );
|
||||
// assert_eq!(
|
||||
// eval_str("(let* (a 1234 b (+ a 4321)) (+ b 4444))"),
|
||||
// Value::Integer(9999)
|
||||
// );
|
||||
//
|
||||
// // Nested let
|
||||
// assert_eq!(
|
||||
// eval_str("(let (a 1234) (let (b 4321) (+ a b)))"),
|
||||
// Value::Integer(5555)
|
||||
// );
|
||||
// // Doesn't shadow
|
||||
// assert_eq!(
|
||||
// eval_str("(let (a 1234) (let (a 9999 b (+ a 4321)) b))"),
|
||||
// Value::Integer(5555)
|
||||
// );
|
||||
// // Does shadow
|
||||
// assert_eq!(
|
||||
// eval_str("(let (a 1234) (let* (a 9999 b (+ a 4321)) b))"),
|
||||
// Value::Integer(14320)
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_macro() {
|
||||
// assert_eq!(
|
||||
// eval_str("(defmacro stringify (x) (cons 'list `,x)) (stringify (1 2 3 4))"),
|
||||
// Value::list_or_nil([
|
||||
// Value::Integer(1),
|
||||
// Value::Integer(2),
|
||||
// Value::Integer(3),
|
||||
// Value::Integer(4)
|
||||
// ])
|
||||
// );
|
||||
// }
|
||||
use std::io::{self, BufReader, Read};
|
||||
|
||||
use lysp::{
|
||||
error::MachineErrorAt,
|
||||
read::{FileReader, read},
|
||||
vm::{env::Environment, machine::Machine, prelude, value::Value},
|
||||
};
|
||||
|
||||
struct SliceReader<'a>(&'a [u8]);
|
||||
|
||||
impl Read for SliceReader<'_> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let count = self.0.len().min(buf.len());
|
||||
buf[..count].copy_from_slice(&self.0[..count]);
|
||||
self.0 = &self.0[count..];
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str_in(code: &str, env: &mut Environment) -> Result<Value, MachineErrorAt> {
|
||||
let mut machine = Machine::default();
|
||||
let reader = BufReader::new(SliceReader(code.as_bytes()));
|
||||
let mut reader = FileReader::new(reader);
|
||||
|
||||
let mut last_value = None;
|
||||
loop {
|
||||
let value = match read(&mut reader, &mut machine, env) {
|
||||
Ok(Some(value)) => value,
|
||||
Ok(None) => break,
|
||||
Err(error) => panic!("{error}"),
|
||||
};
|
||||
|
||||
last_value = Some(machine.evaluate_value(Default::default(), None, env, value));
|
||||
}
|
||||
|
||||
last_value.expect("no expressions evaluated")
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str(code: &str) -> Value {
|
||||
let mut env = Environment::default();
|
||||
prelude::load(&mut env);
|
||||
eval_str_in(code, &mut env).expect("expression evaluation failed")
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str_err(code: &str) -> MachineErrorAt {
|
||||
let mut env = Environment::default();
|
||||
prelude::load(&mut env);
|
||||
eval_str_in(code, &mut env).expect_err("expression was expected to fail")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math() {
|
||||
// math
|
||||
assert_eq!(eval_str("(+ 1 2 3)"), Value::Number(6.into()));
|
||||
assert_eq!(eval_str("(- 3 2 1)"), Value::Number(0.into()));
|
||||
assert_eq!(eval_str("(* 2 3 4)"), Value::Number(24.into()));
|
||||
assert_eq!(eval_str("(/ 16 4 2)"), Value::Number(2.into()));
|
||||
assert_eq!(eval_str("(% 35 16)"), Value::Number(3.into()));
|
||||
// TODO
|
||||
// assert_eq!(eval_str("(| 1 2 4)"), Value::Number(7.into()));
|
||||
// assert_eq!(eval_str("(& 1 2 4)"), Value::Number(0.into()));
|
||||
// assert_eq!(eval_str("(& 1 3 7)"), Value::Number(1.into()));
|
||||
// assert_eq!(eval_str("(^ 1 3 8)"), Value::Number(10.into()));
|
||||
// comparison
|
||||
assert_eq!(eval_str("(< 1 2 3 4 5)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(< 1 2 3 3 4 5)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(> 1 2 3 4 5)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(>= 5 5 4 3 2 1)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(> 5 5 4 3 2 1)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(<= 1 2 3 3 3 4)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(/= 1 2 3 4)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(/= 1 2 2 4)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(= 1 2 3 4)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(= 1 1 1 1)"), Value::Boolean(true.into()));
|
||||
// logic
|
||||
// TODO
|
||||
// assert_eq!(eval_str("(&& 1 0)"), Value::Boolean(false.into()));
|
||||
// assert_eq!(eval_str("(&& #t 1 \"yes\")"), Value::Boolean(true.into()));
|
||||
// assert_eq!(
|
||||
// eval_str("(|| #f NIL \"\" ())"),
|
||||
// Value::Boolean(false.into())
|
||||
// );
|
||||
// assert_eq!(
|
||||
// eval_str("(|| #f '(1 2 3) \"\" ())"),
|
||||
// Value::Boolean(true.into())
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_abort() {
|
||||
// let err = eval_str_err("(assert (= 1 (+ 1 1)))");
|
||||
// let EvalError::Machine(MachineError {
|
||||
// error: MachineErrorKind::Aborted(message),
|
||||
// ..
|
||||
// }) = err
|
||||
// else {
|
||||
// panic!("Invalid error returned")
|
||||
// };
|
||||
// assert_eq!(&message, "<undefined>: assertion failed: (= 1 (+ 1 1))");
|
||||
|
||||
// let err = eval_str_err("(abort 1234)");
|
||||
// let EvalError::Machine(MachineError {
|
||||
// error: MachineErrorKind::Aborted(message),
|
||||
// ..
|
||||
// }) = err
|
||||
// else {
|
||||
// panic!("Invalid error returned")
|
||||
// };
|
||||
// assert_eq!(&message, "1234");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda() {
|
||||
assert_eq!(
|
||||
eval_str("((lambda (x) (+ x 1)) 1)"),
|
||||
Value::Number(2.into())
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("((lambda (x) (+ ((lambda () 2)) 1)) 1)"),
|
||||
Value::Number(3.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_behaves_the_same_inside_apply() {
|
||||
assert_eq!(eval_str("(apply + '(1 2 3 4))"), eval_str("(+ 1 2 3 4)"));
|
||||
assert_eq!(eval_str("(apply * '(1 2 3 4))"), eval_str("(* 1 2 3 4)"));
|
||||
assert_eq!(eval_str("(apply - '(1 2 3 4))"), eval_str("(- 1 2 3 4)"));
|
||||
assert_eq!(eval_str("(apply / '(100 25 2))"), eval_str("(/ 100 25 2)"));
|
||||
assert_eq!(eval_str("(apply % '(90 37))"), eval_str("(% 90 37)"));
|
||||
// TODO
|
||||
// assert_eq!(eval_str("(apply | '(1 2 4))"), eval_str("(| 1 2 4)"));
|
||||
// assert_eq!(eval_str("(apply & '(1 3 7))"), eval_str("(& 1 3 7)"));
|
||||
// assert_eq!(eval_str("(apply ^ '(1 3 7))"), eval_str("(^ 1 3 7)"));
|
||||
// assert_eq!(eval_str("(apply < '(1 2 3 4))"), eval_str("(< 1 2 3 4)"));
|
||||
// assert_eq!(eval_str("(apply > '(1 2 3 4))"), eval_str("(> 1 2 3 4)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setq() {
|
||||
assert_eq!(eval_str("(setq a 1234) a\n"), Value::Number(1234.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_let() {
|
||||
// Should fail
|
||||
eval_str_err("(let (a 1234 b (+ a 1)) NIL)");
|
||||
eval_str_err("(let (a 1234) (let (a 9999 b (+ a 4321)) b))");
|
||||
|
||||
assert_eq!(
|
||||
eval_str("(let (a 1234 b 4321) (+ a b))"),
|
||||
Value::Number(5555.into())
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("(let* (a 1234 b (+ a 4321)) (+ b 4444))"),
|
||||
Value::Number(9999.into())
|
||||
);
|
||||
|
||||
// Nested let
|
||||
assert_eq!(
|
||||
eval_str("(let (a 1234) (let (b 4321) (+ a b)))"),
|
||||
Value::Number(5555.into())
|
||||
);
|
||||
// Does shadow
|
||||
assert_eq!(
|
||||
eval_str("(let (a 1234) (let* (a 9999 b (+ a 4321)) b))"),
|
||||
Value::Number(14320.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro() {
|
||||
assert_eq!(
|
||||
eval_str("(defmacro stringify (x) (cons 'list `,x)) (stringify (1 2 3 4))"),
|
||||
Value::list_or_nil([
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
Value::Number(4.into())
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user