From ca49529a46498fc87251a077bd65d2ff898cfa57 Mon Sep 17 00:00:00 2001 From: "Guillem L. Jara" <4lon3ly0@tutanota.com> Date: Tue, 26 May 2026 09:01:14 +0200 Subject: [PATCH] interpreter: ordered boolean comparisons --- interpreter/src/ir.rs | 2 +- interpreter/src/types.rs | 53 ++++++++++++++++++++++++++++++++++++++++ interpreter/src/vm.rs | 14 ++++++++--- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/interpreter/src/ir.rs b/interpreter/src/ir.rs index 8d3d2df..34df616 100644 --- a/interpreter/src/ir.rs +++ b/interpreter/src/ir.rs @@ -42,7 +42,6 @@ pub enum OpCode { Negation, ToInt, Negative, - Concat, // Binary operations Eq, @@ -61,6 +60,7 @@ pub enum OpCode { Divide, Raise, Modulo, + Concat, // Intrinsic operations LoadUser, diff --git a/interpreter/src/types.rs b/interpreter/src/types.rs index 62fdebc..1ad71cc 100644 --- a/interpreter/src/types.rs +++ b/interpreter/src/types.rs @@ -65,6 +65,10 @@ impl Value<'_> { } } + pub fn b2f(b: bool) -> Self { + Self::Float(b as usize as f64) + } + pub fn write_string(&self, f: &mut Vec) { match self { Self::String(s) | Self::Regex(s) => f.extend_from_slice(s), @@ -171,6 +175,55 @@ impl PartialEq for Value<'_> { } } +impl PartialOrd for Value<'_> { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + // Numeric comparisons + (&Self::Float(lhs), Self::Float(rhs)) => lhs.partial_cmp(rhs), + (&Self::Bool(lhs), Self::Bool(rhs)) => lhs.partial_cmp(rhs), + (&Self::Float(f), &Self::Bool(b)) => f.partial_cmp(&(b as usize as _)), + (&Self::Bool(b), Self::Float(f)) => (b as usize as f64).partial_cmp(f), + // String-based comparisons + (Self::String(lhs) | Self::Regex(lhs), Self::String(rhs) | Self::Regex(rhs)) => { + lhs.as_ref().partial_cmp(rhs) + } + (&Self::Float(f), Self::String(s) | Self::Regex(s)) => { + f.to_string().as_bytes().partial_cmp(s) + } + (Self::String(s) | Self::Regex(s), &Self::Float(f)) => { + s.as_ref().partial_cmp(f.to_string().as_bytes()) + } + (&Self::Bool(b), Self::String(s) | Self::Regex(s)) => { + (if b { b"1" } else { b"0" }).as_ref().partial_cmp(s) + } + (Self::String(s) | Self::Regex(s), &Self::Bool(b)) => { + s.as_ref().partial_cmp(if b { b"1" } else { b"0" }) + } + (Self::Untyped | Self::Unassigned, Self::String(s) | Self::Regex(s)) => { + b"".as_ref().partial_cmp(s) + } + (Self::String(s) | Self::Regex(s), Self::Untyped | Self::Unassigned) => { + s.as_ref().partial_cmp(b"") + } + (Self::Array(_), _) | (_, Self::Array(_)) => { + panic!("Attempted to use array in scalar context!") + } + (Self::Untyped | Self::Unassigned, Self::Untyped | Self::Unassigned) => { + b"".partial_cmp(b"") + } + // Copyful comparisons + (lhs, rhs) => { + let mut str_buf: Vec = Vec::new(); + str_buf.reserve_exact(lhs.string_size_hint() + rhs.string_size_hint()); + lhs.write_string(&mut str_buf); + let midpoint = str_buf.len(); + rhs.write_string(&mut str_buf); + str_buf[0..midpoint].partial_cmp(&str_buf[midpoint..]) + } + } + } +} + impl Eq for Value<'_> {} impl Hash for Value<'_> { fn hash(&self, state: &mut H) { diff --git a/interpreter/src/vm.rs b/interpreter/src/vm.rs index 0fce9d8..d9773ad 100644 --- a/interpreter/src/vm.rs +++ b/interpreter/src/vm.rs @@ -130,8 +130,16 @@ impl Interpreter<'_> { OpCode::Raise => lhs ^ rhs, OpCode::Modulo => lhs % rhs, // Float values on boolean cmps are intentional. - OpCode::Eq => Value::Float((lhs == rhs) as usize as _), - OpCode::NEq => Value::Float((lhs != rhs) as usize as _), + OpCode::Eq => Value::b2f(lhs == rhs), + OpCode::NEq => Value::b2f(lhs != rhs), + OpCode::Gt => Value::b2f(lhs > rhs), + OpCode::Lt => Value::b2f(lhs < rhs), + OpCode::GtE => Value::b2f(lhs >= rhs), + OpCode::LtE => Value::b2f(lhs <= rhs), + OpCode::And => todo!(), + OpCode::Or => todo!(), + OpCode::Matches => todo!(), + OpCode::MatchesNot => todo!(), OpCode::Concat => { let mut buf = StdVec::with_capacity( lhs.string_size_hint() + rhs.string_size_hint(), @@ -140,7 +148,7 @@ impl Interpreter<'_> { rhs.write_string(&mut buf); Value::String(buf.into()) } - _ => todo!(), + _ => unreachable!(), }; self.registers.write(dest, val); }