diff --git a/src/stl.rs b/src/stl.rs index 38071c7..33388b4 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -15,15 +15,14 @@ * along with this program. If not, see . */ -use crate::eval::eval; use crate::segment::{Ctr, Seg, Type}; -use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType}; -use std::env; +use crate::sym::{Args, SymTable, Symbol, ValueType}; use std::rc::Rc; pub mod append; pub mod boolean; pub mod control; +pub mod decl; //pub mod str; /// static_stdlib @@ -157,8 +156,8 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> { name: String::from("help"), args: Args::Strict(vec![Type::Symbol]), conditional_branches: true, - docs: HELP_DOCSTRING.to_string(), - value: ValueType::Internal(Rc::new(_help_callback)), + docs: decl::HELP_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(decl::help_callback)), }, ); @@ -168,8 +167,8 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> { name: String::from("set?"), args: Args::Strict(vec![Type::Symbol]), conditional_branches: true, - docs: ISSET_DOCSTRING.to_string(), - value: ValueType::Internal(Rc::new(_isset_callback)), + docs: decl::ISSET_DOCSTRING.to_string(), + value: ValueType::Internal(Rc::new(decl::isset_callback)), }, ); @@ -193,10 +192,10 @@ pub fn dynamic_stdlib(syms: &mut SymTable) -> Result<(), String> { name: String::from("define"), args: Args::Infinite, conditional_branches: true, - docs: STORE_DOCSTRING.to_string(), + docs: decl::STORE_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new( move |ast: &Seg, syms: &mut SymTable| -> Result { - _store_callback(ast, syms, env_cfg_user_form) + decl::store_callback(ast, syms, env_cfg_user_form) }, )), }, @@ -216,173 +215,3 @@ fn _echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result { Ok(Ctr::None) } - -pub const HELP_DOCSTRING: &str = "prints help text for a given symbol. Expects only one argument."; - -fn _help_callback(ast: &Seg, syms: &mut SymTable) -> Result { - if ast.len() != 1 { - return Err("help only takes a single argument".to_string()); - } - if let Ctr::Symbol(ref symbol) = *ast.car { - if let Some(ref sym) = syms.get(symbol) { - let args_str: String; - if let ValueType::VarForm(_) = sym.value { - args_str = "(its a variable)".to_string(); - } else { - args_str = sym.args.to_string(); - } - println!( - "NAME: {0}\n -ARGS: {1}\n -DOCUMENTATION:\n -{2}\n -CURRENT VALUE AND/OR BODY: -{3}", - sym.name, args_str, sym.docs, sym.value - ); - } else { - return Err("undefined symbol".to_string()); - } - } else { - return Err("help should only be called on a symbol".to_string()); - } - - Ok(Ctr::None) -} - -pub const STORE_DOCSTRING: &str = "allows user to define functions and variables. - A call may take one of three forms: - 1. variable declaration: - Takes a name, doc string, and a value. - (def myvar 'my special variable' 'my var value') - 2. function declaration: - Takes a name, doc string, list of arguments, and one or more bodies to evaluate. - Result of evaluating the final body is returned. - (def myfunc 'does a thing' (myarg1 myarg2) (dothing myarg1 myarg2) (add myarg1 myarg2)) - 3. symbol un-definition: - Takes just a name. Removes variable from table. - (def useless-var)"; - -fn _store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result { - let is_var = ast.len() == 3; - if let Ctr::Symbol(ref identifier) = *ast.car { - match &*ast.cdr { - // define a symbol - Ctr::Seg(doc_tree) => { - if let Ctr::String(ref doc) = *doc_tree.car { - match &*doc_tree.cdr { - // define a variable - Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) { - Ok(seg) => { - if let Ctr::Seg(ref val) = *seg { - syms.insert( - identifier.clone(), - Symbol { - value: ValueType::VarForm(val.car.clone()), - name: identifier.clone(), - args: Args::None, - docs: doc.to_owned(), - conditional_branches: false, - }, - ); - if env_cfg { - env::set_var(identifier.clone(), val.car.to_string()); - } - } else { - return Err("impossible args to export".to_string()); - } - } - Err(e) => return Err(format!("couldnt eval symbol: {}", e)), - }, - - // define a function - Ctr::Seg(data_tree) if !is_var => { - if let Ctr::Seg(ref args) = *data_tree.car { - let mut arg_list = vec![]; - if !args.circuit(&mut |c: &Ctr| -> bool { - if let Ctr::Symbol(ref arg) = c { - arg_list.push(arg.clone()); - true - } else if let Ctr::None = c { - // a user cannot type a None - // this case represents no args - true - } else { - false - } - }) { - return Err( - "all arguments defined for function must be of type symbol" - .to_string(), - ); - }; - - if let Ctr::Seg(ref bodies) = *data_tree.cdr { - syms.insert( - identifier.clone(), - Symbol { - value: ValueType::FuncForm(UserFn { - ast: Box::new(bodies.clone()), - arg_syms: arg_list.clone(), - }), - name: identifier.clone(), - args: Args::Lazy(arg_list.len() as u128), - docs: doc.to_owned(), - conditional_branches: false, - }, - ); - } else { - return Err( - "expected one or more function bodies in function definition" - .to_string(), - ); - } - } else { - return Err( - "expected list of arguments in function definition".to_string() - ); - } - } - - // theres a name and a doc string but nothing else - _ => return Err("have name and doc string, but no body.".to_string()), - } - } else { - return Err("doc string is a required argument".to_string()); - } - } - - // undefine a symbol - Ctr::None => { - syms.remove(&identifier.to_string()); - if env_cfg { - env::remove_var(identifier); - } - } - - _ => return Err("arguments not in standard form".to_string()), - } - } else { - return Err("first argument to export must be a symbol".to_string()); - } - Ok(Ctr::None) -} - -pub const ISSET_DOCSTRING: &str = "accepts a single argument: a symbol. -returns true or false according to whether or not the symbol is found in the symbol table."; - -fn _isset_callback(ast: &Seg, syms: &mut SymTable) -> Result { - if ast.len() != 1 { - Err("help only takes a single argument".to_string()) - } else { - if let Ctr::Symbol(ref symbol) = *ast.car { - if let Some(_) = syms.get(symbol) { - Ok(Ctr::Bool(true)) - } else { - Ok(Ctr::Bool(false)) - } - } else { - Err("help should only be called on a symbol".to_string()) - } - } -} diff --git a/src/stl/decl.rs b/src/stl/decl.rs new file mode 100644 index 0000000..ff3f556 --- /dev/null +++ b/src/stl/decl.rs @@ -0,0 +1,191 @@ +/* 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::{Args, SymTable, Symbol, UserFn, ValueType}; +use std::env; + +pub const HELP_DOCSTRING: &str = "prints help text for a given symbol. Expects only one argument."; + +pub fn help_callback(ast: &Seg, syms: &mut SymTable) -> Result { + if ast.len() != 1 { + return Err("help only takes a single argument".to_string()); + } + if let Ctr::Symbol(ref symbol) = *ast.car { + if let Some(ref sym) = syms.get(symbol) { + let args_str: String; + if let ValueType::VarForm(_) = sym.value { + args_str = "(its a variable)".to_string(); + } else { + args_str = sym.args.to_string(); + } + println!( + "NAME: {0}\n +ARGS: {1}\n +DOCUMENTATION:\n +{2}\n +CURRENT VALUE AND/OR BODY: +{3}", + sym.name, args_str, sym.docs, sym.value + ); + } else { + return Err("undefined symbol".to_string()); + } + } else { + return Err("help should only be called on a symbol".to_string()); + } + + Ok(Ctr::None) +} + +pub const STORE_DOCSTRING: &str = "allows user to define functions and variables. + A call may take one of three forms: + 1. variable declaration: + Takes a name, doc string, and a value. + (def myvar 'my special variable' 'my var value') + 2. function declaration: + Takes a name, doc string, list of arguments, and one or more bodies to evaluate. + Result of evaluating the final body is returned. + (def myfunc 'does a thing' (myarg1 myarg2) (dothing myarg1 myarg2) (add myarg1 myarg2)) + 3. symbol un-definition: + Takes just a name. Removes variable from table. + (def useless-var)"; + +pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result { + let is_var = ast.len() == 3; + if let Ctr::Symbol(ref identifier) = *ast.car { + match &*ast.cdr { + // define a symbol + Ctr::Seg(doc_tree) => { + if let Ctr::String(ref doc) = *doc_tree.car { + match &*doc_tree.cdr { + // define a variable + Ctr::Seg(data_tree) if is_var => match eval(&Box::new(data_tree), syms) { + Ok(seg) => { + if let Ctr::Seg(ref val) = *seg { + syms.insert( + identifier.clone(), + Symbol { + value: ValueType::VarForm(val.car.clone()), + name: identifier.clone(), + args: Args::None, + docs: doc.to_owned(), + conditional_branches: false, + }, + ); + if env_cfg { + env::set_var(identifier.clone(), val.car.to_string()); + } + } else { + return Err("impossible args to export".to_string()); + } + } + Err(e) => return Err(format!("couldnt eval symbol: {}", e)), + }, + + // define a function + Ctr::Seg(data_tree) if !is_var => { + if let Ctr::Seg(ref args) = *data_tree.car { + let mut arg_list = vec![]; + if !args.circuit(&mut |c: &Ctr| -> bool { + if let Ctr::Symbol(ref arg) = c { + arg_list.push(arg.clone()); + true + } else if let Ctr::None = c { + // a user cannot type a None + // this case represents no args + true + } else { + false + } + }) { + return Err( + "all arguments defined for function must be of type symbol" + .to_string(), + ); + }; + + if let Ctr::Seg(ref bodies) = *data_tree.cdr { + syms.insert( + identifier.clone(), + Symbol { + value: ValueType::FuncForm(UserFn { + ast: Box::new(bodies.clone()), + arg_syms: arg_list.clone(), + }), + name: identifier.clone(), + args: Args::Lazy(arg_list.len() as u128), + docs: doc.to_owned(), + conditional_branches: false, + }, + ); + } else { + return Err( + "expected one or more function bodies in function definition" + .to_string(), + ); + } + } else { + return Err( + "expected list of arguments in function definition".to_string() + ); + } + } + + // theres a name and a doc string but nothing else + _ => return Err("have name and doc string, but no body.".to_string()), + } + } else { + return Err("doc string is a required argument".to_string()); + } + } + + // undefine a symbol + Ctr::None => { + syms.remove(&identifier.to_string()); + if env_cfg { + env::remove_var(identifier); + } + } + + _ => return Err("arguments not in standard form".to_string()), + } + } else { + return Err("first argument to export must be a symbol".to_string()); + } + Ok(Ctr::None) +} + +pub const ISSET_DOCSTRING: &str = "accepts a single argument: a symbol. +returns true or false according to whether or not the symbol is found in the symbol table."; + +pub fn isset_callback(ast: &Seg, syms: &mut SymTable) -> Result { + if ast.len() != 1 { + Err("help only takes a single argument".to_string()) + } else { + if let Ctr::Symbol(ref symbol) = *ast.car { + if let Some(_) = syms.get(symbol) { + Ok(Ctr::Bool(true)) + } else { + Ok(Ctr::Bool(false)) + } + } else { + Err("help should only be called on a symbol".to_string()) + } + } +}