Re-implemented aliases
This commit is contained in:
43
src/builtin/alias.rs
Normal file
43
src/builtin/alias.rs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
use crate::{jobs::JobBldr, libsh::error::{ShErr, ShErrKind, ShResult}, parse::{NdRule, Node}, prelude::*, procio::{borrow_fd, IoStack}, state::{self, read_logic, write_logic}};
|
||||||
|
|
||||||
|
use super::setup_builtin;
|
||||||
|
|
||||||
|
pub fn alias(node: Node, io_stack: &mut IoStack, job: &mut JobBldr) -> ShResult<()> {
|
||||||
|
let NdRule::Command { assignments: _, argv } = node.class else {
|
||||||
|
unreachable!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let (argv,io_frame) = setup_builtin(argv, job, Some((io_stack,node.redirs)))?;
|
||||||
|
|
||||||
|
if argv.is_empty() {
|
||||||
|
// Display the environment variables
|
||||||
|
let mut alias_output = read_logic(|l| {
|
||||||
|
l.aliases()
|
||||||
|
.iter()
|
||||||
|
.map(|ent| format!("{} = \"{}\"", ent.0, ent.1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
alias_output.sort(); // Sort them alphabetically
|
||||||
|
let mut alias_output = alias_output.join("\n"); // Join them with newlines
|
||||||
|
alias_output.push('\n'); // Push a final newline
|
||||||
|
|
||||||
|
let stdout = borrow_fd(STDOUT_FILENO);
|
||||||
|
write(stdout, alias_output.as_bytes())?; // Write it
|
||||||
|
} else {
|
||||||
|
for (arg,span) in argv {
|
||||||
|
let Some((name,body)) = arg.split_once('=') else {
|
||||||
|
return Err(
|
||||||
|
ShErr::full(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
"alias: Expected an assignment in alias args",
|
||||||
|
span.into()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
write_logic(|l| l.insert_alias(name, body));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
io_frame.unwrap().restore()?;
|
||||||
|
state::set_status(0);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -9,8 +9,9 @@ pub mod pwd;
|
|||||||
pub mod source;
|
pub mod source;
|
||||||
pub mod shift;
|
pub mod shift;
|
||||||
pub mod jobctl;
|
pub mod jobctl;
|
||||||
|
pub mod alias;
|
||||||
|
|
||||||
pub const BUILTINS: [&str;9] = [
|
pub const BUILTINS: [&str;10] = [
|
||||||
"echo",
|
"echo",
|
||||||
"cd",
|
"cd",
|
||||||
"export",
|
"export",
|
||||||
@@ -19,7 +20,8 @@ pub const BUILTINS: [&str;9] = [
|
|||||||
"shift",
|
"shift",
|
||||||
"jobs",
|
"jobs",
|
||||||
"fg",
|
"fg",
|
||||||
"bg"
|
"bg",
|
||||||
|
"alias"
|
||||||
];
|
];
|
||||||
|
|
||||||
/// Sets up a builtin command
|
/// Sets up a builtin command
|
||||||
|
|||||||
107
src/expand.rs
107
src/expand.rs
@@ -1,4 +1,6 @@
|
|||||||
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}};
|
use std::collections::{HashSet, VecDeque};
|
||||||
|
|
||||||
|
use crate::{libsh::error::ShResult, parse::lex::{is_field_sep, is_hard_sep, is_keyword, LexFlags, LexStream, Span, Tk, TkFlags, TkRule}, prelude::*, state::{read_logic, 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}';
|
||||||
@@ -479,9 +481,7 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
|||||||
PromptTk::Text(txt) => result.push_str(&txt),
|
PromptTk::Text(txt) => result.push_str(&txt),
|
||||||
PromptTk::AnsiSeq(params) => result.push_str(¶ms),
|
PromptTk::AnsiSeq(params) => result.push_str(¶ms),
|
||||||
PromptTk::Runtime => {
|
PromptTk::Runtime => {
|
||||||
flog!(INFO, "getting runtime");
|
|
||||||
if let Some(runtime) = write_meta(|m| m.stop_timer()) {
|
if let Some(runtime) = write_meta(|m| m.stop_timer()) {
|
||||||
flog!(DEBUG, runtime);
|
|
||||||
let runtime_fmt = format_cmd_runtime(runtime);
|
let runtime_fmt = format_cmd_runtime(runtime);
|
||||||
result.push_str(&runtime_fmt);
|
result.push_str(&runtime_fmt);
|
||||||
}
|
}
|
||||||
@@ -543,3 +543,104 @@ pub fn expand_prompt(raw: &str) -> ShResult<String> {
|
|||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Expand aliases in the given input string
|
||||||
|
pub fn expand_aliases(input: String, mut already_expanded: HashSet<String>) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut cur_word = String::new();
|
||||||
|
let mut chars = input.chars().peekable();
|
||||||
|
let mut is_cmd = true;
|
||||||
|
|
||||||
|
let mut expanded_this_iter = HashSet::new();
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
';' | '\n' => {
|
||||||
|
if is_cmd {
|
||||||
|
if !is_keyword(&cur_word) {
|
||||||
|
if !already_expanded.contains(&cur_word) {
|
||||||
|
if let Some(alias) = read_logic(|l| l.get_alias(&cur_word)) {
|
||||||
|
result.push_str(&alias);
|
||||||
|
expanded_this_iter.insert(cur_word.clone());
|
||||||
|
cur_word.clear();
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
result.push(ch);
|
||||||
|
is_cmd = true;
|
||||||
|
while let Some(next_ch) = chars.peek() {
|
||||||
|
if is_hard_sep(*next_ch) {
|
||||||
|
result.push(chars.next().unwrap());
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' ' | '\t' => {
|
||||||
|
if is_cmd {
|
||||||
|
if !is_keyword(&cur_word) {
|
||||||
|
if let Some(alias) = read_logic(|l| l.get_alias(&cur_word)) {
|
||||||
|
if !already_expanded.contains(&cur_word) {
|
||||||
|
result.push_str(&alias);
|
||||||
|
expanded_this_iter.insert(cur_word.clone());
|
||||||
|
cur_word.clear();
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
is_cmd = false;
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
is_cmd = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
result.push(ch);
|
||||||
|
while let Some(next_ch) = chars.peek() {
|
||||||
|
if is_field_sep(*next_ch) {
|
||||||
|
result.push(chars.next().unwrap());
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => cur_word.push(ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !cur_word.is_empty() {
|
||||||
|
if is_cmd {
|
||||||
|
if !is_keyword(&cur_word) {
|
||||||
|
if let Some(alias) = read_logic(|l| l.get_alias(&cur_word)) {
|
||||||
|
if !already_expanded.contains(&cur_word) {
|
||||||
|
result.push_str(&alias);
|
||||||
|
expanded_this_iter.insert(cur_word.clone());
|
||||||
|
cur_word.clear();
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push_str(&mem::take(&mut cur_word));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expanded_this_iter.is_empty() {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
already_expanded.extend(expanded_this_iter.into_iter());
|
||||||
|
return expand_aliases(result, already_expanded)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
15
src/fern.rs
15
src/fern.rs
@@ -8,14 +8,16 @@ pub mod state;
|
|||||||
pub mod builtin;
|
pub mod builtin;
|
||||||
pub mod jobs;
|
pub mod jobs;
|
||||||
pub mod signal;
|
pub mod signal;
|
||||||
#[cfg(test)]
|
|
||||||
pub mod tests;
|
pub mod tests;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use expand::expand_aliases;
|
||||||
use libsh::error::ShResult;
|
use libsh::error::ShResult;
|
||||||
use parse::{execute::Dispatcher, lex::{LexFlags, LexStream}, Ast, ParseStream, ParsedSrc};
|
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 state::{source_rc, write_logic, write_meta};
|
||||||
use termios::{LocalFlags, Termios};
|
use termios::{LocalFlags, Termios};
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
@@ -51,14 +53,12 @@ fn set_termios() {
|
|||||||
|
|
||||||
pub fn exec_input(input: String) -> ShResult<()> {
|
pub fn exec_input(input: String) -> ShResult<()> {
|
||||||
write_meta(|m| m.start_timer());
|
write_meta(|m| m.start_timer());
|
||||||
|
let input = expand_aliases(input, HashSet::new());
|
||||||
let mut parser = ParsedSrc::new(Rc::new(input));
|
let mut parser = ParsedSrc::new(Rc::new(input));
|
||||||
parser.parse_src()?;
|
parser.parse_src()?;
|
||||||
|
|
||||||
let exec_start = Instant::now();
|
|
||||||
|
|
||||||
let mut dispatcher = Dispatcher::new(parser.extract_nodes());
|
let mut dispatcher = Dispatcher::new(parser.extract_nodes());
|
||||||
dispatcher.begin_dispatch()?;
|
dispatcher.begin_dispatch()
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -66,6 +66,9 @@ fn main() {
|
|||||||
set_termios();
|
set_termios();
|
||||||
sig_setup();
|
sig_setup();
|
||||||
|
|
||||||
|
if let Err(e) = source_rc() {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let input = match prompt::read_line() {
|
let input = match prompt::read_line() {
|
||||||
|
|||||||
@@ -611,9 +611,11 @@ pub fn enable_reaping() -> ShResult<()> {
|
|||||||
|
|
||||||
/// Waits on the current foreground job and updates the shell's last status code
|
/// Waits on the current foreground job and updates the shell's last status code
|
||||||
pub fn wait_fg(job: Job) -> ShResult<()> {
|
pub fn wait_fg(job: Job) -> ShResult<()> {
|
||||||
|
if job.children().is_empty() {
|
||||||
|
return Ok(()) // Nothing to do
|
||||||
|
}
|
||||||
flog!(TRACE, "Waiting on foreground job");
|
flog!(TRACE, "Waiting on foreground job");
|
||||||
let mut code = 0;
|
let mut code = 0;
|
||||||
flog!(DEBUG,job.pgid());
|
|
||||||
attach_tty(job.pgid())?;
|
attach_tty(job.pgid())?;
|
||||||
disable_reaping()?;
|
disable_reaping()?;
|
||||||
let statuses = write_jobs(|j| j.new_fg(job))?;
|
let statuses = write_jobs(|j| j.new_fg(job))?;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ impl<T> ShResultExt for Result<T,ShErr> {
|
|||||||
return self
|
return self
|
||||||
};
|
};
|
||||||
match e {
|
match e {
|
||||||
ShErr::Simple { kind, msg } => Err(ShErr::full(*kind, msg, new_span)),
|
ShErr::Simple { kind, msg } => Err(ShErr::full(kind.clone(), msg, new_span)),
|
||||||
ShErr::Full { kind: _, msg: _, span: _ } => self
|
ShErr::Full { kind: _, msg: _, span: _ } => self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,7 +86,7 @@ impl ShErr {
|
|||||||
if ch == '\n' {
|
if ch == '\n' {
|
||||||
total_lines += 1;
|
total_lines += 1;
|
||||||
|
|
||||||
if total_len >= span.start {
|
if total_len > span.start {
|
||||||
let line = (
|
let line = (
|
||||||
total_lines,
|
total_lines,
|
||||||
mem::take(&mut cur_line)
|
mem::take(&mut cur_line)
|
||||||
@@ -96,6 +96,7 @@ impl ShErr {
|
|||||||
if total_len >= span.end {
|
if total_len >= span.end {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
cur_line.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,6 +162,7 @@ impl Display for ShErr {
|
|||||||
|
|
||||||
for (lineno,line) in window {
|
for (lineno,line) in window {
|
||||||
let lineno = lineno.to_string();
|
let lineno = lineno.to_string();
|
||||||
|
let line = line.trim();
|
||||||
let mut prefix = format!("{padding}|");
|
let mut prefix = format!("{padding}|");
|
||||||
prefix.replace_range(0..lineno.len(), &lineno);
|
prefix.replace_range(0..lineno.len(), &lineno);
|
||||||
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
prefix = prefix.styled(Style::Cyan | Style::Bold);
|
||||||
@@ -203,7 +205,7 @@ impl From<Errno> for ShErr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone,Copy)]
|
#[derive(Debug,Clone)]
|
||||||
pub enum ShErrKind {
|
pub enum ShErrKind {
|
||||||
IoErr,
|
IoErr,
|
||||||
SyntaxErr,
|
SyntaxErr,
|
||||||
@@ -213,8 +215,8 @@ pub enum ShErrKind {
|
|||||||
ResourceLimitExceeded,
|
ResourceLimitExceeded,
|
||||||
BadPermission,
|
BadPermission,
|
||||||
Errno,
|
Errno,
|
||||||
FileNotFound,
|
FileNotFound(String),
|
||||||
CmdNotFound,
|
CmdNotFound(String),
|
||||||
CleanExit,
|
CleanExit,
|
||||||
FuncReturn,
|
FuncReturn,
|
||||||
LoopContinue,
|
LoopContinue,
|
||||||
@@ -233,8 +235,8 @@ impl Display for ShErrKind {
|
|||||||
ShErrKind::ResourceLimitExceeded => "Resource Limit Exceeded",
|
ShErrKind::ResourceLimitExceeded => "Resource Limit Exceeded",
|
||||||
ShErrKind::BadPermission => "Bad Permissions",
|
ShErrKind::BadPermission => "Bad Permissions",
|
||||||
ShErrKind::Errno => "ERRNO",
|
ShErrKind::Errno => "ERRNO",
|
||||||
ShErrKind::FileNotFound => "File Not Found",
|
ShErrKind::FileNotFound(file) => &format!("File not found: {file}"),
|
||||||
ShErrKind::CmdNotFound => "Command Not Found",
|
ShErrKind::CmdNotFound(cmd) => &format!("Command not found: {cmd}"),
|
||||||
ShErrKind::CleanExit => "",
|
ShErrKind::CleanExit => "",
|
||||||
ShErrKind::FuncReturn => "",
|
ShErrKind::FuncReturn => "",
|
||||||
ShErrKind::LoopContinue => "",
|
ShErrKind::LoopContinue => "",
|
||||||
|
|||||||
@@ -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::{ShErr, ShErrKind, ShResult, ShResultExt}, utils::RedirVecUtils}, prelude::*, procio::{IoFrame, IoMode, IoStack}, state::{self, read_logic, read_vars, write_logic, write_vars, ShFunc, VarTab}};
|
use crate::{builtin::{alias::alias, 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, ParsedSrc, Redir, RedirType};
|
use super::{lex::{LexFlags, LexStream, Span, Tk, TkFlags, KEYWORDS}, AssignKind, CondNode, ConjunctNode, ConjunctOp, LoopKind, NdFlags, NdRule, Node, ParseStream, ParsedSrc, Redir, RedirType};
|
||||||
|
|
||||||
pub enum AssignBehavior {
|
pub enum AssignBehavior {
|
||||||
Export,
|
Export,
|
||||||
@@ -12,7 +12,7 @@ pub enum AssignBehavior {
|
|||||||
|
|
||||||
/// Arguments to the execvpe function
|
/// Arguments to the execvpe function
|
||||||
pub struct ExecArgs {
|
pub struct ExecArgs {
|
||||||
pub cmd: CString,
|
pub cmd: (CString,Span),
|
||||||
pub argv: Vec<CString>,
|
pub argv: Vec<CString>,
|
||||||
pub envp: Vec<CString>
|
pub envp: Vec<CString>
|
||||||
}
|
}
|
||||||
@@ -26,8 +26,8 @@ impl ExecArgs {
|
|||||||
let envp = Self::get_envp();
|
let envp = Self::get_envp();
|
||||||
Self { cmd, argv, envp }
|
Self { cmd, argv, envp }
|
||||||
}
|
}
|
||||||
pub fn get_cmd(argv: &[(String,Span)]) -> CString {
|
pub fn get_cmd(argv: &[(String,Span)]) -> (CString,Span) {
|
||||||
CString::new(argv[0].0.as_str()).unwrap()
|
(CString::new(argv[0].0.as_str()).unwrap(),argv[0].1.clone())
|
||||||
}
|
}
|
||||||
pub fn get_argv(argv: Vec<(String,Span)>) -> Vec<CString> {
|
pub fn get_argv(argv: Vec<(String,Span)>) -> Vec<CString> {
|
||||||
argv.into_iter().map(|s| CString::new(s.0).unwrap()).collect()
|
argv.into_iter().map(|s| CString::new(s.0).unwrap()).collect()
|
||||||
@@ -101,6 +101,7 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_func_def(&mut self, func_def: Node) -> ShResult<()> {
|
pub fn exec_func_def(&mut self, func_def: Node) -> ShResult<()> {
|
||||||
|
let blame = func_def.get_span();
|
||||||
let NdRule::FuncDef { name, body } = func_def.class else {
|
let NdRule::FuncDef { name, body } = func_def.class else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
@@ -108,6 +109,16 @@ impl Dispatcher {
|
|||||||
let body = body_span.as_str().to_string();
|
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();
|
||||||
|
|
||||||
|
if KEYWORDS.contains(&name) {
|
||||||
|
return Err(
|
||||||
|
ShErr::full(
|
||||||
|
ShErrKind::SyntaxErr,
|
||||||
|
format!("function: Forbidden function name `{name}`"),
|
||||||
|
blame
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
let mut func_parser = ParsedSrc::new(Rc::new(body));
|
let mut func_parser = ParsedSrc::new(Rc::new(body));
|
||||||
func_parser.parse_src()?; // Parse the function
|
func_parser.parse_src()?; // Parse the function
|
||||||
|
|
||||||
@@ -256,7 +267,7 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_pipeline(&mut self, pipeline: Node) -> 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!()
|
||||||
};
|
};
|
||||||
self.job_stack.new_job();
|
self.job_stack.new_job();
|
||||||
@@ -281,7 +292,7 @@ impl Dispatcher {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn exec_builtin(&mut self, mut cmd: Node) -> 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!()
|
||||||
};
|
};
|
||||||
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
|
let env_vars_to_unset = self.set_assignments(mem::take(assignments), AssignBehavior::Export);
|
||||||
@@ -300,6 +311,7 @@ impl Dispatcher {
|
|||||||
"fg" => continue_job(cmd, curr_job_mut, JobBehavior::Foregound),
|
"fg" => continue_job(cmd, curr_job_mut, JobBehavior::Foregound),
|
||||||
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
"bg" => continue_job(cmd, curr_job_mut, JobBehavior::Background),
|
||||||
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
"jobs" => jobs(cmd, io_stack_mut, curr_job_mut),
|
||||||
|
"alias" => alias(cmd, io_stack_mut, curr_job_mut),
|
||||||
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
_ => unimplemented!("Have not yet added support for builtin '{}'", cmd_raw.span.as_str())
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -422,7 +434,7 @@ where
|
|||||||
}
|
}
|
||||||
ForkResult::Parent { child } => {
|
ForkResult::Parent { child } => {
|
||||||
let cmd = if let Some(args) = exec_args {
|
let cmd = if let Some(args) = exec_args {
|
||||||
Some(args.cmd.to_str().unwrap().to_string())
|
Some(args.cmd.0.to_str().unwrap().to_string())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -437,11 +449,29 @@ pub fn def_child_action(mut io_frame: IoFrame, exec_args: Option<ExecArgs>) {
|
|||||||
eprintln!("{e}");
|
eprintln!("{e}");
|
||||||
}
|
}
|
||||||
let exec_args = exec_args.unwrap();
|
let exec_args = exec_args.unwrap();
|
||||||
let cmd = &exec_args.cmd.to_str().unwrap().to_string();
|
let cmd = &exec_args.cmd.0;
|
||||||
let Err(e) = execvpe(&exec_args.cmd, &exec_args.argv, &exec_args.envp);
|
let span = exec_args.cmd.1;
|
||||||
|
|
||||||
|
let Err(e) = execvpe(&cmd, &exec_args.argv, &exec_args.envp);
|
||||||
|
|
||||||
|
let cmd = cmd.to_str().unwrap().to_string();
|
||||||
match e {
|
match e {
|
||||||
Errno::ENOENT => eprintln!("Command not found: {}", cmd),
|
Errno::ENOENT => {
|
||||||
_ => eprintln!("{e}")
|
let err = ShErr::full(
|
||||||
|
ShErrKind::CmdNotFound(cmd),
|
||||||
|
"",
|
||||||
|
span
|
||||||
|
);
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let err = ShErr::full(
|
||||||
|
ShErrKind::Errno,
|
||||||
|
format!("{e}"),
|
||||||
|
span
|
||||||
|
);
|
||||||
|
eprintln!("{err}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exit(e as i32)
|
exit(e as i32)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ fn get_hist_path() -> ShResult<PathBuf> {
|
|||||||
Ok(PathBuf::from(path))
|
Ok(PathBuf::from(path))
|
||||||
} else {
|
} else {
|
||||||
let home = env::var("HOME")?;
|
let home = env::var("HOME")?;
|
||||||
Ok(PathBuf::from(format!("{home}/.fernhist")))
|
let path = PathBuf::from(format!("{home}/.fernhist"));
|
||||||
|
flog!(DEBUG, path);
|
||||||
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
25
src/state.rs
25
src/state.rs
@@ -2,7 +2,7 @@ use std::{collections::{HashMap, VecDeque}, ops::{Deref, Range}, sync::{LazyLock
|
|||||||
|
|
||||||
use nix::unistd::{gethostname, getppid, User};
|
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::*};
|
use crate::{exec_input, jobs::JobTab, libsh::{error::{ShErr, ShErrKind, 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()));
|
||||||
|
|
||||||
@@ -67,6 +67,12 @@ impl LogTab {
|
|||||||
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
|
pub fn get_func(&self, name: &str) -> Option<ShFunc> {
|
||||||
self.functions.get(name).cloned()
|
self.functions.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
pub fn funcs(&self) -> &HashMap<String,ShFunc> {
|
||||||
|
&self.functions
|
||||||
|
}
|
||||||
|
pub fn aliases(&self) -> &HashMap<String,String> {
|
||||||
|
&self.aliases
|
||||||
|
}
|
||||||
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
pub fn insert_alias(&mut self, name: &str, body: &str) {
|
||||||
self.aliases.insert(name.into(), body.into());
|
self.aliases.insert(name.into(), body.into());
|
||||||
}
|
}
|
||||||
@@ -142,7 +148,7 @@ impl VarTab {
|
|||||||
env::set_var("OLDPWD", 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("HOME", home.clone());
|
||||||
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
env::set_var("SHELL", pathbuf_to_string(std::env::current_exe()));
|
||||||
env::set_var("FERN_HIST",format!("{}/.fern_hist",home));
|
env::set_var("FERN_HIST",format!("{}/.fernhist",home));
|
||||||
env::set_var("FERN_RC",format!("{}/.fernrc",home));
|
env::set_var("FERN_RC",format!("{}/.fernrc",home));
|
||||||
}
|
}
|
||||||
pub fn init_sh_argv(&mut self) {
|
pub fn init_sh_argv(&mut self) {
|
||||||
@@ -313,6 +319,21 @@ pub fn set_status(code: i32) {
|
|||||||
write_vars(|v| v.set_param('?', &code.to_string()))
|
write_vars(|v| v.set_param('?', &code.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn source_rc() -> ShResult<()> {
|
||||||
|
let path = if let Ok(path) = env::var("FERN_RC") {
|
||||||
|
PathBuf::from(&path)
|
||||||
|
} else {
|
||||||
|
let home = env::var("HOME").unwrap();
|
||||||
|
PathBuf::from(format!("{home}/.fernrc"))
|
||||||
|
};
|
||||||
|
if !path.exists() {
|
||||||
|
return Err(
|
||||||
|
ShErr::simple(ShErrKind::InternalErr, ".fernrc not found")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
source_file(path)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
pub fn source_file(path: PathBuf) -> ShResult<()> {
|
||||||
let mut file = OpenOptions::new()
|
let mut file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
use expand::unescape_str;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use expand::{expand_aliases, unescape_str};
|
||||||
use parse::lex::{Tk, TkFlags, TkRule};
|
use parse::lex::{Tk, TkFlags, TkRule};
|
||||||
use state::write_vars;
|
use state::{write_logic, write_vars};
|
||||||
use super::super::*;
|
use super::super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -8,7 +10,7 @@ fn simple_expansion() {
|
|||||||
let varsub = "$foo";
|
let varsub = "$foo";
|
||||||
write_vars(|v| v.new_var("foo", "this is the value of the variable".into()));
|
write_vars(|v| v.new_var("foo", "this is the value of the variable".into()));
|
||||||
|
|
||||||
let mut tokens: Vec<Tk> = LexStream::new(varsub, LexFlags::empty())
|
let mut tokens: Vec<Tk> = LexStream::new(Rc::new(varsub.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
.filter(|tk| !matches!(tk.class, TkRule::EOI | TkRule::SOI))
|
||||||
.collect();
|
.collect();
|
||||||
@@ -27,3 +29,54 @@ fn unescape_string() {
|
|||||||
|
|
||||||
insta::assert_snapshot!(unescaped)
|
insta::assert_snapshot!(unescaped)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_alias_simple() {
|
||||||
|
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
||||||
|
|
||||||
|
let input = String::from("foo");
|
||||||
|
|
||||||
|
let result = expand_aliases(input, HashSet::new());
|
||||||
|
assert_eq!(result.as_str(),"echo foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_alias_in_if() {
|
||||||
|
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
||||||
|
|
||||||
|
let input = String::from("if foo; then echo bar; fi");
|
||||||
|
|
||||||
|
let result = expand_aliases(input, HashSet::new());
|
||||||
|
assert_eq!(result.as_str(),"if echo foo; then echo bar; fi")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_multiple_aliases() {
|
||||||
|
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
||||||
|
write_logic(|l| l.insert_alias("bar", "echo bar"));
|
||||||
|
write_logic(|l| l.insert_alias("biz", "echo biz"));
|
||||||
|
|
||||||
|
let input = String::from("foo; bar; biz");
|
||||||
|
|
||||||
|
let result = expand_aliases(input, HashSet::new());
|
||||||
|
assert_eq!(result.as_str(),"echo foo; echo bar; echo biz")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expand_recursive_alias() {
|
||||||
|
write_logic(|l| l.insert_alias("foo", "echo foo"));
|
||||||
|
write_logic(|l| l.insert_alias("bar", "foo bar"));
|
||||||
|
|
||||||
|
let input = String::from("bar");
|
||||||
|
let result = expand_aliases(input, HashSet::new());
|
||||||
|
assert_eq!(result.as_str(),"echo foo bar")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_infinite_recursive_alias() {
|
||||||
|
write_logic(|l| l.insert_alias("foo", "foo"));
|
||||||
|
|
||||||
|
let input = String::from("foo");
|
||||||
|
let result = expand_aliases(input, HashSet::new());
|
||||||
|
assert_eq!(result.as_str(),"foo")
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,35 +2,35 @@ use super::super::*;
|
|||||||
#[test]
|
#[test]
|
||||||
fn lex_simple() {
|
fn lex_simple() {
|
||||||
let input = "echo hello world";
|
let input = "echo hello world";
|
||||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_redir() {
|
fn lex_redir() {
|
||||||
let input = "echo foo > bar.txt";
|
let input = "echo foo > bar.txt";
|
||||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_redir_fds() {
|
fn lex_redir_fds() {
|
||||||
let input = "echo foo 1>&2";
|
let input = "echo foo 1>&2";
|
||||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_quote_str() {
|
fn lex_quote_str() {
|
||||||
let input = "echo \"foo bar\" biz baz";
|
let input = "echo \"foo bar\" biz baz";
|
||||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn lex_with_keywords() {
|
fn lex_with_keywords() {
|
||||||
let input = "if true; then echo foo; fi";
|
let input = "if true; then echo foo; fi";
|
||||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ fn lex_with_keywords() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn lex_multiline() {
|
fn lex_multiline() {
|
||||||
let input = "echo hello world\necho foo bar\necho boo biz";
|
let input = "echo hello world\necho foo bar\necho boo biz";
|
||||||
let tokens: Vec<_> = LexStream::new(input, LexFlags::empty()).collect();
|
let tokens: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty()).collect();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(tokens)
|
insta::assert_debug_snapshot!(tokens)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use super::super::*;
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_simple() {
|
fn parse_simple() {
|
||||||
let input = "echo hello world";
|
let input = "echo hello world";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -14,7 +14,7 @@ fn parse_simple() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_pipeline() {
|
fn parse_pipeline() {
|
||||||
let input = "echo foo | sed s/foo/bar";
|
let input = "echo foo | sed s/foo/bar";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -25,7 +25,7 @@ fn parse_pipeline() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_conjunction() {
|
fn parse_conjunction() {
|
||||||
let input = "echo foo && echo bar";
|
let input = "echo foo && echo bar";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -36,7 +36,7 @@ fn parse_conjunction() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_conjunction_and_pipeline() {
|
fn parse_conjunction_and_pipeline() {
|
||||||
let input = "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/";
|
let input = "echo foo | sed s/foo/bar/ && echo bar | sed s/bar/foo/ || echo foo bar | sed s/foo bar/bar foo/";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -50,7 +50,7 @@ fn parse_multiline() {
|
|||||||
echo hello world
|
echo hello world
|
||||||
echo foo bar
|
echo foo bar
|
||||||
echo boo biz";
|
echo boo biz";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -61,7 +61,7 @@ echo boo biz";
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_if_simple() {
|
fn parse_if_simple() {
|
||||||
let input = "if foo; then echo bar; fi";
|
let input = "if foo; then echo bar; fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -71,7 +71,7 @@ fn parse_if_simple() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_if_with_elif() {
|
fn parse_if_with_elif() {
|
||||||
let input = "if foo; then echo bar; elif bar; then echo foo; fi";
|
let input = "if foo; then echo bar; elif bar; then echo foo; fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -81,7 +81,7 @@ fn parse_if_with_elif() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_if_multiple_elif() {
|
fn parse_if_multiple_elif() {
|
||||||
let input = "if foo; then echo bar; elif bar; then echo foo; elif biz; then echo baz; fi";
|
let input = "if foo; then echo bar; elif bar; then echo foo; elif biz; then echo baz; fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -98,7 +98,7 @@ elif bar; then
|
|||||||
elif biz; then
|
elif biz; then
|
||||||
echo baz
|
echo baz
|
||||||
fi";
|
fi";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -108,7 +108,7 @@ fi";
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_loop_simple() {
|
fn parse_loop_simple() {
|
||||||
let input = "while foo; do bar; done";
|
let input = "while foo; do bar; done";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -118,7 +118,7 @@ fn parse_loop_simple() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_loop_until() {
|
fn parse_loop_until() {
|
||||||
let input = "until foo; do bar; done";
|
let input = "until foo; do bar; done";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
@@ -131,7 +131,7 @@ fn parse_loop_multiline() {
|
|||||||
until foo; do
|
until foo; do
|
||||||
bar
|
bar
|
||||||
done";
|
done";
|
||||||
let tk_stream: Vec<_> = LexStream::new(input, LexFlags::empty())
|
let tk_stream: Vec<_> = LexStream::new(Rc::new(input.to_string()), LexFlags::empty())
|
||||||
.map(|tk| tk.unwrap())
|
.map(|tk| tk.unwrap())
|
||||||
.collect();
|
.collect();
|
||||||
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
let nodes: Vec<_> = ParseStream::new(tk_stream).collect();
|
||||||
|
|||||||
Reference in New Issue
Block a user