From 9f3b6694208b2323af46102abb5252b7b62572eb Mon Sep 17 00:00:00 2001 From: meso03 Date: Mon, 6 Oct 2025 09:36:12 -0400 Subject: [PATCH 01/10] Allows Python Function Registration --- Cargo.toml | 4 ++ src/domains/mod.rs | 3 + src/domains/simple.rs | 61 +++++++++++++++++--- src/domains/simple_python.rs | 50 ++++++++++++++++ src/dsl.rs | 107 +++++++++++++++++++++++++++++++++++ src/eval.rs | 3 +- src/lib.rs | 3 + src/python_bridge.rs | 94 ++++++++++++++++++++++++++++++ 8 files changed, 316 insertions(+), 9 deletions(-) create mode 100644 src/domains/simple_python.rs create mode 100644 src/python_bridge.rs diff --git a/Cargo.toml b/Cargo.toml index da3c457..d45f195 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,9 @@ description = "A library for defining domain specific languages in a polymorphic repository = "https://github.com/mlb2251/lambdas" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +python = ["dep:pyo3"] +default = [] [dependencies] serde_json = {version = "1.0", features = ["preserve_order"]} @@ -15,6 +18,7 @@ serde = {version = "1.0", features = ["derive"]} once_cell = "1.16.0" string_cache = "0.8.4" rustc-hash = "1.1.0" +pyo3 = { version = "0.25.1", features = ["auto-initialize"], optional=true} # [profile.release] # debug = true # for flamegraphs diff --git a/src/domains/mod.rs b/src/domains/mod.rs index 9f9c938..625c8c6 100644 --- a/src/domains/mod.rs +++ b/src/domains/mod.rs @@ -2,3 +2,6 @@ // just register each domain here by including it with `pub mod domain_name;` pub mod simple; pub mod prim_lists; + +#[cfg(feature = "python")] +pub mod simple_python; // <-- gate the module declaration diff --git a/src/domains/simple.rs b/src/domains/simple.rs index d650c93..d03ef83 100644 --- a/src/domains/simple.rs +++ b/src/domains/simple.rs @@ -1,5 +1,15 @@ +// lambdas/src/domains/simple.rs + /// This is an example domain, heavily commented to explain how to implement your own! use crate::*; +#[cfg(feature = "python")] +// use crate::domains::simple_python::{val_to_py, py_to_val}; +#[cfg(feature = "python")] +use pyo3::prelude::*; +// #[cfg(feature = "python")] +// use pyo3::types::PyTuple; +#[cfg(feature = "python")] +use pyo3::types::PyAny; /// A simple domain with ints and polymorphic lists (allows nested lists). /// Generally it's good to be able to imagine the hindley milner type system @@ -27,7 +37,6 @@ type Env = crate::eval::Env; // to more concisely refer to the variants use SimpleVal::*; - // From impls are needed for unwrapping values. We can assume the program // has been type checked so it's okay to panic if the type is wrong. Each val variant // must map to exactly one unwrapped type (though it doesnt need to be one to one in the @@ -67,17 +76,55 @@ impl Domain for SimpleVal { // we dont use Data here type Data = (); + #[cfg(feature = "python")] + fn py_val_to_py(py: Python<'_>, v: crate::eval::Val) -> PyResult> { + // delegate to your existing converter + crate::domains::simple_python::val_to_py(py, v) + } + + #[cfg(feature = "python")] + fn py_py_to_val(obj: &Bound<'_, PyAny>) -> Result, String> { + // delegate to your existing converter + crate::domains::simple_python::py_to_val(obj) + } + + // fn new_dsl() -> DSL { + // DSL::new(vec![ + // Production::func("+", "int -> int -> int", add), + // Production::func("*", "int -> int -> int", mul), + // Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), + // Production::func("sum", "list int -> int", sum), + // Production::val("0", "int", Dom(Int(0))), + // Production::val("1", "int", Dom(Int(1))), + // Production::val("2", "int", Dom(Int(2))), + // Production::val("[]", "(list t0)", Dom(List(vec![]))), + // ]) + // } + fn new_dsl() -> DSL { - DSL::new(vec![ - Production::func("+", "int -> int -> int", add), - Production::func("*", "int -> int -> int", mul), + let mut prods = vec![ + // Production::func("+", "int -> int -> int", add), + // Production::func("*", "int -> int -> int", mul), Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), - Production::func("sum", "list int -> int", sum), + // Production::func("sum", "list int -> int", sum), Production::val("0", "int", Dom(Int(0))), Production::val("1", "int", Dom(Int(1))), Production::val("2", "int", Dom(Int(2))), Production::val("[]", "(list t0)", Dom(List(vec![]))), - ]) + ]; + + // Log the built-ins you just added + eprintln!("[DSL] built-ins:"); + // eprintln!(" func * : int -> int -> int"); + eprintln!(" func map : (t0 -> t1) -> (list t0) -> (list t1)"); + eprintln!(" val 0 : int"); + eprintln!(" val 1 : int"); + eprintln!(" val 2 : int"); + eprintln!(" val [] : (list t0)"); + + let dsl = DSL::new(prods); + // eprintln!("[DSL] build complete."); + dsl } // val_of_prim takes a symbol like "+" or "0" and returns the corresponding Val. @@ -160,8 +207,6 @@ fn sum(mut args: Env, _handle: &Evaluator) -> VResult { ok(xs.iter().sum::()) } - - #[cfg(test)] mod tests { use super::*; diff --git a/src/domains/simple_python.rs b/src/domains/simple_python.rs new file mode 100644 index 0000000..4deaa19 --- /dev/null +++ b/src/domains/simple_python.rs @@ -0,0 +1,50 @@ +//lambdas/src/domains/simple_python.rs + +#![cfg(feature = "python")] + +use pyo3::prelude::*; +use pyo3::types::PyList; +use pyo3::conversion::IntoPyObjectExt; // for .into_py_any on integers + +use crate::domains::simple::SimpleVal; +use crate::eval; +type Val = eval::Val; +use SimpleVal::*; + +/// Rust Val -> Python object +pub fn val_to_py(py: Python<'_>, v: Val) -> PyResult> { + match v.dom().expect("Val should be Dom") { + Int(i) => { + // i32 -> Py + Ok(i.into_py_any(py)?) + } + List(xs) => { + // Convert each element first + let mut elems: Vec> = Vec::with_capacity(xs.len()); + for x in xs { + elems.push(val_to_py(py, x.clone())?); + } + + // IMPORTANT: PyList::new(...) -> PyResult> + let list_bound: Bound<'_, PyList> = PyList::new(py, &elems)?; + + // Bound -> Bound -> Py + Ok(list_bound.into_any().unbind()) + } + } +} + +/// Python object -> Rust Val +pub fn py_to_val(obj: &Bound<'_, PyAny>) -> Result { + if let Ok(i) = obj.extract::() { + return Ok(Val::from(Int(i))); + } + if let Ok(list) = obj.downcast::() { + let mut out: Vec = Vec::with_capacity(list.len()); + for item in list.iter() { + out.push(py_to_val(&item)?); + } + return Ok(Val::from(List(out))); + } + Err("unsupported Python type for SimpleVal".into()) +} diff --git a/src/dsl.rs b/src/dsl.rs index b78495f..5c50730 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -3,7 +3,13 @@ use crate::*; use std::collections::{HashMap, HashSet}; use std::fmt::{Debug}; use std::hash::Hash; +use std::sync::Arc; +#[cfg(feature = "python")] +use pyo3::prelude::*; +#[cfg(feature = "python")] +use pyo3::types::PyList; +// use crate::domains::simple::SimpleVal; pub type DSLFn = fn(Env, &Evaluator) -> VResult; @@ -15,6 +21,8 @@ pub struct Production { pub arity: usize, pub lazy_args: HashSet, pub fn_ptr: Option>, + #[cfg(feature = "python")] + pub py_fn: Option>>, } impl Debug for Production { @@ -23,6 +31,44 @@ impl Debug for Production { } } +impl Production { + // #[inline] + // pub fn call(&self, args: Env, handle: &Evaluator) -> VResult { + // // TEMP: still uses your existing native function pointer. + // // We’ll change the internals later, but the signature will stay. + // (self.fn_ptr.unwrap())(args, handle) + // } + #[inline] + pub fn call(&self, args: Env, handle: &Evaluator) -> VResult { + #[cfg(feature = "python")] + if let Some(pyf) = &self.py_fn { + // Convert via the Domain hooks + return Python::with_gil(|py| { + use pyo3::types::PyList; + + // Env -> Python list + let mut elems: Vec> = Vec::with_capacity(args.len()); + for v in &args.env { + elems.push(D::py_val_to_py(py, v.clone())?); + } + let list_bound = PyList::new(py, &elems)?; // Bound + + // Call Python + //let ret = pyf.bind(py).call1((list_bound,))?; + let ret = (**pyf).bind(py).call1((list_bound,))?; + + // Python -> Val + D::py_py_to_val(&ret) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e)) + }) + .map_err(|pyerr| pyerr.to_string()); // VError = String + } + + // Otherwise fall back to the native fn pointer (existing behavior): + (self.fn_ptr.unwrap())(args, handle) + } +} + #[derive(Clone, Debug)] pub struct DSL { @@ -54,6 +100,8 @@ impl Production { arity: 0, lazy_args: Default::default(), fn_ptr: None, + #[cfg(feature = "python")] + py_fn: None, } } @@ -66,6 +114,8 @@ impl Production { arity, lazy_args, fn_ptr: Some(fn_ptr), + #[cfg(feature = "python")] + py_fn: None, } } @@ -98,6 +148,49 @@ impl DSL { }) } + /// Early-capture install of a Python-backed primitive. + /// - `name`: symbol of the primitive + /// - `tp`: its type (you already have SlowType; we use that) + /// - `lazy_args`: optional indices of lazy arguments; use None for strict + /// - `pyfunc`: owned Python callable captured into this production + #[cfg(feature = "python")] + pub fn add_python_primitive( + &mut self, + name: Symbol, + tp: SlowType, + lazy_args: Option<&[usize]>, + pyfunc: Py, + ) { + let arity = tp.arity(); + let lazy: HashSet = lazy_args + .map(|xs| xs.iter().copied().collect()) + .unwrap_or_default(); + + use crate::eval::{CurriedFn, Val}; // for PrimFun constructor + + // Insert or update the production entry for this symbol. + let entry = self.productions.entry(name.clone()).or_insert_with(|| Production { + name: name.clone(), + val: Val::PrimFun(CurriedFn::::new(name.clone(), arity)), + tp: tp.clone(), + arity, + lazy_args: lazy.clone(), + fn_ptr: None, // no native body required + #[cfg(feature = "python")] + py_fn: None, // will set below + }); + + // Keep metadata consistent if it already existed + entry.tp = tp; + entry.arity = arity; + entry.lazy_args = lazy; + entry.val = Val::PrimFun(CurriedFn::::new(name, arity)); + #[cfg(feature = "python")] + {entry.py_fn = Some(Arc::new(pyfunc));} // <-- early-captured callable lives here + // NOTE: we leave `fn_ptr` as-is; it can be None or Some(native). + // If you prefer “no conflict”, you can set `entry.fn_ptr = None;` + } + } @@ -110,5 +203,19 @@ pub trait Domain: Clone + Debug + PartialEq + Eq + Hash + Send + Sync { fn type_of_dom_val(&self) -> SlowType; fn new_dsl() -> DSL; + + #[cfg(feature = "python")] + fn py_val_to_py(py: Python<'_>, v: Val) -> PyResult> { + // default: not supported for this domain + Err(pyo3::exceptions::PyTypeError::new_err( + "Python bridge not implemented for this domain", + )) + } + + #[cfg(feature = "python")] + fn py_py_to_val(_obj: &Bound<'_, PyAny>) -> Result, String> { + Err("Python bridge not implemented for this domain".into()) + } + } diff --git a/src/eval.rs b/src/eval.rs index ea44495..e7da4ab 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -131,7 +131,8 @@ impl CurriedFn { pub fn apply(mut self, arg: Val, handle: &Evaluator) -> VResult { self.partial_args.push_back(arg); if self.partial_args.len() == self.arity { - handle.dsl.productions.get(&self.name).unwrap().fn_ptr.unwrap() (self.partial_args, handle) + //handle.dsl.productions.get(&self.name).unwrap().fn_ptr.unwrap() (self.partial_args, handle) + handle.dsl.productions.get(&self.name).unwrap().call(self.partial_args, handle) } else { Ok(Val::PrimFun(self)) } diff --git a/src/lib.rs b/src/lib.rs index 823cc22..7c48fa3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,9 @@ #[macro_use] mod macros; +#[cfg(feature = "python")] +pub mod python_bridge; + mod expr; mod dsl; mod eval; diff --git a/src/python_bridge.rs b/src/python_bridge.rs new file mode 100644 index 0000000..faee2d4 --- /dev/null +++ b/src/python_bridge.rs @@ -0,0 +1,94 @@ +// // lambdas/src/python_bridge.rs + +// #![cfg(feature = "python")] + +// use once_cell::sync::OnceCell; +// use std::cell::RefCell; +// use std::collections::HashMap; +// use std::sync::Mutex; +// use pyo3::prelude::*; +// use pyo3::Py; +// use pyo3::prelude::*; +// use pyo3::types::{PyDict, PyTuple}; + +// /// symbol -> Python callable +// static CALLABLES: OnceCell>>> = OnceCell::new(); + +// /// (symbol, type_str, _reg_name) list; we’ll dispatch by *symbol* +// static PRIMS: OnceCell>> = OnceCell::new(); + +// fn callables() -> &'static Mutex>> { +// CALLABLES.get_or_init(|| Mutex::new(HashMap::new())) +// } +// fn prims() -> &'static Mutex> { +// PRIMS.get_or_init(|| Mutex::new(Vec::new())) +// } + +// /// Thread-local “which symbol is currently executing” +// thread_local! { +// static CURRENT_SYMBOL: RefCell> = RefCell::new(None); +// } + +// pub fn set_current_symbol(sym: &str) { +// CURRENT_SYMBOL.with(|s| *s.borrow_mut() = Some(sym.to_string())); +// } +// pub fn clear_current_symbol() { +// CURRENT_SYMBOL.with(|s| *s.borrow_mut() = None); +// } +// pub fn current_symbol() -> Option { +// CURRENT_SYMBOL.with(|s| s.borrow().clone()) +// } + +// pub fn clone_callable(symbol: &str, py: Python<'_>) -> PyResult> { +// let m = callables().lock().unwrap(); +// match m.get(symbol) { +// Some(obj) => Ok(obj.clone_ref(py)), // <- correct way to duplicate Py +// None => Err(pyo3::exceptions::PyKeyError::new_err( +// format!("no python callable for symbol `{symbol}`"), +// )), +// } +// } + +// /// Python calls this to register a callable for a *symbol*. +// /// Use the *symbol* as the key you’ll write in DSL programs, e.g. "add". +// pub fn register_callable(symbol: String, func: &Bound) -> PyResult<()> { +// let owned: Py = func.clone().unbind(); +// let mut m = callables().lock().unwrap(); +// m.insert(symbol, owned); +// Ok(()) +// } + +// pub fn register_primitive(symbol: String, ty: String, registry_name: String) -> PyResult<()> { +// let mut v = prims().lock().unwrap(); +// v.push((symbol, ty, registry_name)); +// Ok(()) +// } + +// /// Take and clear the primitive list (so we won’t double-append) +// pub fn prims_take() -> Vec<(String, String, String)> { +// std::mem::take(&mut *prims().lock().unwrap()) +// } + +// /// Low-level call by symbol: (*args, **kwargs) → PyObject +// pub fn call_by_symbol_raw( +// symbol: &str, +// py: Python<'_>, +// args: &Bound, +// kwargs: Option<&Bound>, +// ) -> PyResult { +// // Clone while not holding Python or calling back +// let f: Py = { +// let map = callables().lock().unwrap(); +// match map.get(symbol) { +// Some(obj) => obj.clone_ref(py), +// None => { +// return Err(pyo3::exceptions::PyKeyError::new_err( +// format!("no python callable for symbol `{symbol}`"), +// )) +// } +// } +// }; +// let f = f.bind(py); +// let out = f.call(args, kwargs)?; +// Ok(out.unbind().into()) +// } From a6f4b79ee3ccc784bb273dc9792ee399a5a207ae Mon Sep 17 00:00:00 2001 From: meso03 Date: Tue, 14 Oct 2025 13:16:43 -0400 Subject: [PATCH 02/10] fixed errors --- src/domains/simple.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/domains/simple.rs b/src/domains/simple.rs index d03ef83..5795ad1 100644 --- a/src/domains/simple.rs +++ b/src/domains/simple.rs @@ -103,10 +103,10 @@ impl Domain for SimpleVal { fn new_dsl() -> DSL { let mut prods = vec![ - // Production::func("+", "int -> int -> int", add), - // Production::func("*", "int -> int -> int", mul), + Production::func("+", "int -> int -> int", add), + Production::func("*", "int -> int -> int", mul), Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), - // Production::func("sum", "list int -> int", sum), + Production::func("sum", "list int -> int", sum), Production::val("0", "int", Dom(Int(0))), Production::val("1", "int", Dom(Int(1))), Production::val("2", "int", Dom(Int(2))), From f33d27936ba9a6e0935e4a9e4ab656d96796b088 Mon Sep 17 00:00:00 2001 From: meso03 Date: Sun, 19 Oct 2025 20:29:26 -0400 Subject: [PATCH 03/10] Created new Python domain --- src/domains/mod.rs | 2 + src/domains/py.rs | 146 +++++++++++++++++++++++++++++++++++ src/domains/simple.rs | 59 ++------------ src/domains/simple_python.rs | 8 +- src/dsl.rs | 7 -- src/lib.rs | 4 - src/python_bridge.rs | 94 ---------------------- 7 files changed, 159 insertions(+), 161 deletions(-) create mode 100644 src/domains/py.rs delete mode 100644 src/python_bridge.rs diff --git a/src/domains/mod.rs b/src/domains/mod.rs index 625c8c6..fb192e7 100644 --- a/src/domains/mod.rs +++ b/src/domains/mod.rs @@ -5,3 +5,5 @@ pub mod prim_lists; #[cfg(feature = "python")] pub mod simple_python; // <-- gate the module declaration + +pub mod py; \ No newline at end of file diff --git a/src/domains/py.rs b/src/domains/py.rs new file mode 100644 index 0000000..7e91f93 --- /dev/null +++ b/src/domains/py.rs @@ -0,0 +1,146 @@ +use crate::*; + +#[cfg(feature = "python")] +use pyo3::prelude::*; +// #[cfg(feature = "python")] +// use pyo3::types::PyTuple; +#[cfg(feature = "python")] +use pyo3::types::PyAny; + +/// by dreamegg::domain::Val so they don't appear here. +#[derive(Clone,Debug, PartialEq, Eq, Hash)] +pub enum PyVal { + Int(i32), + List(Vec), +} + +#[derive(Clone,Debug, PartialEq, Eq, Hash)] +pub enum PyType { + TInt, + TList +} + +// aliases of various typed specialized to our SimpleVal +type Val = crate::eval::Val; +type Evaluator<'a> = crate::eval::Evaluator<'a,PyVal>; +type VResult = crate::eval::VResult; +type Env = crate::eval::Env; + +// to more concisely refer to the variants +use PyVal::*; + +// From impls are needed for unwrapping values. We can assume the program +// has been type checked so it's okay to panic if the type is wrong. Each val variant +// must map to exactly one unwrapped type (though it doesnt need to be one to one in the +// other direction) +impl FromVal for i32 { + fn from_val(v: Val) -> Result { + match v { + Dom(Int(i)) => Ok(i), + _ => Err("from_val_to_i32: not an int".into()) + } + } +} +impl> FromVal for Vec { + fn from_val(v: Val) -> Result { + match v { + Dom(List(v)) => v.into_iter().map(|v| T::from_val(v)).collect(), + _ => Err("from_val_to_vec: not a list".into()) + } + } +} + +// These Intos are convenience functions. It's okay if theres not a one to one mapping +// like this in all domains - it just makes .into() save us a lot of work if there is. +impl From for Val { + fn from(i: i32) -> Val { + Dom(Int(i)) + } +} +impl> From> for Val { + fn from(vec: Vec) -> Val { + Dom(List(vec.into_iter().map(|v| v.into()).collect())) + } +} + +// here we actually implement Domain for our domain. +impl Domain for PyVal { + // we dont use Data here + type Data = (); + + #[cfg(feature = "python")] + fn py_val_to_py(py: Python<'_>, v: crate::eval::Val) -> PyResult> { + // delegate to your existing converter + crate::domains::simple_python::val_to_py(py, v) + } + + #[cfg(feature = "python")] + fn py_py_to_val(obj: &Bound<'_, PyAny>) -> Result, String> { + // delegate to your existing converter + crate::domains::simple_python::py_to_val(obj) + } + + fn new_dsl() -> DSL { + let mut prods = vec![ + // Production::func("+", "int -> int -> int", add), + // Production::func("*", "int -> int -> int", mul), + // Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), + // Production::func("sum", "list int -> int", sum), + // Production::val("0", "int", Dom(Int(0))), + // Production::val("1", "int", Dom(Int(1))), + // Production::val("2", "int", Dom(Int(2))), + // Production::val("[]", "(list t0)", Dom(List(vec![]))), + ]; + + // // Log the built-ins you just added + // eprintln!("[DSL] built-ins:"); + // // eprintln!(" func * : int -> int -> int"); + // eprintln!(" func map : (t0 -> t1) -> (list t0) -> (list t1)"); + // eprintln!(" val 0 : int"); + // eprintln!(" val 1 : int"); + // eprintln!(" val 2 : int"); + // eprintln!(" val [] : (list t0)"); + + let dsl = DSL::new(prods); + // eprintln!("[DSL] build complete."); + dsl + } + + // val_of_prim takes a symbol like "+" or "0" and returns the corresponding Val. + // Note that it can largely just be a call to the global hashmap PRIMS that define_semantics generated + // however you're also free to do any sort of generic parsing you want, allowing for domains with + // infinite sets of values or dynamically generated values. For example here we support all integers + // and all integer lists. + fn val_of_prim_fallback(p: &Symbol) -> Option { + // starts with digit -> Int + if p.chars().next().unwrap().is_ascii_digit() { + let i: i32 = p.parse().ok()?; + Some(Int(i).into()) + } + // starts with `[` -> List (must be all ints) + else if p.starts_with('[') { + let intvec: Vec = serde_json::from_str(p).ok()?; + let valvec: Vec = intvec.into_iter().map(|v|Dom(Int(v))).collect(); + Some(List(valvec).into()) + } else { + None + } + } + + fn type_of_dom_val(&self) -> SlowType { + match self { + Int(_) => SlowType::base(Symbol::from("int")), + List(xs) => { + let elem_tp = if xs.is_empty() { + SlowType::Var(0) // (list t0) + } else { + // todo here we just use the type of the first entry as the type + Self::type_of_dom_val(&xs.first().unwrap().clone().dom().unwrap()) + // assert!(xs.iter().all(|v| Self::type_of_dom_val(v.clone().dom().unwrap()))) + }; + SlowType::Term("list".into(),vec![elem_tp]) + }, + } + } + +} \ No newline at end of file diff --git a/src/domains/simple.rs b/src/domains/simple.rs index 5795ad1..85fa89b 100644 --- a/src/domains/simple.rs +++ b/src/domains/simple.rs @@ -1,15 +1,5 @@ -// lambdas/src/domains/simple.rs - /// This is an example domain, heavily commented to explain how to implement your own! use crate::*; -#[cfg(feature = "python")] -// use crate::domains::simple_python::{val_to_py, py_to_val}; -#[cfg(feature = "python")] -use pyo3::prelude::*; -// #[cfg(feature = "python")] -// use pyo3::types::PyTuple; -#[cfg(feature = "python")] -use pyo3::types::PyAny; /// A simple domain with ints and polymorphic lists (allows nested lists). /// Generally it's good to be able to imagine the hindley milner type system @@ -37,6 +27,7 @@ type Env = crate::eval::Env; // to more concisely refer to the variants use SimpleVal::*; + // From impls are needed for unwrapping values. We can assume the program // has been type checked so it's okay to panic if the type is wrong. Each val variant // must map to exactly one unwrapped type (though it doesnt need to be one to one in the @@ -76,33 +67,8 @@ impl Domain for SimpleVal { // we dont use Data here type Data = (); - #[cfg(feature = "python")] - fn py_val_to_py(py: Python<'_>, v: crate::eval::Val) -> PyResult> { - // delegate to your existing converter - crate::domains::simple_python::val_to_py(py, v) - } - - #[cfg(feature = "python")] - fn py_py_to_val(obj: &Bound<'_, PyAny>) -> Result, String> { - // delegate to your existing converter - crate::domains::simple_python::py_to_val(obj) - } - - // fn new_dsl() -> DSL { - // DSL::new(vec![ - // Production::func("+", "int -> int -> int", add), - // Production::func("*", "int -> int -> int", mul), - // Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), - // Production::func("sum", "list int -> int", sum), - // Production::val("0", "int", Dom(Int(0))), - // Production::val("1", "int", Dom(Int(1))), - // Production::val("2", "int", Dom(Int(2))), - // Production::val("[]", "(list t0)", Dom(List(vec![]))), - // ]) - // } - fn new_dsl() -> DSL { - let mut prods = vec![ + DSL::new(vec![ Production::func("+", "int -> int -> int", add), Production::func("*", "int -> int -> int", mul), Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), @@ -111,20 +77,7 @@ impl Domain for SimpleVal { Production::val("1", "int", Dom(Int(1))), Production::val("2", "int", Dom(Int(2))), Production::val("[]", "(list t0)", Dom(List(vec![]))), - ]; - - // Log the built-ins you just added - eprintln!("[DSL] built-ins:"); - // eprintln!(" func * : int -> int -> int"); - eprintln!(" func map : (t0 -> t1) -> (list t0) -> (list t1)"); - eprintln!(" val 0 : int"); - eprintln!(" val 1 : int"); - eprintln!(" val 2 : int"); - eprintln!(" val [] : (list t0)"); - - let dsl = DSL::new(prods); - // eprintln!("[DSL] build complete."); - dsl + ]) } // val_of_prim takes a symbol like "+" or "0" and returns the corresponding Val. @@ -165,10 +118,10 @@ impl Domain for SimpleVal { } -} + } -// *** DSL FUNCTIONS *** + // *** DSL FUNCTIONS *** // See comments throughout pointing out useful aspects fn add(mut args: Env, _handle: &Evaluator) -> VResult { @@ -207,6 +160,8 @@ fn sum(mut args: Env, _handle: &Evaluator) -> VResult { ok(xs.iter().sum::()) } + + #[cfg(test)] mod tests { use super::*; diff --git a/src/domains/simple_python.rs b/src/domains/simple_python.rs index 4deaa19..2d64e01 100644 --- a/src/domains/simple_python.rs +++ b/src/domains/simple_python.rs @@ -6,10 +6,10 @@ use pyo3::prelude::*; use pyo3::types::PyList; use pyo3::conversion::IntoPyObjectExt; // for .into_py_any on integers -use crate::domains::simple::SimpleVal; +use crate::domains::py::PyVal; use crate::eval; -type Val = eval::Val; -use SimpleVal::*; +type Val = eval::Val; +use PyVal::*; /// Rust Val -> Python object pub fn val_to_py(py: Python<'_>, v: Val) -> PyResult> { @@ -46,5 +46,5 @@ pub fn py_to_val(obj: &Bound<'_, PyAny>) -> Result { } return Ok(Val::from(List(out))); } - Err("unsupported Python type for SimpleVal".into()) + Err("unsupported Python type for PyVal".into()) } diff --git a/src/dsl.rs b/src/dsl.rs index 5c50730..9741990 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -9,7 +9,6 @@ use std::sync::Arc; use pyo3::prelude::*; #[cfg(feature = "python")] use pyo3::types::PyList; -// use crate::domains::simple::SimpleVal; pub type DSLFn = fn(Env, &Evaluator) -> VResult; @@ -32,12 +31,6 @@ impl Debug for Production { } impl Production { - // #[inline] - // pub fn call(&self, args: Env, handle: &Evaluator) -> VResult { - // // TEMP: still uses your existing native function pointer. - // // We’ll change the internals later, but the signature will stay. - // (self.fn_ptr.unwrap())(args, handle) - // } #[inline] pub fn call(&self, args: Env, handle: &Evaluator) -> VResult { #[cfg(feature = "python")] diff --git a/src/lib.rs b/src/lib.rs index 7c48fa3..1925620 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,5 @@ #[macro_use] mod macros; - -#[cfg(feature = "python")] -pub mod python_bridge; - mod expr; mod dsl; mod eval; diff --git a/src/python_bridge.rs b/src/python_bridge.rs deleted file mode 100644 index faee2d4..0000000 --- a/src/python_bridge.rs +++ /dev/null @@ -1,94 +0,0 @@ -// // lambdas/src/python_bridge.rs - -// #![cfg(feature = "python")] - -// use once_cell::sync::OnceCell; -// use std::cell::RefCell; -// use std::collections::HashMap; -// use std::sync::Mutex; -// use pyo3::prelude::*; -// use pyo3::Py; -// use pyo3::prelude::*; -// use pyo3::types::{PyDict, PyTuple}; - -// /// symbol -> Python callable -// static CALLABLES: OnceCell>>> = OnceCell::new(); - -// /// (symbol, type_str, _reg_name) list; we’ll dispatch by *symbol* -// static PRIMS: OnceCell>> = OnceCell::new(); - -// fn callables() -> &'static Mutex>> { -// CALLABLES.get_or_init(|| Mutex::new(HashMap::new())) -// } -// fn prims() -> &'static Mutex> { -// PRIMS.get_or_init(|| Mutex::new(Vec::new())) -// } - -// /// Thread-local “which symbol is currently executing” -// thread_local! { -// static CURRENT_SYMBOL: RefCell> = RefCell::new(None); -// } - -// pub fn set_current_symbol(sym: &str) { -// CURRENT_SYMBOL.with(|s| *s.borrow_mut() = Some(sym.to_string())); -// } -// pub fn clear_current_symbol() { -// CURRENT_SYMBOL.with(|s| *s.borrow_mut() = None); -// } -// pub fn current_symbol() -> Option { -// CURRENT_SYMBOL.with(|s| s.borrow().clone()) -// } - -// pub fn clone_callable(symbol: &str, py: Python<'_>) -> PyResult> { -// let m = callables().lock().unwrap(); -// match m.get(symbol) { -// Some(obj) => Ok(obj.clone_ref(py)), // <- correct way to duplicate Py -// None => Err(pyo3::exceptions::PyKeyError::new_err( -// format!("no python callable for symbol `{symbol}`"), -// )), -// } -// } - -// /// Python calls this to register a callable for a *symbol*. -// /// Use the *symbol* as the key you’ll write in DSL programs, e.g. "add". -// pub fn register_callable(symbol: String, func: &Bound) -> PyResult<()> { -// let owned: Py = func.clone().unbind(); -// let mut m = callables().lock().unwrap(); -// m.insert(symbol, owned); -// Ok(()) -// } - -// pub fn register_primitive(symbol: String, ty: String, registry_name: String) -> PyResult<()> { -// let mut v = prims().lock().unwrap(); -// v.push((symbol, ty, registry_name)); -// Ok(()) -// } - -// /// Take and clear the primitive list (so we won’t double-append) -// pub fn prims_take() -> Vec<(String, String, String)> { -// std::mem::take(&mut *prims().lock().unwrap()) -// } - -// /// Low-level call by symbol: (*args, **kwargs) → PyObject -// pub fn call_by_symbol_raw( -// symbol: &str, -// py: Python<'_>, -// args: &Bound, -// kwargs: Option<&Bound>, -// ) -> PyResult { -// // Clone while not holding Python or calling back -// let f: Py = { -// let map = callables().lock().unwrap(); -// match map.get(symbol) { -// Some(obj) => obj.clone_ref(py), -// None => { -// return Err(pyo3::exceptions::PyKeyError::new_err( -// format!("no python callable for symbol `{symbol}`"), -// )) -// } -// } -// }; -// let f = f.bind(py); -// let out = f.call(args, kwargs)?; -// Ok(out.unbind().into()) -// } From 9f9ea9888397691f02c49975ca862e97bf47f46c Mon Sep 17 00:00:00 2001 From: meso03 Date: Wed, 12 Nov 2025 14:42:30 -0500 Subject: [PATCH 04/10] Addressed some comments --- src/domains/mod.rs | 3 +- src/domains/py.rs | 71 +++++++++----------------------------- src/dsl.rs | 85 ++++++++++++++++++++++------------------------ 3 files changed, 59 insertions(+), 100 deletions(-) diff --git a/src/domains/mod.rs b/src/domains/mod.rs index fb192e7..ce9f377 100644 --- a/src/domains/mod.rs +++ b/src/domains/mod.rs @@ -4,6 +4,7 @@ pub mod simple; pub mod prim_lists; #[cfg(feature = "python")] -pub mod simple_python; // <-- gate the module declaration +pub mod simple_python; +#[cfg(feature = "python")] pub mod py; \ No newline at end of file diff --git a/src/domains/py.rs b/src/domains/py.rs index 7e91f93..9e9cb96 100644 --- a/src/domains/py.rs +++ b/src/domains/py.rs @@ -2,12 +2,9 @@ use crate::*; #[cfg(feature = "python")] use pyo3::prelude::*; -// #[cfg(feature = "python")] -// use pyo3::types::PyTuple; #[cfg(feature = "python")] use pyo3::types::PyAny; -/// by dreamegg::domain::Val so they don't appear here. #[derive(Clone,Debug, PartialEq, Eq, Hash)] pub enum PyVal { Int(i32), @@ -20,19 +17,14 @@ pub enum PyType { TList } -// aliases of various typed specialized to our SimpleVal +// aliases of various typed specialized to our PyVal type Val = crate::eval::Val; type Evaluator<'a> = crate::eval::Evaluator<'a,PyVal>; type VResult = crate::eval::VResult; type Env = crate::eval::Env; -// to more concisely refer to the variants -use PyVal::*; -// From impls are needed for unwrapping values. We can assume the program -// has been type checked so it's okay to panic if the type is wrong. Each val variant -// must map to exactly one unwrapped type (though it doesnt need to be one to one in the -// other direction) +use PyVal::*; impl FromVal for i32 { fn from_val(v: Val) -> Result { match v { @@ -50,8 +42,6 @@ impl> FromVal for Vec { } } -// These Intos are convenience functions. It's okay if theres not a one to one mapping -// like this in all domains - it just makes .into() save us a lot of work if there is. impl From for Val { fn from(i: i32) -> Val { Dom(Int(i)) @@ -63,68 +53,39 @@ impl> From> for Val { } } -// here we actually implement Domain for our domain. impl Domain for PyVal { - // we dont use Data here type Data = (); #[cfg(feature = "python")] fn py_val_to_py(py: Python<'_>, v: crate::eval::Val) -> PyResult> { - // delegate to your existing converter crate::domains::simple_python::val_to_py(py, v) } #[cfg(feature = "python")] fn py_py_to_val(obj: &Bound<'_, PyAny>) -> Result, String> { - // delegate to your existing converter crate::domains::simple_python::py_to_val(obj) } fn new_dsl() -> DSL { - let mut prods = vec![ - // Production::func("+", "int -> int -> int", add), - // Production::func("*", "int -> int -> int", mul), - // Production::func("map", "(t0 -> t1) -> (list t0) -> (list t1)", map), - // Production::func("sum", "list int -> int", sum), - // Production::val("0", "int", Dom(Int(0))), - // Production::val("1", "int", Dom(Int(1))), - // Production::val("2", "int", Dom(Int(2))), - // Production::val("[]", "(list t0)", Dom(List(vec![]))), - ]; - - // // Log the built-ins you just added - // eprintln!("[DSL] built-ins:"); - // // eprintln!(" func * : int -> int -> int"); - // eprintln!(" func map : (t0 -> t1) -> (list t0) -> (list t1)"); - // eprintln!(" val 0 : int"); - // eprintln!(" val 1 : int"); - // eprintln!(" val 2 : int"); - // eprintln!(" val [] : (list t0)"); - + let mut prods = vec![]; let dsl = DSL::new(prods); - // eprintln!("[DSL] build complete."); dsl } - // val_of_prim takes a symbol like "+" or "0" and returns the corresponding Val. - // Note that it can largely just be a call to the global hashmap PRIMS that define_semantics generated - // however you're also free to do any sort of generic parsing you want, allowing for domains with - // infinite sets of values or dynamically generated values. For example here we support all integers - // and all integer lists. fn val_of_prim_fallback(p: &Symbol) -> Option { - // starts with digit -> Int - if p.chars().next().unwrap().is_ascii_digit() { - let i: i32 = p.parse().ok()?; - Some(Int(i).into()) - } - // starts with `[` -> List (must be all ints) - else if p.starts_with('[') { - let intvec: Vec = serde_json::from_str(p).ok()?; - let valvec: Vec = intvec.into_iter().map(|v|Dom(Int(v))).collect(); - Some(List(valvec).into()) - } else { - None - } + // if p.chars().next().unwrap().is_ascii_digit() { + // let i: i32 = p.parse().ok()?; + // Some(Int(i).into()) + // } + // // starts with `[` -> List (must be all ints) + // else if p.starts_with('[') { + // let intvec: Vec = serde_json::from_str(p).ok()?; + // let valvec: Vec = intvec.into_iter().map(|v|Dom(Int(v))).collect(); + // Some(List(valvec).into()) + // } else { + // None + // } + None // We will add this later if we want to } fn type_of_dom_val(&self) -> SlowType { diff --git a/src/dsl.rs b/src/dsl.rs index 9741990..a9a0b96 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -8,10 +8,17 @@ use std::sync::Arc; #[cfg(feature = "python")] use pyo3::prelude::*; #[cfg(feature = "python")] -use pyo3::types::PyList; +use pyo3::types::{PyList, PyTuple}; pub type DSLFn = fn(Env, &Evaluator) -> VResult; +#[derive(Clone)] +pub enum FnPtr { + Native(DSLFn), + #[cfg(feature = "python")] + Python(Arc>), +} + #[derive(Clone)] pub struct Production { pub name: Symbol, // eg "map" or "0" or "[1,2,3]" @@ -19,9 +26,7 @@ pub struct Production { pub tp: SlowType, pub arity: usize, pub lazy_args: HashSet, - pub fn_ptr: Option>, - #[cfg(feature = "python")] - pub py_fn: Option>>, + pub fn_ptr: Option>, } impl Debug for Production { @@ -33,32 +38,34 @@ impl Debug for Production { impl Production { #[inline] pub fn call(&self, args: Env, handle: &Evaluator) -> VResult { - #[cfg(feature = "python")] - if let Some(pyf) = &self.py_fn { - // Convert via the Domain hooks - return Python::with_gil(|py| { - use pyo3::types::PyList; - - // Env -> Python list - let mut elems: Vec> = Vec::with_capacity(args.len()); - for v in &args.env { - elems.push(D::py_val_to_py(py, v.clone())?); - } - let list_bound = PyList::new(py, &elems)?; // Bound - - // Call Python - //let ret = pyf.bind(py).call1((list_bound,))?; - let ret = (**pyf).bind(py).call1((list_bound,))?; - - // Python -> Val - D::py_py_to_val(&ret) - .map_err(|e| pyo3::exceptions::PyValueError::new_err(e)) - }) - .map_err(|pyerr| pyerr.to_string()); // VError = String - } + match &self.fn_ptr{ + Some(FnPtr::Native(f)) => f(args, handle), - // Otherwise fall back to the native fn pointer (existing behavior): - (self.fn_ptr.unwrap())(args, handle) + #[cfg(feature = "python")] + Some(FnPtr::Python(pyf)) => { + Python::with_gil(|py| -> Result, String> { + // Env -> Python list + let mut elems: Vec> = Vec::with_capacity(args.len()); + for v in &args.env { + let obj = D::py_val_to_py(py, v.clone()) + .map_err(|e| e.to_string())?; + elems.push(obj); + } + + let tuple_bound = PyTuple::new(py, &elems) // Bound + .map_err(|e| e.to_string())?; + + let ret = (**pyf) + .bind(py) + .call1(tuple_bound) + .map_err(|e| e.to_string())?; + + // Python -> Val + D::py_py_to_val(&ret) + }) + } + None => Err("primitive has no function pointer".into()), + } } } @@ -93,8 +100,6 @@ impl Production { arity: 0, lazy_args: Default::default(), fn_ptr: None, - #[cfg(feature = "python")] - py_fn: None, } } @@ -106,9 +111,7 @@ impl Production { tp, arity, lazy_args, - fn_ptr: Some(fn_ptr), - #[cfg(feature = "python")] - py_fn: None, + fn_ptr: Some(FnPtr::Native(fn_ptr)), } } @@ -143,8 +146,8 @@ impl DSL { /// Early-capture install of a Python-backed primitive. /// - `name`: symbol of the primitive - /// - `tp`: its type (you already have SlowType; we use that) - /// - `lazy_args`: optional indices of lazy arguments; use None for strict + /// - `tp`: type + /// - `lazy_args`: optional indices of lazy arguments /// - `pyfunc`: owned Python callable captured into this production #[cfg(feature = "python")] pub fn add_python_primitive( @@ -161,27 +164,21 @@ impl DSL { use crate::eval::{CurriedFn, Val}; // for PrimFun constructor - // Insert or update the production entry for this symbol. let entry = self.productions.entry(name.clone()).or_insert_with(|| Production { name: name.clone(), val: Val::PrimFun(CurriedFn::::new(name.clone(), arity)), tp: tp.clone(), arity, lazy_args: lazy.clone(), - fn_ptr: None, // no native body required - #[cfg(feature = "python")] - py_fn: None, // will set below + fn_ptr: None, }); - // Keep metadata consistent if it already existed entry.tp = tp; entry.arity = arity; entry.lazy_args = lazy; entry.val = Val::PrimFun(CurriedFn::::new(name, arity)); #[cfg(feature = "python")] - {entry.py_fn = Some(Arc::new(pyfunc));} // <-- early-captured callable lives here - // NOTE: we leave `fn_ptr` as-is; it can be None or Some(native). - // If you prefer “no conflict”, you can set `entry.fn_ptr = None;` + {entry.fn_ptr = Some(FnPtr::Python(Arc::new(pyfunc)));} } } From 46c7c8e06065eee8b309a5cee7fa651b4ef4ad5b Mon Sep 17 00:00:00 2001 From: meso03 Date: Mon, 8 Dec 2025 10:14:04 -0500 Subject: [PATCH 05/10] Py domain can register constants --- src/dsl.rs | 7 +++++++ src/eval.rs | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/dsl.rs b/src/dsl.rs index 9741990..7fa820c 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -128,6 +128,13 @@ impl DSL { self.productions.insert(entry.name.clone(), entry); } + /// NEW: add a constant (arity 0) to the DSL + pub fn add_constant(&mut self, name: Symbol, tp: SlowType, val: Val) { + assert_eq!(tp.arity(), 0); + let prod = Production::val_raw(name, tp, val); + self.add_entry(prod); + } + /// given a primitive's symbol return a runtime Val object. For function primitives /// this should return a PrimFun(CurriedFn) object. pub fn val_of_prim(&self, p: &Symbol) -> Option> { diff --git a/src/eval.rs b/src/eval.rs index e7da4ab..0ce353d 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -143,7 +143,7 @@ impl Val { pub fn dom(self) -> Result { match self { Val::Dom(d) => Ok(d), - _ => Err("Val::unwrap_dom: not a domain value".into()) + _ => Err(format!("{:?}", self)) //("Val::unwrap_dom: not a domain value".into()) } } #[inline(always)] From 7c1bcdc76bad353c4190b3844d4faedef828d64e Mon Sep 17 00:00:00 2001 From: Meso Date: Thu, 19 Feb 2026 15:11:59 -0500 Subject: [PATCH 06/10] Made a create python dsl function and removed it from dsl.rs --- src/domains/mod.rs | 2 +- src/domains/py.rs | 29 +++++++++++++++++++++++++++-- src/dsl.rs | 44 -------------------------------------------- 3 files changed, 28 insertions(+), 47 deletions(-) diff --git a/src/domains/mod.rs b/src/domains/mod.rs index fb192e7..93959cb 100644 --- a/src/domains/mod.rs +++ b/src/domains/mod.rs @@ -4,6 +4,6 @@ pub mod simple; pub mod prim_lists; #[cfg(feature = "python")] -pub mod simple_python; // <-- gate the module declaration +pub mod simple_python; pub mod py; \ No newline at end of file diff --git a/src/domains/py.rs b/src/domains/py.rs index 7e91f93..1292f88 100644 --- a/src/domains/py.rs +++ b/src/domains/py.rs @@ -1,9 +1,9 @@ use crate::*; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; #[cfg(feature = "python")] use pyo3::prelude::*; -// #[cfg(feature = "python")] -// use pyo3::types::PyTuple; #[cfg(feature = "python")] use pyo3::types::PyAny; @@ -29,6 +29,31 @@ type Env = crate::eval::Env; // to more concisely refer to the variants use PyVal::*; +#[cfg(feature = "python")] +pub fn create_python_production( + name: Symbol, + tp: SlowType, + lazy_args: Option<&[usize]>, + pyfunc: Py, +) -> Production { + let arity = tp.arity(); + let lazy: HashSet = lazy_args + .map(|xs| xs.iter().copied().collect()) + .unwrap_or_default(); + + use crate::eval::{CurriedFn, Val}; + + Production { + name: name.clone(), + val: Val::PrimFun(CurriedFn::::new(name.clone(), arity)), + tp, + arity, + lazy_args: lazy, + fn_ptr: None, + py_fn: Some(Arc::new(pyfunc)), + } +} + // From impls are needed for unwrapping values. We can assume the program // has been type checked so it's okay to panic if the type is wrong. Each val variant // must map to exactly one unwrapped type (though it doesnt need to be one to one in the diff --git a/src/dsl.rs b/src/dsl.rs index 7fa820c..308eed1 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -147,50 +147,6 @@ impl DSL { D::type_of_dom_val(&self.val_of_prim(p).unwrap().dom().unwrap()) }) } - - /// Early-capture install of a Python-backed primitive. - /// - `name`: symbol of the primitive - /// - `tp`: its type (you already have SlowType; we use that) - /// - `lazy_args`: optional indices of lazy arguments; use None for strict - /// - `pyfunc`: owned Python callable captured into this production - #[cfg(feature = "python")] - pub fn add_python_primitive( - &mut self, - name: Symbol, - tp: SlowType, - lazy_args: Option<&[usize]>, - pyfunc: Py, - ) { - let arity = tp.arity(); - let lazy: HashSet = lazy_args - .map(|xs| xs.iter().copied().collect()) - .unwrap_or_default(); - - use crate::eval::{CurriedFn, Val}; // for PrimFun constructor - - // Insert or update the production entry for this symbol. - let entry = self.productions.entry(name.clone()).or_insert_with(|| Production { - name: name.clone(), - val: Val::PrimFun(CurriedFn::::new(name.clone(), arity)), - tp: tp.clone(), - arity, - lazy_args: lazy.clone(), - fn_ptr: None, // no native body required - #[cfg(feature = "python")] - py_fn: None, // will set below - }); - - // Keep metadata consistent if it already existed - entry.tp = tp; - entry.arity = arity; - entry.lazy_args = lazy; - entry.val = Val::PrimFun(CurriedFn::::new(name, arity)); - #[cfg(feature = "python")] - {entry.py_fn = Some(Arc::new(pyfunc));} // <-- early-captured callable lives here - // NOTE: we leave `fn_ptr` as-is; it can be None or Some(native). - // If you prefer “no conflict”, you can set `entry.fn_ptr = None;` - } - } From f1a8d7ef3742e914a27aa7d937ca7df616f74dd8 Mon Sep 17 00:00:00 2001 From: Meso Date: Thu, 19 Feb 2026 15:19:23 -0500 Subject: [PATCH 07/10] Revert unnecessary changes --- src/domains/simple.rs | 4 ++-- src/lib.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/domains/simple.rs b/src/domains/simple.rs index 85fa89b..d650c93 100644 --- a/src/domains/simple.rs +++ b/src/domains/simple.rs @@ -118,10 +118,10 @@ impl Domain for SimpleVal { } - } +} - // *** DSL FUNCTIONS *** +// *** DSL FUNCTIONS *** // See comments throughout pointing out useful aspects fn add(mut args: Env, _handle: &Evaluator) -> VResult { diff --git a/src/lib.rs b/src/lib.rs index 1925620..823cc22 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #[macro_use] mod macros; + mod expr; mod dsl; mod eval; From abfce3287e2ddc01b6a5a7e3c16e7aa1d6935a46 Mon Sep 17 00:00:00 2001 From: Meso Date: Thu, 19 Feb 2026 15:35:59 -0500 Subject: [PATCH 08/10] Removed unwanted lines --- src/dsl.rs | 1 + src/eval.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/dsl.rs b/src/dsl.rs index c7172ff..362b5b4 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -150,6 +150,7 @@ impl DSL { D::type_of_dom_val(&self.val_of_prim(p).unwrap().dom().unwrap()) }) } + } diff --git a/src/eval.rs b/src/eval.rs index 0ce353d..e7da4ab 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -143,7 +143,7 @@ impl Val { pub fn dom(self) -> Result { match self { Val::Dom(d) => Ok(d), - _ => Err(format!("{:?}", self)) //("Val::unwrap_dom: not a domain value".into()) + _ => Err("Val::unwrap_dom: not a domain value".into()) } } #[inline(always)] From d40f6dc1caa80a759c40acf01bd10d3047957d3a Mon Sep 17 00:00:00 2001 From: Meso Date: Thu, 26 Feb 2026 00:57:10 -0500 Subject: [PATCH 09/10] Addressed code review comments --- src/domains/py.rs | 47 ++++++++++++------------------------ src/domains/simple_python.rs | 11 ++------- src/dsl.rs | 2 +- src/eval.rs | 1 - 4 files changed, 18 insertions(+), 43 deletions(-) diff --git a/src/domains/py.rs b/src/domains/py.rs index 6a611e2..25a733b 100644 --- a/src/domains/py.rs +++ b/src/domains/py.rs @@ -1,10 +1,9 @@ use crate::*; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; use std::sync::Arc; -#[cfg(feature = "python")] + use pyo3::prelude::*; -#[cfg(feature = "python")] use pyo3::types::PyAny; #[derive(Clone,Debug, PartialEq, Eq, Hash)] @@ -19,11 +18,8 @@ pub enum PyType { TList } -// aliases of various typed specialized to our PyVal + type Val = crate::eval::Val; -type Evaluator<'a> = crate::eval::Evaluator<'a,PyVal>; -type VResult = crate::eval::VResult; -type Env = crate::eval::Env; #[cfg(feature = "python")] @@ -46,8 +42,7 @@ pub fn create_python_production( tp, arity, lazy_args: lazy, - fn_ptr: None, - py_fn: Some(Arc::new(pyfunc)), + fn_ptr: Some(FnPtr::Python(Arc::new(pyfunc))) } } @@ -58,7 +53,7 @@ pub fn create_python_production( impl FromVal for i32 { fn from_val(v: Val) -> Result { match v { - Dom(Int(i)) => Ok(i), + Dom(PyVal::Int(i)) => Ok(i), _ => Err("from_val_to_i32: not an int".into()) } } @@ -66,7 +61,7 @@ impl FromVal for i32 { impl> FromVal for Vec { fn from_val(v: Val) -> Result { match v { - Dom(List(v)) => v.into_iter().map(|v| T::from_val(v)).collect(), + Dom(PyVal::List(v)) => v.into_iter().map(|v| T::from_val(v)).collect(), _ => Err("from_val_to_vec: not a list".into()) } } @@ -74,12 +69,12 @@ impl> FromVal for Vec { impl From for Val { fn from(i: i32) -> Val { - Dom(Int(i)) + Dom(PyVal::Int(i)) } } impl> From> for Val { fn from(vec: Vec) -> Val { - Dom(List(vec.into_iter().map(|v| v.into()).collect())) + Dom(PyVal::List(vec.into_iter().map(|v| v.into()).collect())) } } @@ -97,37 +92,25 @@ impl Domain for PyVal { } fn new_dsl() -> DSL { - let mut prods = vec![]; + let prods = vec![]; let dsl = DSL::new(prods); dsl } fn val_of_prim_fallback(p: &Symbol) -> Option { - // if p.chars().next().unwrap().is_ascii_digit() { - // let i: i32 = p.parse().ok()?; - // Some(Int(i).into()) - // } - // // starts with `[` -> List (must be all ints) - // else if p.starts_with('[') { - // let intvec: Vec = serde_json::from_str(p).ok()?; - // let valvec: Vec = intvec.into_iter().map(|v|Dom(Int(v))).collect(); - // Some(List(valvec).into()) - // } else { - // None - // } - None // We will add this later if we want to + None } fn type_of_dom_val(&self) -> SlowType { match self { - Int(_) => SlowType::base(Symbol::from("int")), - List(xs) => { + PyVal::Int(_) => SlowType::base(Symbol::from("int")), + PyVal::List(xs) => { let elem_tp = if xs.is_empty() { SlowType::Var(0) // (list t0) } else { - // todo here we just use the type of the first entry as the type - Self::type_of_dom_val(&xs.first().unwrap().clone().dom().unwrap()) - // assert!(xs.iter().all(|v| Self::type_of_dom_val(v.clone().dom().unwrap()))) + let result = Self::type_of_dom_val(&xs.first().unwrap().clone().dom().unwrap()); + assert!(xs.iter().all(|v| result == Self::type_of_dom_val(&v.clone().dom().unwrap()))); + result }; SlowType::Term("list".into(),vec![elem_tp]) }, diff --git a/src/domains/simple_python.rs b/src/domains/simple_python.rs index 2d64e01..32c6573 100644 --- a/src/domains/simple_python.rs +++ b/src/domains/simple_python.rs @@ -1,40 +1,33 @@ -//lambdas/src/domains/simple_python.rs - #![cfg(feature = "python")] use pyo3::prelude::*; use pyo3::types::PyList; -use pyo3::conversion::IntoPyObjectExt; // for .into_py_any on integers +use pyo3::conversion::IntoPyObjectExt; use crate::domains::py::PyVal; use crate::eval; type Val = eval::Val; use PyVal::*; -/// Rust Val -> Python object + pub fn val_to_py(py: Python<'_>, v: Val) -> PyResult> { match v.dom().expect("Val should be Dom") { Int(i) => { - // i32 -> Py Ok(i.into_py_any(py)?) } List(xs) => { - // Convert each element first let mut elems: Vec> = Vec::with_capacity(xs.len()); for x in xs { elems.push(val_to_py(py, x.clone())?); } - // IMPORTANT: PyList::new(...) -> PyResult> let list_bound: Bound<'_, PyList> = PyList::new(py, &elems)?; - // Bound -> Bound -> Py Ok(list_bound.into_any().unbind()) } } } -/// Python object -> Rust Val pub fn py_to_val(obj: &Bound<'_, PyAny>) -> Result { if let Ok(i) = obj.extract::() { return Ok(Val::from(Int(i))); diff --git a/src/dsl.rs b/src/dsl.rs index 362b5b4..e7aba12 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -52,7 +52,7 @@ impl Production { elems.push(obj); } - let tuple_bound = PyTuple::new(py, &elems) // Bound + let tuple_bound:Bound = PyTuple::new(py, &elems) .map_err(|e| e.to_string())?; let ret = (**pyf) diff --git a/src/eval.rs b/src/eval.rs index e7da4ab..26a55e9 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -131,7 +131,6 @@ impl CurriedFn { pub fn apply(mut self, arg: Val, handle: &Evaluator) -> VResult { self.partial_args.push_back(arg); if self.partial_args.len() == self.arity { - //handle.dsl.productions.get(&self.name).unwrap().fn_ptr.unwrap() (self.partial_args, handle) handle.dsl.productions.get(&self.name).unwrap().call(self.partial_args, handle) } else { Ok(Val::PrimFun(self)) From c28462b56861cb1ffe6cef89cdc62fa1306243f2 Mon Sep 17 00:00:00 2001 From: Meso Date: Thu, 26 Feb 2026 15:42:33 -0500 Subject: [PATCH 10/10] Clippy changes --- src/dsl.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dsl.rs b/src/dsl.rs index e7aba12..27086af 100644 --- a/src/dsl.rs +++ b/src/dsl.rs @@ -3,6 +3,7 @@ use crate::*; use std::collections::{HashMap, HashSet}; use std::fmt::{Debug}; use std::hash::Hash; +#[cfg(feature = "python")] use std::sync::Arc; #[cfg(feature = "python")]