fall back on shell command behaviour for undefined variables

Signed-off-by: Ava Hahn <ava@sunnypup.io>
This commit is contained in:
Ava Apples Affine 2023-10-12 20:50:27 -07:00
parent 0babc1986a
commit c05b94e92a
5 changed files with 84 additions and 15 deletions

View file

@ -22,3 +22,4 @@ phf = { version = "0.11", default-features = false, features = ["macros"] }
[features]
default = ["posix"]
posix = ["dep:nix", "dep:libc"]
implicit-load = ["posix"]

View file

@ -170,3 +170,27 @@ Or:
(pacman-search "xfce4")
#+END_EXAMPLE
* Implicit Load
Typically, any call to an undefined function, lambda, or symbol will result in an undefined symbol error. For example, here is a call to GCC without explicitly prepending the ~load~ function:
#+BEGIN_EXAMPLE
> (gcc myfile.c -o myfile)
** ERROR TRACEBACK
> gdb: (is an undefined symbol)
Please refactor forms and try again...
#+END_EXAMPLE
With the ~implicit load~ feature any call to an undefined function will trigger the ~load~ function as if it had been there the whole time. The above example would yield a new subprocess running GCC (assuming it is installed and accessible). This allows users to quickly type shell commands at the prompt as if they were using a standard shell. Any reference to an undefined symbol outside of where a function would be will still cause an error.
#+BEGIN_EXAMPLE
> ls -la .config
...... (redacted directory list) .....
#+END_EXAMPLE
Implicit load can be used by building relish with the following command:
#+BEGIN_EXAMPLE
cargo build -F implicit-load
#+END_EXAMPLE

View file

@ -57,4 +57,6 @@ pub mod aux {
pub use crate::stl::posix::ShellState;
#[cfg(feature="posix")]
pub use crate::stl::posix::check_jobs;
#[cfg(feature="posix")]
pub use crate::stl::posix::POSIX_LOAD_NAME;
}

View file

@ -60,6 +60,8 @@ use {
},
};
pub const POSIX_LOAD_NAME: &str = "load";
pub struct ShellState {
pub parent_pid: Pid,
pub parent_sid: Pid,
@ -190,7 +192,7 @@ fn launch_command(
.stdin(infd)
.stdout(outfd)
.stderr(errfd)
.process_group(0)
.process_group(0)
.spawn();
}
@ -273,7 +275,7 @@ 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:
examples:call
(l ping google.com)
(let ((ping-count 4))
(l ping -c ping-count google.com))
@ -787,7 +789,7 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
syms.insert(
String::from("l"),
Symbol {
name: String::from("load"),
name: String::from(POSIX_LOAD_NAME),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_DOCSTRING),
@ -799,9 +801,9 @@ pub fn load_posix_shell(syms: &mut SymTable, shell_state: Rc<RefCell<ShellState>
);
syms.insert(
String::from("load"),
String::from(POSIX_LOAD_NAME),
Symbol {
name: String::from("load"),
name: String::from(POSIX_LOAD_NAME),
args: Args::Infinite,
conditional_branches: true,
docs: String::from(LOAD_DOCSTRING),

View file

@ -18,6 +18,7 @@
use crate::eval::eval;
use crate::error::{Traceback, start_trace};
use crate::segment::{Ctr, Seg, Type};
use crate::stl::posix::POSIX_LOAD_NAME;
use std::collections::HashMap;
use std::fmt;
use std::rc::Rc;
@ -129,8 +130,42 @@ impl SymTable {
args: &Seg,
call_func: bool,
) -> Result<Box<Ctr>, Traceback> {
let outer_scope_seg: Seg;
let mut call_args = args;
let mut name_token = name.to_string();
let mut symbol = match self.remove(name) {
Some(s) => s,
/* implicit load:
* on a call to an undefined function
* assume a shell command is being run
*/
None if cfg!(feature="implicit-load")
&& call_func => match self.remove(&POSIX_LOAD_NAME.to_string()) {
Some(s) => {
name_token = String::from(POSIX_LOAD_NAME);
/* highly unfortunate circumstance
* we must now rebuild the original ast
* costs a whole clone of the args
* this feature is non-standard
*/
outer_scope_seg = Seg::from(
Box::from(Ctr::Symbol(name.to_string())),
if let Ctr::None = *args.car {
Box::from(Ctr::None)
} else {
Box::from(Ctr::Seg(args.clone()))
},
);
call_args = &outer_scope_seg;
s
},
None => return Err(
Traceback::new()
.with_trace(("(implicit load)", "(load function not found)").into())
)
},
None => return Err(
Traceback::new()
.with_trace((name, "(is an undefined symbol)").into())
@ -139,7 +174,7 @@ impl SymTable {
// will re-increment when inserted
// but we dont want to increment it
symbol.__generation -= 1;
self.insert(name.to_string(), symbol.clone());
self.insert(name_token, symbol.clone());
if let ValueType::VarForm(ref val) = symbol.value {
match **val {
Ctr::Lambda(ref l) if call_func => {
@ -165,7 +200,7 @@ impl SymTable {
}
}
if call_func {
symbol.call(args, self)
symbol.call(call_args, self)
} else {
// its a function but call_func is off
Ok(Box::new(Ctr::Symbol(name.to_string())))
@ -173,14 +208,19 @@ impl SymTable {
}
pub fn is_function_type(&self, name: &String) -> Option<bool> {
if let ValueType::VarForm(ref val) = self.get(name)?.value {
match **val {
Ctr::Lambda(_) => Some(true),
Ctr::Symbol(ref n) => self.is_function_type(n),
_ => Some(false),
}
} else {
Some(true)
match self.get(name) {
/* implicit-load: assume you just drew load function */
None if cfg!(feature="implicit-load") => Some(true),
Some(value) => if let ValueType::VarForm(ref val) = value.value {
match **val {
Ctr::Lambda(_) => Some(true),
Ctr::Symbol(ref n) => self.is_function_type(n),
_ => Some(false),
}
} else {
Some(true)
},
None => None,
}
}
}