Big referencing refactor
- RC+RefCell pattern used... everywhere - Ast type implemented - unit tests for func_call - more changes, but this commit scope has grown significantly and I cannot list them all
This commit is contained in:
parent
76b12a8214
commit
3434a49cc1
9 changed files with 446 additions and 391 deletions
245
src/cell.rs
245
src/cell.rs
|
|
@ -1,245 +0,0 @@
|
|||
/* relish: versatile lisp shell
|
||||
* Copyright (C) 2021 Aidan Hahn
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::fmt;
|
||||
use std::boxed::Box;
|
||||
|
||||
|
||||
// Container
|
||||
#[derive(Clone)]
|
||||
pub enum Ctr {
|
||||
Symbol(String),
|
||||
String(String),
|
||||
Integer(i128),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Cell(Box<Cell>),
|
||||
None
|
||||
}
|
||||
|
||||
// Type of Container
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub enum Type {
|
||||
Symbol,
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Bool,
|
||||
Cell,
|
||||
None
|
||||
}
|
||||
|
||||
/* Cell
|
||||
* Holds two Containers.
|
||||
* Basic building block for more complex data structures.
|
||||
*/
|
||||
#[derive(Clone)]
|
||||
pub struct Cell {
|
||||
/* "Cell Address Register"
|
||||
* Historical way of referring to the first value in a cell.
|
||||
*/
|
||||
pub car: Ctr,
|
||||
|
||||
/* "Cell Decrement Register"
|
||||
* Historical way of referring to the second value in a cell.
|
||||
*/
|
||||
pub cdr: Ctr
|
||||
}
|
||||
|
||||
impl Ctr {
|
||||
pub fn to_type(&self) -> Type {
|
||||
match self {
|
||||
Ctr::Symbol(_s) => Type::Symbol,
|
||||
Ctr::String(_s) => Type::String,
|
||||
Ctr::Integer(_s) => Type::Integer,
|
||||
Ctr::Float(_s) => Type::Float,
|
||||
Ctr::Bool(_s) => Type::Bool,
|
||||
Ctr::Cell(_s) => Type::Cell,
|
||||
Ctr::None => Type::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn to_str(&self) -> String {
|
||||
let ret: &str;
|
||||
match self {
|
||||
Type::Symbol => ret = "symbol",
|
||||
Type::String => ret = "string",
|
||||
Type::Integer => ret = "integer",
|
||||
Type::Float => ret = "float",
|
||||
Type::Bool => ret = "bool",
|
||||
Type::Cell => ret = "cell",
|
||||
Type::None => ret = "none"
|
||||
}
|
||||
|
||||
ret.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
// creates a cell containing two boxes
|
||||
pub fn cons (l_ctr: Ctr, r_ctr: Ctr) -> Cell {
|
||||
Cell {
|
||||
car: l_ctr,
|
||||
cdr: r_ctr
|
||||
}
|
||||
}
|
||||
|
||||
/* Prints any cell as a string
|
||||
* recurs on CELL type Containers
|
||||
*/
|
||||
pub fn cell_as_string(c: &Cell, with_parens: bool) -> String {
|
||||
let mut string = String::new();
|
||||
let mut prn_space = true;
|
||||
match &c.car {
|
||||
Ctr::Symbol(s) => string.push_str(&s),
|
||||
Ctr::String(s) => {
|
||||
string.push('\'');
|
||||
string.push_str(&s);
|
||||
string.push('\'');
|
||||
},
|
||||
Ctr::Integer(i) => string = string + &i.to_string(),
|
||||
Ctr::Float(f) => string = string + &f.to_string(),
|
||||
Ctr::Bool(b) => string = string + &b.to_string(),
|
||||
Ctr::Cell(c) => string.push_str(cell_as_string(&c, true).as_str()),
|
||||
Ctr::None => prn_space = false
|
||||
}
|
||||
|
||||
if prn_space {
|
||||
string.push(' ');
|
||||
}
|
||||
|
||||
match &c.cdr {
|
||||
Ctr::Symbol(s) => string.push_str(&s),
|
||||
Ctr::String(s) => {
|
||||
string.push('\'');
|
||||
string.push_str(&s);
|
||||
string.push('\'');
|
||||
},
|
||||
Ctr::Integer(i) => string = string + &i.to_string(),
|
||||
Ctr::Float(f) => string = string + &f.to_string(),
|
||||
Ctr::Bool(b) => string = string + &b.to_string(),
|
||||
Ctr::Cell(c) => string.push_str(cell_as_string(&c, false).as_str()),
|
||||
Ctr::None => {
|
||||
if prn_space {
|
||||
string.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe a better way to do this
|
||||
if with_parens {
|
||||
let mut extra = String::from("(");
|
||||
extra.push_str(&string);
|
||||
extra.push(')');
|
||||
string = extra
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
impl fmt::Display for Cell {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", cell_as_string(self, true).as_str())
|
||||
}
|
||||
}
|
||||
|
||||
/* NOTE: "Standard form" is used here to refer to a list of cells
|
||||
* that resembles a typical linked list. This means that Car may hold whatever,
|
||||
* but Cdr must either be Cell or None.
|
||||
*/
|
||||
impl Cell {
|
||||
/* applies a function across a Cell list in standard form
|
||||
* function must take a Ctr and return a bool
|
||||
* short circuits on the first false returned.
|
||||
* also returns false on a non standard form list
|
||||
*/
|
||||
pub fn circuit<F: FnMut(&Ctr)-> bool>(&self, func: &mut F) -> bool{
|
||||
if func(&self.car) {
|
||||
match &self.cdr {
|
||||
Ctr::None => true,
|
||||
Ctr::Cell(c) => c.circuit(func),
|
||||
_ => false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/* recurs over a chain of cells
|
||||
* adds obj to chain
|
||||
* not public, only meant for internal use... yet
|
||||
* steals ownership of obj
|
||||
*/
|
||||
pub fn append(&mut self, obj: Ctr) {
|
||||
match &mut self.car {
|
||||
Ctr::None => {
|
||||
self.car = obj;
|
||||
},
|
||||
_ => {
|
||||
match &mut self.cdr {
|
||||
Ctr::None => {
|
||||
self.cdr = Ctr::Cell(Box::new(Cell{
|
||||
car: obj,
|
||||
cdr: Ctr::None
|
||||
}));
|
||||
},
|
||||
Ctr::Cell(cell) => {
|
||||
cell.append(obj);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* recurs over a chain of cell
|
||||
* returns length of chain
|
||||
*/
|
||||
pub fn len(&self) -> i128 {
|
||||
match &self.cdr {
|
||||
Ctr::Cell(c) => c.len() + 1,
|
||||
_ => 1
|
||||
}
|
||||
}
|
||||
|
||||
/* iterates through a list.
|
||||
* returns a CLONE of the cell at the cooresponding index
|
||||
*/
|
||||
pub fn index(&self, idx: usize) -> Ctr {
|
||||
if idx > 0 {
|
||||
match &self.cdr {
|
||||
Ctr::None => Ctr::None,
|
||||
Ctr::Cell(c) => c.index(idx-1),
|
||||
_ => {
|
||||
if idx == 1 {
|
||||
self.cdr.clone()
|
||||
} else {
|
||||
Ctr::None
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match self.car {
|
||||
Ctr::None => Ctr::None,
|
||||
_ => {
|
||||
self.cdr.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/eval.rs
13
src/eval.rs
|
|
@ -15,8 +15,9 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
use crate::cell::{Cell};
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::segment::{Ast};
|
||||
use crate::func::FTable;
|
||||
use crate::vars::VTable;
|
||||
|
||||
|
|
@ -25,10 +26,10 @@ use crate::vars::VTable;
|
|||
* representing the simplest possible form of the input
|
||||
*/
|
||||
pub fn eval(
|
||||
_ast: &Box<Cell>,
|
||||
_vars: &Box<VTable>,
|
||||
_funcs: &Box<FTable>,
|
||||
_ast: Ast,
|
||||
_vars: Rc<RefCell<VTable>>,
|
||||
_funcs: Rc<RefCell<FTable>>,
|
||||
_sym_loose: bool
|
||||
) -> Result<Box<Cell>, String> {
|
||||
) -> Result<Ast, String> {
|
||||
Err("Unimplemented".to_string())
|
||||
}
|
||||
|
|
|
|||
81
src/func.rs
81
src/func.rs
|
|
@ -15,23 +15,24 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::collections::HashMap;
|
||||
use crate::cell::{Ctr, Cell, Type};
|
||||
use crate::segment::{Ctr, Type, circuit, list_len, list_idx, Ast};
|
||||
use crate::vars::{VTable};
|
||||
use crate::eval::eval;
|
||||
|
||||
pub type FTable = HashMap<String, Box<Function>>;
|
||||
pub type FTable = HashMap<String, Rc<RefCell<Function>>>;
|
||||
|
||||
// Standardized function signature for stdlib functions
|
||||
pub type InternalOperation = fn(&Box<Cell>, &mut Box<VTable>, &mut Box<FTable>) -> Box<Cell>;
|
||||
pub type InternalOperation = fn(Ast, Rc<RefCell<VTable>>, Rc<RefCell<FTable>>) -> Ast;
|
||||
pub struct ExternalOperation {
|
||||
// Un-evaluated abstract syntax tree
|
||||
// TODO: Intermediate evaluation to simplify branches with no argument in them
|
||||
// Simplified branches must not have side effects.
|
||||
// TODO: Apply Memoization?
|
||||
ast: Box<Cell>,
|
||||
ast: Ast,
|
||||
// list of argument string tokens
|
||||
arg_syms: Vec<String>
|
||||
}
|
||||
|
|
@ -67,47 +68,43 @@ pub struct Function {
|
|||
pub eval_lazy: bool
|
||||
}
|
||||
|
||||
impl Function {
|
||||
/* call
|
||||
* routine is called by eval when a function call is detected
|
||||
*/
|
||||
pub fn call(
|
||||
&self,
|
||||
args: &Box<Cell>,
|
||||
vars: &mut Box<VTable>,
|
||||
funcs: &mut Box<FTable>
|
||||
) -> Result<Box<Cell>, String> {
|
||||
let n_args: &Box<Cell>;
|
||||
let outer_owner: Box<Cell>;
|
||||
if !self.eval_lazy {
|
||||
match eval(args, vars, funcs, self.loose_syms) {
|
||||
Ok(box_cell) => outer_owner = box_cell,
|
||||
pub fn func_call(
|
||||
function: Rc<RefCell<Function>>,
|
||||
args: Ast,
|
||||
vars: Rc<RefCell<VTable>>,
|
||||
funcs: Rc<RefCell<FTable>>
|
||||
) -> Result<Ast, String> {
|
||||
let called_func = function.borrow_mut();
|
||||
let mut n_args: Ast = args.clone();
|
||||
if !called_func.eval_lazy {
|
||||
match eval(args, vars.clone(), funcs.clone(), called_func.loose_syms) {
|
||||
Ok(rc_seg) => n_args = rc_seg.clone(),
|
||||
Err(s) => return Err(
|
||||
format!(
|
||||
"error evaluating args to {}: {}",
|
||||
self.name,
|
||||
called_func.name,
|
||||
s
|
||||
)
|
||||
)
|
||||
}
|
||||
n_args = &outer_owner;
|
||||
} else {
|
||||
n_args = args;
|
||||
}
|
||||
|
||||
match &self.args {
|
||||
match &called_func.args {
|
||||
Args::Lazy(num) => {
|
||||
if *num < 0 {
|
||||
}
|
||||
|
||||
if !(*num == (n_args.len() - 1)) {
|
||||
return Err(format!("expected {} args in call to {}", num, self.name))
|
||||
if !(*num == (list_len(n_args.clone()) as i128 - 1)) {
|
||||
return Err(format!("expected {} args in call to {}", num, called_func.name))
|
||||
}
|
||||
},
|
||||
|
||||
Args::Strict(arg_types) => {
|
||||
let mut idx: usize = 0;
|
||||
let passes = n_args.circuit(&mut |c: &Ctr| {
|
||||
let passes = circuit(n_args.clone(), &mut |c: &Ctr| {
|
||||
if idx >= arg_types.len() {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -127,7 +124,7 @@ impl Function {
|
|||
return Err(format!(
|
||||
"{} too little arguments in call to {}",
|
||||
arg_types.len() - (idx + 1),
|
||||
self.name
|
||||
called_func.name
|
||||
));
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +133,7 @@ impl Function {
|
|||
return Err(format!(
|
||||
"argument {} in call to {} is of wrong type (expected {})",
|
||||
idx + 1,
|
||||
self.name,
|
||||
called_func.name,
|
||||
arg_types[idx].to_str()
|
||||
));
|
||||
}
|
||||
|
|
@ -144,36 +141,37 @@ impl Function {
|
|||
if idx == (arg_types.len() - 1) {
|
||||
return Err(format!(
|
||||
"too many arguments in call to {}",
|
||||
self.name
|
||||
called_func.name
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match &self.function {
|
||||
Operation::Internal(f) => Ok((f)(&n_args, vars, funcs)),
|
||||
match &called_func.function {
|
||||
Operation::Internal(f) => Ok((f)(n_args, vars, funcs)),
|
||||
Operation::External(f) => {
|
||||
// copy var table and add args
|
||||
let mut temp = vars.clone();
|
||||
let mut temp = vars.borrow().clone();
|
||||
for n in 0..f.arg_syms.len() {
|
||||
temp.insert(
|
||||
f.arg_syms[n].clone(),
|
||||
Box::new(n_args.index(n))
|
||||
Rc::new(list_idx(n_args.clone(), n as u128))
|
||||
);
|
||||
}
|
||||
eval(&f.ast, &temp, funcs, self.loose_syms)
|
||||
}
|
||||
|
||||
eval(f.ast.clone(), Rc::new(RefCell::new(temp)), funcs, called_func.loose_syms)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn declare(
|
||||
ft: &mut Box<FTable>,
|
||||
f: Box<Function>
|
||||
pub fn func_declare(
|
||||
ft: Rc<RefCell<FTable>>,
|
||||
f: Rc<RefCell<Function>>
|
||||
) -> Option<String> {
|
||||
if let Operation::External(fun) = &f.function {
|
||||
if let Args::Lazy(i) = f.args {
|
||||
let func = f.borrow();
|
||||
let name = func.name.clone();
|
||||
if let Operation::External(fun) = &func.function {
|
||||
if let Args::Lazy(i) = func.args {
|
||||
if fun.arg_syms.len() != i.try_into().unwrap() {
|
||||
return Some(
|
||||
"external function must have lazy args equal to declared arg_syms length"
|
||||
|
|
@ -188,6 +186,7 @@ pub fn declare(
|
|||
}
|
||||
}
|
||||
|
||||
ft.insert(f.name.clone(), f);
|
||||
drop(func);
|
||||
ft.borrow_mut().insert(name, f);
|
||||
None
|
||||
}
|
||||
|
|
|
|||
30
src/lex.rs
30
src/lex.rs
|
|
@ -15,8 +15,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
use crate::cell::{Ctr, Cell};
|
||||
use crate::segment::{Ctr, Ast, list_append, new_ast};
|
||||
|
||||
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
|
||||
const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input";
|
||||
|
|
@ -24,7 +23,7 @@ const UNMATCHED_LIST_DELIM: &str = "Unmatched list delimiter in input";
|
|||
/* takes a line of user input
|
||||
* returns an unsimplified tree of tokens.
|
||||
*/
|
||||
pub fn lex(document: String) -> Result<Box<Cell>, String> {
|
||||
pub fn lex(document: String) -> Result<Ast, String> {
|
||||
if !document.is_ascii() {
|
||||
return Err("document may only contain ascii characters".to_string());
|
||||
}
|
||||
|
|
@ -40,10 +39,10 @@ pub fn lex(document: String) -> Result<Box<Cell>, String> {
|
|||
}
|
||||
|
||||
/* The logic used in lex
|
||||
* Returns Ok(Box<Cell>) if lexing passes
|
||||
* Returns Ok(Rc<Seg>) if lexing passes
|
||||
* Returns Err(String) if an error occurs
|
||||
*/
|
||||
fn process(document: String) -> Result<Box<Cell>, String> {
|
||||
fn process(document: String) -> Result<Ast, String> {
|
||||
let doc_len = document.len();
|
||||
|
||||
if doc_len == 0 {
|
||||
|
|
@ -110,16 +109,13 @@ fn process(document: String) -> Result<Box<Cell>, String> {
|
|||
// match a delimiter
|
||||
if !needs_alloc {
|
||||
match c {
|
||||
// add a new Cell reference to the stack
|
||||
// add a new Seg reference to the stack
|
||||
'(' => {
|
||||
if token != "" {
|
||||
return Err("list started in middle of another token".to_string());
|
||||
}
|
||||
|
||||
ref_stack.push(Box::new(Cell{
|
||||
car: Ctr::None,
|
||||
cdr: Ctr::None
|
||||
}));
|
||||
ref_stack.push(new_ast(Ctr::None, Ctr::None));
|
||||
|
||||
delim_stack.push(')');
|
||||
},
|
||||
|
|
@ -151,7 +147,7 @@ fn process(document: String) -> Result<Box<Cell>, String> {
|
|||
return Err("Empty token".to_string());
|
||||
}
|
||||
|
||||
let mut current_cell_ref = ref_stack.pop().unwrap();
|
||||
let mut current_seg_ref = ref_stack.pop().unwrap();
|
||||
let mut obj;
|
||||
if token.len() > 0 {
|
||||
if is_str {
|
||||
|
|
@ -172,22 +168,22 @@ fn process(document: String) -> Result<Box<Cell>, String> {
|
|||
}
|
||||
|
||||
token = String::new();
|
||||
current_cell_ref.append(obj);
|
||||
list_append(current_seg_ref.clone(), obj);
|
||||
}
|
||||
|
||||
if alloc_list {
|
||||
// return if we have finished the document
|
||||
if ref_stack.len() == 0 {
|
||||
return Ok(Box::new(*current_cell_ref));
|
||||
return Ok(current_seg_ref);
|
||||
}
|
||||
|
||||
// shortening this will lead to naught but pain
|
||||
obj = Ctr::Cell(Box::new(*current_cell_ref));
|
||||
current_cell_ref = ref_stack.pop().unwrap();
|
||||
current_cell_ref.append(obj);
|
||||
obj = Ctr::Seg(current_seg_ref.clone());
|
||||
current_seg_ref = ref_stack.pop().unwrap();
|
||||
list_append(current_seg_ref.clone(), obj);
|
||||
}
|
||||
|
||||
ref_stack.push(current_cell_ref);
|
||||
ref_stack.push(current_seg_ref);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,18 +15,18 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
mod cell;
|
||||
mod segment;
|
||||
mod lex;
|
||||
mod func;
|
||||
mod eval;
|
||||
mod vars;
|
||||
|
||||
pub mod ast {
|
||||
pub use crate::cell::{Cell, Ctr, cons, cell_as_string};
|
||||
pub use crate::segment::{Seg, Ctr, ast_to_string, Type, Ast, new_ast};
|
||||
pub use crate::lex::lex;
|
||||
pub use crate::func::{Function, Operation, FTable, Args,
|
||||
InternalOperation, ExternalOperation,
|
||||
declare};
|
||||
pub use crate::vars::VTable;
|
||||
func_declare, func_call};
|
||||
pub use crate::vars::{VTable, define};
|
||||
pub use crate::eval::eval;
|
||||
}
|
||||
|
|
|
|||
245
src/segment.rs
Normal file
245
src/segment.rs
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/* relish: versatile lisp shell
|
||||
* Copyright (C) 2021 Aidan Hahn
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
|
||||
// Recursive data type for a tree of Segments
|
||||
pub type Ast = Rc<RefCell<Seg>>;
|
||||
|
||||
// Container
|
||||
#[derive(Clone)]
|
||||
pub enum Ctr {
|
||||
Symbol(String),
|
||||
String(String),
|
||||
Integer(i128),
|
||||
Float(f64),
|
||||
Bool(bool),
|
||||
Seg(Ast),
|
||||
None
|
||||
}
|
||||
|
||||
// Type of Container
|
||||
#[derive(PartialEq)]
|
||||
#[derive(Clone)]
|
||||
pub enum Type {
|
||||
Symbol,
|
||||
String,
|
||||
Integer,
|
||||
Float,
|
||||
Bool,
|
||||
Seg,
|
||||
None
|
||||
}
|
||||
|
||||
/* Segment
|
||||
* Holds two Containers.
|
||||
* Basic building block for more complex data structures.
|
||||
* I was going to call it Cell and then I learned about
|
||||
* how important RefCells were in Rust
|
||||
*/
|
||||
#[derive(Clone)]
|
||||
pub struct Seg {
|
||||
/* "Contents of Address Register"
|
||||
* Historical way of referring to the first value in a cell.
|
||||
*/
|
||||
pub car: Ctr,
|
||||
|
||||
/* "Contents of Decrement Register"
|
||||
* Historical way of referring to the second value in a cell.
|
||||
*/
|
||||
pub cdr: Ctr
|
||||
}
|
||||
|
||||
impl Ctr {
|
||||
pub fn to_type(&self) -> Type {
|
||||
match self {
|
||||
Ctr::Symbol(_s) => Type::Symbol,
|
||||
Ctr::String(_s) => Type::String,
|
||||
Ctr::Integer(_s) => Type::Integer,
|
||||
Ctr::Float(_s) => Type::Float,
|
||||
Ctr::Bool(_s) => Type::Bool,
|
||||
Ctr::Seg(_s) => Type::Seg,
|
||||
Ctr::None => Type::None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Type {
|
||||
pub fn to_str(&self) -> String {
|
||||
let ret: &str;
|
||||
match self {
|
||||
Type::Symbol => ret = "symbol",
|
||||
Type::String => ret = "string",
|
||||
Type::Integer => ret = "integer",
|
||||
Type::Float => ret = "float",
|
||||
Type::Bool => ret = "bool",
|
||||
Type::Seg => ret = "segment",
|
||||
Type::None => ret = "none"
|
||||
}
|
||||
|
||||
ret.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
/* Prints a Syntax Tree as a string
|
||||
*/
|
||||
pub fn ast_as_string(c: Ast, with_parens: bool) -> String {
|
||||
let mut string = String::new();
|
||||
let mut prn_space = true;
|
||||
let seg = c.borrow();
|
||||
match &seg.car {
|
||||
Ctr::Symbol(s) => string.push_str(&s),
|
||||
Ctr::String(s) => {
|
||||
string.push('\'');
|
||||
string.push_str(&s);
|
||||
string.push('\'');
|
||||
},
|
||||
Ctr::Integer(i) => string = string + &i.to_string(),
|
||||
Ctr::Float(f) => string = string + &f.to_string(),
|
||||
Ctr::Bool(b) => string = string + &b.to_string(),
|
||||
Ctr::Seg(c) => string.push_str(ast_as_string(c.clone(), true).as_str()),
|
||||
Ctr::None => prn_space = false
|
||||
}
|
||||
|
||||
if prn_space {
|
||||
string.push(' ');
|
||||
}
|
||||
|
||||
match &seg.cdr {
|
||||
Ctr::Symbol(s) => string.push_str(&s),
|
||||
Ctr::String(s) => {
|
||||
string.push('\'');
|
||||
string.push_str(&s);
|
||||
string.push('\'');
|
||||
},
|
||||
Ctr::Integer(i) => string = string + &i.to_string(),
|
||||
Ctr::Float(f) => string = string + &f.to_string(),
|
||||
Ctr::Bool(b) => string = string + &b.to_string(),
|
||||
Ctr::Seg(c) => string.push_str(ast_as_string(c.clone(), false).as_str()),
|
||||
Ctr::None => {
|
||||
if prn_space {
|
||||
string.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: maybe a better way to do this
|
||||
if with_parens {
|
||||
let mut extra = String::from("(");
|
||||
extra.push_str(&string);
|
||||
extra.push(')');
|
||||
string = extra
|
||||
}
|
||||
return string
|
||||
}
|
||||
|
||||
pub fn ast_to_string(c: Ast) -> String {
|
||||
ast_as_string(c.clone(), true)
|
||||
}
|
||||
|
||||
/* NOTE: "Standard form" is used here to refer to a list of segments
|
||||
* that resembles a typical linked list. This means that Car may hold whatever,
|
||||
* but Cdr must either be Seg or None.
|
||||
*/
|
||||
|
||||
/* Initializes a new ast node with segment car and cdr passed in
|
||||
*/
|
||||
pub fn new_ast(car: Ctr, cdr: Ctr) -> Ast {
|
||||
Rc::new(RefCell::new(Seg{
|
||||
car: car,
|
||||
cdr: cdr
|
||||
}))
|
||||
}
|
||||
|
||||
/* applies a function across a list in standard form
|
||||
* function must take a Ctr and return a bool
|
||||
* short circuits on the first false returned.
|
||||
* also returns false on a non standard form list
|
||||
*/
|
||||
pub fn circuit<F: FnMut(&Ctr)-> bool>(tree: Ast, func: &mut F) -> bool{
|
||||
let inner = tree.borrow();
|
||||
if func(&inner.car) {
|
||||
match &inner.cdr {
|
||||
Ctr::None => true,
|
||||
Ctr::Seg(c) => circuit(c.clone(), func),
|
||||
_ => false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/* recurs over ast assumed to be list in standard form
|
||||
* returns length
|
||||
*/
|
||||
pub fn list_len(tree: Ast) -> u128 {
|
||||
match &tree.borrow().cdr {
|
||||
Ctr::Seg(c) => list_len(c.clone()) + 1,
|
||||
_ => 1
|
||||
}
|
||||
}
|
||||
|
||||
/* recurs over tree assumed to be list in standard form
|
||||
* returns clone of ctr at index provided
|
||||
*/
|
||||
pub fn list_idx(tree: Ast, idx: u128) -> Ctr {
|
||||
let inner = tree.borrow();
|
||||
if idx > 0 {
|
||||
match &inner.cdr {
|
||||
Ctr::None => Ctr::None,
|
||||
Ctr::Seg(c) => list_idx(c.clone(), idx - 1),
|
||||
_ => {
|
||||
if idx == 1 {
|
||||
inner.cdr.clone()
|
||||
} else {
|
||||
Ctr::None
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match inner.car {
|
||||
Ctr::None => Ctr::None,
|
||||
_ => {
|
||||
inner.cdr.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* recurs over tree assumed to be list in standard form
|
||||
* appends object to end of list
|
||||
*/
|
||||
pub fn list_append(tree: Ast, obj: Ctr) {
|
||||
let mut inner = tree.borrow_mut();
|
||||
match &inner.car {
|
||||
Ctr::None => {
|
||||
inner.car = obj;
|
||||
},
|
||||
_ => {
|
||||
match &inner.cdr {
|
||||
Ctr::None => {
|
||||
inner.cdr = Ctr::Seg(new_ast(obj, Ctr::None));
|
||||
},
|
||||
Ctr::Seg(tr) => {
|
||||
list_append(tr.clone(), obj);
|
||||
},
|
||||
_ => ()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/vars.rs
17
src/vars.rs
|
|
@ -15,22 +15,23 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::boxed::Box;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
use crate::cell::{Ctr};
|
||||
use crate::segment::{Ctr};
|
||||
|
||||
/* Mapping between a string token and a tree of Cells
|
||||
/* Mapping between a string token and a tree of Segments
|
||||
* The string token can be found in any Ctr::Symbol value
|
||||
* it is expected that the trees stored are already evaluated
|
||||
*/
|
||||
pub type VTable = HashMap<String, Box<Ctr>>;
|
||||
pub type VTable = HashMap<String, Rc<Ctr>>;
|
||||
|
||||
pub fn define(
|
||||
vt: &mut Box<VTable>,
|
||||
vt: Rc<RefCell<VTable>>,
|
||||
identifier: String,
|
||||
var_tree: Box<Ctr>
|
||||
var_tree: Rc<Ctr>
|
||||
) {
|
||||
if let Some(boxed_cell) = vt.insert(identifier, var_tree) {
|
||||
drop(boxed_cell);
|
||||
if let Some(rc_segment) = vt.borrow_mut().insert(identifier, var_tree) {
|
||||
drop(rc_segment);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
tests/test_func.rs
Normal file
58
tests/test_func.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
mod func_tests {
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use relish::ast::{Ast, Type, Ctr, new_ast};
|
||||
use relish::ast::VTable;
|
||||
use relish::ast::{Function, Operation, FTable, Args, func_declare, func_call};
|
||||
|
||||
#[test]
|
||||
fn decl_and_call_internal_func() {
|
||||
let test_internal_func: Function = Function{
|
||||
name: String::from("test_func_in"),
|
||||
loose_syms: false,
|
||||
eval_lazy: true,
|
||||
args: Args::Strict(vec![Type::Bool]),
|
||||
function: Operation::Internal(
|
||||
|a: Ast, _b: Rc<RefCell<VTable>>, _c: Rc<RefCell<FTable>>| -> Ast {
|
||||
let inner = a.borrow();
|
||||
let mut is_bool = false;
|
||||
if let Ctr::Bool(_) = &inner.car {
|
||||
is_bool = true;
|
||||
}
|
||||
|
||||
new_ast(Ctr::Bool(is_bool), Ctr::None)
|
||||
}
|
||||
)
|
||||
};
|
||||
let ft = Rc::new(RefCell::new(FTable::new()));
|
||||
let vt = Rc::new(RefCell::new(VTable::new()));
|
||||
let args = new_ast(Ctr::Bool(true), Ctr::None);
|
||||
if let Some(s) = func_declare(ft.clone(),
|
||||
Rc::new(RefCell::new(test_internal_func))) {
|
||||
print!("{}", s);
|
||||
assert!(false);
|
||||
}
|
||||
|
||||
let func: Rc<RefCell<Function>>;
|
||||
if let Some(f) = ft.borrow().get(&"test_func_in".to_string()) {
|
||||
func = f.clone();
|
||||
} else {
|
||||
print!("failed to retrieve function!");
|
||||
assert!(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(ast) = func_call(func, args, vt, ft) {
|
||||
match &ast.borrow().car {
|
||||
Ctr::Bool(b) => assert!(b),
|
||||
_ => {
|
||||
print!("invalid return from func!");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
print!("call to function failed!");
|
||||
assert!(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
mod lex_tests {
|
||||
use relish::ast::{lex};
|
||||
use relish::ast::{lex, ast_to_string};
|
||||
|
||||
#[test]
|
||||
fn test_lex_basic_pair() {
|
||||
let document: &str = "(hello 'world')";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
assert_eq!(format!("{}", *box_cell), document);
|
||||
Ok(tree) => {
|
||||
assert_eq!(ast_to_string(tree), document);
|
||||
},
|
||||
Err(s) => {
|
||||
print!("{}\n", s);
|
||||
|
|
@ -19,8 +19,8 @@ mod lex_tests {
|
|||
fn test_lex_basic_list() {
|
||||
let document: &str = "(hello 'world' 1 2 3)";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
assert_eq!(format!("{}", *box_cell), document);
|
||||
Ok(tree) => {
|
||||
assert_eq!(ast_to_string(tree), document);
|
||||
},
|
||||
Err(s) => {
|
||||
print!("{}\n", s);
|
||||
|
|
@ -33,8 +33,8 @@ mod lex_tests {
|
|||
fn test_lex_complex_list() {
|
||||
let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
assert_eq!(format!("{}", *box_cell), document);
|
||||
Ok(tree) => {
|
||||
assert_eq!(ast_to_string(tree), document);
|
||||
},
|
||||
Err(s) => {
|
||||
print!("{}\n", s);
|
||||
|
|
@ -48,8 +48,8 @@ mod lex_tests {
|
|||
let document: &str = "(as;dd)";
|
||||
let output: &str = "Problem lexing document: \"Unparsable token:as;dd\"";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
print!("Bad token yielded: {}\n", *box_cell);
|
||||
Ok(tree) => {
|
||||
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||
assert!(false);
|
||||
},
|
||||
Err(s) => {
|
||||
|
|
@ -63,8 +63,8 @@ mod lex_tests {
|
|||
let document: &str = "(one two";
|
||||
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
print!("Bad token yielded: {}\n", *box_cell);
|
||||
Ok(tree) => {
|
||||
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||
assert!(false);
|
||||
},
|
||||
Err(s) => {
|
||||
|
|
@ -78,8 +78,8 @@ mod lex_tests {
|
|||
let document: &str = "(one two (three)";
|
||||
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
print!("Bad token yielded: {}\n", *box_cell);
|
||||
Ok(tree) => {
|
||||
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||
assert!(false);
|
||||
},
|
||||
Err(s) => {
|
||||
|
|
@ -93,8 +93,8 @@ mod lex_tests {
|
|||
let document: &str = "#!/bin/relish\n(one two)";
|
||||
let output: &str = "(one two)";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
assert_eq!(format!("{}", *box_cell), output.to_string());
|
||||
Ok(tree) => {
|
||||
assert_eq!(ast_to_string(tree), output.to_string());
|
||||
},
|
||||
Err(s) => {
|
||||
print!("{}\n", s);
|
||||
|
|
@ -108,8 +108,8 @@ mod lex_tests {
|
|||
let document: &str = "#!/bin/relish\n((one two)# another doc comment\n(three four))";
|
||||
let output: &str = "((one two) (three four))";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
assert_eq!(format!("{}", *box_cell), output.to_string());
|
||||
Ok(tree) => {
|
||||
assert_eq!(ast_to_string(tree), output.to_string());
|
||||
},
|
||||
Err(s) => {
|
||||
print!("{}\n", s);
|
||||
|
|
@ -123,8 +123,8 @@ mod lex_tests {
|
|||
let document: &str = "#!/bin/relish\n((one two)\n# another doc comment\nthree)";
|
||||
let output: &str = "((one two) three)";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
assert_eq!(format!("{}", *box_cell), output.to_string());
|
||||
Ok(tree) => {
|
||||
assert_eq!(ast_to_string(tree), output.to_string());
|
||||
},
|
||||
Err(s) => {
|
||||
print!("{}\n", s);
|
||||
|
|
@ -138,8 +138,8 @@ mod lex_tests {
|
|||
let document: &str = "(one t(wo)";
|
||||
let output: &str = "Problem lexing document: \"list started in middle of another token\"";
|
||||
match lex(document.to_string()) {
|
||||
Ok(box_cell) => {
|
||||
print!("Bad token yielded: {}\n", *box_cell);
|
||||
Ok(tree) => {
|
||||
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||
assert!(false);
|
||||
},
|
||||
Err(s) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue