Value conversions + more useful native functions

This commit is contained in:
2026-05-02 23:20:15 +03:00
parent e3ec0884d2
commit 1392801e9f
11 changed files with 354 additions and 98 deletions
+2 -2
View File
@@ -204,7 +204,7 @@ impl<'a> LocalBlock<'a> {
Ok(CompileValue::Stack)
}
fn compile_builtin_sub(&mut self, args: &[Expression]) -> Result<CompileValue, CompileError> {
fn compile_builtin_sub(&mut self, _args: &[Expression]) -> Result<CompileValue, CompileError> {
todo!()
}
@@ -239,7 +239,6 @@ impl<'a> LocalBlock<'a> {
}
fn compile_call(&mut self, call: &CallExpression) -> Result<CompileValue, CompileError> {
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)),
]
);
+1 -1
View File
@@ -1,4 +1,4 @@
use std::{error::Error as StdError, fmt, rc::Rc};
use std::rc::Rc;
use crate::vm::value::{ConsCell, Keyword, Value};
+115
View File
@@ -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<Self, MachineError>;
fn try_from_value_list(mut list_value: &'a Value) -> Result<Vec<Self>, 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<Self, MachineError> {
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<Value, MachineError> {
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<Self, MachineError> {
match value {
Value::Keyword(value) => Ok(*value),
_ => Err(MachineError::InvalidArgument),
}
}
}
impl TryFromValue<'_> for bool {
fn try_from_value(value: &Value) -> Result<Self, MachineError> {
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<Self, MachineError> {
Ok(value.clone())
}
}
impl<'a> TryFromValue<'a> for &'a Value {
fn try_from_value(value: &'a Value) -> Result<Self, MachineError> {
Ok(value)
}
}
impl TryFromValue<'_> for AnyFunction {
fn try_from_value(value: &Value) -> Result<Self, MachineError> {
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<Value> for Result<Value, MachineError> {
fn from(value: Value) -> Self {
Ok(value)
}
}
+2 -1
View File
@@ -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;
+1 -1
View File
@@ -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)
+80 -39
View File
@@ -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<str>),
#[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<S: Into<Rc<str>>>(&mut self, identifier: S, value: Value) {
self.globals.insert(identifier.into(), value);
}
pub fn defun_native<S, F>(&mut self, identifier: S, function: F)
where
S: Into<Rc<str>>,
F: Fn(&mut Machine, &[Value]) -> Result<Value, MachineError> + 'static,
{
let identifier = identifier.into();
let native = NativeFunction::new(identifier.clone(), function);
self.set_global(identifier, Value::NativeFunction(native));
}
fn pop(&mut self) -> Result<Value, MachineError> {
self.value_stack
.pop()
@@ -87,14 +105,34 @@ impl Machine {
.map_err(|_| MachineError::ValueStackOverflow)
}
fn enter_bytecode_function(
&mut self,
bytecode: BytecodeFunction,
arguments: Vec<Value>,
) -> 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<S: Into<Rc<str>>>(&mut self, identifier: S, value: Value) {
self.globals.insert(identifier.into(), value);
}
pub fn execute_next(&mut self) -> Result<ExecutionEvent, MachineError> {
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<Value, MachineError> {
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<ModuleRef, MachineError> {
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<F: Fn(u32, &mut ModuleBuilder), G: FnOnce(&mut Machine)>(
@@ -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());
+1
View File
@@ -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;
+22 -7
View File
@@ -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]
+54
View File
@@ -0,0 +1,54 @@
use std::{fmt, rc::Rc};
use crate::vm::{
machine::{Machine, MachineError},
value::Value,
};
#[derive(Clone)]
pub struct NativeFunction {
name: Rc<str>,
inner: Rc<dyn Fn(&mut Machine, &[Value]) -> Result<Value, MachineError> + 'static>,
}
impl NativeFunction {
pub fn new<S, F>(name: S, function: F) -> Self
where
S: Into<Rc<str>>,
F: Fn(&mut Machine, &[Value]) -> Result<Value, MachineError> + 'static,
{
Self {
name: name.into(),
inner: Rc::new(function),
}
}
pub fn invoke(
&self,
machine: &mut Machine,
arguments: &[Value],
) -> Result<Value, MachineError> {
(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, "<native {:?} {:p}>", self.name, self.inner)
}
}
+28 -2
View File
@@ -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)
});
}
+48 -45
View File
@@ -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<str>,
inner: fn(&mut Machine, &[Value]) -> Result<Value, Error>,
}
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<dyn Any>,
}
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<Self::Item> {
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<T: 'static>(&self) -> Result<&T, Error> {
self.inner.downcast_ref().ok_or(Error::InvalidArgument)
pub fn cast<T: 'static>(&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<Self> {
pub fn parse(s: &str) -> Option<Self> {
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<T: 'static>(&self) -> Result<&T, Error> {
pub fn as_opaque<T: 'static>(&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<I: IntoIterator<Item = Result<Self, MachineError>>>(
items: I,
) -> Result<Self, MachineError> {
Self::try_list_or_nil_inner(&mut items.into_iter())
}
pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self {
Self::list_or_nil_inner(&mut items.into_iter())
}
fn try_list_or_nil_inner<I: Iterator<Item = Result<Self, MachineError>>>(
items: &mut I,
) -> Result<Self, MachineError> {
match items.next() {
Some(value) => Ok(value?.cons(Self::try_list_or_nil_inner(items)?)),
None => Ok(Self::Nil),
}
}
fn list_or_nil_inner<I: Iterator<Item = Self>>(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<S: Into<Rc<str>>>(
name: S,
inner: fn(&mut Machine, &[Value]) -> Result<Value, Error>,
) -> Self {
Self {
name: name.into(),
inner,
}
}
pub fn apply(&self, machine: &mut Machine, arguments: &[Value]) -> Result<Value, Error> {
(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, "<native {:?} {:p}>", self.name, self.inner)
}
}
impl fmt::Display for BytecodeFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<bytecode {:p}:{}>", self.module, self.address)