/* relish: versatile lisp shell * Copyright (C) 2021 Aidan Hahn * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ use crate::eval::eval; use crate::segment::{Ctr, Seg}; use crate::sym::{SymTable, Symbol}; pub const IF_DOCSTRING: &str = "accepts three bodies, a condition, an unevaluated consequence, and an alternative consequence. If the condition is evaluated to true, the first consequence is evaluated. If the condition is evaluated to false, the second consequence is evaluated. Otherwise, an error is thrown. example: (if my-state-switch (do-my-thing) (else-an-other-thing))"; pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result { let cond: bool; println!("Y: {}", *ast.car); match *ast.car { Ctr::Seg(ref cond_form) => { if let Ctr::Bool(cond_from_eval) = *eval(cond_form, syms)? { cond = cond_from_eval; } else { return Err("first argument to if must evaluate to be a boolean".to_string()); } } Ctr::Symbol(ref cond_name) => { if let Ctr::Bool(cond_from_eval) = *syms.call_symbol(cond_name, &Seg::new(), false)? { cond = cond_from_eval; } else { return Err("first argument to if must evaluate to be a boolean".to_string()); } } Ctr::Bool(cond_from_car) => cond = cond_from_car, _ => return Err("first argument to if must evaluate to be a boolean".to_string()), } let then_form: &Seg; if let Ctr::Seg(ref s) = *ast.cdr { then_form = s; } else { return Err("impossible condition: not enough args to if".to_string()); } if cond { // then match *then_form.car { Ctr::Seg(ref first_arg) => Ok(*eval(first_arg, syms)?), _ => { let eval_tree = &Seg::from_mono(then_form.car.clone()); let eval_res = *eval(eval_tree, syms)?; if let Ctr::Seg(ref s) = eval_res { Ok(*s.car.clone()) } else { Err("impossible condition: list evals to non list".to_string()) } } } } else { // else if let Ctr::Seg(ref else_form) = *then_form.cdr { match *else_form.car { Ctr::Seg(ref second_arg) => Ok(*eval(second_arg, syms)?), _ => { let eval_tree = &Seg::from_mono(else_form.car.clone()); let eval_res = *eval(eval_tree, syms)?; if let Ctr::Seg(ref s) = eval_res { Ok(*s.car.clone()) } else { Err("impossible condition: list evals to non list".to_string()) } } } } else { Err("impossible condition: args not in standard form".to_string()) } } } pub const LET_DOCSTRING: &str = "creates a stack of local variables for a sequence of operations. returns the result of the final operation. example: (let ((step1 'hello') (step2 (concat step1 '-')) (step3 (concat step2 'world'))) (echo step3) (some-func some-args)) In this example step1, step2, and step3 are created sequentially. Then, the echo form is evaluated, printing 'hello-world'. Finally, the some-func form is evaluated. Since the call to some-func is the final form, its value is returned."; pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result { let mut localsyms = syms.clone(); let mut locals = vec![]; let locals_form: &Seg; let eval_forms: &Seg; if let Ctr::Seg(ref locals_form_list) = *ast.car { locals_form = locals_form_list; } else { return Err("first element of let form must contain local variables".to_string()); } if let Ctr::Seg(ref eval_forms_head) = *ast.cdr { eval_forms = eval_forms_head; } else { return Err("let form should contain one or more elements to evaluate".to_string()); } // process locals forms if !locals_form.circuit(&mut |var_decl: &Ctr| -> bool { if let Ctr::Seg(ref var_form) = *var_decl { if let Ctr::Symbol(ref name) = *var_form.car { if let Ctr::Seg(ref var_val_form) = *var_form.cdr { let var_val_res: Result, String>; if let Ctr::Seg(ref val_form) = *var_val_form.car { var_val_res = eval(val_form, &mut localsyms); } else { let var_tree = Seg::from_mono(Box::new(*var_val_form.car.clone())); let intermediate = eval(&var_tree, &mut localsyms); if intermediate.is_err() { var_val_res = intermediate; } else if let Ctr::Seg(ref intermediate_result) = *intermediate.unwrap() { var_val_res = Ok(intermediate_result.car.clone()) } else { panic!() } } if let Err(e) = var_val_res { eprintln!("failed to evaluate definition of {}: {}", name, e); return false; } localsyms.insert( name.clone(), Symbol::from_ast( name, &"variable used in let form".to_string(), &Seg::from_mono(Box::new(*var_val_res.unwrap().clone())), None), ); locals.push(name.clone()); } } else if let Ctr::None = *var_form.car { // nothing to declare return true; } else { eprintln!("improper declaration of {}: not a list", var_decl); return false; } } else { eprintln!("improper declaration form: {}", var_decl); return false; } true }) { return Err("local variable declaration failure".to_string()); } let mut result: Box = Box::new(Ctr::None); if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool { let res: Result, String>; if let Ctr::Seg(ref eval_tree) = eval_form { res = eval(&eval_tree, &mut localsyms); } else { let eval_tree = Seg::from_mono(Box::new(eval_form.clone())); let intermediate = eval(&eval_tree, &mut localsyms); if intermediate.is_err() { res = intermediate; } else if let Ctr::Seg(ref intermediate_result) = *intermediate.unwrap() { res = Ok(intermediate_result.car.clone()) } else { panic!() } } if let Err(e) = res { eprintln!("{}", e); return false; } result = res.unwrap().clone(); true }) { return Err("evaluation failure".to_string()); } // we need to get any var declared from within the let eval forms // those need to exist in the outside context for i in localsyms.iter() { if !locals.contains(i.0) && !syms.contains_key(i.0) { syms.insert( i.0.clone(), i.1.clone(), ); } } Ok((*result).clone()) } pub const WHILE_DOCSTRING: &str = "traverses a list of N un-evaluated forms. the first form is expected to evaluate to a boolean. if it evaluates to false, while will stop and return. Otherwise, while will evaluate each form in a loop. example: (while (check-my-state) (do-thing-1 args) (do-thing-2 args) (edit-state my-state))"; pub fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result { let eval_cond: &Seg; let outer_maybe: Seg; let eval_bodies_head: &Seg; let mut unwrap = false; let mut result: Result, String> = Ok(Box::new(Ctr::None)); if let Ctr::Seg(ref cond) = *ast.car { eval_cond = cond; } else { outer_maybe = Seg::from_mono(ast.car.clone()); eval_cond = &outer_maybe; unwrap = true; } if let Ctr::Seg(ref eval) = *ast.cdr { eval_bodies_head = eval; } else { return Err("expected N more bodies in while form".to_string()); } loop { let cond_res = *eval(eval_cond, syms)?; if unwrap { if let Ctr::Seg(ref cond_res_inner) = cond_res { if let Ctr::Bool(ref b) = *cond_res_inner.car { if !b { break; } } } else { panic!() } } else if let Ctr::Bool(b) = cond_res { if !b { break; } } else { return Err("first body of while form should evaluate to a bool".to_string()); } if !eval_bodies_head.circuit(&mut |body: &Ctr| -> bool { let outer_scope_seg: Seg; let eval_arg: &Seg; if let Ctr::Seg(ref eval_body) = *body { eval_arg = eval_body; } else { outer_scope_seg = Seg::from_mono(Box::new(body.clone())); eval_arg = &outer_scope_seg; } result = eval(eval_arg, syms); result.is_ok() }) { return Err(result.err().unwrap()); } } Ok(*(result.unwrap()).clone()) } pub const CIRCUIT_DOCSTRING: &str = "traverses a list of N un-evaluated forms. evaluates each one until it stops. Circuit will stop when a form errors during evaluation. Circuit will also stop when a form does not evaluate to a boolean, or evaluates to false. example: (circuit (eq? (do-operation) myresult) (and state1 state2 (boolean-operation3)) false (do-another-operation)) in this example, do-another-operation will not be called"; pub fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result { let mut cursor = 0; let mut error: String = String::new(); let result = ast.circuit(&mut |form: &Ctr| -> bool { cursor += 1; let operand: &Seg; let mut expand_eval_res = false; let outer_scope_seg: Seg; if let Ctr::Seg(ref s) = form { operand = s; } else { outer_scope_seg = Seg::from_mono(Box::new(form.clone())); operand = &outer_scope_seg; expand_eval_res = true; } let eval_result = eval(operand, syms); match eval_result { Err(s) => error = format!("eval failed at form {cursor}: {s}"), Ok(s) => match *s { Ctr::Bool(b) => return b, Ctr::Seg(s) if expand_eval_res => { if let Ctr::Bool(b) = *s.car { return b; } else { error = "impossible condition in circuit form".to_string(); } } _ => error = format!("{cursor} form did not evaluate to a boolean"), }, } false }); if !result && !error.is_empty() { Err(format!("circuit stopped at form {cursor}: {error}")) } else { if !result { eprintln!("circuit stopped at form {cursor}"); } Ok(Ctr::Bool(result)) } }