/* 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 .
*/
use {
crate::{
segment::{
Ctr, Seg, Type
},
sym::{
SymTable, ValueType,
Symbol, Args,
},
error::{
Traceback, start_trace,
},
eval::eval,
run,
},
libc::{
sigaddset, sigemptyset, sigprocmask, exit,
SIGINT, SIGCHLD, SIGTTOU, SIGTTIN, SIGQUIT, SIGTSTP,
SIG_BLOCK, SIG_UNBLOCK
},
std::{
collections::VecDeque,
cell::RefCell,
io::Result as IOResult,
rc::Rc,
fs::{
File, OpenOptions, canonicalize,
},
path::Path,
process::{
Command, Stdio, Child
},
env::set_current_dir,
os::unix::process::CommandExt,
mem,
thread,
time::Duration,
},
nix::{
unistd, unistd::Pid,
sys::termios::{
Termios, tcgetattr,
tcsetattr, SetArg,
},
},
};
pub const POSIX_LOAD_NAME: &str = "load";
pub const CD_USER_CB: &str = "CFG_FLESH_CD_CB";
pub struct ShellState {
pub parent_pid: Pid,
pub parent_sid: Pid,
pub children: Vec,
pub last_exit_code: i32,
pub attr: Option,
}
pub fn ign_sigs() {
unsafe {
let mut mask = mem::zeroed();
sigemptyset(&mut mask);
sigaddset(&mut mask, SIGINT);
sigaddset(&mut mask, SIGQUIT);
sigaddset(&mut mask, SIGCHLD);
sigaddset(&mut mask, SIGTSTP);
sigaddset(&mut mask, SIGTTOU);
sigaddset(&mut mask, SIGTTIN);
sigprocmask(
SIG_BLOCK,
&mask as *const libc::sigset_t,
std::ptr::null_mut()
);
}
}
pub fn unign_sigs() {
unsafe {
let mut mask = mem::zeroed();
sigemptyset(&mut mask);
sigaddset(&mut mask, SIGINT);
sigaddset(&mut mask, SIGQUIT);
sigaddset(&mut mask, SIGCHLD);
sigaddset(&mut mask, SIGTSTP);
sigaddset(&mut mask, SIGTTOU);
sigaddset(&mut mask, SIGTTIN);
sigprocmask(
SIG_UNBLOCK,
&mask as *const libc::sigset_t,
std::ptr::null_mut()
);
}
}
// TODO: trigger this on SIGCHLD instead of traditional shell once per input
pub fn check_jobs(state: &mut ShellState) {
let mut idx = 0usize;
while idx < state.children.len() {
if state.children[idx].try_wait().is_ok() {
state.children.remove(idx);
continue;
}
idx += 1;
}
}
pub fn args_from_ast(ast: &Seg, syms: &mut SymTable) -> Vec {
let mut args = vec![];
ast.circuit(&mut |arg_ctr: &Ctr| -> bool {
match arg_ctr {
Ctr::String(s) => args.push(s.clone()) == (),
Ctr::Symbol(ref s) => if !syms.contains_key(s) {
args.push(s.clone()) == ()
} else {
let eval_args = Seg::new();
let eval_res = syms.call_symbol(s, &eval_args, false);
if let Ok(res_ctr) = eval_res {
match *res_ctr {
Ctr::Lambda(_) => false,
Ctr::Seg(_) => false,
Ctr::String(s) => args.push(s) == (),
_ => args.push(res_ctr.to_string()) == (),
}
} else {
eprintln!("couldnt eval args: {}",
eval_res.err().unwrap()) != ()
}
},
Ctr::Seg(ref form) => {
let eval_res = eval(form, syms);
if let Ok(res_ctr) = eval_res {
match *res_ctr {
Ctr::Lambda(_) => false,
Ctr::Seg(_) => false,
Ctr::String(s) => args.push(s) == (),
_ => args.push(res_ctr.to_string()) == (),
}
} else {
eprintln!("couldnt eval args: {}",
eval_res.err().unwrap()) != ()
}
},
Ctr::Lambda(_) => eprintln!("lambda passed as shell parameter") != (),
_ => args.push(arg_ctr.to_string()) == (),
}
});
args
}
fn launch_command(
path: String,
args: &[String],
infd: Stdio,
outfd: Stdio,
errfd: Stdio,
background: bool,
state: &mut ShellState
) -> Result<(), String> {
let newchld: Result;
unsafe {
newchld = Command::new(path)
.pre_exec(move || -> IOResult<()> {
let pid = unistd::getpid();
if unistd::setpgid(pid, pid).is_err() {
// crying would be nice... if you had eyes
}
if !background &&unistd::tcsetpgrp(0, pid).is_err() {
// you wish to scream, but fork() has taken from you your throat
}
unign_sigs();
Ok(())
})
.args(args)
.stdin(infd)
.stdout(outfd)
.stderr(errfd)
.process_group(0)
.spawn();
}
// glibc says to do this in both parent and child
if let Ok(child) = newchld {
let pid = child.id();
_ = unistd::setpgid(
Pid::from_raw(pid as i32),
Pid::from_raw(pid as i32),
); // ignore result
state.children.push(child);
if !background {
make_foreground(pid, state)
} else {
Ok(())
}
} else {
Err(format!("couldnt spawn command: {}", newchld.err().unwrap()))
}
}
// the analogous make_background happens in the app when the user sends it the corresponding signal
fn make_foreground(pid: u32, state: &mut ShellState) -> Result<(), String> {
for i in &mut state.children {
if i.id() == pid {
let pid_fancy = Pid::from_raw(pid as i32);
if let Err(e) = unistd::tcsetpgrp(0, pid_fancy) {
eprintln!("couldn't give child the terminal: {}!", e);
}
nix::sys::signal::kill(
pid_fancy,
nix::sys::signal::Signal::SIGCONT,
).expect(
"couldnt sigcont... better get another tty."
);
loop {
match i.try_wait() {
Ok(maybe) => {
if let Some(exit) = maybe {
if let Err(e) = unistd::tcsetpgrp(0, state.parent_pid) {
return Err(format!(
"error re-acquiring terminal: {}!", e
));
}
// TODO: this could be more elegant
if let Some(ref attr) = state.attr {
tcsetattr(0, SetArg::TCSADRAIN, attr).ok();
} else {
panic!("somehow no attrs")
}
state.last_exit_code = exit
.code()
.unwrap_or(-1);
break;
} else {
// sleep 1 sec
thread::sleep(Duration::from_millis(1000));
}
},
Err(e) => {
return Err(format!("error waiting on child: {}", e))
}
}
}
}
}
if let Some(pos) = state.children.iter().position(|x| x.id() == pid) {
state.children.remove(pos);
Ok(())
} else {
Err(format!("pid not found in active children"))
}
}
const LOAD_DOCSTRING: &str = "Calls a binary off disk with given arguments.
Arguments may be of any type except lambda. If a symbol is not defined it will be passed as a string.
first argument (command name) will be found on path (or an error returned).
examples:call
(l ping google.com)
(let ((ping-count 4))
(l ping -c ping-count google.com))
(l emacs -nw (concat HOME '/.relishrc'))
";
fn load_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result {
if ast.is_empty() {
Err(start_trace(
("load", "expected at least one input")
.into()))
} else {
let mut args = VecDeque::from(args_from_ast(ast, syms));
if args.is_empty() {
Err(start_trace(
("load", "empty command")
.into()))
} else if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
if let Err(e) = launch_command(
filepath,
&Vec::from(args.make_contiguous()),
Stdio::inherit(),
Stdio::inherit(),
Stdio::inherit(),
false,
state,
) {
Err(start_trace(
("load", e)
.into()))
} else {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
} else {
Err(start_trace(
("load", "binary not found on PATH")
.into()))
}
}
}
const PIPE_DOCSTRING: &str = "Calls a sequence of shell commands.
Each one has their output redirected to the input of the next one.
Example:
(pipe
(ls -la)
(grep '.rs')
(tr -d '.rs'))";
fn pipe_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result {
if ast.is_empty() {
return Err(start_trace(
("pipe", "expected at least one input")
.into()))
}
let mut err: String = String::new();
// posix layer will never control pid 0
let mut lastpid = 0;
if !ast.circuit(&mut |arg: &Ctr| -> bool {
if let Ctr::Seg(command_form) = arg {
let mut args = VecDeque::from(args_from_ast(command_form, syms));
if args.is_empty() {
return false
}
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
let mut newchld = Command::new(filepath);
newchld.args(Vec::from(args.make_contiguous()))
.stdout(Stdio::piped())
.process_group(0);
if let Some(pos) = state.children
.iter()
.position(|x| x.id() == lastpid)
{
newchld.stdin(state.children.remove(pos).stdout.take().unwrap());
}
let chld_spwn = newchld.spawn();
if let Ok(child) = chld_spwn {
let pid = child.id();
lastpid = pid;
state.children.push(child);
true
} else {
err = format!("failed to spawn: {}", chld_spwn.err().unwrap());
false
}
} else {
err = "file not found".to_string();
false
}
} else {
err = format!("{} not a shell command", arg);
false
}
}) {
Err(start_trace(
("pipe", err)
.into()))
} else if lastpid > 0 {
if let Some(pos) = state.children
.iter()
.position(|x| x.id() == lastpid)
{
let chld = state.children.remove(pos);
let exit = chld.wait_with_output()
.expect("failed to wait on last child");
state.last_exit_code = exit.status
.code()
.or(Some(-1))
.unwrap();
println!("{}", String::from_utf8_lossy(&exit.stdout));
Ok(Ctr::Integer(state.last_exit_code.into()))
} else {
Err(start_trace(
("pipe", format!("lost last child (pid {})", lastpid))
.into()))
}
} else {
Err(start_trace(
("pipe", "impossible error state".to_string())
.into()))
}
}
const LOAD_TO_STRING_DOCSTRING: &str = "Calls a binary off disk with given arguments.
Arguments may be of any type except lambda. If a symbol is not defined it will be passed as a string.
first argument (command name) will be found on path (or an error returned).
examples:
(l ping google.com)
(let ((ping-count 4))
(l ping -c ping-count google.com))
(l emacs -nw (concat HOME '/.relishrc'))
Unlike with the normal load function, the load to string function collects stdout output and returns it as a string.
";
fn load_to_string_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result {
if ast.is_empty() {
Err(start_trace(
("load-to-string", "expected at least one input")
.into()))
} else {
let mut args = VecDeque::from(args_from_ast(ast, syms));
if args.is_empty() {
Err(start_trace(
("load-to-string", "command is empty")
.into()))
} else if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
let res = Command::new(filepath).args(args).output();
if let Ok(output) = res {
if let Some(code) = output.status.code() {
state.last_exit_code = code;
}
if let Ok(string) = String::from_utf8(output.stdout) {
Ok(Ctr::String(string.trim_end().to_string()))
} else {
Err(start_trace(
("load-to-string", "couldn't marshall utf-8 command output into a string")
.into()))
}
} else {
Err(start_trace(
("load-to-string", format!("{}", res.err().unwrap()))
.into()))
}
} else {
Err(start_trace(
("load-to-string", "binary not found on PATH")
.into()))
}
}
}
// TODO: rework this callback to reduce spaghett
const LOAD_WITH_DOCSTRING: &str = "Takes two arguments.
1. a list of up to three redirects
- A redirect looks like a variable declaration in a let form
Ex: ('stdout' '/dev/null')
- The first value is one of 'stdout', 'stdin', or 'stderr'
- The second value is a filename.
The file must exist for stdin redirects.
For other redirects the file will be created if not existing.
Example: (('stdin' '/path/to/myinput.txt')
('stdout' '/dev/null'))
2. a shell command
- exactly as would be passed to load
Example: (ping -c 4 google.com)
Example invocation:
(load-with (('stdout' '/dev/null'))
(ping -c 4 google.com))";
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result {
if ast.len() != 2 {
Err(start_trace(
("load-with", "expected two inputs")
.into()))
} else {
if let Ctr::Seg(ref fd_redirect_forms) = *ast.car {
let mut stdout: Option = None;
let mut stdin: Option = None;
let mut stderr: Option = None;
let mut e: Option = None;
if !fd_redirect_forms.circuit(&mut |arg: &Ctr| -> bool {
// of the form Seg(Str(fd), Seg(Str(filepath)))
if let Ctr::Seg(ref io_redir) = arg {
if let Ctr::String(ref fd) = *io_redir.car {
if let Ctr::Seg(ref io_file) = *io_redir.cdr {
if let Ctr::String(ref filename) = *io_file.car {
match fd.as_str() {
"stdin" => {
if !stdin.is_none() {
e = Some(format!("stdin already defined"));
return false
}
let input = File::open(filename);
if !input.is_ok() {
e = Some(format!("input file does not exist"));
return false
}
stdin = Some(Stdio::from(input.ok().unwrap()));
true
},
"stdout" => {
if !stdout.is_none() {
e = Some(format!("stdout already defined"));
return false
}
let out = OpenOptions::new()
.write(true)
.create(true)
.open(filename);
if !out.is_ok() {
e = Some(out.unwrap_err().to_string());
return false
}
stdout = Some(Stdio::from(out.ok().unwrap()));
true
},
"stderr" => {
if !stderr.is_none() {
e = Some(format!("stderr already defined"));
return false
}
let err = OpenOptions::new()
.write(true)
.create(true)
.open(filename);
if !err.is_ok() {
e = Some(err.unwrap_err().to_string());
return false
}
stderr = Some(Stdio::from(err.ok().unwrap()));
true
},
_ => {
e = Some(format!("can only override stdin, stdout, or stderr"));
false
}
}
} else {
e = Some(format!("{} is not a string", *io_file.car));
false
}
} else {
e = Some(format!("fd override must have both an fd and a filename"));
false
}
} else {
e = Some(format!("{} is not a string", *io_redir.car));
false
}
} else {
e = Some(format!("not a list: {}", arg));
false
}
}) {
return Err(start_trace(
("load-with", e.unwrap())
.into()))
}
// now deal with the actual command
if let Ctr::Seg(ref command_forms) = *ast.cdr {
let mut args: VecDeque;
if let Ctr::Seg(ref command) = *command_forms.car {
args = VecDeque::from(args_from_ast(command, syms));
} else {
return Err(start_trace(
("load-with", "expected command input to be a list")
.into()))
}
if args.is_empty() {
Err(start_trace(
("load-with", "empty command")
.into()))
} else {
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
if let Err(e) = launch_command(
filepath,
&Vec::from(args.make_contiguous()),
stdin.or(Some(Stdio::inherit())).unwrap(),
stdout.or(Some(Stdio::inherit())).unwrap(),
stderr.or(Some(Stdio::inherit())).unwrap(),
false,
state,
) {
Err(start_trace(
("load-with", e)
.into()))
} else {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
} else {
Err(start_trace(
("load-with", "binary not found on PATH")
.into()))
}
}
} else {
Err(start_trace(
("load-with", "expected second input to be list of command elements")
.into()))
}
} else {
Err(start_trace(
("load-with", "expected first input to be a list of lists")
.into()))
}
}
}
const Q_DOCSTRING: &str = "returns exit code of last process to be run in posix layer";
fn q_callback(_ast: &Seg, _syms: &SymTable, state: &mut ShellState) -> Result {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
const EXIT_DOCSTRING: &str = "Takes on integer input. calls libc exit() with given argument.";
fn exit_callback(ast: &Seg, _syms: &mut SymTable) -> Result {
let mut ret = -1;
if let Ctr::Integer(i) = *ast.car {
ret = i;
} else {
eprintln!("WARNING: input was not an integer. Exiting -1.")
}
unsafe {
exit(ret as i32);
}
}
const BG_DOCSTRING: &str = "Calls a binary off disk with given arguments.
Arguments may be of any type except lambda. If a symbol is not defined it will be passed as a string.
first argument (command name) will be found on path (or an error returned).
The command executed in the background while your shell remains active.
Output from your command may appear in the terminal, but it will not be able to read from the terminal.
examples:
(bg ping -c4 google.com)
(bg vim) ;; vim waits patiently for you to foreground the process with 'fg'
";
fn bg_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result {
if ast.is_empty() {
Err(start_trace(
("bg", "expected at least one input")
.into()))
} else {
let mut args = VecDeque::from(args_from_ast(ast, syms));
if args.is_empty() {
Err(start_trace(
("bg", "empty command")
.into()))
} else if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
if let Err(e) = launch_command(
filepath,
&Vec::from(args.make_contiguous()),
Stdio::inherit(),
Stdio::inherit(),
Stdio::inherit(),
true,
state,
) {
Err(start_trace(
("bg", e)
.into()))
} else {
Ok(Ctr::Integer(state.last_exit_code.into()))
}
} else {
Err(start_trace(
("bg", "binary not found on PATH")
.into()))
}
}
}
const FG_DOCSTRING: &str = "takes one argument (an integer).
Integer is presumed to be a PID of a running background job.
If PID corresponds with a background job, fg will try to foreground it.
If PID is not a current running background job, fg will return an error.
Examples:
(bg vim) (fg 13871)
";
fn fg_callback(
ast: &Seg,
_syms: &mut SymTable,
shell_state: &mut ShellState
) -> Result {
if let Ctr::Integer(i) = *ast.car {
if let Err(e) = make_foreground(i as u32, shell_state) {
Err(start_trace(
("fg", e)
.into()))
} else {
Ok(Ctr::None)
}
} else {
Err(start_trace(
("fg", "expected input to be an integer")
.into()))
}
}
const CD_DOCSTRING: &str =
"Expects 1 argument (a string, symbol, or form). Changes to a directory.
WARNING: Symbols will be taken as strings unless wrapped in a form.
> Example: cd HOME => cd 'HOME'
> Example: cd (eval HOME) => cd /path/to/home/dir
If CFG_RELISH_CD_CB is defined, cd will evaluate it.
cd returns the result that CFG_RELISH_CD_CB returns, or None";
fn cd_callback(ast: &Seg, syms: &mut SymTable) -> Result {
let dir: &String;
match *ast.car {
Ctr::String(ref d) => dir = d,
Ctr::Symbol(ref d) => dir = d,
Ctr::Seg(ref s) => {
match eval(s, syms) {
Ok(r) => {
if r == ast.car {
return Err(start_trace(("cd", "innapropriate input.").into()));
}
return cd_callback(&Seg::from_mono(r), syms);
}
Err(e) => {
return Err(e.with_trace(("cd", "couldnt evaluate input").into()));
}
}
},
_ => return Err(start_trace(
("cd", "expected input to be a string or symbol")
.into()))
}
let dirp = Path::new(dir);
match canonicalize(dirp) {
Ok(abs) => {
if let Err(s) = set_current_dir(dirp) {
Err(start_trace(
("cd", s.to_string())
.into()))
} else {
syms.insert(
String::from("PWD"),
Symbol::from_ast(
&String::from("PWD"),
&String::from("current working directory, set by cd"),
&Seg::from_mono(Box::new(
Ctr::String(abs.to_string_lossy().into()))),
None,
));
if !syms.contains_key(&CD_USER_CB.to_string()) {
return Ok(Ctr::None)
}
match syms.call_symbol(
&CD_USER_CB.to_string(),
&Seg::new(),
true,
) {
Ok(r) => {
Ok(*r)
},
Err(s) => {
Err(s.with_trace(("cd", "failed to call callback").into()))
},
}
}
},
Err(e) => {
Err(
start_trace( ("cd", e.to_string()).into())
.with_trace(("cd", "could not make absolute path").into()))
}
}
}
pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc>) {
let pid = unistd::getpid();
let pgid_res = unistd::getpgid(Some(pid));
let sid_res = unistd::getsid(Some(pid));
if pgid_res.is_err() || sid_res.is_err() {
panic!("couldn't get pgid")
}
let pgid = pgid_res.ok().unwrap();
let sid = sid_res.ok().unwrap();
let shattr: Termios;
// one mut borrow
{
let mut state = shell_state.borrow_mut();
state.parent_pid = pid;
state.parent_sid = sid;
if let Ok(attr) = tcgetattr(0) {
state.attr = Some(attr.clone());
shattr = attr;
} else {
panic!("couldn't get term attrs");
}
}
let term_pgrp_res = unistd::tcgetpgrp(0);
if term_pgrp_res.is_err() {
panic!("couldn't get terminal's pgrp: {:?}", term_pgrp_res)
}
let term_owner = term_pgrp_res.ok().unwrap();
if pgid != term_owner {
nix::sys::signal::kill(
term_owner,
nix::sys::signal::Signal::SIGTTIN,
).expect("couldn't take terminal from owning process")
}
if pid != pgid {
if let Err(e) = unistd::setpgid(pid, pid) {
panic!("couldn't set PGID: {}", e)
}
}
if let Err(e) = unistd::tcsetpgrp(0, pid) {
panic!("couldn't grab terminal: {}", e)
}
// these clones are needed for callbacks which move references
let load_ss = shell_state.clone();
let bg_ss = shell_state.clone();
let q_ss = shell_state.clone();
let lw_ss = shell_state.clone();
let lts_ss = shell_state.clone();
let p_ss = shell_state.clone();
let fg_ss = shell_state.clone();
ign_sigs();
syms.insert(
String::from("l"),
Symbol {
name: String::from(POSIX_LOAD_NAME),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
load_callback(ast, symtable, &mut shell_state.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from(POSIX_LOAD_NAME),
Symbol {
name: String::from(POSIX_LOAD_NAME),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
load_callback(ast, symtable, &mut load_ss.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from("bg"),
Symbol {
name: String::from("background"),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(BG_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
bg_callback(ast, symtable, &mut bg_ss.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from("fg"),
Symbol {
name: String::from("foreground"),
args: Args::Strict(vec![Type::Integer]),
conditional_branches: true,
docs: String::from(FG_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
fg_callback(ast, symtable, &mut fg_ss.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from("?"),
Symbol {
name: String::from("?"),
args: Args::None,
conditional_branches: false,
docs: String::from(Q_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
q_callback(ast, symtable, &mut q_ss.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from("load-with"),
Symbol {
name: String::from("load-with"),
args: Args::Lazy(2),
conditional_branches: true,
docs: String::from(LOAD_WITH_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
load_with_callback(ast, symtable, &mut lw_ss.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from("load-to-string"),
Symbol {
name: String::from("load-to-string"),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_TO_STRING_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
// extra nonsense needed to allow nested calls
load_to_string_callback(
ast,
symtable,
&mut lts_ss
.try_borrow_mut()
.unwrap_or(RefCell::from(ShellState{
parent_pid: pid,
parent_sid: pgid,
children: vec![],
last_exit_code: 0,
attr: Some(shattr.clone()),
}).borrow_mut()))
})),
..Default::default()
},
);
syms.insert(
String::from("pipe"),
Symbol {
name: String::from("pipe"),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(PIPE_DOCSTRING),
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result {
pipe_callback(ast, symtable, &mut p_ss.borrow_mut())
})),
..Default::default()
},
);
syms.insert(
String::from("exit"),
Symbol {
name: String::from("exit"),
args: Args::Strict(vec![Type::Integer]),
conditional_branches: false,
docs: String::from(EXIT_DOCSTRING),
value: ValueType::Internal(Rc::new(exit_callback)),
..Default::default()
},
);
syms.insert(
"cd".to_string(),
Symbol {
name: String::from("cd"),
args: Args::Lazy(1),
conditional_branches: true,
docs: CD_DOCSTRING.to_string(),
value: ValueType::Internal(Rc::new(cd_callback)),
..Default::default()
},
);
}