From 6969ea63bcedd391a521a6f4c0143010b1568ad4 Mon Sep 17 00:00:00 2001 From: Ava Hahn Date: Sun, 21 May 2023 23:53:00 +0000 Subject: [PATCH] usability improvements to env and def * env prints variables and functions in seperate columns * run/stl seperation of concerns significantly better * def doesnt store lists or lambdas in the environment --- Cargo.toml | 2 + Readme.org | 10 +++- src/bin/relish.rs | 69 +++++++++++++++++++--- src/lib.rs | 18 +++++- src/run.rs | 108 +--------------------------------- src/stl.rs | 143 +++++++++++++++++++++++++++++++++++++++++++++- src/stl/decl.rs | 90 ++++++++++++++++++++++++----- 7 files changed, 309 insertions(+), 131 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cccb4b9..3338b55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/Readme.org b/Readme.org index 9ffaae5..bd06a70 100644 --- a/Readme.org +++ b/Readme.org @@ -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 diff --git a/src/bin/relish.rs b/src/bin/relish.rs index 4927898..ecbdd0b 100644 --- a/src/bin/relish.rs +++ b/src/bin/relish.rs @@ -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("".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("".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("".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) +} diff --git a/src/lib.rs b/src/lib.rs index b3a29c1..2f5b656 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 { diff --git a/src/run.rs b/src/run.rs index 35a9371..b63f036 100644 --- a/src/run.rs +++ b/src/run.rs @@ -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 { - Ok(Ctr::String(">".to_string())) -} - -fn r_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result { - Ok(Ctr::String(String::new())) -} - -fn prompt_delimiter_default_callback(_: &Seg, _: &mut SymTable) -> Result { - Ok(Ctr::String("λ ".to_string())) -} +use std::env::{var, current_dir}; fn get_paths() -> Vec { Vec::from_iter(var("PATH") @@ -44,95 +31,6 @@ fn get_paths() -> Vec { .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 { let mut prefixes = get_paths(); if let Ok(s) = current_dir() { diff --git a/src/stl.rs b/src/stl.rs index 7aefafd..03a828e 100644 --- a/src/stl.rs +++ b/src/stl.rs @@ -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 { + Ok(Ctr::String(">".to_string())) +} + +fn r_prompt_default_callback(_: &Seg, _: &mut SymTable) -> Result { + Ok(Ctr::String(String::new())) +} + +fn prompt_delimiter_default_callback(_: &Seg, _: &mut SymTable) -> Result { + 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>>) -> 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 Result { + // 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!("{}: ", name), + Type::Seg => token = format!("{}:
", 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 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 {}, + 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) }