diff --git a/Readme.org b/Readme.org index 4ec705b..cac5582 100644 --- a/Readme.org +++ b/Readme.org @@ -20,21 +20,18 @@ https://gitlab.com/whom/shs https://matrix.to/#/#vomitorium:matrix.sunnypup.io * How to use -** Syntax -*** Basic data types -TODO -*** S-Expressions -TODO -**** calling a function -TODO -*** Control flow -TODO -*** Defining variables and functions -TODO -**** Undefining variables and functions -TODO -*** Builtin functions -TODO +** TODO Syntax +*** TODO Basic data types +*** TODO S-Expressions +**** TODO calling a function +*** TODO Control flow +**** TODO if +**** TODO while +**** TODO let +**** TODO circuit +*** TODO Defining variables and functions +**** TODO Undefining variables and functions +*** TODO Builtin functions ** Configuration By default Relish will read from ~/.relishrc for configuration, but the default shell will also accept a filename from the RELISH_CFG_FILE environment variable. @@ -58,6 +55,8 @@ Errors during configuration are non-terminal. In such a case any defaults which - CFG_RELISH_PROMPT (default (echo "λ ")): A *function* definition which is called in order to output the prompt for each loop of the REPL. This function will be reloaded each REPL loop and will be called by the interpreter with no arguments. +** TODO Further configuration + ** Compilation #+BEGIN_SRC sh cargo build @@ -128,7 +127,7 @@ This contains any executable target of this project. Notably the main shell file Note: this section will not show the status of each item unless you are viewing it with a proper orgmode viewer *** TODO Rudimentary Control Flow **** DONE if clause -**** TODO loop clause +**** TODO let clause **** TODO while clause **** TODO circuit clause *** TODO Help function @@ -150,13 +149,18 @@ Will need a concatenate function for tables **** TODO head (returns (head rest)) **** TODO tail (returns (rest tail)) **** TODO queue (append to front) +**** TODO snippet for dequeue +**** TODO snippet for pop *** TODO boolean operations -**** TODO and +**** TODO and (circuit) **** TODO or **** TODO xor -**** TODO not +**** TODO no +**** TODO eq? **** TODO toggle *** TODO string operations +**** TODO contains +**** TODO len **** TODO concat **** TODO substr by index **** TODO split (on delimiter) @@ -171,6 +175,9 @@ Will need a concatenate function for tables **** TODO inc **** TODO dec **** TODO int (float to int) +**** TODO gt? +**** TODO lt? +**** TODO snippets for gte and lte *** TODO file operations **** TODO read-to-string **** TODO write-to-file diff --git a/src/stl.rs b/src/stl.rs index 976ac0b..e8211c6 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -69,6 +69,16 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> { }, ); + syms.insert( + "let".to_string(), + Symbol { + name: String::from("let"), + args: Args::Infinite, + conditional_branches: true, + value: ValueType::Internal(Rc::new(control::let_callback)), + }, + ); + Ok(()) } diff --git a/src/stl/control.rs b/src/stl/control.rs index cc9b4e0..f6bb064 100644 --- a/src/stl/control.rs +++ b/src/stl/control.rs @@ -17,7 +17,7 @@ use crate::eval::eval; use crate::segment::{Ctr, Seg}; -use crate::sym::SymTable; +use crate::sym::{SymTable, Symbol, ValueType, Args}; pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result { let cond: bool; @@ -46,7 +46,6 @@ pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result { match *then_form.car { Ctr::Seg(ref first_arg) => Ok(*eval(first_arg, syms)?), _ => { - //Ok(*eval(&Seg::from_mono(then_form.car.clone()), 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 { @@ -62,7 +61,6 @@ pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result { match *else_form.car { Ctr::Seg(ref second_arg) => Ok(*eval(second_arg, syms)?), _ => { - //Ok(*eval(&Seg::from_mono(then_form.car.clone()), 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 { @@ -78,18 +76,104 @@ pub fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result { } } -pub fn let_callback(_ast: &Seg, _syms: &mut SymTable) -> Result { - todo!() +pub fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result { + let mut localsyms = syms.clone(); + 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 + } + + let temp = var_val_res.clone().unwrap(); + println!("dbg: {}", temp); + + localsyms.insert(name.clone(), Symbol { + name: name.clone(), + args: Args::None, + conditional_branches: false, + value: ValueType::VarForm(Box::new(*var_val_res.unwrap().clone())), + }); + } + } 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!("error evaluating let form: {}", e); + return false + } + + result = res.unwrap().clone(); + true + }) { + return Err("evaluation failure".to_string()) + } + + Ok((*result).clone()) } pub fn while_callback(_ast: &Seg, _syms: &mut SymTable) -> Result { todo!() } -pub fn map_callback(_ast: &Seg, _syms: &mut SymTable) -> Result { - todo!() -} - pub fn circuit_callback(_ast: &Seg, _syms: &mut SymTable) -> Result { todo!() } diff --git a/src/sym.rs b/src/sym.rs index 96e6b4d..d9db7c2 100644 --- a/src/sym.rs +++ b/src/sym.rs @@ -19,6 +19,8 @@ use crate::eval::eval; use crate::segment::{Ctr, Seg, Type}; use std::collections::HashMap; use std::rc::Rc; + +#[derive(Clone)] pub struct SymTable(HashMap); #[derive(Debug, Clone)] diff --git a/tests/test_lib_control.rs b/tests/test_lib_control.rs index 8dd5476..d561131 100644 --- a/tests/test_lib_control.rs +++ b/tests/test_lib_control.rs @@ -62,4 +62,72 @@ mod control_lib_tests { assert!(false); } } + + #[test] + fn test_let_multiphase_locals() { + let document = "(let ( + (temp '1') + (temp (append () temp '2'))) + temp)"; + let result = "('1' '2')"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + + if let Ok(tree) = lex(&document.to_string()) { + if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { + assert_eq!(i.to_string(), result); + } else { + assert!(false); + } + } else { + assert!(false); + } + } + + #[test] + fn test_let_multibody_evals() { + let document = "(let ((temp '1')) temp (append () temp '2'))"; + let result = "('1' '2')"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + + if let Ok(tree) = lex(&document.to_string()) { + if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { + assert_eq!(i.to_string(), result); + } else { + assert!(false); + } + } else { + assert!(false); + } + } + + #[test] + fn test_let_multiphase_local_multibody_evals() { + let document = "(let ( + (temp '1') + (temp (append () temp '2'))) + (echo 'first body') + (append temp '3'))"; + + let result = "('1' '2' '3')"; + + let mut syms = SymTable::new(); + static_stdlib(&mut syms).unwrap(); + dynamic_stdlib(&mut syms).unwrap(); + + if let Ok(tree) = lex(&document.to_string()) { + if let Ctr::Seg(ref i) = *eval(&tree, &mut syms).unwrap() { + assert_eq!(i.to_string(), result); + } else { + assert!(false); + } + } else { + assert!(false); + } + } }