Implemented prompt expansion, and display for errors
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}};
|
use crate::{jobs::{ChildProc, JobBldr, JobCmdFlags, JobID}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, lex::Span, NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_jobs, write_jobs}};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ pub enum JobBehavior {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> {
|
pub fn continue_job(node: Node, job: &mut JobBldr, behavior: JobBehavior) -> ShResult<()> {
|
||||||
let blame = ErrSpan::from(node.get_span());
|
let blame = node.get_span().clone();
|
||||||
let cmd = match behavior {
|
let cmd = match behavior {
|
||||||
JobBehavior::Foregound => "fg",
|
JobBehavior::Foregound => "fg",
|
||||||
JobBehavior::Background => "bg"
|
JobBehavior::Background => "bg"
|
||||||
|
|||||||
@@ -44,11 +44,11 @@ pub const BUILTINS: [&str;9] = [
|
|||||||
/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame`
|
/// * If redirections are given to this function, the caller must call `IoFrame.restore()` on the returned `IoFrame`
|
||||||
/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()`
|
/// * If redirections are given, the second field of the resulting tuple will *always* be `Some()`
|
||||||
/// * If no redirections are given, the second field will *always* be `None`
|
/// * If no redirections are given, the second field will *always* be `None`
|
||||||
pub fn setup_builtin<'t>(
|
pub fn setup_builtin(
|
||||||
argv: Vec<Tk<'t>>,
|
argv: Vec<Tk>,
|
||||||
job: &'t mut JobBldr,
|
job: &mut JobBldr,
|
||||||
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
|
io_mode: Option<(&mut IoStack,Vec<Redir>)>,
|
||||||
) -> ShResult<(Vec<(String,Span<'t>)>, Option<IoFrame>)> {
|
) -> ShResult<(Vec<(String,Span)>, Option<IoFrame>)> {
|
||||||
let mut argv: Vec<(String,Span)> = prepare_argv(argv);
|
let mut argv: Vec<(String,Span)> = prepare_argv(argv);
|
||||||
|
|
||||||
let child_pgid = if let Some(pgid) = job.pgid() {
|
let child_pgid = if let Some(pgid) = job.pgid() {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ErrSpan, ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}};
|
use crate::{jobs::{ChildProc, JobBldr}, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{execute::prepare_argv, NdRule, Node}, prelude::*, state::{self, write_vars}};
|
||||||
|
|
||||||
use super::setup_builtin;
|
use super::setup_builtin;
|
||||||
|
|
||||||
|
|||||||
431
src/expand.rs
431
src/expand.rs
@@ -1,4 +1,4 @@
|
|||||||
use crate::{prelude::*, parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, state::read_vars};
|
use crate::{libsh::error::ShResult, parse::lex::{is_field_sep, is_hard_sep, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, prelude::*, state::{read_meta, read_vars, write_meta}};
|
||||||
|
|
||||||
/// Variable substitution marker
|
/// Variable substitution marker
|
||||||
pub const VAR_SUB: char = '\u{fdd0}';
|
pub const VAR_SUB: char = '\u{fdd0}';
|
||||||
@@ -7,14 +7,14 @@ pub const DUB_QUOTE: char = '\u{fdd1}';
|
|||||||
/// Single quote '\\'' marker
|
/// Single quote '\\'' marker
|
||||||
pub const SNG_QUOTE: char = '\u{fdd2}';
|
pub const SNG_QUOTE: char = '\u{fdd2}';
|
||||||
|
|
||||||
impl<'t> Tk<'t> {
|
impl Tk {
|
||||||
/// Create a new expanded token
|
/// Create a new expanded token
|
||||||
///
|
///
|
||||||
/// params
|
/// params
|
||||||
/// tokens: A vector of raw tokens lexed from the expansion result
|
/// tokens: A vector of raw tokens lexed from the expansion result
|
||||||
/// span: The span of the original token that is being expanded
|
/// span: The span of the original token that is being expanded
|
||||||
/// flags: some TkFlags
|
/// flags: some TkFlags
|
||||||
pub fn expand(self, span: Span<'t>, flags: TkFlags) -> Self {
|
pub fn expand(self, span: Span, flags: TkFlags) -> Self {
|
||||||
let exp = Expander::new(self).expand();
|
let exp = Expander::new(self).expand();
|
||||||
let class = TkRule::Expanded { exp };
|
let class = TkRule::Expanded { exp };
|
||||||
Self { class, span, flags, }
|
Self { class, span, flags, }
|
||||||
@@ -31,12 +31,12 @@ pub struct Expander {
|
|||||||
raw: String,
|
raw: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Expander {
|
impl Expander {
|
||||||
pub fn new(raw: Tk<'t>) -> Self {
|
pub fn new(raw: Tk) -> Self {
|
||||||
let unescaped = unescape_str(raw.span.as_str());
|
let unescaped = unescape_str(raw.span.as_str());
|
||||||
Self { raw: unescaped }
|
Self { raw: unescaped }
|
||||||
}
|
}
|
||||||
pub fn expand(&'t mut self) -> Vec<String> {
|
pub fn expand(&mut self) -> Vec<String> {
|
||||||
self.raw = self.expand_raw();
|
self.raw = self.expand_raw();
|
||||||
self.split_words()
|
self.split_words()
|
||||||
}
|
}
|
||||||
@@ -85,7 +85,7 @@ impl<'t> Expander {
|
|||||||
var_name.clear();
|
var_name.clear();
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
_ if is_hard_sep(ch) => {
|
_ if is_hard_sep(ch) || ch == DUB_QUOTE => {
|
||||||
let var_val = read_vars(|v| v.get_var(&var_name));
|
let var_val = read_vars(|v| v.get_var(&var_name));
|
||||||
result.push_str(&var_val);
|
result.push_str(&var_val);
|
||||||
result.push(ch);
|
result.push(ch);
|
||||||
@@ -122,11 +122,424 @@ pub fn unescape_str(raw: &str) -> String {
|
|||||||
result.push(next_ch)
|
result.push(next_ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
'"' => result.push(DUB_QUOTE),
|
'"' => {
|
||||||
'\'' => result.push(SNG_QUOTE),
|
result.push(DUB_QUOTE);
|
||||||
|
while let Some(q_ch) = chars.next() {
|
||||||
|
match q_ch {
|
||||||
|
'\\' => {
|
||||||
|
result.push(q_ch);
|
||||||
|
if let Some(next_ch) = chars.next() {
|
||||||
|
result.push(next_ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'$' => result.push(VAR_SUB),
|
||||||
|
'"' => {
|
||||||
|
result.push(DUB_QUOTE);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ => result.push(q_ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'\'' => {
|
||||||
|
result.push(SNG_QUOTE);
|
||||||
|
while let Some(q_ch) = chars.next() {
|
||||||
|
match q_ch {
|
||||||
|
'\'' => {
|
||||||
|
result.push(SNG_QUOTE);
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_ => result.push(q_ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
'$' => result.push(VAR_SUB),
|
'$' => result.push(VAR_SUB),
|
||||||
_ => result.push(ch)
|
_ => result.push(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum PromptTk {
|
||||||
|
AsciiOct(i32),
|
||||||
|
Text(String),
|
||||||
|
AnsiSeq(String),
|
||||||
|
VisGrp,
|
||||||
|
UserSeq,
|
||||||
|
Runtime,
|
||||||
|
Weekday,
|
||||||
|
Dquote,
|
||||||
|
Squote,
|
||||||
|
Return,
|
||||||
|
Newline,
|
||||||
|
Pwd,
|
||||||
|
PwdShort,
|
||||||
|
Hostname,
|
||||||
|
HostnameShort,
|
||||||
|
ShellName,
|
||||||
|
Username,
|
||||||
|
PromptSymbol,
|
||||||
|
ExitCode,
|
||||||
|
SuccessSymbol,
|
||||||
|
FailureSymbol,
|
||||||
|
JobCount
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_cmd_runtime(dur: std::time::Duration) -> String {
|
||||||
|
const ETERNITY: u128 = f32::INFINITY as u128;
|
||||||
|
let mut micros = dur.as_micros();
|
||||||
|
let mut millis = 0;
|
||||||
|
let mut seconds = 0;
|
||||||
|
let mut minutes = 0;
|
||||||
|
let mut hours = 0;
|
||||||
|
let mut days = 0;
|
||||||
|
let mut weeks = 0;
|
||||||
|
let mut months = 0;
|
||||||
|
let mut years = 0;
|
||||||
|
let mut decades = 0;
|
||||||
|
let mut centuries = 0;
|
||||||
|
let mut millennia = 0;
|
||||||
|
let mut epochs = 0;
|
||||||
|
let mut aeons = 0;
|
||||||
|
let mut eternities = 0;
|
||||||
|
|
||||||
|
if micros >= 1000 {
|
||||||
|
millis = micros / 1000;
|
||||||
|
micros %= 1000;
|
||||||
|
}
|
||||||
|
if millis >= 1000 {
|
||||||
|
seconds = millis / 1000;
|
||||||
|
millis %= 1000;
|
||||||
|
}
|
||||||
|
if seconds >= 60 {
|
||||||
|
minutes = seconds / 60;
|
||||||
|
seconds %= 60;
|
||||||
|
}
|
||||||
|
if minutes >= 60 {
|
||||||
|
hours = minutes / 60;
|
||||||
|
minutes %= 60;
|
||||||
|
}
|
||||||
|
if hours >= 24 {
|
||||||
|
days = hours / 24;
|
||||||
|
hours %= 24;
|
||||||
|
}
|
||||||
|
if days >= 7 {
|
||||||
|
weeks = days / 7;
|
||||||
|
days %= 7;
|
||||||
|
}
|
||||||
|
if weeks >= 4 {
|
||||||
|
months = weeks / 4;
|
||||||
|
weeks %= 4;
|
||||||
|
}
|
||||||
|
if months >= 12 {
|
||||||
|
years = months / 12;
|
||||||
|
weeks %= 12;
|
||||||
|
}
|
||||||
|
if years >= 10 {
|
||||||
|
decades = years / 10;
|
||||||
|
years %= 10;
|
||||||
|
}
|
||||||
|
if decades >= 10 {
|
||||||
|
centuries = decades / 10;
|
||||||
|
decades %= 10;
|
||||||
|
}
|
||||||
|
if centuries >= 10 {
|
||||||
|
millennia = centuries / 10;
|
||||||
|
centuries %= 10;
|
||||||
|
}
|
||||||
|
if millennia >= 1000 {
|
||||||
|
epochs = millennia / 1000;
|
||||||
|
millennia %= 1000;
|
||||||
|
}
|
||||||
|
if epochs >= 1000 {
|
||||||
|
aeons = epochs / 1000;
|
||||||
|
epochs %= aeons;
|
||||||
|
}
|
||||||
|
if aeons == ETERNITY {
|
||||||
|
eternities = aeons / ETERNITY;
|
||||||
|
aeons %= ETERNITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the result
|
||||||
|
let mut result = Vec::new();
|
||||||
|
if eternities > 0 {
|
||||||
|
let mut string = format!("{} eternit", eternities);
|
||||||
|
if eternities > 1 {
|
||||||
|
string.push_str("ies");
|
||||||
|
} else {
|
||||||
|
string.push('y');
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if aeons > 0 {
|
||||||
|
let mut string = format!("{} aeon", aeons);
|
||||||
|
if aeons > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if epochs > 0 {
|
||||||
|
let mut string = format!("{} epoch", epochs);
|
||||||
|
if epochs > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if millennia > 0 {
|
||||||
|
let mut string = format!("{} millenni", millennia);
|
||||||
|
if millennia > 1 {
|
||||||
|
string.push_str("um")
|
||||||
|
} else {
|
||||||
|
string.push('a')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if centuries > 0 {
|
||||||
|
let mut string = format!("{} centur", centuries);
|
||||||
|
if centuries > 1 {
|
||||||
|
string.push_str("ies")
|
||||||
|
} else {
|
||||||
|
string.push('y')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if decades > 0 {
|
||||||
|
let mut string = format!("{} decade", decades);
|
||||||
|
if decades > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if years > 0 {
|
||||||
|
let mut string = format!("{} year", years);
|
||||||
|
if years > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if months > 0 {
|
||||||
|
let mut string = format!("{} month", months);
|
||||||
|
if months > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if weeks > 0 {
|
||||||
|
let mut string = format!("{} week", weeks);
|
||||||
|
if weeks > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if days > 0 {
|
||||||
|
let mut string = format!("{} day", days);
|
||||||
|
if days > 1 {
|
||||||
|
string.push('s')
|
||||||
|
}
|
||||||
|
result.push(string)
|
||||||
|
}
|
||||||
|
if hours > 0 {
|
||||||
|
let string = format!("{}h", hours);
|
||||||
|
result.push(string);
|
||||||
|
}
|
||||||
|
if minutes > 0 {
|
||||||
|
let string = format!("{}m", minutes);
|
||||||
|
result.push(string);
|
||||||
|
}
|
||||||
|
if seconds > 0 {
|
||||||
|
let string = format!("{}s", seconds);
|
||||||
|
result.push(string);
|
||||||
|
}
|
||||||
|
if millis > 0 {
|
||||||
|
let string = format!("{}ms",millis);
|
||||||
|
result.push(string);
|
||||||
|
}
|
||||||
|
if result.is_empty() && micros > 0 {
|
||||||
|
let string = format!("{}µs",micros);
|
||||||
|
result.push(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tokenize_prompt(raw: &str) -> Vec<PromptTk> {
|
||||||
|
let mut chars = raw.chars().peekable();
|
||||||
|
let mut tk_text = String::new();
|
||||||
|
let mut tokens = vec![];
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'\\' => {
|
||||||
|
// Push any accumulated text as a token
|
||||||
|
if !tk_text.is_empty() {
|
||||||
|
tokens.push(PromptTk::Text(std::mem::take(&mut tk_text)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the escape sequence
|
||||||
|
if let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'w' => tokens.push(PromptTk::Pwd),
|
||||||
|
'W' => tokens.push(PromptTk::PwdShort),
|
||||||
|
'h' => tokens.push(PromptTk::Hostname),
|
||||||
|
'H' => tokens.push(PromptTk::HostnameShort),
|
||||||
|
's' => tokens.push(PromptTk::ShellName),
|
||||||
|
'u' => tokens.push(PromptTk::Username),
|
||||||
|
'$' => tokens.push(PromptTk::PromptSymbol),
|
||||||
|
'n' => tokens.push(PromptTk::Text("\n".into())),
|
||||||
|
'r' => tokens.push(PromptTk::Text("\r".into())),
|
||||||
|
'T' => tokens.push(PromptTk::Runtime),
|
||||||
|
'\\' => tokens.push(PromptTk::Text("\\".into())),
|
||||||
|
'"' => tokens.push(PromptTk::Text("\"".into())),
|
||||||
|
'\'' => tokens.push(PromptTk::Text("'".into())),
|
||||||
|
'e' => {
|
||||||
|
if chars.next() == Some('[') {
|
||||||
|
let mut params = String::new();
|
||||||
|
|
||||||
|
// Collect parameters and final character
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'0'..='9' | ';' | '?' | ':' => params.push(ch), // Valid parameter characters
|
||||||
|
'A'..='Z' | 'a'..='z' => { // Final character (letter)
|
||||||
|
params.push(ch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Invalid character in ANSI sequence
|
||||||
|
tokens.push(PromptTk::Text(format!("\x1b[{params}")));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens.push(PromptTk::AnsiSeq(format!("\x1b[{params}")));
|
||||||
|
} else {
|
||||||
|
// Handle case where 'e' is not followed by '['
|
||||||
|
tokens.push(PromptTk::Text("\\e".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'0'..='7' => {
|
||||||
|
// Handle octal escape
|
||||||
|
let mut octal_str = String::new();
|
||||||
|
octal_str.push(ch);
|
||||||
|
|
||||||
|
// Collect up to 2 more octal digits
|
||||||
|
for _ in 0..2 {
|
||||||
|
if let Some(&next_ch) = chars.peek() {
|
||||||
|
if next_ch >= '0' && next_ch <= '7' {
|
||||||
|
octal_str.push(chars.next().unwrap());
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the octal string into an integer
|
||||||
|
if let Ok(octal) = i32::from_str_radix(&octal_str, 8) {
|
||||||
|
tokens.push(PromptTk::AsciiOct(octal));
|
||||||
|
} else {
|
||||||
|
// Fallback: treat as raw text
|
||||||
|
tokens.push(PromptTk::Text(format!("\\{octal_str}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Unknown escape sequence: treat as raw text
|
||||||
|
tokens.push(PromptTk::Text(format!("\\{ch}")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Handle trailing backslash
|
||||||
|
tokens.push(PromptTk::Text("\\".into()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Accumulate non-escape characters
|
||||||
|
tk_text.push(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push any remaining text as a token
|
||||||
|
if !tk_text.is_empty() {
|
||||||
|
tokens.push(PromptTk::Text(tk_text));
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
||||||
|
let mut tokens = tokenize_prompt(raw).into_iter();
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
while let Some(token) = tokens.next() {
|
||||||
|
match token {
|
||||||
|
PromptTk::AsciiOct(_) => todo!(),
|
||||||
|
PromptTk::Text(txt) => result.push_str(&txt),
|
||||||
|
PromptTk::AnsiSeq(params) => result.push_str(¶ms),
|
||||||
|
PromptTk::Runtime => {
|
||||||
|
flog!(INFO, "getting runtime");
|
||||||
|
if let Some(runtime) = write_meta(|m| m.stop_timer()) {
|
||||||
|
flog!(DEBUG, runtime);
|
||||||
|
let runtime_fmt = format_cmd_runtime(runtime);
|
||||||
|
result.push_str(&runtime_fmt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PromptTk::Pwd => {
|
||||||
|
let mut pwd = std::env::var("PWD")?;
|
||||||
|
let home = std::env::var("HOME")?;
|
||||||
|
if pwd.starts_with(&home) {
|
||||||
|
pwd = pwd.replacen(&home, "~", 1);
|
||||||
|
}
|
||||||
|
result.push_str(&pwd);
|
||||||
|
}
|
||||||
|
PromptTk::PwdShort => {
|
||||||
|
let mut path = std::env::var("PWD")?;
|
||||||
|
let home = std::env::var("HOME")?;
|
||||||
|
if path.starts_with(&home) {
|
||||||
|
path = path.replacen(&home, "~", 1);
|
||||||
|
}
|
||||||
|
let pathbuf = PathBuf::from(&path);
|
||||||
|
let mut segments = pathbuf.iter().count();
|
||||||
|
let mut path_iter = pathbuf.into_iter();
|
||||||
|
while segments > 4 {
|
||||||
|
path_iter.next();
|
||||||
|
segments -= 1;
|
||||||
|
}
|
||||||
|
let path_rebuilt: PathBuf = path_iter.collect();
|
||||||
|
let mut path_rebuilt = path_rebuilt.to_str().unwrap().to_string();
|
||||||
|
if path_rebuilt.starts_with(&home) {
|
||||||
|
path_rebuilt = path_rebuilt.replacen(&home, "~", 1);
|
||||||
|
}
|
||||||
|
result.push_str(&path_rebuilt);
|
||||||
|
}
|
||||||
|
PromptTk::Hostname => {
|
||||||
|
let hostname = std::env::var("HOSTNAME")?;
|
||||||
|
result.push_str(&hostname);
|
||||||
|
}
|
||||||
|
PromptTk::HostnameShort => todo!(),
|
||||||
|
PromptTk::ShellName => result.push_str("fern"),
|
||||||
|
PromptTk::Username => {
|
||||||
|
let username = std::env::var("USER")?;
|
||||||
|
result.push_str(&username);
|
||||||
|
}
|
||||||
|
PromptTk::PromptSymbol => {
|
||||||
|
let uid = std::env::var("UID")?;
|
||||||
|
let symbol = if &uid == "0" {
|
||||||
|
'#'
|
||||||
|
} else {
|
||||||
|
'$'
|
||||||
|
};
|
||||||
|
result.push(symbol);
|
||||||
|
}
|
||||||
|
PromptTk::ExitCode => todo!(),
|
||||||
|
PromptTk::SuccessSymbol => todo!(),
|
||||||
|
PromptTk::FailureSymbol => todo!(),
|
||||||
|
PromptTk::JobCount => todo!(),
|
||||||
|
_ => unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|||||||
39
src/fern.rs
39
src/fern.rs
@@ -12,14 +12,16 @@ pub mod signal;
|
|||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
use libsh::error::ShResult;
|
use libsh::error::ShResult;
|
||||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, ParseStream};
|
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, Ast, ParseStream, ParsedSrc};
|
||||||
use procio::IoFrame;
|
use procio::IoFrame;
|
||||||
use signal::sig_setup;
|
use signal::sig_setup;
|
||||||
|
use state::write_meta;
|
||||||
use termios::{LocalFlags, Termios};
|
use termios::{LocalFlags, Termios};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
pub static mut SAVED_TERMIOS: Option<Option<Termios>> = None;
|
||||||
|
|
||||||
|
|
||||||
pub fn save_termios() {
|
pub fn save_termios() {
|
||||||
unsafe {
|
unsafe {
|
||||||
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
SAVED_TERMIOS = Some(if isatty(std::io::stdin().as_raw_fd()).unwrap() {
|
||||||
@@ -47,28 +49,15 @@ fn set_termios() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exec_input(input: &str, io_frame: Option<IoFrame>) -> ShResult<()> {
|
pub fn exec_input(input: String) -> ShResult<()> {
|
||||||
let parse_start = Instant::now();
|
write_meta(|m| m.start_timer());
|
||||||
let mut tokens = vec![];
|
let mut parser = ParsedSrc::new(Rc::new(input));
|
||||||
for token in LexStream::new(&input, LexFlags::empty()) {
|
parser.parse_src()?;
|
||||||
tokens.push(token?);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut nodes = vec![];
|
|
||||||
for result in ParseStream::new(tokens) {
|
|
||||||
nodes.push(result?);
|
|
||||||
}
|
|
||||||
flog!(INFO, "parse duration: {:?}", parse_start.elapsed());
|
|
||||||
|
|
||||||
let exec_start = Instant::now();
|
let exec_start = Instant::now();
|
||||||
let mut dispatcher = Dispatcher::new(nodes);
|
|
||||||
if let Some(frame) = io_frame {
|
|
||||||
dispatcher.io_stack.push(frame)
|
|
||||||
}
|
|
||||||
dispatcher.begin_dispatch()?;
|
|
||||||
flog!(INFO, "cmd duration: {:?}", exec_start.elapsed());
|
|
||||||
|
|
||||||
flog!(INFO, "total duration: {:?}", parse_start.elapsed());
|
let mut dispatcher = Dispatcher::new(parser.extract_nodes());
|
||||||
|
dispatcher.begin_dispatch()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +68,15 @@ fn main() {
|
|||||||
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let input = prompt::read_line().unwrap();
|
let input = match prompt::read_line() {
|
||||||
|
Ok(line) => line,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("{e}");
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if let Err(e) = exec_input(&input,None) {
|
if let Err(e) = exec_input(input) {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,63 @@
|
|||||||
use std::{fmt::Display, ops::Range};
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::{parse::lex::Span, prelude::*};
|
use crate::{
|
||||||
|
libsh::term::{Style, Styled},
|
||||||
|
parse::lex::Span,
|
||||||
|
prelude::*
|
||||||
|
};
|
||||||
|
|
||||||
pub type ShResult<T> = Result<T,ShErr>;
|
pub type ShResult<T> = Result<T,ShErr>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub trait ShResultExt {
|
||||||
pub struct ErrSpan {
|
fn blame(self, span: Span) -> Self;
|
||||||
range: Range<usize>,
|
fn try_blame(self, span: Span) -> Self;
|
||||||
source: String
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> From<Span<'s>> for ErrSpan {
|
impl<T> ShResultExt for Result<T,ShErr> {
|
||||||
fn from(value: Span<'s>) -> Self {
|
/// Blame a span for an error
|
||||||
let range = value.range();
|
fn blame(self, new_span: Span) -> Self {
|
||||||
let source = value.get_source().to_string();
|
let Err(e) = self else {
|
||||||
Self { range, source }
|
return self
|
||||||
|
};
|
||||||
|
match e {
|
||||||
|
ShErr::Simple { kind, msg } |
|
||||||
|
ShErr::Full { kind, msg, span: _ } => Err(ShErr::full(kind, msg, new_span)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Blame a span if no blame has been assigned yet
|
||||||
|
fn try_blame(self, new_span: Span) -> Self {
|
||||||
|
let Err(e) = &self else {
|
||||||
|
return self
|
||||||
|
};
|
||||||
|
match e {
|
||||||
|
ShErr::Simple { kind, msg } => Err(ShErr::full(*kind, msg, new_span)),
|
||||||
|
ShErr::Full { kind: _, msg: _, span: _ } => self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ShErr {
|
pub enum ShErr {
|
||||||
Simple { kind: ShErrKind, msg: String },
|
Simple { kind: ShErrKind, msg: String },
|
||||||
Full { kind: ShErrKind, msg: String, span: ErrSpan }
|
Full { kind: ShErrKind, msg: String, span: Span }
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> ShErr {
|
impl ShErr {
|
||||||
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
pub fn simple(kind: ShErrKind, msg: impl Into<String>) -> Self {
|
||||||
let msg = msg.into();
|
let msg = msg.into();
|
||||||
Self::Simple { kind, msg }
|
Self::Simple { kind, msg }
|
||||||
}
|
}
|
||||||
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: ErrSpan) -> Self {
|
pub fn full(kind: ShErrKind, msg: impl Into<String>, span: Span) -> Self {
|
||||||
let msg = msg.into();
|
let msg = msg.into();
|
||||||
Self::Full { kind, msg, span }
|
Self::Full { kind, msg, span }
|
||||||
}
|
}
|
||||||
pub fn unpack(self) -> (ShErrKind,String,Option<ErrSpan>) {
|
pub fn unpack(self) -> (ShErrKind,String,Option<Span>) {
|
||||||
match self {
|
match self {
|
||||||
ShErr::Simple { kind, msg } => (kind,msg,None),
|
ShErr::Simple { kind, msg } => (kind,msg,None),
|
||||||
ShErr::Full { kind, msg, span } => (kind,msg,Some(span))
|
ShErr::Full { kind, msg, span } => (kind,msg,Some(span))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn with_span(sherr: ShErr, span: Span<'s>) -> Self {
|
pub fn with_span(sherr: ShErr, span: Span) -> Self {
|
||||||
let (kind,msg,_) = sherr.unpack();
|
let (kind,msg,_) = sherr.unpack();
|
||||||
let span = span.into();
|
let span = span.into();
|
||||||
Self::Full { kind, msg, span }
|
Self::Full { kind, msg, span }
|
||||||
@@ -50,13 +68,112 @@ impl<'s> ShErr {
|
|||||||
ShErr::Full { kind, msg: _, span: _ } => kind
|
ShErr::Full { kind, msg: _, span: _ } => kind
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pub fn get_window(&self) -> Vec<(usize,String)> {
|
||||||
|
let ShErr::Full { kind: _, msg: _, span } = self else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let mut total_len: usize = 0;
|
||||||
|
let mut total_lines: usize = 1;
|
||||||
|
let mut lines = vec![];
|
||||||
|
let mut cur_line = String::new();
|
||||||
|
|
||||||
|
let src = span.get_source();
|
||||||
|
let mut chars = src.chars();
|
||||||
|
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
total_len += ch.len_utf8();
|
||||||
|
cur_line.push(ch);
|
||||||
|
if ch == '\n' {
|
||||||
|
total_lines += 1;
|
||||||
|
|
||||||
|
if total_len >= span.start {
|
||||||
|
let line = (
|
||||||
|
total_lines,
|
||||||
|
mem::take(&mut cur_line)
|
||||||
|
);
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
if total_len >= span.end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cur_line.is_empty() {
|
||||||
|
let line = (
|
||||||
|
total_lines,
|
||||||
|
mem::take(&mut cur_line)
|
||||||
|
);
|
||||||
|
lines.push(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
lines
|
||||||
|
}
|
||||||
|
pub fn get_line_col(&self) -> (usize,usize) {
|
||||||
|
let ShErr::Full { kind: _, msg: _, span } = self else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut lineno = 1;
|
||||||
|
let mut colno = 1;
|
||||||
|
let src = span.get_source();
|
||||||
|
let mut chars = src.chars().enumerate();
|
||||||
|
while let Some((pos,ch)) = chars.next() {
|
||||||
|
if pos >= span.start {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ch == '\n' {
|
||||||
|
lineno += 1;
|
||||||
|
colno = 1;
|
||||||
|
} else {
|
||||||
|
colno += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(lineno,colno)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ShErr {
|
impl Display for ShErr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg),
|
Self::Simple { msg, kind: _ } => writeln!(f, "{}", msg),
|
||||||
Self::Full { msg, kind: _, span: _ } => writeln!(f, "{}", msg)
|
Self::Full { msg, kind, span: _ } => {
|
||||||
|
let window = self.get_window();
|
||||||
|
let mut lineno_pad_count = 0;
|
||||||
|
for (lineno,_) in window.clone() {
|
||||||
|
if lineno.to_string().len() > lineno_pad_count {
|
||||||
|
lineno_pad_count = lineno.to_string().len() + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let (line,col) = self.get_line_col();
|
||||||
|
let line = line.styled(Style::Cyan | Style::Bold);
|
||||||
|
let col = col.styled(Style::Cyan | Style::Bold);
|
||||||
|
let kind = kind.styled(Style::Red | Style::Bold);
|
||||||
|
let padding = " ".repeat(lineno_pad_count);
|
||||||
|
let arrow = "->".styled(Style::Cyan | Style::Bold);
|
||||||
|
writeln!(f,
|
||||||
|
"{padding}{arrow} [{line};{col}] - {kind}",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut bar = format!("{padding}|");
|
||||||
|
bar = bar.styled(Style::Cyan | Style::Bold);
|
||||||
|
writeln!(f,"{bar}")?;
|
||||||
|
|
||||||
|
for (lineno,line) in window {
|
||||||
|
let lineno = lineno.to_string();
|
||||||
|
let mut prefix = format!("{padding}|");
|
||||||
|
prefix.replace_range(0..lineno.len(), &lineno);
|
||||||
|
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
||||||
|
writeln!(f,"{prefix} {line}")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
writeln!(f,"{bar}")?;
|
||||||
|
|
||||||
|
let bar_break = "-".styled(Style::Cyan | Style::Bold);
|
||||||
|
writeln!(f,
|
||||||
|
"{padding}{bar_break} {msg}",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +203,7 @@ impl From<Errno> for ShErr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug,Clone,Copy)]
|
||||||
pub enum ShErrKind {
|
pub enum ShErrKind {
|
||||||
IoErr,
|
IoErr,
|
||||||
SyntaxErr,
|
SyntaxErr,
|
||||||
@@ -104,3 +221,26 @@ pub enum ShErrKind {
|
|||||||
LoopBreak,
|
LoopBreak,
|
||||||
Null
|
Null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for ShErrKind {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let output = match self {
|
||||||
|
ShErrKind::IoErr => "I/O Error",
|
||||||
|
ShErrKind::SyntaxErr => "Syntax Error",
|
||||||
|
ShErrKind::ParseErr => "Parse Error",
|
||||||
|
ShErrKind::InternalErr => "Internal Error",
|
||||||
|
ShErrKind::ExecFail => "Execution Failed",
|
||||||
|
ShErrKind::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||||
|
ShErrKind::BadPermission => "Bad Permissions",
|
||||||
|
ShErrKind::Errno => "ERRNO",
|
||||||
|
ShErrKind::FileNotFound => "File Not Found",
|
||||||
|
ShErrKind::CmdNotFound => "Command Not Found",
|
||||||
|
ShErrKind::CleanExit => "",
|
||||||
|
ShErrKind::FuncReturn => "",
|
||||||
|
ShErrKind::LoopContinue => "",
|
||||||
|
ShErrKind::LoopBreak => "",
|
||||||
|
ShErrKind::Null => "",
|
||||||
|
};
|
||||||
|
write!(f,"{output}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ impl<T> VecDequeExt<T> for VecDeque<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> TkVecUtils<Tk<'t>> for Vec<Tk<'t>> {
|
impl TkVecUtils<Tk> for Vec<Tk> {
|
||||||
fn get_span(&self) -> Option<Span<'t>> {
|
fn get_span(&self) -> Option<Span> {
|
||||||
if let Some(first_tk) = self.first() {
|
if let Some(first_tk) = self.first() {
|
||||||
if let Some(last_tk) = self.last() {
|
if let Some(last_tk) = self.last() {
|
||||||
Some(
|
Some(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
|
||||||
use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ErrSpan, ShErr, ShErrKind, ShResult}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars}};
|
use crate::{builtin::{cd::cd, echo::echo, export::export, jobctl::{continue_job, jobs, JobBehavior}, pwd::pwd, shift::shift, source::source}, exec_input, jobs::{dispatch_job, ChildProc, Job, JobBldr, JobStack}, libsh::{error::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars, ShFunc, VarTab}};
|
||||||
|
|
||||||
use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, Redir, RedirType};
|
use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, ParsedSrc, Redir, RedirType};
|
||||||
|
|
||||||
pub enum AssignBehavior {
|
pub enum AssignBehavior {
|
||||||
Export,
|
Export,
|
||||||
@@ -37,25 +37,26 @@ impl ExecArgs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Dispatcher<'t> {
|
pub struct Dispatcher {
|
||||||
nodes: VecDeque<Node<'t>>,
|
nodes: VecDeque<Node>,
|
||||||
pub io_stack: IoStack,
|
pub io_stack: IoStack,
|
||||||
pub job_stack: JobStack
|
pub job_stack: JobStack
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Dispatcher<'t> {
|
impl Dispatcher {
|
||||||
pub fn new(nodes: Vec<Node<'t>>) -> Self {
|
pub fn new(nodes: Vec<Node>) -> Self {
|
||||||
let nodes = VecDeque::from(nodes);
|
let nodes = VecDeque::from(nodes);
|
||||||
Self { nodes, io_stack: IoStack::new(), job_stack: JobStack::new() }
|
Self { nodes, io_stack: IoStack::new(), job_stack: JobStack::new() }
|
||||||
}
|
}
|
||||||
pub fn begin_dispatch(&mut self) -> ShResult<()> {
|
pub fn begin_dispatch(&mut self) -> ShResult<()> {
|
||||||
flog!(TRACE, "beginning dispatch");
|
flog!(TRACE, "beginning dispatch");
|
||||||
while let Some(list) = self.nodes.pop_front() {
|
while let Some(node) = self.nodes.pop_front() {
|
||||||
self.dispatch_node(list)?;
|
let blame = node.get_span();
|
||||||
|
self.dispatch_node(node).try_blame(blame)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn dispatch_node(&mut self, node: Node<'t>) -> ShResult<()> {
|
pub fn dispatch_node(&mut self, node: Node) -> ShResult<()> {
|
||||||
match node.class {
|
match node.class {
|
||||||
NdRule::Conjunction {..} => self.exec_conjunction(node)?,
|
NdRule::Conjunction {..} => self.exec_conjunction(node)?,
|
||||||
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
NdRule::Pipeline {..} => self.exec_pipeline(node)?,
|
||||||
@@ -68,7 +69,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn dispatch_cmd(&mut self, node: Node<'t>) -> ShResult<()> {
|
pub fn dispatch_cmd(&mut self, node: Node) -> ShResult<()> {
|
||||||
let Some(cmd) = node.get_command() else {
|
let Some(cmd) = node.get_command() else {
|
||||||
return self.exec_cmd(node) // Argv is empty, probably an assignment
|
return self.exec_cmd(node) // Argv is empty, probably an assignment
|
||||||
};
|
};
|
||||||
@@ -80,7 +81,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
self.exec_cmd(node)
|
self.exec_cmd(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn exec_conjunction(&mut self, conjunction: Node<'t>) -> ShResult<()> {
|
pub fn exec_conjunction(&mut self, conjunction: Node) -> ShResult<()> {
|
||||||
let NdRule::Conjunction { elements } = conjunction.class else {
|
let NdRule::Conjunction { elements } = conjunction.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -99,42 +100,51 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_func_def(&mut self, func_def: Node<'t>) -> ShResult<()> {
|
pub fn exec_func_def(&mut self, func_def: Node) -> ShResult<()> {
|
||||||
let NdRule::FuncDef { name, body } = func_def.class else {
|
let NdRule::FuncDef { name, body } = func_def.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let body_span = body.get_span();
|
let body_span = body.get_span();
|
||||||
let body = body_span.as_str();
|
let body = body_span.as_str().to_string();
|
||||||
let name = name.span.as_str().strip_suffix("()").unwrap();
|
let name = name.span.as_str().strip_suffix("()").unwrap();
|
||||||
write_logic(|l| l.insert_func(name, body));
|
|
||||||
|
let mut func_parser = ParsedSrc::new(Rc::new(body));
|
||||||
|
func_parser.parse_src()?; // Parse the function
|
||||||
|
|
||||||
|
let func = ShFunc::new(func_parser);
|
||||||
|
write_logic(|l| l.insert_func(name, func)); // Store the AST
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_func(&mut self, func: Node<'t>) -> ShResult<()> {
|
pub fn exec_func(&mut self, func: Node) -> ShResult<()> {
|
||||||
let blame: ErrSpan = func.get_span().into();
|
let blame = func.get_span().clone();
|
||||||
// TODO: Find a way to store functions as pre-parsed nodes so we don't have to re-parse them
|
|
||||||
let NdRule::Command { assignments, mut argv } = func.class else {
|
let NdRule::Command { assignments, mut argv } = func.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
self.set_assignments(assignments, AssignBehavior::Export);
|
self.set_assignments(assignments, AssignBehavior::Export);
|
||||||
|
|
||||||
let mut io_frame = self.io_stack.pop_frame();
|
self.io_stack.append_to_frame(func.redirs);
|
||||||
io_frame.extend(func.redirs);
|
|
||||||
|
|
||||||
let func_name = argv.remove(0).span.as_str().to_string();
|
let func_name = argv.remove(0).span.as_str().to_string();
|
||||||
if let Some(func_body) = read_logic(|l| l.get_func(&func_name)) {
|
if let Some(func) = read_logic(|l| l.get_func(&func_name)) {
|
||||||
let saved_sh_args = read_vars(|v| v.sh_argv().clone());
|
let scope_snapshot = read_vars(|v| v.clone());
|
||||||
|
// Set up the inner scope
|
||||||
write_vars(|v| {
|
write_vars(|v| {
|
||||||
|
**v = VarTab::new();
|
||||||
v.clear_args();
|
v.clear_args();
|
||||||
for arg in argv {
|
for arg in argv {
|
||||||
v.bpush_arg(arg.to_string());
|
v.bpush_arg(arg.to_string());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = exec_input(&func_body, Some(io_frame));
|
if let Err(e) = self.exec_brc_grp((*func).clone()) {
|
||||||
|
write_vars(|v| **v = scope_snapshot);
|
||||||
|
return Err(e.into())
|
||||||
|
}
|
||||||
|
|
||||||
write_vars(|v| *v.sh_argv_mut() = saved_sh_args);
|
// Return to the outer scope
|
||||||
Ok(result?)
|
write_vars(|v| **v = scope_snapshot);
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(
|
Err(
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
@@ -145,7 +155,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn exec_brc_grp(&mut self, brc_grp: Node<'t>) -> ShResult<()> {
|
pub fn exec_brc_grp(&mut self, brc_grp: Node) -> ShResult<()> {
|
||||||
let NdRule::BraceGrp { body } = brc_grp.class else {
|
let NdRule::BraceGrp { body } = brc_grp.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -153,13 +163,14 @@ impl<'t> Dispatcher<'t> {
|
|||||||
io_frame.extend(brc_grp.redirs);
|
io_frame.extend(brc_grp.redirs);
|
||||||
|
|
||||||
for node in body {
|
for node in body {
|
||||||
|
let blame = node.get_span();
|
||||||
self.io_stack.push_frame(io_frame.clone());
|
self.io_stack.push_frame(io_frame.clone());
|
||||||
self.dispatch_node(node)?;
|
self.dispatch_node(node).try_blame(blame)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_loop(&mut self, loop_stmt: Node<'t>) -> ShResult<()> {
|
pub fn exec_loop(&mut self, loop_stmt: Node) -> ShResult<()> {
|
||||||
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
|
let NdRule::LoopNode { kind, cond_node } = loop_stmt.class else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
@@ -204,7 +215,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_if(&mut self, if_stmt: Node<'t>) -> ShResult<()> {
|
pub fn exec_if(&mut self, if_stmt: Node) -> ShResult<()> {
|
||||||
let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else {
|
let NdRule::IfNode { cond_nodes, else_block } = if_stmt.class else {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
};
|
};
|
||||||
@@ -244,7 +255,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_pipeline(&mut self, pipeline: Node<'t>) -> ShResult<()> {
|
pub fn exec_pipeline(&mut self, pipeline: Node) -> ShResult<()> {
|
||||||
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
let NdRule::Pipeline { cmds, pipe_err } = pipeline.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -254,6 +265,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.zip(cmds);
|
.zip(cmds);
|
||||||
|
|
||||||
|
|
||||||
for ((rpipe,wpipe), cmd) in pipes_and_cmds {
|
for ((rpipe,wpipe), cmd) in pipes_and_cmds {
|
||||||
if let Some(pipe) = rpipe {
|
if let Some(pipe) = rpipe {
|
||||||
self.io_stack.push_to_frame(pipe);
|
self.io_stack.push_to_frame(pipe);
|
||||||
@@ -268,7 +280,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
dispatch_job(job, is_bg)?;
|
dispatch_job(job, is_bg)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_builtin(&mut self, mut cmd: Node<'t>) -> ShResult<()> {
|
pub fn exec_builtin(&mut self, mut cmd: Node) -> ShResult<()> {
|
||||||
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
|
let NdRule::Command { ref mut assignments, argv } = &mut cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -301,7 +313,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_cmd(&mut self, cmd: Node<'t>) -> ShResult<()> {
|
pub fn exec_cmd(&mut self, cmd: Node) -> ShResult<()> {
|
||||||
let NdRule::Command { assignments, argv } = cmd.class else {
|
let NdRule::Command { assignments, argv } = cmd.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -337,7 +349,7 @@ impl<'t> Dispatcher<'t> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn set_assignments(&self, assigns: Vec<Node<'t>>, behavior: AssignBehavior) -> Vec<String> {
|
pub fn set_assignments(&self, assigns: Vec<Node>, behavior: AssignBehavior) -> Vec<String> {
|
||||||
let mut new_env_vars = vec![];
|
let mut new_env_vars = vec![];
|
||||||
match behavior {
|
match behavior {
|
||||||
AssignBehavior::Export => {
|
AssignBehavior::Export => {
|
||||||
@@ -420,7 +432,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The default behavior for the child process after forking
|
/// The default behavior for the child process after forking
|
||||||
pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: Option<ExecArgs>) {
|
pub fn def_child_action(mut io_frame: IoFrame, exec_args: Option<ExecArgs>) {
|
||||||
if let Err(e) = io_frame.redirect() {
|
if let Err(e) = io_frame.redirect() {
|
||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
@@ -435,7 +447,7 @@ pub fn def_child_action<'t>(mut io_frame: IoFrame, exec_args: Option<ExecArgs>)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The default behavior for the parent process after forking
|
/// The default behavior for the parent process after forking
|
||||||
pub fn def_parent_action<'t>(
|
pub fn def_parent_action(
|
||||||
io_frame: IoFrame,
|
io_frame: IoFrame,
|
||||||
job: &mut JobBldr,
|
job: &mut JobBldr,
|
||||||
cmd: Option<&str>,
|
cmd: Option<&str>,
|
||||||
@@ -478,7 +490,7 @@ pub fn get_pipe_stack(num_cmds: usize) -> Vec<(Option<Redir>,Option<Redir>)> {
|
|||||||
stack
|
stack
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_func<'t>(tk: Option<Tk<'t>>) -> bool {
|
pub fn is_func(tk: Option<Tk>) -> bool {
|
||||||
let Some(tk) = tk else {
|
let Some(tk) = tk else {
|
||||||
return false
|
return false
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,14 +31,14 @@ pub const OPENERS: [&'static str;6] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Clone,PartialEq,Default,Debug)]
|
#[derive(Clone,PartialEq,Default,Debug)]
|
||||||
pub struct Span<'s> {
|
pub struct Span {
|
||||||
range: Range<usize>,
|
range: Range<usize>,
|
||||||
source: &'s str
|
source: Rc<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Span<'s> {
|
impl Span {
|
||||||
/// New `Span`. Wraps a range and a string slice that it refers to.
|
/// New `Span`. Wraps a range and a string slice that it refers to.
|
||||||
pub fn new(range: Range<usize>, source: &'s str) -> Self {
|
pub fn new(range: Range<usize>, source: Rc<String>) -> Self {
|
||||||
Span {
|
Span {
|
||||||
range,
|
range,
|
||||||
source,
|
source,
|
||||||
@@ -48,8 +48,8 @@ impl<'s> Span<'s> {
|
|||||||
pub fn as_str(&self) -> &str {
|
pub fn as_str(&self) -> &str {
|
||||||
&self.source[self.start..self.end]
|
&self.source[self.start..self.end]
|
||||||
}
|
}
|
||||||
pub fn get_source(&'s self) -> &'s str {
|
pub fn get_source(&self) -> Rc<String> {
|
||||||
self.source
|
self.source.clone()
|
||||||
}
|
}
|
||||||
pub fn range(&self) -> Range<usize> {
|
pub fn range(&self) -> Range<usize> {
|
||||||
self.range.clone()
|
self.range.clone()
|
||||||
@@ -57,7 +57,7 @@ impl<'s> Span<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Allows simple access to the underlying range wrapped by the span
|
/// Allows simple access to the underlying range wrapped by the span
|
||||||
impl<'s> Deref for Span<'s> {
|
impl Deref for Span {
|
||||||
type Target = Range<usize>;
|
type Target = Range<usize>;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.range
|
&self.range
|
||||||
@@ -90,15 +90,15 @@ impl Default for TkRule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq,Default)]
|
#[derive(Clone,Debug,PartialEq,Default)]
|
||||||
pub struct Tk<'s> {
|
pub struct Tk {
|
||||||
pub class: TkRule,
|
pub class: TkRule,
|
||||||
pub span: Span<'s>,
|
pub span: Span,
|
||||||
pub flags: TkFlags
|
pub flags: TkFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
// There's one impl here and then another in expand.rs which has the expansion logic
|
// There's one impl here and then another in expand.rs which has the expansion logic
|
||||||
impl<'s> Tk<'s> {
|
impl Tk {
|
||||||
pub fn new(class: TkRule, span: Span<'s>) -> Self {
|
pub fn new(class: TkRule, span: Span) -> Self {
|
||||||
Self { class, span, flags: TkFlags::empty() }
|
Self { class, span, flags: TkFlags::empty() }
|
||||||
}
|
}
|
||||||
pub fn to_string(&self) -> String {
|
pub fn to_string(&self) -> String {
|
||||||
@@ -107,12 +107,12 @@ impl<'s> Tk<'s> {
|
|||||||
_ => self.span.as_str().to_string()
|
_ => self.span.as_str().to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn source(&self) -> &'s str {
|
pub fn source(&self) -> Rc<String> {
|
||||||
self.span.source
|
self.span.source.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> Display for Tk<'s> {
|
impl Display for Tk {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self.class {
|
match &self.class {
|
||||||
TkRule::Expanded { exp } => write!(f,"{}",exp.join(" ")),
|
TkRule::Expanded { exp } => write!(f,"{}",exp.join(" ")),
|
||||||
@@ -135,8 +135,8 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LexStream<'t> {
|
pub struct LexStream {
|
||||||
source: &'t str,
|
source: Rc<String>,
|
||||||
pub cursor: usize,
|
pub cursor: usize,
|
||||||
in_quote: bool,
|
in_quote: bool,
|
||||||
flags: LexFlags,
|
flags: LexFlags,
|
||||||
@@ -162,8 +162,8 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> LexStream<'t> {
|
impl LexStream {
|
||||||
pub fn new(source: &'t str, flags: LexFlags) -> Self {
|
pub fn new(source: Rc<String>, flags: LexFlags) -> Self {
|
||||||
flog!(TRACE, "new lex stream");
|
flog!(TRACE, "new lex stream");
|
||||||
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
let flags = flags | LexFlags::FRESH | LexFlags::NEXT_IS_CMD;
|
||||||
Self { source, cursor: 0, in_quote: false, flags }
|
Self { source, cursor: 0, in_quote: false, flags }
|
||||||
@@ -178,7 +178,7 @@ impl<'t> LexStream<'t> {
|
|||||||
/// `LexStream.slice(..10)`
|
/// `LexStream.slice(..10)`
|
||||||
/// `LexStream.slice(1..)`
|
/// `LexStream.slice(1..)`
|
||||||
///
|
///
|
||||||
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> Option<&'t str> {
|
pub fn slice<R: RangeBounds<usize>>(&self, range: R) -> Option<&str> {
|
||||||
// Sketchy downcast
|
// Sketchy downcast
|
||||||
let start = match range.start_bound() {
|
let start = match range.start_bound() {
|
||||||
Bound::Included(&start) => start,
|
Bound::Included(&start) => start,
|
||||||
@@ -192,7 +192,7 @@ impl<'t> LexStream<'t> {
|
|||||||
};
|
};
|
||||||
self.source.get(start..end)
|
self.source.get(start..end)
|
||||||
}
|
}
|
||||||
pub fn slice_from_cursor(&self) -> Option<&'t str> {
|
pub fn slice_from_cursor(&self) -> Option<&str> {
|
||||||
self.slice(self.cursor..)
|
self.slice(self.cursor..)
|
||||||
}
|
}
|
||||||
pub fn in_brc_grp(&self) -> bool {
|
pub fn in_brc_grp(&self) -> bool {
|
||||||
@@ -216,7 +216,7 @@ impl<'t> LexStream<'t> {
|
|||||||
self.flags &= !LexFlags::NEXT_IS_CMD;
|
self.flags &= !LexFlags::NEXT_IS_CMD;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn read_redir(&mut self) -> Option<ShResult<Tk<'t>>> {
|
pub fn read_redir(&mut self) -> Option<ShResult<Tk>> {
|
||||||
assert!(self.cursor <= self.source.len());
|
assert!(self.cursor <= self.source.len());
|
||||||
let slice = self.slice(self.cursor..)?;
|
let slice = self.slice(self.cursor..)?;
|
||||||
let mut pos = self.cursor;
|
let mut pos = self.cursor;
|
||||||
@@ -248,7 +248,7 @@ impl<'t> LexStream<'t> {
|
|||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
"Invalid redirection",
|
"Invalid redirection",
|
||||||
Span::new(self.cursor..pos, self.source).into()
|
Span::new(self.cursor..pos, self.source.clone()).into()
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
} else {
|
} else {
|
||||||
@@ -294,9 +294,9 @@ impl<'t> LexStream<'t> {
|
|||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
Some(Ok(tk))
|
Some(Ok(tk))
|
||||||
}
|
}
|
||||||
pub fn read_string(&mut self) -> ShResult<Tk<'t>> {
|
pub fn read_string(&mut self) -> ShResult<Tk> {
|
||||||
assert!(self.cursor <= self.source.len());
|
assert!(self.cursor <= self.source.len());
|
||||||
let slice = self.slice_from_cursor().unwrap();
|
let slice = self.slice_from_cursor().unwrap().to_string();
|
||||||
let mut pos = self.cursor;
|
let mut pos = self.cursor;
|
||||||
let mut chars = slice.chars();
|
let mut chars = slice.chars();
|
||||||
let mut quote_pos = None;
|
let mut quote_pos = None;
|
||||||
@@ -393,14 +393,14 @@ impl<'t> LexStream<'t> {
|
|||||||
self.cursor = pos;
|
self.cursor = pos;
|
||||||
Ok(new_tk)
|
Ok(new_tk)
|
||||||
}
|
}
|
||||||
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk<'t> {
|
pub fn get_token(&self, range: Range<usize>, class: TkRule) -> Tk {
|
||||||
let span = Span::new(range, self.source);
|
let span = Span::new(range, self.source.clone());
|
||||||
Tk::new(class, span)
|
Tk::new(class, span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Iterator for LexStream<'t> {
|
impl Iterator for LexStream {
|
||||||
type Item = ShResult<Tk<'t>>;
|
type Item = ShResult<Tk>;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
assert!(self.cursor <= self.source.len());
|
assert!(self.cursor <= self.source.len());
|
||||||
// We are at the end of the input
|
// We are at the end of the input
|
||||||
@@ -432,7 +432,7 @@ impl<'t> Iterator for LexStream<'t> {
|
|||||||
let pos = self.cursor;
|
let pos = self.cursor;
|
||||||
if self.slice(pos..pos+2) == Some("\\\n") {
|
if self.slice(pos..pos+2) == Some("\\\n") {
|
||||||
self.cursor += 2;
|
self.cursor += 2;
|
||||||
} else if pos < self.source.len() && is_field_sep(get_char(self.source, pos).unwrap()) {
|
} else if pos < self.source.len() && is_field_sep(get_char(&self.source, pos).unwrap()) {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@@ -443,13 +443,13 @@ impl<'t> Iterator for LexStream<'t> {
|
|||||||
return None
|
return None
|
||||||
}
|
}
|
||||||
|
|
||||||
let token = match get_char(self.source, self.cursor).unwrap() {
|
let token = match get_char(&self.source, self.cursor).unwrap() {
|
||||||
'\r' | '\n' | ';' => {
|
'\r' | '\n' | ';' => {
|
||||||
let ch_idx = self.cursor;
|
let ch_idx = self.cursor;
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
|
|
||||||
while let Some(ch) = get_char(self.source, self.cursor) {
|
while let Some(ch) = get_char(&self.source, self.cursor) {
|
||||||
if is_hard_sep(ch) { // Combine consecutive separators into one, including whitespace
|
if is_hard_sep(ch) { // Combine consecutive separators into one, including whitespace
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
} else {
|
} else {
|
||||||
@@ -462,7 +462,7 @@ impl<'t> Iterator for LexStream<'t> {
|
|||||||
let ch_idx = self.cursor;
|
let ch_idx = self.cursor;
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
|
|
||||||
while let Some(ch) = get_char(self.source, self.cursor) {
|
while let Some(ch) = get_char(&self.source, self.cursor) {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
break
|
break
|
||||||
@@ -476,10 +476,10 @@ impl<'t> Iterator for LexStream<'t> {
|
|||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
|
|
||||||
let tk_type = if let Some('|') = get_char(self.source, self.cursor) {
|
let tk_type = if let Some('|') = get_char(&self.source, self.cursor) {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
TkRule::Or
|
TkRule::Or
|
||||||
} else if let Some('&') = get_char(self.source, self.cursor) {
|
} else if let Some('&') = get_char(&self.source, self.cursor) {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
TkRule::ErrPipe
|
TkRule::ErrPipe
|
||||||
} else {
|
} else {
|
||||||
@@ -493,7 +493,7 @@ impl<'t> Iterator for LexStream<'t> {
|
|||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
self.set_next_is_cmd(true);
|
self.set_next_is_cmd(true);
|
||||||
|
|
||||||
let tk_type = if let Some('&') = get_char(self.source, self.cursor) {
|
let tk_type = if let Some('&') = get_char(&self.source, self.cursor) {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
TkRule::And
|
TkRule::And
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
154
src/parse/mod.rs
154
src/parse/mod.rs
@@ -2,7 +2,7 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use fmt::Display;
|
use fmt::Display;
|
||||||
use lex::{Span, Tk, TkFlags, TkRule};
|
use lex::{LexFlags, LexStream, Span, Tk, TkFlags, TkRule};
|
||||||
|
|
||||||
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode};
|
use crate::{libsh::{error::{ShErr, ShErrKind, ShResult}, utils::TkVecUtils}, prelude::*, procio::IoMode};
|
||||||
|
|
||||||
@@ -22,23 +22,71 @@ macro_rules! try_match {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The parsed AST along with the source input it parsed
|
||||||
|
///
|
||||||
|
/// Uses Rc<String> instead of &str because the reference has to stay alive while errors are propagated upwards
|
||||||
|
/// The string also has to stay alive in the case of pre-parsed shell function nodes, which live in the logic table
|
||||||
|
/// Using &str for this use-case dramatically overcomplicates the code
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct Node<'t> {
|
pub struct ParsedSrc {
|
||||||
pub class: NdRule<'t>,
|
pub src: Rc<String>,
|
||||||
pub flags: NdFlags,
|
pub ast: Ast
|
||||||
pub redirs: Vec<Redir>,
|
|
||||||
pub tokens: Vec<Tk<'t>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Node<'t> {
|
impl ParsedSrc {
|
||||||
pub fn get_command(&'t self) -> Option<&'t Tk<'t>> {
|
pub fn new(src: Rc<String>) -> Self {
|
||||||
|
Self { src, ast: Ast::new(vec![]) }
|
||||||
|
}
|
||||||
|
pub fn parse_src(&mut self) -> ShResult<()> {
|
||||||
|
let mut tokens = vec![];
|
||||||
|
for token in LexStream::new(self.src.clone(), LexFlags::empty()) {
|
||||||
|
tokens.push(token?);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut nodes = vec![];
|
||||||
|
for result in ParseStream::new(tokens) {
|
||||||
|
nodes.push(result?);
|
||||||
|
}
|
||||||
|
*self.ast.tree_mut() = nodes;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn extract_nodes(&mut self) -> Vec<Node> {
|
||||||
|
mem::take(self.ast.tree_mut())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct Ast(Vec<Node>);
|
||||||
|
|
||||||
|
impl Ast {
|
||||||
|
pub fn new(tree: Vec<Node>) -> Self {
|
||||||
|
Self(tree)
|
||||||
|
}
|
||||||
|
pub fn into_inner(self) -> Vec<Node> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
pub fn tree_mut(&mut self) -> &mut Vec<Node> {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct Node {
|
||||||
|
pub class: NdRule,
|
||||||
|
pub flags: NdFlags,
|
||||||
|
pub redirs: Vec<Redir>,
|
||||||
|
pub tokens: Vec<Tk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node {
|
||||||
|
pub fn get_command(&self) -> Option<&Tk> {
|
||||||
let NdRule::Command { assignments: _, argv } = &self.class else {
|
let NdRule::Command { assignments: _, argv } = &self.class else {
|
||||||
return None
|
return None
|
||||||
};
|
};
|
||||||
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
|
let command = argv.iter().find(|tk| tk.flags.contains(TkFlags::IS_CMD))?;
|
||||||
Some(command)
|
Some(command)
|
||||||
}
|
}
|
||||||
pub fn get_span(&'t self) -> Span<'t> {
|
pub fn get_span(&self) -> Span {
|
||||||
let Some(first_tk) = self.tokens.first() else {
|
let Some(first_tk) = self.tokens.first() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -187,15 +235,15 @@ pub enum RedirType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct CondNode<'t> {
|
pub struct CondNode {
|
||||||
pub cond: Box<Node<'t>>,
|
pub cond: Box<Node>,
|
||||||
pub body: Vec<Node<'t>>
|
pub body: Vec<Node>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct CaseNode<'t> {
|
pub struct CaseNode {
|
||||||
pub pattern: Tk<'t>,
|
pub pattern: Tk,
|
||||||
pub body: Vec<Node<'t>>
|
pub body: Vec<Node>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Copy,PartialEq,Debug)]
|
#[derive(Clone,Copy,PartialEq,Debug)]
|
||||||
@@ -206,8 +254,8 @@ pub enum ConjunctOp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub struct ConjunctNode<'t> {
|
pub struct ConjunctNode {
|
||||||
pub cmd: Box<Node<'t>>,
|
pub cmd: Box<Node>,
|
||||||
pub operator: ConjunctOp
|
pub operator: ConjunctOp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,7 +277,7 @@ impl FromStr for LoopKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Display for LoopKind {
|
impl Display for LoopKind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
LoopKind::While => write!(f,"while"),
|
LoopKind::While => write!(f,"while"),
|
||||||
LoopKind::Until => write!(f,"until")
|
LoopKind::Until => write!(f,"until")
|
||||||
@@ -247,22 +295,22 @@ pub enum AssignKind {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone,Debug)]
|
||||||
pub enum NdRule<'t> {
|
pub enum NdRule {
|
||||||
IfNode { cond_nodes: Vec<CondNode<'t>>, else_block: Vec<Node<'t>> },
|
IfNode { cond_nodes: Vec<CondNode>, else_block: Vec<Node> },
|
||||||
LoopNode { kind: LoopKind, cond_node: CondNode<'t> },
|
LoopNode { kind: LoopKind, cond_node: CondNode },
|
||||||
ForNode { vars: Vec<Tk<'t>>, arr: Vec<Tk<'t>>, body: Vec<Node<'t>> },
|
ForNode { vars: Vec<Tk>, arr: Vec<Tk>, body: Vec<Node> },
|
||||||
CaseNode { pattern: Tk<'t>, case_blocks: Vec<CaseNode<'t>> },
|
CaseNode { pattern: Tk, case_blocks: Vec<CaseNode> },
|
||||||
Command { assignments: Vec<Node<'t>>, argv: Vec<Tk<'t>> },
|
Command { assignments: Vec<Node>, argv: Vec<Tk> },
|
||||||
Pipeline { cmds: Vec<Node<'t>>, pipe_err: bool },
|
Pipeline { cmds: Vec<Node>, pipe_err: bool },
|
||||||
Conjunction { elements: Vec<ConjunctNode<'t>> },
|
Conjunction { elements: Vec<ConjunctNode> },
|
||||||
Assignment { kind: AssignKind, var: Tk<'t>, val: Tk<'t> },
|
Assignment { kind: AssignKind, var: Tk, val: Tk },
|
||||||
BraceGrp { body: Vec<Node<'t>> },
|
BraceGrp { body: Vec<Node> },
|
||||||
FuncDef { name: Tk<'t>, body: Box<Node<'t>> }
|
FuncDef { name: Tk, body: Box<Node> }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ParseStream<'t> {
|
pub struct ParseStream {
|
||||||
pub tokens: Vec<Tk<'t>>,
|
pub tokens: Vec<Tk>,
|
||||||
pub flags: ParseFlags
|
pub flags: ParseFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,8 +321,8 @@ bitflags! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> ParseStream<'t> {
|
impl ParseStream {
|
||||||
pub fn new(tokens: Vec<Tk<'t>>) -> Self {
|
pub fn new(tokens: Vec<Tk>) -> Self {
|
||||||
Self { tokens, flags: ParseFlags::empty() }
|
Self { tokens, flags: ParseFlags::empty() }
|
||||||
}
|
}
|
||||||
fn next_tk_class(&self) -> &TkRule {
|
fn next_tk_class(&self) -> &TkRule {
|
||||||
@@ -284,10 +332,10 @@ impl<'t> ParseStream<'t> {
|
|||||||
&TkRule::Null
|
&TkRule::Null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn peek_tk(&self) -> Option<&Tk<'t>> {
|
fn peek_tk(&self) -> Option<&Tk> {
|
||||||
self.tokens.first()
|
self.tokens.first()
|
||||||
}
|
}
|
||||||
fn next_tk(&mut self) -> Option<Tk<'t>> {
|
fn next_tk(&mut self) -> Option<Tk> {
|
||||||
if !self.tokens.is_empty() {
|
if !self.tokens.is_empty() {
|
||||||
if *self.next_tk_class() == TkRule::EOI {
|
if *self.next_tk_class() == TkRule::EOI {
|
||||||
return None
|
return None
|
||||||
@@ -306,12 +354,12 @@ impl<'t> ParseStream<'t> {
|
|||||||
/// fi
|
/// fi
|
||||||
/// ```
|
/// ```
|
||||||
/// are valid syntax
|
/// are valid syntax
|
||||||
fn catch_separator(&mut self, node_tks: &mut Vec<Tk<'t>>) {
|
fn catch_separator(&mut self, node_tks: &mut Vec<Tk>) {
|
||||||
if *self.next_tk_class() == TkRule::Sep {
|
if *self.next_tk_class() == TkRule::Sep {
|
||||||
node_tks.push(self.next_tk().unwrap());
|
node_tks.push(self.next_tk().unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn assert_separator(&mut self, node_tks: &mut Vec<Tk<'t>>) -> ShResult<()> {
|
fn assert_separator(&mut self, node_tks: &mut Vec<Tk>) -> ShResult<()> {
|
||||||
let next_class = self.next_tk_class();
|
let next_class = self.next_tk_class();
|
||||||
match next_class {
|
match next_class {
|
||||||
TkRule::EOI |
|
TkRule::EOI |
|
||||||
@@ -352,7 +400,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
assert!(num_consumed <= self.tokens.len());
|
assert!(num_consumed <= self.tokens.len());
|
||||||
self.tokens = self.tokens[num_consumed..].to_vec();
|
self.tokens = self.tokens[num_consumed..].to_vec();
|
||||||
}
|
}
|
||||||
fn parse_cmd_list(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_cmd_list(&mut self) -> ShResult<Option<Node>> {
|
||||||
let mut elements = vec![];
|
let mut elements = vec![];
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
|
|
||||||
@@ -390,7 +438,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
/// Matches shell commands like if-then-fi, pipelines, etc.
|
/// Matches shell commands like if-then-fi, pipelines, etc.
|
||||||
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
/// Ordered from specialized to general, with more generally matchable stuff appearing at the bottom
|
||||||
/// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeline()
|
/// The check_pipelines parameter is used to prevent left-recursion issues in self.parse_pipeline()
|
||||||
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node<'t>>> {
|
fn parse_block(&mut self, check_pipelines: bool) -> ShResult<Option<Node>> {
|
||||||
try_match!(self.parse_func_def()?);
|
try_match!(self.parse_func_def()?);
|
||||||
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
|
try_match!(self.parse_brc_grp(false /* from_func_def */)?);
|
||||||
try_match!(self.parse_loop()?);
|
try_match!(self.parse_loop()?);
|
||||||
@@ -402,7 +450,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
fn parse_func_def(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_func_def(&mut self) -> ShResult<Option<Node>> {
|
||||||
let mut node_tks: Vec<Tk> = vec![];
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
let name;
|
let name;
|
||||||
let body;
|
let body;
|
||||||
@@ -429,11 +477,10 @@ impl<'t> ParseStream<'t> {
|
|||||||
redirs: vec![],
|
redirs: vec![],
|
||||||
tokens: node_tks
|
tokens: node_tks
|
||||||
};
|
};
|
||||||
flog!(DEBUG,node);
|
|
||||||
|
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node<'t>>> {
|
fn parse_brc_grp(&mut self, from_func_def: bool) -> ShResult<Option<Node>> {
|
||||||
let mut node_tks: Vec<Tk> = vec![];
|
let mut node_tks: Vec<Tk> = vec![];
|
||||||
let mut body: Vec<Node> = vec![];
|
let mut body: Vec<Node> = vec![];
|
||||||
let mut redirs: Vec<Redir> = vec![];
|
let mut redirs: Vec<Redir> = vec![];
|
||||||
@@ -507,10 +554,9 @@ impl<'t> ParseStream<'t> {
|
|||||||
redirs,
|
redirs,
|
||||||
tokens: node_tks
|
tokens: node_tks
|
||||||
};
|
};
|
||||||
flog!(DEBUG, node);
|
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn parse_if(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_if(&mut self) -> ShResult<Option<Node>> {
|
||||||
// Needs at last one 'if-then',
|
// Needs at last one 'if-then',
|
||||||
// Any number of 'elif-then',
|
// Any number of 'elif-then',
|
||||||
// Zero or one 'else'
|
// Zero or one 'else'
|
||||||
@@ -639,10 +685,10 @@ impl<'t> ParseStream<'t> {
|
|||||||
};
|
};
|
||||||
Ok(Some(node))
|
Ok(Some(node))
|
||||||
}
|
}
|
||||||
fn parse_loop(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_loop(&mut self) -> ShResult<Option<Node>> {
|
||||||
// Requires a single CondNode and a LoopKind
|
// Requires a single CondNode and a LoopKind
|
||||||
let loop_kind: LoopKind;
|
let loop_kind: LoopKind;
|
||||||
let cond_node: CondNode<'t>;
|
let cond_node: CondNode;
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
|
|
||||||
if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() {
|
if (!self.check_keyword("while") && !self.check_keyword("until")) || !self.next_tk_is_some() {
|
||||||
@@ -703,7 +749,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
};
|
};
|
||||||
Ok(Some(loop_node))
|
Ok(Some(loop_node))
|
||||||
}
|
}
|
||||||
fn parse_pipeline(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_pipeline(&mut self) -> ShResult<Option<Node>> {
|
||||||
let mut cmds = vec![];
|
let mut cmds = vec![];
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
while let Some(cmd) = self.parse_block(false)? {
|
while let Some(cmd) = self.parse_block(false)? {
|
||||||
@@ -732,7 +778,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn parse_cmd(&mut self) -> ShResult<Option<Node<'t>>> {
|
fn parse_cmd(&mut self) -> ShResult<Option<Node>> {
|
||||||
let tk_slice = self.tokens.as_slice();
|
let tk_slice = self.tokens.as_slice();
|
||||||
let mut tk_iter = tk_slice.iter();
|
let mut tk_iter = tk_slice.iter();
|
||||||
let mut node_tks = vec![];
|
let mut node_tks = vec![];
|
||||||
@@ -827,7 +873,7 @@ impl<'t> ParseStream<'t> {
|
|||||||
redirs,
|
redirs,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
fn parse_assignment(&self, token: &Tk<'t>) -> Option<Node<'t>> {
|
fn parse_assignment(&self, token: &Tk) -> Option<Node> {
|
||||||
let mut chars = token.span.as_str().chars();
|
let mut chars = token.span.as_str().chars();
|
||||||
let mut var_name = String::new();
|
let mut var_name = String::new();
|
||||||
let mut name_range = token.span.start..token.span.start;
|
let mut name_range = token.span.start..token.span.start;
|
||||||
@@ -930,8 +976,8 @@ impl<'t> ParseStream<'t> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Iterator for ParseStream<'t> {
|
impl Iterator for ParseStream {
|
||||||
type Item = ShResult<Node<'t>>;
|
type Item = ShResult<Node>;
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
// Empty token vector or only SOI/EOI tokens, nothing to do
|
// Empty token vector or only SOI/EOI tokens, nothing to do
|
||||||
if self.tokens.is_empty() || self.tokens.len() == 2 {
|
if self.tokens.is_empty() || self.tokens.len() == 2 {
|
||||||
@@ -960,7 +1006,7 @@ impl<'t> Iterator for ParseStream<'t> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn node_is_punctuated<'t>(tokens: &Vec<Tk>) -> bool {
|
fn node_is_punctuated(tokens: &Vec<Tk>) -> bool {
|
||||||
tokens.last().is_some_and(|tk| {
|
tokens.last().is_some_and(|tk| {
|
||||||
matches!(tk.class, TkRule::Sep)
|
matches!(tk.class, TkRule::Sep)
|
||||||
})
|
})
|
||||||
@@ -992,7 +1038,7 @@ fn get_redir_file(class: RedirType, path: PathBuf) -> ShResult<File> {
|
|||||||
Ok(result?)
|
Ok(result?)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr {
|
fn parse_err_full(reason: &str, blame: &Span) -> ShErr {
|
||||||
ShErr::full(
|
ShErr::full(
|
||||||
ShErrKind::ParseErr,
|
ShErrKind::ParseErr,
|
||||||
reason,
|
reason,
|
||||||
@@ -1000,7 +1046,7 @@ fn parse_err_full<'t>(reason: &str, blame: &Span<'t>) -> ShErr {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_func_name<'t>(tk: Option<&Tk<'t>>) -> bool {
|
fn is_func_name(tk: Option<&Tk>) -> bool {
|
||||||
tk.is_some_and(|tk| {
|
tk.is_some_and(|tk| {
|
||||||
tk.flags.contains(TkFlags::KEYWORD) &&
|
tk.flags.contains(TkFlags::KEYWORD) &&
|
||||||
(tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()"))
|
(tk.span.as_str().ends_with("()") && !tk.span.as_str().ends_with("\\()"))
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ impl DerefMut for IoFrame {
|
|||||||
/// Each executed command requires an `IoFrame` in order to perform redirections.
|
/// Each executed command requires an `IoFrame` in order to perform redirections.
|
||||||
/// As nodes are walked through by the `Dispatcher`, it pushes new frames in certain contexts, and pops frames in others.
|
/// As nodes are walked through by the `Dispatcher`, it pushes new frames in certain contexts, and pops frames in others.
|
||||||
/// Each command calls pop_frame() in order to get the current IoFrame in order to perform redirection
|
/// Each command calls pop_frame() in order to get the current IoFrame in order to perform redirection
|
||||||
#[derive(Default)]
|
#[derive(Debug,Default)]
|
||||||
pub struct IoStack {
|
pub struct IoStack {
|
||||||
stack: Vec<IoFrame>,
|
stack: Vec<IoFrame>,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ use std::path::Path;
|
|||||||
use readline::FernReadline;
|
use readline::FernReadline;
|
||||||
use rustyline::{error::ReadlineError, history::FileHistory, Editor};
|
use rustyline::{error::ReadlineError, history::FileHistory, Editor};
|
||||||
|
|
||||||
use crate::{libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
use crate::{expand::expand_prompt, libsh::{error::ShResult, term::{Style, Styled}}, prelude::*};
|
||||||
|
|
||||||
fn init_rl<'s>() -> ShResult<Editor<FernReadline,FileHistory>> {
|
fn init_rl() -> ShResult<Editor<FernReadline,FileHistory>> {
|
||||||
let rl = FernReadline::new();
|
let rl = FernReadline::new();
|
||||||
let mut editor = Editor::new()?;
|
let mut editor = Editor::new()?;
|
||||||
editor.set_helper(Some(rl));
|
editor.set_helper(Some(rl));
|
||||||
@@ -16,15 +16,35 @@ fn init_rl<'s>() -> ShResult<Editor<FernReadline,FileHistory>> {
|
|||||||
Ok(editor)
|
Ok(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_line<'s>() -> ShResult<String> {
|
fn get_prompt() -> ShResult<String> {
|
||||||
|
let Ok(prompt) = env::var("PS1") else {
|
||||||
|
return Ok("$ ".styled(Style::Green | Style::Bold))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!("\n{}",expand_prompt(&prompt)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_hist_path() -> ShResult<PathBuf> {
|
||||||
|
if let Ok(path) = env::var("FERN_HIST") {
|
||||||
|
Ok(PathBuf::from(path))
|
||||||
|
} else {
|
||||||
|
let home = env::var("HOME")?;
|
||||||
|
Ok(PathBuf::from(format!("{home}/.fernhist")))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_line() -> ShResult<String> {
|
||||||
assert!(isatty(STDIN_FILENO).unwrap());
|
assert!(isatty(STDIN_FILENO).unwrap());
|
||||||
let mut editor = init_rl()?;
|
let mut editor = init_rl()?;
|
||||||
let prompt = "$ ".styled(Style::Green | Style::Bold);
|
let prompt = get_prompt()?;
|
||||||
match editor.readline(&prompt) {
|
match editor.readline(&prompt) {
|
||||||
Ok(line) => {
|
Ok(line) => {
|
||||||
if !line.is_empty() {
|
if !line.is_empty() {
|
||||||
|
let hist_path = get_hist_path()?;
|
||||||
|
flog!(DEBUG, hist_path);
|
||||||
editor.add_history_entry(&line)?;
|
editor.add_history_entry(&line)?;
|
||||||
editor.save_history(&Path::new("/home/pagedmov/.fernhist"))?;
|
editor.save_history(&hist_path)?;
|
||||||
}
|
}
|
||||||
Ok(line)
|
Ok(line)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::borrow::Cow;
|
|||||||
use rustyline::{completion::Completer, highlight::Highlighter, hint::{Hint, Hinter}, validate::{ValidationResult, Validator}, Helper};
|
use rustyline::{completion::Completer, highlight::Highlighter, hint::{Hint, Hinter}, validate::{ValidationResult, Validator}, Helper};
|
||||||
|
|
||||||
use crate::{libsh::term::{Style, Styled}, parse::{lex::{LexFlags, LexStream}, ParseStream}};
|
use crate::{libsh::term::{Style, Styled}, parse::{lex::{LexFlags, LexStream}, ParseStream}};
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
pub struct FernReadline {
|
pub struct FernReadline {
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,7 @@ impl Validator for FernReadline {
|
|||||||
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
fn validate(&self, ctx: &mut rustyline::validate::ValidationContext) -> rustyline::Result<rustyline::validate::ValidationResult> {
|
||||||
return Ok(ValidationResult::Valid(None));
|
return Ok(ValidationResult::Valid(None));
|
||||||
let mut tokens = vec![];
|
let mut tokens = vec![];
|
||||||
let tk_stream = LexStream::new(ctx.input(), LexFlags::empty());
|
let tk_stream = LexStream::new(Rc::new(ctx.input().to_string()), LexFlags::empty());
|
||||||
for tk in tk_stream {
|
for tk in tk_stream {
|
||||||
if tk.is_err() {
|
if tk.is_err() {
|
||||||
return Ok(ValidationResult::Incomplete)
|
return Ok(ValidationResult::Incomplete)
|
||||||
|
|||||||
167
src/state.rs
167
src/state.rs
@@ -1,23 +1,59 @@
|
|||||||
use std::{cell::RefCell, collections::{HashMap, VecDeque}, ops::Range, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}};
|
use std::{collections::{HashMap, VecDeque}, ops::{Deref, Range}, sync::{LazyLock, RwLock, RwLockReadGuard, RwLockWriteGuard}, time::Duration};
|
||||||
|
|
||||||
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, Node}, prelude::*};
|
use nix::unistd::{gethostname, getppid, User};
|
||||||
|
|
||||||
|
use crate::{exec_input, jobs::JobTab, libsh::{error::ShResult, utils::VecDequeExt}, parse::{lex::{get_char, Tk}, ConjunctNode, NdRule, Node, ParsedSrc}, prelude::*};
|
||||||
|
|
||||||
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()));
|
||||||
|
|
||||||
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
pub static VAR_TABLE: LazyLock<RwLock<VarTab>> = LazyLock::new(|| RwLock::new(VarTab::new()));
|
||||||
|
|
||||||
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
|
pub static META_TABLE: LazyLock<RwLock<MetaTab>> = LazyLock::new(|| RwLock::new(MetaTab::new()));
|
||||||
|
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
pub static LAST_INPUT: RefCell<String> = RefCell::new(String::new());
|
pub static LOGIC_TABLE: LazyLock<RwLock<LogTab>> = LazyLock::new(|| RwLock::new(LogTab::new()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shell function
|
||||||
|
///
|
||||||
|
/// Consists of the BraceGrp Node and the stored ParsedSrc that the node refers to
|
||||||
|
/// The Node must be stored with the ParsedSrc because the tokens of the node contain an Rc<String>
|
||||||
|
/// Which refers to the String held in ParsedSrc
|
||||||
|
///
|
||||||
|
/// Can be dereferenced to pull out the wrapped Node
|
||||||
|
#[derive(Clone,Debug)]
|
||||||
|
pub struct ShFunc(Node);
|
||||||
|
|
||||||
|
impl ShFunc {
|
||||||
|
pub fn new(mut src: ParsedSrc) -> Self {
|
||||||
|
let body = Self::extract_brc_grp_hack(src.extract_nodes());
|
||||||
|
Self(body)
|
||||||
|
}
|
||||||
|
fn extract_brc_grp_hack(mut tree: Vec<Node>) -> Node {
|
||||||
|
// FIXME: find a better way to do this
|
||||||
|
let conjunction = tree.pop().unwrap();
|
||||||
|
let NdRule::Conjunction { mut elements } = conjunction.class else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
let conjunct_node = elements.pop().unwrap();
|
||||||
|
let ConjunctNode { cmd, operator: _ } = conjunct_node;
|
||||||
|
*cmd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for ShFunc {
|
||||||
|
type Target = Node;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The logic table for the shell
|
/// The logic table for the shell
|
||||||
///
|
///
|
||||||
/// Contains aliases and functions
|
/// Contains aliases and functions
|
||||||
pub struct LogTab {
|
pub struct LogTab {
|
||||||
// TODO: Find a way to store actual owned nodes instead of strings that must be re-parsed
|
functions: HashMap<String,ShFunc>,
|
||||||
functions: HashMap<String,String>,
|
|
||||||
aliases: HashMap<String,String>
|
aliases: HashMap<String,String>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,10 +61,10 @@ impl LogTab {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { functions: HashMap::new(), aliases: HashMap::new() }
|
Self { functions: HashMap::new(), aliases: HashMap::new() }
|
||||||
}
|
}
|
||||||
pub fn insert_func(&mut self, name: &str, body: &str) {
|
pub fn insert_func(&mut self, name: &str, src: ShFunc) {
|
||||||
self.functions.insert(name.into(), body.into());
|
self.functions.insert(name.into(), src);
|
||||||
}
|
}
|
||||||
pub fn get_func(&self, name: &str) -> Option<String> {
|
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
|
||||||
self.functions.get(name).cloned()
|
self.functions.get(name).cloned()
|
||||||
}
|
}
|
||||||
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
||||||
@@ -39,6 +75,7 @@ impl LogTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct VarTab {
|
pub struct VarTab {
|
||||||
vars: HashMap<String,String>,
|
vars: HashMap<String,String>,
|
||||||
params: HashMap<char,String>,
|
params: HashMap<char,String>,
|
||||||
@@ -49,6 +86,7 @@ impl VarTab {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let vars = HashMap::new();
|
let vars = HashMap::new();
|
||||||
let params = Self::init_params();
|
let params = Self::init_params();
|
||||||
|
Self::init_env();
|
||||||
let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() };
|
let mut var_tab = Self { vars, params, sh_argv: VecDeque::new() };
|
||||||
var_tab.init_sh_argv();
|
var_tab.init_sh_argv();
|
||||||
var_tab
|
var_tab
|
||||||
@@ -62,6 +100,51 @@ impl VarTab {
|
|||||||
params.insert('!', "".into()); // PID of the last background job (if any)
|
params.insert('!', "".into()); // PID of the last background job (if any)
|
||||||
params
|
params
|
||||||
}
|
}
|
||||||
|
fn init_env() {
|
||||||
|
let pathbuf_to_string = |pb: Result<PathBuf, std::io::Error>| pb.unwrap_or_default().to_string_lossy().to_string();
|
||||||
|
// First, inherit any env vars from the parent process
|
||||||
|
let term = {
|
||||||
|
if isatty(1).unwrap() {
|
||||||
|
if let Ok(term) = std::env::var("TERM") {
|
||||||
|
term
|
||||||
|
} else {
|
||||||
|
"linux".to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"xterm-256color".to_string()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let home;
|
||||||
|
let username;
|
||||||
|
let uid;
|
||||||
|
if let Some(user) = User::from_uid(nix::unistd::Uid::current()).ok().flatten() {
|
||||||
|
home = user.dir;
|
||||||
|
username = user.name;
|
||||||
|
uid = user.uid;
|
||||||
|
} else {
|
||||||
|
home = PathBuf::new();
|
||||||
|
username = "unknown".into();
|
||||||
|
uid = 0.into();
|
||||||
|
}
|
||||||
|
let home = pathbuf_to_string(Ok(home));
|
||||||
|
let hostname = gethostname().map(|hname| hname.to_string_lossy().to_string()).unwrap_or_default();
|
||||||
|
|
||||||
|
env::set_var("IFS", " \t\n");
|
||||||
|
env::set_var("HOSTNAME", hostname);
|
||||||
|
env::set_var("UID", uid.to_string());
|
||||||
|
env::set_var("PPID", getppid().to_string());
|
||||||
|
env::set_var("TMPDIR", "/tmp");
|
||||||
|
env::set_var("TERM", term);
|
||||||
|
env::set_var("LANG", "en_US.UTF-8");
|
||||||
|
env::set_var("USER", username.clone());
|
||||||
|
env::set_var("LOGNAME", username);
|
||||||
|
env::set_var("PWD", pathbuf_to_string(std::env::current_dir()));
|
||||||
|
env::set_var("OLDPWD", pathbuf_to_string(std::env::current_dir()));
|
||||||
|
env::set_var("HOME", home.clone());
|
||||||
|
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||||
|
env::set_var("FERN_HIST",format!("{}/.fern_hist",home));
|
||||||
|
env::set_var("FERN_RC",format!("{}/.fernrc",home));
|
||||||
|
}
|
||||||
pub fn init_sh_argv(&mut self) {
|
pub fn init_sh_argv(&mut self) {
|
||||||
for arg in env::args() {
|
for arg in env::args() {
|
||||||
self.bpush_arg(arg);
|
self.bpush_arg(arg);
|
||||||
@@ -74,11 +157,15 @@ impl VarTab {
|
|||||||
&mut self.sh_argv
|
&mut self.sh_argv
|
||||||
}
|
}
|
||||||
pub fn clear_args(&mut self) {
|
pub fn clear_args(&mut self) {
|
||||||
self.sh_argv.clear()
|
self.sh_argv.clear();
|
||||||
|
// Push the current exe again
|
||||||
|
// This makes sure that $0 is always the current shell, no matter what
|
||||||
|
// It also updates the arg parameters '@' and '#' as well
|
||||||
|
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().join(" "));
|
self.set_param('@', &self.sh_argv.clone().to_vec()[1..].join(" "));
|
||||||
self.set_param('#', &self.sh_argv.len().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) {
|
||||||
@@ -139,7 +226,8 @@ impl VarTab {
|
|||||||
.to_string()
|
.to_string()
|
||||||
.parse::<usize>()
|
.parse::<usize>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
return 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
|
||||||
} else if param == '?' {
|
} else if param == '?' {
|
||||||
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into())
|
self.params.get(¶m).map(|s| s.to_string()).unwrap_or("0".into())
|
||||||
} else {
|
} else {
|
||||||
@@ -148,6 +236,25 @@ impl VarTab {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A table of metadata for the shell
|
||||||
|
pub struct MetaTab {
|
||||||
|
runtime_start: Option<Instant>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MetaTab {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { runtime_start: None }
|
||||||
|
}
|
||||||
|
pub fn start_timer(&mut self) {
|
||||||
|
self.runtime_start = Some(Instant::now());
|
||||||
|
}
|
||||||
|
pub fn stop_timer(&mut self) -> Option<Duration> {
|
||||||
|
self.runtime_start
|
||||||
|
.take() // runtime_start returns to None
|
||||||
|
.map(|start| start.elapsed()) // return the duration, if any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Read from the job table
|
/// Read from the job table
|
||||||
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
pub fn read_jobs<T, F: FnOnce(RwLockReadGuard<JobTab>) -> T>(f: F) -> T {
|
||||||
let lock = JOB_TABLE.read().unwrap();
|
let lock = JOB_TABLE.read().unwrap();
|
||||||
@@ -172,30 +279,30 @@ pub fn write_vars<T, F: FnOnce(&mut RwLockWriteGuard<VarTab>) -> T>(f: F) -> T {
|
|||||||
f(lock)
|
f(lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn read_meta<T, F: FnOnce(RwLockReadGuard<MetaTab>) -> T>(f: F) -> T {
|
||||||
|
let lock = META_TABLE.read().unwrap();
|
||||||
|
f(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write to the variable table
|
||||||
|
pub fn write_meta<T, F: FnOnce(&mut RwLockWriteGuard<MetaTab>) -> T>(f: F) -> T {
|
||||||
|
let lock = &mut META_TABLE.write().unwrap();
|
||||||
|
f(lock)
|
||||||
|
}
|
||||||
|
|
||||||
/// Read from the logic table
|
/// Read from the logic table
|
||||||
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
pub fn read_logic<T, F: FnOnce(RwLockReadGuard<LogTab>) -> T>(f: F) -> T {
|
||||||
let lock = LOGIC_TABLE.read().unwrap();
|
LOGIC_TABLE.with(|log| {
|
||||||
|
let lock = log.read().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write to the logic table
|
/// Write to the logic table
|
||||||
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
pub fn write_logic<T, F: FnOnce(&mut RwLockWriteGuard<LogTab>) -> T>(f: F) -> T {
|
||||||
let lock = &mut LOGIC_TABLE.write().unwrap();
|
LOGIC_TABLE.with(|log| {
|
||||||
|
let lock = &mut log.write().unwrap();
|
||||||
f(lock)
|
f(lock)
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_last_input(input: &str) {
|
|
||||||
LAST_INPUT.with(|input_ref| {
|
|
||||||
let mut last_input = input_ref.borrow_mut();
|
|
||||||
last_input.clear();
|
|
||||||
last_input.push_str(input);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slice_last_input(range: Range<usize>) -> String {
|
|
||||||
LAST_INPUT.with(|input_ref| {
|
|
||||||
let input = input_ref.borrow();
|
|
||||||
input[range].to_string()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,6 +320,6 @@ pub fn source_file(path: PathBuf) -> ShResult<()> {
|
|||||||
|
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
file.read_to_string(&mut buf)?;
|
file.read_to_string(&mut buf)?;
|
||||||
exec_input(&buf, None)?;
|
exec_input(buf)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user