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
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()
|
||||
}
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue