Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions crates/deno_task_shell/src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ UNQUOTED_PENDING_WORD = ${
(TILDE_PREFIX ~ (!(OPERATOR | WHITESPACE | NEWLINE) ~ (
EXIT_STATUS |
UNQUOTED_ESCAPE_CHAR |
ARITHMETIC_EXPRESSION |
SUB_COMMAND |
("$" ~ "{" ~ VARIABLE ~ "}" | "$" ~ VARIABLE) |
UNQUOTED_CHAR |
Expand All @@ -20,6 +21,7 @@ UNQUOTED_PENDING_WORD = ${
(!(OPERATOR | WHITESPACE | NEWLINE) ~ (
EXIT_STATUS |
UNQUOTED_ESCAPE_CHAR |
ARITHMETIC_EXPRESSION |
SUB_COMMAND |
("$" ~ "{" ~ VARIABLE ~ "}" | "$" ~ VARIABLE) |
UNQUOTED_CHAR |
Expand Down Expand Up @@ -148,15 +150,24 @@ command = !{

compound_command = {
brace_group |
subshell |
// subshell |
for_clause |
case_clause |
if_clause |
while_clause |
until_clause
}

subshell = !{ "(" ~ compound_list ~ ")" }
ARITHMETIC_EXPRESSION = { "$(" ~ "(" ~ arithmetic_expr ~ ")" ~ ")" }

arithmetic_expr = { arithmetic_term ~ (add_op ~ arithmetic_term)* }
arithmetic_term = { arithmetic_factor ~ (mul_op ~ arithmetic_factor)* }
arithmetic_factor = { number | "(" ~ arithmetic_expr ~ ")" }
number = @{ ASCII_DIGIT+ }
add_op = { "+" | "-" }
mul_op = { "*" | "/" }

// subshell = !{ "(" ~ compound_list ~ ")" }
compound_list = !{ (newline_list? ~ term ~ separator?)+ }
term = !{ and_or ~ (separator ~ and_or)* }

Expand Down
74 changes: 72 additions & 2 deletions crates/deno_task_shell/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ pub struct BooleanList {

#[cfg_attr(feature = "serialization", derive(serde::Serialize))]
#[cfg_attr(feature = "serialization", serde(rename_all = "camelCase"))]
#[derive(Copy, Clone, Debug, PartialEq, Eq, Error)]
#[derive(Debug, Clone, PartialEq, Eq, Error)]
pub enum PipeSequenceOperator {
#[error("Stdout pipe operator")]
Stdout,
Expand Down Expand Up @@ -374,6 +374,8 @@ pub enum WordPart {
Variable(String),
#[error("Invalid command")]
Command(SequentialList),
#[error("Invalid arithmetic expression")]
Arithmetic(ArithmeticExpr),
#[error("Invalid quoted string")]
Quoted(Vec<WordPart>),
#[error("Invalid tilde prefix")]
Expand Down Expand Up @@ -774,7 +776,7 @@ fn parse_compound_command(pair: Pair<Rule>) -> Result<Command> {
Rule::brace_group => {
Err(miette!("Unsupported compound command brace_group"))
}
Rule::subshell => parse_subshell(inner),
// Rule::subshell => parse_subshell(inner),
Rule::for_clause => Err(miette!("Unsupported compound command for_clause")),
Rule::case_clause => {
Err(miette!("Unsupported compound command case_clause"))
Expand Down Expand Up @@ -1085,6 +1087,11 @@ fn parse_word(pair: Pair<Rule>) -> Result<Word> {
let tilde_prefix = parse_tilde_prefix(part)?;
parts.push(tilde_prefix);
}
Rule::ARITHMETIC_EXPRESSION => {
let expr = part.into_inner().next().unwrap();
let result = parse_arithmetic_expression(expr).unwrap();
parts.push(WordPart::Arithmetic(result));
}
_ => {
return Err(miette!(
"Unexpected rule in UNQUOTED_PENDING_WORD: {:?}",
Expand Down Expand Up @@ -1173,6 +1180,7 @@ fn parse_quoted_word(pair: Pair<Rule>) -> Result<WordPart> {
match part.as_rule() {
Rule::EXIT_STATUS => parts.push(WordPart::Text("$?".to_string())),
Rule::QUOTED_ESCAPE_CHAR => {
println!("QUOTED_ESCAPE_CHAR: {:?}", part.as_str());
if let Some(WordPart::Text(ref mut s)) = parts.last_mut() {
s.push_str(part.as_str());
} else {
Expand Down Expand Up @@ -1353,6 +1361,68 @@ fn parse_io_file(pair: Pair<Rule>) -> Result<(RedirectOp, IoFile)> {
Ok((redirect_op, io_file))
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ArithmeticExpr {
Number(i64),
Variable(String),
Add(Box<ArithmeticExpr>, Box<ArithmeticExpr>),
Subtract(Box<ArithmeticExpr>, Box<ArithmeticExpr>),
Multiply(Box<ArithmeticExpr>, Box<ArithmeticExpr>),
Divide(Box<ArithmeticExpr>, Box<ArithmeticExpr>),
}

fn parse_arithmetic_expression(expr: Pair<Rule>) -> Result<ArithmeticExpr, String> {
println!("parse_arithmetic_expression: {:?}", expr.as_rule());
match expr.as_rule() {
Rule::arithmetic_expr => parse_add_sub(expr.into_inner()),
// Rule::arithmetic_term => parse_mul_div(expr.into_inner()),
// Rule::arithmetic_factor => parse_factor(expr.into_inner()),
_ => Err(format!("Unexpected rule: {:?}", expr.as_rule())),
}
}

fn parse_add_sub(mut pairs: pest::iterators::Pairs<Rule>) -> Result<ArithmeticExpr, String> {
let mut expr = parse_mul_div(pairs.next().unwrap())?;
while let Some(op) = pairs.next() {
let rhs = parse_mul_div(pairs.next().unwrap())?;
expr = match op.as_str() {
"+" => ArithmeticExpr::Add(Box::new(expr), Box::new(rhs)),
"-" => ArithmeticExpr::Subtract(Box::new(expr), Box::new(rhs)),
_ => return Err(format!("Invalid operator: {}", op.as_str())),
};
}
Ok(expr)
}

fn parse_mul_div(pair: Pair<Rule>) -> Result<ArithmeticExpr, String> {
match pair.as_rule() {
Rule::arithmetic_term => {
let mut pairs = pair.into_inner();
let mut expr = parse_factor(pairs.next().unwrap())?;
while let Some(op) = pairs.next() {
let rhs = parse_factor(pairs.next().unwrap())?;
expr = match op.as_str() {
"*" => ArithmeticExpr::Multiply(Box::new(expr), Box::new(rhs)),
"/" => ArithmeticExpr::Divide(Box::new(expr), Box::new(rhs)),
_ => return Err(format!("Invalid operator: {}", op.as_str())),
};
}
Ok(expr)
}
_ => Err(format!("Unexpected rule: {:?}", pair.as_rule())),
}
}

fn parse_factor(pair: Pair<Rule>) -> Result<ArithmeticExpr, String> {
println!("parse_factor: {:?}", pair.as_rule());
match pair.as_rule() {
Rule::number => pair.as_str().parse::<i64>().map(ArithmeticExpr::Number).map_err(|e| e.to_string()),
Rule::arithmetic_expr => parse_add_sub(pair.into_inner()),
Rule::arithmetic_factor => parse_factor(pair.into_inner()),
_ => Err(format!("Unexpected rule: {:?}", pair.as_rule())),
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down
44 changes: 44 additions & 0 deletions crates/deno_task_shell/src/shell/execute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use thiserror::Error;
use tokio::task::JoinHandle;
use tokio_util::sync::CancellationToken;

use crate::parser::ArithmeticExpr;
use crate::parser::BinaryOp;
use crate::parser::Condition;
use crate::parser::ConditionInner;
Expand Down Expand Up @@ -882,6 +883,8 @@ pub enum EvaluateWordTextError {
NoFilesMatched { pattern: String },
#[error("Failed to get home directory")]
FailedToGetHomeDirectory(anyhow::Error),
#[error("Arithmetic error: {0}")]
ArithmeticError(String),
}

impl EvaluateWordTextError {
Expand Down Expand Up @@ -1041,6 +1044,11 @@ fn evaluate_word_parts(
)
.await,
),
WordPart::Arithmetic(expr) => {
let text = evaluate_arithmetic_expression(expr, &state).map_err(|e| EvaluateWordTextError::ArithmeticError(e.to_string()))?;
current_text.push(TextPart::Text(text.to_string()));
continue;
}
WordPart::Quoted(parts) => {
let text = evaluate_word_parts_inner(
parts,
Expand Down Expand Up @@ -1125,6 +1133,42 @@ fn evaluate_word_parts(
evaluate_word_parts_inner(parts, false, state, stdin, stderr)
}

fn evaluate_arithmetic_expression(expr: ArithmeticExpr, state: &ShellState) -> Result<i64, String> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this considers precedence?

match expr {
ArithmeticExpr::Number(n) => Ok(n),
ArithmeticExpr::Variable(var) => {
state.get_var(&var)
.and_then(|v| v.parse::<i64>().ok())
.ok_or_else(|| format!("Variable '{}' not found or not a valid integer", var))
},
ArithmeticExpr::Add(left, right) => {
let l = evaluate_arithmetic_expression(*left, state)?;
let r = evaluate_arithmetic_expression(*right, state)?;
Ok(l + r)
},
ArithmeticExpr::Subtract(left, right) => {
let l = evaluate_arithmetic_expression(*left, state)?;
let r = evaluate_arithmetic_expression(*right, state)?;
Ok(l - r)
},
ArithmeticExpr::Multiply(left, right) => {
let l = evaluate_arithmetic_expression(*left, state)?;
let r = evaluate_arithmetic_expression(*right, state)?;
Ok(l * r)
},
ArithmeticExpr::Divide(left, right) => {
let l = evaluate_arithmetic_expression(*left, state)?;
let r = evaluate_arithmetic_expression(*right, state)?;
if r == 0 {
Err("Division by zero".to_string())
} else {
Ok(l / r)
}
},
}

}

async fn evaluate_command_substitution(
list: SequentialList,
state: &ShellState,
Expand Down