mirror of
https://github.com/duhanbalci/dexpr.git
synced 2026-08-30 16:01:41 +00:00
0.4.1
This commit is contained in:
+5
-2
@@ -1,6 +1,6 @@
|
||||
use crate::ast::value::Value;
|
||||
use crate::opcodes::default_fn;
|
||||
use rust_decimal::{prelude::ToPrimitive, Decimal, MathematicalOps};
|
||||
use rust_decimal::{prelude::ToPrimitive, Decimal, MathematicalOps, RoundingStrategy};
|
||||
|
||||
use super::error::VMError;
|
||||
use super::vm::VM;
|
||||
@@ -134,7 +134,10 @@ impl<'a> VM<'a> {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
self.registers[dest] = Value::Number(n.round_dp(places));
|
||||
// Half away from zero (2.5 → 3, -2.5 → -3), not banker's rounding.
|
||||
self.registers[dest] = Value::Number(
|
||||
n.round_dp_with_strategy(places, RoundingStrategy::MidpointAwayFromZero),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
+32
-1
@@ -135,7 +135,7 @@ impl<'a> VM<'a> {
|
||||
)),
|
||||
}
|
||||
}
|
||||
"length" => Ok(Value::Number(Decimal::from(s.len()))),
|
||||
"length" => Ok(Value::Number(Decimal::from(s.chars().count()))),
|
||||
"charAt" => {
|
||||
if args.is_empty() {
|
||||
return Err(VMError::RuntimeError(
|
||||
@@ -733,6 +733,37 @@ impl<'a> VM<'a> {
|
||||
_ => Err(VMError::RuntimeError("sort() requires a string field name".to_string())),
|
||||
}
|
||||
}
|
||||
// Numeric aggregates: a List holding only Numbers behaves like a
|
||||
// NumberList (an empty projection like `items.amount` yields an empty
|
||||
// List, so `items.amount.sum()` must still work).
|
||||
"sum" | "avg" | "min" | "max" => {
|
||||
let mut nums: Vec<Decimal> = Vec::with_capacity(list.len());
|
||||
for item in list.iter() {
|
||||
match item {
|
||||
Value::Number(n) => nums.push(*n),
|
||||
other => {
|
||||
return Err(VMError::RuntimeError(format!(
|
||||
"{}() requires a list of numbers, found {}",
|
||||
method,
|
||||
other.type_name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
match method {
|
||||
"sum" => Ok(Value::Number(nums.iter().sum())),
|
||||
"avg" => {
|
||||
if nums.is_empty() {
|
||||
Ok(Value::Null)
|
||||
} else {
|
||||
let sum: Decimal = nums.iter().sum();
|
||||
Ok(Value::Number(sum / Decimal::from(nums.len())))
|
||||
}
|
||||
}
|
||||
"min" => Ok(nums.iter().min().map(|n| Value::Number(*n)).unwrap_or(Value::Null)),
|
||||
_ => Ok(nums.iter().max().map(|n| Value::Number(*n)).unwrap_or(Value::Null)),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let key = (SmolStr::new_static("List"), SmolStr::from(method));
|
||||
if let Some(ext_method) = self.external_methods.as_ref().and_then(|m| m.get(&key)) {
|
||||
|
||||
@@ -221,6 +221,7 @@ impl<'a> VM<'a> {
|
||||
OpCodeByte::Not => self.handle_not(),
|
||||
OpCodeByte::Jump => self.handle_jump(),
|
||||
OpCodeByte::JumpIfFalse => self.handle_jump_if_false(),
|
||||
OpCodeByte::JumpIfTrue => self.handle_jump_if_true(),
|
||||
OpCodeByte::Concat => self.handle_concat(),
|
||||
OpCodeByte::GetProperty => self.handle_get_property(),
|
||||
OpCodeByte::SetProperty => self.handle_set_property(),
|
||||
@@ -654,6 +655,32 @@ impl<'a> VM<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Handle JumpIfTrue opcode - conditional jump (used for `||` short-circuit)
|
||||
#[inline]
|
||||
fn handle_jump_if_true(&mut self) -> Result<(), VMError> {
|
||||
let cond_reg = self.read_register_checked()?;
|
||||
let addr = self.read_jump_address()?;
|
||||
|
||||
match &self.registers[cond_reg] {
|
||||
Value::Boolean(condition) => {
|
||||
if *condition {
|
||||
self.set_position(addr)?;
|
||||
log_debug!(self, "JumpIfTrue to {} (condition=true)", addr);
|
||||
} else {
|
||||
log_debug!(self, "JumpIfTrue not taken (condition=false)");
|
||||
}
|
||||
}
|
||||
v => {
|
||||
return Err(VMError::TypeMismatch {
|
||||
expected: "Boolean".to_string(),
|
||||
got: v.type_name().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Opcode Handlers - String Operations
|
||||
// ============================================================================
|
||||
@@ -687,6 +714,22 @@ impl<'a> VM<'a> {
|
||||
let value = map.get(prop).cloned().unwrap_or(Value::Null);
|
||||
self.registers[dest] = value;
|
||||
}
|
||||
// `.length` as a property (sugar for `.length()`) on strings and lists.
|
||||
Value::String(s) if prop == "length" => {
|
||||
self.registers[dest] = Value::Number(Decimal::from(s.chars().count()));
|
||||
}
|
||||
Value::NumberList(list) if prop == "length" => {
|
||||
self.registers[dest] = Value::Number(Decimal::from(list.len()));
|
||||
}
|
||||
Value::StringList(list) if prop == "length" => {
|
||||
self.registers[dest] = Value::Number(Decimal::from(list.len()));
|
||||
}
|
||||
Value::List(list)
|
||||
if prop == "length" && !matches!(list.first(), Some(Value::Object(_))) =>
|
||||
{
|
||||
// Only when this can't be a projection of an Object field named "length"
|
||||
self.registers[dest] = Value::Number(Decimal::from(list.len()));
|
||||
}
|
||||
Value::List(list) => {
|
||||
// Property projection: list.field → extract field from each Object element
|
||||
let mut values: Vec<Value> = Vec::with_capacity(list.len());
|
||||
|
||||
Reference in New Issue
Block a user