2023-03-24 18:14:33 -07:00
|
|
|
/* 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};
|
|
|
|
|
use crate::sym::{SymTable, ValueType, Symbol, Args};
|
|
|
|
|
use crate::eval::eval;
|
|
|
|
|
use crate::run;
|
|
|
|
|
use std::collections::VecDeque;
|
|
|
|
|
use std::cell::RefCell;
|
|
|
|
|
use std::rc::Rc;
|
2023-03-28 20:47:55 -07:00
|
|
|
use std::fs::File;
|
2023-03-24 18:14:33 -07:00
|
|
|
use std::process::{Command, Child, Stdio};
|
|
|
|
|
use nix::unistd;
|
|
|
|
|
use ctrlc;
|
2023-03-30 20:02:45 -07:00
|
|
|
use std::fs::OpenOptions;
|
2023-03-24 18:14:33 -07:00
|
|
|
|
|
|
|
|
pub struct ShellState {
|
|
|
|
|
pub parent_pid: unistd::Pid,
|
|
|
|
|
pub parent_pgid: unistd::Pid,
|
|
|
|
|
pub children: Vec<Child>,
|
|
|
|
|
pub last_exit_code: i32,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn args_from_ast(ast: &Seg, syms: &mut SymTable) -> Vec<String> {
|
|
|
|
|
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.clone()) == (),
|
|
|
|
|
_ => args.push(res_ctr.to_string()) == (),
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
eprintln!("couldnt eval args!") != ()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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!") != ()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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 = Command::new(path)
|
|
|
|
|
.args(args)
|
|
|
|
|
.stdin(infd)
|
|
|
|
|
.stdout(outfd)
|
|
|
|
|
.stderr(errfd).spawn();
|
|
|
|
|
|
|
|
|
|
if let Ok(child) = newchld {
|
|
|
|
|
let pid = child.id();
|
|
|
|
|
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 exit = i.wait().unwrap();
|
|
|
|
|
if let Some(code) = exit.code() {
|
|
|
|
|
state.last_exit_code = code;
|
|
|
|
|
} else {
|
|
|
|
|
state.last_exit_code = -1;
|
|
|
|
|
}
|
|
|
|
|
if let Err(e) = unistd::tcsetpgrp(0, state.parent_pgid) {
|
|
|
|
|
return Err(format!("error setting terminal: {}!", e));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(pos) = state.children.iter().position(|x| x.id() == pid) {
|
|
|
|
|
state.children.remove(pos);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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:
|
|
|
|
|
(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<Ctr, String> {
|
|
|
|
|
if ast.is_empty() {
|
|
|
|
|
Err("need at least one argument".to_string())
|
|
|
|
|
} else {
|
|
|
|
|
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
2023-03-28 20:47:55 -07:00
|
|
|
if args.is_empty() {
|
|
|
|
|
Err("empty command".to_string())
|
2023-03-24 18:14:33 -07:00
|
|
|
} else {
|
2023-03-28 20:47:55 -07:00
|
|
|
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
|
|
|
|
launch_command(
|
|
|
|
|
filepath,
|
|
|
|
|
&Vec::from(args.make_contiguous()),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
false,
|
|
|
|
|
state,
|
|
|
|
|
)?;
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
|
|
|
|
} else {
|
|
|
|
|
Err("file not found".to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-05 21:18:40 -07:00
|
|
|
// TODO: maybe flesh out this docstring a bit
|
|
|
|
|
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<Ctr, String> {
|
|
|
|
|
if ast.is_empty() {
|
|
|
|
|
return Err("need at least one argument".to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let mut input = Stdio::inherit();
|
|
|
|
|
if !ast.circuit(&mut |arg: &Ctr| -> bool {
|
|
|
|
|
if let Ctr::Seg(command_form) = arg {
|
|
|
|
|
let args = VecDeque::from(args_from_ast(ast, syms));
|
|
|
|
|
if args.is_empty() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
|
|
|
|
let tmp = Stdio::piped();
|
|
|
|
|
// TODO Youll just have to break open launch_command here
|
|
|
|
|
// youll need seperate calls for .stdin and .stdout anyways
|
|
|
|
|
// and also the move semantics are not working
|
|
|
|
|
launch_command(
|
|
|
|
|
filepath,
|
|
|
|
|
&Vec::from(args.make_contiguous()),
|
|
|
|
|
input,
|
|
|
|
|
Stdio::inherit(),
|
|
|
|
|
tmp,
|
|
|
|
|
false,
|
|
|
|
|
state,
|
|
|
|
|
).unwrap();
|
|
|
|
|
input = tmp;
|
|
|
|
|
true
|
|
|
|
|
} else {
|
|
|
|
|
eprintln!("file not found");
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
false
|
|
|
|
|
}
|
|
|
|
|
}) {
|
|
|
|
|
Err("all arguments must be full shell commands".to_string())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Ctr::None)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-03-30 20:47:22 -07:00
|
|
|
|
|
|
|
|
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<Ctr, String> {
|
|
|
|
|
if ast.is_empty() {
|
|
|
|
|
Err("need at least one argument".to_string())
|
|
|
|
|
} else {
|
|
|
|
|
let mut args = VecDeque::from(args_from_ast(ast, syms));
|
|
|
|
|
if args.is_empty() {
|
|
|
|
|
Err("empty command".to_string())
|
|
|
|
|
} 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))
|
|
|
|
|
} else {
|
|
|
|
|
Err(format!("could'nt marshall utf-8 command output into a string"))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err(format!("{}", res.err().unwrap()))
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err("file not found".to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-28 20:47:55 -07:00
|
|
|
// TODO: rework this callback to reduce spaghett
|
2023-04-05 21:18:40 -07:00
|
|
|
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))";
|
2023-03-28 20:47:55 -07:00
|
|
|
fn load_with_callback(ast: &Seg, syms: &mut SymTable, state: &mut ShellState) -> Result<Ctr, String> {
|
|
|
|
|
if ast.len() != 2 {
|
|
|
|
|
Err("exactly two arguments needed".to_string())
|
|
|
|
|
} else {
|
|
|
|
|
if let Ctr::Seg(ref fd_redirect_forms) = *ast.car {
|
|
|
|
|
let mut stdout: Option<Stdio> = None;
|
|
|
|
|
let mut stdin: Option<Stdio> = None;
|
|
|
|
|
let mut stderr: Option<Stdio> = None;
|
|
|
|
|
let mut e: Option<String> = 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()));
|
|
|
|
|
return true
|
|
|
|
|
},
|
|
|
|
|
"stdout" => {
|
|
|
|
|
if !stdout.is_none() {
|
|
|
|
|
e = Some(format!("stdout already defined"));
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 20:02:45 -07:00
|
|
|
let out = OpenOptions::new()
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.open(filename);
|
2023-03-28 20:47:55 -07:00
|
|
|
if !out.is_ok() {
|
|
|
|
|
e = Some(out.unwrap_err().to_string());
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
stdout = Some(Stdio::from(out.ok().unwrap()));
|
|
|
|
|
return true
|
|
|
|
|
},
|
|
|
|
|
"stderr" => {
|
|
|
|
|
if !stderr.is_none() {
|
|
|
|
|
e = Some(format!("stderr already defined"));
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-30 20:02:45 -07:00
|
|
|
let err = OpenOptions::new()
|
|
|
|
|
.write(true)
|
|
|
|
|
.create(true)
|
|
|
|
|
.open(filename);
|
2023-03-28 20:47:55 -07:00
|
|
|
if !err.is_ok() {
|
|
|
|
|
e = Some(err.unwrap_err().to_string());
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
stderr = Some(Stdio::from(err.ok().unwrap()));
|
|
|
|
|
return 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(e.unwrap())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now deal with the actual command
|
|
|
|
|
if let Ctr::Seg(ref command_forms) = *ast.cdr {
|
|
|
|
|
let mut args: VecDeque<String>;
|
|
|
|
|
if let Ctr::Seg(ref command) = *command_forms.car {
|
|
|
|
|
args = VecDeque::from(args_from_ast(command, syms));
|
|
|
|
|
} else {
|
|
|
|
|
return Err("command not list".to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if args.is_empty() {
|
|
|
|
|
Err("empty command".to_string())
|
|
|
|
|
} else {
|
|
|
|
|
if let Some(filepath) = run::find_on_path(args.pop_front().unwrap()) {
|
|
|
|
|
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,
|
|
|
|
|
)?;
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
|
|
|
|
} else {
|
|
|
|
|
Err("file not found".to_string())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err("second argument expected to be a list of command elements".to_string())
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Err("first argument expected to be a list of lists".to_string())
|
2023-03-24 18:14:33 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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<Ctr, String> {
|
|
|
|
|
Ok(Ctr::Integer(state.last_exit_code.into()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const BG_DOCSTRING: &str = "";
|
|
|
|
|
fn bg_callback(_ast: &Seg, _syms: &mut SymTable, _state: &mut ShellState) -> Result<Ctr, String> {
|
|
|
|
|
unimplemented!()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>>) {
|
|
|
|
|
let pid = unistd::getpid();
|
|
|
|
|
let pgid_res = unistd::getpgid(Some(pid));
|
|
|
|
|
if !pgid_res.is_ok() {
|
|
|
|
|
panic!("couldn't get pgid")
|
|
|
|
|
}
|
|
|
|
|
let pgid = pgid_res.ok().unwrap();
|
|
|
|
|
|
|
|
|
|
// one mut borrow
|
|
|
|
|
{
|
|
|
|
|
let mut state = shell_state.borrow_mut();
|
|
|
|
|
state.parent_pid = pid;
|
|
|
|
|
state.parent_pgid = pgid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let term_pgrp_res = unistd::tcgetpgrp(0);
|
|
|
|
|
if !term_pgrp_res.is_ok() {
|
|
|
|
|
panic!("couldn't get terminal's pgrp")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 let Err(e) = unistd::setpgid(
|
|
|
|
|
unistd::Pid::from_raw(0),
|
|
|
|
|
unistd::Pid::from_raw(0)
|
|
|
|
|
) {
|
|
|
|
|
panic!("couldn't set PGID: {}", e)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
if let Err(e) = unistd::tcsetpgrp(0, pid) {
|
|
|
|
|
panic!("couldn't grab terminal: {}", e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2023-03-27 14:34:07 -07:00
|
|
|
// these clones are needed for callbacks which move references
|
|
|
|
|
let load_ss = shell_state.clone();
|
2023-03-24 18:14:33 -07:00
|
|
|
let bg_ss = shell_state.clone();
|
|
|
|
|
let q_ss = shell_state.clone();
|
2023-03-28 20:47:55 -07:00
|
|
|
let lw_ss = shell_state.clone();
|
2023-03-30 20:47:22 -07:00
|
|
|
let lts_ss = shell_state.clone();
|
2023-03-24 18:14:33 -07:00
|
|
|
|
|
|
|
|
syms.insert(
|
|
|
|
|
String::from("l"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("load"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(LOAD_DOCSTRING),
|
|
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
|
|
|
|
load_callback(ast, symtable, &mut shell_state.borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-27 14:34:07 -07:00
|
|
|
syms.insert(
|
|
|
|
|
String::from("load"),
|
|
|
|
|
Symbol {
|
|
|
|
|
name: String::from("load"),
|
|
|
|
|
args: Args::Infinite,
|
|
|
|
|
conditional_branches: true,
|
|
|
|
|
docs: String::from(LOAD_DOCSTRING),
|
|
|
|
|
value: ValueType::Internal(Rc::new(move |ast: &Seg, symtable: &mut SymTable| -> Result<Ctr, String> {
|
|
|
|
|
load_callback(ast, symtable, &mut load_ss.borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-24 18:14:33 -07:00
|
|
|
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<Ctr, String> {
|
|
|
|
|
bg_callback(ast, symtable, &mut bg_ss.clone().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<Ctr, String> {
|
|
|
|
|
q_callback(ast, symtable, &mut q_ss.clone().borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-28 20:47:55 -07:00
|
|
|
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<Ctr, String> {
|
|
|
|
|
load_with_callback(ast, symtable, &mut lw_ss.clone().borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
2023-03-30 20:47:22 -07:00
|
|
|
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<Ctr, String> {
|
|
|
|
|
load_to_string_callback(ast, symtable, &mut lts_ss.clone().borrow_mut())
|
|
|
|
|
})),
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
2023-03-28 20:47:55 -07:00
|
|
|
|
2023-03-24 18:14:33 -07:00
|
|
|
if let Err(e) = ctrlc::set_handler(move || println!("POSIX layer caught SIG-something")) {
|
|
|
|
|
eprintln!("WARNING: couldn't set sig handler: {}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|