/* 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::segment::{Ctr, Seg, Type}; use crate::sym::{SymTable, Symbol, ValueType, Args}; use crate::error::{Traceback, start_trace}; use std::io::Write; use std::io; use std::rc::Rc; const ECHO_DOCSTRING: &str = "traverses any number of arguments. Prints their evaluated values on a new line for each."; fn echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result { ast.circuit(&mut |arg: &Ctr| match arg { Ctr::String(s) => print!("{}", s) == (), _ => print!("{}", arg) == (), }); println!(); Ok(Ctr::None) } const CONCAT_DOCSTRING: &str = "Iterates over N args of any type other than SYMBOL. converts each argument to a string. Combines all strings. Returns final (combined) string."; fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result { let mut string = String::from(""); if !ast.circuit(&mut |arg: &Ctr| { match arg { // should be a thing here Ctr::Symbol(_) => return false, Ctr::String(s) => string.push_str(&s), Ctr::Integer(i) => string.push_str(&i.to_string()), Ctr::Float(f) => string.push_str(&f.to_string()), Ctr::Bool(b) => string.push_str(&b.to_string()), Ctr::Seg(c) => string.push_str(&c.to_string()), Ctr::Lambda(l) => string.push_str(&l.to_string()), Ctr::None => (), } true }) { return Err(start_trace( ("concat", "highly suspicious that an input was an unevaluated symbol") .into())) } return Ok(Ctr::String(string)); } const STRLEN_DOCSTRING: &str = "Takes a single arg of any type. Arg is converted to a string if not already a string. Returns string length of arg."; fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result { match &*ast.car { Ctr::Symbol(s) => Ok(Ctr::Integer(s.len() as i128)), Ctr::String(s) => Ok(Ctr::Integer(s.len() as i128)), Ctr::Integer(i) => Ok(Ctr::Integer(i.to_string().len() as i128)), Ctr::Float(f) => Ok(Ctr::Integer(f.to_string().len() as i128)), Ctr::Bool(b) => Ok(Ctr::Integer(b.to_string().len() as i128)), Ctr::Seg(c) => Ok(Ctr::Integer(c.to_string().len() as i128)), Ctr::Lambda(l) => Ok(Ctr::Integer(l.to_string().len() as i128)), // highly suspicious case below Ctr::None => Ok(Ctr::Integer(0)), } } const STRCAST_DOCSTRING: &str = "Takes a single arg of any type. Arg is converted to a string and returned."; fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result { match &*ast.car { Ctr::Symbol(s) => Ok(Ctr::String(s.clone())), Ctr::String(_) => Ok(*ast.car.clone()), Ctr::Integer(i) => Ok(Ctr::String(i.to_string())), Ctr::Float(f) => Ok(Ctr::String(f.to_string())), Ctr::Bool(b) => Ok(Ctr::String(b.to_string())), Ctr::Seg(c) => Ok(Ctr::String(c.to_string())), Ctr::Lambda(l) => Ok(Ctr::String(l.to_string())), // highly suspicious case below Ctr::None => Ok(Ctr::String(String::new())), } } const SUBSTR_DOCSTRING: &str = "Takes two strings. Returns true if string1 contains at least one instance of string2"; fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result { let parent_str: String; if let Ctr::String(ref s) = *ast.car { parent_str = s.to_string(); } else { return Err(start_trace( ("substr", "expected first input to be a string") .into())) } let second_arg_obj: &Ctr; let child_str: String; if let Ctr::Seg(ref s) = *ast.cdr { second_arg_obj = &*s.car; } else { return Err(start_trace( ("substr", "expected two inputs") .into())) } if let Ctr::String(ref s) = &*second_arg_obj { child_str = s.clone(); } else { return Err(start_trace( ("substr", "expected second input to be a string") .into())) } Ok(Ctr::Bool(parent_str.contains(&child_str))) } const SPLIT_DOCSTRING: &str = "Takes two strings. String 1 is a source string and string 2 is a delimiter. Returns a list of substrings from string 1 that were found delimited by string 2."; fn split_callback(ast: &Seg, _syms: &mut SymTable) -> Result { let parent_str: String; if let Ctr::String(ref s) = *ast.car { parent_str = s.to_string(); } else { return Err(start_trace( ("split", "expected first input to be a string") .into())) } let second_arg_obj: &Ctr; let delim_str: String; if let Ctr::Seg(ref s) = *ast.cdr { second_arg_obj = &*s.car; } else { return Err(start_trace( ("split", "expected two inputs") .into())) } if let Ctr::String(ref s) = &*second_arg_obj { delim_str = s.clone(); } else { return Err(start_trace( ("split", "expected second input to be a string") .into())) } let mut ret = Seg::new(); for substr in parent_str.split(&delim_str) { ret.append(Box::new(Ctr::String(substr.to_string()))); } Ok(Ctr::Seg(ret)) } const INPUT_DOCSTRING: &str = "Takes one argument (string) and prints it. Then prompts for user input. User input is returned as a string"; fn input_callback(ast: &Seg, _syms: &mut SymTable) -> Result { if let Ctr::String(ref s) = *ast.car { print!("{}", s); let _= io::stdout().flush(); let mut input = String::new(); io::stdin().read_line(&mut input).expect("couldnt read user input"); Ok(Ctr::String(input.trim().to_string())) } else { return Err(start_trace( ("input", "expected a string input to prompt user with") .into())) } } pub fn add_string_lib(syms: &mut SymTable) { syms.insert( "echo".to_string(), Symbol { name: String::from("echo"), args: Args::Infinite, conditional_branches: false, docs: ECHO_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(echo_callback)), ..Default::default() }, ); syms.insert( "concat".to_string(), Symbol { name: String::from("concat"), args: Args::Infinite, conditional_branches: false, docs: CONCAT_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(concat_callback)), ..Default::default() }, ); syms.insert( "substr?".to_string(), Symbol { name: String::from("substr?"), args: Args::Strict(vec![Type::String, Type::String]), conditional_branches: false, docs: SUBSTR_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(substr_callback)), ..Default::default() }, ); syms.insert( "split".to_string(), Symbol { name: String::from("split"), args: Args::Strict(vec![Type::String, Type::String]), conditional_branches: false, docs: SPLIT_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(split_callback)), ..Default::default() }, ); syms.insert( "strlen".to_string(), Symbol { name: String::from("strlen"), args: Args::Lazy(1), conditional_branches: false, docs: STRLEN_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(strlen_callback)), ..Default::default() }, ); syms.insert( "string".to_string(), Symbol { name: String::from("string"), args: Args::Lazy(1), conditional_branches: false, docs: STRCAST_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(strcast_callback)), ..Default::default() }, ); syms.insert( "input".to_string(), Symbol { name: String::from("input"), args: Args::Strict(vec![Type::String]), conditional_branches: false, docs: INPUT_DOCSTRING.to_string(), value: ValueType::Internal(Rc::new(input_callback)), ..Default::default() } ); }