feat(lisp): rework foreign data system
This commit is contained in:
parent
61b5437561
commit
4b227fdd28
@ -12,7 +12,6 @@ fn main() {
|
|||||||
"pow",
|
"pow",
|
||||||
"(pow 2 10)",
|
"(pow 2 10)",
|
||||||
"(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))",
|
"(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))",
|
||||||
"(let '((a . (vec3 1 2 3)) (b . (vec3 4 5 6))) (vec3-dot (vec3-norm (vec3-add a b)) a))",
|
|
||||||
"(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))",
|
"(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))",
|
||||||
"(do-n-times (lambda () (print 'hello)) 5)",
|
"(do-n-times (lambda () (print 'hello)) 5)",
|
||||||
"(progn (print 'hello) (print 'world))",
|
"(progn (print 'hello) (print 'world))",
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::{expression::Expression, prelude::mk_prelude, vec::mk_vec3};
|
use super::{expression::Expression, prelude::mk_prelude};
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
@ -124,11 +124,10 @@ impl<'a> Environment<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Environment<'_> {
|
impl Default for Environment<'_> {
|
||||||
/// Get the default prelude+vec3 layer
|
/// Get the default prelude layer
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
let mut d = EnvironmentLayer::new();
|
let mut d = EnvironmentLayer::new();
|
||||||
mk_prelude(&mut d);
|
mk_prelude(&mut d);
|
||||||
mk_vec3(&mut d);
|
|
||||||
Environment {
|
Environment {
|
||||||
layer: d,
|
layer: d,
|
||||||
outer: None,
|
outer: None,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ pub enum EvalError {
|
|||||||
ArgumentError(String),
|
ArgumentError(String),
|
||||||
TypeError(String),
|
TypeError(String),
|
||||||
NotASymbol(Expression),
|
NotASymbol(Expression),
|
||||||
|
RuntimeError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for EvalError {
|
impl Display for EvalError {
|
||||||
@ -24,6 +25,7 @@ impl Display for EvalError {
|
|||||||
EvalError::ArgumentError(s) => write!(f, "Argument error: {}", s),
|
EvalError::ArgumentError(s) => write!(f, "Argument error: {}", s),
|
||||||
EvalError::TypeError(s) => write!(f, "Type error: {}", s),
|
EvalError::TypeError(s) => write!(f, "Type error: {}", s),
|
||||||
EvalError::NotASymbol(e) => write!(f, "Expression {} is not a symbol", e),
|
EvalError::NotASymbol(e) => write!(f, "Expression {} is not a symbol", e),
|
||||||
|
EvalError::RuntimeError(s) => write!(f, "Runtime error: {}", s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
|
use std::any::Any;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
use std::ops::Deref;
|
||||||
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
use as_any::AsAny;
|
use as_any::AsAny;
|
||||||
|
|
||||||
@ -9,51 +12,110 @@ use super::eval::EvalError;
|
|||||||
|
|
||||||
/// A trait for foreign data types that can be used in lisp expressions.
|
/// A trait for foreign data types that can be used in lisp expressions.
|
||||||
/// Note: This trait requires explicit implementation of:
|
/// Note: This trait requires explicit implementation of:
|
||||||
/// - partial_cmp
|
/// - partial_cmp_impl
|
||||||
/// - clone_data
|
/// - clone_impl
|
||||||
/// - eq
|
/// - eq_impl
|
||||||
/// To avoid a derive cycle.
|
/// - as_any_box
|
||||||
|
/// to ensure object safety.
|
||||||
pub trait ForeignData: Debug + Display + AsAny {
|
pub trait ForeignData: Debug + Display + AsAny {
|
||||||
fn partial_cmp(&self, other: &dyn ForeignData) -> Option<std::cmp::Ordering>;
|
fn partial_cmp_impl(&self, other: &dyn ForeignData) -> Option<std::cmp::Ordering>;
|
||||||
fn clone_data(&self) -> Box<dyn ForeignData>;
|
fn clone_impl(&self) -> Box<dyn ForeignData>;
|
||||||
fn eq(&self, other: &dyn ForeignData) -> bool;
|
fn eq_impl(&self, other: &dyn ForeignData) -> bool;
|
||||||
|
fn as_any_box(self: Box<Self>) -> Box<dyn Any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl<T: Debug + Display + AsAny + PartialOrd + PartialEq + Clone + 'static> ForeignData for T {
|
||||||
/// A Wrapper struct for foreign data types injected in expressions.
|
fn partial_cmp_impl(&self, other: &dyn ForeignData) -> Option<std::cmp::Ordering> {
|
||||||
pub struct ForeignDataWrapper {
|
if let Some(other) = other.as_any().downcast_ref::<T>() {
|
||||||
/// The actual foreign data.
|
self.partial_cmp(other)
|
||||||
pub data: Box<dyn ForeignData>,
|
} else {
|
||||||
}
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ForeignDataWrapper {
|
fn clone_impl(&self) -> Box<dyn ForeignData> {
|
||||||
/// Create a new ForeignDataWrapper from a ForeignData trait object.
|
Box::new(self.clone())
|
||||||
pub fn new(data: Box<dyn ForeignData>) -> Self {
|
}
|
||||||
ForeignDataWrapper { data }
|
|
||||||
|
fn eq_impl(&self, other: &dyn ForeignData) -> bool {
|
||||||
|
if let Some(other) = other.as_any().downcast_ref::<T>() {
|
||||||
|
self.eq(other)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_any_box(self: Box<Self>) -> Box<dyn Any> {
|
||||||
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for ForeignDataWrapper {
|
/// A wrapper struct around any foreign data type. This struct is used to convert
|
||||||
|
/// any T implementing ForeignData to an Expression and vice versa.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ForeignDataWrapper<T: ForeignData>(pub Box<T>);
|
||||||
|
impl<T: ForeignData> ForeignDataWrapper<T> {
|
||||||
|
/// Create a new ForeignDataWrapper from an object implementing ForeignData.
|
||||||
|
pub fn new(data: T) -> Self {
|
||||||
|
ForeignDataWrapper(Box::new(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ForeignData> Deref for ForeignDataWrapper<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ForeignData> DerefMut for ForeignDataWrapper<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// A Store struct for foreign data types injected in expressions.
|
||||||
|
pub struct ForeignDataStore {
|
||||||
|
/// The actual foreign data.
|
||||||
|
data: Box<dyn ForeignData>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The ForeignDataStore struct is used to store any foreign data type in an Expression
|
||||||
|
/// and cannot be constructed outside of this scope.
|
||||||
|
impl ForeignDataStore {
|
||||||
|
/// Create a new ForeignDataStore from a ForeignData trait object.
|
||||||
|
fn new(data: Box<dyn ForeignData>) -> Self {
|
||||||
|
ForeignDataStore { data }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the contained box as an Any-Box with type info of the actual data.
|
||||||
|
fn as_any_box(self) -> Box<dyn Any> {
|
||||||
|
self.data.as_any_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for ForeignDataStore {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
ForeignDataWrapper {
|
ForeignDataStore {
|
||||||
data: self.data.clone_data(),
|
data: self.data.clone_impl(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for ForeignDataWrapper {
|
impl PartialEq for ForeignDataStore {
|
||||||
fn eq(&self, other: &Self) -> bool {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
self.data.eq(other.data.as_ref())
|
self.data.eq_impl(other.data.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialOrd for ForeignDataWrapper {
|
impl PartialOrd for ForeignDataStore {
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
self.data.partial_cmp(other.data.as_ref())
|
self.data.partial_cmp_impl(other.data.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ForeignDataWrapper {
|
impl Display for ForeignDataStore {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.data)
|
write!(f, "{}", self.data)
|
||||||
}
|
}
|
||||||
@ -72,7 +134,7 @@ pub enum Expression {
|
|||||||
body: Box<Expression>,
|
body: Box<Expression>,
|
||||||
},
|
},
|
||||||
/// A foreign data expression.
|
/// A foreign data expression.
|
||||||
ForeignExpression(ForeignDataWrapper),
|
ForeignExpression(ForeignDataStore),
|
||||||
/// A Quoted expression.
|
/// A Quoted expression.
|
||||||
Quote(Box<Expression>),
|
Quote(Box<Expression>),
|
||||||
/// A symbol.
|
/// A symbol.
|
||||||
@ -89,6 +151,29 @@ pub enum Expression {
|
|||||||
Nil,
|
Nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: ForeignData> From<ForeignDataWrapper<T>> for Expression {
|
||||||
|
fn from(value: ForeignDataWrapper<T>) -> Expression {
|
||||||
|
Expression::ForeignExpression(ForeignDataStore::new(value.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: ForeignData> TryFrom<Expression> for ForeignDataWrapper<T> {
|
||||||
|
type Error = EvalError;
|
||||||
|
fn try_from(value: Expression) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
Expression::ForeignExpression(f) => match f.as_any_box().downcast::<T>() {
|
||||||
|
Ok(data) => Ok(ForeignDataWrapper(data)),
|
||||||
|
Err(_) => Err(EvalError::TypeError(
|
||||||
|
"Expression is not a ForeignDataWrapper".to_string(),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
_ => Err(EvalError::TypeError(
|
||||||
|
"Expression is not a ForeignDataWrapper".to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<fn(&Environment, Expression) -> Result<Expression, EvalError>> for Expression {
|
impl From<fn(&Environment, Expression) -> Result<Expression, EvalError>> for Expression {
|
||||||
fn from(f: fn(&Environment, Expression) -> Result<Expression, EvalError>) -> Self {
|
fn from(f: fn(&Environment, Expression) -> Result<Expression, EvalError>) -> Self {
|
||||||
Expression::Function(f)
|
Expression::Function(f)
|
||||||
|
|||||||
@ -2,7 +2,6 @@ pub mod environment;
|
|||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod expression;
|
pub mod expression;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
pub mod vec;
|
|
||||||
|
|
||||||
pub use environment::Environment;
|
pub use environment::Environment;
|
||||||
pub use eval::eval;
|
pub use eval::eval;
|
||||||
|
|||||||
141
src/lisp/vec.rs
141
src/lisp/vec.rs
@ -1,141 +0,0 @@
|
|||||||
use std::fmt::Display;
|
|
||||||
|
|
||||||
use super::{
|
|
||||||
environment::{Environment, EnvironmentLayer},
|
|
||||||
eval::{eval, EvalError},
|
|
||||||
expression::ForeignData,
|
|
||||||
expression::{Expression, ForeignDataWrapper},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
/// A simple 3d vector.
|
|
||||||
struct Vec3 {
|
|
||||||
x: f64,
|
|
||||||
y: f64,
|
|
||||||
z: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for Vec3 {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "(vec3 {} {} {})", self.x, self.y, self.z)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ForeignData for Vec3 {
|
|
||||||
fn clone_data(&self) -> Box<dyn ForeignData> {
|
|
||||||
Box::new(*self)
|
|
||||||
}
|
|
||||||
fn eq(&self, other: &dyn ForeignData) -> bool {
|
|
||||||
if let Some(other) = other.as_any().downcast_ref::<Vec3>() {
|
|
||||||
self.x == other.x && self.y == other.y && self.z == other.z
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn partial_cmp(&self, other: &dyn ForeignData) -> Option<std::cmp::Ordering> {
|
|
||||||
if let Some(other) = other.as_any().downcast_ref::<Vec3>() {
|
|
||||||
Some(
|
|
||||||
self.x
|
|
||||||
.partial_cmp(&other.x)?
|
|
||||||
.then(self.y.partial_cmp(&other.y)?)
|
|
||||||
.then(self.z.partial_cmp(&other.z)?),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<Expression> for Vec3 {
|
|
||||||
type Error = EvalError;
|
|
||||||
fn try_from(value: Expression) -> Result<Self, Self::Error> {
|
|
||||||
match value {
|
|
||||||
Expression::ForeignExpression(fe) => {
|
|
||||||
if let Some(vec) = fe.data.as_ref().as_any().downcast_ref::<Vec3>() {
|
|
||||||
Ok(*vec)
|
|
||||||
} else {
|
|
||||||
Err(EvalError::TypeError("Expected vec3".to_string()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => Err(EvalError::TypeError("Expected vec3".to_string())),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec3> for Expression {
|
|
||||||
fn from(value: Vec3) -> Self {
|
|
||||||
Expression::ForeignExpression(ForeignDataWrapper::new(Box::new(value)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a vec3 expression from a list of 3 floats
|
|
||||||
pub fn vec_vec(_env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
|
|
||||||
let [x, y, z]: [f64; 3] = expr.try_into()?;
|
|
||||||
|
|
||||||
Ok(Vec3 { x, y, z }.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add two vec3 expressions
|
|
||||||
pub fn vec_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
|
|
||||||
let [a, b]: [Expression; 2] = expr.try_into()?;
|
|
||||||
|
|
||||||
let a = Vec3::try_from(eval(env, a)?)?;
|
|
||||||
let b = Vec3::try_from(eval(env, b)?)?;
|
|
||||||
|
|
||||||
Ok(Vec3 {
|
|
||||||
x: a.x + b.x,
|
|
||||||
y: a.y + b.y,
|
|
||||||
z: a.z + b.z,
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scale a vector by a factor. First argument is the factor, second the vector
|
|
||||||
pub fn vec_scale(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
|
|
||||||
let [a, b]: [Expression; 2] = expr.try_into()?;
|
|
||||||
|
|
||||||
let a = f64::try_from(eval(env, a)?)?;
|
|
||||||
let b = Vec3::try_from(eval(env, b)?)?;
|
|
||||||
|
|
||||||
Ok(Vec3 {
|
|
||||||
x: a * b.x,
|
|
||||||
y: a * b.y,
|
|
||||||
z: a * b.z,
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the dot product of two vec3
|
|
||||||
pub fn vec_dot(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
|
|
||||||
let [a, b]: [Expression; 2] = expr.try_into()?;
|
|
||||||
|
|
||||||
let a = Vec3::try_from(eval(env, a)?)?;
|
|
||||||
let b = Vec3::try_from(eval(env, b)?)?;
|
|
||||||
|
|
||||||
Ok(Expression::Float(a.x * b.x + a.y * b.y + a.z * b.z))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the L2-norm of a vector
|
|
||||||
pub fn vec_norm(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
|
|
||||||
let [arg]: [Expression; 1] = expr.try_into()?;
|
|
||||||
|
|
||||||
let vec = Vec3::try_from(eval(env, arg)?)?;
|
|
||||||
|
|
||||||
let length = (vec.x.powi(2) + vec.y.powi(2) + vec.z.powi(2)).sqrt();
|
|
||||||
|
|
||||||
Ok(Vec3 {
|
|
||||||
x: vec.x / length,
|
|
||||||
y: vec.y / length,
|
|
||||||
z: vec.z / length,
|
|
||||||
}
|
|
||||||
.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add vec3 functions to a layer
|
|
||||||
pub fn mk_vec3(layer: &mut EnvironmentLayer) {
|
|
||||||
layer.set("vec3".to_string(), Expression::Function(vec_vec));
|
|
||||||
layer.set("vec3-add".to_string(), Expression::Function(vec_add));
|
|
||||||
layer.set("vec3-scale".to_string(), Expression::Function(vec_scale));
|
|
||||||
layer.set("vec3-dot".to_string(), Expression::Function(vec_dot));
|
|
||||||
layer.set("vec3-norm".to_string(), Expression::Function(vec_norm));
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user