diff --git a/COMPATIBILITY.md b/COMPATIBILITY.md index cdb6108f..6c54f84b 100644 --- a/COMPATIBILITY.md +++ b/COMPATIBILITY.md @@ -92,13 +92,13 @@ likely not be implemented due to differences between piccolo and PUC-Lua. | 🔵 | `byte(s[, i, j])` | | | | 🔵 | `char(args...)` | | | | ⚫️️ | `dump(function[, strip])` | | | -| ⚫️️ | `find(s, pattern[, init, plain])` | | | +| 🔵 | `find(s, pattern[, init, plain])` | | | | ⚫️️ | `format(formatstring, args...)` | | | -| ⚫️️ | `gmatch(s, pattern[, init])` | | | -| ⚫️️ | `gsub(s, pattern, repl[, n])` | | | +| 🔵 | `gmatch(s, pattern[, init])` | | | +| 🔵 | `gsub(s, pattern, repl[, n])` | | | | 🔵 | `len(s)` | | | | 🔵 | `lower(s)` | | | -| ⚫️️ | `match(s, pattern[, init])` | | | +| 🔵 | `match(s, pattern[, init])` | | | | ⚫️️ | `pack(fmt, values...)` | | | | ⚫️️ | `packsize(fmt)` | | | | ⚫️️ | `rep(s, n[, sep])` | | | diff --git a/Cargo.lock b/Cargo.lock index e3476b71..d3f49709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "ahash" @@ -245,6 +245,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lsonar" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "156eab9d44bb1641543489513038937530aa708cb2ebd19f9d9f343ef2aa930c" + [[package]] name = "memchr" version = "2.7.4" @@ -288,6 +294,7 @@ dependencies = [ "clap", "gc-arena", "hashbrown", + "lsonar", "rand", "rustyline", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 06712a6e..588751dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,7 @@ allocator-api2.workspace = true anyhow.workspace = true gc-arena.workspace = true hashbrown.workspace = true +lsonar = "0.2.4" rand.workspace = true thiserror.workspace = true diff --git a/src/stdlib/string.rs b/src/stdlib/string.rs index 556537c8..1cab3bec 100644 --- a/src/stdlib/string.rs +++ b/src/stdlib/string.rs @@ -1,4 +1,9 @@ -use crate::{Callback, CallbackReturn, Context, FromValue, String, Table, Value}; +use std::collections::HashMap; + +use crate::{ + meta_ops, BoxSequence, Callback, CallbackReturn, Context, Error, FromValue, IntoValue, + Sequence, String, Table, Value, +}; pub fn load_string<'gc>(ctx: Context<'gc>) { let string = Table::new(&ctx); @@ -97,6 +102,206 @@ pub fn load_string<'gc>(ctx: Context<'gc>) { }), ); + string.set_field( + ctx, + "find", + Callback::from_fn(&ctx, |ctx, _, mut stack| { + let (s, pattern, init, plain) = + stack.consume::<(String, String, Option, Option)>(ctx)?; + let plain = plain.unwrap_or(false); + + let pattern = pattern.to_str()?; + let s = s.to_str()?; + + let Some((start, end, captures)) = + lsonar::find(s, pattern, init.map(|i| i as isize), plain).map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })? + else { + stack.replace(ctx, Value::Nil); + return Ok(CallbackReturn::Return); + }; + + stack.clear(); + stack.into_back(ctx, start as i64); + stack.into_back(ctx, end as i64); + + for capture in captures { + stack.into_back(ctx, capture) + } + + Ok(CallbackReturn::Return) + }), + ); + + string.set_field( + ctx, + "match", + Callback::from_fn(&ctx, |ctx, _, mut stack| { + let (s, pattern, init) = stack.consume::<(String, String, Option)>(ctx)?; + + let pattern = pattern.to_str()?; + let s = s.to_str()?; + + let Some(captures) = + lsonar::r#match(s, pattern, init.map(|i| i as isize)).map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })? + else { + stack.replace(ctx, Value::Nil); + return Ok(CallbackReturn::Return); + }; + + stack.clear(); + for capture in captures { + stack.into_back(ctx, capture) + } + + Ok(CallbackReturn::Return) + }), + ); + + string.set_field( + ctx, + "gmatch", + Callback::from_fn(&ctx, |ctx, _, mut stack| { + use std::{rc::Rc, sync::Mutex}; + + #[derive(gc_arena::Collect, Clone)] + #[collect(require_static)] + struct GMatchIteratorWrapper(Rc>); + + impl GMatchIteratorWrapper { + fn new(iter: lsonar::gmatch::GMatchIterator) -> Self { + Self(Rc::new(Mutex::new(iter))) + } + } + + impl<'gc> Sequence<'gc> for GMatchIteratorWrapper { + fn poll( + self: std::pin::Pin<&mut Self>, + ctx: Context<'gc>, + _exec: crate::Execution<'gc, '_>, + mut stack: crate::Stack<'gc, '_>, + ) -> Result, Error<'gc>> { + stack.clear(); + let root = Rc::clone(&self.0); + let mut root = root.lock().map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })?; + match root.next() { + Some(captures) => { + let captures = captures.map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })?; + for capture in captures { + stack.into_back(ctx, capture) + } + Ok(crate::SequencePoll::Return) + } + None => { + stack.into_back(ctx, Value::Nil); + Ok(crate::SequencePoll::Return) + } + } + } + } + + let (s, pattern) = stack.consume::<(String, String)>(ctx)?; + + let s = s.to_str()?; + let pattern = pattern.to_str()?; + + let iter = lsonar::gmatch(s, pattern).map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })?; + + let root = GMatchIteratorWrapper::new(iter); + + let gmatch = Callback::from_fn_with(&ctx, root, |root, ctx, _, _| { + Ok(CallbackReturn::Sequence(BoxSequence::new( + &ctx, + root.clone(), + ))) + }); + + stack.replace(ctx, gmatch); + Ok(CallbackReturn::Return) + }), + ); + + string.set_field( + ctx, + "gsub", + Callback::from_fn(&ctx, |ctx, _, mut stack| { + let (s, pattern, repl, n) = + stack.consume::<(String, String, Value, Option)>(ctx)?; + + let pattern = pattern.to_str()?; + let s = s.to_str()?; + + let (value, n) = match repl { + Value::String(repl) => lsonar::gsub( + s, + pattern, + lsonar::Repl::String(repl.to_str()?), + n.map(|n| n as usize), + ) + .map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })?, + Value::Table(repl) => { + let mut map = HashMap::with_capacity(repl.length() as usize); // TODO: we need work with `Table` directly + for (key, value) in repl.iter() { + let key = key.into_string(ctx).ok_or_else(|| { + Error::from_value( + "key must be a `string`, `number` or `integer`".into_value(ctx), + ) + })?; + let value = value.into_string(ctx).ok_or_else(|| { + Error::from_value( + "value must be a `string`, `number`, or `integer`".into_value(ctx), + ) + })?; + map.insert( + key.display_lossy().to_string(), + value.display_lossy().to_string(), + ); + } + lsonar::gsub(s, pattern, lsonar::Repl::Table(&map), n.map(|n| n as usize)) + .map_err(|err| { + let err = err.to_string(); + err.into_value(ctx) + })? + } + Value::Function(_) => { + // TODO: we need to implement this, but i don't know how to do it + let _call = meta_ops::call(ctx, repl)?; + return Err("not implemented".into_value(ctx).into()); + } + _ => { + return Err(format!( + "invalid `repl` value, expected `string`, `table` or `function`" + ) + .into_value(ctx) + .into()) + } + }; + + stack.clear(); + stack.into_back(ctx, value); + stack.into_back(ctx, n as i64); + + Ok(CallbackReturn::Return) + }), + ); + ctx.set_global("string", string); }