172 lines
6.4 KiB
Rust
172 lines
6.4 KiB
Rust
/* 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()
|
|
}
|
|
);
|
|
|
|
}
|