mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-07-01 16:31:16 -03:00
Allow foo=bar global variable assignments
override fixes
This commit is contained in:
203
src/ast.rs
203
src/ast.rs
@@ -14,7 +14,7 @@
|
||||
use crate::parse_constants::{
|
||||
token_type_user_presentable_description, ParseError, ParseErrorCode, ParseErrorList,
|
||||
ParseKeyword, ParseTokenType, ParseTreeFlags, SourceRange, StatementDecoration,
|
||||
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, SOURCE_OFFSET_INVALID,
|
||||
INVALID_PIPELINE_CMD_ERR_MSG, SOURCE_OFFSET_INVALID,
|
||||
};
|
||||
use crate::parse_tree::ParseToken;
|
||||
#[cfg(test)]
|
||||
@@ -55,7 +55,12 @@ pub struct MissingEndError {
|
||||
token: ParseToken,
|
||||
}
|
||||
|
||||
pub type VisitResult = ControlFlow<MissingEndError>;
|
||||
pub enum NonLocalError {
|
||||
MissingEndError(MissingEndError),
|
||||
MissingStatementError,
|
||||
}
|
||||
|
||||
pub type VisitResult = ControlFlow<NonLocalError>;
|
||||
|
||||
trait NodeVisitorMut {
|
||||
/// will_visit (did_visit) is called before (after) a node's fields are visited.
|
||||
@@ -85,6 +90,7 @@ fn visit_decorated_statement_decorator(
|
||||
fn visit_then(&mut self, _node: &mut Option<KeywordThen>);
|
||||
fn visit_time(&mut self, _node: &mut Option<KeywordTime>);
|
||||
fn visit_token_background(&mut self, _node: &mut Option<TokenBackground>);
|
||||
fn visit_optional_statement(&mut self, _node: &mut Option<Statement>);
|
||||
}
|
||||
|
||||
trait AcceptorMut {
|
||||
@@ -185,6 +191,9 @@ fn as_argument_or_redirection_list(&self) -> Option<&ArgumentOrRedirectionList>
|
||||
fn as_command_token(&self) -> Option<&CommandToken> {
|
||||
None
|
||||
}
|
||||
fn as_variable_statement(&self) -> Option<&VariableStatement> {
|
||||
None
|
||||
}
|
||||
fn as_statement(&self) -> Option<&Statement> {
|
||||
None
|
||||
}
|
||||
@@ -309,6 +318,9 @@ fn as_mut_argument_or_redirection_list(&mut self) -> Option<&mut ArgumentOrRedir
|
||||
fn as_mut_command_token(&mut self) -> Option<&mut CommandToken> {
|
||||
None
|
||||
}
|
||||
fn as_mut_variable_statement(&mut self) -> Option<&mut VariableStatement> {
|
||||
None
|
||||
}
|
||||
fn as_mut_statement(&mut self) -> Option<&mut Statement> {
|
||||
None
|
||||
}
|
||||
@@ -988,6 +1000,9 @@ macro_rules! visit_optional_field_mut {
|
||||
(TokenBackground, $field:expr, $visitor:ident) => {
|
||||
$visitor.visit_token_background(&mut $field);
|
||||
};
|
||||
(Statement, $field:expr, $visitor:ident) => {
|
||||
$visitor.visit_optional_statement(&mut $field);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! visit_result {
|
||||
@@ -1227,6 +1242,31 @@ pub fn is_left_brace(&self) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct VariableStatement {
|
||||
parent: Option<*const dyn Node>,
|
||||
/// A (possibly empty) list of variable assignments.
|
||||
pub variables: VariableAssignmentList,
|
||||
/// The statement.
|
||||
pub statement: Option<Statement>,
|
||||
}
|
||||
implement_node!(VariableStatement, branch, variable_statement);
|
||||
implement_acceptor_for_branch!(
|
||||
VariableStatement,
|
||||
(variables: (VariableAssignmentList)),
|
||||
(statement: (Option<Statement>)),
|
||||
);
|
||||
impl ConcreteNode for VariableStatement {
|
||||
fn as_variable_statement(&self) -> Option<&VariableStatement> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl ConcreteNodeMut for VariableStatement {
|
||||
fn as_mut_variable_statement(&mut self) -> Option<&mut VariableStatement> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// A statement is a normal command, or an if / while / etc
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Statement {
|
||||
@@ -1246,6 +1286,16 @@ fn as_mut_statement(&mut self) -> Option<&mut Statement> {
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckParse for Statement {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
let next = pop.peek_token(0);
|
||||
matches!(
|
||||
next.typ,
|
||||
ParseTokenType::terminate | ParseTokenType::left_brace
|
||||
) || (next.typ == ParseTokenType::string && !next.is_variable_assignment)
|
||||
}
|
||||
}
|
||||
|
||||
/// A job is a non-empty list of statements, separated by pipes. (Non-empty is useful for cases
|
||||
/// like if statements, where we require a command).
|
||||
#[derive(Default, Debug)]
|
||||
@@ -1253,10 +1303,7 @@ pub struct JobPipeline {
|
||||
parent: Option<*const dyn Node>,
|
||||
/// Maybe the time keyword.
|
||||
pub time: Option<KeywordTime>,
|
||||
/// A (possibly empty) list of variable assignments.
|
||||
pub variables: VariableAssignmentList,
|
||||
/// The statement.
|
||||
pub statement: Statement,
|
||||
pub statement2: VariableStatement,
|
||||
/// Piped remainder.
|
||||
pub continuation: JobContinuationList,
|
||||
/// Maybe backgrounded.
|
||||
@@ -1266,8 +1313,7 @@ pub struct JobPipeline {
|
||||
implement_acceptor_for_branch!(
|
||||
JobPipeline,
|
||||
(time: (Option<KeywordTime>)),
|
||||
(variables: (VariableAssignmentList)),
|
||||
(statement: (Statement)),
|
||||
(statement2: (VariableStatement)),
|
||||
(continuation: (JobContinuationList)),
|
||||
(bg: (Option<TokenBackground>)),
|
||||
);
|
||||
@@ -1281,6 +1327,11 @@ fn as_mut_job_pipeline(&mut self) -> Option<&mut JobPipeline> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
impl JobPipeline {
|
||||
pub fn statement3(&self) -> Option<&Statement> {
|
||||
self.statement2.statement.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A job_conjunction is a job followed by a && or || continuations.
|
||||
#[derive(Default, Debug)]
|
||||
@@ -1759,16 +1810,14 @@ pub struct NotStatement {
|
||||
/// Keyword, either not or exclam.
|
||||
pub kw: KeywordNot,
|
||||
pub time: Option<KeywordTime>,
|
||||
pub variables: VariableAssignmentList,
|
||||
pub contents: Statement,
|
||||
pub negated_statement: VariableStatement,
|
||||
}
|
||||
implement_node!(NotStatement, branch, not_statement);
|
||||
implement_acceptor_for_branch!(
|
||||
NotStatement,
|
||||
(kw: (KeywordNot)),
|
||||
(time: (Option<KeywordTime>)),
|
||||
(variables: (VariableAssignmentList)),
|
||||
(contents: (Statement)),
|
||||
(negated_statement: (VariableStatement)),
|
||||
);
|
||||
impl ConcreteNode for NotStatement {
|
||||
fn as_not_statement(&self) -> Option<&NotStatement> {
|
||||
@@ -1786,16 +1835,14 @@ pub struct JobContinuation {
|
||||
parent: Option<*const dyn Node>,
|
||||
pub pipe: TokenPipe,
|
||||
pub newlines: MaybeNewlines,
|
||||
pub variables: VariableAssignmentList,
|
||||
pub statement: Statement,
|
||||
pub statement2: VariableStatement,
|
||||
}
|
||||
implement_node!(JobContinuation, branch, job_continuation);
|
||||
implement_acceptor_for_branch!(
|
||||
JobContinuation,
|
||||
(pipe: (TokenPipe)),
|
||||
(newlines: (MaybeNewlines)),
|
||||
(variables: (VariableAssignmentList)),
|
||||
(statement: (Statement)),
|
||||
(statement2: (VariableStatement)),
|
||||
);
|
||||
impl ConcreteNode for JobContinuation {
|
||||
fn as_job_continuation(&self) -> Option<&JobContinuation> {
|
||||
@@ -1812,6 +1859,11 @@ fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
pop.peek_type(0) == ParseTokenType::pipe
|
||||
}
|
||||
}
|
||||
impl JobContinuation {
|
||||
pub fn statement(&self) -> Option<&Statement> {
|
||||
self.statement2.statement.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
define_list_node!(JobContinuationList, job_continuation_list, JobContinuation);
|
||||
impl ConcreteNode for JobContinuationList {
|
||||
@@ -2010,20 +2062,18 @@ fn as_mut_variable_assignment(&mut self) -> Option<&mut VariableAssignment> {
|
||||
impl CheckParse for VariableAssignment {
|
||||
fn can_be_parsed(pop: &mut Populator<'_>) -> bool {
|
||||
// Do we have a variable assignment at all?
|
||||
if !pop.peek_token(0).may_be_variable_assignment {
|
||||
return false;
|
||||
}
|
||||
// What is the token after it?
|
||||
match pop.peek_type(1) {
|
||||
// We have `a= cmd` and should treat it as a variable assignment.
|
||||
ParseTokenType::string | ParseTokenType::left_brace => true,
|
||||
// We have `a=` which is OK if we are allowing incomplete, an error otherwise.
|
||||
ParseTokenType::terminate => pop.allow_incomplete(),
|
||||
// We have e.g. `a= >` which is an error.
|
||||
// Note that we do not produce an error here. Instead we return false
|
||||
// so this the token will be seen by allocate_populate_statement_contents.
|
||||
_ => false,
|
||||
}
|
||||
pop.peek_token(0).is_variable_assignment
|
||||
// // What is the token after it?
|
||||
// match pop.peek_type(1) {
|
||||
// // We have `a= cmd` and should treat it as a variable assignment.
|
||||
// ParseTokenType::string | ParseTokenType::left_brace => true,
|
||||
// // We have `a=` which is OK if we are allowing incomplete, an error otherwise.
|
||||
// ParseTokenType::terminate => pop.allow_incomplete(),
|
||||
// // We have e.g. `a= >` which is an error.
|
||||
// // Note that we do not produce an error here. Instead we return false
|
||||
// // so this the token will be seen by allocate_populate_statement_contents.
|
||||
// _ => false,
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2578,6 +2628,7 @@ pub fn ast_type_to_string(t: Type) -> &'static wstr {
|
||||
Type::argument_or_redirection => L!("argument_or_redirection"),
|
||||
Type::argument_or_redirection_list => L!("argument_or_redirection_list"),
|
||||
Type::command_token => L!("command_token"),
|
||||
Type::variable_statement => L!("variable_statement"),
|
||||
Type::statement => L!("statement"),
|
||||
Type::job_pipeline => L!("job_pipeline"),
|
||||
Type::job_conjunction => L!("job_conjunction"),
|
||||
@@ -2919,7 +2970,7 @@ fn advance_1(&mut self) -> ParseToken {
|
||||
result.has_dash_prefix = text.starts_with('-');
|
||||
result.is_help_argument = [L!("-h"), L!("--help")].contains(&text);
|
||||
result.is_newline = result.typ == ParseTokenType::end && text == "\n";
|
||||
result.may_be_variable_assignment = variable_assignment_equals_pos(text).is_some();
|
||||
result.is_variable_assignment = variable_assignment_equals_pos(text).is_some();
|
||||
result.tok_error = token.error;
|
||||
|
||||
assert!(token.offset() < SOURCE_OFFSET_INVALID);
|
||||
@@ -3029,6 +3080,9 @@ struct Populator<'a> {
|
||||
/// Stream of tokens which we consume.
|
||||
tokens: TokenStream<'a>,
|
||||
|
||||
/// Whether this statement has a prefixed variable override.
|
||||
parsing_variable_statement: Option<bool>,
|
||||
|
||||
/** The type which we are attempting to parse, typically job_list but may be
|
||||
freestanding_argument_list. */
|
||||
top_type: Type,
|
||||
@@ -3080,9 +3134,42 @@ fn visit_mut(&mut self, node: &mut dyn NodeMut) -> VisitResult {
|
||||
Category::leaf => {}
|
||||
// Visit branch nodes by just calling accept() to visit their fields.
|
||||
Category::branch => {
|
||||
self.parsing_variable_statement = Some(
|
||||
node.as_variable_statement().is_some()
|
||||
&& self.peek_token(0).is_variable_assignment,
|
||||
);
|
||||
// This field is a direct embedding of an AST value.
|
||||
node.accept_mut(self, false);
|
||||
return VisitResult::Continue(());
|
||||
let Some(variable_statement) = node.as_mut_variable_statement() else {
|
||||
return VisitResult::Continue(());
|
||||
};
|
||||
if self.peek_token(0).typ == ParseTokenType::terminate {
|
||||
return VisitResult::Continue(());
|
||||
}
|
||||
if variable_statement.statement.as_ref().is_some_and(|stmt|
|
||||
// TODO(posix_mode) # Un-clone
|
||||
// This may happen if we just have a 'time' prefix.
|
||||
// Construct a decorated statement, which will be unsourced.
|
||||
stmt
|
||||
.contents
|
||||
.as_decorated_statement()
|
||||
.is_some_and(|ds| ds.try_source_range().is_none()))
|
||||
{
|
||||
variable_statement.statement = None;
|
||||
}
|
||||
if variable_statement.statement.is_some()
|
||||
|| !variable_statement.variables.is_empty()
|
||||
{
|
||||
return VisitResult::Continue(());
|
||||
}
|
||||
parse_error!(
|
||||
self,
|
||||
self.peek_token(0),
|
||||
ParseErrorCode::generic,
|
||||
"asdf Expected a command but found %ls",
|
||||
self.peek_token(0).user_presentable_description()
|
||||
);
|
||||
return VisitResult::Break(NonLocalError::MissingStatementError);
|
||||
}
|
||||
Category::list => {
|
||||
// This field is an embedding of an array of (pointers to) ContentsNode.
|
||||
@@ -3150,6 +3237,9 @@ fn did_visit_fields_of<'a>(&'a mut self, node: &'a dyn NodeMut, flow: VisitResul
|
||||
let VisitResult::Break(error) = flow else {
|
||||
return;
|
||||
};
|
||||
let NonLocalError::MissingEndError(error) = error else {
|
||||
return;
|
||||
};
|
||||
|
||||
let token = &error.token;
|
||||
// To-do: maybe extend this to other tokenizer errors?
|
||||
@@ -3304,6 +3394,14 @@ fn visit_then(&mut self, node: &mut Option<KeywordThen>) {
|
||||
fn visit_token_background(&mut self, node: &mut Option<TokenBackground>) {
|
||||
*node = self.try_parse::<TokenBackground>().map(|b| *b);
|
||||
}
|
||||
fn visit_optional_statement(&mut self, node: &mut Option<Statement>) {
|
||||
if self.parsing_variable_statement.take().unwrap()
|
||||
&& self.peek_token(0).typ == ParseTokenType::terminate
|
||||
{
|
||||
return;
|
||||
}
|
||||
*node = self.try_parse::<Statement>().map(|b| *b);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to describe a list of keywords.
|
||||
@@ -3350,6 +3448,7 @@ fn new(
|
||||
semis: vec![],
|
||||
errors: vec![],
|
||||
tokens: TokenStream::new(src, flags, top_type == Type::freestanding_argument_list),
|
||||
parsing_variable_statement: None,
|
||||
top_type,
|
||||
unwinding: false,
|
||||
any_error: false,
|
||||
@@ -3858,25 +3957,26 @@ fn new_decorated_statement(slf: &mut Populator<'_>) -> StatementVariant {
|
||||
self.peek_token(0).user_presentable_description()
|
||||
);
|
||||
return got_error(self);
|
||||
} else if self.peek_token(0).may_be_variable_assignment {
|
||||
} else if self.peek_token(0).is_variable_assignment {
|
||||
// Here we have a variable assignment which we chose to not parse as a variable
|
||||
// assignment because there was no string after it.
|
||||
// Ensure we consume the token, so we don't get back here again at the same place.
|
||||
let token = &self.consume_any_token();
|
||||
let text = &self.tokens.src
|
||||
[token.source_start()..token.source_start() + token.source_length()];
|
||||
let equals_pos = variable_assignment_equals_pos(text).unwrap();
|
||||
let variable = &text[..equals_pos];
|
||||
let value = &text[equals_pos + 1..];
|
||||
parse_error!(
|
||||
self,
|
||||
token,
|
||||
ParseErrorCode::bare_variable_assignment,
|
||||
ERROR_BAD_COMMAND_ASSIGN_ERR_MSG,
|
||||
variable,
|
||||
value
|
||||
);
|
||||
return got_error(self);
|
||||
// let token = &self.consume_any_token();
|
||||
// let text = &self.tokens.src
|
||||
// [token.source_start()..token.source_start() + token.source_length()];
|
||||
// let equals_pos = variable_assignment_equals_pos(text).unwrap();
|
||||
// let variable = &text[..equals_pos];
|
||||
// let value = &text[equals_pos + 1..];
|
||||
// parse_error!(
|
||||
// self,
|
||||
// token,
|
||||
// ParseErrorCode::bare_variable_assignment,
|
||||
// ERROR_BAD_COMMAND_ASSIGN_ERR_MSG,
|
||||
// variable,
|
||||
// value
|
||||
// );
|
||||
// return got_error(self);
|
||||
return new_decorated_statement(self);
|
||||
}
|
||||
|
||||
// In some cases a block starter is a decorated statement instead, mostly if invoked with "--help".
|
||||
@@ -4035,7 +4135,7 @@ fn visit_variable_assignment(&mut self, varas: &mut VariableAssignment) {
|
||||
varas.range = None;
|
||||
return;
|
||||
}
|
||||
if !self.peek_token(0).may_be_variable_assignment {
|
||||
if !self.peek_token(0).is_variable_assignment {
|
||||
internal_error!(
|
||||
self,
|
||||
visit_variable_assignment,
|
||||
@@ -4116,10 +4216,10 @@ fn visit_keyword(&mut self, keyword: &mut dyn Keyword) -> VisitResult {
|
||||
// Special error reporting for keyword_t<kw_end>.
|
||||
let allowed_keywords = keyword.allowed_keywords();
|
||||
if allowed_keywords.contains(&ParseKeyword::kw_end) {
|
||||
return VisitResult::Break(MissingEndError {
|
||||
return VisitResult::Break(NonLocalError::MissingEndError(MissingEndError {
|
||||
allowed_keywords,
|
||||
token: *self.peek_token(0),
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
parse_error!(
|
||||
self,
|
||||
@@ -4334,6 +4434,7 @@ pub enum Type {
|
||||
argument_or_redirection,
|
||||
argument_or_redirection_list,
|
||||
command_token,
|
||||
variable_statement,
|
||||
statement,
|
||||
job_pipeline,
|
||||
job_conjunction,
|
||||
|
||||
@@ -349,18 +349,20 @@ fn gap_text_flags_before_node(&self, node: &dyn Node) -> GapFlags {
|
||||
let p = p.parent().unwrap();
|
||||
assert_eq!(p.typ(), Type::statement);
|
||||
let p = p.parent().unwrap();
|
||||
if let Some(job) = p.as_job_pipeline() {
|
||||
if !job.variables.is_empty() {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
#[allow(clippy::manual_map)]
|
||||
if (if let Some(job) = p.as_job_pipeline() {
|
||||
Some(&job.statement2)
|
||||
} else if let Some(job_cnt) = p.as_job_continuation() {
|
||||
if !job_cnt.variables.is_empty() {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
Some(&job_cnt.statement2)
|
||||
} else if let Some(not_stmt) = p.as_not_statement() {
|
||||
if !not_stmt.variables.is_empty() {
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
Some(¬_stmt.negated_statement)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.is_some_and(|stmt| !stmt.variables.is_empty())
|
||||
{
|
||||
result.allow_escaped_newlines = true;
|
||||
result.allow_escaped_newlines = true;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
|
||||
@@ -283,7 +283,7 @@ fn autosuggest_parse_command(
|
||||
|
||||
// Find the first statement.
|
||||
let jc = ast.top().as_job_list().unwrap().get(0)?;
|
||||
let first_statement = jc.job.statement.contents.as_decorated_statement()?;
|
||||
let first_statement = jc.job.statement3()?.contents.as_decorated_statement()?;
|
||||
|
||||
if let Some(expanded_command) = statement_get_expanded_command(buff, first_statement, ctx) {
|
||||
let mut arg = WString::new();
|
||||
|
||||
@@ -138,12 +138,11 @@ pub enum ParseErrorCode {
|
||||
tokenizer_unterminated_escape,
|
||||
tokenizer_other,
|
||||
|
||||
unbalancing_end, // end outside of block
|
||||
unbalancing_else, // else outside of if
|
||||
unbalancing_case, // case outside of switch
|
||||
unbalancing_brace, // } outside of {
|
||||
bare_variable_assignment, // a=b without command
|
||||
andor_in_pipeline, // "and" or "or" after a pipe
|
||||
unbalancing_end, // end outside of block
|
||||
unbalancing_else, // else outside of if
|
||||
unbalancing_case, // case outside of switch
|
||||
unbalancing_brace, // } outside of {
|
||||
andor_in_pipeline, // "and" or "or" after a pipe
|
||||
}
|
||||
|
||||
// The location of a pipeline.
|
||||
|
||||
@@ -450,11 +450,14 @@ fn infinite_recursive_statement_in_job_list<'b>(
|
||||
};
|
||||
|
||||
// Check main statement.
|
||||
let infinite_recursive_statement = statement_recurses(&jc.job.statement)
|
||||
let infinite_recursive_statement = statement_recurses(jc.job.statement3()?)
|
||||
// Check piped remainder.
|
||||
.or_else(|| {
|
||||
for c in &job.continuation {
|
||||
let s = statement_recurses(&c.statement);
|
||||
let Some(stmt) = c.statement() else {
|
||||
continue;
|
||||
};
|
||||
let s = statement_recurses(stmt);
|
||||
if s.is_some() {
|
||||
return s;
|
||||
}
|
||||
@@ -564,9 +567,12 @@ fn job_is_simple_block(&self, job: &ast::JobPipeline) -> bool {
|
||||
let no_redirs =
|
||||
|list: &ast::ArgumentOrRedirectionList| !list.iter().any(|val| val.is_redirection());
|
||||
|
||||
let Some(statement) = job.statement3() else {
|
||||
return true;
|
||||
};
|
||||
// Check if we're a block statement with redirections. We do it this obnoxious way to preserve
|
||||
// type safety (in case we add more specific statement types).
|
||||
match &job.statement.contents {
|
||||
match &statement.contents {
|
||||
StatementVariant::BlockStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
StatementVariant::BraceStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
StatementVariant::SwitchStatement(stmt) => no_redirs(&stmt.args_or_redirs),
|
||||
@@ -608,12 +614,17 @@ fn apply_variable_assignments(
|
||||
ctx: &OperationContext<'_>,
|
||||
mut proc: Option<&mut Process>,
|
||||
variable_assignment_list: &ast::VariableAssignmentList,
|
||||
block: &mut Option<BlockId>,
|
||||
block: Option<&mut Option<BlockId>>,
|
||||
) -> EndExecutionReason {
|
||||
if variable_assignment_list.is_empty() {
|
||||
return EndExecutionReason::ok;
|
||||
}
|
||||
*block = Some(ctx.parser().push_block(Block::variable_assignment_block()));
|
||||
// FIXME EnvMode::USER
|
||||
let mut flags = EnvMode::default();
|
||||
if let Some(block) = block {
|
||||
*block = Some(ctx.parser().push_block(Block::variable_assignment_block()));
|
||||
flags = EnvMode::LOCAL | EnvMode::EXPORT;
|
||||
}
|
||||
for variable_assignment in variable_assignment_list {
|
||||
let source = self.node_source(&**variable_assignment);
|
||||
let equals_pos = variable_assignment_equals_pos(source).unwrap();
|
||||
@@ -653,8 +664,7 @@ fn apply_variable_assignments(
|
||||
vals.clone(),
|
||||
));
|
||||
}
|
||||
ctx.parser()
|
||||
.set_var_and_fire(variable_name, EnvMode::LOCAL | EnvMode::EXPORT, vals);
|
||||
ctx.parser().set_var_and_fire(variable_name, flags, vals);
|
||||
}
|
||||
EndExecutionReason::ok
|
||||
}
|
||||
@@ -665,17 +675,20 @@ fn populate_job_process(
|
||||
ctx: &OperationContext<'_>,
|
||||
job: &mut Job,
|
||||
proc: &mut Process,
|
||||
statement: &ast::Statement,
|
||||
variable_assignments: &ast::VariableAssignmentList,
|
||||
variable_statement: &ast::VariableStatement,
|
||||
) -> EndExecutionReason {
|
||||
// Get the "specific statement" which is boolean / block / if / switch / decorated.
|
||||
let specific_statement = &statement.contents;
|
||||
|
||||
let mut block = None;
|
||||
let result =
|
||||
self.apply_variable_assignments(ctx, Some(proc), variable_assignments, &mut block);
|
||||
let mut block = variable_statement.statement.is_some().then_some(None);
|
||||
let result = self.apply_variable_assignments(
|
||||
ctx,
|
||||
Some(proc),
|
||||
&variable_statement.variables,
|
||||
block.as_mut(),
|
||||
);
|
||||
let Some(statement) = variable_statement.statement.as_ref() else {
|
||||
return result;
|
||||
};
|
||||
let _scope = ScopeGuard::new((), |()| {
|
||||
if let Some(block) = block {
|
||||
if let Some(Some(block)) = block {
|
||||
ctx.parser().pop_block(block);
|
||||
}
|
||||
});
|
||||
@@ -683,6 +696,8 @@ fn populate_job_process(
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get the "specific statement" which is boolean / block / if / switch / decorated.
|
||||
let specific_statement = &statement.contents;
|
||||
match &specific_statement {
|
||||
StatementVariant::NotStatement(not_statement) => {
|
||||
self.populate_not_process(ctx, job, proc, not_statement)
|
||||
@@ -711,13 +726,7 @@ fn populate_not_process(
|
||||
let mut flags = job.mut_flags();
|
||||
flags.negate = !flags.negate;
|
||||
}
|
||||
self.populate_job_process(
|
||||
ctx,
|
||||
job,
|
||||
proc,
|
||||
¬_statement.contents,
|
||||
¬_statement.variables,
|
||||
)
|
||||
self.populate_job_process(ctx, job, proc, ¬_statement.negated_statement)
|
||||
}
|
||||
|
||||
/// Creates a 'normal' (non-block) process.
|
||||
@@ -1583,16 +1592,24 @@ fn run_1_job(
|
||||
// However, if there are no redirections, then we can just jump into the block directly, which
|
||||
// is significantly faster.
|
||||
if self.job_is_simple_block(job_node) {
|
||||
let mut block = None;
|
||||
let mut result =
|
||||
self.apply_variable_assignments(ctx, None, &job_node.variables, &mut block);
|
||||
let variable_statement = &job_node.statement2;
|
||||
let mut block = variable_statement.statement.is_some().then_some(None);
|
||||
let mut result = self.apply_variable_assignments(
|
||||
ctx,
|
||||
None,
|
||||
&variable_statement.variables,
|
||||
block.as_mut(),
|
||||
);
|
||||
let Some(statement) = variable_statement.statement.as_ref() else {
|
||||
return result;
|
||||
};
|
||||
let _scope = ScopeGuard::new((), |()| {
|
||||
if let Some(block) = block {
|
||||
if let Some(Some(block)) = block {
|
||||
ctx.parser().pop_block(block);
|
||||
}
|
||||
});
|
||||
|
||||
let specific_statement = &job_node.statement.contents;
|
||||
let specific_statement = &statement.contents;
|
||||
assert!(specific_statement_type_is_redirectable_block(
|
||||
specific_statement
|
||||
));
|
||||
@@ -1819,13 +1836,7 @@ fn populate_job_from_job_node(
|
||||
// Create processes. Each one may fail.
|
||||
let mut processes = ProcessList::new();
|
||||
processes.push(Box::new(Process::new()));
|
||||
let mut result = self.populate_job_process(
|
||||
ctx,
|
||||
j,
|
||||
&mut processes[0],
|
||||
&job_node.statement,
|
||||
&job_node.variables,
|
||||
);
|
||||
let mut result = self.populate_job_process(ctx, j, &mut processes[0], &job_node.statement2);
|
||||
|
||||
// Construct process_ts for job continuations (pipelines).
|
||||
for jc in &job_node.continuation {
|
||||
@@ -1859,13 +1870,8 @@ fn populate_job_from_job_node(
|
||||
|
||||
// Store the new process (and maybe with an error).
|
||||
processes.push(Box::new(Process::new()));
|
||||
result = self.populate_job_process(
|
||||
ctx,
|
||||
j,
|
||||
processes.last_mut().unwrap(),
|
||||
&jc.statement,
|
||||
&jc.variables,
|
||||
);
|
||||
result =
|
||||
self.populate_job_process(ctx, j, processes.last_mut().unwrap(), &jc.statement2);
|
||||
}
|
||||
|
||||
// Inform our processes of who is first and last
|
||||
@@ -2016,18 +2022,29 @@ fn job_node_wants_timing(job_node: &ast::JobPipeline) -> bool {
|
||||
if ns.time.is_some() {
|
||||
return true;
|
||||
}
|
||||
stat = &ns.contents;
|
||||
stat = match ns.negated_statement.statement.as_ref() {
|
||||
Some(s) => s,
|
||||
None => return false,
|
||||
};
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
};
|
||||
|
||||
// Do we have a 'not time ...' anywhere in our pipeline?
|
||||
if is_timed_not_statement(&job_node.statement) {
|
||||
if job_node
|
||||
.statement3()
|
||||
.map(is_timed_not_statement)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
for jc in &job_node.continuation {
|
||||
if is_timed_not_statement(&jc.statement) {
|
||||
if jc
|
||||
.statement()
|
||||
.map(is_timed_not_statement)
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct ParseToken {
|
||||
/// Hackish: if TOK_END, whether the source is a newline.
|
||||
pub is_newline: bool,
|
||||
// Hackish: whether this token is a string like FOO=bar
|
||||
pub may_be_variable_assignment: bool,
|
||||
pub is_variable_assignment: bool,
|
||||
/// If this is a tokenizer error, that error.
|
||||
pub tok_error: TokenizerError,
|
||||
source_start: SourceOffset,
|
||||
@@ -43,7 +43,7 @@ pub fn new(typ: ParseTokenType) -> Self {
|
||||
has_dash_prefix: false,
|
||||
is_help_argument: false,
|
||||
is_newline: false,
|
||||
may_be_variable_assignment: false,
|
||||
is_variable_assignment: false,
|
||||
tok_error: TokenizerError::none,
|
||||
source_start: SOURCE_OFFSET_INVALID.try_into().unwrap(),
|
||||
source_length: 0,
|
||||
|
||||
@@ -1201,7 +1201,12 @@ pub fn parse_util_detect_errors_in_ast(
|
||||
if let Some(jc) = node.as_job_continuation() {
|
||||
// Somewhat clumsy way of checking for a statement without source in a pipeline.
|
||||
// See if our pipe has source but our statement does not.
|
||||
if jc.pipe.has_source() && jc.statement.try_source_range().is_none() {
|
||||
if jc.pipe.has_source()
|
||||
&& jc
|
||||
.statement()
|
||||
.and_then(|stmt| stmt.try_source_range())
|
||||
.is_none()
|
||||
{
|
||||
has_unclosed_pipe = true;
|
||||
}
|
||||
} else if let Some(job_conjunction) = node.as_job_conjunction() {
|
||||
@@ -1627,7 +1632,8 @@ fn detect_errors_in_decorated_statement(
|
||||
// Check our pipeline position.
|
||||
let pipe_pos = if job.continuation.is_empty() {
|
||||
PipelinePosition::none
|
||||
} else if job.statement.pointer_eq(st) {
|
||||
} else if job.statement3().unwrap().pointer_eq(st) {
|
||||
// TODO(posix_mode) unwrap
|
||||
PipelinePosition::first
|
||||
} else {
|
||||
PipelinePosition::subsequent
|
||||
|
||||
@@ -41,6 +41,11 @@ fn detect_argument_errors(src: &str) -> Result<(), ParserTestErrorBits> {
|
||||
parse_util_detect_errors_in_argument(first_arg, first_arg.source(&src), &mut errors)
|
||||
}
|
||||
|
||||
assert!(
|
||||
detect_errors!("true && ") == Err(ParserTestErrorBits::INCOMPLETE),
|
||||
"unterminated conjunction not reported properly"
|
||||
);
|
||||
|
||||
// Testing block nesting
|
||||
assert!(
|
||||
detect_errors!("if; end").is_err(),
|
||||
@@ -547,13 +552,9 @@ fn test_new_parser_ad_hoc() {
|
||||
// The bug was that "a=" would produce an error but not be consumed,
|
||||
// leading to an infinite loop.
|
||||
|
||||
// By itself it should produce an error.
|
||||
let ast = Ast::parse(L!("a="), ParseTreeFlags::default(), None);
|
||||
assert!(ast.errored());
|
||||
assert!(!ast.errored());
|
||||
|
||||
// If we are leaving things unterminated, this should not produce an error.
|
||||
// i.e. when typing "a=" at the command line, it should be treated as valid
|
||||
// because we don't want to color it as an error.
|
||||
let ast = Ast::parse(L!("a="), ParseTreeFlags::LEAVE_UNTERMINATED, None);
|
||||
assert!(!ast.errored());
|
||||
|
||||
@@ -617,8 +618,6 @@ macro_rules! validate {
|
||||
validate!("begin ; }", ParseErrorCode::unbalancing_brace);
|
||||
|
||||
validate!("true | and", ParseErrorCode::andor_in_pipeline);
|
||||
|
||||
validate!("a=", ParseErrorCode::bare_variable_assignment);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user