323 lines · 9.5 KB · Rust
RawDownload
//! expr -- evaluate expressions (POSIX)//!//! Recursive-descent parser for POSIX expr grammar.//! Operators: | & = != < > <= >= + - * / % : (regex match)//! Uses the `regex` crate for the `:` operator (anchored match). use std::ffi::OsString;use std::io::{self, Write}; const MAX_EXPR_DEPTH: usize = 1024; pub fn main(args: Vec<OsString>) -> i32 {    let str_args: Vec<String> = args        .iter()        .skip(1)        .map(|a| a.to_string_lossy().to_string())        .collect();     if str_args.is_empty() {        eprintln!("expr: missing operand");        return 2;    }     let mut parser = Parser {        tokens: str_args,        pos: 0,        depth: 0,    };     match parser.parse_or() {        Ok(val) => {            if parser.pos < parser.tokens.len() {                eprintln!(                    "expr: syntax error: unexpected argument '{}'",                    parser.tokens[parser.pos]                );                return 2;            }            if let Err(msg) = write_value(&val) {                eprintln!("expr: {}", msg);                return 2;            }            if val.is_null() {                1            } else {                0            }        }        Err(msg) => {            eprintln!("expr: {}", msg);            2        }    }} /// Represents an expr value -- either an integer or a string.#[derive(Clone, Debug)]enum Value {    Int(i64),    Str(String),} impl Value {    fn is_null(&self) -> bool {        match self {            Value::Int(n) => *n == 0,            Value::Str(s) => s.is_empty(),        }    }     fn as_int(&self) -> Option<i64> {        match self {            Value::Int(n) => Some(*n),            Value::Str(s) => s.parse::<i64>().ok(),        }    }     fn as_str(&self) -> String {        match self {            Value::Int(n) => n.to_string(),            Value::Str(s) => s.clone(),        }    }} impl std::fmt::Display for Value {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        match self {            Value::Int(n) => write!(f, "{}", n),            Value::Str(s) => write!(f, "{}", s),        }    }} struct Parser {    tokens: Vec<String>,    pos: usize,    depth: usize,} impl Parser {    fn peek(&self) -> Option<&str> {        self.tokens.get(self.pos).map(|s| &s[..])    }     fn next(&mut self) -> Option<&str> {        if self.pos < self.tokens.len() {            let tok = &self.tokens[self.pos];            self.pos += 1;            Some(&tok[..])        } else {            None        }    }     fn expect(&mut self, expected: &str) -> Result<(), String> {        match self.next() {            Some(tok) if tok == expected => Ok(()),            Some(tok) => Err(format!(                "syntax error: expected '{}', got '{}'",                expected, tok            )),            None => Err(format!("syntax error: expected '{}'", expected)),        }    }     /// expr : or_expr    /// or_expr : and_expr ( '|' and_expr )*    fn parse_or(&mut self) -> Result<Value, String> {        let mut left = self.parse_and()?;        while self.peek() == Some("|") {            self.next();            let right = self.parse_and()?;            left = if !left.is_null() { left } else { right };        }        Ok(left)    }     /// and_expr : compare_expr ( '&' compare_expr )*    fn parse_and(&mut self) -> Result<Value, String> {        let mut left = self.parse_compare()?;        while self.peek() == Some("&") {            self.next();            let right = self.parse_compare()?;            left = if !left.is_null() && !right.is_null() {                left            } else {                Value::Int(0)            };        }        Ok(left)    }     /// compare_expr : add_expr ( ( '=' | '!=' | '<' | '>' | '<=' | '>=' ) add_expr )?    fn parse_compare(&mut self) -> Result<Value, String> {        let left = self.parse_add()?;        match self.peek() {            Some("=" | "!=" | "<" | ">" | "<=" | ">=") => {                let op = self.next().unwrap().to_string();                let right = self.parse_add()?;                let result = compare_values(&left, &right, &op);                Ok(Value::Int(if result { 1 } else { 0 }))            }            _ => Ok(left),        }    }     /// add_expr : mul_expr ( ( '+' | '-' ) mul_expr )*    fn parse_add(&mut self) -> Result<Value, String> {        let mut left = self.parse_mul()?;        loop {            match self.peek() {                Some("+") | Some("-") => {                    let op = self.next().unwrap().to_string();                    let right = self.parse_mul()?;                    let a = left.as_int().ok_or("non-integer argument")?;                    let b = right.as_int().ok_or("non-integer argument")?;                    let value = if op == "+" {                        a.checked_add(b)                    } else {                        a.checked_sub(b)                    };                    left = Value::Int(value.ok_or("integer overflow")?);                }                _ => break,            }        }        Ok(left)    }     /// mul_expr : match_expr ( ( '*' | '/' | '%' ) match_expr )*    fn parse_mul(&mut self) -> Result<Value, String> {        let mut left = self.parse_match()?;        loop {            match self.peek() {                Some("*") | Some("/") | Some("%") => {                    let op = self.next().unwrap().to_string();                    let right = self.parse_match()?;                    let a = left.as_int().ok_or("non-integer argument")?;                    let b = right.as_int().ok_or("non-integer argument")?;                    if (op == "/" || op == "%") && b == 0 {                        return Err("division by zero".to_string());                    }                    let value = match &op[..] {                        "*" => a.checked_mul(b),                        "/" => a.checked_div(b),                        "%" => a.checked_rem(b),                        _ => unreachable!(),                    };                    left = Value::Int(value.ok_or("integer overflow")?);                }                _ => break,            }        }        Ok(left)    }     /// match_expr : primary ( ':' primary )?    fn parse_match(&mut self) -> Result<Value, String> {        let left = self.parse_primary()?;        if self.peek() == Some(":") {            self.next();            let right = self.parse_primary()?;            let string = left.as_str();            let pattern = right.as_str();            return regex_match(&string, &pattern);        }        Ok(left)    }     /// primary : '(' expr ')' | TOKEN    fn parse_primary(&mut self) -> Result<Value, String> {        if self.peek() == Some("(") {            if self.depth >= MAX_EXPR_DEPTH {                return Err("expression nesting too deep".to_string());            }            self.next();            self.depth += 1;            let val = self.parse_or();            self.depth -= 1;            let val = val?;            self.expect(")")?;            return Ok(val);        }         match self.next() {            Some(tok) => {                // Try to parse as integer                if let Ok(n) = tok.parse::<i64>() {                    Ok(Value::Int(n))                } else {                    Ok(Value::Str(tok.to_string()))                }            }            None => Err("syntax error: missing argument".to_string()),        }    }} fn write_value(value: &Value) -> Result<(), String> {    let stdout = io::stdout();    let mut out = stdout.lock();    writeln!(out, "{value}").map_err(|e| format!("failed to write output: {e}"))?;    out.flush()        .map_err(|e| format!("failed to write output: {e}"))} fn compare_values(left: &Value, right: &Value, op: &str) -> bool {    // If both are integers, compare numerically    if let (Some(a), Some(b)) = (left.as_int(), right.as_int()) {        return match op {            "=" => a == b,            "!=" => a != b,            "<" => a < b,            ">" => a > b,            "<=" => a <= b,            ">=" => a >= b,            _ => false,        };    }    // Otherwise compare as strings    let a = left.as_str();    let b = right.as_str();    match op {        "=" => a == b,        "!=" => a != b,        "<" => a < b,        ">" => a > b,        "<=" => a <= b,        ">=" => a >= b,        _ => false,    }} /// POSIX expr regex match: anchored at start of string./// Returns the captured group \1 if present, else the match length.fn regex_match(string: &str, pattern: &str) -> Result<Value, String> {    // POSIX expr : always anchors pattern at start    let anchored = format!("^(?:{})", pattern);    let re = regex::Regex::new(&anchored).map_err(|e| format!("invalid regex: {}", e))?;     match re.captures(string) {        Some(caps) => {            // If there's a capture group, return it            if caps.len() > 1 {                let m = caps.get(1).map_or("", |m| m.as_str());                Ok(Value::Str(m.to_string()))            } else {                // Return length of match                let m = caps.get(0).unwrap();                Ok(Value::Int(m.as_str().len() as i64))            }        }        None => {            // No match: return "" if pattern has group, else 0            if pattern.contains('(') {                Ok(Value::Str(String::new()))            } else {                Ok(Value::Int(0))            }        }    }}