From c491ce1027cda85a951e8e196d1922dd81bd318b Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 21 Mar 2026 12:15:25 +0100 Subject: [PATCH 1/3] feat: switched from single number value to integer and float --- crates/core/src/context/argument.rs | 16 +- crates/core/src/context/executor.rs | 12 +- crates/core/src/lib.rs | 1 + crates/core/src/runtime/functions/array.rs | 166 ++++-- crates/core/src/runtime/functions/boolean.rs | 12 +- crates/core/src/runtime/functions/http.rs | 9 +- crates/core/src/runtime/functions/number.rs | 561 ++++++++++++++----- crates/core/src/runtime/functions/object.rs | 16 +- crates/core/src/runtime/functions/text.rs | 79 +-- crates/core/src/value.rs | 55 ++ 10 files changed, 678 insertions(+), 249 deletions(-) create mode 100644 crates/core/src/value.rs diff --git a/crates/core/src/context/argument.rs b/crates/core/src/context/argument.rs index a0a61af..b49fa68 100644 --- a/crates/core/src/context/argument.rs +++ b/crates/core/src/context/argument.rs @@ -2,8 +2,9 @@ use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; use std::convert::Infallible; use tucana::shared::value::Kind; -use tucana::shared::{ListValue, Struct, Value}; +use tucana::shared::{ListValue, NumberValue, Struct, Value}; +use crate::value::number_to_f64; #[derive(Clone, Debug)] pub enum Argument { // Eval => Evaluated Value @@ -40,12 +41,23 @@ impl TryFromArgument for Value { } } +impl TryFromArgument for NumberValue { + fn try_from_argument(a: &Argument) -> Result { + match a { + Argument::Eval(Value { + kind: Some(Kind::NumberValue(n)), + }) => Ok(n.clone()), + _ => Err(type_err("Expected number", a)), + } + } +} + impl TryFromArgument for f64 { fn try_from_argument(a: &Argument) -> Result { match a { Argument::Eval(Value { kind: Some(Kind::NumberValue(n)), - }) => Ok(*n), + }) => number_to_f64(n).ok_or_else(|| type_err("Expected number", a)), _ => Err(type_err("Expected number", a)), } } diff --git a/crates/core/src/context/executor.rs b/crates/core/src/context/executor.rs index 307ee79..fe618c4 100644 --- a/crates/core/src/context/executor.rs +++ b/crates/core/src/context/executor.rs @@ -371,7 +371,17 @@ fn preview_value(value: &Value) -> String { fn format_value_json(value: &Value) -> String { match value.kind.as_ref() { - Some(Kind::NumberValue(v)) => v.to_string(), + Some(Kind::NumberValue(v)) => { + match v.number { + Some(kind) => { + match kind { + tucana::shared::number_value::Number::Integer(i) => i.to_string(), + tucana::shared::number_value::Number::Float(f) => f.to_string(), + } + } + _ => "null".to_string(), + } + }, Some(Kind::BoolValue(v)) => v.to_string(), Some(Kind::StringValue(v)) => format!("{:?}", v), Some(Kind::NullValue(_)) | None => "null".to_string(), diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index c818ae7..5770cd0 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,3 +1,4 @@ pub mod context; pub mod debug; pub mod runtime; +pub mod value; diff --git a/crates/core/src/runtime/functions/array.rs b/crates/core/src/runtime/functions/array.rs index ddd180d..57cbcda 100644 --- a/crates/core/src/runtime/functions/array.rs +++ b/crates/core/src/runtime/functions/array.rs @@ -10,6 +10,7 @@ use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; +use crate::value::{number_to_f64, number_to_string, value_from_f64, value_from_i64}; pub fn collect_array_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ @@ -339,9 +340,7 @@ fn find_index( Signal::Success(v) => match as_bool(&v) { Ok(true) => { ctx.clear_input_type(input_type); - return Signal::Success(Value { - kind: Some(Kind::NumberValue(idx as f64)), - }); + return Signal::Success(value_from_i64(idx as i64)); } Ok(false) => continue, Err(e) => { @@ -446,7 +445,7 @@ fn preview_value(value: &Value) -> String { fn format_value_json(value: &Value) -> String { match value.kind.as_ref() { - Some(Kind::NumberValue(v)) => v.to_string(), + Some(Kind::NumberValue(v)) => number_to_string(v), Some(Kind::BoolValue(v)) => v.to_string(), Some(Kind::StringValue(v)) => format!("{:?}", v), Some(Kind::NullValue(_)) | None => "null".to_string(), @@ -610,9 +609,7 @@ fn size( "Expected an array as an argument", )); }; - Signal::Success(Value { - kind: Some(Kind::NumberValue(array.values.len() as f64)), - }) + Signal::Success(value_from_i64(array.values.len() as i64)) } fn index_of( @@ -629,9 +626,7 @@ fn index_of( }; match array.values.iter().position(|x| *x == item) { - Some(i) => Signal::Success(Value { - kind: Some(Kind::NumberValue(i as f64)), - }), + Some(i) => Signal::Success(value_from_i64(i as i64)), None => Signal::Failure(RuntimeError::simple( "ValueNotFoundRuntimeError", format!("Item {:?} not found in array", item), @@ -722,7 +717,19 @@ fn sort( kind: Some(Kind::NumberValue(i)), } = v { - out.push(i); + match number_to_f64(&i) { + Some(i) => out.push(i), + None => { + ctx.clear_input_type(input_type); + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "expected return value of comparator to be a number but was {:?}", + v + ), + )); + } + } } else { ctx.clear_input_type(input_type); return Signal::Failure(RuntimeError::simple( @@ -816,7 +823,19 @@ fn sort_reverse( kind: Some(Kind::NumberValue(i)), } = v { - out.push(i); + match number_to_f64(&i) { + Some(i) => out.push(i), + None => { + ctx.clear_input_type(input_type); + return Signal::Failure(RuntimeError::simple( + "InvalidArgumentRuntimeError", + format!( + "expected return value of comparator to be a number but was {:?}", + v + ), + )); + } + } } else { ctx.clear_input_type(input_type); return Signal::Failure(RuntimeError::simple( @@ -906,16 +925,30 @@ fn min( args!(args => array: ListValue); let mut nums: Vec = Vec::new(); + let mut all_int = true; + let mut min_i64: Option = None; for v in &array.values { - if let Some(Kind::NumberValue(n)) = v.kind { - nums.push(n); + if let Some(Kind::NumberValue(n)) = &v.kind { + match n.number { + Some(tucana::shared::number_value::Number::Integer(i)) => { + min_i64 = Some(match min_i64 { + Some(curr) => curr.min(i), + None => i, + }); + nums.push(i as f64); + } + Some(tucana::shared::number_value::Number::Float(f)) => { + all_int = false; + nums.push(f); + } + None => {} + } } } match nums.iter().min_by(|a, b| a.total_cmp(b)) { - Some(m) => Signal::Success(Value { - kind: Some(Kind::NumberValue(*m)), - }), + Some(m) if all_int => Signal::Success(value_from_i64(min_i64.unwrap_or(*m as i64))), + Some(m) => Signal::Success(value_from_f64(*m)), None => Signal::Failure(RuntimeError::simple_str( "ArrayEmptyRuntimeError", "Array is empty", @@ -931,16 +964,30 @@ fn max( args!(args => array: ListValue); let mut nums: Vec = Vec::new(); + let mut all_int = true; + let mut max_i64: Option = None; for v in &array.values { - if let Some(Kind::NumberValue(n)) = v.kind { - nums.push(n); + if let Some(Kind::NumberValue(n)) = &v.kind { + match n.number { + Some(tucana::shared::number_value::Number::Integer(i)) => { + max_i64 = Some(match max_i64 { + Some(curr) => curr.max(i), + None => i, + }); + nums.push(i as f64); + } + Some(tucana::shared::number_value::Number::Float(f)) => { + all_int = false; + nums.push(f); + } + None => {} + } } } match nums.iter().max_by(|a, b| a.total_cmp(b)) { - Some(m) => Signal::Success(Value { - kind: Some(Kind::NumberValue(*m)), - }), + Some(m) if all_int => Signal::Success(value_from_i64(max_i64.unwrap_or(*m as i64))), + Some(m) => Signal::Success(value_from_f64(*m)), None => Signal::Failure(RuntimeError::simple_str( "ArrayEmptyRuntimeError", "Array is empty", @@ -955,16 +1002,37 @@ fn sum( ) -> Signal { args!(args => array: ListValue); - let mut s = 0.0; + let mut s_f = 0.0; + let mut s_i: i64 = 0; + let mut all_int = true; for v in &array.values { - if let Some(Kind::NumberValue(n)) = v.kind { - s += n; + if let Some(Kind::NumberValue(n)) = &v.kind { + match n.number { + Some(tucana::shared::number_value::Number::Integer(i)) => { + if let Some(next) = s_i.checked_add(i) { + s_i = next; + s_f += i as f64; + } else { + all_int = false; + if let Some(f) = number_to_f64(n) { + s_f += f; + } + } + } + Some(tucana::shared::number_value::Number::Float(f)) => { + all_int = false; + s_f += f; + } + None => {} + } } } - Signal::Success(Value { - kind: Some(Kind::NumberValue(s)), - }) + if all_int { + Signal::Success(value_from_i64(s_i)) + } else { + Signal::Success(value_from_f64(s_f)) + } } fn join( @@ -989,6 +1057,7 @@ fn join( mod tests { use super::*; use crate::context::context::Context; + use crate::value::{number_to_f64, number_value_from_f64, value_from_f64}; use tucana::shared::{ListValue, Value, value::Kind}; // --- helpers ------------------------------------------------------------- @@ -999,9 +1068,7 @@ mod tests { Argument::Thunk(id) } fn v_num(n: f64) -> Value { - Value { - kind: Some(Kind::NumberValue(n)), - } + value_from_f64(n) } fn v_str(s: &str) -> Value { Value { @@ -1018,12 +1085,15 @@ mod tests { kind: Some(Kind::ListValue(ListValue { values })), } } + fn k_num(n: f64) -> Option { + Some(Kind::NumberValue(number_value_from_f64(n))) + } fn expect_num(sig: Signal) -> f64 { match sig { Signal::Success(Value { kind: Some(Kind::NumberValue(n)), - }) => n, + }) => number_to_f64(&n).unwrap_or_default(), x => panic!("Expected NumberValue, got {:?}", x), } } @@ -1154,8 +1224,8 @@ mod tests { let b = v_list(vec![v_num(3.0), v_num(4.0)]); let out = expect_list(concat(&[a_val(a), a_val(b)], &mut ctx, &mut run)); assert_eq!(out.len(), 4); - assert_eq!(out[0].kind, Some(Kind::NumberValue(1.0))); - assert_eq!(out[3].kind, Some(Kind::NumberValue(4.0))); + assert_eq!(out[0].kind, k_num(1.0)); + assert_eq!(out[3].kind, k_num(4.0)); } #[test] @@ -1189,8 +1259,8 @@ mod tests { let mut run = run_from_bools(vec![true, false, true]); let out = expect_list(filter(&[a_val(array), a_thunk(1)], &mut ctx, &mut run)); assert_eq!(out.len(), 2); - assert_eq!(out[0].kind, Some(Kind::NumberValue(1.0))); - assert_eq!(out[1].kind, Some(Kind::NumberValue(3.0))); + assert_eq!(out[0].kind, k_num(1.0)); + assert_eq!(out[1].kind, k_num(3.0)); } #[test] @@ -1318,7 +1388,7 @@ mod tests { &mut run, )); assert_eq!(out.len(), 3); - assert_eq!(out[2].kind, Some(Kind::NumberValue(3.0))); + assert_eq!(out[2].kind, k_num(3.0)); } #[test] @@ -1349,8 +1419,8 @@ mod tests { &mut run, )); assert_eq!(out.len(), 2); - assert_eq!(out[0].kind, Some(Kind::NumberValue(1.0))); - assert_eq!(out[1].kind, Some(Kind::NumberValue(2.0))); + assert_eq!(out[0].kind, k_num(1.0)); + assert_eq!(out[1].kind, k_num(2.0)); } #[test] @@ -1480,9 +1550,9 @@ mod tests { let uniq = expect_list(to_unique(&[a_val(arr)], &mut ctx, &mut run)); assert_eq!(uniq.len(), 3); - assert_eq!(uniq[0].kind, Some(Kind::NumberValue(10.0))); - assert_eq!(uniq[1].kind, Some(Kind::NumberValue(42.0))); - assert_eq!(uniq[2].kind, Some(Kind::NumberValue(30.0))); + assert_eq!(uniq[0].kind, k_num(10.0)); + assert_eq!(uniq[1].kind, k_num(42.0)); + assert_eq!(uniq[2].kind, k_num(30.0)); } #[test] @@ -1530,8 +1600,8 @@ mod tests { &mut ctx, &mut run, )); - assert_eq!(out[0].kind, Some(Kind::NumberValue(3.0))); - assert_eq!(out[2].kind, Some(Kind::NumberValue(1.0))); + assert_eq!(out[0].kind, k_num(3.0)); + assert_eq!(out[2].kind, k_num(1.0)); match reverse(&[a_val(v_str("nope"))], &mut ctx, &mut run) { Signal::Failure(_) => {} @@ -1555,10 +1625,10 @@ mod tests { ]); let out = expect_list(flat(&[a_val(nested)], &mut ctx, &mut run)); assert_eq!(out.len(), 4); - assert_eq!(out[0].kind, Some(Kind::NumberValue(1.0))); - assert_eq!(out[1].kind, Some(Kind::NumberValue(2.0))); - assert_eq!(out[2].kind, Some(Kind::NumberValue(3.0))); - assert_eq!(out[3].kind, Some(Kind::NumberValue(4.0))); + assert_eq!(out[0].kind, k_num(1.0)); + assert_eq!(out[1].kind, k_num(2.0)); + assert_eq!(out[2].kind, k_num(3.0)); + assert_eq!(out[3].kind, k_num(4.0)); } // --- min / max / sum ----------------------------------------------------- diff --git a/crates/core/src/runtime/functions/boolean.rs b/crates/core/src/runtime/functions/boolean.rs index cb3e4de..c4b230a 100644 --- a/crates/core/src/runtime/functions/boolean.rs +++ b/crates/core/src/runtime/functions/boolean.rs @@ -4,6 +4,7 @@ use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; +use crate::value::value_from_i64; use tucana::shared::{Value, value::Kind}; pub fn collect_boolean_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { @@ -26,9 +27,7 @@ fn as_number( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { args!(args => value: bool); - Signal::Success(Value { - kind: Some(Kind::NumberValue((value as i64) as f64)), - }) + Signal::Success(value_from_i64(if value { 1 } else { 0 })) } fn as_text( @@ -97,6 +96,7 @@ fn negate( mod tests { use super::*; use crate::context::context::Context; + use crate::value::{number_to_f64, value_from_f64}; use tucana::shared::{Value, value::Kind}; // ---- helpers: make Arguments ---- @@ -106,9 +106,7 @@ mod tests { }) } fn a_num(n: f64) -> Argument { - Argument::Eval(Value { - kind: Some(Kind::NumberValue(n)), - }) + Argument::Eval(value_from_f64(n)) } fn a_str(s: &str) -> Argument { Argument::Eval(Value { @@ -121,7 +119,7 @@ mod tests { match sig { Signal::Success(Value { kind: Some(Kind::NumberValue(n)), - }) => n, + }) => number_to_f64(&n).unwrap_or_default(), other => panic!("Expected NumberValue, got {:?}", other), } } diff --git a/crates/core/src/runtime/functions/http.rs b/crates/core/src/runtime/functions/http.rs index 9fe305f..4de951e 100644 --- a/crates/core/src/runtime/functions/http.rs +++ b/crates/core/src/runtime/functions/http.rs @@ -4,6 +4,7 @@ use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; +use crate::value::value_from_i64; use tucana::shared::value::Kind; use tucana::shared::{Struct, Value}; @@ -116,20 +117,18 @@ fn create_response( args!(args => http_status_code: String, headers: Struct, payload: Value); let mut fields = std::collections::HashMap::new(); - let code = match http_status_code.as_str().parse::() { + let code = match http_status_code.as_str().parse::() { Ok(c) => c, Err(_) => { return Signal::Failure(RuntimeError::simple_str( "InvalidArgumentExeption", - "Expected http_status_code to be parsed to float", + "Expected http_status_code to be parsed to integer", )); } }; fields.insert( "status_code".to_string(), - Value { - kind: Some(Kind::NumberValue(code)), - }, + value_from_i64(code), ); fields.insert( diff --git a/crates/core/src/runtime/functions/number.rs b/crates/core/src/runtime/functions/number.rs index c60e023..cded942 100644 --- a/crates/core/src/runtime/functions/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -1,6 +1,6 @@ use std::f64; -use tucana::shared::{Value, value::Kind}; +use tucana::shared::{number_value, NumberValue, Value, value::Kind}; use crate::context::argument::Argument; use crate::context::context::Context; @@ -8,6 +8,16 @@ use crate::context::macros::{args, no_args}; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; +use crate::value::{number_to_f64, value_from_f64, value_from_i64}; + +fn num_f64(n: &NumberValue) -> Result { + number_to_f64(n).ok_or_else(|| { + Signal::Failure(RuntimeError::simple_str( + "InvalidArgumentRuntimeError", + "Expected number", + )) + }) +} pub fn collect_number_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ @@ -57,10 +67,24 @@ fn add( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs + rhs)), - }) + args!(args => lhs: NumberValue, rhs: NumberValue); + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + if let Some(sum) = a.checked_add(b) { + return Signal::Success(value_from_i64(sum)); + } + } + _ => {} + } + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs + rhs)) } fn multiply( @@ -68,10 +92,24 @@ fn multiply( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs * rhs)), - }) + args!(args => lhs: NumberValue, rhs: NumberValue); + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + if let Some(prod) = a.checked_mul(b) { + return Signal::Success(value_from_i64(prod)); + } + } + _ => {} + } + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs * rhs)) } fn substract( @@ -79,10 +117,24 @@ fn substract( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs - rhs)), - }) + args!(args => lhs: NumberValue, rhs: NumberValue); + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + if let Some(diff) = a.checked_sub(b) { + return Signal::Success(value_from_i64(diff)); + } + } + _ => {} + } + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs - rhs)) } fn divide( @@ -90,18 +142,34 @@ fn divide( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); + args!(args => lhs: NumberValue, rhs: NumberValue); + + let rhs_f = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; - if rhs == 0.0 { + if rhs_f == 0.0 { return Signal::Failure(RuntimeError::simple_str( "DivisionByZero", "You cannot divide by zero", )); } - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs / rhs)), - }) + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + if b != 0 && a % b == 0 { + return Signal::Success(value_from_i64(a / b)); + } + } + _ => {} + } + + let lhs_f = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs_f / rhs_f)) } fn modulo( @@ -109,18 +177,34 @@ fn modulo( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); + args!(args => lhs: NumberValue, rhs: NumberValue); + + let rhs_f = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; - if rhs == 0.0 { + if rhs_f == 0.0 { return Signal::Failure(RuntimeError::simple_str( "DivisionByZero", "You cannot divide by zero", )); } - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs % rhs)), - }) + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + if b != 0 { + return Signal::Success(value_from_i64(a % b)); + } + } + _ => {} + } + + let lhs_f = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs_f % rhs_f)) } fn abs( @@ -128,10 +212,20 @@ fn abs( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.abs())), - }) + args!(args => value: NumberValue); + match value.number { + Some(number_value::Number::Integer(i)) => { + if let Some(abs) = i.checked_abs() { + return Signal::Success(value_from_i64(abs)); + } + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.abs())) } fn is_positive( @@ -139,7 +233,11 @@ fn is_positive( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; Signal::Success(Value { kind: Some(Kind::BoolValue(!value.is_sign_negative())), }) @@ -150,7 +248,15 @@ fn is_greater( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); + args!(args => lhs: NumberValue, rhs: NumberValue); + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; Signal::Success(Value { kind: Some(Kind::BoolValue(lhs > rhs)), }) @@ -161,7 +267,15 @@ fn is_less( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); + args!(args => lhs: NumberValue, rhs: NumberValue); + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; Signal::Success(Value { kind: Some(Kind::BoolValue(lhs < rhs)), }) @@ -172,7 +286,11 @@ fn is_zero( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; Signal::Success(Value { kind: Some(Kind::BoolValue(value == 0.0)), }) @@ -183,10 +301,20 @@ fn square( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.powf(2.0))), - }) + args!(args => value: NumberValue); + match value.number { + Some(number_value::Number::Integer(i)) => { + if let Some(prod) = i.checked_mul(i) { + return Signal::Success(value_from_i64(prod)); + } + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.powf(2.0))) } fn exponential( @@ -194,10 +322,28 @@ fn exponential( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => base: f64, exponent: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(base.powf(exponent))), - }) + args!(args => base: NumberValue, exponent: NumberValue); + match (base.number, exponent.number) { + (Some(number_value::Number::Integer(b)), Some(number_value::Number::Integer(e))) + if e >= 0 => + { + if let Ok(exp) = u32::try_from(e) { + if let Some(pow) = b.checked_pow(exp) { + return Signal::Success(value_from_i64(pow)); + } + } + } + _ => {} + } + let base = match num_f64(&base) { + Ok(v) => v, + Err(e) => return e, + }; + let exponent = match num_f64(&exponent) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(base.powf(exponent))) } fn pi( @@ -206,9 +352,7 @@ fn pi( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { no_args!(args); - Signal::Success(Value { - kind: Some(Kind::NumberValue(f64::consts::PI)), - }) + Signal::Success(value_from_f64(f64::consts::PI)) } fn euler( @@ -217,9 +361,7 @@ fn euler( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { no_args!(args); - Signal::Success(Value { - kind: Some(Kind::NumberValue(f64::consts::E)), - }) + Signal::Success(value_from_f64(f64::consts::E)) } fn infinity( @@ -228,9 +370,7 @@ fn infinity( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { no_args!(args); - Signal::Success(Value { - kind: Some(Kind::NumberValue(f64::INFINITY)), - }) + Signal::Success(value_from_f64(f64::INFINITY)) } fn round_up( @@ -238,11 +378,23 @@ fn round_up( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64, decimal_places: f64); + args!(args => value: NumberValue, decimal_places: NumberValue); + let decimal_places = match num_f64(&decimal_places) { + Ok(v) => v, + Err(e) => return e, + }; + match value.number { + Some(number_value::Number::Integer(i)) if decimal_places <= 0.0 => { + return Signal::Success(value_from_i64(i)); + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; let factor = 10_f64.powi(decimal_places as i32); - Signal::Success(Value { - kind: Some(Kind::NumberValue((value * factor).ceil() / factor)), - }) + Signal::Success(value_from_f64((value * factor).ceil() / factor)) } fn round_down( @@ -250,11 +402,23 @@ fn round_down( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64, decimal_places: f64); + args!(args => value: NumberValue, decimal_places: NumberValue); + let decimal_places = match num_f64(&decimal_places) { + Ok(v) => v, + Err(e) => return e, + }; + match value.number { + Some(number_value::Number::Integer(i)) if decimal_places <= 0.0 => { + return Signal::Success(value_from_i64(i)); + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; let factor = 10_f64.powi(decimal_places as i32); - Signal::Success(Value { - kind: Some(Kind::NumberValue((value * factor).floor() / factor)), - }) + Signal::Success(value_from_f64((value * factor).floor() / factor)) } fn round( @@ -262,11 +426,23 @@ fn round( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64, decimal_places: f64); + args!(args => value: NumberValue, decimal_places: NumberValue); + let decimal_places = match num_f64(&decimal_places) { + Ok(v) => v, + Err(e) => return e, + }; + match value.number { + Some(number_value::Number::Integer(i)) if decimal_places <= 0.0 => { + return Signal::Success(value_from_i64(i)); + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; let factor = 10_f64.powi(decimal_places as i32); - Signal::Success(Value { - kind: Some(Kind::NumberValue((value * factor).round() / factor)), - }) + Signal::Success(value_from_f64((value * factor).round() / factor)) } fn square_root( @@ -274,10 +450,12 @@ fn square_root( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.sqrt())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.sqrt())) } fn root( @@ -285,10 +463,16 @@ fn root( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64, root: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.powf(root))), - }) + args!(args => value: NumberValue, root: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + let root = match num_f64(&root) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.powf(root))) } fn log( @@ -296,10 +480,16 @@ fn log( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64, base: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.log(base))), - }) + args!(args => value: NumberValue, base: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + let base = match num_f64(&base) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.log(base))) } fn ln( @@ -307,10 +497,12 @@ fn ln( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.ln())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.ln())) } fn from_text( @@ -320,10 +512,11 @@ fn from_text( ) -> Signal { args!(args => string_value: String); + if let Ok(v) = string_value.parse::() { + return Signal::Success(value_from_i64(v)); + } match string_value.parse::() { - Ok(v) => Signal::Success(Value { - kind: Some(Kind::NumberValue(v)), - }), + Ok(v) => Signal::Success(value_from_f64(v)), Err(_) => Signal::Failure(RuntimeError::simple( "InvalidArgumentRuntimeError", format!("Failed to parse string as number: {}", string_value), @@ -336,7 +529,11 @@ fn as_text( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; Signal::Success(Value { kind: Some(Kind::StringValue(value.to_string())), }) @@ -347,10 +544,22 @@ fn min( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs.min(rhs))), - }) + args!(args => lhs: NumberValue, rhs: NumberValue); + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + return Signal::Success(value_from_i64(a.min(b))); + } + _ => {} + } + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs.min(rhs))) } fn max( @@ -358,10 +567,22 @@ fn max( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(lhs.max(rhs))), - }) + args!(args => lhs: NumberValue, rhs: NumberValue); + match (lhs.number, rhs.number) { + (Some(number_value::Number::Integer(a)), Some(number_value::Number::Integer(b))) => { + return Signal::Success(value_from_i64(a.max(b))); + } + _ => {} + } + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(lhs.max(rhs))) } fn negate( @@ -369,10 +590,20 @@ fn negate( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(-value)), - }) + args!(args => value: NumberValue); + match value.number { + Some(number_value::Number::Integer(i)) => { + if let Some(neg) = i.checked_neg() { + return Signal::Success(value_from_i64(neg)); + } + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(-value)) } fn random( @@ -380,20 +611,27 @@ fn random( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => min: f64, max: f64); - - if min > max { + args!(args => min: NumberValue, max: NumberValue); + + let min_f = match num_f64(&min) { + Ok(v) => v, + Err(e) => return e, + }; + let max_f = match num_f64(&max) { + Ok(v) => v, + Err(e) => return e, + }; + + if min_f > max_f { return Signal::Failure(RuntimeError::simple_str( "InvalidRange", "First number can't be bigger then second when creating a range for std::math::random", )); } - let value = rand::random_range(min..=max); + let value = rand::random_range(min_f..=max_f); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value)), - }) + Signal::Success(value_from_f64(value)) } fn sin( @@ -401,10 +639,12 @@ fn sin( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.sin())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.sin())) } fn cos( @@ -412,10 +652,12 @@ fn cos( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.cos())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.cos())) } fn tan( @@ -423,10 +665,12 @@ fn tan( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.tan())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.tan())) } fn arcsin( @@ -434,10 +678,12 @@ fn arcsin( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.asin())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.asin())) } fn arccos( @@ -445,10 +691,12 @@ fn arccos( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.acos())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.acos())) } fn arctan( @@ -456,10 +704,12 @@ fn arctan( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.atan())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.atan())) } fn sinh( @@ -467,10 +717,12 @@ fn sinh( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.sinh())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.sinh())) } fn cosh( @@ -478,10 +730,12 @@ fn cosh( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.cosh())), - }) + args!(args => value: NumberValue); + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.cosh())) } fn clamp( @@ -489,10 +743,30 @@ fn clamp( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: f64, min: f64, max: f64); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.clamp(min, max))), - }) + args!(args => value: NumberValue, min: NumberValue, max: NumberValue); + match (value.number, min.number, max.number) { + ( + Some(number_value::Number::Integer(v)), + Some(number_value::Number::Integer(min)), + Some(number_value::Number::Integer(max)), + ) => { + return Signal::Success(value_from_i64(v.clamp(min, max))); + } + _ => {} + } + let value = match num_f64(&value) { + Ok(v) => v, + Err(e) => return e, + }; + let min = match num_f64(&min) { + Ok(v) => v, + Err(e) => return e, + }; + let max = match num_f64(&max) { + Ok(v) => v, + Err(e) => return e, + }; + Signal::Success(value_from_f64(value.clamp(min, max))) } fn is_equal( @@ -500,7 +774,15 @@ fn is_equal( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => lhs: f64, rhs: f64); + args!(args => lhs: NumberValue, rhs: NumberValue); + let lhs = match num_f64(&lhs) { + Ok(v) => v, + Err(e) => return e, + }; + let rhs = match num_f64(&rhs) { + Ok(v) => v, + Err(e) => return e, + }; Signal::Success(Value { kind: Some(Kind::BoolValue(lhs == rhs)), }) @@ -510,13 +792,12 @@ mod tests { use super::*; use crate::context::argument::Argument; use crate::context::context::Context; + use crate::value::{number_to_f64, value_from_f64}; use tucana::shared::{Value, value::Kind}; // ---- helpers: Arguments ---- fn a_num(n: f64) -> Argument { - Argument::Eval(Value { - kind: Some(Kind::NumberValue(n)), - }) + Argument::Eval(value_from_f64(n)) } fn a_str(s: &str) -> Argument { Argument::Eval(Value { @@ -529,7 +810,7 @@ mod tests { match sig { Signal::Success(Value { kind: Some(Kind::NumberValue(n)), - }) => n, + }) => number_to_f64(&n).unwrap_or_default(), other => panic!("Expected NumberValue, got {:?}", other), } } diff --git a/crates/core/src/runtime/functions/object.rs b/crates/core/src/runtime/functions/object.rs index 6f8c228..f128312 100644 --- a/crates/core/src/runtime/functions/object.rs +++ b/crates/core/src/runtime/functions/object.rs @@ -5,6 +5,7 @@ use crate::context::context::Context; use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; +use crate::value::value_from_i64; pub fn collect_object_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { vec![ @@ -37,9 +38,7 @@ fn size( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { args!(args => object: Struct); - Signal::Success(Value { - kind: Some(Kind::NumberValue(object.fields.len() as f64)), - }) + Signal::Success(value_from_i64(object.fields.len() as i64)) } fn keys( @@ -80,6 +79,7 @@ mod tests { use super::*; use crate::context::argument::Argument; use crate::context::context::Context; + use crate::value::{number_to_f64, value_from_f64}; use std::collections::HashMap; use tucana::shared::{Struct as TcStruct, Value, value::Kind}; @@ -90,9 +90,7 @@ mod tests { } } fn v_number(n: f64) -> Value { - Value { - kind: Some(Kind::NumberValue(n)), - } + value_from_f64(n) } fn v_bool(b: bool) -> Value { Value { @@ -206,7 +204,7 @@ mod tests { _ => panic!("Expected Success"), }; match v.kind { - Some(Kind::NumberValue(n)) => assert_eq!(n, 3.0), + Some(Kind::NumberValue(n)) => assert_eq!(number_to_f64(&n).unwrap_or_default(), 3.0), _ => panic!("Expected NumberValue"), } @@ -219,7 +217,7 @@ mod tests { _ => panic!("Expected Success"), }; match v.kind { - Some(Kind::NumberValue(n)) => assert_eq!(n, 0.0), + Some(Kind::NumberValue(n)) => assert_eq!(number_to_f64(&n).unwrap_or_default(), 0.0), _ => panic!("Expected NumberValue"), } } @@ -322,7 +320,7 @@ mod tests { Some(Value { kind: Some(Kind::NumberValue(n)), .. - }) => assert_eq!(*n, 31.0), + }) => assert_eq!(number_to_f64(n).unwrap_or_default(), 31.0), _ => panic!("Expected age to be a number"), } } diff --git a/crates/core/src/runtime/functions/text.rs b/crates/core/src/runtime/functions/text.rs index 37b70f2..8176fb9 100644 --- a/crates/core/src/runtime/functions/text.rs +++ b/crates/core/src/runtime/functions/text.rs @@ -4,6 +4,7 @@ use crate::context::macros::args; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; +use crate::value::{number_to_f64, number_to_i64_lossy, value_from_i64}; use base64::Engine; use tucana::shared::{ListValue, Value, value::Kind}; @@ -62,9 +63,7 @@ fn as_bytes( let bytes: Vec = value .as_bytes() .iter() - .map(|b| Value { - kind: Some(Kind::NumberValue(*b as f64)), - }) + .map(|b| value_from_i64(*b as i64)) .collect(); Signal::Success(Value { @@ -78,9 +77,7 @@ fn byte_size( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { args!(args => value: String); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.len() as f64)), - }) + Signal::Success(value_from_i64(value.len() as i64)) } fn capitalize( @@ -192,9 +189,13 @@ fn at( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: String, index: f64); + args!(args => value: String, index: tucana::shared::NumberValue); + let index = match number_to_i64_lossy(&index) { + Some(v) => v, + None => return arg_err("Expected a number index"), + }; - if index < 0.0 { + if index < 0 { return arg_err("Expected a non-negative index"); } @@ -241,9 +242,13 @@ fn insert( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: String, position: f64, text: String); + args!(args => value: String, position: tucana::shared::NumberValue, text: String); + let position = match number_to_i64_lossy(&position) { + Some(v) => v, + None => return arg_err("Expected a number position"), + }; - if position < 0.0 { + if position < 0 { return arg_err("Expected a non-negative position"); } @@ -270,9 +275,7 @@ fn length( _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { args!(args => value: String); - Signal::Success(Value { - kind: Some(Kind::NumberValue(value.chars().count() as f64)), - }) + Signal::Success(value_from_i64(value.chars().count() as i64)) } fn remove( @@ -280,9 +283,17 @@ fn remove( _ctx: &mut Context, _run: &mut dyn FnMut(i64, &mut Context) -> Signal, ) -> Signal { - args!(args => value: String, from: f64, to: f64); + args!(args => value: String, from: tucana::shared::NumberValue, to: tucana::shared::NumberValue); + let from = match number_to_i64_lossy(&from) { + Some(v) => v, + None => return arg_err("Expected number 'from'"), + }; + let to = match number_to_i64_lossy(&to) { + Some(v) => v, + None => return arg_err("Expected number 'to'"), + }; - if from < 0.0 || to < 0.0 { + if from < 0 || to < 0 { return arg_err("Expected non-negative indices"); } @@ -408,12 +419,8 @@ fn index_of( args!(args => value: String, sub: String); match value.find(&sub) { - Some(idx) => Signal::Success(Value { - kind: Some(Kind::NumberValue(idx as f64)), - }), - None => Signal::Success(Value { - kind: Some(Kind::NumberValue(-1.0)), - }), + Some(idx) => Signal::Success(value_from_i64(idx as i64)), + None => Signal::Success(value_from_i64(-1)), } } @@ -491,9 +498,7 @@ fn to_ascii( let ascii = value .bytes() - .map(|b| Value { - kind: Some(Kind::NumberValue(b as f64)), - }) + .map(|b| value_from_i64(b as i64)) .collect::>(); Signal::Success(Value { @@ -515,7 +520,10 @@ fn from_ascii( .map(|v| match v { Value { kind: Some(Kind::NumberValue(n)), - } if *n >= 0.0 && *n <= 127.0 => Some(*n as u8 as char), + } => match number_to_f64(n) { + Some(n) if n >= 0.0 && n <= 127.0 => Some(n as u8 as char), + _ => None, + }, _ => None, }) .collect::>(); @@ -596,6 +604,7 @@ fn is_equal( mod tests { use super::*; use crate::context::context::Context; + use crate::value::{number_to_f64, value_from_f64, value_from_i64}; use tucana::shared::{ListValue, Value, value::Kind}; // ---------- helpers: build Arguments ---------- @@ -605,9 +614,7 @@ mod tests { }) } fn a_num(n: f64) -> Argument { - Argument::Eval(Value { - kind: Some(Kind::NumberValue(n)), - }) + Argument::Eval(value_from_f64(n)) } fn a_list(vals: Vec) -> Argument { Argument::Eval(Value { @@ -621,10 +628,8 @@ mod tests { kind: Some(Kind::StringValue(s.to_string())), } } - fn v_num(n: f64) -> Value { - Value { - kind: Some(Kind::NumberValue(n)), - } + fn v_num(n: i64) -> Value { + value_from_i64(n) } // ---------- helpers: extract from Signal ---------- @@ -632,7 +637,7 @@ mod tests { match sig { Signal::Success(Value { kind: Some(Kind::NumberValue(n)), - }) => n, + }) => number_to_f64(&n).unwrap_or_default(), other => panic!("Expected NumberValue, got {:?}", other), } } @@ -678,7 +683,7 @@ mod tests { // "hello" -> 5 bytes let bytes = expect_list(as_bytes(&[a_str("hello")], &mut ctx, &mut run)); assert_eq!(bytes.len(), 5); - assert_eq!(bytes[0], v_num(104.0)); // 'h' + assert_eq!(bytes[0], v_num(104)); // 'h' let mut run = dummy_run; assert_eq!( @@ -926,10 +931,10 @@ mod tests { let mut run = dummy_run; let ascii_vals = expect_list(to_ascii(&[a_str("AB")], &mut ctx, &mut run)); - assert_eq!(ascii_vals, vec![v_num(65.0), v_num(66.0)]); + assert_eq!(ascii_vals, vec![v_num(65), v_num(66)]); let mut run = dummy_run; - let list_arg = a_list(vec![v_num(65.0), v_num(66.0), v_num(67.0)]); + let list_arg = a_list(vec![v_num(65), v_num(66), v_num(67)]); assert_eq!( expect_str(from_ascii(&[list_arg], &mut ctx, &mut run)), "ABC" @@ -937,7 +942,7 @@ mod tests { // invalid element let mut run = dummy_run; - let list_arg = a_list(vec![v_num(65.0), v_num(128.0)]); + let list_arg = a_list(vec![v_num(65), v_num(128)]); match from_ascii(&[list_arg], &mut ctx, &mut run) { Signal::Failure(_) => {} s => panic!("Expected Failure for invalid ASCII, got {:?}", s), diff --git a/crates/core/src/value.rs b/crates/core/src/value.rs new file mode 100644 index 0000000..033896d --- /dev/null +++ b/crates/core/src/value.rs @@ -0,0 +1,55 @@ +use tucana::shared::{number_value, value::Kind, NumberValue, Value}; + +pub fn number_value_from_f64(n: f64) -> NumberValue { + NumberValue { + number: Some(number_value::Number::Float(n)), + } +} + +pub fn number_value_from_i64(n: i64) -> NumberValue { + NumberValue { + number: Some(number_value::Number::Integer(n)), + } +} + +pub fn value_from_f64(n: f64) -> Value { + Value { + kind: Some(Kind::NumberValue(number_value_from_f64(n))), + } +} + +pub fn value_from_i64(n: i64) -> Value { + Value { + kind: Some(Kind::NumberValue(number_value_from_i64(n))), + } +} + +pub fn number_to_f64(n: &NumberValue) -> Option { + match n.number { + Some(number_value::Number::Integer(i)) => Some(i as f64), + Some(number_value::Number::Float(f)) => Some(f), + None => None, + } +} + +pub fn number_to_i64_lossy(n: &NumberValue) -> Option { + match n.number { + Some(number_value::Number::Integer(i)) => Some(i), + Some(number_value::Number::Float(f)) => { + if f.is_finite() { + Some(f as i64) + } else { + None + } + } + None => None, + } +} + +pub fn number_to_string(n: &NumberValue) -> String { + match n.number { + Some(number_value::Number::Integer(i)) => i.to_string(), + Some(number_value::Number::Float(f)) => f.to_string(), + None => "null".to_string(), + } +} From 240cbdc124a3d23e6c74056d13965b8d88fd508f Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 21 Mar 2026 12:15:52 +0100 Subject: [PATCH 2/3] feat: updated flow definitions to latest tucana definitions --- crates/tests/flows/03_for_each.json | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/tests/flows/03_for_each.json b/crates/tests/flows/03_for_each.json index 4f8740d..231d005 100644 --- a/crates/tests/flows/03_for_each.json +++ b/crates/tests/flows/03_for_each.json @@ -22,22 +22,34 @@ "listValue": { "values": [ { - "numberValue": 1.0 + "numberValue": { + "integer": "1" + } }, { - "numberValue": 2.0 + "numberValue": { + "integer": "2" + } }, { - "numberValue": 3.0 + "numberValue": { + "integer": "3" + } }, { - "numberValue": 4.0 + "numberValue": { + "integer": "4" + } }, { - "numberValue": 5.0 + "numberValue": { + "integer": "5" + } }, { - "numberValue": 6.0 + "numberValue": { + "integer": "6" + } } ] } @@ -74,7 +86,9 @@ "runtimeParameterId": "second", "value": { "literalValue": { - "numberValue": 2.0 + "numberValue": { + "integer": "2" + } } } } From 42a6e4b8407b90d483e2bb92313399b37ec59cc3 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 21 Mar 2026 12:34:58 +0100 Subject: [PATCH 3/3] feat: added implementations for has_digits and remove_digits --- crates/core/src/runtime/functions/number.rs | 92 ++++++++++++++++++++- 1 file changed, 88 insertions(+), 4 deletions(-) diff --git a/crates/core/src/runtime/functions/number.rs b/crates/core/src/runtime/functions/number.rs index cded942..577915b 100644 --- a/crates/core/src/runtime/functions/number.rs +++ b/crates/core/src/runtime/functions/number.rs @@ -1,6 +1,7 @@ use std::f64; -use tucana::shared::{number_value, NumberValue, Value, value::Kind}; +use tucana::shared::helper::value::ToValue; +use tucana::shared::{NumberValue, Value, number_value, value::Kind}; use crate::context::argument::Argument; use crate::context::context::Context; @@ -8,7 +9,7 @@ use crate::context::macros::{args, no_args}; use crate::context::registry::{HandlerFn, HandlerFunctionEntry, IntoFunctionEntry}; use crate::context::signal::Signal; use crate::runtime::error::RuntimeError; -use crate::value::{number_to_f64, value_from_f64, value_from_i64}; +use crate::value::{number_to_f64, number_to_i64_lossy, value_from_f64, value_from_i64}; fn num_f64(n: &NumberValue) -> Result { number_to_f64(n).ok_or_else(|| { @@ -59,9 +60,52 @@ pub fn collect_number_functions() -> Vec<(&'static str, HandlerFunctionEntry)> { ("std::number::cosh", HandlerFn::eager(cosh, 1)), ("std::number::clamp", HandlerFn::eager(clamp, 3)), ("std::number::is_equal", HandlerFn::eager(is_equal, 2)), + ("std::number::has_digits", HandlerFn::eager(has_digits, 2)), + ( + "std::number::remove_digits", + HandlerFn::eager(remove_digits, 2), + ), ] } +fn has_digits( + args: &[Argument], + _ctx: &mut Context, + _run: &mut dyn FnMut(i64, &mut Context) -> Signal, +) -> Signal { + args!(args => value: NumberValue); + + match value.number { + Some(number) => match number { + number_value::Number::Integer(_) => return Signal::Success(false.to_value()), + number_value::Number::Float(_) => return Signal::Success(true.to_value()), + }, + None => { + return Signal::Failure(RuntimeError::simple_str( + "InvlaidArgumentExeption", + "Had NumberValue but no inner number value (was null)", + )); + } + } +} + +fn remove_digits( + args: &[Argument], + _ctx: &mut Context, + _run: &mut dyn FnMut(i64, &mut Context) -> Signal, +) -> Signal { + args!(args => value: NumberValue); + match number_to_i64_lossy(&value) { + Some(number) => return Signal::Success(value_from_i64(number)), + None => { + return Signal::Failure(RuntimeError::simple_str( + "InvlaidArgumentExeption", + "Had NumberValue but no inner number value (was null)", + )); + } + } +} + fn add( args: &[Argument], _ctx: &mut Context, @@ -792,13 +836,16 @@ mod tests { use super::*; use crate::context::argument::Argument; use crate::context::context::Context; - use crate::value::{number_to_f64, value_from_f64}; - use tucana::shared::{Value, value::Kind}; + use crate::value::{number_to_f64, value_from_f64, value_from_i64}; + use tucana::shared::{number_value, Value, value::Kind}; // ---- helpers: Arguments ---- fn a_num(n: f64) -> Argument { Argument::Eval(value_from_f64(n)) } + fn a_int(n: i64) -> Argument { + Argument::Eval(value_from_i64(n)) + } fn a_str(s: &str) -> Argument { Argument::Eval(Value { kind: Some(Kind::StringValue(s.to_string())), @@ -822,6 +869,20 @@ mod tests { other => panic!("Expected BoolValue, got {:?}", other), } } + fn expect_int(sig: Signal) -> i64 { + match sig { + Signal::Success(Value { + kind: Some(Kind::NumberValue(n)), + }) => match n.number { + Some(number_value::Number::Integer(i)) => i, + Some(number_value::Number::Float(f)) => { + panic!("Expected Integer NumberValue, got Float({})", f) + } + None => panic!("Expected Integer NumberValue, got None"), + }, + other => panic!("Expected NumberValue, got {:?}", other), + } + } fn expect_str(sig: Signal) -> String { match sig { Signal::Success(Value { @@ -854,6 +915,29 @@ mod tests { ); } + #[test] + fn test_has_digits_and_remove_digits() { + let mut ctx = Context::default(); + + let mut run = dummy_run; + assert!(!expect_bool(has_digits(&[a_int(42)], &mut ctx, &mut run))); + + let mut run = dummy_run; + assert!(expect_bool(has_digits(&[a_num(42.5)], &mut ctx, &mut run))); + + let mut run = dummy_run; + assert_eq!( + expect_int(remove_digits(&[a_int(123)], &mut ctx, &mut run)), + 123 + ); + + let mut run = dummy_run; + assert_eq!( + expect_int(remove_digits(&[a_num(12.99)], &mut ctx, &mut run)), + 12 + ); + } + #[test] fn test_substract_and_divide() { let mut ctx = Context::default();