began work on implementing those weird variable string op things
This commit is contained in:
257
src/expand.rs
257
src/expand.rs
@@ -1,6 +1,9 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use crate::state::{read_vars, write_meta, LogTab};
|
use glob::Pattern;
|
||||||
|
|
||||||
|
use crate::state::{read_vars, write_meta, write_vars, LogTab};
|
||||||
use crate::procio::{IoBuf, IoFrame, IoMode};
|
use crate::procio::{IoBuf, IoFrame, IoMode};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::parse::{Redir, RedirType};
|
use crate::parse::{Redir, RedirType};
|
||||||
@@ -122,7 +125,7 @@ impl Expander {
|
|||||||
}
|
}
|
||||||
'{' if var_name.is_empty() => in_brace = true,
|
'{' if var_name.is_empty() => in_brace = true,
|
||||||
'}' if in_brace => {
|
'}' if in_brace => {
|
||||||
let var_val = read_vars(|v| v.get_var(&var_name));
|
let var_val = perform_param_expansion(&var_name)?;
|
||||||
result.push_str(&var_val);
|
result.push_str(&var_val);
|
||||||
var_name.clear();
|
var_name.clear();
|
||||||
break
|
break
|
||||||
@@ -321,6 +324,256 @@ pub fn unescape_str(raw: &str) -> String {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum ParamExp {
|
||||||
|
Len, // #var_name
|
||||||
|
DefaultUnsetOrNull(String), // :-
|
||||||
|
DefaultUnset(String), // -
|
||||||
|
SetDefaultUnsetOrNull(String), // :=
|
||||||
|
SetDefaultUnset(String), // =
|
||||||
|
AltSetNotNull(String), // :+
|
||||||
|
AltNotNull(String), // +
|
||||||
|
ErrUnsetOrNull(String), // :?
|
||||||
|
ErrUnset(String), // ?
|
||||||
|
Substr(usize), // :pos
|
||||||
|
SubstrLen(usize,usize), // :pos:len
|
||||||
|
RemShortestPrefix(String), // #pattern
|
||||||
|
RemLongestPrefix(String), // ##pattern
|
||||||
|
RemShortestSuffix(String), // %pattern
|
||||||
|
RemLongestSuffix(String), // %%pattern
|
||||||
|
ReplaceFirstMatch(String,String), // /search/replace
|
||||||
|
ReplaceAllMatches(String,String), // //search/replace
|
||||||
|
ReplacePrefix(String,String), // #search/replace
|
||||||
|
ReplaceSuffix(String,String), // %search/replace
|
||||||
|
VarNamesWithSuffix(String), // !prefix@ || !prefix*
|
||||||
|
ExpandInnerVar(String), // !var
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ParamExp {
|
||||||
|
type Err = ShErr;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
use ParamExp::*;
|
||||||
|
|
||||||
|
let parse_err = || Err(ShErr::Simple {
|
||||||
|
kind: ShErrKind::SyntaxErr,
|
||||||
|
msg: "Invalid parameter expansion".into(),
|
||||||
|
notes: vec![],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle indirect var expansion: ${!var}
|
||||||
|
if let Some(var) = s.strip_prefix('!') {
|
||||||
|
if var.ends_with('*') || var.ends_with('@') {
|
||||||
|
return Ok(VarNamesWithSuffix(var.to_string()));
|
||||||
|
}
|
||||||
|
return Ok(ExpandInnerVar(var.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pattern removals
|
||||||
|
if let Some(rest) = s.strip_prefix("##") {
|
||||||
|
return Ok(RemLongestPrefix(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix('#') {
|
||||||
|
return Ok(RemShortestPrefix(rest.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(rest) = s.strip_prefix("%%") {
|
||||||
|
return Ok(RemLongestSuffix(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix('%') {
|
||||||
|
return Ok(RemShortestSuffix(rest.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replacements
|
||||||
|
if let Some(rest) = s.strip_prefix("//") {
|
||||||
|
let mut parts = rest.splitn(2, '/');
|
||||||
|
let pattern = parts.next().unwrap_or("");
|
||||||
|
let repl = parts.next().unwrap_or("");
|
||||||
|
return Ok(ReplaceAllMatches(pattern.to_string(), repl.to_string()));
|
||||||
|
}
|
||||||
|
if let Some(rest) = s.strip_prefix('/') {
|
||||||
|
let mut parts = rest.splitn(2, '/');
|
||||||
|
let pattern = parts.next().unwrap_or("");
|
||||||
|
let repl = parts.next().unwrap_or("");
|
||||||
|
return Ok(ReplaceFirstMatch(pattern.to_string(), repl.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback / assignment / alt
|
||||||
|
if let Some(rest) = s.strip_prefix(":-") {
|
||||||
|
return Ok(DefaultUnsetOrNull(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix('-') {
|
||||||
|
return Ok(DefaultUnset(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix(":+") {
|
||||||
|
return Ok(AltSetNotNull(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix('+') {
|
||||||
|
return Ok(AltNotNull(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix(":=") {
|
||||||
|
return Ok(SetDefaultUnsetOrNull(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix('=') {
|
||||||
|
return Ok(SetDefaultUnset(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix(":?") {
|
||||||
|
return Ok(ErrUnsetOrNull(rest.to_string()));
|
||||||
|
} else if let Some(rest) = s.strip_prefix('?') {
|
||||||
|
return Ok(ErrUnset(rest.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substring
|
||||||
|
if let Some((pos, len)) = parse_pos_len(s) {
|
||||||
|
return Ok(match len {
|
||||||
|
Some(l) => SubstrLen(pos, l),
|
||||||
|
None => Substr(pos),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_pos_len(s: &str) -> Option<(usize, Option<usize>)> {
|
||||||
|
let raw = s.strip_prefix(':')?;
|
||||||
|
if let Some((start,len)) = raw.split_once(':') {
|
||||||
|
Some((
|
||||||
|
start.parse::<usize>().ok()?,
|
||||||
|
len.parse::<usize>().ok(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Some((
|
||||||
|
raw.parse::<usize>().ok()?,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn perform_param_expansion(raw: &str) -> ShResult<String> {
|
||||||
|
let vars = read_vars(|v| v.clone());
|
||||||
|
let mut chars = raw.chars();
|
||||||
|
let mut var_name = String::new();
|
||||||
|
let mut rest = String::new();
|
||||||
|
if raw.starts_with('#') {
|
||||||
|
return Ok(vars.get_var(raw.strip_prefix('#').unwrap()).len().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'!' |
|
||||||
|
'#' |
|
||||||
|
'%' |
|
||||||
|
':' |
|
||||||
|
'-' |
|
||||||
|
'+' |
|
||||||
|
'=' |
|
||||||
|
'?' => {
|
||||||
|
rest = chars.collect();
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ => var_name.push(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(expansion) = rest.parse::<ParamExp>() {
|
||||||
|
match expansion {
|
||||||
|
ParamExp::Len => unreachable!(),
|
||||||
|
ParamExp::DefaultUnsetOrNull(default) => {
|
||||||
|
if !vars.var_exists(&var_name) || vars.get_var(&var_name).is_empty() {
|
||||||
|
Ok(default)
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::DefaultUnset(default) => {
|
||||||
|
if !vars.var_exists(&var_name) {
|
||||||
|
Ok(default)
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::SetDefaultUnsetOrNull(default) => {
|
||||||
|
if !vars.var_exists(&var_name) || vars.get_var(&var_name).is_empty() {
|
||||||
|
write_vars(|v| v.set_var(&var_name, &default, false));
|
||||||
|
Ok(default)
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::SetDefaultUnset(default) => {
|
||||||
|
if !vars.var_exists(&var_name) {
|
||||||
|
write_vars(|v| v.set_var(&var_name, &default, false));
|
||||||
|
Ok(default)
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::AltSetNotNull(alt) => {
|
||||||
|
if vars.var_exists(&var_name) && !vars.get_var(&var_name).is_empty() {
|
||||||
|
Ok(alt)
|
||||||
|
} else {
|
||||||
|
Ok("".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::AltNotNull(alt) => {
|
||||||
|
if vars.var_exists(&var_name) {
|
||||||
|
Ok(alt)
|
||||||
|
} else {
|
||||||
|
Ok("".into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::ErrUnsetOrNull(err) => {
|
||||||
|
if !vars.var_exists(&var_name) || vars.get_var(&var_name).is_empty() {
|
||||||
|
Err(
|
||||||
|
ShErr::Simple { kind: ShErrKind::ExecFail, msg: err, notes: vec![] }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::ErrUnset(err) => {
|
||||||
|
if !vars.var_exists(&var_name) {
|
||||||
|
Err(
|
||||||
|
ShErr::Simple { kind: ShErrKind::ExecFail, msg: err, notes: vec![] }
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::Substr(pos) => {
|
||||||
|
let value = vars.get_var(&var_name);
|
||||||
|
if let Some(substr) = value.get(pos..) {
|
||||||
|
Ok(substr.to_string())
|
||||||
|
} else {
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::SubstrLen(pos, len) => {
|
||||||
|
let value = vars.get_var(&var_name);
|
||||||
|
let end = pos.saturating_add(len);
|
||||||
|
if let Some(substr) = value.get(pos..end) {
|
||||||
|
Ok(substr.to_string())
|
||||||
|
} else {
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ParamExp::RemShortestPrefix(prefix) => {
|
||||||
|
let value = vars.get_var(&var_name);
|
||||||
|
let pattern = Pattern::new(&prefix).unwrap();
|
||||||
|
for i in 0..=value.len() {
|
||||||
|
let sliced = &value[..i];
|
||||||
|
if pattern.matches(sliced) {
|
||||||
|
return Ok(value[i..].to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
ParamExp::RemLongestPrefix(prefix) => todo!(),
|
||||||
|
ParamExp::RemShortestSuffix(suffix) => todo!(),
|
||||||
|
ParamExp::RemLongestSuffix(suffix) => todo!(),
|
||||||
|
ParamExp::ReplaceFirstMatch(search, replace) => todo!(),
|
||||||
|
ParamExp::ReplaceAllMatches(search, replace) => todo!(),
|
||||||
|
ParamExp::ReplacePrefix(search, replace) => todo!(),
|
||||||
|
ParamExp::ReplaceSuffix(search, replace) => todo!(),
|
||||||
|
ParamExp::VarNamesWithSuffix(suffix) => todo!(),
|
||||||
|
ParamExp::ExpandInnerVar(var_name) => todo!(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(vars.get_var(&var_name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum PromptTk {
|
pub enum PromptTk {
|
||||||
AsciiOct(i32),
|
AsciiOct(i32),
|
||||||
|
|||||||
57
src/state.rs
57
src/state.rs
@@ -2,7 +2,7 @@ use std::{collections::{HashMap, VecDeque}, ops::Deref, sync::{LazyLock, RwLock,
|
|||||||
|
|
||||||
use nix::unistd::{gethostname, getppid, User};
|
use nix::unistd::{gethostname, getppid, User};
|
||||||
|
|
||||||
use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{lex::get_char, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts};
|
use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, ShResult}, utils::VecDequeExt}, parse::{ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*, shopt::ShOpts};
|
||||||
|
|
||||||
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
pub static JOB_TABLE: LazyLock<RwLock<JobTab>> = LazyLock::new(|| RwLock::new(JobTab::new()));
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ impl Deref for Var {
|
|||||||
#[derive(Default,Clone,Debug)]
|
#[derive(Default,Clone,Debug)]
|
||||||
pub struct VarTab {
|
pub struct VarTab {
|
||||||
vars: HashMap<String,Var>,
|
vars: HashMap<String,Var>,
|
||||||
params: HashMap<char,String>,
|
params: HashMap<String,String>,
|
||||||
sh_argv: VecDeque<String>, // Using a VecDeque makes the implementation of `shift` straightforward
|
sh_argv: VecDeque<String>, // Using a VecDeque makes the implementation of `shift` straightforward
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,13 +131,13 @@ impl VarTab {
|
|||||||
var_tab.init_sh_argv();
|
var_tab.init_sh_argv();
|
||||||
var_tab
|
var_tab
|
||||||
}
|
}
|
||||||
fn init_params() -> HashMap<char, String> {
|
fn init_params() -> HashMap<String, String> {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert('?', "0".into()); // Last command exit status
|
params.insert("?".into(), "0".into()); // Last command exit status
|
||||||
params.insert('#', "0".into()); // Number of positional parameters
|
params.insert("#".into(), "0".into()); // Number of positional parameters
|
||||||
params.insert('0', std::env::current_exe().unwrap().to_str().unwrap().to_string()); // Name of the shell
|
params.insert("0".into(), std::env::current_exe().unwrap().to_str().unwrap().to_string()); // Name of the shell
|
||||||
params.insert('$', Pid::this().to_string()); // PID of the shell
|
params.insert("$".into(), Pid::this().to_string()); // PID of the shell
|
||||||
params.insert('!', "".into()); // PID of the last background job (if any)
|
params.insert("!".into(), "".into()); // PID of the last background job (if any)
|
||||||
params
|
params
|
||||||
}
|
}
|
||||||
fn init_env() {
|
fn init_env() {
|
||||||
@@ -214,8 +214,8 @@ impl VarTab {
|
|||||||
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
self.bpush_arg(env::current_exe().unwrap().to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
fn update_arg_params(&mut self) {
|
fn update_arg_params(&mut self) {
|
||||||
self.set_param('@', &self.sh_argv.clone().to_vec()[1..].join(" "));
|
self.set_param("@", &self.sh_argv.clone().to_vec()[1..].join(" "));
|
||||||
self.set_param('#', &(self.sh_argv.len() - 1).to_string());
|
self.set_param("#", &(self.sh_argv.len() - 1).to_string());
|
||||||
}
|
}
|
||||||
/// Push an arg to the front of the arg deque
|
/// Push an arg to the front of the arg deque
|
||||||
pub fn fpush_arg(&mut self, arg: String) {
|
pub fn fpush_arg(&mut self, arg: String) {
|
||||||
@@ -245,10 +245,10 @@ impl VarTab {
|
|||||||
pub fn vars_mut(&mut self) -> &mut HashMap<String,Var> {
|
pub fn vars_mut(&mut self) -> &mut HashMap<String,Var> {
|
||||||
&mut self.vars
|
&mut self.vars
|
||||||
}
|
}
|
||||||
pub fn params(&self) -> &HashMap<char,String> {
|
pub fn params(&self) -> &HashMap<String,String> {
|
||||||
&self.params
|
&self.params
|
||||||
}
|
}
|
||||||
pub fn params_mut(&mut self) -> &mut HashMap<char,String> {
|
pub fn params_mut(&mut self) -> &mut HashMap<String,String> {
|
||||||
&mut self.params
|
&mut self.params
|
||||||
}
|
}
|
||||||
pub fn export_var(&mut self, var_name: &str) {
|
pub fn export_var(&mut self, var_name: &str) {
|
||||||
@@ -258,8 +258,9 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_var(&self, var: &str) -> String {
|
pub fn get_var(&self, var: &str) -> String {
|
||||||
if var.chars().count() == 1 {
|
if var.chars().count() == 1 ||
|
||||||
let param = self.get_param(get_char(var, 0).unwrap());
|
var.parse::<usize>().is_ok() {
|
||||||
|
let param = self.get_param(var);
|
||||||
if !param.is_empty() {
|
if !param.is_empty() {
|
||||||
return param
|
return param
|
||||||
}
|
}
|
||||||
@@ -285,21 +286,31 @@ impl VarTab {
|
|||||||
self.vars.insert(var_name.to_string(), var);
|
self.vars.insert(var_name.to_string(), var);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn set_param(&mut self, param: char, val: &str) {
|
pub fn var_exists(&self, var_name: &str) -> bool {
|
||||||
self.params.insert(param,val.to_string());
|
if var_name.parse::<usize>().is_ok() {
|
||||||
|
return self.params.contains_key(var_name);
|
||||||
|
}
|
||||||
|
self.vars.contains_key(var_name) ||
|
||||||
|
(
|
||||||
|
var_name.len() == 1 &&
|
||||||
|
self.params.contains_key(var_name)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
pub fn get_param(&self, param: char) -> String {
|
pub fn set_param(&mut self, param: &str, val: &str) {
|
||||||
if param.is_ascii_digit() {
|
self.params.insert(param.to_string(),val.to_string());
|
||||||
|
}
|
||||||
|
pub fn get_param(&self, param: &str) -> String {
|
||||||
|
if param.parse::<usize>().is_ok() {
|
||||||
let argv_idx = param
|
let argv_idx = param
|
||||||
.to_string()
|
.to_string()
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default();
|
let arg = self.sh_argv.get(argv_idx).map(|s| s.to_string()).unwrap_or_default();
|
||||||
arg
|
arg
|
||||||
} else if param == '?' {
|
} else if param == "?" {
|
||||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into())
|
self.params.get(param).map(|s| s.to_string()).unwrap_or("0".into())
|
||||||
} else {
|
} else {
|
||||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or_default()
|
self.params.get(param).map(|s| s.to_string()).unwrap_or_default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -389,11 +400,11 @@ pub fn get_shopt(path: &str) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status() -> i32 {
|
pub fn get_status() -> i32 {
|
||||||
read_vars(|v| v.get_param('?')).parse::<i32>().unwrap()
|
read_vars(|v| v.get_param("?")).parse::<i32>().unwrap()
|
||||||
}
|
}
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn set_status(code: i32) {
|
pub fn set_status(code: i32) {
|
||||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
write_vars(|v| v.set_param("?", &code.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Save the current state of the logic and variable table, and the working directory path
|
/// Save the current state of the logic and variable table, and the working directory path
|
||||||
|
|||||||
Reference in New Issue
Block a user