Add basic io, port integration tests

This commit is contained in:
2026-05-21 11:36:19 +03:00
parent 2b02d382c2
commit 777c10d79c
20 changed files with 602 additions and 307 deletions
+11
View File
@@ -0,0 +1,11 @@
(print (+ (car *args*) ":"))
(setq handle (fopen (car *args*)))
(loop
(let (data (fread handle))
(if data
(fwrite *stdout* data)
(break)
)
)
)
+1 -1
View File
@@ -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()))
+14 -9
View File
@@ -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(),
&[
+4 -2
View File
@@ -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 {
+1
View File
@@ -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()],
+1
View File
@@ -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
View File
@@ -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,
+1
View File
@@ -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()),
+1
View File
@@ -40,6 +40,7 @@ pub fn load(env: &mut Environment) {
function.docstring()
)
}
Value::NativeValue(_) => "a native value".into(),
};
Ok(Value::String(explanation.into()))
},
-11
View File
@@ -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",
+25 -1
View File
@@ -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);
+152
View File
@@ -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)
});
}
+2
View File
@@ -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
View File
@@ -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,
);
+1 -1
View File
@@ -52,6 +52,6 @@ impl_keyword! {
Break => "break",
Continue => "continue",
Declare => "declare",
Cons => "cons",
// Cons => "cons",
}
}
+15 -1
View File
@@ -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
View File
@@ -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)
}
}
+20
View File
@@ -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
View File
@@ -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
View File
@@ -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())
])
);
}