Big project dir refactor

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

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

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

@ -0,0 +1,297 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, Args, ValueType};
use crate::error::{Traceback, start_trace};
use std::rc::Rc;
const CONS_DOCSTRING: &str = "traverses any number of arguments collecting them into a list.
If the first argument is a list, all other arguments are added sequentially to the end of the list contained in the first argument.";
fn cons_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
let mut temp = s.clone();
if let Ctr::Seg(ref list) = *ast.cdr {
list.circuit(&mut |c: &Ctr| -> bool {
temp.append(Box::new(c.clone()));
true
});
} else {
temp.append(Box::new(*ast.cdr.clone()));
}
Ok(Ctr::Seg(temp))
} else {
let mut temp = Seg::new();
let mut car_iter = &ast.car;
let mut cdr_iter = &ast.cdr;
loop {
temp.append(car_iter.clone());
if let Ctr::Seg(ref s) = **cdr_iter {
car_iter = &s.car;
cdr_iter = &s.cdr;
} else {
break;
}
}
Ok(Ctr::Seg(temp))
}
}
const LEN_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns the length of said list.
Interpreter will panic if the length of the list is greater than the max value of a signed 128 bit integer";
fn len_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if let Ctr::None = *s.car {
Ok(Ctr::Integer(0))
} else {
Ok(Ctr::Integer(s.len() as i128))
}
} else {
Err(start_trace(("len", "input is not a list").into()))
}
}
const CAR_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a copy of the first value in that list.
If the list is empty, returns an err to avoid passing back a null value.";
fn car_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if let Ctr::None = *s.car {
Err(start_trace(("len", "input is empty").into()))
} else {
Ok(*s.car.clone())
}
} else {
Err(start_trace(("len", "input is not a list").into()))
}
}
const CDR_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a copy of the last value in that list.
If the list is empty, returns an err to avoid passing back a null value.";
fn cdr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
let mut ret = &s.car;
let mut iter = &s.cdr;
while let Ctr::Seg(ref next) = **iter {
ret = &next.car;
iter = &next.cdr;
}
if let Ctr::None = **ret {
Err(start_trace(("cdr", "input is empty").into()))
} else {
Ok(*ret.clone())
}
} else {
Err(start_trace(("cdr", "input is not a list").into()))
}
}
const POP_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a list containing the first element, and the rest of the elements.
Example: (pop (1 2 3)) -> (1 (2 3)).
The head can then be accessed by car and the rest can be accessed by cdr.";
fn pop_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if s.is_empty() {
return Err(start_trace(("pop", "input is empty").into()));
}
let mut ret = Seg::new();
ret.append(s.car.clone());
if let Ctr::Seg(ref s2) = *s.cdr {
ret.append(Box::new(Ctr::Seg(s2.clone())));
} else {
ret.append(Box::new(Ctr::Seg(Seg::new())));
}
Ok(Ctr::Seg(ret))
} else {
Err(start_trace(("pop", "input is not a list").into()))
}
}
const DEQUEUE_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns a list containing the last element, and the rest of the elements.
Example: (dq (1 2 3)) -> (3 (1 2)).
The last element can then be accessed by car and the rest can be accessed by cdr.";
fn dequeue_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
if s.is_empty() {
return Err(start_trace(("dequeue", "expected an input").into()));
}
let mut rest = Seg::new();
let mut iter = s;
while *iter.cdr != Ctr::None {
rest.append(Box::new(*iter.car.clone()));
if let Ctr::Seg(ref next) = *iter.cdr {
iter = next;
} else {
break;
}
}
let mut ret = Seg::new();
match *iter.car {
Ctr::Seg(ref s) => {
ret.append(s.car.clone());
}
_ => ret.append(iter.car.clone()),
}
ret.append(Box::new(Ctr::Seg(rest)));
Ok(Ctr::Seg(ret))
} else {
Err(start_trace(("dequeue", "input is not a list").into()))
}
}
const REVERSE_DOCSTRING: &str = "Takes a single argument, expected to be a list.
Returns the same list, but in reverse order.";
fn reverse_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(ref s) = *ast.car {
let mut ret = Seg::new();
s.circuit_reverse(&mut |arg: &Ctr| -> bool {
ret.append(Box::new(arg.clone()));
true
});
Ok(Ctr::Seg(ret))
} else {
Err(start_trace(("reverse", "input is not a list").into()))
}
}
const ISLIST_DOCSTRING: &str = "takes a single argument, returns true if argument is a list";
fn islist_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Seg(_) = *ast.car {
Ok(Ctr::Bool(true))
} else {
Ok(Ctr::Bool(false))
}
}
pub fn add_list_lib(syms: &mut SymTable) {
syms.insert(
"cons".to_string(),
Symbol {
name: String::from("cons"),
args: Args::Infinite,
conditional_branches: false,
docs: CONS_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(cons_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"len".to_string(),
Symbol {
name: String::from("len"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: LEN_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(len_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"car".to_string(),
Symbol {
name: String::from("car"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: CAR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(car_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"cdr".to_string(),
Symbol {
name: String::from("cdr"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: CDR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(cdr_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"pop".to_string(),
Symbol {
name: String::from("pop"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: POP_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(pop_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"dq".to_string(),
Symbol {
name: String::from("dequeue"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: DEQUEUE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(dequeue_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"reverse".to_string(),
Symbol {
name: String::from("reverse"),
args: Args::Strict(vec![Type::Seg]),
conditional_branches: false,
docs: REVERSE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(reverse_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"list?".to_string(),
Symbol {
name: String::from("list?"),
args: Args::Lazy(1),
conditional_branches: false,
docs: ISLIST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(islist_callback)),
optimizable: true,
..Default::default()
},
);
}

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

@ -0,0 +1,221 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::segment::{Ctr, Seg, Type};
use crate::error::{Traceback, start_trace};
use crate::sym::{SymTable, ValueType, Args, Symbol};
use std::rc::Rc;
const AND_DOCSTRING: &str =
"traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 AND arg2, and then calculates prev_result AND next_arg.
returns final result.";
fn and_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut type_error = false;
let mut cursor = 0;
let result = ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Bool(b) = *arg {
cursor += 1;
b
} else {
type_error = true;
false
}
});
if type_error {
Err(start_trace(("and", format!("input {} not a boolean", cursor)).into()))
} else {
Ok(Ctr::Bool(result))
}
}
const OR_DOCSTRING: &str =
"traverses a list of N arguments, all of which are expected to be boolean.
starts with arg1 OR arg2, and then calculates prev_result OR next_arg.
returns final result.";
fn or_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut result = false;
let mut cursor = 0;
let correct_types = ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Bool(b) = *arg {
cursor += 1;
result = result || b;
true
} else {
false
}
});
if !correct_types {
Err(start_trace(("or", format!("input {} not a boolean", cursor)).into()))
} else {
Ok(Ctr::Bool(result))
}
}
const NOT_DOCSTRING: &str = "takes a single argument (expects a boolean).
returns false if arg is true or true if arg is false.";
fn not_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = *ast.car {
Ok(Ctr::Bool(!b))
} else {
Err(start_trace(("not", "input is not a bool").into()))
}
}
const ISEQ_DOCSTRING: &str = "traverses a list of N arguments.
returns true if all arguments hold the same value.
NOTE: 1 and 1.0 are the same, but '1' 'one' or one (symbol) aren't";
fn iseq_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let head_ctr_ref = &*ast.car;
Ok(Ctr::Bool(
ast.circuit(&mut |arg: &Ctr| -> bool { arg == head_ctr_ref }),
))
}
const TOGGLE_DOCSTRING: &str = "switches a boolean symbol between true or false.
Takes a single argument (a symbol). Looks it up in the variable table.
Either sets the symbol to true if it is currently false, or vice versa.";
fn toggle_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let var_name: String;
if let Ctr::Symbol(ref s) = *ast.car {
var_name = s.clone();
} else {
return Err(start_trace(("toggle", "input must be a symbol").into()));
}
let mut sym = syms
.remove(&var_name)
.expect(&format!("symbol {var_name} is not defined"));
if let ValueType::VarForm(ref var) = sym.value {
if let Ctr::Bool(ref b) = **var {
sym.value = ValueType::VarForm(Box::new(Ctr::Bool(!b)));
} else {
syms.insert(var_name, sym);
return Err(start_trace(("toggle", "can only toggle a boolean").into()));
}
} else {
syms.insert(var_name, sym);
return Err(start_trace(("toggle", "cannot toggle a function").into()));
}
syms.insert(var_name, sym);
Ok(Ctr::None)
}
const BOOLCAST_DOCSTRING: &str = "takes one argument of any type.
attempts to cast argument to a bool.
Strings will cast to a bool if they are 'true' or 'false'.
Integers and Floats will cast to true if they are 0 and false otherwise.";
fn boolcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car {
Ctr::Bool(_) => Ok(*ast.car.clone()),
Ctr::String(s) => {
if s == "true" {
Ok(Ctr::Bool(true))
} else if s == "false" {
Ok(Ctr::Bool(false))
} else {
Err(start_trace(("bool", "string cannot be parsed as a bool").into()))
}
},
Ctr::Integer(i) => Ok(Ctr::Bool(*i == 0)),
Ctr::Float(f) => Ok(Ctr::Bool(*f == 0.0)),
_ => Err(start_trace(("bool", format!("cannot convert a {} to a boolean",
ast.car.to_type())).into())),
}
}
pub fn add_bool_lib(syms: &mut SymTable) {
syms.insert(
"and".to_string(),
Symbol {
name: String::from("and"),
args: Args::Infinite,
conditional_branches: false,
docs: AND_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(and_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"bool".to_string(),
Symbol {
name: String::from("bool"),
args: Args::Infinite,
conditional_branches: false,
docs: BOOLCAST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(boolcast_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"or".to_string(),
Symbol {
name: String::from("or"),
args: Args::Infinite,
conditional_branches: false,
docs: OR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(or_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"not".to_string(),
Symbol {
name: String::from("not"),
args: Args::Strict(vec![Type::Bool]),
conditional_branches: false,
docs: NOT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(not_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"eq?".to_string(),
Symbol {
name: String::from("eq?"),
args: Args::Infinite,
conditional_branches: false,
docs: ISEQ_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(iseq_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"toggle".to_string(),
Symbol {
name: String::from("toggle"),
args: Args::Lazy(1),
conditional_branches: true,
docs: TOGGLE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(toggle_callback)),
..Default::default()
},
);
}

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

@ -0,0 +1,489 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, ValueType, Args};
use std::rc::Rc;
use std::process;
const IF_DOCSTRING: &str =
"accepts three bodies, a condition, an unevaluated consequence, and an alternative consequence.
If the condition is evaluated to true, the first consequence is evaluated.
If the condition is evaluated to false, the second consequence is evaluated.
Otherwise, an error is thrown.
example: (if my-state-switch
(do-my-thing)
(else-an-other-thing))";
fn if_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let cond: bool;
match *ast.car {
Ctr::Seg(ref cond_form) => {
let intermediate = eval(cond_form, syms);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval;
} else {
return Err(start_trace(("if", "first arg must be a bool").into()));
}
}
Ctr::Symbol(ref cond_name) => {
let intermediate = syms.call_symbol(cond_name, &Seg::new(), false);
if let Err(e) = intermediate {
return Err(e.with_trace(("if", "error evaluating conditional").into()))
}
if let Ctr::Bool(cond_from_eval) = *intermediate? {
cond = cond_from_eval;
} else {
return Err(start_trace(("if", "first arg must be a bool").into()));
}
}
Ctr::Bool(cond_from_car) => cond = cond_from_car,
_ => return Err(start_trace(("if", "first arg must be a bool").into())),
}
let then_form: &Seg;
if let Ctr::Seg(ref s) = *ast.cdr {
then_form = s;
} else {
return Err(start_trace(("if", "not enough args").into()));
}
if cond {
// then
match *then_form.car {
Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating then form").into())),
Ok(val) => Ok(*val)
},
_ => {
let eval_tree = &Seg::from_mono(then_form.car.clone());
let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating then form").into()))
}
let eval_res = *eval_intermediate?;
if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone())
} else {
Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
}
}
}
} else {
// else
if let Ctr::Seg(ref else_form) = *then_form.cdr {
match *else_form.car {
Ctr::Seg(ref first_arg) => match eval(first_arg, syms) {
Err(e) => Err(e.with_trace(("if", "error evaluating else form").into())),
Ok(val) => Ok(*val)
},
_ => {
let eval_tree = &Seg::from_mono(else_form.car.clone());
let eval_intermediate = eval(eval_tree, syms);
if let Err(e) = eval_intermediate {
return Err(e.with_trace(("if", "error evaluating else form").into()))
}
let eval_res = *eval_intermediate?;
if let Ctr::Seg(ref s) = eval_res {
Ok(*s.car.clone())
} else {
Err(start_trace(("if", "impossible condition: list evaluates to non list")
.into()))
}
}
}
} else {
Err(start_trace(("if", "impossible condition: args not in standard form").into()))
}
}
}
const LET_DOCSTRING: &str = "creates a stack of local variables for a sequence of operations.
returns the result of the final operation.
example: (let ((step1 'hello')
(step2 (concat step1 '-'))
(step3 (concat step2 'world')))
(echo step3)
(some-func some-args))
In this example step1, step2, and step3 are created sequentially.
Then, the echo form is evaluated, printing 'hello-world'.
Finally, the some-func form is evaluated.
Since the call to some-func is the final form, its value is returned.";
fn let_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut localsyms = syms.clone();
let mut locals = vec![];
let locals_form: &Seg;
let eval_forms: &Seg;
if let Ctr::Seg(ref locals_form_list) = *ast.car {
locals_form = locals_form_list;
} else {
return Err(start_trace(("let", "first form does not contain list of local declarations")
.into()));
}
if let Ctr::Seg(ref eval_forms_head) = *ast.cdr {
eval_forms = eval_forms_head;
} else {
return Err(start_trace(("let", "missing one or more forms to evaluate").into()));
}
let mut err_trace: Traceback = Traceback::new();
// process locals forms
if !locals_form.circuit(&mut |var_decl: &Ctr| -> bool {
if let Ctr::Seg(ref var_form) = *var_decl {
if let Ctr::Symbol(ref name) = *var_form.car {
if let Ctr::Seg(ref var_val_form) = *var_form.cdr {
let var_val_res: Result<Box<Ctr>, Traceback>;
if let Ctr::Seg(ref val_form) = *var_val_form.car {
var_val_res = eval(val_form, &mut localsyms);
} else {
let var_tree = Seg::from_mono(Box::new(*var_val_form.car.clone()));
let intermediate = eval(&var_tree, &mut localsyms);
if intermediate.is_err() {
var_val_res = intermediate;
} else if let Ctr::Seg(ref intermediate_result) = *intermediate.unwrap() {
var_val_res = Ok(intermediate_result.car.clone())
} else {
panic!()
}
}
if let Err(e) = var_val_res {
err_trace = e
.with_trace(
("let", format!("failed to evaluate definition of {}", name))
.into());
return false;
}
localsyms.insert(
name.clone(),
Symbol::from_ast(
name, &"variable used in let form".to_string(),
&Seg::from_mono(Box::new(*var_val_res.unwrap())),
None),
);
locals.push(name.clone());
}
} else if let Ctr::None = *var_form.car {
// nothing to declare
return true;
} else {
err_trace = start_trace(
("let", format!("improper declaration of {}: not a list", var_form))
.into());
return false;
}
} else {
err_trace = start_trace(
("let", format!("improper declaration form: {}", var_decl))
.into());
return false;
}
true
}) {
assert!(err_trace.depth() > 0);
return Err(err_trace);
}
assert!(err_trace.depth() < 1);
let mut result: Box<Ctr> = Box::new(Ctr::None);
if !eval_forms.circuit(&mut |eval_form: &Ctr| -> bool {
let res: Result<Box<Ctr>, Traceback>;
if let Ctr::Seg(ref eval_tree) = eval_form {
res = eval(eval_tree, &mut localsyms);
} else {
let eval_tree = Seg::from_mono(Box::new(eval_form.clone()));
let intermediate = eval(&eval_tree, &mut localsyms);
if intermediate.is_err() {
res = intermediate;
} else if let Ctr::Seg(ref intermediate_result) = *intermediate.unwrap() {
res = Ok(intermediate_result.car.clone())
} else {
panic!()
}
}
if let Err(e) = res {
err_trace = e.with_trace(
("let", "evaluation failure")
.into());
return false;
}
result = res.unwrap();
true
}) {
assert!(err_trace.depth() > 0);
return Err(err_trace);
}
assert!(err_trace.depth() < 1);
for i in locals {
localsyms.remove(&i);
}
syms.update(&mut localsyms);
Ok((*result).clone())
}
const WHILE_DOCSTRING: &str = "traverses a list of N un-evaluated forms.
the first form is expected to evaluate to a boolean. if it evaluates to false, while will stop and return. Otherwise, while will evaluate each form in a loop.
example: (while (check-my-state)
(do-thing-1 args)
(do-thing-2 args)
(edit-state my-state))";
fn while_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let eval_cond: &Seg;
let outer_maybe: Seg;
let eval_bodies_head: &Seg;
let mut unwrap = false;
let mut result: Result<Box<Ctr>, Traceback> = Ok(Box::new(Ctr::None));
if let Ctr::Seg(ref cond) = *ast.car {
eval_cond = cond;
} else {
outer_maybe = Seg::from_mono(ast.car.clone());
eval_cond = &outer_maybe;
unwrap = true;
}
if let Ctr::Seg(ref eval) = *ast.cdr {
eval_bodies_head = eval;
} else {
return Err(start_trace(("while", "expected one or more forms to evaluate").into()));
}
loop {
let cond_res = *eval(eval_cond, syms)?;
if unwrap {
if let Ctr::Seg(ref cond_res_inner) = cond_res {
if let Ctr::Bool(ref b) = *cond_res_inner.car {
if !b {
break;
}
}
} else {
panic!()
}
} else if let Ctr::Bool(b) = cond_res {
if !b {
break;
}
} else {
return Err(start_trace(("while", "expected first form to evaluate to a boolean").into()));
}
if !eval_bodies_head.circuit(&mut |body: &Ctr| -> bool {
let outer_scope_seg: Seg;
let eval_arg: &Seg;
if let Ctr::Seg(ref eval_body) = *body {
eval_arg = eval_body;
} else {
outer_scope_seg = Seg::from_mono(Box::new(body.clone()));
eval_arg = &outer_scope_seg;
}
result = eval(eval_arg, syms);
result.is_ok()
}) {
return Err(result.err().unwrap().with_trace(("while", "evaluation failure").into()));
}
}
Ok(*(result.unwrap()))
}
const CIRCUIT_DOCSTRING: &str = "traverses a list of N un-evaluated forms.
evaluates each one until it stops. Circuit will stop when a form errors during evaluation.
Circuit will also stop when a form does not evaluate to a boolean, or evaluates to false.
example: (circuit (eq? (do-operation) myresult)
(and state1 state2 (boolean-operation3))
false
(do-another-operation))
in this example, do-another-operation will not be called";
fn circuit_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut cursor = 0;
let mut err_trace: Traceback = Traceback::new();
let result = ast.circuit(&mut |form: &Ctr| -> bool {
cursor += 1;
let operand: &Seg;
let mut expand_eval_res = false;
let outer_scope_seg: Seg;
if let Ctr::Seg(ref s) = form {
operand = s;
} else {
outer_scope_seg = Seg::from_mono(Box::new(form.clone()));
operand = &outer_scope_seg;
expand_eval_res = true;
}
let eval_result = eval(operand, syms);
match eval_result {
Err(s) => err_trace = s.with_trace(
("circuit", format!("failed at form {cursor}"))
.into()),
Ok(s) => match *s {
Ctr::Bool(b) => return b,
Ctr::Seg(s) if expand_eval_res => {
if let Ctr::Bool(b) = *s.car {
return b;
} else if let Ctr::Integer(i) = *s.car {
return i==0;
} else {
err_trace = err_trace.clone().with_trace(
("circuit", "impossible condition")
.into());
}
},
Ctr::Integer(i) => return i == 0,
_ => err_trace = err_trace.clone().with_trace(
("circuit", format!("form {cursor} did not evaluate to a boolean"))
.into()),
},
}
false
});
if !result && err_trace.depth() > 0 {
Err(err_trace)
} else {
Ok(Ctr::Bool(result))
}
}
const ASSERT_DOCSTRING: &str = "Takes one input: a boolean (or form that evaluates to a boolean).
If input is false, a traceback is started and code throws an error.
Otherwise, if input is true, returns None.";
fn assert_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Bool(b) = *ast.car {
if b {
Ok(Ctr::None)
} else {
Err(start_trace(("assert", "assertion failed").into()))
}
} else {
Err(start_trace(("assert", "impossible arg").into()))
}
}
const EXIT_DOCSTRING: &str = "Takes on input: an integer used as an exit code.
Entire REPL process quits with exit code.";
fn exit_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Integer(i) = *ast.car {
if i > 2 ^ 32 {
panic!("argument to exit too large!")
} else {
process::exit(i as i32);
}
} else {
panic!("impossible argument to exit")
}
}
pub fn add_control_lib(syms: &mut SymTable) {
syms.insert(
"exit".to_string(),
Symbol {
name: String::from("exit"),
args: Args::Strict(vec![Type::Integer]),
conditional_branches: false,
docs: EXIT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(exit_callback)),
..Default::default()
},
);
syms.insert(
"assert".to_string(),
Symbol {
name: String::from("assert"),
args: Args::Strict(vec![Type::Bool]),
conditional_branches: false,
docs: ASSERT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(assert_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"if".to_string(),
Symbol {
name: String::from("if"),
args: Args::Lazy(3),
conditional_branches: true,
docs: IF_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(if_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"let".to_string(),
Symbol {
name: String::from("let"),
args: Args::Infinite,
conditional_branches: true,
docs: LET_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(let_callback)),
..Default::default()
},
);
syms.insert(
"while".to_string(),
Symbol {
name: String::from("while"),
args: Args::Infinite,
conditional_branches: true,
docs: WHILE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(while_callback)),
..Default::default()
},
);
syms.insert(
"circuit".to_string(),
Symbol {
name: String::from("circuit"),
args: Args::Infinite,
conditional_branches: true,
docs: CIRCUIT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(circuit_callback)),
optimizable: true,
..Default::default()
},
);
}

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

@ -0,0 +1,541 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Hahn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, UserFn, ValueType, Args};
use std::rc::Rc;
const QUOTE_DOCSTRING: &str = "takes a single unevaluated tree and returns it as it is: unevaluated.";
fn quote_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() > 1 {
Err(start_trace(("quote", "do not quote more than one thing at a time").into()))
} else {
Ok(*ast.car.clone())
}
}
const EVAL_DOCSTRING: &str = "takes an unevaluated argument and evaluates it.
Specifically, does one pass of the tree simplification algorithm.
If you have a variable referencing another variable you will get the
referenced variable.";
fn eval_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() > 1 {
Err(start_trace(
("eval", "do not eval more than one thing at a time")
.into()))
} else {
match *ast.car {
Ctr::Seg(ref s) => {
match eval(s, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => if let Ctr::Seg(ref inner) = *s {
match eval(inner, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => Ok(*s),
}
} else {
Ok(*s)
},
}
}
Ctr::Symbol(ref sym) => {
let intermediate = syms.call_symbol(sym, &Seg::new(), true);
if let Err(e) = intermediate {
return Err(e.with_trace(
("eval", "evaluation failure")
.into()))
}
let res = *intermediate?;
if let Ctr::Seg(ref s) = res {
match eval(s, syms) {
Err(e) => Err(e.with_trace(
("eval", "evaluation failure")
.into())),
Ok(s) => Ok(*s),
}
} else {
Ok(res)
}
},
_ => Ok(*ast.car.clone())
}
}
}
const HELP_DOCSTRING: &str = "prints help text for a given symbol. Expects only one argument.";
fn help_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 1 {
return Err(start_trace(("help", "expected one input").into()));
}
if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(sym) = syms.get(symbol) {
let args_str: String;
if let ValueType::VarForm(_) = sym.value {
args_str = "(its a variable)".to_string();
} else {
args_str = sym.args.to_string();
}
println!(
"NAME: {0}\n
ARGS: {1}\n
DOCUMENTATION:\n
{2}\n
CURRENT VALUE AND/OR BODY:
{3}",
sym.name, args_str, sym.docs, sym.value
);
} else {
return Err(start_trace(("help", format!("{symbol} is undefined")).into()));
}
} else {
return Err(start_trace(("help", "expected input to be a symbol").into()));
}
Ok(Ctr::None)
}
const ISSET_DOCSTRING: &str = "accepts a single argument: a symbol.
returns true or false according to whether or not the symbol is found in the symbol table.";
fn isset_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 1 {
Err(start_trace(("set?", "expcted one input").into()))
} else if let Ctr::Symbol(ref symbol) = *ast.car {
Ok(Ctr::Bool(syms.get(symbol).is_some()))
} else {
Err(start_trace(("set?", "expected argument to be a input").into()))
}
}
const ENV_DOCSTRING: &str = "takes no arguments
prints out all available symbols and their associated values";
fn env_callback(_ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut functions = vec![];
let mut variables = vec![];
for (name, val) in syms.iter() {
if let ValueType::VarForm(l) = &val.value {
let token: String = match l.to_type() {
Type::Lambda => format!("{}: <lambda>", name),
Type::Seg => format!("{}: <form>", name),
_ => format!("{}: {}", name, val.value),
};
variables.push(token);
} else {
functions.push(name.clone());
}
}
println!("VARIABLES:");
for var in variables {
println!("{}", var)
}
println!("\nFUNCTIONS:");
for func in functions {
println!("{}", func);
}
Ok(Ctr::None)
}
const LAMBDA_DOCSTRING: &str = "Takes two arguments of any type.
No args are evaluated when lambda is called.
Lambda makes sure the first argument is a list of symbols (or 'arguments') to the lambda function.
The next arg is stored in a tree to evaluate on demand.
Example: (lambda (x y) (add x y))
This can then be evaluated like so:
((lambda (x y) (add x y)) 1 2)
which is functionally equivalent to:
(add 1 2)";
fn lambda_callback(
ast: &Seg,
_syms: &mut SymTable
) -> Result<Ctr, Traceback> {
let mut args = vec![];
if let Ctr::Seg(ref arg_head) = *ast.car {
if !arg_head.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Symbol(ref s) = *arg {
args.push(s.clone());
true
} else if let Ctr::None = *arg {
// no args case
true
} else {
false
}
}) {
Err(start_trace(("lambda", "lambda inputs should all be symbols").into()))
} else if let Ctr::Seg(ref eval_head) = *ast.cdr {
if let Ctr::Seg(_) = *eval_head.car {
Ok(Ctr::Lambda(UserFn{
ast: Box::new(eval_head.clone()),
arg_syms: args,
}))
} else {
Err(start_trace(("lambda", "expected list of forms for lambda body").into()))
}
} else {
Err(start_trace(("lambda", "not enough args").into()))
}
} else {
Err(start_trace(("lambda", "expected list of lambda inputs").into()))
}
}
const GETDOC_DOCSTRING: &str = "accepts an unevaluated symbol, returns the doc string.
Returns an error if symbol is undefined.
Note: make sure to quote the input like this:
(get-doc (quote symbol-name))";
fn getdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(sym) = syms.get(symbol) {
Ok(Ctr::String(sym.docs.clone()))
} else {
Err(start_trace(("get-doc", "input is undefined").into()))
}
} else {
Err(start_trace(("get-doc", "expected input to be a symbol").into()))
}
}
const SETDOC_DOCSTRING: &str = "accepts a symbol and a doc string.
Returns an error if symbol is undefined, otherwise sets the symbols docstring to the argument.
Note: make sure to quote the input like this:
(set-doc (quote symbol-name) my-new-docs)";
fn setdoc_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
if ast.len() != 2 {
Err(start_trace(
("set-doc", "expected two inputs")
.into()))
} else if let Ctr::Symbol(ref symbol) = *ast.car {
if let Some(mut sym) = syms.remove(symbol) {
if let Ctr::Seg(ref doc_node) = *ast.cdr {
if let Ctr::String(ref doc) = *doc_node.car {
sym.docs = doc.clone();
syms.insert(sym.name.clone(), sym);
Ok(Ctr::None)
} else {
syms.insert(sym.name.clone(), sym);
Err(start_trace(
("set-doc", "expected second input to be a string")
.into()))
}
} else {
Err(start_trace(
("set-doc", "missing second input somehow")
.into()))
}
} else {
Err(start_trace(
("set-doc", format!("{symbol} is undefined"))
.into()))
}
} else {
Err(start_trace(
("set-doc", "first input must be a symbol")
.into()))
}
}
pub const STORE_DOCSTRING: &str = "allows user to define functions and variables.
A call may take one of three forms:
1. variable declaration:
Takes a name, doc string, and a value.
(def myvar 'my special variable' 'my var value')
2. function declaration:
Takes a name, doc string, list of arguments, and one or more bodies to evaluate.
Result of evaluating the final body is returned.
(def myfunc 'does a thing' (myarg1 myarg2) (dothing myarg1 myarg2) (add myarg1 myarg2))
3. symbol un-definition:
Takes just a name. Removes variable from table.
(def useless-var)
Additionally, passing a tree as a name will trigger def to evaluate the tree and try to derive
a value from it. If it does not return a String or a Symbol this will result in a failure.
The name of the symbol operated on is returned (as a string).";
pub fn store_callback(ast: &Seg, syms: &mut SymTable) -> Result<Ctr, Traceback> {
let is_var = ast.len() == 3;
let name: String;
let docs: String;
match *ast.car {
Ctr::String(ref s) => name = s.clone(),
Ctr::Symbol(ref s) => name = s.clone(),
Ctr::Seg(ref s) => match eval(s, syms) {
Err(e) => return Err(e.with_trace(("def", "failed to evaluate symbol name").into())),
Ok(s) => match *s {
Ctr::String(ref s) => name = s.clone(),
Ctr::Symbol(ref s) => name = s.clone(),
_ => {
return Err(start_trace(
("def", "expected symbol name input to evaluate to a symbol or a string")
.into()));
},
}
},
_ => return Err(start_trace(
("def", "expected a string or a symbol as input for symbol name")
.into()))
}
// remove var case
if ast.len() == 1 {
syms.remove(&name);
return Ok(Ctr::String(name))
} else if ast.len() < 3 || ast.len() > 4 {
return Err(start_trace(("def", "expected 3 or 4 inputs").into()))
}
let mut iter: &Seg;
if let Ctr::Seg(ref s) = *ast.cdr {
iter = s;
} else {
return Err(start_trace(("def", "not enough inputs").into()))
}
match *iter.car {
Ctr::String(ref s) => docs = s.clone(),
Ctr::Symbol(ref s) => match syms.call_symbol(s, &Seg::new(), true) {
Ok(d) => if let Ctr::String(doc) = *d {
docs = doc;
} else {
return Err(start_trace(("def", "expected docs input to evaluate to a string").into()))
},
Err(e) => return Err(e.with_trace(
("def", "couldnt evaluate docs form")
.into()))
},
_ => return Err(start_trace(
("def", "expected docs input to at least evaluate to a string if not be one")
.into()))
}
if let Ctr::Seg(ref s) = *iter.cdr {
iter = s;
} else {
return Err(start_trace(("def", "not enough inputs").into()))
}
let mut outer_scope_val: Seg = Seg::new();
let noseg = Seg::new(); // similarly, rust shouldnt need this either
let mut args = &noseg;
let mut var_val_form: &Seg = &outer_scope_val;
let mut expand = false;
match *iter.car {
Ctr::Seg(ref s) if !is_var => args = s,
Ctr::Seg(ref s) if is_var => var_val_form = s,
_ if is_var => {
expand = true;
outer_scope_val = Seg::from_mono(Box::new(*iter.car.clone()));
var_val_form = &outer_scope_val;
},
_ if !is_var => return Err(start_trace(("def", "expected a list of inputs").into())),
_ => unimplemented!(), // rustc is haunted and cursed
}
if is_var {
let var_eval_result = eval(var_val_form, syms);
if let Err(e) = var_eval_result {
return Err(e.with_trace(
("def", format!("couldnt evaluate {var_val_form}"))
.into()))
}
let var_eval_final = *var_eval_result?;
let var_val: Ctr = match var_eval_final {
Ctr::Seg(ref s) if expand => *s.car.clone(),
Ctr::Seg(ref s) if !expand => Ctr::Seg(s.clone()),
_ => var_eval_final,
};
let outer_seg = Seg::from_mono(Box::new(var_val.clone()));
syms.insert(
name.clone(),
Symbol::from_ast(&name, &docs, &outer_seg, None),
);
return Ok(Ctr::String(name))
}
let mut arg_list = vec![];
if !args.circuit(&mut |c: &Ctr| -> bool {
if let Ctr::Symbol(s) = c {
arg_list.push(s.clone());
true
} else if let Ctr::None = c {
// no args case
true
} else {
false
}
}) {
return Err(start_trace(
("def", "all inputs to function must be of type symbol")
.into()))
}
if let Ctr::Seg(ref eval_bodies) = *iter.cdr {
syms.insert(
name.clone(),
Symbol::from_ast(
&name, &docs,
eval_bodies,
Some(arg_list),
),
);
Ok(Ctr::String(name))
} else {
Err(start_trace(
("def", "expected one or more forms to evaluate in function body")
.into()))
}
}
pub fn add_decl_lib_static(syms: &mut SymTable) {
syms.insert(
"help".to_string(),
Symbol {
name: String::from("help"),
args: Args::Strict(vec![Type::Symbol]),
conditional_branches: true,
docs: HELP_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(help_callback)),
..Default::default()
},
);
syms.insert(
"set?".to_string(),
Symbol {
name: String::from("set?"),
args: Args::Strict(vec![Type::Symbol]),
conditional_branches: true,
docs: ISSET_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(isset_callback)),
..Default::default()
},
);
syms.insert(
"quote".to_string(),
Symbol {
name: String::from("quote"),
args: Args::Lazy(1),
conditional_branches: true,
docs: QUOTE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(quote_callback)),
..Default::default()
},
);
syms.insert(
"q".to_string(),
Symbol {
name: String::from("quote"),
args: Args::Lazy(1),
conditional_branches: true,
docs: QUOTE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(quote_callback)),
..Default::default()
},
);
syms.insert(
"eval".to_string(),
Symbol {
name: String::from("eval"),
args: Args::Lazy(1),
conditional_branches: true,
docs: EVAL_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(eval_callback)),
..Default::default()
},
);
syms.insert(
"lambda".to_string(),
Symbol {
name: String::from("lambda"),
args: Args::Lazy(2),
conditional_branches: true,
docs: LAMBDA_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(lambda_callback)),
..Default::default()
}
);
syms.insert(
"get-doc".to_string(),
Symbol {
name: String::from("get-doc"),
args: Args::Strict(vec![Type::Symbol]),
conditional_branches: false,
docs: GETDOC_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(getdoc_callback)),
..Default::default()
}
);
syms.insert(
"set-doc".to_string(),
Symbol {
name: String::from("get-doc"),
args: Args::Strict(vec![Type::Symbol, Type::String]),
conditional_branches: false,
docs: SETDOC_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(setdoc_callback)),
..Default::default()
}
);
syms.insert(
"env".to_string(),
Symbol {
name: String::from("env"),
args: Args::None,
conditional_branches: false,
docs: ENV_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(env_callback)),
..Default::default()
},
);
syms.insert(
"def".to_string(),
Symbol {
name: String::from("define"),
args: Args::Infinite,
conditional_branches: true,
docs: STORE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(
move |ast: &Seg, syms: &mut SymTable| -> Result<Ctr, Traceback> {
store_callback(ast, syms)
},
)),
..Default::default()
},
);
}

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

@ -0,0 +1,172 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Hahn
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, ValueType, Args};
use crate::error::{Traceback, start_trace};
use std::io::Write;
use std::fs::{File, read_to_string, OpenOptions};
use std::rc::Rc;
use std::path::Path;
const READ_TO_STRING_DOCSTRING: &str = "Takes one input (filename).
If file exists, returns a string containing file contents.
If the file does not exist returns error.";
fn read_to_string_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
let res = read_to_string(filename);
if let Ok(s) = res {
Ok(Ctr::String(s))
} else {
Err(start_trace(
("read-file", res.err().unwrap().to_string())
.into()))
}
} else {
Err(start_trace(("read-file", "impossible arg").into()))
}
}
const WRITE_TO_FILE_DOCSTRING: &str = "Takes two inputs: a filename and a string of content.
Writes contents to the file and returns None.";
fn write_to_file_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
if let Ctr::Seg(ref next) = *ast.cdr {
if let Ctr::String(ref body) = *next.car {
let fres = OpenOptions::new()
.create(true)
.truncate(true)
.write(true)
.open(filename);
if fres.is_err() {
Err(start_trace(
("write-file",
format!("couldn't open file: {}", fres.err().unwrap().to_string()))
.into()))
} else {
if let Err(e) = write!(&mut fres.unwrap(), "{}", body) {
Err(start_trace(
("write-file", format!("failed to write to file: {}", e))
.into()))
} else {
Ok(Ctr::None)
}
}
} else {
Err(start_trace(("write-file", "impossible arg").into()))
}
} else {
Err(start_trace(("write-file", "not enough args").into()))
}
} else {
Err(start_trace(("write-file", "impossible arg").into()))
}
}
const APPEND_TO_FILE_DOCSTRING: &str = "Takes two inputs: a filename and a string of content.
Appends content to the end of the file and returns None";
fn append_to_file_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
if let Ctr::Seg(ref next) = *ast.cdr {
if let Ctr::String(ref body) = *next.car {
let fres = File::options().append(true).open(filename);
if fres.is_err() {
Err(start_trace(
("append-file",
format!("couldn't open file: {}", fres.err().unwrap().to_string()))
.into()))
} else {
if let Err(e) = write!(&mut fres.unwrap(), "{}", body) {
Err(start_trace(
("append-file", format!("failed to write to file: {}", e))
.into()))
} else {
Ok(Ctr::None)
}
}
} else {
Err(start_trace(("append-file", "impossible arg").into()))
}
} else {
Err(start_trace(("append-file", "not enough args").into()))
}
} else {
Err(start_trace(("append-file", "impossible arg").into()))
}
}
const IS_FILE_EXISTS_DOCSTRING: &str = "Takes one input: a filename.
Returns true or false depending on if the file exists.";
fn is_file_exists_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref filename) = *ast.car {
Ok(Ctr::Bool(Path::new(&filename).exists()))
} else {
Err(Traceback::new().with_trace(("exists?", "impossible arg").into()))
}
}
pub fn add_file_lib(syms: &mut SymTable) {
syms.insert(
"read-file".to_string(),
Symbol {
name: String::from("read-file"),
args: Args::Strict(vec![Type::String]),
conditional_branches: false,
docs: READ_TO_STRING_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(read_to_string_callback)),
..Default::default()
}
);
syms.insert(
"write-file".to_string(),
Symbol {
name: String::from("write-file"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: WRITE_TO_FILE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(write_to_file_callback)),
..Default::default()
}
);
syms.insert(
"append-file".to_string(),
Symbol {
name: String::from("append-file"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: APPEND_TO_FILE_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(append_to_file_callback)),
..Default::default()
}
);
syms.insert(
"exists?".to_string(),
Symbol {
name: String::from("exists?"),
args: Args::Strict(vec![Type::String]),
conditional_branches: false,
docs: IS_FILE_EXISTS_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(is_file_exists_callback)),
..Default::default()
}
);
}

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

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

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

@ -0,0 +1,358 @@
/* Flesh: Flexible Shell
* Copyright (C) 2021 Ava Affine
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
use crate::segment::{Ctr, Seg, Type};
use crate::sym::{SymTable, Symbol, ValueType, Args};
use crate::error::{Traceback, start_trace};
use std::io::Write;
use std::io;
use std::rc::Rc;
const ECHO_DOCSTRING: &str =
"traverses any number of arguments. Prints their evaluated values on a new line for each.";
fn echo_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
ast.circuit(&mut |arg: &Ctr| match arg {
Ctr::String(s) => print!("{}", s) == (),
_ => print!("{}", arg) == (),
});
println!();
Ok(Ctr::None)
}
const CONCAT_DOCSTRING: &str = "Iterates over N args of any type other than SYMBOL.
converts each argument to a string. Combines all strings.
Returns final (combined) string.";
fn concat_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let mut string = String::from("");
if !ast.circuit(&mut |arg: &Ctr| {
match arg {
// should be a thing here
Ctr::Symbol(_) => return false,
Ctr::String(s) => string.push_str(s),
Ctr::Integer(i) => string.push_str(&i.to_string()),
Ctr::Float(f) => string.push_str(&f.to_string()),
Ctr::Bool(b) => string.push_str(&b.to_string()),
Ctr::Seg(c) => string.push_str(&c.to_string()),
Ctr::Lambda(l) => string.push_str(&l.to_string()),
Ctr::None => (),
}
true
}) {
return Err(start_trace(
("concat", "highly suspicious that an input was an unevaluated symbol")
.into()))
}
Ok(Ctr::String(string))
}
const STRLEN_DOCSTRING: &str = "Takes a single arg of any type.
Arg is converted to a string if not already a string.
Returns string length of arg.";
fn strlen_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car {
Ctr::Symbol(s) => Ok(Ctr::Integer(s.len() as i128)),
Ctr::String(s) => Ok(Ctr::Integer(s.len() as i128)),
Ctr::Integer(i) => Ok(Ctr::Integer(i.to_string().len() as i128)),
Ctr::Float(f) => Ok(Ctr::Integer(f.to_string().len() as i128)),
Ctr::Bool(b) => Ok(Ctr::Integer(b.to_string().len() as i128)),
Ctr::Seg(c) => Ok(Ctr::Integer(c.to_string().len() as i128)),
Ctr::Lambda(l) => Ok(Ctr::Integer(l.to_string().len() as i128)),
// highly suspicious case below
Ctr::None => Ok(Ctr::Integer(0)),
}
}
const STRCAST_DOCSTRING: &str = "Takes a single arg of any type.
Arg is converted to a string and returned.";
fn strcast_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
match &*ast.car {
Ctr::Symbol(s) => Ok(Ctr::String(s.clone())),
Ctr::String(_) => Ok(*ast.car.clone()),
Ctr::Integer(i) => Ok(Ctr::String(i.to_string())),
Ctr::Float(f) => Ok(Ctr::String(f.to_string())),
Ctr::Bool(b) => Ok(Ctr::String(b.to_string())),
Ctr::Seg(c) => Ok(Ctr::String(c.to_string())),
Ctr::Lambda(l) => Ok(Ctr::String(l.to_string())),
// highly suspicious case below
Ctr::None => Ok(Ctr::String(String::new())),
}
}
const SUBSTR_DOCSTRING: &str =
"Takes a string and two integers (arg1, arg2 and arg3 respectively).
Returns the substring of arg1 starting at arg2 and ending at arg3.
Returns error if arg2 or arg3 are negative, and if arg3 is larger than the length of arg1.";
fn substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let parent_str: String;
if let Ctr::String(ref s) = *ast.car {
parent_str = s.to_string();
} else {
return Err(start_trace(
("substr", "expected first input to be a string")
.into()))
}
let second_arg_obj: &Ctr;
let third_arg_obj: &Ctr;
let start: usize;
if let Ctr::Seg(ref s) = *ast.cdr {
second_arg_obj = &*s.car;
third_arg_obj = &*s.cdr;
} else {
return Err(start_trace(
("substr", "expected three inputs")
.into()))
}
if let Ctr::Integer(i) = &*second_arg_obj {
if i < &0 {
return Err(start_trace(("substr", "start index cannot be negative").into()))
}
start = i.clone() as usize;
} else {
return Err(start_trace(
("substr", "expected second input to be an integer")
.into()))
}
if start > parent_str.len() {
return Err(start_trace(("substr", "start index larger than source string").into()))
}
let end: usize;
let third_arg_inner: &Ctr;
if let Ctr::Seg(ref s) = *third_arg_obj {
third_arg_inner = &*s.car;
} else {
return Err(start_trace(
("substr", "expected three inputs")
.into()))
}
if let Ctr::Integer(i) = &*third_arg_inner {
if i < &0 {
return Err(start_trace(("substr", "end index cannot be negative").into()))
}
end = i.clone() as usize;
} else {
return Err(start_trace(
("substr", "expected third input to be an integer")
.into()))
}
if end > parent_str.len() {
return Err(start_trace(("substr", "end index larger than source string").into()))
}
if end <= start {
return Err(start_trace(("substr", "end index must be larger than start index").into()))
}
Ok(Ctr::String(parent_str[start..end].to_string()))
}
const IS_SUBSTR_DOCSTRING: &str =
"Takes two strings. Returns true if string1 contains at least one instance of string2";
fn is_substr_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let parent_str: String;
if let Ctr::String(ref s) = *ast.car {
parent_str = s.to_string();
} else {
return Err(start_trace(
("substr?", "expected first input to be a string")
.into()))
}
let second_arg_obj: &Ctr;
let child_str: String;
if let Ctr::Seg(ref s) = *ast.cdr {
second_arg_obj = &*s.car;
} else {
return Err(start_trace(
("substr?", "expected two inputs")
.into()))
}
if let Ctr::String(ref s) = &*second_arg_obj {
child_str = s.clone();
} else {
return Err(start_trace(
("substr?", "expected second input to be a string")
.into()))
}
Ok(Ctr::Bool(parent_str.contains(&child_str)))
}
const SPLIT_DOCSTRING: &str = "Takes two strings. String 1 is a source string and string 2 is a delimiter.
Returns a list of substrings from string 1 that were found delimited by string 2.";
fn split_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
let parent_str: String;
if let Ctr::String(ref s) = *ast.car {
parent_str = s.to_string();
} else {
return Err(start_trace(
("split", "expected first input to be a string")
.into()))
}
let second_arg_obj: &Ctr;
let delim_str: String;
if let Ctr::Seg(ref s) = *ast.cdr {
second_arg_obj = &*s.car;
} else {
return Err(start_trace(
("split", "expected two inputs")
.into()))
}
if let Ctr::String(ref s) = second_arg_obj {
delim_str = s.clone();
} else {
return Err(start_trace(
("split", "expected second input to be a string")
.into()))
}
let mut ret = Seg::new();
for substr in parent_str.split(&delim_str) {
ret.append(Box::new(Ctr::String(substr.to_string())));
}
Ok(Ctr::Seg(ret))
}
const INPUT_DOCSTRING: &str = "Takes one argument (string) and prints it.
Then prompts for user input.
User input is returned as a string";
fn input_callback(ast: &Seg, _syms: &mut SymTable) -> Result<Ctr, Traceback> {
if let Ctr::String(ref s) = *ast.car {
print!("{}", s);
let _= io::stdout().flush();
let mut input = String::new();
io::stdin().read_line(&mut input).expect("couldnt read user input");
Ok(Ctr::String(input.trim().to_string()))
} else {
Err(start_trace(
("input", "expected a string input to prompt user with")
.into()))
}
}
pub fn add_string_lib(syms: &mut SymTable) {
syms.insert(
"echo".to_string(),
Symbol {
name: String::from("echo"),
args: Args::Infinite,
conditional_branches: false,
docs: ECHO_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(echo_callback)),
..Default::default()
},
);
syms.insert(
"concat".to_string(),
Symbol {
name: String::from("concat"),
args: Args::Infinite,
conditional_branches: false,
docs: CONCAT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(concat_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"substr?".to_string(),
Symbol {
name: String::from("substr?"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: IS_SUBSTR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(is_substr_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"substr".to_string(),
Symbol {
name: String::from("substr"),
args: Args::Strict(vec![Type::String, Type::Integer, Type::Integer]),
conditional_branches: false,
docs: SUBSTR_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(substr_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"split".to_string(),
Symbol {
name: String::from("split"),
args: Args::Strict(vec![Type::String, Type::String]),
conditional_branches: false,
docs: SPLIT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(split_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"strlen".to_string(),
Symbol {
name: String::from("strlen"),
args: Args::Lazy(1),
conditional_branches: false,
docs: STRLEN_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(strlen_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"string".to_string(),
Symbol {
name: String::from("string"),
args: Args::Lazy(1),
conditional_branches: false,
docs: STRCAST_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(strcast_callback)),
optimizable: true,
..Default::default()
},
);
syms.insert(
"input".to_string(),
Symbol {
name: String::from("input"),
args: Args::Strict(vec![Type::String]),
conditional_branches: false,
docs: INPUT_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(input_callback)),
..Default::default()
}
);
}