diff --git a/src/cell.rs b/src/cell.rs index b862113..fd8e6a6 100644 --- a/src/cell.rs +++ b/src/cell.rs @@ -1,4 +1,4 @@ -/* relish: highly versatile lisp interpreter +/* relish: versatile lisp shell * Copyright (C) 2021 Aidan Hahn * * This program is free software: you can redistribute it and/or modify @@ -21,12 +21,24 @@ use std::boxed::Box; // Container pub enum Ctr { - SYMBOL(String), - STRING(String), - INTEGER(i128), - FLOAT(f64), - BOOL(bool), - CELL(Box), + Symbol(String), + String(String), + Integer(i128), + Float(f64), + Bool(bool), + Cell(Box), + None +} + +// Type of Container +#[derive(PartialEq)] +pub enum Type { + Symbol, + String, + Integer, + Float, + Bool, + Cell, None } @@ -46,6 +58,37 @@ pub struct Cell { pub cdr: Ctr } +impl Ctr { + pub fn to_type(&mut self) -> Type { + match self { + Ctr::Symbol(_s) => Type::Symbol, + Ctr::String(_s) => Type::String, + Ctr::Integer(_s) => Type::Integer, + Ctr::Float(_s) => Type::Float, + Ctr::Bool(_s) => Type::Bool, + Ctr::Cell(_s) => Type::Cell, + Ctr::None => Type::None + } + } +} + +impl Type { + pub fn to_str(self) -> String { + let ret: &str; + match self { + Type::Symbol => ret = "symbol", + Type::String => ret = "string", + Type::Integer => ret = "integer", + Type::Float => ret = "float", + Type::Bool => ret = "bool", + Type::Cell => ret = "cell", + Type::None => ret = "none" + } + + ret.to_owned() + } +} + // creates a cell containing two boxes pub fn cons (l_ctr: Ctr, r_ctr: Ctr) -> Cell { Cell { @@ -61,16 +104,16 @@ pub fn cell_as_string(c: &Cell, with_parens: bool) -> String { let mut string = String::new(); let mut prn_space = true; match &c.car { - Ctr::SYMBOL(s) => string.push_str(&s), - Ctr::STRING(s) => { + Ctr::Symbol(s) => string.push_str(&s), + Ctr::String(s) => { string.push('\''); string.push_str(&s); string.push('\''); }, - Ctr::INTEGER(i) => string = string + &i.to_string(), - Ctr::FLOAT(f) => string = string + &f.to_string(), - Ctr::BOOL(b) => string = string + &b.to_string(), - Ctr::CELL(c) => string.push_str(cell_as_string(&c, true).as_str()), + Ctr::Integer(i) => string = string + &i.to_string(), + Ctr::Float(f) => string = string + &f.to_string(), + Ctr::Bool(b) => string = string + &b.to_string(), + Ctr::Cell(c) => string.push_str(cell_as_string(&c, true).as_str()), Ctr::None => prn_space = false } @@ -79,16 +122,16 @@ pub fn cell_as_string(c: &Cell, with_parens: bool) -> String { } match &c.cdr { - Ctr::SYMBOL(s) => string.push_str(&s), - Ctr::STRING(s) => { + Ctr::Symbol(s) => string.push_str(&s), + Ctr::String(s) => { string.push('\''); string.push_str(&s); string.push('\''); }, - Ctr::INTEGER(i) => string = string + &i.to_string(), - Ctr::FLOAT(f) => string = string + &f.to_string(), - Ctr::BOOL(b) => string = string + &b.to_string(), - Ctr::CELL(c) => string.push_str(cell_as_string(&c, false).as_str()), + Ctr::Integer(i) => string = string + &i.to_string(), + Ctr::Float(f) => string = string + &f.to_string(), + Ctr::Bool(b) => string = string + &b.to_string(), + Ctr::Cell(c) => string.push_str(cell_as_string(&c, false).as_str()), Ctr::None => { if prn_space { string.pop(); @@ -112,29 +155,62 @@ impl fmt::Display for Cell { } } -/* recurs over a chain of cells - * adds obj to chain - * not public, only meant for internal use... yet - * steals ownership of obj +/* NOTE: "Standard form" is used here to refer to a list of cells + * that resembles a typical linked list. This means that Car may hold whatever, + * but Cdr must either be Cell or None. */ -pub fn append(c: &mut Cell, obj: Ctr) { - match &mut c.car { - Ctr::None => { - c.car = obj; - }, - _ => { - match &mut c.cdr { - Ctr::None => { - c.cdr = Ctr::CELL(Box::new(Cell{ - car: obj, - cdr: Ctr::None - })); - }, - Ctr::CELL(cell) => { - append(cell, obj); - }, - _ => () +impl Cell { + /* applies a function across a Cell list in standard form + * function must take a Ctr and return a bool + * short circuits on the first false returned. + * also returns false on a non standard form list + */ + pub fn circuit bool>(&mut self, func: F) -> bool{ + if func(self.car) { + match self.cdr { + Ctr::None => true, + Ctr::Cell(c) => c.circuit(func), + _ => false + } + } else { + false + } + } + + /* recurs over a chain of cells + * adds obj to chain + * not public, only meant for internal use... yet + * steals ownership of obj + */ + pub fn append(&mut self, obj: Ctr) { + match &mut self.car { + Ctr::None => { + self.car = obj; + }, + _ => { + match &mut self.cdr { + Ctr::None => { + self.cdr = Ctr::Cell(Box::new(Cell{ + car: obj, + cdr: Ctr::None + })); + }, + Ctr::Cell(cell) => { + cell.append(obj); + }, + _ => () + } } } } + + /* recurs over a chain of cell + * returns length of chain + */ + pub fn len(&self) -> i128 { + match self.cdr { + Ctr::Cell(c) => c.len() + 1, + _ => 1 + } + } } diff --git a/src/func.rs b/src/func.rs new file mode 100644 index 0000000..803c02c --- /dev/null +++ b/src/func.rs @@ -0,0 +1,137 @@ +/* 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 std::boxed::Box; +use std::collections::HashMap; +use crate::cell::{Ctr, Cell, Type}; +use crate::vars::{VTable}; +use crate::eval::eval; + +// Standardized function signature for stdlib functions +pub type Operation = fn(Box, Box, Box) -> Box; +pub type FTable = HashMap; + +/* Function Args + * If Lazy, is an integer denoting number of args + * If Strict, is a list of Ctrs (with no wrapped values) denoting arg type. + */ +pub enum Args { + // signed: -1 denotes infinite args + Lazy(i128), + Strict(Vec) +} + +// function which does not need args checked +pub struct Function { + pub function: Operation, + pub name: String, + pub times_called: u128, + pub args: Args, + + // dont fail on undefined symbol (passed to eval) + pub loose_syms: bool, + + // dont evaluate args at all. leave that to the function + pub eval_lazy: bool +} + +/* call_function + * routine is called by eval when a function call is detected + * full list of cells considered to be a function call is passed in + * (that means that args contains the function symbol in 0 index) + */ +pub fn call_function( + args: Box, + func: Function, + vars: Box, + funcs: Box +) -> Result, String> { + let mut n_args = args; + if !func.eval_lazy { + match eval(args, vars, funcs, func.loose_syms) { + Ok(box_cell) => n_args = box_cell, + Err(s) => return Err( + format!( + "error evaluating args to {}: {}", + func.name, + s + ) + ) + } + } + + let mut passes = false; + match func.args { + Args::Lazy(num) => { + if num < 0 { + passes = true + } + + if !(num == (args.len() - 1)) { + return Err(format!("expected {} args in call to {}", num, func.name)) + } + }, + + Args::Strict(arg_types) => { + let idx: usize = 0; + passes = args.circuit(|c: Ctr| { + if idx >= arg_types.len() { + return false; + } + + if let c = Ctr::None { + return false; + } + + let ret = arg_types[idx] == c.to_type(); + if ret { + idx += 1; + } + return ret; + }); + + if passes && idx < (arg_types.len() - 1) { + Err(format!( + "{} too little arguments in call to {}", + arg_types.len() - (idx + 1), + func.name + )); + } + + if !passes { + if idx < (arg_types.len() - 1) { + Err(format!( + "argument {} in call to {} is of wrong type (expected {})", + idx + 1, + func.name, + arg_types[idx].to_str() + )); + } + + if idx == (arg_types.len() - 1) { + Err(format!( + "too many arguments in call to {}", + func.name + )); + } + } + } + } + + func.times_called += 1; + return Ok((func.function)(args, vars, funcs)); +} diff --git a/src/lex.rs b/src/lex.rs index d26b350..dc8cbc2 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -16,7 +16,7 @@ */ use std::boxed::Box; -use crate::cell::{Ctr, append, Cell}; +use crate::cell::{Ctr, Cell}; const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input"; const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input"; @@ -161,24 +161,24 @@ fn process(document: String) -> Result, String> { let mut obj; if token.len() > 0 { if is_str { - obj = Ctr::STRING(token); + obj = Ctr::String(token); is_str = false; } else if token == "true" { - obj = Ctr::BOOL(true); + obj = Ctr::Bool(true); } else if token == "false" { - obj = Ctr::BOOL(false); + obj = Ctr::Bool(false); } else if let Ok(i) = token.parse::() { - obj = Ctr::INTEGER(i); + obj = Ctr::Integer(i); } else if let Ok(f) = token.parse::() { - obj = Ctr::FLOAT(f); + obj = Ctr::Float(f); } else if let Some(s) = tok_is_symbol(&token) { - obj = Ctr::SYMBOL(s); + obj = Ctr::Symbol(s); } else { return Err(format!("Unparsable token:{}", token)); } token = String::new(); - append(&mut current_cell_ref, obj); + current_cell_ref.append(obj); } if alloc_list { @@ -188,9 +188,9 @@ fn process(document: String) -> Result, String> { } // shortening this will lead to naught but pain - obj = Ctr::CELL(Box::new(*current_cell_ref)); + obj = Ctr::Cell(Box::new(*current_cell_ref)); current_cell_ref = ref_stack.pop().unwrap(); - append(&mut current_cell_ref, obj); + current_cell_ref.append(obj); } ref_stack.push(current_cell_ref); diff --git a/src/lib.rs b/src/lib.rs index 8b980b2..7ee3609 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,8 +17,12 @@ mod cell; mod lex; +mod func; +mod eval; pub mod ast { pub use crate::cell::{Cell, Ctr, cons, cell_as_string}; pub use crate::lex::{lex}; + pub use crate::func::{Function, Operation, FTable}; + pub use crate::eval::{eval}; }