Merge branch 'better-env-print' into 'main'

usability improvements to env and def

See merge request whom/relish!2
This commit is contained in:
Ava Apples Affine 2023-05-21 23:53:00 +00:00
commit 91ad4eed12
7 changed files with 309 additions and 131 deletions

View file

@ -16,3 +16,5 @@ nix = "0.26.2"
# string escaping in the lexer
phf = { version = "0.11", default-features = false, features = ["macros"] }
libc = "0.2.144"
# used by main shell to update consol dimensions
termion = "2.0.1"

View file

@ -488,12 +488,18 @@ Note: this section will not show the status of each item unless you are viewing
Note: this section only tracks the state of incomplete TODO items. Having everything on here would be cluttered.
** TODO alpha tasks
- env prints without using columns
- lambdas shouldn't hit the env
- circuit should actually be ~(reduce (lambda (form state) (and state (bool form))) my-args)~
- error display must improve at all costs
- exit function (causes program to shut down and return code)
- probably push the symtable inserts into functions in individual stl modules (like posix.rs does)
- fix the links in the readme like the ones in shell.org
** TODO v1.0 tasks
- Be able to use features to compile without env or posix stuff
- add a new binary target that is a simple posix repl demo
- I think you'll need to refactor userlib to hide set behind a conditional
based on whether CFG_RELISH_POSIX is set or not
- add a compilation task to CI
- finish basic goals in file:snippets/interactive-devel.rls
- Investigate has_next member function for &Seg and maybe simplify stdlib and probably also eval/sym
- Rename to Flesh

View file

@ -19,11 +19,13 @@ use {
relish::{
ast::{
eval, lex, run,
Ctr, Seg, SymTable,
load_defaults, load_environment
Ctr, Seg, SymTable, Symbol,
},
stdlib::{
static_stdlib, dynamic_stdlib
static_stdlib, dynamic_stdlib, load_defaults,
load_environment,
CONSOLE_XDIM_VNAME, CONSOLE_YDIM_VNAME, CFG_FILE_VNAME,
L_PROMPT_VNAME, R_PROMPT_VNAME, PROMPT_DELIM_VNAME,
},
aux::{ShellState, check_jobs},
},
@ -46,6 +48,7 @@ use {
nix::unistd,
nu_ansi_term::{Color, Style},
dirs::home_dir,
termion::terminal_size,
};
#[derive(Clone)]
@ -252,7 +255,7 @@ fn main() {
// this is a user shell. attempt to load configuration
{
// scope the below borrow of syms
let cfg_file = env::var("RELISH_CFG_FILE").unwrap_or(cfg_file_name);
let cfg_file = env::var(CFG_FILE_VNAME).unwrap_or(cfg_file_name);
run(cfg_file.clone(), &mut syms)
.unwrap_or_else(|err: String| eprintln!("failed to load script {}\n{}", cfg_file, err));
}
@ -284,8 +287,11 @@ fn main() {
.with_style(Style::new().italic().fg(Color::LightGray)),
)).with_validator(Box::new(DefaultValidator));
let mut xdimension: u16 = 0;
let mut ydimension: u16 = 0;
loop {
{
// update state
check_jobs(&mut prompt_ss.borrow_mut());
}
let readline_prompt = make_prompt(&mut syms);
@ -293,6 +299,11 @@ fn main() {
// realloc with each loop because syms can change
rl = rl.with_completer(Box::new(completer));
let user_doc = rl.read_line(&readline_prompt).unwrap();
// doing this update here prevents needing to update twice before dimensions take effect
(xdimension, ydimension) =
check_and_update_console_dimensions(&mut syms, xdimension, ydimension);
match user_doc {
Signal::Success(line) => {
println!(""); // add a new line before output gets printed
@ -320,19 +331,19 @@ fn main() {
fn make_prompt(syms: &mut SymTable) -> CustomPrompt {
let l_ctr = *syms
.call_symbol(&"CFG_RELISH_L_PROMPT".to_string(), &Seg::new(), true)
.call_symbol(&L_PROMPT_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|err: String| {
eprintln!("{}", err);
Box::new(Ctr::String("<prompt broken!>".to_string()))
});
let r_ctr = *syms
.call_symbol(&"CFG_RELISH_R_PROMPT".to_string(), &Seg::new(), true)
.call_symbol(&R_PROMPT_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|err: String| {
eprintln!("{}", err);
Box::new(Ctr::String("<prompt broken!>".to_string()))
});
let d_ctr = *syms
.call_symbol(&"CFG_RELISH_PROMPT_DELIMITER".to_string(), &Seg::new(), true)
.call_symbol(&PROMPT_DELIM_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|err: String| {
eprintln!("{}", err);
Box::new(Ctr::String("<prompt broken!>".to_string()))
@ -397,3 +408,47 @@ fn get_token_to_complete(line: &str, pos: usize) -> (String, bool, usize) {
return (res, is_str, start_idx)
}
/* It was considered that a SIGWINCH handler would be a more
* elegant solution to this. Such a solution would need to
* modify the current libc based approach to use a state-enclosing
* closure as a signal handler. As of May 2023 there is no clear
* way of doing such a thing.
*
* Luckily this data is only used within Relish, so we can simply
* rely on it only being read during the evaluation phase of the REPL.
* This method will at least work for that. (ava)
*/
fn check_and_update_console_dimensions(
syms: &mut SymTable,
last_xdim: u16,
last_ydim: u16
) -> (u16, u16) {
let (xdim, ydim) = terminal_size().expect("Couldnt get console dimensions");
if xdim != last_xdim {
syms.insert(
String::from(CONSOLE_XDIM_VNAME),
Symbol::from_ast(
&String::from(CONSOLE_XDIM_VNAME),
&String::from("Length of current console"),
&Seg::from_mono(Box::new(Ctr::Integer(xdim.into()))),
None,
)
);
}
if ydim != last_ydim {
syms.insert(
String::from(CONSOLE_YDIM_VNAME),
Symbol::from_ast(
&String::from(CONSOLE_YDIM_VNAME),
&String::from("Height of current console"),
&Seg::from_mono(Box::new(Ctr::Integer(ydim.into()))),
None,
)
);
}
(xdim, ydim)
}

View file

@ -23,7 +23,7 @@ mod stl;
mod sym;
pub mod ast {
pub use crate::run::{run, load_defaults, load_environment};
pub use crate::run::run;
pub use crate::eval::eval;
pub use crate::lex::lex;
pub use crate::segment::{Ctr, Seg, Type};
@ -31,7 +31,21 @@ pub mod ast {
}
pub mod stdlib {
pub use crate::stl::{dynamic_stdlib, static_stdlib};
pub use crate::stl::{};
pub use crate::stl::{
dynamic_stdlib, static_stdlib,
load_defaults, load_environment,
CONSOLE_XDIM_VNAME,
CONSOLE_YDIM_VNAME,
POSIX_CFG_VNAME,
MODENV_CFG_VNAME,
L_PROMPT_VNAME,
R_PROMPT_VNAME,
PROMPT_DELIM_VNAME,
CFG_FILE_VNAME,
RELISH_DEFAULT_CONS_HEIGHT,
RELISH_DEFAULT_CONS_WIDTH,
};
}
pub mod aux {

View file

@ -18,24 +18,11 @@
use crate::eval::eval;
use crate::lex::lex;
use crate::segment::{Ctr, Seg};
use crate::sym::{Args, SymTable, Symbol, ValueType};
use std::path::{Path};
use crate::sym::SymTable;
use std::path::Path;
use std::fs;
use std::iter::FromIterator;
use std::env::{vars, var, current_dir};
use std::rc::Rc;
fn l_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::String(">".to_string()))
}
fn r_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::String(String::new()))
}
fn prompt_delimiter_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::String("λ ".to_string()))
}
use std::env::{var, current_dir};
fn get_paths() -> Vec<String> {
Vec::from_iter(var("PATH")
@ -44,95 +31,6 @@ fn get_paths() -> Vec<String> {
.map(String::from))
}
pub fn load_defaults(syms: &mut SymTable) {
syms.insert(
"CFG_RELISH_POSIX".to_string(),
Symbol {
name: String::from("CFG_RELISH_POSIX"),
args: Args::None,
conditional_branches: false,
docs: "variable holding whether or not POSIX job control functions are to be loaded.
checked at shell startup by configuration daemon. not used afterwards.
default value: false".to_string(),
value: ValueType::VarForm(Box::new(Ctr::Bool(false))),
..Default::default()
},
);
syms.insert(
"CFG_RELISH_ENV".to_string(),
Symbol {
name: String::from("CFG_RELISH_ENV"),
args: Args::None,
conditional_branches: false,
docs: "variable holding whether or not vars and other symbols should be linked to process environment variables.
If set/defined all calls to def will result in additions or subtractions from user environment variables.
checked at shell startup by configuration daemon. not used afterwards.
default value: 1 (set)
".to_string(),
value: ValueType::VarForm(Box::new(Ctr::Bool(true))),
..Default::default()
},
);
syms.insert(
"CFG_RELISH_L_PROMPT".to_string(),
Symbol {
name: String::from("default relish left prompt"),
args: Args::None,
conditional_branches: false,
docs: "function called to output prompt on left hand. this function is called with no arguments."
.to_string(),
value: ValueType::Internal(Rc::new(l_prompt_default_callback)),
..Default::default()
},
);
syms.insert(
"CFG_RELISH_R_PROMPT".to_string(),
Symbol {
name: String::from("default relish right prompt"),
args: Args::None,
conditional_branches: false,
docs: "function called to output prompt on right hand. this function is called with no arguments."
.to_string(),
value: ValueType::Internal(Rc::new(r_prompt_default_callback)),
..Default::default()
},
);
syms.insert(
"CFG_RELISH_PROMPT_DELIMITER".to_string(),
Symbol {
name: String::from("default relish prompt delimiter"),
args: Args::None,
conditional_branches: false,
docs: "function called to output prompt delimiter. this function is called with no arguments."
.to_string(),
value: ValueType::Internal(Rc::new(prompt_delimiter_default_callback)),
..Default::default()
},
);
}
pub fn load_environment(syms: &mut SymTable) {
for (key, value) in vars() {
syms.insert(
key.clone(),
Symbol{
name: key,
args: Args::None,
conditional_branches: false,
docs: String::from("from env vars at time of load"),
value: ValueType::VarForm(Box::new(Ctr::String(value))),
..Default::default()
}
);
}
}
pub fn find_on_path(filename: String) -> Option<String> {
let mut prefixes = get_paths();
if let Ok(s) = current_dir() {

View file

@ -20,6 +20,7 @@ use crate::run::{run_callback, RUN_DOCSTRING};
use crate::sym::{Args, SymTable, Symbol, ValueType};
use std::rc::Rc;
use std::cell::RefCell;
use std::env::vars;
pub mod posix;
pub mod append;
@ -29,6 +30,29 @@ pub mod decl;
pub mod math;
pub mod strings;
pub const CONSOLE_XDIM_VNAME: &str = "_RELISH_WIDTH";
pub const CONSOLE_YDIM_VNAME: &str = "_RELISH_HEIGHT";
pub const POSIX_CFG_VNAME: &str = "CFG_RELISH_POSIX";
pub const MODENV_CFG_VNAME: &str = "CFG_RELISH_ENV";
pub const L_PROMPT_VNAME: &str = "CFG_RELISH_L_PROMPT";
pub const R_PROMPT_VNAME: &str = "CFG_RELISH_R_PROMPT";
pub const PROMPT_DELIM_VNAME: &str = "CFG_RELISH_PROMPT_DELIMITER";
pub const CFG_FILE_VNAME: &str = "RELISH_CFG_FILE";
pub const RELISH_DEFAULT_CONS_HEIGHT: i16 = 24;
pub const RELISH_DEFAULT_CONS_WIDTH: i16 = 80;
fn l_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::String(">".to_string()))
}
fn r_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::String(String::new()))
}
fn prompt_delimiter_default_callback(_: &Seg, _: &mut SymTable) -> Result<Ctr, String> {
Ok(Ctr::String("λ ".to_string()))
}
/// static_stdlib
/// inserts all stdlib functions that can be inserted without
/// any kind of further configuration data into a symtable
@ -618,11 +642,12 @@ pub fn static_stdlib(syms: &mut SymTable) -> Result<(), String> {
pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::ShellState>>>) -> Result<(), String> {
//get CFG_RELISH_ENV from syms
let env_cfg_user_form = syms
.call_symbol(&"CFG_RELISH_ENV".to_string(), &Seg::new(), true)
.call_symbol(&MODENV_CFG_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None))
.to_string()
.eq("true");
// this also depends on the value of CFG_RELISH_ENV
syms.insert(
"def".to_string(),
Symbol {
@ -639,9 +664,10 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
},
);
// This should be replaced by actual compiler conditionals in the future
if let Some(shell_state) = shell {
let posix_cfg_user_form = syms
.call_symbol(&"CFG_RELISH_POSIX".to_string(), &Seg::new(), true)
.call_symbol(&POSIX_CFG_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None))
.to_string()
.eq("true");
@ -664,3 +690,116 @@ pub fn dynamic_stdlib(syms: &mut SymTable, shell: Option<Rc<RefCell<posix::Shell
Ok(())
}
pub fn load_environment(syms: &mut SymTable) {
for (key, value) in vars() {
syms.insert(
key.clone(),
Symbol{
name: key,
args: Args::None,
conditional_branches: false,
docs: String::from("from env vars at time of load"),
value: ValueType::VarForm(Box::new(Ctr::String(value))),
..Default::default()
}
);
}
}
pub fn load_defaults(syms: &mut SymTable) {
syms.insert(
POSIX_CFG_VNAME.to_string(),
Symbol {
name: String::from(POSIX_CFG_VNAME),
args: Args::None,
conditional_branches: false,
docs: "variable holding whether or not POSIX job control functions are to be loaded.
checked at shell startup by configuration daemon. not used afterwards.
default value: false".to_string(),
value: ValueType::VarForm(Box::new(Ctr::Bool(false))),
..Default::default()
},
);
syms.insert(
MODENV_CFG_VNAME.to_string(),
Symbol {
name: String::from(MODENV_CFG_VNAME),
args: Args::None,
conditional_branches: false,
docs: "variable holding whether or not vars and other symbols should be linked to process environment variables.
If set/defined all calls to def will result in additions or subtractions from user environment variables.
checked at shell startup by configuration daemon. not used afterwards.
default value: 1 (set)
".to_string(),
value: ValueType::VarForm(Box::new(Ctr::Bool(true))),
..Default::default()
},
);
syms.insert(
L_PROMPT_VNAME.to_string(),
Symbol {
name: String::from(L_PROMPT_VNAME),
args: Args::None,
conditional_branches: false,
docs: "function called to output prompt on left hand. this function is called with no arguments."
.to_string(),
value: ValueType::Internal(Rc::new(l_prompt_default_callback)),
..Default::default()
},
);
syms.insert(
R_PROMPT_VNAME.to_string(),
Symbol {
name: String::from(R_PROMPT_VNAME),
args: Args::None,
conditional_branches: false,
docs: "function called to output prompt on right hand. this function is called with no arguments."
.to_string(),
value: ValueType::Internal(Rc::new(r_prompt_default_callback)),
..Default::default()
},
);
syms.insert(
PROMPT_DELIM_VNAME.to_string(),
Symbol {
name: String::from(PROMPT_DELIM_VNAME),
args: Args::None,
conditional_branches: false,
docs: "function called to output prompt delimiter. this function is called with no arguments."
.to_string(),
value: ValueType::Internal(Rc::new(prompt_delimiter_default_callback)),
..Default::default()
},
);
syms.insert(
String::from(CONSOLE_XDIM_VNAME),
Symbol::from_ast(
&String::from(CONSOLE_XDIM_VNAME),
&String::from("Length of current console"),
&Seg::from_mono(Box::new(
Ctr::Integer(RELISH_DEFAULT_CONS_WIDTH.into())
)),
None,
)
);
syms.insert(
String::from(CONSOLE_YDIM_VNAME),
Symbol::from_ast(
&String::from(CONSOLE_YDIM_VNAME),
&String::from("Height of current console"),
&Seg::from_mono(Box::new(
Ctr::Integer(RELISH_DEFAULT_CONS_HEIGHT.into())
)),
None,
)
);
}

View file

@ -16,7 +16,8 @@
*/
use crate::eval::eval;
use crate::segment::{Ctr, Seg};
use crate::segment::{Ctr, Seg, Type};
use crate::stdlib::{CONSOLE_XDIM_VNAME, RELISH_DEFAULT_CONS_WIDTH};
use crate::sym::{SymTable, Symbol, UserFn, ValueType};
use std::env;
@ -129,19 +130,76 @@ pub const ENV_DOCSTRING: &str = "takes no arguments
prints out all available symbols and their associated values";
pub fn env_callback(_ast: &Seg, syms: &mut SymTable) -> Result<Ctr, String> {
// get width of current output
let xdim: i128;
if let Ctr::Integer(dim) = *syms
.call_symbol(&CONSOLE_XDIM_VNAME.to_string(), &Seg::new(), true)
.unwrap_or_else(|_: String| Box::new(Ctr::None)) {
xdim = dim;
} else {
println!("{} contains non integer value, defaulting to {}",
CONSOLE_XDIM_VNAME, RELISH_DEFAULT_CONS_WIDTH);
xdim = RELISH_DEFAULT_CONS_WIDTH as i128;
}
let mut v_col_len = 0;
let mut f_col_len = 0;
let mut functions = vec![];
println!("VARIABLES:");
let mut variables = vec![];
for (name, val) in syms.iter() {
match val.value {
ValueType::VarForm(_) => {
println!(" {}: {}", &name, val.value);
if let ValueType::VarForm(l) = &val.value {
let token: String;
match l.to_type() {
Type::Lambda => token = format!("{}: <lambda>", name),
Type::Seg => token = format!("{}: <form>", name),
_ => token = format!("{}: {}", name, val.value.to_string()),
}
_ => functions.push(name.clone()),
if token.len() > v_col_len && token.len() < xdim as usize {
v_col_len = token.len();
}
variables.push(token);
} else {
if f_col_len < name.len() && name.len() < xdim as usize {
f_col_len = name.len();
}
functions.push(name.clone());
}
}
println!("FUNCTIONS:");
let mut n_v_cols = xdim / v_col_len as i128;
// now decrement to make sure theres room for two spaces of padding
while n_v_cols > 1 && xdim % (v_col_len as i128) < (2 * n_v_cols) {
n_v_cols -= 1;
}
// again for functions
let mut n_f_cols = xdim / f_col_len as i128;
while n_f_cols > 1 && xdim & (f_col_len as i128) < (2 * n_f_cols) {
n_f_cols -= 1;
}
let mut col_iter = 0;
println!("VARIABLES:");
for var in variables {
print!("{:v_col_len$}", var);
col_iter += 1;
if col_iter % n_v_cols == 0 {
print!("\n");
} else {
print!(" ");
}
}
println!("\nFUNCTIONS:");
col_iter = 0;
for func in functions {
println!(" {}", func);
print!("{:f_col_len$}", func);
col_iter += 1;
if col_iter % n_f_cols == 0 {
print!("\n");
} else {
print!(" ");
}
}
Ok(Ctr::None)
}
@ -335,7 +393,7 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
var_val_form = &outer_scope_val;
},
_ if !is_var => return Err("arg list must at least be a list".to_string()),
_ => unimplemented!(), // rustc is haunted
_ => unimplemented!(), // rustc is haunted and cursed
}
if is_var {
@ -353,11 +411,17 @@ pub fn store_callback(ast: &Seg, syms: &mut SymTable, env_cfg: bool) -> Result<C
Symbol::from_ast(&name, &docs, &outer_seg, None),
);
if env_cfg {
let mut s = var_val.to_string();
if let Ctr::String(tok) = var_val {
s = tok;
match var_val.to_type() {
Type::Lambda => {},
Type::Seg => {},
_ => {
let mut s = var_val.to_string();
if let Ctr::String(tok) = var_val {
s = tok;
}
env::set_var(name.clone(), s);
}
}
env::set_var(name.clone(), s);
}
return Ok(Ctr::None)
}