diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index f3fddee1..fd6636fc 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -101,8 +101,8 @@ likely not be implemented due to differences between piccolo and PUC-Lua. | Status | Function | Differences | Notes | | ------ | --------------------------------- | ----------- | ----- | -| ⚫️️ | `byte(s[, i, j])` | | | -| ⚫️️ | `char(args...)` | | | +| 🔵 | `byte(s[, i, j])` | | | +| 🔵 | `char(args...)` | | | | ⚫️️ | `dump(function[, strip])` | | | | ⚫️️ | `find(s, pattern[, init, plain])` | | | | ⚫️️ | `format(formatstring, args...)` | | | diff --git a/src/stdlib/string.rs b/src/stdlib/string.rs index 04deba71..556537c8 100644 --- a/src/stdlib/string.rs +++ b/src/stdlib/string.rs @@ -1,4 +1,4 @@ -use crate::{Callback, CallbackReturn, Context, String, Table}; +use crate::{Callback, CallbackReturn, Context, FromValue, String, Table, Value}; pub fn load_string<'gc>(ctx: Context<'gc>) { let string = Table::new(&ctx); @@ -16,39 +16,37 @@ pub fn load_string<'gc>(ctx: Context<'gc>) { string.set_field( ctx, - "sub", + "byte", Callback::from_fn(&ctx, |ctx, _, mut stack| { - fn operate_sub( - string: &[u8], - i: i64, - j: Option, - ) -> Result<&[u8], std::num::TryFromIntError> { - let i = match i { - i if i > 0 => i.saturating_sub(1).try_into()?, - 0 => 0, - i => string.len().saturating_sub(i.unsigned_abs().try_into()?), - }; - let j = if let Some(j) = j { - if j >= 0 { - j.try_into()? - } else { - let j: usize = j.unsigned_abs().try_into()?; - string.len().saturating_sub(j.saturating_sub(1)) - } - } else { - string.len() - } - .clamp(0, string.len()); + let (string, i, j) = stack.consume::<(String, Option, Option)>(ctx)?; + let i = i.unwrap_or(1); + let substr = sub(string.as_bytes(), i, j.or(Some(i)))?; + stack.extend(substr.iter().map(|b| Value::Integer(i64::from(*b)))); + Ok(CallbackReturn::Return) + }), + ); - Ok(if i >= j || i >= string.len() { - &[] - } else { - &string[i..j] - }) - } + string.set_field( + ctx, + "char", + Callback::from_fn(&ctx, |ctx, _, mut stack| { + let string = ctx.intern( + &stack + .into_iter() + .map(|c| u8::from_value(ctx, c)) + .collect::, _>>()?, + ); + stack.replace(ctx, string); + Ok(CallbackReturn::Return) + }), + ); + string.set_field( + ctx, + "sub", + Callback::from_fn(&ctx, |ctx, _, mut stack| { let (string, i, j) = stack.consume::<(String, i64, Option)>(ctx)?; - let substr = ctx.intern(operate_sub(string.as_bytes(), i, j)?); + let substr = ctx.intern(sub(string.as_bytes(), i, j)?); stack.replace(ctx, substr); Ok(CallbackReturn::Return) }), @@ -101,3 +99,28 @@ pub fn load_string<'gc>(ctx: Context<'gc>) { ctx.set_global("string", string); } + +fn sub(string: &[u8], i: i64, j: Option) -> Result<&[u8], std::num::TryFromIntError> { + let i = match i { + i if i > 0 => i.saturating_sub(1).try_into()?, + 0 => 0, + i => string.len().saturating_sub(i.unsigned_abs().try_into()?), + }; + let j = if let Some(j) = j { + if j >= 0 { + j.try_into()? + } else { + let j: usize = j.unsigned_abs().try_into()?; + string.len().saturating_sub(j.saturating_sub(1)) + } + } else { + string.len() + } + .clamp(0, string.len()); + + Ok(if i >= j || i >= string.len() { + &[] + } else { + &string[i..j] + }) +} diff --git a/tests/scripts/string.lua b/tests/scripts/string.lua index ed141f68..f3853919 100644 --- a/tests/scripts/string.lua +++ b/tests/scripts/string.lua @@ -96,3 +96,44 @@ do assert(string.upper(80) == "80") assert(string.upper(3.14) == "3.14") end + +do + assert(is_err(function() return string.byte(nil) end)) + assert(is_err(function() return string.byte(true) end)) + assert(is_err(function() return string.byte(false) end)) + assert(is_err(function() return string.byte({}) end)) + assert(is_err(function() return string.byte(is_err) end)) + assert(is_err(function() return string.byte(coroutine.create(test_coroutine_len)) end)) + assert(string.byte("") == nil) + assert(string.byte("abcd") == 97) + assert(string.byte("\xef") == 0xef) + assert(string.byte("\0") == 0) + assert(string.byte(1) == string.byte("1")) + assert(string.byte("abcd", 2) == string.byte("b")) + assert(string.byte("abcd", -1) == string.byte("d")) + + local b, c, d, e = string.byte("abcd", 2, -1) + assert(b == string.byte("b") and + c == string.byte("c") and + d == string.byte("d") and + e == nil) + b, c, d = string.byte("abcd", 2, 3) + assert(b == string.byte("b") and + c == string.byte("c") and + d == nil) + assert(string.byte("abcd", 2, -5) == nil) + assert(string.byte("abcd", 3, 1) == nil) +end + +do + assert(is_err(function() return string.char(nil) end)) + assert(is_err(function() return string.char(true) end)) + assert(is_err(function() return string.char(false) end)) + assert(is_err(function() return string.char({}) end)) + assert(is_err(function() return string.char(is_err) end)) + assert(is_err(function() return string.char(coroutine.create(test_coroutine_len)) end)) + assert(string.char() == "") + assert(string.char(0, "1", 97, 98, 255) == "\0\x01ab\xff") + assert(is_err(function() return string.char(256) end)) + assert(is_err(function() return string.char(-1) end)) +end