mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-27 11:07:44 +00:00
Merge pull request #4558 from leon3s/fix-test-with-parse-error
test: replace panic in favor of ParseError
This commit is contained in:
commit
76793d523d
4 changed files with 150 additions and 75 deletions
37
src/uu/test/src/error.rs
Normal file
37
src/uu/test/src/error.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
/// Represents an error encountered while parsing a test expression
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ParseError {
|
||||||
|
ExpectedValue,
|
||||||
|
Expected(String),
|
||||||
|
ExtraArgument(String),
|
||||||
|
MissingArgument(String),
|
||||||
|
UnknownOperator(String),
|
||||||
|
InvalidInteger(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Result type for parsing test expressions
|
||||||
|
pub type ParseResult<T> = Result<T, ParseError>;
|
||||||
|
|
||||||
|
/// Implement Display trait for ParseError to make it easier to print useful errors.
|
||||||
|
impl std::fmt::Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Expected(s) => write!(f, "expected {}", s),
|
||||||
|
Self::ExpectedValue => write!(f, "expected value"),
|
||||||
|
Self::MissingArgument(s) => write!(f, "missing argument after {}", s),
|
||||||
|
Self::ExtraArgument(s) => write!(f, "extra argument {}", s),
|
||||||
|
Self::UnknownOperator(s) => write!(f, "unknown operator {}", s),
|
||||||
|
Self::InvalidInteger(s) => write!(f, "invalid integer {}", s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement UError trait for ParseError to make it easier to return useful error codes from main().
|
||||||
|
impl uucore::error::UError for ParseError {
|
||||||
|
fn code(&self) -> i32 {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implement standard Error trait for UError
|
||||||
|
impl std::error::Error for ParseError {}
|
|
@ -7,11 +7,12 @@
|
||||||
|
|
||||||
// spell-checker:ignore (grammar) BOOLOP STRLEN FILETEST FILEOP INTOP STRINGOP ; (vars) LParen StrlenOp
|
// spell-checker:ignore (grammar) BOOLOP STRLEN FILETEST FILEOP INTOP STRINGOP ; (vars) LParen StrlenOp
|
||||||
|
|
||||||
use std::ffi::OsString;
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::iter::Peekable;
|
use std::iter::Peekable;
|
||||||
|
|
||||||
|
use super::error::{ParseError, ParseResult};
|
||||||
|
|
||||||
use uucore::display::Quotable;
|
use uucore::display::Quotable;
|
||||||
use uucore::show_error;
|
|
||||||
|
|
||||||
/// Represents one of the binary comparison operators for strings, integers, or files
|
/// Represents one of the binary comparison operators for strings, integers, or files
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
@ -90,6 +91,27 @@ impl Symbol {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Implement Display trait for Symbol to make it easier to print useful errors.
|
||||||
|
/// We will try to match the format in which the symbol appears in the input.
|
||||||
|
impl std::fmt::Display for Symbol {
|
||||||
|
/// Format a Symbol for printing
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let s = match &self {
|
||||||
|
Self::LParen => OsStr::new("("),
|
||||||
|
Self::Bang => OsStr::new("!"),
|
||||||
|
Self::BoolOp(s)
|
||||||
|
| Self::Literal(s)
|
||||||
|
| Self::Op(Operator::String(s))
|
||||||
|
| Self::Op(Operator::Int(s))
|
||||||
|
| Self::Op(Operator::File(s))
|
||||||
|
| Self::UnaryOp(UnaryOperator::StrlenOp(s))
|
||||||
|
| Self::UnaryOp(UnaryOperator::FiletestOp(s)) => OsStr::new(s),
|
||||||
|
Self::None => OsStr::new("None"),
|
||||||
|
};
|
||||||
|
write!(f, "{}", s.quote())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Recursive descent parser for test, which converts a list of OsStrings
|
/// Recursive descent parser for test, which converts a list of OsStrings
|
||||||
/// (typically command line arguments) into a stack of Symbols in postfix
|
/// (typically command line arguments) into a stack of Symbols in postfix
|
||||||
/// order.
|
/// order.
|
||||||
|
@ -133,16 +155,10 @@ impl Parser {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consume the next token & verify that it matches the provided value.
|
/// Consume the next token & verify that it matches the provided value.
|
||||||
///
|
fn expect(&mut self, value: &str) -> ParseResult<()> {
|
||||||
/// # Panics
|
|
||||||
///
|
|
||||||
/// Panics if the next token does not match the provided value.
|
|
||||||
///
|
|
||||||
/// TODO: remove panics and convert Parser to return error messages.
|
|
||||||
fn expect(&mut self, value: &str) {
|
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Symbol::Literal(s) if s == value => (),
|
Symbol::Literal(s) if s == value => Ok(()),
|
||||||
_ => panic!("expected ‘{value}’"),
|
_ => Err(ParseError::Expected(value.quote().to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,25 +178,27 @@ impl Parser {
|
||||||
/// Parse an expression.
|
/// Parse an expression.
|
||||||
///
|
///
|
||||||
/// EXPR → TERM | EXPR BOOLOP EXPR
|
/// EXPR → TERM | EXPR BOOLOP EXPR
|
||||||
fn expr(&mut self) {
|
fn expr(&mut self) -> ParseResult<()> {
|
||||||
if !self.peek_is_boolop() {
|
if !self.peek_is_boolop() {
|
||||||
self.term();
|
self.term()?;
|
||||||
}
|
}
|
||||||
self.maybe_boolop();
|
self.maybe_boolop()?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a term token and possible subsequent symbols: "(", "!", UOP,
|
/// Parse a term token and possible subsequent symbols: "(", "!", UOP,
|
||||||
/// literal, or None.
|
/// literal, or None.
|
||||||
fn term(&mut self) {
|
fn term(&mut self) -> ParseResult<()> {
|
||||||
let symbol = self.next_token();
|
let symbol = self.next_token();
|
||||||
|
|
||||||
match symbol {
|
match symbol {
|
||||||
Symbol::LParen => self.lparen(),
|
Symbol::LParen => self.lparen()?,
|
||||||
Symbol::Bang => self.bang(),
|
Symbol::Bang => self.bang()?,
|
||||||
Symbol::UnaryOp(_) => self.uop(symbol),
|
Symbol::UnaryOp(_) => self.uop(symbol),
|
||||||
Symbol::None => self.stack.push(symbol),
|
Symbol::None => self.stack.push(symbol),
|
||||||
literal => self.literal(literal),
|
literal => self.literal(literal)?,
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a (possibly) parenthesized expression.
|
/// Parse a (possibly) parenthesized expression.
|
||||||
|
@ -192,7 +210,7 @@ impl Parser {
|
||||||
/// * when followed by a binary operator that is not _itself_ interpreted
|
/// * when followed by a binary operator that is not _itself_ interpreted
|
||||||
/// as a literal
|
/// as a literal
|
||||||
///
|
///
|
||||||
fn lparen(&mut self) {
|
fn lparen(&mut self) -> ParseResult<()> {
|
||||||
// Look ahead up to 3 tokens to determine if the lparen is being used
|
// Look ahead up to 3 tokens to determine if the lparen is being used
|
||||||
// as a grouping operator or should be treated as a literal string
|
// as a grouping operator or should be treated as a literal string
|
||||||
let peek3: Vec<Symbol> = self
|
let peek3: Vec<Symbol> = self
|
||||||
|
@ -204,13 +222,13 @@ impl Parser {
|
||||||
|
|
||||||
match peek3.as_slice() {
|
match peek3.as_slice() {
|
||||||
// case 1: lparen is a literal when followed by nothing
|
// case 1: lparen is a literal when followed by nothing
|
||||||
[] => self.literal(Symbol::LParen.into_literal()),
|
[] => {
|
||||||
|
self.literal(Symbol::LParen.into_literal())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
// case 2: error if end of stream is `( <any_token>`
|
// case 2: error if end of stream is `( <any_token>`
|
||||||
[symbol] => {
|
[symbol] => Err(ParseError::MissingArgument(format!("{symbol}"))),
|
||||||
show_error!("missing argument after ‘{:?}’", symbol);
|
|
||||||
std::process::exit(2);
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 3: `( uop <any_token> )` → parenthesized unary operation;
|
// case 3: `( uop <any_token> )` → parenthesized unary operation;
|
||||||
// this case ensures we don’t get confused by `( -f ) )`
|
// this case ensures we don’t get confused by `( -f ) )`
|
||||||
|
@ -218,43 +236,49 @@ impl Parser {
|
||||||
[Symbol::UnaryOp(_), _, Symbol::Literal(s)] if s == ")" => {
|
[Symbol::UnaryOp(_), _, Symbol::Literal(s)] if s == ")" => {
|
||||||
let symbol = self.next_token();
|
let symbol = self.next_token();
|
||||||
self.uop(symbol);
|
self.uop(symbol);
|
||||||
self.expect(")");
|
self.expect(")")?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 4: binary comparison of literal lparen, e.g. `( != )`
|
// case 4: binary comparison of literal lparen, e.g. `( != )`
|
||||||
[Symbol::Op(_), Symbol::Literal(s)] | [Symbol::Op(_), Symbol::Literal(s), _]
|
[Symbol::Op(_), Symbol::Literal(s)] | [Symbol::Op(_), Symbol::Literal(s), _]
|
||||||
if s == ")" =>
|
if s == ")" =>
|
||||||
{
|
{
|
||||||
self.literal(Symbol::LParen.into_literal());
|
self.literal(Symbol::LParen.into_literal())?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 5: after handling the prior cases, any single token inside
|
// case 5: after handling the prior cases, any single token inside
|
||||||
// parentheses is a literal, e.g. `( -f )`
|
// parentheses is a literal, e.g. `( -f )`
|
||||||
[_, Symbol::Literal(s)] | [_, Symbol::Literal(s), _] if s == ")" => {
|
[_, Symbol::Literal(s)] | [_, Symbol::Literal(s), _] if s == ")" => {
|
||||||
let symbol = self.next_token();
|
let symbol = self.next_token();
|
||||||
self.literal(symbol);
|
self.literal(symbol)?;
|
||||||
self.expect(")");
|
self.expect(")")?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 6: two binary ops in a row, treat the first op as a literal
|
// case 6: two binary ops in a row, treat the first op as a literal
|
||||||
[Symbol::Op(_), Symbol::Op(_), _] => {
|
[Symbol::Op(_), Symbol::Op(_), _] => {
|
||||||
let symbol = self.next_token();
|
let symbol = self.next_token();
|
||||||
self.literal(symbol);
|
self.literal(symbol)?;
|
||||||
self.expect(")");
|
self.expect(")")?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// case 7: if earlier cases didn’t match, `( op <any_token>…`
|
// case 7: if earlier cases didn’t match, `( op <any_token>…`
|
||||||
// indicates binary comparison of literal lparen with
|
// indicates binary comparison of literal lparen with
|
||||||
// anything _except_ ")" (case 4)
|
// anything _except_ ")" (case 4)
|
||||||
[Symbol::Op(_), _] | [Symbol::Op(_), _, _] => {
|
[Symbol::Op(_), _] | [Symbol::Op(_), _, _] => {
|
||||||
self.literal(Symbol::LParen.into_literal());
|
self.literal(Symbol::LParen.into_literal())?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, lparen indicates the start of a parenthesized
|
// Otherwise, lparen indicates the start of a parenthesized
|
||||||
// expression
|
// expression
|
||||||
_ => {
|
_ => {
|
||||||
self.expr();
|
self.expr()?;
|
||||||
self.expect(")");
|
self.expect(")")?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,7 +300,7 @@ impl Parser {
|
||||||
/// * `! str BOOLOP str`: negate the entire Boolean expression
|
/// * `! str BOOLOP str`: negate the entire Boolean expression
|
||||||
/// * `! str BOOLOP EXPR BOOLOP EXPR`: negate the value of the first `str` term
|
/// * `! str BOOLOP EXPR BOOLOP EXPR`: negate the value of the first `str` term
|
||||||
///
|
///
|
||||||
fn bang(&mut self) {
|
fn bang(&mut self) -> ParseResult<()> {
|
||||||
match self.peek() {
|
match self.peek() {
|
||||||
Symbol::Op(_) | Symbol::BoolOp(_) => {
|
Symbol::Op(_) | Symbol::BoolOp(_) => {
|
||||||
// we need to peek ahead one more token to disambiguate the first
|
// we need to peek ahead one more token to disambiguate the first
|
||||||
|
@ -289,14 +313,14 @@ impl Parser {
|
||||||
Symbol::Op(_) | Symbol::None => {
|
Symbol::Op(_) | Symbol::None => {
|
||||||
// op is literal
|
// op is literal
|
||||||
let op = self.next_token().into_literal();
|
let op = self.next_token().into_literal();
|
||||||
self.literal(op);
|
self.literal(op)?;
|
||||||
self.stack.push(Symbol::Bang);
|
self.stack.push(Symbol::Bang);
|
||||||
}
|
}
|
||||||
// case 2: `<! as literal> OP str [BOOLOP EXPR]`.
|
// case 2: `<! as literal> OP str [BOOLOP EXPR]`.
|
||||||
_ => {
|
_ => {
|
||||||
// bang is literal; parsing continues with op
|
// bang is literal; parsing continues with op
|
||||||
self.literal(Symbol::Bang.into_literal());
|
self.literal(Symbol::Bang.into_literal())?;
|
||||||
self.maybe_boolop();
|
self.maybe_boolop()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -317,32 +341,34 @@ impl Parser {
|
||||||
match peek4.as_slice() {
|
match peek4.as_slice() {
|
||||||
// we peeked ahead 4 but there were only 3 tokens left
|
// we peeked ahead 4 but there were only 3 tokens left
|
||||||
[Symbol::Literal(_), Symbol::BoolOp(_), Symbol::Literal(_)] => {
|
[Symbol::Literal(_), Symbol::BoolOp(_), Symbol::Literal(_)] => {
|
||||||
self.expr();
|
self.expr()?;
|
||||||
self.stack.push(Symbol::Bang);
|
self.stack.push(Symbol::Bang);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
self.term();
|
self.term()?;
|
||||||
self.stack.push(Symbol::Bang);
|
self.stack.push(Symbol::Bang);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peek at the next token and parse it as a BOOLOP or string literal,
|
/// Peek at the next token and parse it as a BOOLOP or string literal,
|
||||||
/// as appropriate.
|
/// as appropriate.
|
||||||
fn maybe_boolop(&mut self) {
|
fn maybe_boolop(&mut self) -> ParseResult<()> {
|
||||||
if self.peek_is_boolop() {
|
if self.peek_is_boolop() {
|
||||||
let symbol = self.next_token();
|
let symbol = self.next_token();
|
||||||
|
|
||||||
// BoolOp by itself interpreted as Literal
|
// BoolOp by itself interpreted as Literal
|
||||||
if let Symbol::None = self.peek() {
|
if let Symbol::None = self.peek() {
|
||||||
self.literal(symbol.into_literal());
|
self.literal(symbol.into_literal())?;
|
||||||
} else {
|
} else {
|
||||||
self.boolop(symbol);
|
self.boolop(symbol)?;
|
||||||
self.maybe_boolop();
|
self.maybe_boolop()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a Boolean expression.
|
/// Parse a Boolean expression.
|
||||||
|
@ -350,13 +376,14 @@ impl Parser {
|
||||||
/// Logical and (-a) has higher precedence than or (-o), so in an
|
/// Logical and (-a) has higher precedence than or (-o), so in an
|
||||||
/// expression like `foo -o '' -a ''`, the and subexpression is evaluated
|
/// expression like `foo -o '' -a ''`, the and subexpression is evaluated
|
||||||
/// first.
|
/// first.
|
||||||
fn boolop(&mut self, op: Symbol) {
|
fn boolop(&mut self, op: Symbol) -> ParseResult<()> {
|
||||||
if op == Symbol::BoolOp(OsString::from("-a")) {
|
if op == Symbol::BoolOp(OsString::from("-a")) {
|
||||||
self.term();
|
self.term()?;
|
||||||
} else {
|
} else {
|
||||||
self.expr();
|
self.expr()?;
|
||||||
}
|
}
|
||||||
self.stack.push(op);
|
self.stack.push(op);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a (possible) unary argument test (string length or file
|
/// Parse a (possible) unary argument test (string length or file
|
||||||
|
@ -375,7 +402,7 @@ impl Parser {
|
||||||
|
|
||||||
/// Parse a string literal, optionally followed by a comparison operator
|
/// Parse a string literal, optionally followed by a comparison operator
|
||||||
/// and a second string literal.
|
/// and a second string literal.
|
||||||
fn literal(&mut self, token: Symbol) {
|
fn literal(&mut self, token: Symbol) -> ParseResult<()> {
|
||||||
self.stack.push(token.into_literal());
|
self.stack.push(token.into_literal());
|
||||||
|
|
||||||
// EXPR → str OP str
|
// EXPR → str OP str
|
||||||
|
@ -383,21 +410,24 @@ impl Parser {
|
||||||
let op = self.next_token();
|
let op = self.next_token();
|
||||||
|
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Symbol::None => panic!("missing argument after {op:?}"),
|
Symbol::None => {
|
||||||
|
return Err(ParseError::MissingArgument(format!("{op}")));
|
||||||
|
}
|
||||||
token => self.stack.push(token.into_literal()),
|
token => self.stack.push(token.into_literal()),
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stack.push(op);
|
self.stack.push(op);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parser entry point: parse the token stream `self.tokens`, storing the
|
/// Parser entry point: parse the token stream `self.tokens`, storing the
|
||||||
/// resulting `Symbol` stack in `self.stack`.
|
/// resulting `Symbol` stack in `self.stack`.
|
||||||
fn parse(&mut self) -> Result<(), String> {
|
fn parse(&mut self) -> ParseResult<()> {
|
||||||
self.expr();
|
self.expr()?;
|
||||||
|
|
||||||
match self.tokens.next() {
|
match self.tokens.next() {
|
||||||
Some(token) => Err(format!("extra argument {}", token.quote())),
|
Some(token) => Err(ParseError::ExtraArgument(token.quote().to_string())),
|
||||||
None => Ok(()),
|
None => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -405,7 +435,7 @@ impl Parser {
|
||||||
|
|
||||||
/// Parse the token stream `args`, returning a `Symbol` stack representing the
|
/// Parse the token stream `args`, returning a `Symbol` stack representing the
|
||||||
/// operations to perform in postfix order.
|
/// operations to perform in postfix order.
|
||||||
pub fn parse(args: Vec<OsString>) -> Result<Vec<Symbol>, String> {
|
pub fn parse(args: Vec<OsString>) -> ParseResult<Vec<Symbol>> {
|
||||||
let mut p = Parser::new(args);
|
let mut p = Parser::new(args);
|
||||||
p.parse()?;
|
p.parse()?;
|
||||||
Ok(p.stack)
|
Ok(p.stack)
|
||||||
|
|
|
@ -8,9 +8,11 @@
|
||||||
|
|
||||||
// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp
|
// spell-checker:ignore (vars) egid euid FiletestOp StrlenOp
|
||||||
|
|
||||||
|
pub(crate) mod error;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
use clap::{crate_version, Command};
|
use clap::{crate_version, Command};
|
||||||
|
use error::{ParseError, ParseResult};
|
||||||
use parser::{parse, Operator, Symbol, UnaryOperator};
|
use parser::{parse, Operator, Symbol, UnaryOperator};
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
@ -128,23 +130,18 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = parse(args).and_then(|mut stack| eval(&mut stack));
|
let result = parse(args).map(|mut stack| eval(&mut stack))??;
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(result) => {
|
|
||||||
if result {
|
if result {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(1.into())
|
Err(1.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => Err(USimpleError::new(2, e)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluate a stack of Symbols, returning the result of the evaluation or
|
/// Evaluate a stack of Symbols, returning the result of the evaluation or
|
||||||
/// an error message if evaluation failed.
|
/// an error message if evaluation failed.
|
||||||
fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
|
fn eval(stack: &mut Vec<Symbol>) -> ParseResult<bool> {
|
||||||
macro_rules! pop_literal {
|
macro_rules! pop_literal {
|
||||||
() => {
|
() => {
|
||||||
match stack.pop() {
|
match stack.pop() {
|
||||||
|
@ -186,7 +183,7 @@ fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!("missing argument after '{op:?}'"));
|
return Err(ParseError::MissingArgument(op.quote().to_string()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -233,7 +230,7 @@ fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
|
||||||
|
|
||||||
Ok(if op == "-a" { a && b } else { a || b })
|
Ok(if op == "-a" { a && b } else { a || b })
|
||||||
}
|
}
|
||||||
_ => Err("expected value".to_string()),
|
_ => Err(ParseError::ExpectedValue),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,19 +238,17 @@ fn eval(stack: &mut Vec<Symbol>) -> Result<bool, String> {
|
||||||
/// `a` is the left hand side
|
/// `a` is the left hand side
|
||||||
/// `b` is the left hand side
|
/// `b` is the left hand side
|
||||||
/// `op` the operation (ex: -eq, -lt, etc)
|
/// `op` the operation (ex: -eq, -lt, etc)
|
||||||
fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
|
fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> ParseResult<bool> {
|
||||||
let format_err = |value: &OsStr| format!("invalid integer {}", value.quote());
|
|
||||||
|
|
||||||
// Parse the two inputs
|
// Parse the two inputs
|
||||||
let a: i128 = a
|
let a: i128 = a
|
||||||
.to_str()
|
.to_str()
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.ok_or_else(|| format_err(a))?;
|
.ok_or_else(|| ParseError::InvalidInteger(a.quote().to_string()))?;
|
||||||
|
|
||||||
let b: i128 = b
|
let b: i128 = b
|
||||||
.to_str()
|
.to_str()
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.ok_or_else(|| format_err(b))?;
|
.ok_or_else(|| ParseError::InvalidInteger(b.quote().to_string()))?;
|
||||||
|
|
||||||
// Do the maths
|
// Do the maths
|
||||||
Ok(match op.to_str() {
|
Ok(match op.to_str() {
|
||||||
|
@ -263,7 +258,7 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
|
||||||
Some("-ge") => a >= b,
|
Some("-ge") => a >= b,
|
||||||
Some("-lt") => a < b,
|
Some("-lt") => a < b,
|
||||||
Some("-le") => a <= b,
|
Some("-le") => a <= b,
|
||||||
_ => return Err(format!("unknown operator {}", op.quote())),
|
_ => return Err(ParseError::UnknownOperator(op.quote().to_string())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,7 +266,7 @@ fn integers(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
|
||||||
/// `a` is the left hand side
|
/// `a` is the left hand side
|
||||||
/// `b` is the left hand side
|
/// `b` is the left hand side
|
||||||
/// `op` the operation (ex: -ef, -nt, etc)
|
/// `op` the operation (ex: -ef, -nt, etc)
|
||||||
fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
|
fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> ParseResult<bool> {
|
||||||
// Don't manage the error. GNU doesn't show error when doing
|
// Don't manage the error. GNU doesn't show error when doing
|
||||||
// test foo -nt bar
|
// test foo -nt bar
|
||||||
let f_a = match fs::metadata(a) {
|
let f_a = match fs::metadata(a) {
|
||||||
|
@ -290,14 +285,14 @@ fn files(a: &OsStr, b: &OsStr, op: &OsStr) -> Result<bool, String> {
|
||||||
Some("-ef") => unimplemented!(),
|
Some("-ef") => unimplemented!(),
|
||||||
Some("-nt") => f_a.modified().unwrap() > f_b.modified().unwrap(),
|
Some("-nt") => f_a.modified().unwrap() > f_b.modified().unwrap(),
|
||||||
Some("-ot") => f_a.created().unwrap() > f_b.created().unwrap(),
|
Some("-ot") => f_a.created().unwrap() > f_b.created().unwrap(),
|
||||||
_ => return Err(format!("unknown operator {}", op.quote())),
|
_ => return Err(ParseError::UnknownOperator(op.quote().to_string())),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn isatty(fd: &OsStr) -> Result<bool, String> {
|
fn isatty(fd: &OsStr) -> ParseResult<bool> {
|
||||||
fd.to_str()
|
fd.to_str()
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.ok_or_else(|| format!("invalid integer {}", fd.quote()))
|
.ok_or_else(|| ParseError::InvalidInteger(fd.quote().to_string()))
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
#[cfg(not(target_os = "redox"))]
|
#[cfg(not(target_os = "redox"))]
|
||||||
unsafe {
|
unsafe {
|
||||||
|
|
|
@ -928,3 +928,16 @@ fn test_long_integer() {
|
||||||
])
|
])
|
||||||
.fails();
|
.fails();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_missing_argument_after() {
|
||||||
|
let mut ucmd = new_ucmd!();
|
||||||
|
|
||||||
|
let result = ucmd.args(&["(", "foo"]).fails();
|
||||||
|
result.no_stdout();
|
||||||
|
assert_eq!(result.exit_status().code().unwrap(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
result.stderr_str().trim(),
|
||||||
|
"test: missing argument after 'foo'"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue