Big project dir refactor

* split into multi member workspace in preparation for a no_std core
* env and posix stuff neatly crammed into a seperate shell project
* some pokes at interactive-devel.f
* updated ci
* removed 'l' shortcut for 'load' and update docs
* remove out of date readme content
* updated tests
* more sensible cond implementation and extra tests
* substr stdlib function with tests

Signed-off-by: Ava Affine <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2024-07-10 13:22:28 -07:00
parent aa56570d7d
commit 6d2925984f
44 changed files with 967 additions and 779 deletions

99
core/src/error.rs Normal file
View file

@ -0,0 +1,99 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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 std::fmt;
#[derive(Debug, Clone)]
pub struct TraceItem {
pub caller: String,
pub message: String,
}
#[derive(Debug, Clone)]
pub struct Traceback(pub Vec<TraceItem>);
pub fn start_trace(item: TraceItem) -> Traceback {
Traceback::new().with_trace(item)
}
impl Traceback {
pub fn new() -> Traceback {
Traceback(vec![])
}
pub fn with_trace(mut self, item: TraceItem) -> Traceback {
self.0.push(item);
self
}
pub fn depth(&self) -> usize {
self.0.len()
}
}
impl fmt::Display for Traceback {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
_ = writeln!(f, "** ERROR TRACEBACK");
for trace in self.0.iter().rev() {
_ = writeln!(f, " > {}: {}", trace.caller, trace.message);
}
writeln!(f, "Please refactor forms and try again...")
}
}
impl std::convert::From<Traceback> for String {
fn from(arg: Traceback) -> Self {
format!("{}", arg)
}
}
impl std::convert::From<(&String, &str)> for TraceItem {
fn from(value: (&String, &str)) -> Self {
TraceItem {
caller: value.0.clone(),
message: String::from(value.1),
}
}
}
impl std::convert::From<(&String, String)> for TraceItem {
fn from(value: (&String, String)) -> Self {
TraceItem {
caller: value.0.clone(),
message: value.1,
}
}
}
impl std::convert::From<(&str, String)> for TraceItem {
fn from(value: (&str, String)) -> Self {
TraceItem {
caller: String::from(value.0),
message: value.1,
}
}
}
impl std::convert::From<(&str, &str)> for TraceItem {
fn from(value: (&str, &str)) -> Self {
TraceItem {
caller: String::from(value.0),
message: String::from(value.1),
}
}
}

110
core/src/eval.rs Normal file
View file

@ -0,0 +1,110 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::segment::{Ctr, Seg};
use crate::sym::{SymTable, call_lambda};
use crate::error::Traceback;
/* iterates over a syntax tree
* returns a NEW LIST of values
* representing the simplest possible form of the input
*/
pub fn eval(ast: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, Traceback> {
// data to return
let mut ret = Box::from(Ctr::None);
let mut first = true;
// to be assigned from cloned/evaled data
let mut car;
let mut cdr;
// lets me redirect the input
let mut arg_car = &ast.car;
let mut arg_cdr = &ast.cdr;
// iterate over ast and build out ret
let mut none = false;
while !none {
match &**arg_car {
Ctr::Seg(ref inner) => {
let interm = eval(inner, syms)?;
if let Ctr::Lambda(ref l) = *interm {
return call_lambda(l, arg_cdr, syms)
} else {
car = interm;
}
},
Ctr::Lambda(ref l) => return Ok(call_lambda(l, arg_cdr, syms)?),
Ctr::Symbol(ref tok) => {
let outer_scope_seg_holder: Seg;
let args: &Seg;
if let Ctr::Seg(ref candidate_args) = **arg_cdr {
args = candidate_args;
} else {
outer_scope_seg_holder = Seg::from_mono(arg_cdr.clone());
args = &outer_scope_seg_holder;
}
car = syms.call_symbol(tok, args, first)?;
if let Some(b) = syms.is_function_type(tok) {
if b {
return Ok(car);
}
}
}
_ => {
car = arg_car.clone();
}
}
match **arg_cdr {
Ctr::Symbol(ref tok) => {
let blank = Seg::new();
cdr = syms.call_symbol(tok, &blank, false)?;
none = true;
}
Ctr::Seg(ref next) => {
cdr = Box::from(Ctr::None);
arg_car = &next.car;
arg_cdr = &next.cdr
}
_ => {
cdr = arg_cdr.clone();
none = true;
}
}
if let Ctr::None = *ret {
*ret = Ctr::Seg(Seg::from(car, cdr))
} else if let Ctr::Seg(ref mut s) = *ret {
s.append(car);
if let Ctr::None = *cdr {
} else {
s.append(cdr);
}
}
first = false;
}
Ok(ret)
}

246
core/src/lex.rs Normal file
View file

@ -0,0 +1,246 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::segment::{Ctr, Seg};
use crate::error::{Traceback, start_trace};
use phf::{Map, phf_map};
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input";
static ESCAPES: Map<char, char> = phf_map! {
'n' => '\n',
't' => '\t',
'\\' => '\\',
};
/* takes a line of user input
* returns an unsimplified tree of tokens.
*/
pub fn lex(document: &String) -> Result<Box<Seg>, Traceback> {
if !document.is_ascii() {
return Err(start_trace(
("<lex>", "document may only contain ascii characters".to_string())
.into()))
}
// finish a singlet token, or do nothing
let document_normal = document.clone() + " ";
let tree = process(&document_normal);
match tree {
Err(e) => Err(start_trace(
("<lex>", format!("Problem lexing document: {:?}", e))
.into())),
Ok(t) => Ok(t),
}
}
/* The logic used in lex
* Returns Ok(Rc<Seg>) if lexing passes
* Returns Err(String) if an error occurs
*/
fn process(document: &String) -> Result<Box<Seg>, String> {
let doc_len = document.len();
if doc_len == 0 {
return Err("Empty document".to_string());
}
/* State variables
*/
let mut is_str = false;
let mut ign = false;
let mut token = String::new();
let mut delim_stack = Vec::new();
let mut ref_stack = vec![];
let mut cursor = 0;
/* Iterate over document
* Manage currently sought delimiter
*/
for c in document.chars() {
let mut needs_alloc = false;
let mut alloc_list = false;
let delim: char;
cursor += 1;
if let Some(d) = delim_stack.last() {
delim = *d;
if delim == '*' {
token.push(ESCAPES.get(&c)
.cloned()
.or(Some(c))
.unwrap());
delim_stack.pop();
continue;
// normal delimiter cases
} else if c == delim {
needs_alloc = true;
// reset comment line status
if delim == '\n' {
delim_stack.pop();
ign = false;
continue;
}
// catch too many list end
// set alloc_list
if delim == ')' {
alloc_list = true;
if ref_stack.is_empty() {
return Err("too many end parens".to_string());
}
}
delim_stack.pop();
// if we are in a commented out space, skip this char
} else if ign {
continue;
}
}
// try to generalize all whitespace
if !needs_alloc && char::is_whitespace(c) && !is_str {
// dont make empty tokens just because the document
// has consecutive whitespace
if token.is_empty() {
continue;
}
needs_alloc = true;
}
// match a delimiter
if !needs_alloc {
match c {
// add a new Seg reference to the stack
'(' if !is_str && token.is_empty() => {
let mut s = Seg::new();
ref_stack.push(Seg::new());
delim_stack.push(')');
}
// specific error case
'(' if !is_str && !token.is_empty() => {
return Err(
format!("list started in middle of another token: {}", token)
)
}
// begin parsing a string
'"' | '`' if !is_str => {
is_str = true;
delim_stack.push(c);
}
// eat the whole line
'#' | ';' if !is_str => {
ign = true;
delim_stack.push('\n');
}
// escape next char
'\\' => if is_str {
delim_stack.push('*');
}
// add to token
_ => {
token.push(c);
}
}
/* 1. Handle allocation of new Ctr
* 2. Handle expansion of current list ref
*/
} else {
if token.is_empty() && !is_str && !alloc_list {
return Err("Empty token".to_string());
}
let mut return_singlet = false;
let mut current_seg = ref_stack.pop().unwrap_or_else(|| {
return_singlet = true;
Seg::new()
});
let obj;
if is_str {
obj = Box::from(Ctr::String(token));
is_str = false;
token = String::new();
current_seg.append(obj);
} else if !token.is_empty() {
if token == "true" {
obj = Box::from(Ctr::Bool(true));
} else if token == "false" {
obj = Box::from(Ctr::Bool(false));
} else if let Ok(i) = token.parse::<i128>() {
obj = Box::from(Ctr::Integer(i));
} else if let Ok(f) = token.parse::<f64>() {
obj = Box::from(Ctr::Float(f));
} else if let Some(s) = tok_is_symbol(&token) {
obj = Box::from(Ctr::Symbol(s));
} else {
return Err(format!("Unparsable token \"{}\" at char {}", token, cursor));
}
token = String::new();
current_seg.append(obj.clone());
}
if alloc_list || return_singlet {
// return if we have finished the document
if ref_stack.is_empty() {
return Ok(Box::new(current_seg));
}
let t = current_seg;
current_seg = ref_stack.pop().unwrap();
current_seg.append(Box::from(Ctr::Seg(t)));
}
ref_stack.push(current_seg);
}
}
if is_str {
Err(UNMATCHED_STR_DELIM.to_string())
} else {
Err(UNMATCHED_LIST_DELIM.to_string())
}
}
/* Returns true if token
* - is all alphanumeric except dash, question, and underscore
* - equals is also allowed but only for shell command compatibility
* else returns false
*/
fn tok_is_symbol(token: &str) -> Option<String> {
for t in token.chars() {
if !t.is_alphanumeric() &&
t != '-' &&
t != '_' &&
t != '?' &&
t != '=' &&
t != '.' &&
t != '/' &&
t != '.'
{
return None;
}
}
Some(String::from(token))
}

42
core/src/lib.rs Normal file
View file

@ -0,0 +1,42 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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/>.
*/
//#![no_std]
mod eval;
mod lex;
mod segment;
mod stl;
mod sym;
mod error;
pub mod ast {
pub use crate::eval::eval;
pub use crate::lex::lex;
pub use crate::segment::{Ctr, Seg, Type};
pub use crate::sym::{Args, SymTable, Symbol, UserFn, ValueType};
pub use crate::error::{Traceback, start_trace};
}
pub mod stdlib {
pub use crate::stl::{
static_stdlib,
CFG_FILE_VNAME,
decl::STORE_DOCSTRING,
decl::store_callback,
};
}

425
core/src/segment.rs Normal file
View file

@ -0,0 +1,425 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::sym::UserFn;
use std::fmt;
use std::marker::PhantomData;
use std::ops::{Add, Div, Index, Mul, Sub};
// Container
#[derive(Debug, Default)]
pub enum Ctr {
Symbol(String),
String(String),
Integer(i128),
Float(f64),
Bool(bool),
Seg(Seg),
Lambda(UserFn),
#[default]
None,
}
// Type of Container
#[derive(PartialEq, Clone)]
pub enum Type {
Symbol,
String,
Integer,
Float,
Bool,
Seg,
Lambda,
None,
}
/* Segment
* Holds two Containers.
* Basic building block for more complex data structures.
* I was going to call it Cell and then I learned about
* how important RefCells were in Rust
*/
#[derive(Debug, Default, PartialEq)]
pub struct Seg {
/* "Contents of Address Register"
* Historical way of referring to the first value in a cell.
*/
pub car: Box<Ctr>,
/* "Contents of Decrement Register"
* Historical way of referring to the second value in a cell.
*/
pub cdr: Box<Ctr>,
/* Stupid hack that makes rust look foolish.
* Needed to determine variance of lifetime.
* How this is an acceptable solution I have
* not a single clue.
*/
_lifetime_variance_determinant: PhantomData<()>,
}
static NOTHING: Ctr = Ctr::None;
impl Ctr {
pub fn to_type(&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::Seg(_s) => Type::Seg,
Ctr::Lambda(_s) => Type::Lambda,
Ctr::None => Type::None,
}
}
}
impl Seg {
/* recurs over tree assumed to be list in standard form
* appends object to end of list
*/
pub fn append(&mut self, obj: Box<Ctr>) {
if let Ctr::None = &*(self.car) {
self.car = obj;
return;
}
if let Ctr::Seg(s) = &mut *(self.cdr) {
s.append(obj);
return;
}
if let Ctr::None = &mut *(self.cdr) {
self.cdr = Box::new(Ctr::Seg(Seg::from_mono(obj)));
// pray for memory lost to the void
}
}
/* applies a function across a 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<F: FnMut(&Ctr) -> bool>(&self, func: &mut F) -> bool {
if func(&self.car) {
match &*(self.cdr) {
Ctr::None => true,
Ctr::Seg(l) => l.circuit(func),
_ => false,
}
} else {
false
}
}
/* applies a function across a list in standard form
* recurs before applying function to go in reverse
* 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_reverse<F: FnMut(&Ctr) -> bool>(&self, func: &mut F) -> bool {
match &*self.cdr {
Ctr::None => func(&self.car),
Ctr::Seg(l) => l.circuit_reverse(func) && func(&self.car),
_ => false,
}
}
pub fn from_mono(arg: Box<Ctr>) -> Seg {
Seg {
car: arg,
cdr: Box::new(Ctr::None),
_lifetime_variance_determinant: PhantomData,
}
}
pub fn from(car: Box<Ctr>, cdr: Box<Ctr>) -> Seg {
Seg {
car,
cdr,
_lifetime_variance_determinant: PhantomData,
}
}
/* recurs over ast assumed to be list in standard form
* returns length
*/
pub fn len(&self) -> u128 {
let mut len = 0;
self.circuit(&mut |_c: &Ctr| -> bool {
len += 1;
true
});
len
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn new() -> Seg {
Seg {
car: Box::new(Ctr::None),
cdr: Box::new(Ctr::None),
_lifetime_variance_determinant: PhantomData,
}
}
}
fn seg_to_string(s: &Seg, parens: bool) -> String {
let mut string = String::new();
if parens {
string.push('(');
}
match *(s.car) {
Ctr::None => string.push_str("<nil>"),
_ => string.push_str(&s.car.to_string()),
}
string.push(' ');
match &*(s.cdr) {
Ctr::Seg(inner) => string.push_str(&seg_to_string(inner, false)),
Ctr::None => {
string.pop();
}
_ => string.push_str(&s.cdr.to_string()),
}
if parens {
string.push(')');
}
string
}
impl Clone for Seg {
fn clone(&self) -> Seg {
Seg {
car: self.car.clone(),
cdr: self.cdr.clone(),
_lifetime_variance_determinant: PhantomData,
}
}
}
impl Index<usize> for Seg {
type Output = Ctr;
fn index(&self, idx: usize) -> &Self::Output {
if idx == 0 {
return &self.car;
}
if let Ctr::Seg(ref s) = *self.cdr {
return s.index(idx - 1);
}
&NOTHING
}
}
impl Clone for Ctr {
fn clone(&self) -> Ctr {
match self {
Ctr::Symbol(s) => Ctr::Symbol(s.clone()),
Ctr::String(s) => Ctr::String(s.clone()),
Ctr::Integer(s) => Ctr::Integer(*s),
Ctr::Float(s) => Ctr::Float(*s),
Ctr::Bool(s) => Ctr::Bool(*s),
Ctr::Seg(s) => Ctr::Seg(s.clone()),
Ctr::Lambda(s) => Ctr::Lambda(s.clone()),
Ctr::None => Ctr::None,
}
}
}
impl fmt::Display for Ctr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Ctr::Symbol(s) => write!(f, "{}", s),
Ctr::String(s) => write!(f, "\"{}\"", s),
Ctr::Integer(s) => write!(f, "{}", s),
Ctr::Float(s) => write!(f, "{}", s),
Ctr::Bool(s) => {
if *s {
write!(f, "true")
} else {
write!(f, "false")
}
}
Ctr::Seg(s) => write!(f, "{}", s),
Ctr::Lambda(l) => write!(f, "{}", l),
Ctr::None => Ok(()),
}
}
}
impl PartialEq for Ctr {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Ctr::Symbol(r), Ctr::Symbol(l)) => *r == *l,
(Ctr::String(r), Ctr::String(l)) => *r == *l,
(Ctr::Bool(r), Ctr::Bool(l)) => *r == *l,
(Ctr::Seg(r), Ctr::Seg(l)) => *r == *l,
(Ctr::None, Ctr::None) => true,
(Ctr::Integer(r), Ctr::Integer(l)) => *r == *l,
(Ctr::Float(r), Ctr::Float(l)) => *r == *l,
(Ctr::Integer(r), Ctr::Float(l)) => *r < f64::MAX as i128 && *r as f64 == *l,
(Ctr::Float(r), Ctr::Integer(l)) => *l < f64::MAX as i128 && *r == *l as f64,
_ => false,
}
}
}
impl Add<Ctr> for Ctr {
type Output = Ctr;
fn add(self, rh: Ctr) -> Ctr {
match self {
Ctr::Float(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn + rhn),
Ctr::Integer(rhn) => Ctr::Float(lhn + rhn as f64),
_ => unimplemented!("non-numeral on right hand side of add"),
},
Ctr::Integer(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn as f64 + rhn),
Ctr::Integer(rhn) => Ctr::Integer(lhn + rhn),
_ => unimplemented!("non-numeral on right hand side of add"),
},
Ctr::String(lhs) => match rh {
Ctr::String(rhs) => Ctr::String(lhs + &rhs),
_ => unimplemented!("add to string only implemented for other strings"),
},
_ => {
unimplemented!("datum does not support add")
}
}
}
}
impl Sub<Ctr> for Ctr {
type Output = Ctr;
fn sub(self, rh: Ctr) -> Ctr {
match self {
Ctr::Float(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn - rhn),
Ctr::Integer(rhn) => Ctr::Float(lhn - rhn as f64),
_ => unimplemented!("non-numeral on right hand side of sub"),
},
Ctr::Integer(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn as f64 - rhn),
Ctr::Integer(rhn) => Ctr::Integer(lhn - rhn),
_ => unimplemented!("non-numeral on right hand side of sub"),
},
_ => {
unimplemented!("datum does not support sub")
}
}
}
}
impl Mul<Ctr> for Ctr {
type Output = Ctr;
fn mul(self, rh: Ctr) -> Ctr {
match self {
Ctr::Float(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn * rhn),
Ctr::Integer(rhn) => Ctr::Float(lhn * rhn as f64),
_ => unimplemented!("non-numeral on right hand side of mul"),
},
Ctr::Integer(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn as f64 * rhn),
Ctr::Integer(rhn) => Ctr::Integer(lhn * rhn),
_ => unimplemented!("non-numeral on right hand side of mul"),
},
_ => {
unimplemented!("datum does not support mul")
}
}
}
}
impl Div<Ctr> for Ctr {
type Output = Ctr;
fn div(self, rh: Ctr) -> Ctr {
match self {
Ctr::Float(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn / rhn),
Ctr::Integer(rhn) => Ctr::Float(lhn / rhn as f64),
_ => unimplemented!("non-numeral on right hand side of div"),
},
Ctr::Integer(lhn) => match rh {
Ctr::Float(rhn) => Ctr::Float(lhn as f64 / rhn),
Ctr::Integer(rhn) => Ctr::Integer(lhn / rhn),
_ => unimplemented!("non-numeral on right hand side of div"),
},
_ => {
unimplemented!("datum does not support div")
}
}
}
}
impl fmt::Display for Seg {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", seg_to_string(self, true))
}
}
impl fmt::Display for Type {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ret: &str = match self {
Type::Symbol => "symbol",
Type::String => "string",
Type::Integer => "integer",
Type::Float => "float",
Type::Bool => "bool",
Type::Seg => "segment",
Type::Lambda => "lambda",
Type::None => "none",
};
write!(f, "{}", ret)
}
}
impl std::convert::From<String> for Type {
fn from(value: String) -> Self {
match value.as_str() {
"symbol" => Type::Symbol,
"string" => Type::String,
"integer" => Type::Integer,
"float" => Type::Float,
"bool" => Type::Bool,
"segment" => Type::Seg,
"lambda" => Type::Lambda,
_ => Type::None,
}
}
}

41
core/src/stl.rs Normal file
View file

@ -0,0 +1,41 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::sym::SymTable;
pub mod append;
pub mod boolean;
pub mod control;
pub mod decl;
pub mod math;
pub mod strings;
pub mod file;
pub const CFG_FILE_VNAME: &str = "FLESH_CFG_FILE";
/// static_stdlib
/// inserts all stdlib functions that can be inserted without
/// any kind of further configuration data into a symtable
pub fn static_stdlib(syms: &mut SymTable) {
append::add_list_lib(syms);
strings::add_string_lib(syms);
decl::add_decl_lib_static(syms);
control::add_control_lib(syms);
boolean::add_bool_lib(syms);
math::add_math_lib(syms);
file::add_file_lib(syms);
}

297
core/src/stl/append.rs Normal file
View file

@ -0,0 +1,297 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, Args, ValueType};
use crate::error::{Traceback, start_trace};
use std::rc::Rc;
const CONS_DOCSTRING: &str = "traverses any number of arguments collecting them into a list.
If the first argument is a list, all other arguments are added sequentially to the end of the list contained in the first argument.";
fn cons_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
let mut temp = s.clone();
if let Ctr::Seg(ref list) = *ast.cdr {
list.circuit(&mut |c: &Ctr| -> bool {
temp.append(Box::new(c.clone()));
true
});
} else {
temp.append(Box::new(*ast.cdr.clone()));
}
Ok(Ctr::Seg(temp))
} else {
let mut temp = Seg::new();
let mut car_iter = &ast.car;
let mut cdr_iter = &ast.cdr;
loop {
temp.append(car_iter.clone());
if let Ctr::Seg(ref s) = **cdr_iter {
car_iter = &s.car;
cdr_iter = &s.cdr;
} else {
break;
}
}
Ok(Ctr::Seg(temp))
}
}
const LEN_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns the length of said list.
Interpreter will panic if the length of the list is greater than the max value of a signed 128 bit integer";
fn len_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if let Ctr::None = *s.car {
Ok(Ctr::Integer(0))
} else {
Ok(Ctr::Integer(s.len() as i128))
}
} else {
Err(start_trace(("len", "input is not a list").into()))
}
}
const CAR_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a copy of the first value in that list.
If the list is empty, returns an err to avoid passing back a null value.";
fn car_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if let Ctr::None = *s.car {
Err(start_trace(("len", "input is empty").into()))
} else {
Ok(*s.car.clone())
}
} else {
Err(start_trace(("len", "input is not a list").into()))
}
}
const CDR_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a copy of the last value in that list.
If the list is empty, returns an err to avoid passing back a null value.";
fn cdr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
let mut ret = &s.car;
let mut iter = &s.cdr;
while let Ctr::Seg(ref next) = **iter {
ret = &next.car;
iter = &next.cdr;
}
if let Ctr::None = **ret {
Err(start_trace(("cdr", "input is empty").into()))
} else {
Ok(*ret.clone())
}
} else {
Err(start_trace(("cdr", "input is not a list").into()))
}
}
const POP_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a list containing the first element, and the rest of the elements.
Example: (pop (1 2 3)) -> (1 (2 3)).
The head can then be accessed by car and the rest can be accessed by cdr.";
fn pop_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if s.is_empty() {
return Err(start_trace(("pop", "input is empty").into()));
}
let mut ret = Seg::new();
ret.append(s.car.clone());
if let Ctr::Seg(ref s2) = *s.cdr {
ret.append(Box::new(Ctr::Seg(s2.clone())));
} else {
ret.append(Box::new(Ctr::Seg(Seg::new())));
}
Ok(Ctr::Seg(ret))
} else {
Err(start_trace(("pop", "input is not a list").into()))
}
}
const DEQUEUE_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a list containing the last element, and the rest of the elements.
Example: (dq (1 2 3)) -> (3 (1 2)).
The last element can then be accessed by car and the rest can be accessed by cdr.";
fn dequeue_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if s.is_empty() {
return Err(start_trace(("dequeue", "expected an input").into()));
}
let mut rest = Seg::new();
let mut iter = s;
while *iter.cdr != Ctr::None {
rest.append(Box::new(*iter.car.clone()));
if let Ctr::Seg(ref next) = *iter.cdr {
iter = next;
} else {
break;
}
}
let mut ret = Seg::new();
match *iter.car {
Ctr::Seg(ref s) => {
ret.append(s.car.clone());
}
_ => ret.append(iter.car.clone()),
}
ret.append(Box::new(Ctr::Seg(rest)));
Ok(Ctr::Seg(ret))
} else {
Err(start_trace(("dequeue", "input is not a list").into()))
}
}
const REVERSE_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns the same list, but in reverse order.";
fn reverse_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
let mut ret = Seg::new();
s.circuit_reverse(&mut |arg: &Ctr| -> bool {
ret.append(Box::new(arg.clone()));
true
});
Ok(Ctr::Seg(ret))
} else {
Err(start_trace(("reverse", "input is not a list").into()))
}
}
const ISLIST_DOCSTRING: &str = "takes a single argument, returns true if argument is a list";
fn islist_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(_) = *ast.car {
Ok(Ctr::Bool(true))
} else {
Ok(Ctr::Bool(false))
}
}
pub fn add_list_lib(syms: &mut SymTable) {
syms.insert(
"cons".to_string(),
Symbol {
name: String::from("cons"),
args: Args::Infinite,
conditional_branches: false,
docs: CONS_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(cons_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"len".to_string(),
Symbol {
name: String::from("len"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: LEN_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(len_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"car".to_string(),
Symbol {
name: String::from("car"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: CAR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(car_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"cdr".to_string(),
Symbol {
name: String::from("cdr"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: CDR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(cdr_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"pop".to_string(),
Symbol {
name: String::from("pop"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: POP_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(pop_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"dq".to_string(),
Symbol {
name: String::from("dequeue"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: DEQUEUE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(dequeue_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"reverse".to_string(),
Symbol {
name: String::from("reverse"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: REVERSE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(reverse_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"list?".to_string(),
Symbol {
name: String::from("list?"),
args: Args::Lazy(1),
conditional_branches: false,
docs: ISLIST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(islist_callback)),
optimizable: true,
..Default::default()
},
);
}

221
core/src/stl/boolean.rs Normal file
View file

@ -0,0 +1,221 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::segment::{Ctr, Seg, Type};
use crate::error::{Traceback, start_trace};
use crate::sym::{SymTable, ValueType, Args, Symbol};
use std::rc::Rc;
const AND_DOCSTRING: &str =
"traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 AND arg2, and then calculates prev_result AND next_arg.
returns final result.";
fn and_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut type_error = false;
let mut cursor = 0;
let result = ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Bool(b) = *arg {
cursor += 1;
b
} else {
type_error = true;
false
}
});
if type_error {
Err(start_trace(("and", format!("input {} not a boolean", cursor)).into()))
} else {
Ok(Ctr::Bool(result))
}
}
const OR_DOCSTRING: &str =
"traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 OR arg2, and then calculates prev_result OR next_arg.
returns final result.";
fn or_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut result = false;
let mut cursor = 0;
let correct_types = ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Bool(b) = *arg {
cursor += 1;
result = result || b;
true
} else {
false
}
});
if !correct_types {
Err(start_trace(("or", format!("input {} not a boolean", cursor)).into()))
} else {
Ok(Ctr::Bool(result))
}
}
const NOT_DOCSTRING: &str = "takes a single argument (expects a boolean).
returns false if arg is true or true if arg is false.";
fn not_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = *ast.car {
Ok(Ctr::Bool(!b))
} else {
Err(start_trace(("not", "input is not a bool").into()))
}
}
const ISEQ_DOCSTRING: &str = "traverses a list of N arguments.
returns true if all arguments hold the same value.
NOTE: 1 and 1.0 are the same, but '1' 'one' or one (symbol) aren't";
fn iseq_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let head_ctr_ref = &*ast.car;
Ok(Ctr::Bool(
ast.circuit(&mut |arg: &Ctr| -> bool { arg == head_ctr_ref }),
))
}
const TOGGLE_DOCSTRING: &str = "switches a boolean symbol between true or false.
Takes a single argument (a symbol). Looks it up in the variable table.
Either sets the symbol to true if it is currently false, or vice versa.";
fn toggle_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone();
} else {
return Err(start_trace(("toggle", "input must be a symbol").into()));
}
let mut sym = syms
.remove(&var_name)
.expect(&format!("symbol {var_name} is not defined"));
if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Bool(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Bool(!b)));
} else {
syms.insert(var_name, sym);
return Err(start_trace(("toggle", "can only toggle a boolean").into()));
}
} else {
syms.insert(var_name, sym);
return Err(start_trace(("toggle", "cannot toggle a function").into()));
}
syms.insert(var_name, sym);
Ok(Ctr::None)
}
const BOOLCAST_DOCSTRING: &str = "takes one argument of any type.
attempts to cast argument to a bool.
Strings will cast to a bool if they are 'true' or 'false'.
Integers and Floats will cast to true if they are 0 and false otherwise.";
fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car {
Ctr::Bool(_) => Ok(*ast.car.clone()),
Ctr::String(s) => {
if s == "true" {
Ok(Ctr::Bool(true))
} else if s == "false" {
Ok(Ctr::Bool(false))
} else {
Err(start_trace(("bool", "string cannot be parsed as a bool").into()))
}
},
Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)),
Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)),
_ => Err(start_trace(("bool", format!("cannot convert a {} to a boolean",
ast.car.to_type())).into())),
}
}
pub fn add_bool_lib(syms: &mut SymTable) {
syms.insert(
"and".to_string(),
Symbol {
name: String::from("and"),
args: Args::Infinite,
conditional_branches: false,
docs: AND_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(and_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"bool".to_string(),
Symbol {
name: String::from("bool"),
args: Args::Infinite,
conditional_branches: false,
docs: BOOLCAST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(boolcast_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"or".to_string(),
Symbol {
name: String::from("or"),
args: Args::Infinite,
conditional_branches: false,
docs: OR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(or_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"not".to_string(),
Symbol {
name: String::from("not"),
args: Args::Strict(vec![Type::Bool]),
conditional_branches: false,
docs: NOT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(not_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"eq?".to_string(),
Symbol {
name: String::from("eq?"),
args: Args::Infinite,
conditional_branches: false,
docs: ISEQ_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(iseq_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"toggle".to_string(),
Symbol {
name: String::from("toggle"),
args: Args::Lazy(1),
conditional_branches: true,
docs: TOGGLE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(toggle_callback)),
..Default::default()
},
);
}

489
core/src/stl/control.rs Normal file
View file

@ -0,0 +1,489 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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, Type};
use crate::sym::{SymTable, Symbol, ValueType, Args};
use std::rc::Rc;
use std::process;
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))";
fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
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? {
cond = cond_from_eval;
} else {
return Err(start_trace(("if", "first arg must be a bool").into()));
}
}
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()));
}
}
Ctr::Bool(cond_from_car) => cond = cond_from_car,
_ => return Err(start_trace(("if", "first arg must be a bool").into())),
}
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()));
}
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)
},
_ => {
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?;
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 {
// 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)
},
_ => {
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?;
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()))
}
}
}
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.";
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()));
}
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())),
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();
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())
}
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))";
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()))
}
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";
fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut cursor = 0;
let mut err_trace: Traceback = 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.clone().with_trace(
("circuit", "impossible condition")
.into());
}
},
Ctr::Integer(i) => return i == 0,
_ => err_trace = err_trace.clone().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))
}
}
const ASSERT_DOCSTRING: &str = "Takes one input: a boolean (or form that evaluates to a boolean).
If input is false, a traceback is started and code throws an error.
Otherwise, if input is true, returns None.";
fn assert_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = *ast.car {
if b {
Ok(Ctr::None)
} else {
Err(start_trace(("assert", "assertion failed").into()))
}
} else {
Err(start_trace(("assert", "impossible arg").into()))
}
}
const EXIT_DOCSTRING: &str = "Takes on input: an integer used as an exit code.
Entire REPL process quits with exit code.";
fn exit_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Integer(i) = *ast.car {
if i > 2 ^ 32 {
panic!("argument to exit too large!")
} else {
process::exit(i as i32);
}
} else {
panic!("impossible argument to exit")
}
}
pub fn add_control_lib(syms: &mut SymTable) {
syms.insert(
"exit".to_string(),
Symbol {
name: String::from("exit"),
args: Args::Strict(vec![Type::Integer]),
conditional_branches: false,
docs: EXIT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(exit_callback)),
..Default::default()
},
);
syms.insert(
"assert".to_string(),
Symbol {
name: String::from("assert"),
args: Args::Strict(vec![Type::Bool]),
conditional_branches: false,
docs: ASSERT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(assert_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"if".to_string(),
Symbol {
name: String::from("if"),
args: Args::Lazy(3),
conditional_branches: true,
docs: IF_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(if_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"let".to_string(),
Symbol {
name: String::from("let"),
args: Args::Infinite,
conditional_branches: true,
docs: LET_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(let_callback)),
..Default::default()
},
);
syms.insert(
"while".to_string(),
Symbol {
name: String::from("while"),
args: Args::Infinite,
conditional_branches: true,
docs: WHILE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(while_callback)),
..Default::default()
},
);
syms.insert(
"circuit".to_string(),
Symbol {
name: String::from("circuit"),
args: Args::Infinite,
conditional_branches: true,
docs: CIRCUIT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(circuit_callback)),
optimizable: true,
..Default::default()
},
);
}

541
core/src/stl/decl.rs Normal file
View file

@ -0,0 +1,541 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava 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, Type};
use crate::sym::{SymTable, Symbol, UserFn, ValueType, Args};
use std::rc::Rc;
const QUOTE_DOCSTRING: &str = "takes a single unevaluated tree and returns it as it is: unevaluated.";
fn quote_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() > 1 {
Err(start_trace(("quote", "do not quote more than one thing at a time").into()))
} else {
Ok(*ast.car.clone())
}
}
const EVAL_DOCSTRING: &str = "takes an unevaluated argument and evaluates it.
Specifically, does one pass of the tree simplification algorithm.
If you have a variable referencing another variable you will get the
referenced variable.";
fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() > 1 {
Err(start_trace(
("eval", "do not eval more than one thing at a time")
.into()))
} else {
match *ast.car {
Ctr::Seg(ref s) => {
match eval(s, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => if let Ctr::Seg(ref inner) = *s {
match eval(inner, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => Ok(*s),
}
} else {
Ok(*s)
},
}
}
Ctr::Symbol(ref sym) => {
let intermediate = syms.call_symbol(sym, &Seg::new(), true);
if let Err(e) = intermediate {
return Err(e.with_trace(
("eval", "evaluation failure")
.into()))
}
let res = *intermediate?;
if let Ctr::Seg(ref s) = res {
match eval(s, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => Ok(*s),
}
} else {
Ok(res)
}
},
_ => Ok(*ast.car.clone())
}
}
}
const HELP_DOCSTRING: &str = "prints help text for a given symbol. Expects only one argument.";
fn help_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 1 {
return Err(start_trace(("help", "expected one input").into()));
}
if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(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(start_trace(("help", format!("{symbol} is undefined")).into()));
}
} else {
return Err(start_trace(("help", "expected input to be a symbol").into()));
}
Ok(Ctr::None)
}
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<Ctr, Traceback> {
if ast.len() != 1 {
Err(start_trace(("set?", "expcted one input").into()))
} else if let Ctr::Symbol(ref symbol) = *ast.car {
Ok(Ctr::Bool(syms.get(symbol).is_some()))
} else {
Err(start_trace(("set?", "expected argument to be a input").into()))
}
}
const ENV_DOCSTRING: &str = "takes no arguments
prints out all available symbols and their associated values";
fn env_callback(_ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut functions = vec![];
let mut variables = vec![];
for (name, val) in syms.iter() {
if let ValueType::VarForm(l) = &val.value {
let token: String = match l.to_type() {
Type::Lambda => format!("{}: <lambda>", name),
Type::Seg => format!("{}: <form>", name),
_ => format!("{}: {}", name, val.value),
};
variables.push(token);
} else {
functions.push(name.clone());
}
}
println!("VARIABLES:");
for var in variables {
println!("{}", var)
}
println!("\nFUNCTIONS:");
for func in functions {
println!("{}", func);
}
Ok(Ctr::None)
}
const LAMBDA_DOCSTRING: &str = "Takes two arguments of any type.
No args are evaluated when lambda is called.
Lambda makes sure the first argument is a list of symbols (or 'arguments') to the lambda function.
The next arg is stored in a tree to evaluate on demand.
Example: (lambda (x y) (add x y))
This can then be evaluated like so:
((lambda (x y) (add x y)) 1 2)
which is functionally equivalent to:
(add 1 2)";
fn lambda_callback(
ast: &Seg,
_syms: &mut SymTable
) -> Result<Ctr, Traceback> {
let mut args = vec![];
if let Ctr::Seg(ref arg_head) = *ast.car {
if !arg_head.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Symbol(ref s) = *arg {
args.push(s.clone());
true
} else if let Ctr::None = *arg {
// no args case
true
} else {
false
}
}) {
Err(start_trace(("lambda", "lambda inputs should all be symbols").into()))
} else if let Ctr::Seg(ref eval_head) = *ast.cdr {
if let Ctr::Seg(_) = *eval_head.car {
Ok(Ctr::Lambda(UserFn{
ast: Box::new(eval_head.clone()),
arg_syms: args,
}))
} else {
Err(start_trace(("lambda", "expected list of forms for lambda body").into()))
}
} else {
Err(start_trace(("lambda", "not enough args").into()))
}
} else {
Err(start_trace(("lambda", "expected list of lambda inputs").into()))
}
}
const GETDOC_DOCSTRING: &str = "accepts an unevaluated symbol, returns the doc string.
Returns an error if symbol is undefined.
Note: make sure to quote the input like this:
(get-doc (quote symbol-name))";
fn getdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(sym) = syms.get(symbol) {
Ok(Ctr::String(sym.docs.clone()))
} else {
Err(start_trace(("get-doc", "input is undefined").into()))
}
} else {
Err(start_trace(("get-doc", "expected input to be a symbol").into()))
}
}
const SETDOC_DOCSTRING: &str = "accepts a symbol and a doc string.
Returns an error if symbol is undefined, otherwise sets the symbols docstring to the argument.
Note: make sure to quote the input like this:
(set-doc (quote symbol-name) my-new-docs)";
fn setdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 2 {
Err(start_trace(
("set-doc", "expected two inputs")
.into()))
} else if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(mut sym) = syms.remove(symbol) {
if let Ctr::Seg(ref doc_node) = *ast.cdr {
if let Ctr::String(ref doc) = *doc_node.car {
sym.docs = doc.clone();
syms.insert(sym.name.clone(), sym);
Ok(Ctr::None)
} else {
syms.insert(sym.name.clone(), sym);
Err(start_trace(
("set-doc", "expected second input to be a string")
.into()))
}
} else {
Err(start_trace(
("set-doc", "missing second input somehow")
.into()))
}
} else {
Err(start_trace(
("set-doc", format!("{symbol} is undefined"))
.into()))
}
} else {
Err(start_trace(
("set-doc", "first input must be a symbol")
.into()))
}
}
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)
Additionally, passing a tree as a name will trigger def to evaluate the tree and try to derive
a value from it. If it does not return a String or a Symbol this will result in a failure.
The name of the symbol operated on is returned (as a string).";
pub fn store_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let is_var = ast.len() == 3;
let name: String;
let docs: String;
match *ast.car {
Ctr::String(ref s) => name = s.clone(),
Ctr::Symbol(ref s) => name = s.clone(),
Ctr::Seg(ref s) => match eval(s, syms) {
Err(e) => return Err(e.with_trace(("def", "failed to evaluate symbol name").into())),
Ok(s) => match *s {
Ctr::String(ref s) => name = s.clone(),
Ctr::Symbol(ref s) => name = s.clone(),
_ => {
return Err(start_trace(
("def", "expected symbol name input to evaluate to a symbol or a string")
.into()));
},
}
},
_ => return Err(start_trace(
("def", "expected a string or a symbol as input for symbol name")
.into()))
}
// remove var case
if ast.len() == 1 {
syms.remove(&name);
return Ok(Ctr::String(name))
} else if ast.len() < 3 || ast.len() > 4 {
return Err(start_trace(("def", "expected 3 or 4 inputs").into()))
}
let mut iter: &Seg;
if let Ctr::Seg(ref s) = *ast.cdr {
iter = s;
} else {
return Err(start_trace(("def", "not enough inputs").into()))
}
match *iter.car {
Ctr::String(ref s) => docs = s.clone(),
Ctr::Symbol(ref s) => match syms.call_symbol(s, &Seg::new(), true) {
Ok(d) => if let Ctr::String(doc) = *d {
docs = doc;
} else {
return Err(start_trace(("def", "expected docs input to evaluate to a string").into()))
},
Err(e) => return Err(e.with_trace(
("def", "couldnt evaluate docs form")
.into()))
},
_ => return Err(start_trace(
("def", "expected docs input to at least evaluate to a string if not be one")
.into()))
}
if let Ctr::Seg(ref s) = *iter.cdr {
iter = s;
} else {
return Err(start_trace(("def", "not enough inputs").into()))
}
let mut outer_scope_val: Seg = Seg::new();
let noseg = Seg::new(); // similarly, rust shouldnt need this either
let mut args = &noseg;
let mut var_val_form: &Seg = &outer_scope_val;
let mut expand = false;
match *iter.car {
Ctr::Seg(ref s) if !is_var => args = s,
Ctr::Seg(ref s) if is_var => var_val_form = s,
_ if is_var => {
expand = true;
outer_scope_val = Seg::from_mono(Box::new(*iter.car.clone()));
var_val_form = &outer_scope_val;
},
_ if !is_var => return Err(start_trace(("def", "expected a list of inputs").into())),
_ => unimplemented!(), // rustc is haunted and cursed
}
if is_var {
let var_eval_result = eval(var_val_form, syms);
if let Err(e) = var_eval_result {
return Err(e.with_trace(
("def", format!("couldnt evaluate {var_val_form}"))
.into()))
}
let var_eval_final = *var_eval_result?;
let var_val: Ctr = match var_eval_final {
Ctr::Seg(ref s) if expand => *s.car.clone(),
Ctr::Seg(ref s) if !expand => Ctr::Seg(s.clone()),
_ => var_eval_final,
};
let outer_seg = Seg::from_mono(Box::new(var_val.clone()));
syms.insert(
name.clone(),
Symbol::from_ast(&name, &docs, &outer_seg, None),
);
return Ok(Ctr::String(name))
}
let mut arg_list = vec![];
if !args.circuit(&mut |c: &Ctr| -> bool {
if let Ctr::Symbol(s) = c {
arg_list.push(s.clone());
true
} else if let Ctr::None = c {
// no args case
true
} else {
false
}
}) {
return Err(start_trace(
("def", "all inputs to function must be of type symbol")
.into()))
}
if let Ctr::Seg(ref eval_bodies) = *iter.cdr {
syms.insert(
name.clone(),
Symbol::from_ast(
&name, &docs,
eval_bodies,
Some(arg_list),
),
);
Ok(Ctr::String(name))
} else {
Err(start_trace(
("def", "expected one or more forms to evaluate in function body")
.into()))
}
}
pub fn add_decl_lib_static(syms: &mut SymTable) {
syms.insert(
"help".to_string(),
Symbol {
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)),
..Default::default()
},
);
syms.insert(
"set?".to_string(),
Symbol {
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)),
..Default::default()
},
);
syms.insert(
"quote".to_string(),
Symbol {
name: String::from("quote"),
args: Args::Lazy(1),
conditional_branches: true,
docs: QUOTE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(quote_callback)),
..Default::default()
},
);
syms.insert(
"q".to_string(),
Symbol {
name: String::from("quote"),
args: Args::Lazy(1),
conditional_branches: true,
docs: QUOTE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(quote_callback)),
..Default::default()
},
);
syms.insert(
"eval".to_string(),
Symbol {
name: String::from("eval"),
args: Args::Lazy(1),
conditional_branches: true,
docs: EVAL_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(eval_callback)),
..Default::default()
},
);
syms.insert(
"lambda".to_string(),
Symbol {
name: String::from("lambda"),
args: Args::Lazy(2),
conditional_branches: true,
docs: LAMBDA_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(lambda_callback)),
..Default::default()
}
);
syms.insert(
"get-doc".to_string(),
Symbol {
name: String::from("get-doc"),
args: Args::Strict(vec![Type::Symbol]),
conditional_branches: false,
docs: GETDOC_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(getdoc_callback)),
..Default::default()
}
);
syms.insert(
"set-doc".to_string(),
Symbol {
name: String::from("get-doc"),
args: Args::Strict(vec![Type::Symbol, Type::String]),
conditional_branches: false,
docs: SETDOC_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(setdoc_callback)),
..Default::default()
}
);
syms.insert(
"env".to_string(),
Symbol {
name: String::from("env"),
args: Args::None,
conditional_branches: false,
docs: ENV_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(env_callback)),
..Default::default()
},
);
syms.insert(
"def".to_string(),
Symbol {
name: String::from("define"),
args: Args::Infinite,
conditional_branches: true,
docs: STORE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(
move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, Traceback> {
store_callback(ast, syms)
},
)),
..Default::default()
},
);
}

172
core/src/stl/file.rs Normal file
View file

@ -0,0 +1,172 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava 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::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, ValueType, Args};
use crate::error::{Traceback, start_trace};
use std::io::Write;
use std::fs::{File, read_to_string, OpenOptions};
use std::rc::Rc;
use std::path::Path;
const READ_TO_STRING_DOCSTRING: &str = "Takes one input (filename).
If file exists, returns a string containing file contents.
If the file does not exist returns error.";
fn read_to_string_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
let res = read_to_string(filename);
if let Ok(s) = res {
Ok(Ctr::String(s))
} else {
Err(start_trace(
("read-file", res.err().unwrap().to_string())
.into()))
}
} else {
Err(start_trace(("read-file", "impossible arg").into()))
}
}
const WRITE_TO_FILE_DOCSTRING: &str = "Takes two inputs: a filename and a string of content.
Writes contents to the file and returns None.";
fn write_to_file_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
if let Ctr::Seg(ref next) = *ast.cdr {
if let Ctr::String(ref body) = *next.car {
let fres = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(filename);
if fres.is_err() {
Err(start_trace(
("write-file",
format!("couldn't open file: {}", fres.err().unwrap().to_string()))
.into()))
} else {
if let Err(e) = write!(&mut fres.unwrap(), "{}", body) {
Err(start_trace(
("write-file", format!("failed to write to file: {}", e))
.into()))
} else {
Ok(Ctr::None)
}
}
} else {
Err(start_trace(("write-file", "impossible arg").into()))
}
} else {
Err(start_trace(("write-file", "not enough args").into()))
}
} else {
Err(start_trace(("write-file", "impossible arg").into()))
}
}
const APPEND_TO_FILE_DOCSTRING: &str = "Takes two inputs: a filename and a string of content.
Appends content to the end of the file and returns None";
fn append_to_file_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
if let Ctr::Seg(ref next) = *ast.cdr {
if let Ctr::String(ref body) = *next.car {
let fres = File::options().append(true).open(filename);
if fres.is_err() {
Err(start_trace(
("append-file",
format!("couldn't open file: {}", fres.err().unwrap().to_string()))
.into()))
} else {
if let Err(e) = write!(&mut fres.unwrap(), "{}", body) {
Err(start_trace(
("append-file", format!("failed to write to file: {}", e))
.into()))
} else {
Ok(Ctr::None)
}
}
} else {
Err(start_trace(("append-file", "impossible arg").into()))
}
} else {
Err(start_trace(("append-file", "not enough args").into()))
}
} else {
Err(start_trace(("append-file", "impossible arg").into()))
}
}
const IS_FILE_EXISTS_DOCSTRING: &str = "Takes one input: a filename.
Returns true or false depending on if the file exists.";
fn is_file_exists_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
Ok(Ctr::Bool(Path::new(&filename).exists()))
} else {
Err(Traceback::new().with_trace(("exists?", "impossible arg").into()))
}
}
pub fn add_file_lib(syms: &mut SymTable) {
syms.insert(
"read-file".to_string(),
Symbol {
name: String::from("read-file"),
args: Args::Strict(vec![Type::String]),
conditional_branches: false,
docs: READ_TO_STRING_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(read_to_string_callback)),
..Default::default()
}
);
syms.insert(
"write-file".to_string(),
Symbol {
name: String::from("write-file"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: WRITE_TO_FILE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(write_to_file_callback)),
..Default::default()
}
);
syms.insert(
"append-file".to_string(),
Symbol {
name: String::from("append-file"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: APPEND_TO_FILE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(append_to_file_callback)),
..Default::default()
}
);
syms.insert(
"exists?".to_string(),
Symbol {
name: String::from("exists?"),
args: Args::Strict(vec![Type::String]),
conditional_branches: false,
docs: IS_FILE_EXISTS_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(is_file_exists_callback)),
..Default::default()
}
);
}

707
core/src/stl/math.rs Normal file
View file

@ -0,0 +1,707 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::segment::{Ctr, Seg};
use crate::sym::{SymTable, ValueType, Symbol, Args};
use crate::error::{Traceback, start_trace};
use std::rc::Rc;
fn isnumeric(arg: &Ctr) -> bool {
matches!(arg, Ctr::Integer(_) | Ctr::Float(_))
}
const ADD_DOCSTRING: &str =
"traverses over N args, which must all evaluate to an Integer or Float.
Adds each arg up to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled.";
fn add_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let mut res = Ctr::Integer(0);
let mut culprit: Ctr = Ctr::None;
let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool {
if !isnumeric(c) {
culprit = c.clone();
false
} else {
res = res.clone() + c.clone();
true
}
});
if !type_consistent {
Err(start_trace(
("add", format!("{} is not a number!", culprit))
.into()))
} else {
Ok(res)
}
}
const SUB_DOCSTRING: &str = "traverses over N args, which must all evaluate to an Integer or Float.
Subtracts each arg from the first leading to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled.";
fn sub_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
if !isnumeric(ast.car.as_ref()) {
return Err(start_trace(
("sub", format!("{} is not a number!", ast.car.as_ref()))
.into()))
}
let mut res = *ast.car.clone();
let mut culprit: Ctr = Ctr::None;
if let Ctr::Seg(ref subsequent_operands) = *ast.cdr {
let type_consistent = subsequent_operands.circuit(&mut |c: &Ctr| -> bool {
if !isnumeric(c) {
culprit = c.clone();
false
} else {
res = res.clone() - c.clone();
true
}
});
if !type_consistent {
Err(start_trace(
("sub", format!("{} is not a number!", culprit))
.into()))
} else {
Ok(res)
}
} else {
Err(start_trace(
("sub", "expected at least two inputs")
.into()))
}
}
const DIV_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float.
divides arg1 by arg2. WARNING: does not acocunt for under/overflows or float precision.
Consult source code for a better understanding of how extreme values will be handled.";
fn div_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err(start_trace(
("div", format!("{} is not a number!", ast.car.as_ref()))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
if !isnumeric(&second) {
return Err(start_trace(
("div", format!("{} is not a number!", second))
.into()))
}
Ok(first / second)
} else {
Err(start_trace(
("div", "expected exactly two inputs")
.into()))
}
}
const MUL_DOCSTRING: &str =
"traverses over N args, which must all evaluate to an Integer or Float.
Multiplies each arg up to a final result. WARNING: does not acocunt for under/overflows.
Consult source code for a better understanding of how extreme values will be handled.";
fn mul_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let mut res = Ctr::Integer(1);
let mut culprit: Ctr = Ctr::None;
let type_consistent = ast.circuit(&mut |c: &Ctr| -> bool {
if !isnumeric(c) {
culprit = c.clone();
false
} else {
res = res.clone() * c.clone();
true
}
});
if !type_consistent {
Err(start_trace(
("mul", format!("{} is not a number!", culprit))
.into()))
} else {
Ok(res)
}
}
const INTCAST_DOCSTRING: &str = "takes a single arg and attempts to cast it to an Integer.
This will work for a float or a potentially a string.
If the cast to Integer fails, it will return Nothing and print an error.
Casting a float to an int will drop its decimal.";
fn intcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
// special case for float
if let Ctr::Float(f) = *ast.car {
Ok(Ctr::Integer(f as i128))
} else if let Ctr::String(ref s) = *ast.car {
let int = str::parse::<i128>(s);
if int.is_err() {
Err(start_trace(
("int", int.err().unwrap().to_string())
.into()))
} else {
Ok(Ctr::Integer(int.ok().unwrap()))
}
} else {
Err(start_trace(
("int", "expected a float or a string")
.into()))
}
}
const FLOATCAST_DOCSTRING: &str = "takes a single arg and attempts to cast it to a float.
This will work for an integer or potentially a string.
If the cast to integer fails, this function will return nothing and print an error.
Casting an integer to a float can result in bad behaviour since float nodes are based on 64bit floats and int nodes are based on 128 bit integers.";
fn floatcast_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
// special case for float
if let Ctr::Integer(i) = *ast.car {
Ok(Ctr::Float(i as f64))
} else if let Ctr::String(ref s) = *ast.car {
let flt = str::parse::<f64>(s);
if flt.is_err() {
Err(start_trace(
("float", flt.err().unwrap().to_string())
.into()))
} else {
Ok(Ctr::Float(flt.ok().unwrap()))
}
} else {
Err(start_trace(
("float", "expected a string or an integer")
.into()))
}
}
const EXP_DOCSTRING: &str = "Takes two args, both expected to be numeric.
Returns the first arg to the power of the second arg.
Does not handle overflow or underflow.
PANIC CASES:
- arg1 is a float and arg2 is greater than an int32
- an integer exceeding the size of a float64 is raised to a float power
- an integer is rased to the power of another integer exceeding the max size of an unsigned 32bit integer";
fn exp_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err(start_trace(
("exp", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err(start_trace(
("exp", "expected at least two inputs")
.into()))
}
if !isnumeric(&second) {
return Err(start_trace(
("exp", format!("{} is not a number!", second))
.into()))
}
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(lf, rf))),
Ctr::Integer(ri) => Ok(Ctr::Float(f64::powi(lf, ri as i32))),
_ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Float(f64::powf(li as f64, rf))),
Ctr::Integer(ri) => Ok(Ctr::Integer(li.pow(ri as u32))),
_ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
},
_ => Err(start_trace(
("exp", "not implemented for these input types")
.into())),
}
}
const MOD_DOCSTRING: &str = "Takes two args, both expected to be numeric.
Returns a list of two values: the modulus and the remainder.
Example: (mod 5 3) -> (1 2)
PANIC CASES:
- A float is modulo an integer larger than a max f64
- An integer larger than a max f64 is modulo a float
";
fn mod_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err(start_trace(
("mod", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err(start_trace(
("mod", "expected at least two inputs")
.into()))
}
if !isnumeric(&second) {
return Err(start_trace(
("mod", format!("{} is not a number!", second))
.into()))
}
let mut ret = Seg::new();
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => {
ret.append(Box::new(Ctr::Integer((lf / rf) as i128)));
ret.append(Box::new(Ctr::Integer((lf % rf) as i128)));
}
Ctr::Integer(ri) => {
ret.append(Box::new(Ctr::Integer((lf / ri as f64) as i128)));
ret.append(Box::new(Ctr::Integer((lf % ri as f64) as i128)));
}
_ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => {
ret.append(Box::new(Ctr::Integer((li as f64 / rf) as i128)));
ret.append(Box::new(Ctr::Integer((li as f64 % rf) as i128)));
}
Ctr::Integer(ri) => {
ret.append(Box::new(Ctr::Integer(li / ri)));
ret.append(Box::new(Ctr::Integer(li % ri)));
}
_ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
},
_ => return Err(start_trace(
("mod", "not implemented for these input types")
.into())),
}
Ok(Ctr::Seg(ret))
}
const ISGT_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float.
Returns true or false according to whether the first argument is bigger than the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
fn isgt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err(start_trace(
("gt?", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err(start_trace(
("gt?", "expected at least two inputs")
.into())) }
if !isnumeric(&second) {
return Err(start_trace(
("gt?", format!("{} is not a number!", second))
.into()))
}
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(lf > rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(lf > ri as f64)),
_ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(li as f64 > rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(li > ri)),
_ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
},
_ => Err(start_trace(
("gt?", "not implemented for these input types")
.into())),
}
}
const ISLT_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float.
Returns true or false according to whether the first argument is smaller than the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
fn islt_callback(ast: &Seg, _: &mut SymTable) -> Result<Ctr, Traceback> {
let first = *ast.car.clone();
if !isnumeric(&first) {
return Err(start_trace(
("lt?", format!("{} is not a number!", first))
.into()))
}
let second: Ctr;
if let Ctr::Seg(ref s) = *ast.cdr {
second = *s.car.clone();
} else {
return Err(start_trace(
("lt?", "expected at least two inputs")
.into()))
}
if !isnumeric(&second) {
return Err(start_trace(
("lt?", format!("{} is not a number!", second))
.into()))
}
match first {
Ctr::Float(lf) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool(lf < rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(lf < ri as f64)),
_ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
},
Ctr::Integer(li) => match second {
Ctr::Float(rf) => Ok(Ctr::Bool((li as f64) < rf)),
Ctr::Integer(ri) => Ok(Ctr::Bool(li < ri)),
_ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
},
_ => Err(start_trace(
("lt?", "not implemented for these input types")
.into())),
}
}
const ISGTE_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float.
Returns true or false according to whether the first argument is greater than or equal to the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
fn isgte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
match islt_callback(ast, syms) {
Ok(s) => if let Ctr::Bool(b) = s {
Ok(Ctr::Bool(!b))
} else {
Err(start_trace(
("gte?", format!("madness: lt? returned non bool {s}"))
.into()))
},
Err(e) => Err(e.with_trace(("gte?", "error calling lt?").into())),
}
}
const ISLTE_DOCSTRING: &str = "takes two args, which must both evaluate to an Integer or Float.
Returns true or false according to whether the first argument is less than or equal to the second argument.
May panic if an integer larger than a max f64 is compared to a float.";
fn islte_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
match isgt_callback(ast, syms) {
Ok(s) => if let Ctr::Bool(b) = s {
Ok(Ctr::Bool(!b))
} else {
Err(start_trace(
("lte?", format!("madness: gt? returned non bool {s}"))
.into()))
},
Err(e) => Err(e.with_trace(("lte?", "error calling gt?").into())),
}
}
const INC_DOCSTRING: &str = "Accepts a single argument, expects it to be a symbol.
The symbol is fetched from the symbol table.
If the symbol is not an integer an error is returned.
The symbol is redefined as symbol + 1.
This call is similar to the following:
(def counter '' (add counter 1))
with the caveat that your docstring is preserved.";
fn inc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone();
} else {
return Err(start_trace(
("inc", "expected input to be a symbol")
.into()));
}
let sym_ret = syms
.remove(&var_name);
if sym_ret.is_none() {
return Err(start_trace(
("inc", format!("input ({var_name}) is not defined"))
.into()))
}
let mut sym = sym_ret.unwrap();
if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Integer(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b + 1)));
} else {
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("inc", format!("expected {var_name} to be an integer"))
.into()));
}
} else {
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("inc", format!("expected {var_name} to be an integer"))
.into()));
}
syms.insert(var_name, sym);
Ok(Ctr::None)
}
const DEC_DOCSTRING: &str = "Accepts a single argument, expects it to be a symbol.
The symbol is fetched from the symbol table.
If the symbol is not an integer an error is returned.
The symbol is redefined as symbol - 1.
This call is similar to the following:
(def counter '' (sub counter 1))
with the caveat that your docstring is preserved.";
fn dec_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone();
} else {
return Err(start_trace(
("dec", "expected input to be a symbol")
.into()));
}
let sym_ret = syms
.remove(&var_name);
if sym_ret.is_none() {
return Err(start_trace(
("dec", format!("input ({var_name}) is not defined"))
.into()))
}
let mut sym = sym_ret.unwrap();
if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Integer(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Integer(b - 1)));
} else {
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("dec", format!("expected {var_name} to be an integer"))
.into()));
}
} else {
syms.insert(var_name.clone(), sym);
return Err(start_trace(
("dec", format!("expected {var_name} to be an integer"))
.into()));
}
syms.insert(var_name, sym);
Ok(Ctr::None)
}
pub fn add_math_lib(syms: &mut SymTable) {
syms.insert(
"add".to_string(),
Symbol {
name: String::from("add"),
args: Args::Infinite,
conditional_branches: false,
docs: ADD_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(add_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"sub".to_string(),
Symbol {
name: String::from("sub"),
args: Args::Infinite,
conditional_branches: false,
docs: SUB_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(sub_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"div".to_string(),
Symbol {
name: String::from("div"),
args: Args::Lazy(2),
conditional_branches: false,
docs: DIV_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(div_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"mul".to_string(),
Symbol {
name: String::from("mul"),
args: Args::Infinite,
conditional_branches: false,
docs: MUL_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(mul_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"int".to_string(),
Symbol {
name: String::from("int"),
args: Args::Lazy(1),
conditional_branches: false,
docs: INTCAST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(intcast_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"float".to_string(),
Symbol {
name: String::from("float"),
args: Args::Lazy(1),
conditional_branches: false,
docs: FLOATCAST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(floatcast_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"exp".to_string(),
Symbol {
name: String::from("exp"),
args: Args::Lazy(2),
conditional_branches: false,
docs: EXP_DOCSTRING.to_string(),
optimizable: true,
value: ValueType::Internal(Rc::new(exp_callback)),
..Default::default()
},
);
syms.insert(
"mod".to_string(),
Symbol {
name: String::from("mod"),
args: Args::Lazy(2),
conditional_branches: false,
docs: MOD_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(mod_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"gt?".to_string(),
Symbol {
name: String::from("gt?"),
args: Args::Lazy(2),
conditional_branches: false,
docs: ISGT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(isgt_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"lt?".to_string(),
Symbol {
name: String::from("lt?"),
args: Args::Lazy(2),
conditional_branches: false,
docs: ISLT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(islt_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"gte?".to_string(),
Symbol {
name: String::from("gt?"),
args: Args::Lazy(2),
conditional_branches: false,
docs: ISGTE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(isgte_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"lte?".to_string(),
Symbol {
name: String::from("lt?"),
args: Args::Lazy(2),
conditional_branches: false,
docs: ISLTE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(islte_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"inc".to_string(),
Symbol {
name: String::from("inc"),
args: Args::Lazy(1),
conditional_branches: true,
docs: INC_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(inc_callback)),
..Default::default()
},
);
syms.insert(
"dec".to_string(),
Symbol {
name: String::from("dec"),
args: Args::Lazy(1),
conditional_branches: true,
docs: DEC_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(dec_callback)),
..Default::default()
},
);
}

358
core/src/stl/strings.rs Normal file
View file

@ -0,0 +1,358 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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::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<Ctr, Traceback> {
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<Ctr, Traceback> {
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()))
}
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<Ctr, Traceback> {
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<Ctr, Traceback> {
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 a string and two integers (arg1, arg2 and arg3 respectively).
Returns the substring of arg1 starting at arg2 and ending at arg3.
Returns error if arg2 or arg3 are negative, and if arg3 is larger than the length of arg1.";
fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
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 third_arg_obj: &Ctr;
let start: usize;
if let Ctr::Seg(ref s) = *ast.cdr {
second_arg_obj = &*s.car;
third_arg_obj = &*s.cdr;
} else {
return Err(start_trace(
("substr", "expected three inputs")
.into()))
}
if let Ctr::Integer(i) = &*second_arg_obj {
if i < &0 {
return Err(start_trace(("substr", "start index cannot be negative").into()))
}
start = i.clone() as usize;
} else {
return Err(start_trace(
("substr", "expected second input to be an integer")
.into()))
}
if start > parent_str.len() {
return Err(start_trace(("substr", "start index larger than source string").into()))
}
let end: usize;
let third_arg_inner: &Ctr;
if let Ctr::Seg(ref s) = *third_arg_obj {
third_arg_inner = &*s.car;
} else {
return Err(start_trace(
("substr", "expected three inputs")
.into()))
}
if let Ctr::Integer(i) = &*third_arg_inner {
if i < &0 {
return Err(start_trace(("substr", "end index cannot be negative").into()))
}
end = i.clone() as usize;
} else {
return Err(start_trace(
("substr", "expected third input to be an integer")
.into()))
}
if end > parent_str.len() {
return Err(start_trace(("substr", "end index larger than source string").into()))
}
if end <= start {
return Err(start_trace(("substr", "end index must be larger than start index").into()))
}
Ok(Ctr::String(parent_str[start..end].to_string()))
}
const IS_SUBSTR_DOCSTRING: &str =
"Takes two strings. Returns true if string1 contains at least one instance of string2";
fn is_substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
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<Ctr, Traceback> {
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<Ctr, Traceback> {
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 {
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)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"substr?".to_string(),
Symbol {
name: String::from("substr?"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: IS_SUBSTR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(is_substr_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"substr".to_string(),
Symbol {
name: String::from("substr"),
args: Args::Strict(vec![Type::String, Type::Integer, Type::Integer]),
conditional_branches: false,
docs: SUBSTR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(substr_callback)),
optimizable: true,
..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)),
optimizable: true,
..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)),
optimizable: true,
..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)),
optimizable: true,
..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()
}
);
}

582
core/src/sym.rs Normal file
View file

@ -0,0 +1,582 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* 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, Type};
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
#[derive(Clone)]
pub struct SymTable(HashMap<String, Symbol>, usize);
#[derive(Debug, Clone)]
pub struct UserFn {
// Un-evaluated abstract syntax tree
pub ast: Box<Seg>,
// list of argument string tokens
pub arg_syms: Vec<String>,
}
/* A symbol may either be a pointer to a function
* or a syntax tree to eval with the arguments or
* a simple variable declaration (which can also
* be a macro)
*/
#[derive(Clone)]
pub enum ValueType {
Internal(Rc<dyn Fn(&Seg, &mut SymTable) -> Result<Ctr, Traceback>>),
FuncForm(UserFn),
VarForm(Box<Ctr>),
}
/* Function Args
* If Lazy, is an integer denoting number of args
* If Strict, is a list of type tags denoting argument type.
*/
#[derive(Clone)]
pub enum Args {
Lazy(u128),
Strict(Vec<Type>),
Infinite,
None,
}
#[derive(Clone)]
pub struct Symbol {
pub value: ValueType,
pub name: String,
pub args: Args,
// for internal control flow constructs
pub conditional_branches: bool,
pub docs: String,
// see SymTable::Insert
// (only pub begrudgingly)
pub __generation: usize,
// here this means either that
// a variable is constant or that
// a function is pure.
pub optimizable: bool,
}
impl SymTable {
pub fn new() -> SymTable {
SymTable(HashMap::<String, Symbol>::new(), 0)
}
pub fn get(&self, arg: &String) -> Option<&Symbol> {
self.0.get(arg)
}
pub fn contains_key(&self, arg: &String) -> bool {
self.0.contains_key(arg)
}
pub fn insert(&mut self, k: String, mut v: Symbol) -> Option<Symbol> {
self.1 += 1;
v.__generation = self.1;
self.0.insert(k, v)
}
pub fn remove(&mut self, arg: &String) -> Option<Symbol> {
self.0.remove(arg)
}
pub fn iter(&self) -> std::collections::hash_map::Iter<'_, String, Symbol> {
self.0.iter()
}
pub fn keys(&self) -> std::collections::hash_map::Keys<String, Symbol> {
self.0.keys()
}
pub fn update(&mut self, other: &mut SymTable) {
/* updates self with all syms in other that match the following cases:
* * sym is not in self
* * sym has a newer generation than the entry in self
*/
let tmp = self.1;
for i in other.iter() {
self.0.entry(i.0.to_string())
.and_modify(|inner: &mut Symbol| {
if tmp < i.1.__generation {
inner.__generation = i.1.__generation;
inner.value = i.1.value.clone();
inner.args = i.1.args.clone();
inner.docs = i.1.docs.clone();
inner.conditional_branches = i.1.conditional_branches;
inner.name = i.1.name.clone();
}
})
.or_insert(i.1.clone());
}
}
pub fn call_symbol(
&mut self,
name: &String,
args: &Seg,
call_func: bool,
) -> Result<Box<Ctr>, Traceback> {
let outer_scope_seg: Seg;
let mut call_args = args;
let mut name_token = name.to_string();
let mut symbol = match self.remove(name) {
Some(s) => s,
/* implicit load:
* on a call to an undefined function
* assume a shell command is being run
*/
#[cfg(feature="implicit-load")]
None if call_func => match self.remove(&env!("POSIX_LOAD_NAME").to_string()) {
Some(s) => {
name_token = String::from(env!("POSIX_LOAD_NAME").to_string());
/* highly unfortunate circumstance
* we must now rebuild the original ast
* costs a whole clone of the args
* this feature is non-standard
*/
outer_scope_seg = Seg::from(
Box::from(Ctr::Symbol(name.to_string())),
if let Ctr::None = *args.car {
Box::from(Ctr::None)
} else {
Box::from(Ctr::Seg(args.clone()))
},
);
call_args = &outer_scope_seg;
s
},
None => return Err(
Traceback::new()
.with_trace((
&format!("(implicit load ({}))", name),
"(load function not found)").into())
)
},
None => return Err(
Traceback::new()
.with_trace((name, "(is an undefined symbol)").into())
),
};
// will re-increment when inserted
// but we dont want to increment it
symbol.__generation -= 1;
self.insert(name_token, symbol.clone());
if let ValueType::VarForm(ref val) = symbol.value {
match **val {
Ctr::Lambda(ref l) if call_func => {
return call_lambda(
l,
&Box::new(Ctr::Seg(args.clone())),
self
)
},
Ctr::Symbol(ref s) if self.is_function_type(s).is_some()
&& self.is_function_type(s).unwrap() => {
symbol = match self.remove(s) {
Some(sym) => sym,
None => return Err(
Traceback::new().with_trace(
(name, format!("(references undefined symbol {})", s))
.into())),
};
symbol.__generation -= 1;
self.insert(symbol.name.clone(), symbol.clone());
},
_ => return Ok(val.clone()),
}
}
if call_func {
symbol.call(call_args, self)
} else {
// its a function but call_func is off
Ok(Box::new(Ctr::Symbol(name.to_string())))
}
}
pub fn is_function_type(&self, name: &String) -> Option<bool> {
match self.get(name) {
/* implicit-load: assume you just drew load function */
None if cfg!(feature="implicit-load") => Some(true),
Some(value) => if let ValueType::VarForm(ref val) = value.value {
match **val {
Ctr::Lambda(_) => Some(true),
Ctr::Symbol(ref n) => self.is_function_type(n),
_ => Some(false),
}
} else {
Some(true)
},
None => None,
}
}
}
impl Default for SymTable {
fn default() -> Self {
Self::new()
}
}
impl Args {
fn validate_inputs(&self, args: &Seg, caller: &String) -> Result<(), Traceback> {
match self {
Args::None => {
if args.len() == 1 {
if let Ctr::None = *args.car {
return Ok(());
} else {
return Err(start_trace(
(caller, "expected no args")
.into()))
}
} else {
return Err(start_trace(
(caller, "expected no args")
.into()))
}
}
Args::Infinite => {
if !args.is_empty() {
return Ok(());
} else {
return Err(start_trace(
(caller, "expected args, but none were provided")
.into()))
}
}
Args::Lazy(ref num) => {
let called_arg_count = args.len();
if *num == 0 {
if let Ctr::None = *args.car {
//pass
} else {
return Err(start_trace(
(caller, "expected no args, got 1 or more")
.into()))
}
} else if *num != called_arg_count {
return Err(start_trace(
(caller, format!("expected {} args. Got {}.", num, called_arg_count))
.into()))
} else if let Ctr::None = *args.car {
return Err(start_trace(
(caller, format!("expected {} args. Got 0.", num))
.into()))
}
}
Args::Strict(ref arg_types) => {
let mut idx: usize = 0;
let mut mismatch = false;
let passes = args.circuit(&mut |c: &Ctr| -> bool {
if idx >= arg_types.len() {
return false;
}
if let Ctr::None = c {
return false;
}
if arg_types[idx] == c.to_type() {
idx += 1;
return true;
}
mismatch = true;
false
});
if passes && idx < (arg_types.len() - 1) {
return Err(start_trace(
(caller, format!("{} too few arguments", arg_types.len() - (idx + 1)))
.into()))
}
if !passes {
if mismatch {
return Err(start_trace(
(caller, format!("arg {} expected to be {}", idx + 1, arg_types[idx]))
.into()))
}
if idx > (arg_types.len() - 1) {
return Err(start_trace(
(caller, "too many arguments".to_string())
.into()))
}
if idx < (arg_types.len() - 1) {
return Err(start_trace(
(caller, "too few arguments".to_string())
.into()))
}
}
}
}
Ok(())
}
}
impl fmt::Display for Args {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Args::None => write!(f, "none"),
Args::Infinite => write!(f, "infinite, untyped"),
Args::Lazy(n) => write!(f, "{} args of any type", n),
Args::Strict(s) => {
write!(f, "types: ")?;
for arg in s {
write!(f, "{} ", arg)?
}
Ok(())
}
}
}
}
impl fmt::Display for UserFn {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "(lambda (")?;
let mut arg_iter = (&self.arg_syms).into_iter();
if let Some(elem) = arg_iter.next() {
write!(f, "{}", elem)?;
for i in arg_iter {
write!(f, " {}", i)?;
}
}
write!(f, ") {})", self.ast.car)?;
Ok(())
}
}
impl fmt::Display for ValueType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ValueType::VarForm(ref s) => write!(f, "{}", s),
ValueType::Internal(_) => write!(f, "<builtin>"),
ValueType::FuncForm(ref form) => {
write!(f, "args: ")?;
for sym in form.arg_syms.clone() {
write!(f, "{} ", sym)?;
}
write!(f, "\nform: {}", form.ast)?;
Ok(())
}
}
}
}
impl Symbol {
/* call
* routine is called by eval when a symbol is expanded
*/
pub fn call(&self, args: &Seg, syms: &mut SymTable) -> Result<Box<Ctr>, Traceback> {
let evaluated_args: &Seg;
let mut outer_scope_seg_storage = Seg::new();
let mut cursor = 0;
let mut errcon: Traceback = Traceback::new();
if !self.conditional_branches {
if !args.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Seg(ref s) = arg {
let eval_res = eval(s, syms);
if eval_res.is_err() {
errcon = eval_res.err().unwrap();
return false
}
outer_scope_seg_storage.append(eval_res.unwrap());
} else if let Ctr::Symbol(ref s) = arg {
let eval_res = syms.call_symbol(
s,
&outer_scope_seg_storage,
false
);
if eval_res.is_err() {
errcon = eval_res.err().unwrap();
return false
}
outer_scope_seg_storage.append(eval_res.unwrap());
} else {
outer_scope_seg_storage.append(Box::new(arg.clone()));
}
cursor += 1;
true
}) {
return Err(
errcon.with_trace((
&self.name,
format!("error evaluating arg {cursor}"),
).into()))
}
evaluated_args = &outer_scope_seg_storage;
} else {
evaluated_args = args;
};
self.args.validate_inputs(evaluated_args, &self.name)?;
match &self.value {
ValueType::VarForm(ref f) => Ok(Box::new(*f.clone())),
ValueType::Internal(ref f) => Ok(Box::new(f(evaluated_args, syms)?)),
ValueType::FuncForm(ref f) => {
// stores any value overwritten by local state
// If this ever becomes ASYNC this will need to
// become a more traditional stack design, and the
// global table will need to be released
let mut holding_table = SymTable::new();
// Prep var table for function execution
for n in 0..f.arg_syms.len() {
if let Some(old) = syms.insert(
f.arg_syms[n].clone(),
Symbol {
name: f.arg_syms[n].clone(),
value: ValueType::VarForm(Box::new(evaluated_args[n].clone())),
args: Args::None,
docs: format!("local argument to {}", f.arg_syms[n].clone()),
conditional_branches: false,
..Default::default()
},
) {
holding_table.insert(f.arg_syms[n].clone(), old);
}
}
// execute function
let mut result: Box<Ctr>;
let mut iterate = &*(f.ast);
loop {
if let Ctr::Seg(ref data) = *iterate.car {
match eval(data, syms) {
Ok(ctr) => result = ctr,
Err(e) => return Err(e.with_trace((&self.name, "returned error").into())),
}
} else {
let temp = Seg::from_mono(iterate.car.clone());
match eval(&temp, syms) {
Ok(ctr) => {
if let Ctr::Seg(s) = *ctr {
result = s.car.clone();
} else {
result = ctr;
}
}
Err(e) => return Err(
e.with_trace((&self.name, "returned error").into())
),
}
}
match *iterate.cdr {
Ctr::Seg(ref next) => iterate = next,
Ctr::None => break,
_ => panic!("function body not in standard form!"),
}
}
// clear local vars and restore previous values
for n in 0..f.arg_syms.len() {
syms.remove(&f.arg_syms[n]);
if let Some(val) = holding_table.remove(&f.arg_syms[n]) {
syms.insert(f.arg_syms[n].clone(), val);
}
}
Ok(result)
}
}
}
pub fn from_ast(
id: &String,
doc: &String,
ast: &Seg,
arg_list: Option<Vec<String>>,
) -> Symbol {
let args: Args;
let value: ValueType;
if let Some(ref arg_syms) = arg_list {
value = ValueType::FuncForm(UserFn{
ast: Box::new(ast.clone()),
arg_syms: arg_syms.clone(),
});
args = Args::Lazy(arg_syms.len() as u128);
} else {
args = Args::None;
value = ValueType::VarForm(ast.car.clone());
}
Symbol {
name: id.clone(),
docs: doc.clone(),
conditional_branches: false,
args, value,
..Default::default()
}
}
}
impl Default for Symbol {
fn default() -> Symbol {
Symbol {
value: ValueType::Internal(
Rc::new(
|_: &Seg, _: &mut SymTable| -> Result<Ctr, Traceback> {
unimplemented!()
}
)
),
name: String::new(),
docs: String::new(),
args: Args::None,
conditional_branches: false,
__generation: 0,
optimizable: false
}
}
}
pub fn call_lambda(
lam: &UserFn,
args_ctr: &Box<Ctr>,
syms: &mut SymTable,
) -> Result<Box<Ctr>, Traceback> {
let temp_sym = Symbol {
name: String::from("<lambda>"),
conditional_branches: false,
docs: String::from("user defined lambda"),
args: Args::Lazy(lam.arg_syms.len() as u128),
value: ValueType::FuncForm(lam.clone()),
..Default::default()
};
let args: &Seg;
let outer_scope_maybe_args: Seg;
if let Ctr::Seg(ref args_head) = **args_ctr {
args = args_head;
} else {
outer_scope_maybe_args = Seg::from_mono(
Box::new(*args_ctr.clone()));
args = &outer_scope_maybe_args;
}
temp_sym.call(args, syms)
}