Skip to main content

uu_expr/
expr.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5
6use clap::{Arg, ArgAction, Command};
7use std::io::{Write, stdout};
8use syntax_tree::{AstNode, is_truthy};
9use thiserror::Error;
10use uucore::os_string_to_vec;
11use uucore::translate;
12use uucore::{
13    display::Quotable,
14    error::{UError, UResult},
15    format_usage,
16};
17
18mod locale_aware;
19mod syntax_tree;
20
21mod options {
22    pub const VERSION: &str = "version";
23    pub const HELP: &str = "help";
24    pub const EXPRESSION: &str = "expression";
25}
26
27pub type ExprResult<T> = Result<T, ExprError>;
28
29#[derive(Error, Clone, Debug, PartialEq, Eq)]
30pub enum ExprError {
31    #[error("{}", translate!("expr-error-unexpected-argument", "arg" => _0.quote()))]
32    UnexpectedArgument(String),
33    #[error("{}", translate!("expr-error-missing-argument", "arg" => _0.quote()))]
34    MissingArgument(String),
35    #[error("{}", translate!("expr-error-non-integer-argument"))]
36    NonIntegerArgument,
37    #[error("{}", translate!("expr-error-missing-operand"))]
38    MissingOperand,
39    #[error("{}", translate!("expr-error-division-by-zero"))]
40    DivisionByZero,
41    #[error("{}", translate!("expr-error-invalid-regex-expression"))]
42    InvalidRegexExpression,
43    #[error("{}", translate!("expr-error-expected-closing-brace-after", "arg" => _0.quote()))]
44    ExpectedClosingBraceAfter(String),
45    #[error("{}", translate!("expr-error-expected-closing-brace-instead-of", "arg" => _0.quote()))]
46    ExpectedClosingBraceInsteadOf(String),
47    #[error("{}", translate!("expr-error-unmatched-opening-parenthesis"))]
48    UnmatchedOpeningParenthesis,
49    #[error("{}", translate!("expr-error-unmatched-closing-parenthesis"))]
50    UnmatchedClosingParenthesis,
51    #[error("{}", translate!("expr-error-unmatched-opening-brace"))]
52    UnmatchedOpeningBrace,
53    #[error("{}", translate!("expr-error-invalid-bracket-content"))]
54    InvalidBracketContent,
55    #[error("{}", translate!("expr-error-trailing-backslash"))]
56    TrailingBackslash,
57    #[error("{}", translate!("expr-error-too-big-range-quantifier-index"))]
58    TooBigRangeQuantifierIndex,
59    #[error("{}", translate!("expr-error-match-utf8", "arg" => _0.quote()))]
60    UnsupportedNonUtf8Match(String),
61}
62
63impl UError for ExprError {
64    fn code(&self) -> i32 {
65        2
66    }
67
68    fn usage(&self) -> bool {
69        *self == Self::MissingOperand
70    }
71}
72
73pub fn uu_app() -> Command {
74    Command::new(uucore::util_name())
75        .version(uucore::crate_version!())
76        .help_template(uucore::localized_help_template(uucore::util_name()))
77        .about(translate!("expr-about"))
78        .override_usage(format_usage(&translate!("expr-usage")))
79        .after_help(translate!("expr-after-help"))
80        .infer_long_args(true)
81        .disable_help_flag(true)
82        .disable_version_flag(true)
83        .arg(
84            Arg::new(options::VERSION)
85                .long(options::VERSION)
86                .help(translate!("expr-help-version"))
87                .action(ArgAction::Version),
88        )
89        .arg(
90            Arg::new(options::HELP)
91                .long(options::HELP)
92                .help(translate!("expr-help-help"))
93                .action(ArgAction::Help),
94        )
95        .arg(
96            Arg::new(options::EXPRESSION)
97                .action(ArgAction::Append)
98                .allow_hyphen_values(true),
99        )
100}
101
102#[uucore::main(no_signals)]
103pub fn uumain(args: impl uucore::Args) -> UResult<()> {
104    // For expr utility we do not want getopts.
105    // The following usage should work without escaping hyphens: `expr -15 = 1 + 2 \* \( 3 - -4 \)`
106    let args = args
107        .skip(1) // Skip binary name
108        .map(os_string_to_vec)
109        .collect::<Result<Vec<_>, _>>()?;
110
111    if args.len() == 1 && args[0] == b"--help" {
112        uu_app().print_help()?;
113    } else if args.len() == 1 && args[0] == b"--version" {
114        writeln!(
115            stdout(),
116            "{} {}",
117            uucore::util_name(),
118            uucore::crate_version!()
119        )?;
120    } else {
121        // The first argument may be "--" and should be be ignored.
122        let args = if !args.is_empty() && args[0] == b"--" {
123            &args[1..]
124        } else {
125            &args
126        };
127
128        let res = AstNode::parse(args)?.eval()?.eval_as_string();
129        let _ = stdout().write_all(&res);
130        let _ = stdout().write_all(b"\n");
131
132        if !is_truthy(&res.into()) {
133            return Err(1.into());
134        }
135    }
136
137    Ok(())
138}