Merge branch 'file-ops' into 'main'
File operations See merge request whom/relish!9
This commit is contained in:
commit
270cc32572
5 changed files with 261 additions and 6 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
#+Title: Relish: Rusty Expressive LIsp SHell
|
#+Title: Relish: Rusty Expressive LIsp SHell
|
||||||
#+Author: Ava Hahn
|
#+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)
|
(See tag: v0.3.0)
|
||||||
** TODO v1.0 tasks
|
** TODO v1.0 tasks
|
||||||
- islist type query
|
- islist type query
|
||||||
|
- assert function
|
||||||
|
- set library
|
||||||
- Can pass args to relish scripts (via interpreter)
|
- Can pass args to relish scripts (via interpreter)
|
||||||
- Can pass args to relish scripts (via command line)
|
- Can pass args to relish scripts (via command line)
|
||||||
- File operations
|
- File operations
|
||||||
- read-to-string
|
|
||||||
- write-to-file
|
|
||||||
- file exists?
|
|
||||||
- (path functions)
|
|
||||||
- (add this all to the readme)
|
- (add this all to the readme)
|
||||||
- finish basic goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
- finish basic goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
||||||
- Release CI
|
- Release CI
|
||||||
|
|
@ -170,7 +169,6 @@ Note: this section only tracks the state of incomplete TODO items. Having everyt
|
||||||
- Post release to relevant channels
|
- Post release to relevant channels
|
||||||
** TODO v1.1 tasks
|
** TODO v1.1 tasks
|
||||||
- finish stretch goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
- finish stretch goals in the [[file:snippets/interactive-devel.rls][interactive development library]]
|
||||||
- Stl boolean assert builtin
|
|
||||||
- History length configurable (env var?)
|
- History length configurable (env var?)
|
||||||
- Lex function
|
- Lex function
|
||||||
- Read function (Input + Lex)
|
- Read function (Input + Lex)
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,10 @@ fn process(document: &String) -> Result<Box<Seg>, String> {
|
||||||
delim = *d;
|
delim = *d;
|
||||||
|
|
||||||
if delim == '*' {
|
if delim == '*' {
|
||||||
token.push(ESCAPES[&c]);
|
token.push(ESCAPES.get(&c)
|
||||||
|
.cloned()
|
||||||
|
.or(Some(c))
|
||||||
|
.unwrap());
|
||||||
delim_stack.pop();
|
delim_stack.pop();
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ pub mod control;
|
||||||
pub mod decl;
|
pub mod decl;
|
||||||
pub mod math;
|
pub mod math;
|
||||||
pub mod strings;
|
pub mod strings;
|
||||||
|
pub mod file;
|
||||||
|
|
||||||
pub const CONSOLE_XDIM_VNAME: &str = "_RELISH_WIDTH";
|
pub const CONSOLE_XDIM_VNAME: &str = "_RELISH_WIDTH";
|
||||||
pub const CONSOLE_YDIM_VNAME: &str = "_RELISH_HEIGHT";
|
pub const CONSOLE_YDIM_VNAME: &str = "_RELISH_HEIGHT";
|
||||||
|
|
@ -66,6 +67,7 @@ pub fn static_stdlib(syms: &mut SymTable) {
|
||||||
control::add_control_lib(syms);
|
control::add_control_lib(syms);
|
||||||
boolean::add_bool_lib(syms);
|
boolean::add_bool_lib(syms);
|
||||||
math::add_math_lib(syms);
|
math::add_math_lib(syms);
|
||||||
|
file::add_file_lib(syms);
|
||||||
|
|
||||||
syms.insert(
|
syms.insert(
|
||||||
"call".to_string(),
|
"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