parse assignments separately from math expressions

This commit is contained in:
Devyn Cairns 2024-07-15 18:09:53 -07:00
parent c16e20592f
commit af2af1bb23
No known key found for this signature in database
2 changed files with 112 additions and 5 deletions

View File

@ -70,6 +70,12 @@ fn is_item_terminator(
|| special_tokens.contains(&c))
}
/// Assignment operators have special handling distinct from math expressions, as they cause the
/// rest of the pipeline to be consumed.
pub fn is_assignment_operator(bytes: &[u8]) -> bool {
matches!(bytes, b"=" | b"+=" | b"++=" | b"-=" | b"*=" | b"/=")
}
// A special token is one that is a byte that stands alone as its own token. For example
// when parsing a signature you may want to have `:` be able to separate tokens and also
// to be handled as its own token to notify you you're about to parse a type in the example
@ -298,7 +304,7 @@ pub fn lex_item(
let mut err = None;
let output = match &input[(span.start - span_offset)..(span.end - span_offset)] {
b"=" | b"+=" | b"++=" | b"-=" | b"*=" | b"/=" => Token {
bytes if is_assignment_operator(bytes) => Token {
contents: TokenContents::AssignmentOperator,
span,
},

View File

@ -1,5 +1,5 @@
use crate::{
lex::{lex, lex_signature},
lex::{is_assignment_operator, lex, lex_signature},
lite_parser::{lite_parse, LiteCommand, LitePipeline, LiteRedirection, LiteRedirectionTarget},
parse_keywords::*,
parse_patterns::parse_pattern,
@ -3382,7 +3382,7 @@ pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) ->
for token in &output {
match token {
Token {
contents: crate::TokenContents::Item,
contents: crate::TokenContents::Item | crate::TokenContents::AssignmentOperator,
span,
} => {
let span = *span;
@ -4799,7 +4799,7 @@ pub fn parse_value(
}
}
pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expression {
pub fn parse_assignment_operator(working_set: &mut StateWorkingSet, span: Span) -> Expression {
let contents = working_set.get_span_contents(span);
let operator = match contents {
@ -4809,6 +4809,98 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
b"-=" => Operator::Assignment(Assignment::MinusAssign),
b"*=" => Operator::Assignment(Assignment::MultiplyAssign),
b"/=" => Operator::Assignment(Assignment::DivideAssign),
_ => {
working_set.error(ParseError::Expected("assignment operator", span));
return garbage(working_set, span);
}
};
Expression::new(working_set, Expr::Operator(operator), span, Type::Any)
}
pub fn parse_assignment_expression(
working_set: &mut StateWorkingSet,
spans: &[Span],
) -> Expression {
trace!("parsing: assignment expression");
let expr_span = Span::concat(spans);
// Assignment always has the most precedence, and its right-hand side can be a pipeline
let Some(op_index) = spans
.iter()
.position(|span| is_assignment_operator(working_set.get_span_contents(*span)))
else {
working_set.error(ParseError::Expected("assignment expression", expr_span));
return garbage(working_set, expr_span);
};
let lhs_spans = &spans[0..op_index];
let op_span = spans[op_index];
let rhs_spans = &spans[(op_index + 1)..];
if lhs_spans.is_empty() {
working_set.error(ParseError::Expected(
"left hand side of assignment",
op_span,
));
return garbage(working_set, expr_span);
}
if rhs_spans.is_empty() {
working_set.error(ParseError::Expected(
"right hand side of assignment",
op_span,
));
return garbage(working_set, expr_span);
}
// Parse the lhs and operator as usual for a math expression
let lhs = parse_expression(working_set, lhs_spans);
let operator = parse_assignment_operator(working_set, op_span);
// Re-parse the right-hand side as a subexpression
let rhs_span = Span::concat(&rhs_spans);
let (rhs_tokens, rhs_error) = lex(
working_set.get_span_contents(rhs_span),
rhs_span.start,
&[],
&[],
true,
);
working_set.parse_errors.extend(rhs_error);
trace!("parsing: assignment right-hand side subexpression");
let rhs_block = parse_block(working_set, &rhs_tokens, rhs_span, false, true);
let rhs_ty = rhs_block.output_type();
let rhs_block_id = working_set.add_block(Arc::new(rhs_block));
let rhs = Expression::new(
working_set,
Expr::Subexpression(rhs_block_id),
rhs_span,
rhs_ty,
);
if !type_compatible(&lhs.ty, &rhs.ty) {
working_set.parse_errors.push(ParseError::TypeMismatch(
lhs.ty.clone(),
rhs.ty.clone(),
rhs_span,
));
}
Expression::new(
working_set,
Expr::BinaryOp(Box::new(lhs), Box::new(operator), Box::new(rhs)),
expr_span,
Type::Nothing,
)
}
pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expression {
let contents = working_set.get_span_contents(span);
let operator = match contents {
b"==" => Operator::Comparison(Comparison::Equal),
b"!=" => Operator::Comparison(Comparison::NotEqual),
b"<" => Operator::Comparison(Comparison::LessThan),
@ -4924,6 +5016,10 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi
));
return garbage(working_set, span);
}
op if is_assignment_operator(op) => {
working_set.error(ParseError::Expected("a non-assignment operator", span));
return garbage(working_set, span);
}
_ => {
working_set.error(ParseError::Expected("operator", span));
return garbage(working_set, span);
@ -5228,7 +5324,12 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex
return garbage(working_set, Span::concat(spans));
}
let output = if is_math_expression_like(working_set, spans[pos]) {
let output = if spans[pos..]
.iter()
.any(|span| is_assignment_operator(working_set.get_span_contents(*span)))
{
parse_assignment_expression(working_set, &spans[pos..])
} else if is_math_expression_like(working_set, spans[pos]) {
parse_math_expression(working_set, &spans[pos..], None)
} else {
let bytes = working_set.get_span_contents(spans[pos]).to_vec();