File operations
* read * write * append * exists? * tests for each
This commit is contained in:
parent
25db2aac47
commit
64be70b3af
5 changed files with 261 additions and 6 deletions
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
#+Title: Relish: Rusty Expressive LIsp SHell
|
||||
#+Author: Ava Hahn
|
||||
|
||||
|
|
@ -155,13 +156,11 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
|||
(See tag: v0.3.0)
|
||||
** TODO v1.0 tasks
|
||||
- islist type query
|
||||
- assert function
|
||||
- set library
|
||||
- Can pass args to relish scripts (via interpreter)
|
||||
- Can pass args to relish scripts (via command line)
|
||||
- File operations
|
||||
- read-to-string
|
||||
- write-to-file
|
||||
- file exists?
|
||||
- (path functions)
|
||||
- (add this all to the readme)
|
||||
- finish basic goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
||||
- Release CI
|
||||
|
|
@ -170,7 +169,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
|||
- Post release to relevant channels
|
||||
** TODO v1.1 tasks
|
||||
- finish stretch goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
||||
- Stl boolean assert builtin
|
||||
- History length configurable (env var?)
|
||||
- Lex function
|
||||
- Read function (Input + Lex)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,10 @@ fn process(document: &String) -> Result<Box<Seg>, String> {
|
|||
delim = *d;
|
||||
|
||||
if delim == '*' {
|
||||
token.push(ESCAPES[&c]);
|
||||
token.push(ESCAPES.get(&c)
|
||||
.cloned()
|
||||
.or(Some(c))
|
||||
.unwrap());
|
||||
delim_stack.pop();
|
||||
continue;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ pub mod control;
|
|||
pub mod decl;
|
||||
pub mod math;
|
||||
pub mod strings;
|
||||
pub mod file;
|
||||
|
||||
pub const CONSOLE_XDIM_VNAME: &str = "_RELISH_WIDTH";
|
||||
pub const CONSOLE_YDIM_VNAME: &str = "_RELISH_HEIGHT";
|
||||
|
|
@ -66,6 +67,7 @@ pub fn static_stdlib(syms: &mut SymTable) {
|
|||
control::add_control_lib(syms);
|
||||
boolean::add_bool_lib(syms);
|
||||
math::add_math_lib(syms);
|
||||
file::add_file_lib(syms);
|
||||
|
||||
syms.insert(
|
||||
"call".to_string(),
|
||||
|
|
|
|||
172
src/stl/file.rs
Normal file
172
src/stl/file.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/* 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 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()
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
80
tests/test_lib_file.rs
Normal file
80
tests/test_lib_file.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
mod file_lib_tests {
|
||||
use relish::ast::{eval, lex, SymTable};
|
||||
use relish::stdlib::{dynamic_stdlib, static_stdlib};
|
||||
|
||||
#[test]
|
||||
fn test_fexists() {
|
||||
let document = "(exists? '/tmp')";
|
||||
let result = "true";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms);
|
||||
dynamic_stdlib(&mut syms, None);
|
||||
assert_eq!(
|
||||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
result.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fexists_doesnt() {
|
||||
let document = "(exists? 'cargo.timtam')";
|
||||
let result = "false";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms);
|
||||
dynamic_stdlib(&mut syms, None);
|
||||
assert_eq!(
|
||||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
result.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_file() {
|
||||
let document = "
|
||||
(let ((s 'test')
|
||||
(t '/tmp/relish-lib-test-file-1'))
|
||||
(write-file t s)
|
||||
(echo (read-file t))
|
||||
(eq? (read-file t) s))";
|
||||
let result = "true";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms);
|
||||
dynamic_stdlib(&mut syms, None);
|
||||
assert_eq!(
|
||||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
result.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_append_file() {
|
||||
let document = "
|
||||
(let ((s 'test')
|
||||
(t '/tmp/relish-lib-test-file-2'))
|
||||
(write-file t s)
|
||||
(append-file t s)
|
||||
(eq? (read-file t)
|
||||
(concat s s)))";
|
||||
let result = "true";
|
||||
|
||||
let mut syms = SymTable::new();
|
||||
static_stdlib(&mut syms);
|
||||
dynamic_stdlib(&mut syms, None);
|
||||
assert_eq!(
|
||||
*eval(&lex(&document.to_string()).unwrap(), &mut syms)
|
||||
.unwrap()
|
||||
.to_string(),
|
||||
result.to_string(),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue