Compare commits

2 Commits
Author SHA1 Message Date
jonas 5215a2e678 feat(demo): add demo-4 2026-09-12 18:52:38 +02:00
jonas 66c3ab128f feat(prelude): lambda capture 2026-09-12 18:52:20 +02:00
9 changed files with 233 additions and 31 deletions
+25 -1
View File
@@ -1,5 +1,5 @@
use super::{expression::Expression, prelude::mk_prelude}; use super::{expression::Expression, prelude::mk_prelude};
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
/// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from /// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from
@@ -136,6 +136,30 @@ impl Default for Environment<'_> {
} }
} }
impl Display for Environment<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = &self.outer {
write!(f, "{}<{}", self.layer, x)
} else {
write!(f, "")
}
}
}
impl Display for EnvironmentLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{{}}}",
self.symbols
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[test] #[test]
fn test_environment() { fn test_environment() {
let mut env = Environment::new(); let mut env = Environment::new();
+21 -10
View File
@@ -81,6 +81,7 @@ impl Iterator for CellIterator {
fn dispatch_anonymous_function( fn dispatch_anonymous_function(
env: &Environment, env: &Environment,
argument_symbols: Vec<String>, argument_symbols: Vec<String>,
capture: EnvironmentLayer,
body: Expression, body: Expression,
args: Expression, args: Expression,
) -> Result<Expression, EvalError> { ) -> Result<Expression, EvalError> {
@@ -97,23 +98,33 @@ fn dispatch_anonymous_function(
} }
for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) { for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) {
overlay.set(symbol.to_owned(), eval(env, arg.to_owned())?); overlay.set(symbol.to_owned(), eval(&env, arg.to_owned())?);
} }
eval(&env.overlay(overlay), body) eval(&env.overlay(capture).overlay(overlay), body)
}
/// Dispatch a function inside an environment
fn dispatch_function(
env: &Environment,
f: Expression,
args: Expression,
) -> Result<Expression, EvalError> {
match f {
Expression::Function(f) => f(env, args),
Expression::AnonymousFunction {
argument_symbols,
capture,
body,
} => dispatch_anonymous_function(env, argument_symbols, capture, *body, args),
a => Err(EvalError::NotAFunction(a)),
}
} }
/// Evaluate an expression inside an environment /// Evaluate an expression inside an environment
pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
match expr { match expr {
Expression::Cell(lhs, rhs) => match eval(env, *lhs)? { Expression::Cell(lhs, rhs) => dispatch_function(env, eval(env, *lhs)?, *rhs),
Expression::Function(f) => f(env, *rhs),
Expression::AnonymousFunction {
argument_symbols,
body,
} => dispatch_anonymous_function(env, argument_symbols, *body, *rhs),
a => Err(EvalError::NotAFunction(a)),
},
Expression::Quote(e) => Ok(*e), Expression::Quote(e) => Ok(*e),
Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)), Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)),
x => Ok(x), x => Ok(x),
+33 -8
View File
@@ -1,3 +1,4 @@
use std::any::type_name;
use std::any::Any; use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
use std::fmt::Display; use std::fmt::Display;
@@ -7,6 +8,7 @@ use std::ops::DerefMut;
use as_any::AsAny; use as_any::AsAny;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer;
use super::eval::CellIterator; use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
@@ -131,6 +133,7 @@ pub enum Expression {
/// A anonymous function expression consisting of bound symbols and a body expression. /// A anonymous function expression consisting of bound symbols and a body expression.
AnonymousFunction { AnonymousFunction {
argument_symbols: Vec<String>, argument_symbols: Vec<String>,
capture: EnvironmentLayer,
body: Box<Expression>, body: Box<Expression>,
}, },
/// A foreign data expression. /// A foreign data expression.
@@ -159,17 +162,25 @@ impl PartialEq for Expression {
( (
AnonymousFunction { AnonymousFunction {
argument_symbols: args1, argument_symbols: args1,
capture: capture1,
body: body1, body: body1,
}, },
AnonymousFunction { AnonymousFunction {
argument_symbols: args2, argument_symbols: args2,
capture: capture2,
body: body2, body: body2,
}, },
) => PartialEq::eq(args1, args2) && PartialEq::eq(body1, body2), ) => {
PartialEq::eq(args1, args2)
&& PartialEq::eq(body1, body2)
&& PartialEq::eq(capture1, capture2)
}
(ForeignExpression(f1), ForeignExpression(f2)) => PartialEq::eq(f1, f2), (ForeignExpression(f1), ForeignExpression(f2)) => PartialEq::eq(f1, f2),
(Quote(e1), Quote(e2)) => PartialEq::eq(e1, e2), (Quote(e1), Quote(e2)) => PartialEq::eq(e1, e2),
(Symbol(s1), Symbol(s2)) => PartialEq::eq(s1, s2), (Symbol(s1), Symbol(s2)) => PartialEq::eq(s1, s2),
(Integer(i1), Integer(i2)) => PartialEq::eq(i1, i2),
(Float(f1), Float(f2)) => PartialEq::eq(f1, f2), (Float(f1), Float(f2)) => PartialEq::eq(f1, f2),
(String(s1), String(s2)) => PartialEq::eq(s1, s2),
(Nil, Nil) => true, (Nil, Nil) => true,
(True, True) => true, (True, True) => true,
_ => false, _ => false,
@@ -185,10 +196,12 @@ impl PartialOrd for Expression {
( (
AnonymousFunction { AnonymousFunction {
argument_symbols: args1, argument_symbols: args1,
capture: _,
body: body1, body: body1,
}, },
AnonymousFunction { AnonymousFunction {
argument_symbols: args2, argument_symbols: args2,
capture: _,
body: body2, body: body2,
}, },
) => args1 ) => args1
@@ -198,6 +211,8 @@ impl PartialOrd for Expression {
(Quote(e1), Quote(e2)) => e1.partial_cmp(e2), (Quote(e1), Quote(e2)) => e1.partial_cmp(e2),
(Symbol(s1), Symbol(s2)) => s1.partial_cmp(s2), (Symbol(s1), Symbol(s2)) => s1.partial_cmp(s2),
(Float(f1), Float(f2)) => f1.partial_cmp(f2), (Float(f1), Float(f2)) => f1.partial_cmp(f2),
(Integer(i1), Integer(i2)) => i1.partial_cmp(i2),
(String(s1), String(s2)) => s1.partial_cmp(s2),
(Nil, Nil) => Some(std::cmp::Ordering::Equal), (Nil, Nil) => Some(std::cmp::Ordering::Equal),
(True, True) => Some(std::cmp::Ordering::Equal), (True, True) => Some(std::cmp::Ordering::Equal),
_ => None, _ => None,
@@ -217,13 +232,16 @@ impl<T: ForeignData> TryFrom<Expression> for ForeignDataWrapper<T> {
match value { match value {
Expression::ForeignExpression(f) => match f.as_any_box().downcast::<T>() { Expression::ForeignExpression(f) => match f.as_any_box().downcast::<T>() {
Ok(data) => Ok(ForeignDataWrapper(data)), Ok(data) => Ok(ForeignDataWrapper(data)),
Err(_) => Err(EvalError::TypeError( Err(a) => Err(EvalError::TypeError(format!(
"Expression is not a ForeignDataWrapper".to_string(), "Expression (type={}) is not a ForeignDataWrapper of type {}",
)), a.type_name(),
type_name::<T>()
))),
}, },
_ => Err(EvalError::TypeError( e => Err(EvalError::TypeError(format!(
"Expression is not a ForeignDataWrapper".to_string(), "Expression ({}) is not a ForeignDataWrapper",
)), e
))),
} }
} }
} }
@@ -409,8 +427,15 @@ impl Display for Expression {
Expression::Function(_) => write!(f, "<function>"), Expression::Function(_) => write!(f, "<function>"),
Expression::AnonymousFunction { Expression::AnonymousFunction {
argument_symbols, argument_symbols,
capture,
body, body,
} => write!(f, "(lambda ({}) {})", argument_symbols.join(" "), body), } => write!(
f,
"(lambda (capture={}) ({}) {})",
capture,
argument_symbols.join(" "),
body
),
Expression::Quote(e) => write!(f, "'{}", e), Expression::Quote(e) => write!(f, "'{}", e),
Expression::Symbol(s) => write!(f, "{}", s), Expression::Symbol(s) => write!(f, "{}", s),
Expression::Integer(i) => write!(f, "{}", i), Expression::Integer(i) => write!(f, "{}", i),
+41 -3
View File
@@ -8,6 +8,7 @@ use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
use super::expression::Expression; use super::expression::Expression;
use std::collections::HashMap; use std::collections::HashMap;
use std::collections::VecDeque;
use std::path::PathBuf; use std::path::PathBuf;
pub fn prelude_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_add(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
@@ -82,7 +83,33 @@ pub fn prelude_div(env: &Environment, expr: Expression) -> Result<Expression, Ev
} }
} }
pub fn prelude_lambda(_env: &Environment, expr: Expression) -> Result<Expression, EvalError> { fn capture_variables(env: &Environment, expr: &Expression) -> Result<EnvironmentLayer, EvalError> {
let mut capture: HashMap<String, Expression> = HashMap::new();
let mut q = VecDeque::new();
q.push_back(expr);
while let Some(expr) = q.pop_front() {
match expr {
Expression::Symbol(s) => {
if let Some(expr) = env.get(&s) {
capture.insert(s.clone(), expr);
}
}
Expression::Cell(x, xs) => {
q.push_back(&*x);
q.push_back(&*xs);
}
Expression::Quote(x) => {
q.push_back(&*x);
}
_ => {}
}
}
Ok(capture.into())
}
pub fn prelude_lambda(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [args, body]: [Expression; 2] = expr.try_into()?; let [args, body]: [Expression; 2] = expr.try_into()?;
let mut arg_exprs: Vec<Expression> = args.try_into()?; let mut arg_exprs: Vec<Expression> = args.try_into()?;
let argument_symbols: Vec<String> = arg_exprs let argument_symbols: Vec<String> = arg_exprs
@@ -92,8 +119,10 @@ pub fn prelude_lambda(_env: &Environment, expr: Expression) -> Result<Expression
x => Err(EvalError::NotASymbol(x.to_owned())), x => Err(EvalError::NotASymbol(x.to_owned())),
}) })
.collect::<Result<Vec<String>, EvalError>>()?; .collect::<Result<Vec<String>, EvalError>>()?;
Ok(Expression::AnonymousFunction { Ok(Expression::AnonymousFunction {
argument_symbols, argument_symbols,
capture: capture_variables(&env, &body)?,
body: Box::new(body), body: Box::new(body),
}) })
} }
@@ -115,6 +144,7 @@ pub fn prelude_defun(env: &Environment, expr: Expression) -> Result<Expression,
let f = Expression::AnonymousFunction { let f = Expression::AnonymousFunction {
argument_symbols, argument_symbols,
capture: EnvironmentLayer::new(),
body: Box::new(body), body: Box::new(body),
}; };
env.shared_set(name, f.clone()); env.shared_set(name, f.clone());
@@ -152,9 +182,11 @@ pub fn prelude_let(env: &Environment, expr: Expression) -> Result<Expression, Ev
} }
pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [predicate, e_then, e_else] = expr.try_into()?; let [predicate, e_then, e_else]: [Expression; 3] = expr.try_into()?;
match eval(env, predicate)? { let val = eval(env, predicate.clone())?;
match val {
Expression::Nil => eval(env, e_else), Expression::Nil => eval(env, e_else),
_ => eval(env, e_then), _ => eval(env, e_then),
} }
@@ -348,18 +380,24 @@ pub fn prelude_include(env: &Environment, expr: Expression) -> Result<Expression
let [expr] = expr.try_into()?; let [expr] = expr.try_into()?;
let lisp_file: String = eval(env, expr)?.try_into()?; let lisp_file: String = eval(env, expr)?.try_into()?;
println!("From File: {}", env.get("FILE").unwrap());
// Try to resolve as relative to FILE // Try to resolve as relative to FILE
let resolved_lisp_file: PathBuf = PathBuf::from( let resolved_lisp_file: PathBuf = PathBuf::from(
env.get("FILE") env.get("FILE")
.map(|x| x.try_into()) .map(|x| x.try_into())
.unwrap_or(Ok(String::new()))?, .unwrap_or(Ok(String::new()))?,
) )
.canonicalize()
.map_err(|x| EvalError::RuntimeError(x.to_string()))?
.parent() .parent()
.ok_or(EvalError::RuntimeError( .ok_or(EvalError::RuntimeError(
"Could not get parent of current file.".to_string(), "Could not get parent of current file.".to_string(),
))? ))?
.join(&lisp_file); .join(&lisp_file);
println!("Loading {}", resolved_lisp_file.display());
let lisp_string = std::fs::read_to_string(&resolved_lisp_file) let lisp_string = std::fs::read_to_string(&resolved_lisp_file)
.map_err(|e| EvalError::RuntimeError(e.to_string()))?; .map_err(|e| EvalError::RuntimeError(e.to_string()))?;
+2
View File
@@ -11,6 +11,7 @@ pub enum ParserError {
UnexpectedToken(Token), UnexpectedToken(Token),
TokenizerError(TokenizerError), TokenizerError(TokenizerError),
UnexpectedEndOfInput, UnexpectedEndOfInput,
RuntimeError(String),
} }
impl From<TokenizerError> for ParserError { impl From<TokenizerError> for ParserError {
@@ -25,6 +26,7 @@ impl Display for ParserError {
ParserError::TokenizerError(t) => write!(f, "Tokenizer Error: {}", t), ParserError::TokenizerError(t) => write!(f, "Tokenizer Error: {}", t),
ParserError::UnexpectedToken(t) => write!(f, "Unexpecte Token: {}", t), ParserError::UnexpectedToken(t) => write!(f, "Unexpecte Token: {}", t),
ParserError::UnexpectedEndOfInput => write!(f, "Unexpected end of input."), ParserError::UnexpectedEndOfInput => write!(f, "Unexpected end of input."),
ParserError::RuntimeError(s) => write!(f, "{}", s),
} }
} }
} }
+48
View File
@@ -0,0 +1,48 @@
(include "./materials.lisp")
(set 'frames 300)
(set 'fps 30)
(set 'ray-depth 5)
(set 'sub-pixel 2)
(set 'mz (mandel-zoom frames 1800 50000))
(println (mz 1))
(defun mandel-plane (t)
(texture-plane
(mz t)
(point 0 0 0)
(vector 0 1 0)
1.0
(vector 1 0 0)
))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(defun scn (t)
(scene
(color 0.1 0.1 0.1)
'((mandel-plane t))
'(l1 l2)
))
(defun cam (t c) c)
(set 'base-cam
(camera
(point 0 3 6)
(point 0 0 0)
(vector 0 1 0)
40 1920 1080))
(render-animation
base-cam
"demo-4-animation.mp4"
scn
cam
frames
fps
ray-depth
sub-pixel)
+29
View File
@@ -35,3 +35,32 @@
(color 0.01 0.05 0.15) (color 0.01 0.05 0.15)
(color 0.01 0.05 0.15) (color 0.01 0.05 0.15)
20 0.7)) 20 0.7))
(set 'mandelbrot-red
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
))
(defun mandel-zoom (n z1 z2)
(let '((n . n)
(z1 . z1)
(z2 . z2))
(lambda (t)
(let '((pct . (/ t (* n 1.0)))
(range . (- z2 z1)))
(mandelbrot-texture
(+ z1 (* pct range))
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
)
)))
)
+14 -2
View File
@@ -8,15 +8,24 @@ fn main() {
"(print myvar) (print 'myvar)", "(print myvar) (print 'myvar)",
"(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)", "(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)",
"(eval (car (cons 'myvar 'b)))", "(eval (car (cons 'myvar 'b)))",
"(defun test (i) (progn (println i) (* 2 i)))",
"(test 10)",
"(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))", "(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))",
"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))",
"(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 () (println 'hello)) 5)",
"(progn (print 'hello) (print 'world))", "(progn (print 'hello) (print 'world))",
"(load \"(defun loaded-foo (x) (+ x 1))\")", "(load \"(defun loaded-foo (x) (+ x 1))\")",
"(loaded-foo 1)", "(loaded-foo 1)",
"(defun bind-test (x) (let '((x . x)) (lambda (y) (+ x y))))",
"(set 'add10 (bind-test 10))",
"(add10 10)",
"(set 'doAdd (let '((a . 20)) (lambda () (add10 a))))",
"(doAdd)",
"(defun mkDoubleClosure (a b c) (let '((x . (+ a b)) (y . (* b c))) (lambda (z) (let '((d . (+ z x))) (* d y)))))",
"((mkDoubleClosure 1 2 3) 2)",
]; ];
let environment = Environment::default(); let environment = Environment::default();
@@ -31,7 +40,10 @@ fn main() {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }
+20 -7
View File
@@ -25,11 +25,6 @@ fn main() {
} }
} }
let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer);
mk_raytrace(&mut layer);
let environment = Environment::from_layer(layer);
let args: Vec<_> = std::env::args().collect(); let args: Vec<_> = std::env::args().collect();
if args.len() != 2 { if args.len() != 2 {
@@ -41,6 +36,21 @@ fn main() {
return; return;
} }
let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer);
mk_raytrace(&mut layer);
let mut environment = Environment::from_layer(layer);
environment.set(
"FILE".to_string(),
scenes
.get(&args[1])
.expect("Scene file not found")
.path()
.display()
.to_string()
.into(),
);
for r in ExpressionStream::from_char_stream( for r in ExpressionStream::from_char_stream(
std::fs::read_to_string(scenes.get(&args[1]).expect("Scene file not found").path()) std::fs::read_to_string(scenes.get(&args[1]).expect("Scene file not found").path())
.expect("Failed to read scene file") .expect("Failed to read scene file")
@@ -49,13 +59,16 @@ fn main() {
match r { match r {
Err(err) => { Err(err) => {
println!("ParserError: {:?}", err); println!("ParserError: {:?}", err);
break; return;
} }
Ok(expr) => { Ok(expr) => {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }