1use std::fmt;
11
12use crate::arithmetic;
13use crate::ast::{BinaryOp, Expr, FileTestOp, Pipeline, StringPart, StringTestOp, TestCmpOp, TestExpr, Value, VarPath};
14use std::path::Path;
15
16use super::result::ExecResult;
17use super::scope::Scope;
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum EvalError {
22 UndefinedVariable(String),
24 InvalidPath(String),
26 TypeError { expected: &'static str, got: String },
28 CommandFailed(String),
30 NoExecutor,
32 ArithmeticError(String),
34 RegexError(String),
36}
37
38impl fmt::Display for EvalError {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 match self {
41 EvalError::UndefinedVariable(name) => write!(f, "undefined variable: {name}"),
42 EvalError::InvalidPath(path) => write!(f, "invalid path: {path}"),
43 EvalError::TypeError { expected, got } => {
44 write!(f, "type error: expected {expected}, got {got}")
45 }
46 EvalError::CommandFailed(msg) => write!(f, "command failed: {msg}"),
47 EvalError::NoExecutor => write!(f, "no executor available for command substitution"),
48 EvalError::ArithmeticError(msg) => write!(f, "arithmetic error: {msg}"),
49 EvalError::RegexError(msg) => write!(f, "regex error: {msg}"),
50 }
51 }
52}
53
54impl std::error::Error for EvalError {}
55
56pub type EvalResult<T> = Result<T, EvalError>;
58
59pub trait Executor {
65 fn execute(&mut self, pipeline: &Pipeline, scope: &mut Scope) -> EvalResult<ExecResult>;
72}
73
74pub struct NoOpExecutor;
78
79impl Executor for NoOpExecutor {
80 fn execute(&mut self, _pipeline: &Pipeline, _scope: &mut Scope) -> EvalResult<ExecResult> {
81 Err(EvalError::NoExecutor)
82 }
83}
84
85pub struct Evaluator<'a, E: Executor> {
90 scope: &'a mut Scope,
91 executor: &'a mut E,
92}
93
94impl<'a, E: Executor> Evaluator<'a, E> {
95 pub fn new(scope: &'a mut Scope, executor: &'a mut E) -> Self {
97 Self { scope, executor }
98 }
99
100 pub fn eval(&mut self, expr: &Expr) -> EvalResult<Value> {
102 match expr {
103 Expr::Literal(value) => self.eval_literal(value),
104 Expr::VarRef(path) => self.eval_var_ref(path),
105 Expr::Interpolated(parts) => self.eval_interpolated(parts),
106 Expr::BinaryOp { left, op, right } => self.eval_binary_op(left, *op, right),
107 Expr::CommandSubst(pipeline) => self.eval_command_subst(pipeline),
108 Expr::Test(test_expr) => self.eval_test(test_expr),
109 Expr::Positional(n) => self.eval_positional(*n),
110 Expr::AllArgs => self.eval_all_args(),
111 Expr::ArgCount => self.eval_arg_count(),
112 Expr::VarLength(name) => self.eval_var_length(name),
113 Expr::VarWithDefault { name, default } => self.eval_var_with_default(name, default),
114 Expr::Arithmetic(expr_str) => self.eval_arithmetic(expr_str),
115 Expr::Command(cmd) => self.eval_command(cmd),
116 Expr::LastExitCode => self.eval_last_exit_code(),
117 Expr::CurrentPid => self.eval_current_pid(),
118 }
119 }
120
121 fn eval_last_exit_code(&self) -> EvalResult<Value> {
123 Ok(Value::Int(self.scope.last_result().code))
124 }
125
126 fn eval_current_pid(&self) -> EvalResult<Value> {
128 Ok(Value::Int(self.scope.pid() as i64))
129 }
130
131 fn eval_command(&mut self, cmd: &crate::ast::Command) -> EvalResult<Value> {
133 match cmd.name.as_str() {
136 "true" => return Ok(Value::Bool(true)),
137 "false" => return Ok(Value::Bool(false)),
138 _ => {}
139 }
140
141 let pipeline = crate::ast::Pipeline {
143 commands: vec![cmd.clone()],
144 background: false,
145 };
146 let result = self.executor.execute(&pipeline, self.scope)?;
147 Ok(Value::Bool(result.code == 0))
149 }
150
151 fn eval_arithmetic(&mut self, expr_str: &str) -> EvalResult<Value> {
153 arithmetic::eval_arithmetic(expr_str, self.scope)
154 .map(Value::Int)
155 .map_err(|e| EvalError::ArithmeticError(e.to_string()))
156 }
157
158 fn eval_test(&mut self, test_expr: &TestExpr) -> EvalResult<Value> {
160 let result = match test_expr {
161 TestExpr::FileTest { op, path } => {
162 let path_value = self.eval(path)?;
163 let path_str = value_to_string(&path_value);
164 let path = Path::new(&path_str);
165 match op {
166 FileTestOp::Exists => path.exists(),
167 FileTestOp::IsFile => path.is_file(),
168 FileTestOp::IsDir => path.is_dir(),
169 FileTestOp::Readable => path.exists() && std::fs::metadata(path).is_ok(),
170 FileTestOp::Writable => {
171 if path.exists() {
173 std::fs::OpenOptions::new().write(true).open(path).is_ok()
174 } else {
175 false
176 }
177 }
178 FileTestOp::Executable => {
179 #[cfg(unix)]
180 {
181 use std::os::unix::fs::PermissionsExt;
182 path.metadata()
183 .map(|m| m.permissions().mode() & 0o111 != 0)
184 .unwrap_or(false)
185 }
186 #[cfg(not(unix))]
187 {
188 path.exists()
189 }
190 }
191 }
192 }
193 TestExpr::StringTest { op, value } => {
194 let val = self.eval(value)?;
195 let s = value_to_string(&val);
196 match op {
197 StringTestOp::IsEmpty => s.is_empty(),
198 StringTestOp::IsNonEmpty => !s.is_empty(),
199 }
200 }
201 TestExpr::Comparison { left, op, right } => {
202 let left_val = self.eval(left)?;
203 let right_val = self.eval(right)?;
204
205 match op {
206 TestCmpOp::Eq => values_equal(&left_val, &right_val),
207 TestCmpOp::NotEq => !values_equal(&left_val, &right_val),
208 TestCmpOp::Match => {
209 match regex_match(&left_val, &right_val, false) {
211 Ok(Value::Bool(b)) => b,
212 Ok(_) => false,
213 Err(_) => false,
214 }
215 }
216 TestCmpOp::NotMatch => {
217 match regex_match(&left_val, &right_val, true) {
219 Ok(Value::Bool(b)) => b,
220 Ok(_) => true,
221 Err(_) => true,
222 }
223 }
224 TestCmpOp::Gt | TestCmpOp::Lt | TestCmpOp::GtEq | TestCmpOp::LtEq => {
225 let ord = compare_values(&left_val, &right_val)?;
228 match op {
229 TestCmpOp::Gt => ord.is_gt(),
230 TestCmpOp::Lt => ord.is_lt(),
231 TestCmpOp::GtEq => ord.is_ge(),
232 TestCmpOp::LtEq => ord.is_le(),
233 _ => unreachable!(),
234 }
235 }
236 }
237 }
238 TestExpr::And { left, right } => {
239 let left_result = self.eval_test(left)?;
241 if !value_to_bool(&left_result) {
242 false } else {
244 value_to_bool(&self.eval_test(right)?)
245 }
246 }
247 TestExpr::Or { left, right } => {
248 let left_result = self.eval_test(left)?;
250 if value_to_bool(&left_result) {
251 true } else {
253 value_to_bool(&self.eval_test(right)?)
254 }
255 }
256 TestExpr::Not { expr } => {
257 let result = self.eval_test(expr)?;
258 !value_to_bool(&result)
259 }
260 };
261 Ok(Value::Bool(result))
262 }
263
264 fn eval_literal(&mut self, value: &Value) -> EvalResult<Value> {
266 Ok(value.clone())
267 }
268
269 fn eval_var_ref(&mut self, path: &VarPath) -> EvalResult<Value> {
271 self.scope
272 .resolve_path(path)
273 .ok_or_else(|| EvalError::InvalidPath(format_path(path)))
274 }
275
276 fn eval_positional(&self, n: usize) -> EvalResult<Value> {
278 match self.scope.get_positional(n) {
279 Some(s) => Ok(Value::String(s.to_string())),
280 None => Ok(Value::String(String::new())), }
282 }
283
284 fn eval_all_args(&self) -> EvalResult<Value> {
288 let args = self.scope.all_args();
289 Ok(Value::String(args.join(" ")))
290 }
291
292 fn eval_arg_count(&self) -> EvalResult<Value> {
294 Ok(Value::Int(self.scope.arg_count() as i64))
295 }
296
297 fn eval_var_length(&self, name: &str) -> EvalResult<Value> {
299 match self.scope.get(name) {
300 Some(value) => {
301 let s = value_to_string(value);
302 Ok(Value::Int(s.len() as i64))
303 }
304 None => Ok(Value::Int(0)), }
306 }
307
308 fn eval_var_with_default(&mut self, name: &str, default: &[StringPart]) -> EvalResult<Value> {
311 match self.scope.get(name) {
312 Some(value) => {
313 let s = value_to_string(value);
314 if s.is_empty() {
315 self.eval_interpolated(default)
317 } else {
318 Ok(value.clone())
319 }
320 }
321 None => {
322 self.eval_interpolated(default)
324 }
325 }
326 }
327
328 fn eval_interpolated(&mut self, parts: &[StringPart]) -> EvalResult<Value> {
330 let mut result = String::new();
331 for part in parts {
332 match part {
333 StringPart::Literal(s) => result.push_str(s),
334 StringPart::Var(path) => {
335 if let Some(value) = self.scope.resolve_path(path) {
337 result.push_str(&value_to_string(&value));
338 }
339 }
340 StringPart::VarWithDefault { name, default } => {
341 let value = self.eval_var_with_default(name, default)?;
342 result.push_str(&value_to_string(&value));
343 }
344 StringPart::VarLength(name) => {
345 let value = self.eval_var_length(name)?;
346 result.push_str(&value_to_string(&value));
347 }
348 StringPart::Positional(n) => {
349 let value = self.eval_positional(*n)?;
350 result.push_str(&value_to_string(&value));
351 }
352 StringPart::AllArgs => {
353 let value = self.eval_all_args()?;
354 result.push_str(&value_to_string(&value));
355 }
356 StringPart::ArgCount => {
357 let value = self.eval_arg_count()?;
358 result.push_str(&value_to_string(&value));
359 }
360 StringPart::Arithmetic(expr) => {
361 let value = self.eval_arithmetic_string(expr)?;
363 result.push_str(&value_to_string(&value));
364 }
365 StringPart::CommandSubst(pipeline) => {
366 let value = self.eval_command_subst(pipeline)?;
368 result.push_str(&value_to_string(&value));
369 }
370 StringPart::LastExitCode => {
371 result.push_str(&self.scope.last_result().code.to_string());
372 }
373 StringPart::CurrentPid => {
374 result.push_str(&self.scope.pid().to_string());
375 }
376 }
377 }
378 Ok(Value::String(result))
379 }
380
381 fn eval_arithmetic_string(&mut self, expr: &str) -> EvalResult<Value> {
383 arithmetic::eval_arithmetic(expr, self.scope)
385 .map(Value::Int)
386 .map_err(|e| EvalError::ArithmeticError(e.to_string()))
387 }
388
389 fn eval_binary_op(&mut self, left: &Expr, op: BinaryOp, right: &Expr) -> EvalResult<Value> {
391 match op {
392 BinaryOp::And => {
394 let left_val = self.eval(left)?;
395 if !is_truthy(&left_val) {
396 return Ok(left_val);
397 }
398 self.eval(right)
399 }
400 BinaryOp::Or => {
401 let left_val = self.eval(left)?;
402 if is_truthy(&left_val) {
403 return Ok(left_val);
404 }
405 self.eval(right)
406 }
407 BinaryOp::Eq => {
409 let left_val = self.eval(left)?;
410 let right_val = self.eval(right)?;
411 Ok(Value::Bool(values_equal(&left_val, &right_val)))
412 }
413 BinaryOp::NotEq => {
414 let left_val = self.eval(left)?;
415 let right_val = self.eval(right)?;
416 Ok(Value::Bool(!values_equal(&left_val, &right_val)))
417 }
418 BinaryOp::Lt => {
419 let left_val = self.eval(left)?;
420 let right_val = self.eval(right)?;
421 compare_values(&left_val, &right_val).map(|ord| Value::Bool(ord.is_lt()))
422 }
423 BinaryOp::Gt => {
424 let left_val = self.eval(left)?;
425 let right_val = self.eval(right)?;
426 compare_values(&left_val, &right_val).map(|ord| Value::Bool(ord.is_gt()))
427 }
428 BinaryOp::LtEq => {
429 let left_val = self.eval(left)?;
430 let right_val = self.eval(right)?;
431 compare_values(&left_val, &right_val).map(|ord| Value::Bool(ord.is_le()))
432 }
433 BinaryOp::GtEq => {
434 let left_val = self.eval(left)?;
435 let right_val = self.eval(right)?;
436 compare_values(&left_val, &right_val).map(|ord| Value::Bool(ord.is_ge()))
437 }
438 BinaryOp::Match => {
440 let left_val = self.eval(left)?;
441 let right_val = self.eval(right)?;
442 regex_match(&left_val, &right_val, false)
443 }
444 BinaryOp::NotMatch => {
445 let left_val = self.eval(left)?;
446 let right_val = self.eval(right)?;
447 regex_match(&left_val, &right_val, true)
448 }
449 }
450 }
451
452 fn eval_command_subst(&mut self, pipeline: &Pipeline) -> EvalResult<Value> {
454 let result = self.executor.execute(pipeline, self.scope)?;
455
456 self.scope.set_last_result(result.clone());
458
459 Ok(result_to_value(&result))
462 }
463}
464
465pub fn value_to_string(value: &Value) -> String {
467 match value {
468 Value::Null => "null".to_string(),
469 Value::Bool(b) => b.to_string(),
470 Value::Int(i) => i.to_string(),
471 Value::Float(f) => f.to_string(),
472 Value::String(s) => s.clone(),
473 Value::Json(json) => json.to_string(),
474 Value::Blob(blob) => format!("[blob: {} {}]", blob.formatted_size(), blob.content_type),
475 }
476}
477
478pub fn value_to_bool(value: &Value) -> bool {
488 match value {
489 Value::Null => false,
490 Value::Bool(b) => *b,
491 Value::Int(i) => *i != 0,
492 Value::Float(f) => *f != 0.0,
493 Value::String(s) => !s.is_empty(),
494 Value::Json(json) => match json {
495 serde_json::Value::Null => false,
496 serde_json::Value::Array(arr) => !arr.is_empty(),
497 serde_json::Value::Object(obj) => !obj.is_empty(),
498 serde_json::Value::Bool(b) => *b,
499 serde_json::Value::Number(n) => n.as_f64().map(|f| f != 0.0).unwrap_or(false),
500 serde_json::Value::String(s) => !s.is_empty(),
501 },
502 Value::Blob(_) => true, }
504}
505
506pub fn expand_tilde(s: &str) -> String {
514 if s == "~" {
515 std::env::var("HOME").unwrap_or_else(|_| "~".to_string())
516 } else if s.starts_with("~/") {
517 match std::env::var("HOME") {
518 Ok(home) => format!("{}{}", home, &s[1..]),
519 Err(_) => s.to_string(),
520 }
521 } else if s.starts_with('~') {
522 expand_tilde_user(s)
524 } else {
525 s.to_string()
526 }
527}
528
529#[cfg(unix)]
531fn expand_tilde_user(s: &str) -> String {
532 let (username, rest) = if let Some(slash_pos) = s[1..].find('/') {
534 (&s[1..slash_pos + 1], &s[slash_pos + 1..])
535 } else {
536 (&s[1..], "")
537 };
538
539 if username.is_empty() {
540 return s.to_string();
541 }
542
543 let passwd = match std::fs::read_to_string("/etc/passwd") {
546 Ok(content) => content,
547 Err(_) => return s.to_string(),
548 };
549
550 for line in passwd.lines() {
551 let fields: Vec<&str> = line.split(':').collect();
552 if fields.len() >= 6 && fields[0] == username {
553 let home_dir = fields[5];
554 return if rest.is_empty() {
555 home_dir.to_string()
556 } else {
557 format!("{}{}", home_dir, rest)
558 };
559 }
560 }
561
562 s.to_string()
564}
565
566#[cfg(not(unix))]
567fn expand_tilde_user(s: &str) -> String {
568 s.to_string()
570}
571
572pub fn value_to_string_with_tilde(value: &Value) -> String {
574 match value {
575 Value::String(s) if s.starts_with('~') => expand_tilde(s),
576 _ => value_to_string(value),
577 }
578}
579
580fn format_path(path: &VarPath) -> String {
582 use crate::ast::VarSegment;
583 let mut result = String::from("${");
584 for (i, seg) in path.segments.iter().enumerate() {
585 match seg {
586 VarSegment::Field(name) => {
587 if i > 0 {
588 result.push('.');
589 }
590 result.push_str(name);
591 }
592 }
593 }
594 result.push('}');
595 result
596}
597
598fn is_truthy(value: &Value) -> bool {
608 value_to_bool(value)
610}
611
612fn values_equal(left: &Value, right: &Value) -> bool {
620 match (left, right) {
621 (Value::Null, Value::Null) => true,
622 (Value::Bool(a), Value::Bool(b)) => a == b,
623 (Value::Int(a), Value::Int(b)) => a == b,
624 (Value::Float(a), Value::Float(b)) => (a - b).abs() < f64::EPSILON,
625 (Value::Int(a), Value::Float(b)) | (Value::Float(b), Value::Int(a)) => {
626 (*a as f64 - b).abs() < f64::EPSILON
627 }
628 (Value::String(a), Value::String(b)) => a == b,
629 (Value::String(s), Value::Int(n)) | (Value::Int(n), Value::String(s)) => {
631 s.parse::<i64>().map(|parsed| parsed == *n).unwrap_or(false)
632 }
633 (Value::String(s), Value::Float(f)) | (Value::Float(f), Value::String(s)) => {
635 s.parse::<f64>().map(|parsed| (parsed - f).abs() < f64::EPSILON).unwrap_or(false)
636 }
637 (Value::Json(a), Value::Json(b)) => a == b,
639 (Value::Blob(a), Value::Blob(b)) => a.id == b.id,
641 _ => false,
642 }
643}
644
645fn compare_values(left: &Value, right: &Value) -> EvalResult<std::cmp::Ordering> {
647 match (left, right) {
648 (Value::Int(a), Value::Int(b)) => Ok(a.cmp(b)),
649 (Value::Float(a), Value::Float(b)) => {
650 a.partial_cmp(b).ok_or_else(|| EvalError::ArithmeticError("NaN comparison".into()))
651 }
652 (Value::Int(a), Value::Float(b)) => {
653 (*a as f64).partial_cmp(b).ok_or_else(|| EvalError::ArithmeticError("NaN comparison".into()))
654 }
655 (Value::Float(a), Value::Int(b)) => {
656 a.partial_cmp(&(*b as f64)).ok_or_else(|| EvalError::ArithmeticError("NaN comparison".into()))
657 }
658 (Value::String(a), Value::String(b)) => Ok(a.cmp(b)),
659 _ => Err(EvalError::TypeError {
660 expected: "comparable types (numbers or strings)",
661 got: format!("{:?} vs {:?}", type_name(left), type_name(right)),
662 }),
663 }
664}
665
666fn type_name(value: &Value) -> &'static str {
668 match value {
669 Value::Null => "null",
670 Value::Bool(_) => "bool",
671 Value::Int(_) => "int",
672 Value::Float(_) => "float",
673 Value::String(_) => "string",
674 Value::Json(_) => "json",
675 Value::Blob(_) => "blob",
676 }
677}
678
679fn result_to_value(result: &ExecResult) -> Value {
685 if let Some(data) = &result.data {
687 return data.clone();
688 }
689 Value::String(result.out.trim_end().to_string())
691}
692
693fn regex_match(left: &Value, right: &Value, negate: bool) -> EvalResult<Value> {
698 let text = match left {
699 Value::String(s) => s.as_str(),
700 _ => {
701 return Err(EvalError::TypeError {
702 expected: "string",
703 got: type_name(left).to_string(),
704 })
705 }
706 };
707
708 let pattern = match right {
709 Value::String(s) => s.as_str(),
710 _ => {
711 return Err(EvalError::TypeError {
712 expected: "string (regex pattern)",
713 got: type_name(right).to_string(),
714 })
715 }
716 };
717
718 let re = regex::Regex::new(pattern).map_err(|e| EvalError::RegexError(e.to_string()))?;
719 let matches = re.is_match(text);
720
721 Ok(Value::Bool(if negate { !matches } else { matches }))
722}
723
724pub fn eval_expr(expr: &Expr, scope: &mut Scope) -> EvalResult<Value> {
728 let mut executor = NoOpExecutor;
729 let mut evaluator = Evaluator::new(scope, &mut executor);
730 evaluator.eval(expr)
731}
732
733#[cfg(test)]
734mod tests {
735 use super::*;
736 use crate::ast::VarSegment;
737
738 fn var_expr(name: &str) -> Expr {
740 Expr::VarRef(VarPath::simple(name))
741 }
742
743 #[test]
744 fn eval_literal_int() {
745 let mut scope = Scope::new();
746 let expr = Expr::Literal(Value::Int(42));
747 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(42)));
748 }
749
750 #[test]
751 fn eval_literal_string() {
752 let mut scope = Scope::new();
753 let expr = Expr::Literal(Value::String("hello".into()));
754 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::String("hello".into())));
755 }
756
757 #[test]
758 fn eval_literal_bool() {
759 let mut scope = Scope::new();
760 assert_eq!(
761 eval_expr(&Expr::Literal(Value::Bool(true)), &mut scope),
762 Ok(Value::Bool(true))
763 );
764 }
765
766 #[test]
767 fn eval_literal_null() {
768 let mut scope = Scope::new();
769 assert_eq!(
770 eval_expr(&Expr::Literal(Value::Null), &mut scope),
771 Ok(Value::Null)
772 );
773 }
774
775 #[test]
776 fn eval_literal_float() {
777 let mut scope = Scope::new();
778 let expr = Expr::Literal(Value::Float(3.14));
779 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Float(3.14)));
780 }
781
782 #[test]
783 fn eval_variable_ref() {
784 let mut scope = Scope::new();
785 scope.set("X", Value::Int(100));
786 assert_eq!(eval_expr(&var_expr("X"), &mut scope), Ok(Value::Int(100)));
787 }
788
789 #[test]
790 fn eval_undefined_variable() {
791 let mut scope = Scope::new();
792 let result = eval_expr(&var_expr("MISSING"), &mut scope);
793 assert!(matches!(result, Err(EvalError::InvalidPath(_))));
794 }
795
796 #[test]
797 fn eval_interpolated_string() {
798 let mut scope = Scope::new();
799 scope.set("NAME", Value::String("World".into()));
800
801 let expr = Expr::Interpolated(vec![
802 StringPart::Literal("Hello, ".into()),
803 StringPart::Var(VarPath::simple("NAME")),
804 StringPart::Literal("!".into()),
805 ]);
806 assert_eq!(
807 eval_expr(&expr, &mut scope),
808 Ok(Value::String("Hello, World!".into()))
809 );
810 }
811
812 #[test]
813 fn eval_interpolated_with_number() {
814 let mut scope = Scope::new();
815 scope.set("COUNT", Value::Int(42));
816
817 let expr = Expr::Interpolated(vec![
818 StringPart::Literal("Count: ".into()),
819 StringPart::Var(VarPath::simple("COUNT")),
820 ]);
821 assert_eq!(
822 eval_expr(&expr, &mut scope),
823 Ok(Value::String("Count: 42".into()))
824 );
825 }
826
827 #[test]
828 fn eval_and_short_circuit_true() {
829 let mut scope = Scope::new();
830 let expr = Expr::BinaryOp {
831 left: Box::new(Expr::Literal(Value::Bool(true))),
832 op: BinaryOp::And,
833 right: Box::new(Expr::Literal(Value::Int(42))),
834 };
835 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(42)));
837 }
838
839 #[test]
840 fn eval_and_short_circuit_false() {
841 let mut scope = Scope::new();
842 let expr = Expr::BinaryOp {
843 left: Box::new(Expr::Literal(Value::Bool(false))),
844 op: BinaryOp::And,
845 right: Box::new(Expr::Literal(Value::Int(42))),
846 };
847 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(false)));
849 }
850
851 #[test]
852 fn eval_or_short_circuit_true() {
853 let mut scope = Scope::new();
854 let expr = Expr::BinaryOp {
855 left: Box::new(Expr::Literal(Value::Bool(true))),
856 op: BinaryOp::Or,
857 right: Box::new(Expr::Literal(Value::Int(42))),
858 };
859 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
861 }
862
863 #[test]
864 fn eval_or_short_circuit_false() {
865 let mut scope = Scope::new();
866 let expr = Expr::BinaryOp {
867 left: Box::new(Expr::Literal(Value::Bool(false))),
868 op: BinaryOp::Or,
869 right: Box::new(Expr::Literal(Value::Int(42))),
870 };
871 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(42)));
873 }
874
875 #[test]
876 fn eval_equality() {
877 let mut scope = Scope::new();
878 let expr = Expr::BinaryOp {
879 left: Box::new(Expr::Literal(Value::Int(5))),
880 op: BinaryOp::Eq,
881 right: Box::new(Expr::Literal(Value::Int(5))),
882 };
883 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
884 }
885
886 #[test]
887 fn eval_inequality() {
888 let mut scope = Scope::new();
889 let expr = Expr::BinaryOp {
890 left: Box::new(Expr::Literal(Value::Int(5))),
891 op: BinaryOp::NotEq,
892 right: Box::new(Expr::Literal(Value::Int(3))),
893 };
894 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
895 }
896
897 #[test]
898 fn eval_less_than() {
899 let mut scope = Scope::new();
900 let expr = Expr::BinaryOp {
901 left: Box::new(Expr::Literal(Value::Int(3))),
902 op: BinaryOp::Lt,
903 right: Box::new(Expr::Literal(Value::Int(5))),
904 };
905 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
906 }
907
908 #[test]
909 fn eval_greater_than() {
910 let mut scope = Scope::new();
911 let expr = Expr::BinaryOp {
912 left: Box::new(Expr::Literal(Value::Int(5))),
913 op: BinaryOp::Gt,
914 right: Box::new(Expr::Literal(Value::Int(3))),
915 };
916 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
917 }
918
919 #[test]
920 fn eval_less_than_or_equal() {
921 let mut scope = Scope::new();
922 let eq = Expr::BinaryOp {
923 left: Box::new(Expr::Literal(Value::Int(5))),
924 op: BinaryOp::LtEq,
925 right: Box::new(Expr::Literal(Value::Int(5))),
926 };
927 let lt = Expr::BinaryOp {
928 left: Box::new(Expr::Literal(Value::Int(3))),
929 op: BinaryOp::LtEq,
930 right: Box::new(Expr::Literal(Value::Int(5))),
931 };
932 assert_eq!(eval_expr(&eq, &mut scope), Ok(Value::Bool(true)));
933 assert_eq!(eval_expr(<, &mut scope), Ok(Value::Bool(true)));
934 }
935
936 #[test]
937 fn eval_greater_than_or_equal() {
938 let mut scope = Scope::new();
939 let eq = Expr::BinaryOp {
940 left: Box::new(Expr::Literal(Value::Int(5))),
941 op: BinaryOp::GtEq,
942 right: Box::new(Expr::Literal(Value::Int(5))),
943 };
944 let gt = Expr::BinaryOp {
945 left: Box::new(Expr::Literal(Value::Int(7))),
946 op: BinaryOp::GtEq,
947 right: Box::new(Expr::Literal(Value::Int(5))),
948 };
949 assert_eq!(eval_expr(&eq, &mut scope), Ok(Value::Bool(true)));
950 assert_eq!(eval_expr(>, &mut scope), Ok(Value::Bool(true)));
951 }
952
953 #[test]
954 fn eval_string_comparison() {
955 let mut scope = Scope::new();
956 let expr = Expr::BinaryOp {
957 left: Box::new(Expr::Literal(Value::String("apple".into()))),
958 op: BinaryOp::Lt,
959 right: Box::new(Expr::Literal(Value::String("banana".into()))),
960 };
961 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
962 }
963
964 #[test]
965 fn eval_mixed_int_float_comparison() {
966 let mut scope = Scope::new();
967 let expr = Expr::BinaryOp {
968 left: Box::new(Expr::Literal(Value::Int(3))),
969 op: BinaryOp::Lt,
970 right: Box::new(Expr::Literal(Value::Float(3.5))),
971 };
972 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
973 }
974
975 #[test]
976 fn eval_int_float_equality() {
977 let mut scope = Scope::new();
978 let expr = Expr::BinaryOp {
979 left: Box::new(Expr::Literal(Value::Int(5))),
980 op: BinaryOp::Eq,
981 right: Box::new(Expr::Literal(Value::Float(5.0))),
982 };
983 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
984 }
985
986 #[test]
987 fn eval_type_mismatch_comparison() {
988 let mut scope = Scope::new();
989 let expr = Expr::BinaryOp {
990 left: Box::new(Expr::Literal(Value::Int(5))),
991 op: BinaryOp::Lt,
992 right: Box::new(Expr::Literal(Value::String("five".into()))),
993 };
994 assert!(matches!(eval_expr(&expr, &mut scope), Err(EvalError::TypeError { .. })));
995 }
996
997 #[test]
998 fn is_truthy_values() {
999 assert!(!is_truthy(&Value::Null));
1000 assert!(!is_truthy(&Value::Bool(false)));
1001 assert!(is_truthy(&Value::Bool(true)));
1002 assert!(!is_truthy(&Value::Int(0)));
1003 assert!(is_truthy(&Value::Int(1)));
1004 assert!(is_truthy(&Value::Int(-1)));
1005 assert!(!is_truthy(&Value::Float(0.0)));
1006 assert!(is_truthy(&Value::Float(0.1)));
1007 assert!(!is_truthy(&Value::String("".into())));
1008 assert!(is_truthy(&Value::String("x".into())));
1009 }
1010
1011 #[test]
1012 fn eval_command_subst_fails_without_executor() {
1013 use crate::ast::{Command, Pipeline};
1014
1015 let mut scope = Scope::new();
1016 let pipeline = Pipeline {
1017 commands: vec![Command {
1018 name: "echo".into(),
1019 args: vec![],
1020 redirects: vec![],
1021 }],
1022 background: false,
1023 };
1024 let expr = Expr::CommandSubst(Box::new(pipeline));
1025
1026 assert!(matches!(
1027 eval_expr(&expr, &mut scope),
1028 Err(EvalError::NoExecutor)
1029 ));
1030 }
1031
1032 #[test]
1033 fn eval_last_result_field() {
1034 let mut scope = Scope::new();
1035 scope.set_last_result(ExecResult::failure(42, "test error"));
1036
1037 let expr = Expr::VarRef(VarPath {
1039 segments: vec![
1040 VarSegment::Field("?".into()),
1041 VarSegment::Field("code".into()),
1042 ],
1043 });
1044 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(42)));
1045
1046 let expr = Expr::VarRef(VarPath {
1048 segments: vec![
1049 VarSegment::Field("?".into()),
1050 VarSegment::Field("err".into()),
1051 ],
1052 });
1053 assert_eq!(
1054 eval_expr(&expr, &mut scope),
1055 Ok(Value::String("test error".into()))
1056 );
1057 }
1058
1059 #[test]
1060 fn value_to_string_all_types() {
1061 assert_eq!(value_to_string(&Value::Null), "null");
1062 assert_eq!(value_to_string(&Value::Bool(true)), "true");
1063 assert_eq!(value_to_string(&Value::Int(42)), "42");
1064 assert_eq!(value_to_string(&Value::Float(3.14)), "3.14");
1065 assert_eq!(value_to_string(&Value::String("hello".into())), "hello");
1066 }
1067
1068 #[test]
1071 fn eval_negative_int() {
1072 let mut scope = Scope::new();
1073 let expr = Expr::Literal(Value::Int(-42));
1074 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(-42)));
1075 }
1076
1077 #[test]
1078 fn eval_negative_float() {
1079 let mut scope = Scope::new();
1080 let expr = Expr::Literal(Value::Float(-3.14));
1081 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Float(-3.14)));
1082 }
1083
1084 #[test]
1085 fn eval_zero_values() {
1086 let mut scope = Scope::new();
1087 assert_eq!(
1088 eval_expr(&Expr::Literal(Value::Int(0)), &mut scope),
1089 Ok(Value::Int(0))
1090 );
1091 assert_eq!(
1092 eval_expr(&Expr::Literal(Value::Float(0.0)), &mut scope),
1093 Ok(Value::Float(0.0))
1094 );
1095 }
1096
1097 #[test]
1098 fn eval_interpolation_empty_var() {
1099 let mut scope = Scope::new();
1100 scope.set("EMPTY", Value::String("".into()));
1101
1102 let expr = Expr::Interpolated(vec![
1103 StringPart::Literal("prefix".into()),
1104 StringPart::Var(VarPath::simple("EMPTY")),
1105 StringPart::Literal("suffix".into()),
1106 ]);
1107 assert_eq!(
1108 eval_expr(&expr, &mut scope),
1109 Ok(Value::String("prefixsuffix".into()))
1110 );
1111 }
1112
1113 #[test]
1114 fn eval_chained_and() {
1115 let mut scope = Scope::new();
1116 let expr = Expr::BinaryOp {
1118 left: Box::new(Expr::BinaryOp {
1119 left: Box::new(Expr::Literal(Value::Bool(true))),
1120 op: BinaryOp::And,
1121 right: Box::new(Expr::Literal(Value::Bool(true))),
1122 }),
1123 op: BinaryOp::And,
1124 right: Box::new(Expr::Literal(Value::Int(42))),
1125 };
1126 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(42)));
1127 }
1128
1129 #[test]
1130 fn eval_chained_or() {
1131 let mut scope = Scope::new();
1132 let expr = Expr::BinaryOp {
1134 left: Box::new(Expr::BinaryOp {
1135 left: Box::new(Expr::Literal(Value::Bool(false))),
1136 op: BinaryOp::Or,
1137 right: Box::new(Expr::Literal(Value::Bool(false))),
1138 }),
1139 op: BinaryOp::Or,
1140 right: Box::new(Expr::Literal(Value::Int(42))),
1141 };
1142 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Int(42)));
1143 }
1144
1145 #[test]
1146 fn eval_mixed_and_or() {
1147 let mut scope = Scope::new();
1148 let expr = Expr::BinaryOp {
1151 left: Box::new(Expr::BinaryOp {
1152 left: Box::new(Expr::Literal(Value::Bool(true))),
1153 op: BinaryOp::Or,
1154 right: Box::new(Expr::Literal(Value::Bool(false))),
1155 }),
1156 op: BinaryOp::And,
1157 right: Box::new(Expr::Literal(Value::Bool(true))),
1158 };
1159 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
1161 }
1162
1163 #[test]
1164 fn eval_comparison_with_variables() {
1165 let mut scope = Scope::new();
1166 scope.set("X", Value::Int(10));
1167 scope.set("Y", Value::Int(5));
1168
1169 let expr = Expr::BinaryOp {
1170 left: Box::new(var_expr("X")),
1171 op: BinaryOp::Gt,
1172 right: Box::new(var_expr("Y")),
1173 };
1174 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
1175 }
1176
1177 #[test]
1178 fn eval_string_equality() {
1179 let mut scope = Scope::new();
1180 let expr = Expr::BinaryOp {
1181 left: Box::new(Expr::Literal(Value::String("hello".into()))),
1182 op: BinaryOp::Eq,
1183 right: Box::new(Expr::Literal(Value::String("hello".into()))),
1184 };
1185 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
1186 }
1187
1188 #[test]
1189 fn eval_string_inequality() {
1190 let mut scope = Scope::new();
1191 let expr = Expr::BinaryOp {
1192 left: Box::new(Expr::Literal(Value::String("hello".into()))),
1193 op: BinaryOp::NotEq,
1194 right: Box::new(Expr::Literal(Value::String("world".into()))),
1195 };
1196 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
1197 }
1198
1199 #[test]
1200 fn eval_null_equality() {
1201 let mut scope = Scope::new();
1202 let expr = Expr::BinaryOp {
1203 left: Box::new(Expr::Literal(Value::Null)),
1204 op: BinaryOp::Eq,
1205 right: Box::new(Expr::Literal(Value::Null)),
1206 };
1207 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
1208 }
1209
1210 #[test]
1211 fn eval_null_not_equal_to_int() {
1212 let mut scope = Scope::new();
1213 let expr = Expr::BinaryOp {
1214 left: Box::new(Expr::Literal(Value::Null)),
1215 op: BinaryOp::Eq,
1216 right: Box::new(Expr::Literal(Value::Int(0))),
1217 };
1218 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(false)));
1219 }
1220
1221 #[test]
1222 fn eval_float_comparison_boundary() {
1223 let mut scope = Scope::new();
1224 let expr = Expr::BinaryOp {
1226 left: Box::new(Expr::Literal(Value::Float(1.0))),
1227 op: BinaryOp::Eq,
1228 right: Box::new(Expr::Literal(Value::Float(1.0))),
1229 };
1230 assert_eq!(eval_expr(&expr, &mut scope), Ok(Value::Bool(true)));
1231 }
1232
1233 #[test]
1234 fn eval_interpolation_with_bool() {
1235 let mut scope = Scope::new();
1236 scope.set("FLAG", Value::Bool(true));
1237
1238 let expr = Expr::Interpolated(vec![
1239 StringPart::Literal("enabled: ".into()),
1240 StringPart::Var(VarPath::simple("FLAG")),
1241 ]);
1242 assert_eq!(
1243 eval_expr(&expr, &mut scope),
1244 Ok(Value::String("enabled: true".into()))
1245 );
1246 }
1247
1248 #[test]
1249 fn eval_interpolation_with_null() {
1250 let mut scope = Scope::new();
1251 scope.set("VAL", Value::Null);
1252
1253 let expr = Expr::Interpolated(vec![
1254 StringPart::Literal("value: ".into()),
1255 StringPart::Var(VarPath::simple("VAL")),
1256 ]);
1257 assert_eq!(
1258 eval_expr(&expr, &mut scope),
1259 Ok(Value::String("value: null".into()))
1260 );
1261 }
1262
1263 #[test]
1264 fn eval_format_path_simple() {
1265 let path = VarPath::simple("X");
1266 assert_eq!(format_path(&path), "${X}");
1267 }
1268
1269 #[test]
1270 fn eval_format_path_nested() {
1271 let path = VarPath {
1272 segments: vec![
1273 VarSegment::Field("?".into()),
1274 VarSegment::Field("code".into()),
1275 ],
1276 };
1277 assert_eq!(format_path(&path), "${?.code}");
1278 }
1279
1280 #[test]
1281 fn type_name_all_types() {
1282 assert_eq!(type_name(&Value::Null), "null");
1283 assert_eq!(type_name(&Value::Bool(true)), "bool");
1284 assert_eq!(type_name(&Value::Int(1)), "int");
1285 assert_eq!(type_name(&Value::Float(1.0)), "float");
1286 assert_eq!(type_name(&Value::String("".into())), "string");
1287 }
1288
1289 #[test]
1290 fn expand_tilde_home() {
1291 if let Ok(home) = std::env::var("HOME") {
1293 assert_eq!(expand_tilde("~"), home);
1294 assert_eq!(expand_tilde("~/foo"), format!("{}/foo", home));
1295 assert_eq!(expand_tilde("~/foo/bar"), format!("{}/foo/bar", home));
1296 }
1297 }
1298
1299 #[test]
1300 fn expand_tilde_passthrough() {
1301 assert_eq!(expand_tilde("/home/user"), "/home/user");
1303 assert_eq!(expand_tilde("foo~bar"), "foo~bar");
1304 assert_eq!(expand_tilde(""), "");
1305 }
1306
1307 #[test]
1308 #[cfg(unix)]
1309 fn expand_tilde_user() {
1310 let expanded = expand_tilde("~root");
1312 assert!(
1314 expanded == "/root" || expanded == "/var/root",
1315 "expected /root or /var/root, got: {}",
1316 expanded
1317 );
1318
1319 let expanded_path = expand_tilde("~root/subdir");
1321 assert!(
1322 expanded_path == "/root/subdir" || expanded_path == "/var/root/subdir",
1323 "expected /root/subdir or /var/root/subdir, got: {}",
1324 expanded_path
1325 );
1326
1327 let nonexistent = expand_tilde("~nonexistent_user_12345");
1329 assert_eq!(nonexistent, "~nonexistent_user_12345");
1330 }
1331
1332 #[test]
1333 fn value_to_string_with_tilde_expansion() {
1334 if let Ok(home) = std::env::var("HOME") {
1335 let val = Value::String("~/test".into());
1336 assert_eq!(value_to_string_with_tilde(&val), format!("{}/test", home));
1337 }
1338 }
1339
1340 #[test]
1341 fn eval_positional_param() {
1342 let mut scope = Scope::new();
1343 scope.set_positional("my_tool", vec!["hello".into(), "world".into()]);
1344
1345 let expr = Expr::Positional(0);
1347 let result = eval_expr(&expr, &mut scope).unwrap();
1348 assert_eq!(result, Value::String("my_tool".into()));
1349
1350 let expr = Expr::Positional(1);
1352 let result = eval_expr(&expr, &mut scope).unwrap();
1353 assert_eq!(result, Value::String("hello".into()));
1354
1355 let expr = Expr::Positional(2);
1357 let result = eval_expr(&expr, &mut scope).unwrap();
1358 assert_eq!(result, Value::String("world".into()));
1359
1360 let expr = Expr::Positional(3);
1362 let result = eval_expr(&expr, &mut scope).unwrap();
1363 assert_eq!(result, Value::String("".into()));
1364 }
1365
1366 #[test]
1367 fn eval_all_args() {
1368 let mut scope = Scope::new();
1369 scope.set_positional("test", vec!["a".into(), "b".into(), "c".into()]);
1370
1371 let expr = Expr::AllArgs;
1372 let result = eval_expr(&expr, &mut scope).unwrap();
1373
1374 assert_eq!(result, Value::String("a b c".into()));
1376 }
1377
1378 #[test]
1379 fn eval_arg_count() {
1380 let mut scope = Scope::new();
1381 scope.set_positional("test", vec!["x".into(), "y".into()]);
1382
1383 let expr = Expr::ArgCount;
1384 let result = eval_expr(&expr, &mut scope).unwrap();
1385 assert_eq!(result, Value::Int(2));
1386 }
1387
1388 #[test]
1389 fn eval_arg_count_empty() {
1390 let mut scope = Scope::new();
1391
1392 let expr = Expr::ArgCount;
1393 let result = eval_expr(&expr, &mut scope).unwrap();
1394 assert_eq!(result, Value::Int(0));
1395 }
1396
1397 #[test]
1398 fn eval_var_length_string() {
1399 let mut scope = Scope::new();
1400 scope.set("NAME", Value::String("hello".into()));
1401
1402 let expr = Expr::VarLength("NAME".into());
1403 let result = eval_expr(&expr, &mut scope).unwrap();
1404 assert_eq!(result, Value::Int(5));
1405 }
1406
1407 #[test]
1408 fn eval_var_length_empty_string() {
1409 let mut scope = Scope::new();
1410 scope.set("EMPTY", Value::String("".into()));
1411
1412 let expr = Expr::VarLength("EMPTY".into());
1413 let result = eval_expr(&expr, &mut scope).unwrap();
1414 assert_eq!(result, Value::Int(0));
1415 }
1416
1417 #[test]
1418 fn eval_var_length_unset() {
1419 let mut scope = Scope::new();
1420
1421 let expr = Expr::VarLength("MISSING".into());
1423 let result = eval_expr(&expr, &mut scope).unwrap();
1424 assert_eq!(result, Value::Int(0));
1425 }
1426
1427 #[test]
1428 fn eval_var_length_int() {
1429 let mut scope = Scope::new();
1430 scope.set("NUM", Value::Int(12345));
1431
1432 let expr = Expr::VarLength("NUM".into());
1434 let result = eval_expr(&expr, &mut scope).unwrap();
1435 assert_eq!(result, Value::Int(5)); }
1437
1438 #[test]
1439 fn eval_var_with_default_set() {
1440 let mut scope = Scope::new();
1441 scope.set("NAME", Value::String("Alice".into()));
1442
1443 let expr = Expr::VarWithDefault {
1445 name: "NAME".into(),
1446 default: vec![StringPart::Literal("default".into())],
1447 };
1448 let result = eval_expr(&expr, &mut scope).unwrap();
1449 assert_eq!(result, Value::String("Alice".into()));
1450 }
1451
1452 #[test]
1453 fn eval_var_with_default_unset() {
1454 let mut scope = Scope::new();
1455
1456 let expr = Expr::VarWithDefault {
1458 name: "MISSING".into(),
1459 default: vec![StringPart::Literal("fallback".into())],
1460 };
1461 let result = eval_expr(&expr, &mut scope).unwrap();
1462 assert_eq!(result, Value::String("fallback".into()));
1463 }
1464
1465 #[test]
1466 fn eval_var_with_default_empty() {
1467 let mut scope = Scope::new();
1468 scope.set("EMPTY", Value::String("".into()));
1469
1470 let expr = Expr::VarWithDefault {
1472 name: "EMPTY".into(),
1473 default: vec![StringPart::Literal("not empty".into())],
1474 };
1475 let result = eval_expr(&expr, &mut scope).unwrap();
1476 assert_eq!(result, Value::String("not empty".into()));
1477 }
1478
1479 #[test]
1480 fn eval_var_with_default_non_string() {
1481 let mut scope = Scope::new();
1482 scope.set("NUM", Value::Int(42));
1483
1484 let expr = Expr::VarWithDefault {
1486 name: "NUM".into(),
1487 default: vec![StringPart::Literal("default".into())],
1488 };
1489 let result = eval_expr(&expr, &mut scope).unwrap();
1490 assert_eq!(result, Value::Int(42));
1491 }
1492
1493 #[test]
1494 fn eval_unset_variable_is_empty() {
1495 let mut scope = Scope::new();
1496 let parts = vec![
1497 StringPart::Literal("prefix:".into()),
1498 StringPart::Var(VarPath::simple("UNSET")),
1499 StringPart::Literal(":suffix".into()),
1500 ];
1501 let expr = Expr::Interpolated(parts);
1502 let result = eval_expr(&expr, &mut scope).unwrap();
1503 assert_eq!(result, Value::String("prefix::suffix".into()));
1504 }
1505
1506 #[test]
1507 fn eval_unset_variable_multiple() {
1508 let mut scope = Scope::new();
1509 scope.set("SET", Value::String("hello".into()));
1510 let parts = vec![
1511 StringPart::Var(VarPath::simple("UNSET1")),
1512 StringPart::Literal("-".into()),
1513 StringPart::Var(VarPath::simple("SET")),
1514 StringPart::Literal("-".into()),
1515 StringPart::Var(VarPath::simple("UNSET2")),
1516 ];
1517 let expr = Expr::Interpolated(parts);
1518 let result = eval_expr(&expr, &mut scope).unwrap();
1519 assert_eq!(result, Value::String("-hello-".into()));
1520 }
1521}