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:
parent
aa56570d7d
commit
6d2925984f
44 changed files with 967 additions and 779 deletions
99
core/src/error.rs
Normal file
99
core/src/error.rs
Normal 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
110
core/src/eval.rs
Normal 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
246
core/src/lex.rs
Normal 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
42
core/src/lib.rs
Normal 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
425
core/src/segment.rs
Normal 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
41
core/src/stl.rs
Normal 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
297
core/src/stl/append.rs
Normal 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
221
core/src/stl/boolean.rs
Normal 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
489
core/src/stl/control.rs
Normal 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
541
core/src/stl/decl.rs
Normal 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
172
core/src/stl/file.rs
Normal 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
707
core/src/stl/math.rs
Normal 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
358
core/src/stl/strings.rs
Normal 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
582
core/src/sym.rs
Normal 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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue