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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::boxed::Box;
|
use std::rc::Rc;
|
||||||
use crate::cell::{Cell};
|
use std::cell::RefCell;
|
||||||
|
use crate::segment::{Ast};
|
||||||
use crate::func::FTable;
|
use crate::func::FTable;
|
||||||
use crate::vars::VTable;
|
use crate::vars::VTable;
|
||||||
|
|
||||||
|
|
@ -25,10 +26,10 @@ use crate::vars::VTable;
|
||||||
* representing the simplest possible form of the input
|
* representing the simplest possible form of the input
|
||||||
*/
|
*/
|
||||||
pub fn eval(
|
pub fn eval(
|
||||||
_ast: &Box<Cell>,
|
_ast: Ast,
|
||||||
_vars: &Box<VTable>,
|
_vars: Rc<RefCell<VTable>>,
|
||||||
_funcs: &Box<FTable>,
|
_funcs: Rc<RefCell<FTable>>,
|
||||||
_sym_loose: bool
|
_sym_loose: bool
|
||||||
) -> Result<Box<Cell>, String> {
|
) -> Result<Ast, String> {
|
||||||
Err("Unimplemented".to_string())
|
Err("Unimplemented".to_string())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
83
src/func.rs
83
src/func.rs
|
|
@ -15,23 +15,24 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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::convert::TryInto;
|
||||||
use std::collections::HashMap;
|
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::vars::{VTable};
|
||||||
use crate::eval::eval;
|
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
|
// 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 {
|
pub struct ExternalOperation {
|
||||||
// Un-evaluated abstract syntax tree
|
// Un-evaluated abstract syntax tree
|
||||||
// TODO: Intermediate evaluation to simplify branches with no argument in them
|
// TODO: Intermediate evaluation to simplify branches with no argument in them
|
||||||
// Simplified branches must not have side effects.
|
// Simplified branches must not have side effects.
|
||||||
// TODO: Apply Memoization?
|
// TODO: Apply Memoization?
|
||||||
ast: Box<Cell>,
|
ast: Ast,
|
||||||
// list of argument string tokens
|
// list of argument string tokens
|
||||||
arg_syms: Vec<String>
|
arg_syms: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
@ -67,47 +68,43 @@ pub struct Function {
|
||||||
pub eval_lazy: bool
|
pub eval_lazy: bool
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Function {
|
/* call
|
||||||
/* call
|
|
||||||
* routine is called by eval when a function call is detected
|
* routine is called by eval when a function call is detected
|
||||||
*/
|
*/
|
||||||
pub fn call(
|
pub fn func_call(
|
||||||
&self,
|
function: Rc<RefCell<Function>>,
|
||||||
args: &Box<Cell>,
|
args: Ast,
|
||||||
vars: &mut Box<VTable>,
|
vars: Rc<RefCell<VTable>>,
|
||||||
funcs: &mut Box<FTable>
|
funcs: Rc<RefCell<FTable>>
|
||||||
) -> Result<Box<Cell>, String> {
|
) -> Result<Ast, String> {
|
||||||
let n_args: &Box<Cell>;
|
let called_func = function.borrow_mut();
|
||||||
let outer_owner: Box<Cell>;
|
let mut n_args: Ast = args.clone();
|
||||||
if !self.eval_lazy {
|
if !called_func.eval_lazy {
|
||||||
match eval(args, vars, funcs, self.loose_syms) {
|
match eval(args, vars.clone(), funcs.clone(), called_func.loose_syms) {
|
||||||
Ok(box_cell) => outer_owner = box_cell,
|
Ok(rc_seg) => n_args = rc_seg.clone(),
|
||||||
Err(s) => return Err(
|
Err(s) => return Err(
|
||||||
format!(
|
format!(
|
||||||
"error evaluating args to {}: {}",
|
"error evaluating args to {}: {}",
|
||||||
self.name,
|
called_func.name,
|
||||||
s
|
s
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
n_args = &outer_owner;
|
|
||||||
} else {
|
|
||||||
n_args = args;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.args {
|
match &called_func.args {
|
||||||
Args::Lazy(num) => {
|
Args::Lazy(num) => {
|
||||||
if *num < 0 {
|
if *num < 0 {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !(*num == (n_args.len() - 1)) {
|
if !(*num == (list_len(n_args.clone()) as i128 - 1)) {
|
||||||
return Err(format!("expected {} args in call to {}", num, self.name))
|
return Err(format!("expected {} args in call to {}", num, called_func.name))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Args::Strict(arg_types) => {
|
Args::Strict(arg_types) => {
|
||||||
let mut idx: usize = 0;
|
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() {
|
if idx >= arg_types.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +124,7 @@ impl Function {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"{} too little arguments in call to {}",
|
"{} too little arguments in call to {}",
|
||||||
arg_types.len() - (idx + 1),
|
arg_types.len() - (idx + 1),
|
||||||
self.name
|
called_func.name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +133,7 @@ impl Function {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"argument {} in call to {} is of wrong type (expected {})",
|
"argument {} in call to {} is of wrong type (expected {})",
|
||||||
idx + 1,
|
idx + 1,
|
||||||
self.name,
|
called_func.name,
|
||||||
arg_types[idx].to_str()
|
arg_types[idx].to_str()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -144,36 +141,37 @@ impl Function {
|
||||||
if idx == (arg_types.len() - 1) {
|
if idx == (arg_types.len() - 1) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"too many arguments in call to {}",
|
"too many arguments in call to {}",
|
||||||
self.name
|
called_func.name
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match &self.function {
|
match &called_func.function {
|
||||||
Operation::Internal(f) => Ok((f)(&n_args, vars, funcs)),
|
Operation::Internal(f) => Ok((f)(n_args, vars, funcs)),
|
||||||
Operation::External(f) => {
|
Operation::External(f) => {
|
||||||
// copy var table and add args
|
let mut temp = vars.borrow().clone();
|
||||||
let mut temp = vars.clone();
|
|
||||||
for n in 0..f.arg_syms.len() {
|
for n in 0..f.arg_syms.len() {
|
||||||
temp.insert(
|
temp.insert(
|
||||||
f.arg_syms[n].clone(),
|
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(
|
pub fn func_declare(
|
||||||
ft: &mut Box<FTable>,
|
ft: Rc<RefCell<FTable>>,
|
||||||
f: Box<Function>
|
f: Rc<RefCell<Function>>
|
||||||
) -> Option<String> {
|
) -> Option<String> {
|
||||||
if let Operation::External(fun) = &f.function {
|
let func = f.borrow();
|
||||||
if let Args::Lazy(i) = f.args {
|
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() {
|
if fun.arg_syms.len() != i.try_into().unwrap() {
|
||||||
return Some(
|
return Some(
|
||||||
"external function must have lazy args equal to declared arg_syms length"
|
"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
|
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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std::boxed::Box;
|
use crate::segment::{Ctr, Ast, list_append, new_ast};
|
||||||
use crate::cell::{Ctr, Cell};
|
|
||||||
|
|
||||||
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
|
const UNMATCHED_STR_DELIM: &str = "Unmatched string delimiter in input";
|
||||||
const UNMATCHED_LIST_DELIM: &str = "Unmatched list 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
|
/* takes a line of user input
|
||||||
* returns an unsimplified tree of tokens.
|
* 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() {
|
if !document.is_ascii() {
|
||||||
return Err("document may only contain ascii characters".to_string());
|
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
|
/* 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
|
* 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();
|
let doc_len = document.len();
|
||||||
|
|
||||||
if doc_len == 0 {
|
if doc_len == 0 {
|
||||||
|
|
@ -110,16 +109,13 @@ fn process(document: String) -> Result<Box<Cell>, String> {
|
||||||
// match a delimiter
|
// match a delimiter
|
||||||
if !needs_alloc {
|
if !needs_alloc {
|
||||||
match c {
|
match c {
|
||||||
// add a new Cell reference to the stack
|
// add a new Seg reference to the stack
|
||||||
'(' => {
|
'(' => {
|
||||||
if token != "" {
|
if token != "" {
|
||||||
return Err("list started in middle of another token".to_string());
|
return Err("list started in middle of another token".to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
ref_stack.push(Box::new(Cell{
|
ref_stack.push(new_ast(Ctr::None, Ctr::None));
|
||||||
car: Ctr::None,
|
|
||||||
cdr: Ctr::None
|
|
||||||
}));
|
|
||||||
|
|
||||||
delim_stack.push(')');
|
delim_stack.push(')');
|
||||||
},
|
},
|
||||||
|
|
@ -151,7 +147,7 @@ fn process(document: String) -> Result<Box<Cell>, String> {
|
||||||
return Err("Empty token".to_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;
|
let mut obj;
|
||||||
if token.len() > 0 {
|
if token.len() > 0 {
|
||||||
if is_str {
|
if is_str {
|
||||||
|
|
@ -172,22 +168,22 @@ fn process(document: String) -> Result<Box<Cell>, String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
token = String::new();
|
token = String::new();
|
||||||
current_cell_ref.append(obj);
|
list_append(current_seg_ref.clone(), obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
if alloc_list {
|
if alloc_list {
|
||||||
// return if we have finished the document
|
// return if we have finished the document
|
||||||
if ref_stack.len() == 0 {
|
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
|
// shortening this will lead to naught but pain
|
||||||
obj = Ctr::Cell(Box::new(*current_cell_ref));
|
obj = Ctr::Seg(current_seg_ref.clone());
|
||||||
current_cell_ref = ref_stack.pop().unwrap();
|
current_seg_ref = ref_stack.pop().unwrap();
|
||||||
current_cell_ref.append(obj);
|
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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
mod cell;
|
mod segment;
|
||||||
mod lex;
|
mod lex;
|
||||||
mod func;
|
mod func;
|
||||||
mod eval;
|
mod eval;
|
||||||
mod vars;
|
mod vars;
|
||||||
|
|
||||||
pub mod ast {
|
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::lex::lex;
|
||||||
pub use crate::func::{Function, Operation, FTable, Args,
|
pub use crate::func::{Function, Operation, FTable, Args,
|
||||||
InternalOperation, ExternalOperation,
|
InternalOperation, ExternalOperation,
|
||||||
declare};
|
func_declare, func_call};
|
||||||
pub use crate::vars::VTable;
|
pub use crate::vars::{VTable, define};
|
||||||
pub use crate::eval::eval;
|
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/>.
|
* 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 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
|
* The string token can be found in any Ctr::Symbol value
|
||||||
* it is expected that the trees stored are already evaluated
|
* 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(
|
pub fn define(
|
||||||
vt: &mut Box<VTable>,
|
vt: Rc<RefCell<VTable>>,
|
||||||
identifier: String,
|
identifier: String,
|
||||||
var_tree: Box<Ctr>
|
var_tree: Rc<Ctr>
|
||||||
) {
|
) {
|
||||||
if let Some(boxed_cell) = vt.insert(identifier, var_tree) {
|
if let Some(rc_segment) = vt.borrow_mut().insert(identifier, var_tree) {
|
||||||
drop(boxed_cell);
|
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 {
|
mod lex_tests {
|
||||||
use relish::ast::{lex};
|
use relish::ast::{lex, ast_to_string};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lex_basic_pair() {
|
fn test_lex_basic_pair() {
|
||||||
let document: &str = "(hello 'world')";
|
let document: &str = "(hello 'world')";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
assert_eq!(format!("{}", *box_cell), document);
|
assert_eq!(ast_to_string(tree), document);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
print!("{}\n", s);
|
print!("{}\n", s);
|
||||||
|
|
@ -19,8 +19,8 @@ mod lex_tests {
|
||||||
fn test_lex_basic_list() {
|
fn test_lex_basic_list() {
|
||||||
let document: &str = "(hello 'world' 1 2 3)";
|
let document: &str = "(hello 'world' 1 2 3)";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
assert_eq!(format!("{}", *box_cell), document);
|
assert_eq!(ast_to_string(tree), document);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
print!("{}\n", s);
|
print!("{}\n", s);
|
||||||
|
|
@ -33,8 +33,8 @@ mod lex_tests {
|
||||||
fn test_lex_complex_list() {
|
fn test_lex_complex_list() {
|
||||||
let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)";
|
let document: &str = "(hello 'world' (1 2 (1 2 3)) 1 2 3)";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
assert_eq!(format!("{}", *box_cell), document);
|
assert_eq!(ast_to_string(tree), document);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
print!("{}\n", s);
|
print!("{}\n", s);
|
||||||
|
|
@ -48,8 +48,8 @@ mod lex_tests {
|
||||||
let document: &str = "(as;dd)";
|
let document: &str = "(as;dd)";
|
||||||
let output: &str = "Problem lexing document: \"Unparsable token:as;dd\"";
|
let output: &str = "Problem lexing document: \"Unparsable token:as;dd\"";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
print!("Bad token yielded: {}\n", *box_cell);
|
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
|
|
@ -63,8 +63,8 @@ mod lex_tests {
|
||||||
let document: &str = "(one two";
|
let document: &str = "(one two";
|
||||||
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
|
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
print!("Bad token yielded: {}\n", *box_cell);
|
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
|
|
@ -78,8 +78,8 @@ mod lex_tests {
|
||||||
let document: &str = "(one two (three)";
|
let document: &str = "(one two (three)";
|
||||||
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
|
let output: &str = "Problem lexing document: \"Unmatched list delimiter in input\"";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
print!("Bad token yielded: {}\n", *box_cell);
|
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
|
|
@ -93,8 +93,8 @@ mod lex_tests {
|
||||||
let document: &str = "#!/bin/relish\n(one two)";
|
let document: &str = "#!/bin/relish\n(one two)";
|
||||||
let output: &str = "(one two)";
|
let output: &str = "(one two)";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
assert_eq!(format!("{}", *box_cell), output.to_string());
|
assert_eq!(ast_to_string(tree), output.to_string());
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
print!("{}\n", 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 document: &str = "#!/bin/relish\n((one two)# another doc comment\n(three four))";
|
||||||
let output: &str = "((one two) (three four))";
|
let output: &str = "((one two) (three four))";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
assert_eq!(format!("{}", *box_cell), output.to_string());
|
assert_eq!(ast_to_string(tree), output.to_string());
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
print!("{}\n", 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 document: &str = "#!/bin/relish\n((one two)\n# another doc comment\nthree)";
|
||||||
let output: &str = "((one two) three)";
|
let output: &str = "((one two) three)";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
assert_eq!(format!("{}", *box_cell), output.to_string());
|
assert_eq!(ast_to_string(tree), output.to_string());
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
print!("{}\n", s);
|
print!("{}\n", s);
|
||||||
|
|
@ -138,8 +138,8 @@ mod lex_tests {
|
||||||
let document: &str = "(one t(wo)";
|
let document: &str = "(one t(wo)";
|
||||||
let output: &str = "Problem lexing document: \"list started in middle of another token\"";
|
let output: &str = "Problem lexing document: \"list started in middle of another token\"";
|
||||||
match lex(document.to_string()) {
|
match lex(document.to_string()) {
|
||||||
Ok(box_cell) => {
|
Ok(tree) => {
|
||||||
print!("Bad token yielded: {}\n", *box_cell);
|
print!("Bad token yielded: {}\n", ast_to_string(tree));
|
||||||
assert!(false);
|
assert!(false);
|
||||||
},
|
},
|
||||||
Err(s) => {
|
Err(s) => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue