piccolo 0.3.3

Stackless Lua VM implemented in pure Rust
Documentation
use crate::{Callback, CallbackReturn, Context, IntoValue, String, Table, TypeError, Value};

pub fn load_string<'gc>(ctx: Context<'gc>) {
    let string = Table::new(&ctx);

    string
        .set(
            ctx,
            "len",
            Callback::from_fn(&ctx, |ctx, _, mut stack| {
                let v: Option<Value> = stack.consume(ctx)?;
                if let Some(len) = v.and_then(|v| match v {
                    Value::Integer(i) => Some(i.to_string().as_bytes().len().try_into().unwrap()),
                    Value::Number(n) => Some(n.to_string().as_bytes().len().try_into().unwrap()),
                    Value::String(s) => Some(s.len()),
                    _ => None,
                }) {
                    stack.replace(ctx, len);
                    Ok(CallbackReturn::Return)
                } else {
                    Err("Bad argument to len".into_value(ctx).into())
                }
            }),
        )
        .unwrap();

    string
        .set(
            ctx,
            "sub",
            Callback::from_fn(&ctx, |ctx, _, mut stack| {
                fn operate_sub(
                    string: &[u8],
                    i: i64,
                    j: Option<i64>,
                ) -> 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]
                    })
                }

                let (string, i, j) = stack.consume::<(Value, i64, Option<i64>)>(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();

    string
        .set(
            ctx,
            "lower",
            Callback::from_fn(&ctx, |ctx, _, mut stack| {
                let s = match stack.consume::<Value>(ctx)? {
                    Value::String(s) => s,
                    Value::Integer(i) => String::from_slice(&ctx, i.to_string()),
                    Value::Number(f) => String::from_slice(&ctx, f.to_string()),
                    v => {
                        return Err(TypeError {
                            expected: "string, integer or number",
                            found: v.type_name(),
                        }
                        .into())
                    }
                };
                let lowered = ctx.intern(
                    &s.as_bytes()
                        .iter()
                        .map(u8::to_ascii_lowercase)
                        .collect::<Vec<_>>(),
                );
                stack.replace(ctx, lowered);
                Ok(CallbackReturn::Return)
            }),
        )
        .unwrap();

    string
        .set(
            ctx,
            "reverse",
            Callback::from_fn(&ctx, |ctx, _, mut stack| {
                let s = match stack.consume::<Value>(ctx)? {
                    Value::String(s) => s,
                    Value::Integer(i) => String::from_slice(&ctx, i.to_string()),
                    Value::Number(f) => String::from_slice(&ctx, f.to_string()),
                    v => {
                        return Err(TypeError {
                            expected: "string, integer or number",
                            found: v.type_name(),
                        }
                        .into())
                    }
                };
                stack.replace(
                    ctx,
                    ctx.intern(&s.as_bytes().iter().copied().rev().collect::<Vec<_>>()),
                );
                Ok(CallbackReturn::Return)
            }),
        )
        .unwrap();

    string
        .set(
            ctx,
            "upper",
            Callback::from_fn(&ctx, |ctx, _, mut stack| {
                let s = match stack.consume::<Value>(ctx)? {
                    Value::String(s) => s,
                    Value::Integer(i) => String::from_slice(&ctx, i.to_string()),
                    Value::Number(f) => String::from_slice(&ctx, f.to_string()),
                    v => {
                        return Err(TypeError {
                            expected: "string, integer or number",
                            found: v.type_name(),
                        }
                        .into())
                    }
                };
                let uppered = ctx.intern(
                    &s.as_bytes()
                        .iter()
                        .map(u8::to_ascii_uppercase)
                        .collect::<Vec<_>>(),
                );
                stack.replace(ctx, uppered);
                Ok(CallbackReturn::Return)
            }),
        )
        .unwrap();

    ctx.set_global("string", string).unwrap();
}