Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/lua.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use gc_arena::{metrics::Metrics, Arena, Collect, CollectionPhase, Mutation, Root
use crate::{
finalizers::Finalizers,
registry::{Fetchable, Stashable},
stdlib::{load_base, load_coroutine, load_io, load_math, load_string, load_table},
stdlib::{load_base, load_coroutine, load_io, load_math, load_string, load_table, load_utf8},
string::InternedStringSet,
Error, FromMultiValue, Fuel, IntoValue, InvalidTableKey, Registry, Singleton, StashedExecutor,
StaticError, String, Table, Value,
Expand Down Expand Up @@ -134,6 +134,7 @@ impl Lua {
load_coroutine(ctx);
load_math(ctx);
load_string(ctx);
load_utf8(ctx);
load_table(ctx);
})
}
Expand Down
3 changes: 2 additions & 1 deletion src/stdlib/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ mod io;
mod math;
mod string;
mod table;
mod utf8;

pub use self::{
base::load_base, coroutine::load_coroutine, io::load_io, math::load_math, string::load_string,
table::load_table,
table::load_table, utf8::load_utf8,
};
60 changes: 60 additions & 0 deletions src/stdlib/utf8.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use crate::{Callback, CallbackReturn, Context, IntoValue, Table, TypeError, Value};

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

utf8.set(
ctx,
"char",
Callback::from_fn(&ctx, |ctx, mut exec, mut stack| {
let mut string = String::new();
for argn in 0..stack.len() {
exec.fuel().consume(1);
let codepoint = match stack.pop_front() {
Value::Integer(i) if i >= 0 && i <= char::MAX as i64 => Ok(i as u32),
Value::Number(n)
if n >= 0.0 && n.fract() == 0.0 && n <= char::MAX as u32 as f64 =>
{
Ok(n as u32)
}
Value::String(s) => String::from_utf8(s.to_vec())
.map_err(|_| format!("failed to decode argument #{argn} as UTF-8"))
.and_then(|s| {
s.parse::<f64>()
.map_err(|_| {
format!("failed to parse argument #{argn} as a number")
})
.and_then(|f| {
(f >= 0.0 && f.fract() == 0.0 && f <= char::MAX as u32 as f64)
.then_some(f as u32)
.ok_or(format!(
"argument #{argn} has no integer representation"
))
})
}),
v => Err(TypeError {
expected: "valid UTF-8 codepoint (string, number, or integer)",
found: v.type_name(),
}
.to_string()),
}
.map_err(|s| s.into_value(ctx))?;

if let Some(c) = char::from_u32(codepoint) {
string.push(c);
} else {
return Err(format!(
"argument #{argn}: {codepoint:x} is not a valid codepoint"
)
.into_value(ctx)
.into());
}
}
stack.replace(ctx, string);
Ok(CallbackReturn::Return)
}),
)
.unwrap();

ctx.set_global("utf8", utf8).unwrap();
}
9 changes: 9 additions & 0 deletions tests/scripts/utf8.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
function is_err(f)
return pcall(f) == false
end

do
assert(is_err(function() return utf8.char(0x110000) end) and
is_err(function() return utf8.char(0.1) end) and
utf8.char(72, 69, 76.0, "76", 79) == "HELLO")
end