From 1392801e9fd5d8c5c9a813e1ff00eed6dc2996dc Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Sat, 2 May 2026 23:20:15 +0300 Subject: [PATCH] Value conversions + more useful native functions --- src/compile/block.rs | 4 +- src/compile/syntax/mod.rs | 2 +- src/convert.rs | 115 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 +- src/parse.rs | 2 +- src/vm/machine.rs | 119 +++++++++++++++++++++++++------------- src/vm/mod.rs | 1 + src/vm/module.rs | 29 +++++++--- src/vm/native.rs | 54 +++++++++++++++++ src/vm/prelude.rs | 30 +++++++++- src/vm/value.rs | 93 +++++++++++++++-------------- 11 files changed, 354 insertions(+), 98 deletions(-) create mode 100644 src/convert.rs create mode 100644 src/vm/native.rs diff --git a/src/compile/block.rs b/src/compile/block.rs index 27f05b8..e8d124a 100644 --- a/src/compile/block.rs +++ b/src/compile/block.rs @@ -204,7 +204,7 @@ impl<'a> LocalBlock<'a> { Ok(CompileValue::Stack) } - fn compile_builtin_sub(&mut self, args: &[Expression]) -> Result { + fn compile_builtin_sub(&mut self, _args: &[Expression]) -> Result { todo!() } @@ -239,7 +239,6 @@ impl<'a> LocalBlock<'a> { } fn compile_call(&mut self, call: &CallExpression) -> Result { - eprintln!("compile_call({:?}, {:?})", &call.callee, &call.arguments); match call.callee.as_ref() { Expression::Identifier(identifier) if let Some(builtin) = BuiltinFunction::from_identifier(identifier.as_ref()) => @@ -396,6 +395,7 @@ mod tests { &[ Instruction::PushInteger(U::truncate(1)), Instruction::PushConstant(U::truncate(1)), + Instruction::GetGlobal, Instruction::Compare(false, Comparison::Gt, U::truncate(2)), ] ); diff --git a/src/compile/syntax/mod.rs b/src/compile/syntax/mod.rs index d46d4cd..09a1a5a 100644 --- a/src/compile/syntax/mod.rs +++ b/src/compile/syntax/mod.rs @@ -1,4 +1,4 @@ -use std::{error::Error as StdError, fmt, rc::Rc}; +use std::rc::Rc; use crate::vm::value::{ConsCell, Keyword, Value}; diff --git a/src/convert.rs b/src/convert.rs new file mode 100644 index 0000000..a693f95 --- /dev/null +++ b/src/convert.rs @@ -0,0 +1,115 @@ +use crate::vm::{ + machine::{Machine, MachineError}, + native::NativeFunction, + value::{BytecodeFunction, ConsCell, Keyword, Value}, +}; + +pub enum AnyFunction { + Bytecode(BytecodeFunction), + Native(NativeFunction), +} + +pub trait TryFromValue<'a>: Sized { + fn try_from_value(value: &'a Value) -> Result; + + fn try_from_value_list(mut list_value: &'a Value) -> Result, MachineError> { + let mut output = vec![]; + while !list_value.is_nil() { + let Value::Cons(cons) = list_value else { + return Err(MachineError::InvalidArgument); + }; + let ConsCell(car, cdr) = cons.as_ref(); + output.push(Self::try_from_value(car)?); + list_value = cdr; + } + Ok(output) + } +} + +// From values +macro_rules! impl_primitive_value { + ($($t:ty),+ $(,)?) => { + $( + impl From<$t> for Value { + fn from(value: $t) -> Self { + Self::Integer(value as i64) + } + } + + impl TryFromValue<'_> for $t { + fn try_from_value(value: &Value) -> Result { + match value { + #[allow(irrefutable_let_patterns)] + &Value::Integer(value) if let Ok(value) = value.try_into() => Ok(value), + _ => Err(MachineError::InvalidArgument) + } + } + } + )+ + }; +} + +impl AnyFunction { + pub fn invoke(&self, vm: &mut Machine, args: &[Value]) -> Result { + match self { + Self::Bytecode(bytecode) => Ok(vm.eval_bytecode_call(bytecode.clone(), args).unwrap()), + Self::Native(native) => native.invoke(vm, args), + } + } +} + +impl TryFromValue<'_> for Keyword { + fn try_from_value(value: &Value) -> Result { + match value { + Value::Keyword(value) => Ok(*value), + _ => Err(MachineError::InvalidArgument), + } + } +} + +impl TryFromValue<'_> for bool { + fn try_from_value(value: &Value) -> Result { + match value { + Value::Nil => Ok(false), + Value::Cons(_) => Ok(true), + Value::Boolean(value) => Ok(*value), + Value::Integer(value) => Ok(*value != 0), + Value::Keyword(_) + | Value::Identifier(_) + | Value::OpaqueValue(_) + | Value::NativeFunction(_) + | Value::BytecodeFunction(_) => Ok(true), + } + } +} + +impl TryFromValue<'_> for Value { + fn try_from_value(value: &Value) -> Result { + Ok(value.clone()) + } +} + +impl<'a> TryFromValue<'a> for &'a Value { + fn try_from_value(value: &'a Value) -> Result { + Ok(value) + } +} + +impl TryFromValue<'_> for AnyFunction { + fn try_from_value(value: &Value) -> Result { + match value { + Value::BytecodeFunction(bytecode) => Ok(Self::Bytecode(bytecode.clone())), + Value::NativeFunction(native) => Ok(Self::Native(native.clone())), + _ => Err(MachineError::InvalidArgument), + } + } +} + +impl_primitive_value!(i8, i16, i32, i64); +impl_primitive_value!(u8, u16, u32, u64); + +impl From for Result { + fn from(value: Value) -> Self { + Ok(value) + } +} diff --git a/src/lib.rs b/src/lib.rs index bbb1650..e47ae52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ -#![feature(coverage_attribute, debug_closure_helpers)] +#![feature(coverage_attribute, debug_closure_helpers, unboxed_closures)] pub mod compile; +pub mod convert; pub mod error; pub mod parse; pub mod vm; diff --git a/src/parse.rs b/src/parse.rs index bdade73..dccf0ec 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -159,7 +159,7 @@ fn parse_identifier(input: &str) -> IResult<&str, &str> { fn parse_identifier_or_keyword_or_nil(input: &str) -> IResult<&str, Value> { map(parse_identifier, |ident| match ident { "NIL" | "nil" => Value::Nil, - _ if let Some(keyword) = Keyword::from_str(ident) => Value::Keyword(keyword), + _ if let Some(keyword) = Keyword::parse(ident) => Value::Keyword(keyword), _ => Value::Identifier(ident.into()), }) .parse(input) diff --git a/src/vm/machine.rs b/src/vm/machine.rs index 7d587a6..8ee8d72 100644 --- a/src/vm/machine.rs +++ b/src/vm/machine.rs @@ -5,8 +5,9 @@ use crate::{ vm::{ instruction::{Comparison, ConstantId, Instruction, InstructionError}, module::{Module, ModuleConstant, ModuleRef}, + native::NativeFunction, stack::Stack, - value::{BytecodeFunction, NativeFunction, Value}, + value::{BytecodeFunction, Value}, }, }; @@ -28,6 +29,8 @@ pub enum MachineError { CallStackOverflow, #[error("Unbound identifier: {0:?}")] UnboundIdentifier(Rc), + #[error("Invalid argument provided in a call")] + InvalidArgument, } #[derive(Debug)] @@ -59,7 +62,8 @@ pub struct Machine { #[derive(Debug, Clone, PartialEq)] pub enum ExecutionEvent { - ModuleEntry(ModuleRef), + ModuleExit(ModuleRef), + BytecodeFunctionExit(BytecodeFunction), None, } @@ -75,6 +79,20 @@ impl Default for Machine { } impl Machine { + pub fn set_global>>(&mut self, identifier: S, value: Value) { + self.globals.insert(identifier.into(), value); + } + + pub fn defun_native(&mut self, identifier: S, function: F) + where + S: Into>, + F: Fn(&mut Machine, &[Value]) -> Result + 'static, + { + let identifier = identifier.into(); + let native = NativeFunction::new(identifier.clone(), function); + self.set_global(identifier, Value::NativeFunction(native)); + } + fn pop(&mut self) -> Result { self.value_stack .pop() @@ -87,14 +105,34 @@ impl Machine { .map_err(|_| MachineError::ValueStackOverflow) } + fn enter_bytecode_function( + &mut self, + bytecode: BytecodeFunction, + arguments: Vec, + ) -> Result<(), MachineError> { + let source_ip = self.ip.clone().unwrap(); + let frame = CallFrame { + arguments, + event: ExecutionEvent::BytecodeFunctionExit(bytecode.clone()), + return_address: InstructionPointer { + module: source_ip.module, + address: source_ip.address + 1, + }, + }; + let BytecodeFunction { module, address } = bytecode; + if self.call_stack.push(frame).is_err() { + return Err(MachineError::CallStackOverflow); + } + self.ip = Some(InstructionPointer { module, address }); + Ok(()) + } + fn execute_call(&mut self, count: usize) -> Result<(), MachineError> { enum Callee { Bytecode(BytecodeFunction), Native(NativeFunction), } - let source_ip = self.ip.clone().unwrap(); - let callee = self.pop()?; let callee = match callee { Value::BytecodeFunction(bytecode) => Callee::Bytecode(bytecode), @@ -107,22 +145,11 @@ impl Machine { } match callee { Callee::Bytecode(bytecode) => { - let BytecodeFunction { module, address } = bytecode; - let frame = CallFrame { - arguments, - event: ExecutionEvent::None, - return_address: InstructionPointer { - module: source_ip.module, - address: source_ip.address + 1, - }, - }; - if self.call_stack.push(frame).is_err() { - return Err(MachineError::CallStackOverflow); - } - self.ip = Some(InstructionPointer { module, address }); + self.enter_bytecode_function(bytecode, arguments)?; } Callee::Native(native) => { - let result = native.apply(self, &arguments).unwrap(); + let source_ip = self.ip.clone().unwrap(); + let result = native.invoke(self, &arguments)?; self.push(result)?; self.ip = Some(InstructionPointer { module: source_ip.module, @@ -140,7 +167,7 @@ impl Machine { Ok(frame.event) } else { self.ip = None; - Ok(ExecutionEvent::ModuleEntry(ip.module)) + Ok(ExecutionEvent::ModuleExit(ip.module)) } } @@ -159,7 +186,7 @@ impl Machine { Ok(()) } - fn execute_sub(&mut self, count: usize) -> Result<(), MachineError> { + fn execute_sub(&mut self, _count: usize) -> Result<(), MachineError> { todo!() } @@ -272,10 +299,6 @@ impl Machine { Ok(()) } - pub fn set_global>>(&mut self, identifier: S, value: Value) { - self.globals.insert(identifier.into(), value); - } - pub fn execute_next(&mut self) -> Result { let ip = self.ip.clone().unwrap(); let instruction = ip @@ -344,6 +367,27 @@ impl Machine { Ok(event) } + pub fn eval_bytecode_call( + &mut self, + function: BytecodeFunction, + args: &[Value], + ) -> Result { + let expect = ExecutionEvent::BytecodeFunctionExit(function.clone()); + self.enter_bytecode_function(function, args.into())?; + loop { + let event = match self.execute_next() { + Ok(event) => event, + // TODO rework error handling + Err(_error) => todo!(), + }; + if event == expect { + break; + } + } + let value = self.pop()?; + Ok(value) + } + pub fn load_module(&mut self, module: ModuleRef) -> Result { let entry = module.entry(); let entry_ip = InstructionPointer { @@ -356,7 +400,7 @@ impl Machine { .push(CallFrame { arguments: vec![], return_address: ip, - event: ExecutionEvent::ModuleEntry(module.clone()), + event: ExecutionEvent::ModuleExit(module.clone()), }) .is_err() { @@ -371,7 +415,7 @@ impl Machine { Ok(module) => module, Err(error) => return EvalResult::LoadErr(error), }; - let expect = ExecutionEvent::ModuleEntry(module.clone()); + let expect = ExecutionEvent::ModuleExit(module.clone()); loop { let ip = self.ip.clone(); let event = match self.execute_next() { @@ -434,7 +478,8 @@ mod tests { instruction::{Instruction, U}, machine::{InstructionPointer, Machine}, module::{Module, ModuleBuilder, ModuleConstant, ModuleRef}, - value::{NativeFunction, Value}, + native::NativeFunction, + value::Value, }; fn execute_all( @@ -560,18 +605,14 @@ mod tests { _ => unreachable!(), }, |m| { - m.set_global( - "native", - Value::NativeFunction(NativeFunction::new("native", |_, args| { - assert_eq!(args.len(), 3); - assert_eq!( - &args, - &[Value::Integer(3), Value::Integer(2), Value::Integer(1)] - ); - NATIVE_STATE.store(4321, Ordering::Release); - Ok(Value::Integer(1234)) - })), - ); + m.defun_native("native", |_vm, args| { + assert_eq!( + &args, + &[Value::Integer(3), Value::Integer(2), Value::Integer(1)] + ); + NATIVE_STATE.store(4321, Ordering::Release); + Ok(Value::Integer(1234)) + }); }, ); assert!(m.value_stack.is_empty()); diff --git a/src/vm/mod.rs b/src/vm/mod.rs index 7550456..fd0cc10 100644 --- a/src/vm/mod.rs +++ b/src/vm/mod.rs @@ -2,6 +2,7 @@ pub mod instruction; pub mod loader; pub mod machine; pub mod module; +pub mod native; pub mod pool; pub mod prelude; pub mod stack; diff --git a/src/vm/module.rs b/src/vm/module.rs index 0c9c00d..b1c03a8 100644 --- a/src/vm/module.rs +++ b/src/vm/module.rs @@ -130,10 +130,17 @@ impl Module { print!("{i:4}: {instruction:08x}"); if let Ok(instruction) = Instruction::try_from(*instruction) { - println!(" {instruction:?}"); - } else { - println!(); + print!(" {instruction:?}"); + + if let Instruction::PushConstant(id) = instruction { + if let Some(value) = self.constant(id) { + print!(" [-> {value}]"); + } else { + print!(" UNDEFINED"); + } + } } + println!(); } } } @@ -171,14 +178,22 @@ impl ModuleBuilder { } } +impl fmt::Display for ModuleConstant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Integer(value) => fmt::Display::fmt(value, f), + Self::Identifier(ident) => write!(f, "ident {ident:?}"), + Self::LocalFunction(address) => write!(f, "label {address}"), + } + } +} + #[cfg(test)] mod tests { - use std::collections::HashMap; - use crate::vm::{ instruction::{Instruction, U}, - module::{Module, ModuleConstant}, - value::{Keyword, Value}, + module::Module, + value::Value, }; #[test] diff --git a/src/vm/native.rs b/src/vm/native.rs new file mode 100644 index 0000000..eeb5e29 --- /dev/null +++ b/src/vm/native.rs @@ -0,0 +1,54 @@ +use std::{fmt, rc::Rc}; + +use crate::vm::{ + machine::{Machine, MachineError}, + value::Value, +}; + +#[derive(Clone)] +pub struct NativeFunction { + name: Rc, + inner: Rc Result + 'static>, +} + +impl NativeFunction { + pub fn new(name: S, function: F) -> Self + where + S: Into>, + F: Fn(&mut Machine, &[Value]) -> Result + 'static, + { + Self { + name: name.into(), + inner: Rc::new(function), + } + } + + pub fn invoke( + &self, + machine: &mut Machine, + arguments: &[Value], + ) -> Result { + (self.inner)(machine, arguments) + } +} + +impl PartialEq for NativeFunction { + fn eq(&self, other: &Self) -> bool { + self.name == other.name + } +} + +impl fmt::Debug for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NativeFunction") + .field("name", &self.name) + .field_with("inner", |f| write!(f, "{:p}", self.inner)) + .finish() + } +} + +impl fmt::Display for NativeFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "", self.name, self.inner) + } +} diff --git a/src/vm/prelude.rs b/src/vm/prelude.rs index 4f53f82..16f104d 100644 --- a/src/vm/prelude.rs +++ b/src/vm/prelude.rs @@ -1,3 +1,29 @@ -use crate::vm::machine::Machine; +use crate::{ + convert::{AnyFunction, TryFromValue}, + vm::{ + machine::{Machine, MachineError}, + value::Value, + }, +}; -pub fn load(_machine: &mut Machine) {} +pub fn load(vm: &mut Machine) { + vm.defun_native("map", |vm, args| { + let [f, xs] = args else { + return Err(MachineError::InvalidArgument); + }; + let f = AnyFunction::try_from_value(f)?; + let xs = xs.proper_iter(); + let out = Value::try_list_or_nil(xs.map(|v| f.invoke(vm, &[v?.clone()])))?; + Ok(out) + }); + vm.defun_native("identity", |_, args| { + let [arg] = args else { + return Err(MachineError::InvalidArgument); + }; + Ok(arg.clone()) + }); + vm.defun_native("list", |_, args| { + let out = Value::list_or_nil(args.iter().cloned()); + Ok(out) + }); +} diff --git a/src/vm/value.rs b/src/vm/value.rs index 4440e22..9ff6154 100644 --- a/src/vm/value.rs +++ b/src/vm/value.rs @@ -1,17 +1,6 @@ use std::{any::Any, fmt, rc::Rc}; -use crate::vm::{machine::Machine, module::ModuleRef}; - -#[derive(Debug)] -pub enum Error { - InvalidArgument, -} - -#[derive(Debug, Clone)] -pub struct NativeFunction { - name: Rc, - inner: fn(&mut Machine, &[Value]) -> Result, -} +use crate::vm::{machine::MachineError, module::ModuleRef, native::NativeFunction}; #[derive(Debug, Clone, PartialEq)] pub struct BytecodeFunction { @@ -24,6 +13,10 @@ pub struct OpaqueValue { inner: Rc, } +pub struct ProperListIter<'a> { + head: Option<&'a Value>, +} + #[derive(Debug, Clone, PartialEq)] pub enum Value { // "Expression" values @@ -39,9 +32,28 @@ pub enum Value { OpaqueValue(OpaqueValue), } +impl<'a> Iterator for ProperListIter<'a> { + type Item = Result<&'a Value, MachineError>; + + fn next(&mut self) -> Option { + let head = self.head.take()?; + match head { + Value::Cons(cons) => { + let ConsCell(car, cdr) = cons.as_ref(); + self.head = Some(cdr); + Some(Ok(car)) + } + Value::Nil => None, + _ => Some(Err(MachineError::InvalidArgument)), + } + } +} + impl OpaqueValue { - pub fn cast(&self) -> Result<&T, Error> { - self.inner.downcast_ref().ok_or(Error::InvalidArgument) + pub fn cast(&self) -> Result<&T, MachineError> { + self.inner + .downcast_ref() + .ok_or(MachineError::InvalidArgument) } } @@ -83,7 +95,7 @@ macro_rules! impl_keyword { } impl $name { - pub fn from_str(s: &str) -> Option { + pub fn parse(s: &str) -> Option { match s { $( $s => Some(Self::$variant), @@ -122,14 +134,18 @@ impl_keyword! { pub struct ConsCell(pub Value, pub Value); impl Value { + pub fn proper_iter(&self) -> ProperListIter<'_> { + ProperListIter { head: Some(self) } + } + pub fn is_nil(&self) -> bool { matches!(self, Self::Nil) } - pub fn as_opaque(&self) -> Result<&T, Error> { + pub fn as_opaque(&self) -> Result<&T, MachineError> { match self { Self::OpaqueValue(opaque) => opaque.cast(), - _ => Err(Error::InvalidArgument), + _ => Err(MachineError::InvalidArgument), } } @@ -137,10 +153,25 @@ impl Value { Self::Cons(Rc::new(ConsCell(self, cdr))) } + pub fn try_list_or_nil>>( + items: I, + ) -> Result { + Self::try_list_or_nil_inner(&mut items.into_iter()) + } + pub fn list_or_nil>(items: I) -> Self { Self::list_or_nil_inner(&mut items.into_iter()) } + fn try_list_or_nil_inner>>( + items: &mut I, + ) -> Result { + match items.next() { + Some(value) => Ok(value?.cons(Self::try_list_or_nil_inner(items)?)), + None => Ok(Self::Nil), + } + } + fn list_or_nil_inner>(items: &mut I) -> Self { match items.next() { Some(value) => value.cons(Self::list_or_nil_inner(items)), @@ -170,40 +201,12 @@ impl ConsCell { } } -impl NativeFunction { - pub fn new>>( - name: S, - inner: fn(&mut Machine, &[Value]) -> Result, - ) -> Self { - Self { - name: name.into(), - inner, - } - } - - pub fn apply(&self, machine: &mut Machine, arguments: &[Value]) -> Result { - (self.inner)(machine, arguments) - } -} - -impl PartialEq for NativeFunction { - fn eq(&self, other: &Self) -> bool { - self.name == other.name - } -} - impl fmt::Display for ConsCell { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.fmt_inner(f, true) } } -impl fmt::Display for NativeFunction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "", self.name, self.inner) - } -} - impl fmt::Display for BytecodeFunction { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "", self.module, self.address)