diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index ec684538..7bc7d3aa 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -115,7 +115,7 @@ likely not be implemented due to differences between piccolo and PUC-Lua. | ⚫️️ | `packsize(fmt)` | | | | ⚫️️ | `rep(s, n[, sep])` | | | | ⚫️️ | `reverse(s)` | | | -| ⚫️️ | `sub(s, i[, j])` | | | +| 🔵 | `sub(s, i[, j])` | | | | ⚫️️ | `unpack(fmt, s[, pos])` | | | | ⚫️️ | `upper(s)` | | | diff --git a/src/stdlib/string.rs b/src/stdlib/string.rs index 58b7e21e..0c2b8d53 100644 --- a/src/stdlib/string.rs +++ b/src/stdlib/string.rs @@ -24,5 +24,66 @@ pub fn load_string<'gc>(ctx: Context<'gc>) { ) .unwrap(); + string + .set( + ctx, + "sub", + Callback::from_fn(&ctx, |ctx, _, mut stack| { + fn operate_sub<'a>( + string: &'a [u8], + i: i64, + j: Option, + ) -> Result<&'a [u8], std::num::TryFromIntError> { + let i = if i > 0 { + i.saturating_sub(1).try_into()? + } else if i == 0 { + 0 + } else { + 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()); + + return Ok(if i >= j || i >= string.len() { + &[] + } else { + &string[i..j] + }); + } + + let (string, i, j) = stack.consume::<(Value, i64, Option)>(ctx)?; + let string = match string { + Value::Integer(int) => { + ctx.intern(operate_sub(int.to_string().as_bytes(), i, j)?) + } + Value::Number(num) => { + ctx.intern(operate_sub(num.to_string().as_bytes(), i, j)?) + } + Value::String(string) => ctx.intern(operate_sub(string.as_bytes(), i, j)?), + v => { + return Err(format!( + "Bad argument to sub: expected string, got {}", + v.type_name() + ) + .into_value(ctx) + .into()) + } + }; + + stack.replace(ctx, string); + Ok(CallbackReturn::Return) + }), + ) + .unwrap(); + ctx.set_global("string", string).unwrap(); } diff --git a/tests/scripts/string.lua b/tests/scripts/string.lua index 277b82b4..450c6850 100644 --- a/tests/scripts/string.lua +++ b/tests/scripts/string.lua @@ -35,6 +35,25 @@ function test_len() string.len(-2147483648) == 11 end +do + assert(is_err(function() return string.sub(nil) end) and + is_err(function() return string.sub(true, 1) end) and + is_err(function() return string.sub(false) end) and + is_err(function() return string.sub({}) end) and + is_err(function() return string.sub(is_err) end) and + is_err(function() return string.sub(coroutine.create(test_coroutine_len)) end) and + string.sub(48, 1) == "48" and + string.sub(48, 2) == "8" and + string.sub(48, -1) == "8" and + string.sub("hilo", -1, -4) == "" and + string.sub("hilo", -4, 4) == "hilo" and + string.sub("hilo", -4, -3) == "hi" and + string.sub("hilo", -4, -6) == "" and + string.sub("hilo", 0, -4) == "h" and + string.sub(3.4, 1, 2) == "3." + ) +end + assert( test_concat() and test_len()