diff --git a/.claude/settings.local.json b/.claude/settings.local.json index bae85eef..caa96769 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -94,7 +94,10 @@ "mcp__ide__getDiagnostics", "Bash(..targetreleasewfl.exe nexus.wfl)", "Bash(gh pr view:*)", - "Bash(git merge:*)" + "Bash(git merge:*)", + "Bash(git push:*)", + "Bash(gh pr checkout:*)", + "Bash(git pull:*)" ], "deny": [], "ask": [] diff --git a/src/stdlib/crypto.rs b/src/stdlib/crypto.rs index ec9a63bb..63e056a7 100644 --- a/src/stdlib/crypto.rs +++ b/src/stdlib/crypto.rs @@ -421,13 +421,9 @@ pub fn native_wflhash256(args: Vec) -> Result { check_arg_count("wflhash256", &args, 1)?; let text = expect_text(&args[0])?; - let input = text.as_bytes(); - let params = WflHashParams::new(32); // 256 bits = 32 bytes - let hash_bytes = wflhash_core_text(input, ¶ms)?; // Validate UTF-8 for text - let hash_hex = bytes_to_hex(&hash_bytes); - - Ok(Value::Text(Rc::from(hash_hex))) + let hash = wflhash_core_text(text.as_bytes(), ¶ms)?; + Ok(Value::Text(Rc::from(bytes_to_hex(&hash)))) } /// WFLHASH-512 implementation with security fixes @@ -435,13 +431,9 @@ pub fn native_wflhash512(args: Vec) -> Result { check_arg_count("wflhash512", &args, 1)?; let text = expect_text(&args[0])?; - let input = text.as_bytes(); - let params = WflHashParams::new(64); // 512 bits = 64 bytes - let hash_bytes = wflhash_core_text(input, ¶ms)?; // Validate UTF-8 for text - let hash_hex = bytes_to_hex(&hash_bytes); - - Ok(Value::Text(Rc::from(hash_hex))) + let hash = wflhash_core_text(text.as_bytes(), ¶ms)?; + Ok(Value::Text(Rc::from(bytes_to_hex(&hash)))) } /// WFLHASH-256 with personalization/salt support @@ -449,16 +441,10 @@ pub fn native_wflhash256_with_salt(args: Vec) -> Result) -> Result { check_arg_count("wflmac256", &args, 2)?; let text = expect_text(&args[0])?; - let input = text.as_bytes(); - - let key_text = expect_text(&args[1])?; - let key = key_text.as_bytes(); - - // Use proper key derivation with error handling - let params = WflHashParams::new_with_key(32, key)?; - let hash_bytes = wflhash_core_text(input, ¶ms)?; - let hash_hex = bytes_to_hex(&hash_bytes); - - Ok(Value::Text(Rc::from(hash_hex))) + let key = expect_text(&args[1])?; + let params = WflHashParams::new_with_key(32, key.as_bytes())?; + let hash = wflhash_core_text(text.as_bytes(), ¶ms)?; + Ok(Value::Text(Rc::from(bytes_to_hex(&hash)))) } /// WFLHASH-256 for binary data (no UTF-8 validation) pub fn native_wflhash256_binary(data: &[u8]) -> Result { let params = WflHashParams::new(32); // 256 bits = 32 bytes - let hash_bytes = wflhash_core(data, ¶ms)?; - Ok(bytes_to_hex(&hash_bytes)) + let hash = wflhash_core(data, ¶ms)?; + Ok(bytes_to_hex(&hash)) } /// Constant-time MAC verification using subtle crate diff --git a/src/stdlib/helpers.rs b/src/stdlib/helpers.rs index f6a942b7..8258f1a1 100644 --- a/src/stdlib/helpers.rs +++ b/src/stdlib/helpers.rs @@ -3,7 +3,36 @@ use crate::interpreter::value::Value; use std::cell::RefCell; use std::rc::Rc; -/// Checks if the number of arguments matches the expected count. +/// Validates that a native function receives exactly the expected number of arguments. +/// +/// This is the most common argument validation helper, used when a function requires +/// a specific number of arguments (not a range or minimum). The error message +/// automatically handles singular/plural grammar for better user experience. +/// +/// # Arguments +/// +/// * `func_name` - The name of the function being validated (used in error messages) +/// * `args` - The slice of argument values to check +/// * `expected` - The exact number of arguments required +/// +/// # Returns +/// +/// Returns `Ok(())` if the argument count matches, allowing the function to proceed. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the argument count doesn't match the expected value. +/// The error message format is: "{func_name} expects {expected} argument(s), got {actual}" +/// with proper singular/plural handling. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_add(args: Vec) -> Result { +/// check_arg_count("add", &args, 2)?; // Requires exactly 2 arguments +/// // ... function implementation +/// } +/// ``` pub fn check_arg_count( func_name: &str, args: &[Value], @@ -25,7 +54,36 @@ pub fn check_arg_count( Ok(()) } -/// Checks if the number of arguments is at least min_count. +/// Validates that a native function receives at least the minimum number of arguments. +/// +/// Use this helper for variadic functions that accept a minimum number of required +/// arguments plus optional additional arguments. This is common for functions like +/// print, format, or concatenation operations that can handle variable inputs. +/// +/// # Arguments +/// +/// * `func_name` - The name of the function being validated (used in error messages) +/// * `args` - The slice of argument values to check +/// * `min_count` - The minimum number of arguments required +/// +/// # Returns +/// +/// Returns `Ok(())` if the argument count is at least `min_count`. +/// +/// # Errors +/// +/// Returns `RuntimeError` if fewer than `min_count` arguments are provided. +/// The error message format is: "{func_name} expects at least {min_count} argument(s), got {actual}" +/// with proper singular/plural handling. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_print(args: Vec) -> Result { +/// check_min_arg_count("print", &args, 1)?; // Requires at least 1 argument +/// // ... can process args.len() arguments +/// } +/// ``` pub fn check_min_arg_count( func_name: &str, args: &[Value], @@ -47,7 +105,36 @@ pub fn check_min_arg_count( Ok(()) } -/// Checks if the number of arguments is within the range [min, max]. +/// Validates that a native function receives an argument count within a specified range. +/// +/// Use this helper for functions that accept a flexible number of arguments within +/// bounds, such as functions with multiple optional parameters. The range is inclusive +/// on both ends: [min, max]. +/// +/// # Arguments +/// +/// * `func_name` - The name of the function being validated (used in error messages) +/// * `args` - The slice of argument values to check +/// * `min` - The minimum number of arguments allowed (inclusive) +/// * `max` - The maximum number of arguments allowed (inclusive) +/// +/// # Returns +/// +/// Returns `Ok(())` if the argument count is within [min, max] (inclusive). +/// +/// # Errors +/// +/// Returns `RuntimeError` if the argument count is outside the specified range. +/// The error message format is: "{func_name} expects between {min} and {max} arguments, got {actual}". +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_substring(args: Vec) -> Result { +/// check_arg_range("substring", &args, 2, 3)?; // Requires 2 or 3 arguments +/// // ... handle optional third argument +/// } +/// ``` pub fn check_arg_range( func_name: &str, args: &[Value], @@ -70,7 +157,33 @@ pub fn check_arg_range( Ok(()) } -/// Expects a number value and returns it as f64. +/// Extracts a number value from a WFL Value, returning it as a primitive f64. +/// +/// This is the most common type extractor for numeric operations. Returns a copy +/// of the f64 value rather than a reference since f64 implements Copy. +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns the f64 number if the value is a Number variant. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a Number, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_abs(args: Vec) -> Result { +/// check_arg_count("abs", &args, 1)?; +/// let num = expect_number(&args[0])?; +/// Ok(Value::Number(num.abs())) +/// } +/// ``` pub fn expect_number(value: &Value) -> Result { match value { Value::Number(n) => Ok(*n), @@ -82,7 +195,34 @@ pub fn expect_number(value: &Value) -> Result { } } -/// Expects a text value and returns it as Rc. +/// Extracts a text value from a WFL Value, returning it as a reference-counted string. +/// +/// Returns an `Rc` to enable efficient memory sharing without copying the string +/// data. This is the standard way to extract text values in the WFL runtime. +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns an `Rc` clone (incrementing the reference count) if the value is a Text variant. +/// The underlying string data is not copied, only the reference count is incremented. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a Text, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_uppercase(args: Vec) -> Result { +/// check_arg_count("uppercase", &args, 1)?; +/// let text = expect_text(&args[0])?; +/// Ok(Value::Text(Rc::from(text.to_uppercase()))) +/// } +/// ``` pub fn expect_text(value: &Value) -> Result, RuntimeError> { match value { Value::Text(s) => Ok(Rc::clone(s)), @@ -94,7 +234,36 @@ pub fn expect_text(value: &Value) -> Result, RuntimeError> { } } -/// Expects a list value and returns it as Rc>>. +/// Extracts a list value from a WFL Value, returning it as a reference-counted mutable vector. +/// +/// Returns an `Rc>>` to enable efficient memory sharing with interior +/// mutability. The RefCell allows mutation of the list contents even through shared references, +/// which is essential for list operations like push, pop, and element modification. +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns an `Rc>>` clone (incrementing the reference count) if the value +/// is a List variant. Multiple references to the same list share the underlying data. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a List, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_push(args: Vec) -> Result { +/// check_arg_count("push", &args, 2)?; +/// let list = expect_list(&args[0])?; +/// list.borrow_mut().push(args[1].clone()); +/// Ok(Value::Nothing) +/// } +/// ``` pub fn expect_list(value: &Value) -> Result>>, RuntimeError> { match value { Value::List(list) => Ok(Rc::clone(list)), @@ -106,7 +275,33 @@ pub fn expect_list(value: &Value) -> Result>>, RuntimeErro } } -/// Expects a boolean value and returns it. +/// Extracts a boolean value from a WFL Value, returning it as a primitive bool. +/// +/// Returns a copy of the bool value rather than a reference since bool implements Copy. +/// This is commonly used in conditional logic and boolean operations. +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns the bool if the value is a Bool variant. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a Bool, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_not(args: Vec) -> Result { +/// check_arg_count("not", &args, 1)?; +/// let b = expect_bool(&args[0])?; +/// Ok(Value::Bool(!b)) +/// } +/// ``` pub fn expect_bool(value: &Value) -> Result { match value { Value::Bool(b) => Ok(*b), @@ -118,7 +313,34 @@ pub fn expect_bool(value: &Value) -> Result { } } -/// Expects a Date value. +/// Extracts a Date value from a WFL Value, returning it as a reference-counted NaiveDate. +/// +/// Returns an `Rc` to enable efficient memory sharing of date values. +/// NaiveDate represents a date without timezone information (year, month, day only). +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns an `Rc` clone (incrementing the reference count) if the value +/// is a Date variant. The underlying date data is shared, not copied. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a Date, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_date_year(args: Vec) -> Result { +/// check_arg_count("date_year", &args, 1)?; +/// let date = expect_date(&args[0])?; +/// Ok(Value::Number(date.year() as f64)) +/// } +/// ``` pub fn expect_date(value: &Value) -> Result, RuntimeError> { match value { Value::Date(d) => Ok(Rc::clone(d)), @@ -130,7 +352,34 @@ pub fn expect_date(value: &Value) -> Result, RuntimeError> } } -/// Expects a Time value. +/// Extracts a Time value from a WFL Value, returning it as a reference-counted NaiveTime. +/// +/// Returns an `Rc` to enable efficient memory sharing of time values. +/// NaiveTime represents a time of day without timezone information (hour, minute, second, nanosecond). +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns an `Rc` clone (incrementing the reference count) if the value +/// is a Time variant. The underlying time data is shared, not copied. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a Time, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_time_hour(args: Vec) -> Result { +/// check_arg_count("time_hour", &args, 1)?; +/// let time = expect_time(&args[0])?; +/// Ok(Value::Number(time.hour() as f64)) +/// } +/// ``` pub fn expect_time(value: &Value) -> Result, RuntimeError> { match value { Value::Time(t) => Ok(Rc::clone(t)), @@ -142,7 +391,38 @@ pub fn expect_time(value: &Value) -> Result, RuntimeError> } } -/// Expects a DateTime value. +/// Extracts a DateTime value from a WFL Value, returning it as a reference-counted NaiveDateTime. +/// +/// Returns an `Rc` to enable efficient memory sharing of datetime values. +/// NaiveDateTime represents a date and time without timezone information, combining both +/// date (year, month, day) and time (hour, minute, second, nanosecond) components. +/// +/// # Arguments +/// +/// * `value` - The WFL Value to extract from +/// +/// # Returns +/// +/// Returns an `Rc` clone (incrementing the reference count) if the value +/// is a DateTime variant. The underlying datetime data is shared, not copied. +/// +/// # Errors +/// +/// Returns `RuntimeError` if the value is not a DateTime, with an error message +/// indicating the expected type and the actual type received. +/// +/// # Examples +/// +/// ```ignore +/// pub fn native_datetime_add_days(args: Vec) -> Result { +/// check_arg_count("datetime_add_days", &args, 2)?; +/// let dt = expect_datetime(&args[0])?; +/// let days = expect_number(&args[1])? as i64; +/// let new_dt = dt.checked_add_signed(Duration::days(days)) +/// .ok_or_else(|| RuntimeError::new("Date overflow".to_string(), 0, 0))?; +/// Ok(Value::DateTime(Rc::new(new_dt))) +/// } +/// ``` pub fn expect_datetime(value: &Value) -> Result, RuntimeError> { match value { Value::DateTime(dt) => Ok(Rc::clone(dt)), diff --git a/tests/time_stdlib_test.rs b/tests/time_stdlib_test.rs new file mode 100644 index 00000000..29e9846a --- /dev/null +++ b/tests/time_stdlib_test.rs @@ -0,0 +1,90 @@ +use wfl::interpreter::value::Value; +use wfl::stdlib::time::native_create_time; + +#[test] +fn test_native_create_time_with_three_args() { + // Verify create_time with 3 args (hour, minute, second) works correctly + let args = vec![ + Value::Number(10.0), + Value::Number(30.0), + Value::Number(45.0), + ]; + let result = native_create_time(args); + assert!( + result.is_ok(), + "create_time should succeed with valid hour, minute, and second" + ); + + // Verify the returned value is a Time variant + let time_value = result.unwrap(); + assert!( + matches!(time_value, Value::Time(_)), + "create_time should return a Time value" + ); +} + +#[test] +fn test_native_create_time_with_two_args() { + // Verify create_time with 2 args (hour, minute) works correctly (seconds defaults to 0) + let args = vec![Value::Number(10.0), Value::Number(30.0)]; + let result = native_create_time(args); + assert!( + result.is_ok(), + "create_time should succeed with valid hour and minute (seconds defaults to 0)" + ); + + // Verify the returned value is a Time variant + let time_value = result.unwrap(); + assert!( + matches!(time_value, Value::Time(_)), + "create_time should return a Time value" + ); +} + +#[test] +fn test_native_create_time_wrong_arg_count() { + // Verify create_time with wrong number of args returns error + let result = native_create_time(vec![]); + assert!(result.is_err(), "create_time should fail with 0 arguments"); + + let result = native_create_time(vec![Value::Number(10.0)]); + assert!(result.is_err(), "create_time should fail with 1 argument"); + + let result = native_create_time(vec![ + Value::Number(10.0), + Value::Number(30.0), + Value::Number(45.0), + Value::Number(0.0), + ]); + assert!(result.is_err(), "create_time should fail with 4 arguments"); +} + +#[test] +fn test_native_create_time_invalid_values() { + // Verify create_time with invalid hour + let args = vec![ + Value::Number(25.0), // Invalid hour + Value::Number(30.0), + Value::Number(45.0), + ]; + let result = native_create_time(args); + assert!(result.is_err(), "create_time should fail with hour > 23"); + + // Verify create_time with invalid minute + let args = vec![ + Value::Number(10.0), + Value::Number(60.0), // Invalid minute + Value::Number(45.0), + ]; + let result = native_create_time(args); + assert!(result.is_err(), "create_time should fail with minute >= 60"); + + // Verify create_time with invalid second + let args = vec![ + Value::Number(10.0), + Value::Number(30.0), + Value::Number(60.0), // Invalid second + ]; + let result = native_create_time(args); + assert!(result.is_err(), "create_time should fail with second >= 60"); +}