flesh/src/stl/control.rs

386 lines
14 KiB
Rust
Raw Normal View History

/* 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 <http://www.gnu.org/licenses/>.
*/
use crate::eval::eval;
use crate::error::{Traceback, start_trace};
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<Ctr, Traceback> {
2023-02-28 11:12:27 -08:00
let cond: bool;
match *ast.car {
Ctr::Seg(ref cond_form) => {
let intermediate = eval(cond_form, syms);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
2023-02-28 11:12:27 -08:00
cond = cond_from_eval;
} else {
return Err(start_trace(("if", "first arg must be a bool").into()));
2023-02-28 11:12:27 -08:00
}
}
Ctr::Symbol(ref cond_name) => {
let intermediate = syms.call_symbol(cond_name, &Seg::new(), false);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval;
} else {
return Err(start_trace(("if", "first arg must be a bool").into()));
}
}
2023-02-28 11:12:27 -08:00
Ctr::Bool(cond_from_car) => cond = cond_from_car,
_ => return Err(start_trace(("if", "first arg must be a bool").into())),
2023-02-28 11:12:27 -08:00
}
let then_form: &Seg;
if let Ctr::Seg(ref s) = *ast.cdr {
then_form = s;
} else {
return Err(start_trace(("if", "not enough args").into()));
2023-02-28 11:12:27 -08:00
}
if cond {
// then
match *then_form.car {
Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating then form").into())),
Ok(val) => Ok(*val)
},
2023-02-28 11:12:27 -08:00
_ => {
let eval_tree = &Seg::from_mono(then_form.car.clone());
let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating then form").into()))
}
let eval_res = *eval_intermediate?;
2023-02-28 11:12:27 -08:00
if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone())
} else {
Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
2023-02-28 11:12:27 -08:00
}
}
2023-02-28 11:12:27 -08:00
}
} else {
// else
if let Ctr::Seg(ref else_form) = *then_form.cdr {
match *else_form.car {
Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating else form").into())),
Ok(val) => Ok(*val)
},
2023-02-28 11:12:27 -08:00
_ => {
let eval_tree = &Seg::from_mono(else_form.car.clone());
let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating else form").into()))
}
let eval_res = *eval_intermediate?;
2023-02-28 11:12:27 -08:00
if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone())
} else {
Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
}
}
}
} else {
Err(start_trace(("if", "impossible condition: args not in standard form").into()))
}
}
}
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<Ctr, Traceback> {
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(start_trace(("let", "first form does not contain list of local declarations")
.into()));
}
2023-02-28 11:12:27 -08:00
if let Ctr::Seg(ref eval_forms_head) = *ast.cdr {
eval_forms = eval_forms_head;
} else {
return Err(start_trace(("let", "missing one or more forms to evaluate").into()));
}
let mut err_trace: Traceback = Traceback::new();
// 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<Box<Ctr>, Traceback>;
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 {
err_trace = e
.with_trace(
("let", format!("failed to evaluate definition of {}", name))
.into());
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 {
err_trace = start_trace(
("let", format!("improper declaration of {}: not a list", var_form))
.into());
return false;
}
} else {
err_trace = start_trace(
("let", format!("improper declaration form: {}", var_decl))
.into());
return false;
}
true
}) {
assert!(err_trace.depth() > 0);
return Err(err_trace);
}
assert!(err_trace.depth() < 1);
let mut result: Box<Ctr> = Box::new(Ctr::None);
if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool {
let res: Result<Box<Ctr>, Traceback>;
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 {
err_trace = e.with_trace(
("let", "evaluation failure")
.into());
return false;
}
result = res.unwrap().clone();
true
}) {
assert!(err_trace.depth() > 0);
return Err(err_trace);
}
assert!(err_trace.depth() < 1);
for i in locals {
localsyms.remove(&i);
}
syms.update(&mut localsyms);
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<Ctr, Traceback> {
let eval_cond: &Seg;
let outer_maybe: Seg;
let eval_bodies_head: &Seg;
let mut unwrap = false;
let mut result: Result<Box<Ctr>, Traceback> = 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(start_trace(("while", "expected one or more forms to evaluate").into()));
}
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(start_trace(("while", "expected first form to evaluate to a boolean").into()));
}
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().with_trace(("while", "evaluation failure").into()));
}
}
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<Ctr, Traceback> {
let mut cursor = 0;
let mut err_trace = Traceback::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) => err_trace = s.with_trace(
("circuit", format!("failed at form {cursor}"))
.into()),
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 if let Ctr::Integer(i) = *s.car {
return i==0;
} else {
err_trace = err_trace.with_trace(
("circuit", "impossible condition")
.into());
}
},
Ctr::Integer(i) => return i == 0,
_ => err_trace = err_trace.with_trace(
("circuit", format!("form {cursor} did not evaluate to a boolean"))
.into()),
},
}
false
});
if !result && err_trace.depth() > 0 {
Err(err_trace)
} else {
Ok(Ctr::Bool(result))
}
}