Skip to content

Instantly share code, notes, and snippets.

@svermeulen
Created November 9, 2023 12:50
Show Gist options
  • Save svermeulen/8294f6f3cddd6615d37976c3540ea65f to your computer and use it in GitHub Desktop.
Save svermeulen/8294f6f3cddd6615d37976c3540ea65f to your computer and use it in GitHub Desktop.
Patches for piccolo project
pub fn index<'gc>(
ctx: Context<'gc>,
table: Value<'gc>,
key: Value<'gc>,
) -> Result<MetaResult<'gc, 2>, TypeError> {
let idx = match table {
Value::String(_) => match ctx.state.globals.get(ctx, "string") {
Value::Table(string_table) => {
let idx = if let Some(mt) = string_table.metatable() {
mt.get(ctx, MetaMethod::Index)
} else {
Value::Nil
};
if idx.is_nil() {
return Ok(MetaResult::Value(Value::Nil));
}
idx
}
_ => {
return Err(TypeError {
expected: "table",
found: table.type_name(),
});
}
},
Value::Table(table) => {
let v = table.get(ctx, key);
if !v.is_nil() {
return Ok(MetaResult::Value(v));
}
let idx = if let Some(mt) = table.metatable() {
mt.get(ctx, MetaMethod::Index)
} else {
Value::Nil
};
if idx.is_nil() {
return Ok(MetaResult::Value(Value::Nil));
}
idx
}
Value::UserData(u) if u.metatable().is_some() => {
let idx = if let Some(mt) = u.metatable() {
mt.get(ctx, MetaMethod::Index)
} else {
Value::Nil
};
if idx.is_nil() {
return Err(TypeError {
expected: "table",
found: table.type_name(),
});
}
idx
}
_ => {
return Err(TypeError {
expected: "table",
found: table.type_name(),
})
}
};
Ok(MetaResult::Call(match idx {
Value::Table(table) => MetaCall {
function: AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let table = stack.get(0);
let key = stack.get(1);
stack.clear();
match index(ctx, table, key)? {
MetaResult::Value(v) => {
stack.push_back(v);
Ok(CallbackReturn::Return.into())
}
MetaResult::Call(call) => {
stack.extend(call.args);
Ok(CallbackReturn::TailCall(call.function, None).into())
}
}
})
.into(),
args: [table.into(), key],
},
_ => MetaCall {
function: call(ctx, idx)?,
args: [table, key],
},
}))
}
use anyhow::Result;
use std::io::Read;
use crate::{
AnyCallback, CallbackReturn, Closure, Context, FunctionProto, IntoValue, String, Table, Value,
};
pub trait ModuleLoader {
fn load_lua_module(&self, module_path: &str) -> Result<Box<dyn Read>>;
}
pub struct DefaultModuleLoader;
impl DefaultModuleLoader {
pub fn new() -> Self {
Self {}
}
}
impl ModuleLoader for DefaultModuleLoader {
fn load_lua_module(&self, _: &str) -> Result<Box<dyn Read>> {
Err(anyhow::anyhow!("No module loader configured with piccolo"))
}
}
pub fn load_package<'gc>(ctx: Context<'gc>, module_loader: Box<dyn ModuleLoader>) {
let package = Table::new(&ctx);
package.set(ctx, "loaded", Table::new(&ctx)).unwrap();
ctx.state
.globals
.set(
ctx,
"require",
AnyCallback::from_fn(&ctx, move |ctx, _, stack, _| {
let key: String = stack.consume(ctx)?;
if let Value::Table(package2) = ctx.state.globals.get(ctx, "package") {
if let Value::Table(package_loaded) = package2.get(ctx, "loaded") {
match package_loaded.get(ctx, key) {
crate::Value::Function(closure) => {
Ok(CallbackReturn::TailCall(closure.into(), None))
}
crate::Value::Nil => {
let file_contents_stream =
module_loader.load_lua_module(key.to_str()?)?;
let closure = Closure::new(
&ctx,
FunctionProto::compile(ctx, file_contents_stream)?,
Some(ctx.state.globals),
)?;
package_loaded.set(ctx, key, closure.clone())?;
Ok(CallbackReturn::TailCall(closure.into(), None))
}
_ => Err("Unexpected existing value found in package.loaded"
.into_value(ctx)
.into()),
}
} else {
Err("Unexpected value found for package".into_value(ctx).into())
}
} else {
Err("Unexpected value found for package.loaded"
.into_value(ctx)
.into())
}
}),
)
.unwrap();
ctx.state.globals.set(ctx, "package", package).unwrap();
}
use crate::{AnySequence, Thread};
use gc_arena::Collect;
use lua_patterns::LuaPattern;
use sprintf::{parse_format_string, vsprintf, ConversionType, Printf};
use unicode_segmentation::UnicodeSegmentation;
use std::string::String as StdString;
use crate::{
meta_ops::{self, MetaResult},
AnyCallback, CallbackReturn, Context, Error, Fuel, IntoValue, Sequence, SequencePoll, Stack,
String, Table, Value, Variadic,
};
pub fn load_string<'gc>(ctx: Context<'gc>) {
let string = Table::new(&ctx);
let string_mt = Table::new(&ctx);
let index_table = Table::new(&ctx);
index_table
.set(
ctx,
"len",
AnyCallback::from_fn(&ctx, |ctx, _, 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();
index_table
.set(
ctx,
"sub",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let (s1, v2, v3): (String, Value, Value) = stack.consume(ctx)?;
let start_index = match v2 {
Value::Integer(i) => i as usize - 1,
Value::Number(i) => i as usize - 1,
_ => return Err("Bad argument to string.match".into_value(ctx).into()),
};
let s1_str_full = s1.to_str()?;
let end_index = match v3 {
Value::Nil => s1_str_full.len(),
Value::Integer(i) => i as usize,
Value::Number(i) => i as usize,
_ => return Err("Bad argument to string.match".into_value(ctx).into()),
};
let s1_str = if start_index == 0 {
s1_str_full
} else {
s1_str_full
.get(start_index..end_index)
.ok_or_else(|| "String index out of bounds".into_value(ctx))?
};
stack.replace(ctx, String::from_slice(&ctx, s1_str));
return Ok(CallbackReturn::Return);
}),
)
.unwrap();
index_table
.set(
ctx,
"match",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let (s1, s2, v3): (String, String, Value) = stack.consume(ctx)?;
let start_index = match v3 {
Value::Nil => 0,
Value::Integer(i) => i as usize - 1,
Value::Number(i) => i as usize - 1,
_ => return Err("Bad argument to string.match".into_value(ctx).into()),
};
let s1_str_full = s1.to_str()?;
let s1_str = if start_index == 0 {
s1_str_full
} else {
s1_str_full
.get(start_index..)
.ok_or_else(|| "String index out of bounds".into_value(ctx))?
};
let s2_str = s2.to_str()?;
let mut m = LuaPattern::new(s2_str);
if let Some(str_match) = m.match_maybe(s1_str) {
stack.replace(ctx, String::from_slice(&ctx, str_match));
} else {
stack.replace(ctx, Value::Nil);
}
return Ok(CallbackReturn::Return);
}),
)
.unwrap();
index_table
.set(
ctx,
"find",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let (s1, s2, v3): (String, String, Value) = stack.consume(ctx)?;
let start_index = match v3 {
Value::Nil => 0,
Value::Integer(i) => i as usize - 1,
Value::Number(i) => i as usize - 1,
_ => return Err("Bad argument to string.find".into_value(ctx).into()),
};
let s1_str_full = s1.to_str()?;
let s1_str = if start_index == 0 {
s1_str_full
} else {
s1_str_full
.get(start_index..)
.ok_or_else(|| "String index out of bounds".into_value(ctx))?
};
let s2_str = s2.to_str()?;
let mut m = LuaPattern::new(s2_str);
if m.matches(s1_str) {
let full_range = m.capture(0);
let mut rvals: Vec<Value> = vec![
Value::Integer((full_range.start + 1 + start_index) as i64),
Value::Integer((full_range.end + start_index) as i64),
];
let captures = m.match_captures(s1_str);
for i in 1..captures.num_matches() {
let capture = captures.get(i);
rvals.push(Value::String(String::from_slice(&ctx, capture)));
}
stack.replace(ctx, Variadic(rvals));
} else {
stack.replace(ctx, Value::Nil);
};
return Ok(CallbackReturn::Return);
}),
)
.unwrap();
index_table
.set(
ctx,
"gsub",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let (s, pattern, replacement, v4): (String, String, String, Value) =
stack.consume(ctx)?;
let max_num_replacements = match v4 {
Value::Nil => None,
Value::Integer(i) => Some(i as i32),
Value::Number(i) => Some(i as i32),
_ => return Err("Bad argument to string.find".into_value(ctx).into()),
};
let s_str = s.to_str()?;
let mut m = LuaPattern::new(pattern.to_str()?);
let replacement_str = replacement.to_str()?;
let (new_s, num_replaced) = m.gsub(s_str, replacement_str, max_num_replacements);
stack.replace(
ctx,
(
String::from_slice(&ctx, new_s),
Value::Integer(num_replaced as i64),
),
);
return Ok(CallbackReturn::Return);
}),
)
.unwrap();
index_table
.set(
ctx,
"upper",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let s: String = stack.consume(ctx)?;
let s_str = s.to_str()?;
stack.replace(ctx, String::from_slice(&ctx, s_str.to_uppercase()));
return Ok(CallbackReturn::Return);
}),
)
.unwrap();
index_table
.set(
ctx,
"lower",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let s: String = stack.consume(ctx)?;
let s_str = s.to_str()?;
stack.replace(ctx, String::from_slice(&ctx, s_str.to_lowercase()));
Ok(CallbackReturn::Return)
}),
)
.unwrap();
index_table
.set(
ctx,
"char",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let args: Variadic<Vec<Value>> = stack.consume(ctx)?;
let values: Result<Vec<u8>, _> = args
.iter()
.map(|v| match v {
Value::Integer(i) => Ok(*i as u8),
Value::Number(i) => Ok(*i as u8),
_ => Err("Bad argument to string.char"),
})
.collect();
let s = StdString::from_utf8(values.map_err(|e| e.into_value(ctx))?)?;
stack.replace(ctx, String::from_slice(&ctx, s));
Ok(CallbackReturn::Return)
}),
)
.unwrap();
index_table
.set(
ctx,
"reverse",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let s: String = stack.consume(ctx)?;
let s_str = s.to_str()?;
let s_str_reversed: StdString = s_str.graphemes(true).rev().collect();
stack.replace(ctx, String::from_slice(&ctx, s_str_reversed));
Ok(CallbackReturn::Return)
}),
)
.unwrap();
index_table
.set(
ctx,
"rep",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let (s, v2): (String, Value) = stack.consume(ctx)?;
let n = match v2 {
Value::Integer(i) => i as usize,
Value::Number(i) => i as usize,
_ => return Err("Bad argument to string.rep".into_value(ctx).into()),
};
let s_str = s.to_str()?;
let result = s_str.repeat(n);
stack.replace(ctx, String::from_slice(&ctx, result));
Ok(CallbackReturn::Return)
}),
)
.unwrap();
index_table
.set(
ctx,
"byte",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let (s, v2, v3): (String, Value, Value) = stack.consume(ctx)?;
let s_str = s.to_str()?;
let start = match v2 {
Value::Integer(i) => i as usize,
Value::Nil => 1,
_ => return Err("Bad argument to string.byte".into_value(ctx).into()),
};
let end = match v3 {
Value::Integer(i) => Some(i as usize),
Value::Nil => None,
_ => return Err("Bad argument to string.byte".into_value(ctx).into()),
};
let end = end.unwrap_or(start);
let bytes: Vec<Value> = s_str
.as_bytes()
.get(start - 1..end)
.ok_or_else(|| "String index out of bounds".into_value(ctx))?
.iter()
.map(|&b| Value::Integer(b.into()))
.collect();
stack.replace(ctx, Variadic(bytes));
Ok(CallbackReturn::Return)
}),
)
.unwrap();
let gmatch_next = AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
let table: Table = stack.consume(ctx)?;
let len = table.length();
if len == 0 {
stack.replace(ctx, Value::Nil);
return Ok(CallbackReturn::Return);
}
let last_index = Value::Integer(len);
if let Value::Table(t) = table.get_value(last_index) {
let _ = table.set_value(&ctx, last_index, Value::Nil);
let mut results = vec![];
let len = t.length();
if len == 1 {
let val = t.get_value(Value::Integer(1));
results.push(val);
} else {
for i in 1..len {
let val = t.get_value(Value::Integer(i + 1));
results.push(val)
}
}
stack.replace(ctx, Variadic(results));
Ok(CallbackReturn::Return)
} else {
Err("unexpected state in string.gmatch".into_value(ctx).into())
}
});
index_table
.set(
ctx,
"gmatch",
AnyCallback::from_fn_with(&ctx, gmatch_next, move |gmatch_next, ctx, _, stack, _| {
let (s, pattern): (String, String) = stack.consume(ctx)?;
let mut m = LuaPattern::new(pattern.to_str()?);
// TODO - Is it possible to avoid expanding out all matches immediately?
let mut matches: Vec<Vec<String>> = m
.gmatch_captures(s.to_str()?)
.map(|caps| {
let mut captures = vec![];
for i in 0..caps.num_matches() {
captures.push(String::from_slice(&ctx, caps.get(i)));
}
captures
})
.collect();
// Reverse so we can pop values off the end as we iterate
matches.reverse();
stack.replace(ctx, (*gmatch_next, matches));
Ok(CallbackReturn::Return)
}),
)
.unwrap();
index_table
.set(
ctx,
"format",
AnyCallback::from_fn(&ctx, |ctx, _, stack, _| {
#[derive(Debug, Copy, Clone, Eq, PartialEq, Collect)]
#[collect(require_static)]
enum Mode {
Init,
First,
Rest,
}
#[derive(Collect)]
#[collect(no_drop)]
enum RequiredType {
Integer(i64),
Number(f64),
String(StdString),
}
#[derive(Collect)]
#[collect(no_drop)]
struct PrintSeq<'gc> {
mode: Mode,
format_string: String<'gc>,
values: Vec<Value<'gc>>,
buffer: Vec<Value<'gc>>,
required_types: Vec<RequiredType>,
num_processed: usize,
}
impl<'gc> Sequence<'gc> for PrintSeq<'gc> {
fn poll(
&mut self,
ctx: Context<'gc>,
_fuel: &mut Fuel,
stack: &mut Stack<'gc>,
_thread: Thread<'gc>,
) -> Result<SequencePoll<'gc>, Error<'gc>> {
if self.mode == Mode::Init {
self.mode = Mode::First;
} else {
self.values.push(stack.get(0));
}
while let Some(value) = self.values.pop() {
let index = self.num_processed;
let required_type = match self.required_types.get(index as usize) {
Some(t) => t,
None => {
return Err("Unexpected state encountered during string.format"
.into_value(ctx)
.into())
}
};
// the vsprintf function is more strict than lua string.format so we have to do
// some conversions ahead of time here
self.required_types[index] = match required_type {
RequiredType::Integer(_) => RequiredType::Integer(match value {
Value::Integer(x) => x,
Value::Number(x) => x as i64,
_ => {
return Err("Bad argument to string.format"
.into_value(ctx)
.into())
}
}),
RequiredType::Number(_) => RequiredType::Number(match value {
Value::Integer(x) => x as f64,
Value::Number(x) => x,
_ => {
return Err("Bad argument to string.format"
.into_value(ctx)
.into())
}
}),
RequiredType::String(_) => {
RequiredType::String(match meta_ops::tostring(ctx, value)? {
MetaResult::Value(v) => {
if self.mode == Mode::First {
self.mode = Mode::Rest;
}
let mut str_buffer = Vec::new();
v.display(&mut str_buffer)?;
StdString::from_utf8(str_buffer)?
}
MetaResult::Call(call) => {
stack.extend(call.args);
return Ok(SequencePoll::Call {
function: call.function,
is_tail: false,
});
}
})
}
};
self.num_processed += 1;
}
let mut printf_values: Vec<&dyn Printf> = vec![];
for val in self.required_types.iter() {
printf_values.push(match val {
RequiredType::Integer(i) => i as &dyn sprintf::Printf,
RequiredType::Number(i) => i as &dyn sprintf::Printf,
RequiredType::String(s) => s as &dyn sprintf::Printf,
})
}
let formatted_value =
vsprintf(&self.format_string.to_string(), &printf_values)
.map_err(|_| "Printf operation failed".into_value(ctx))?;
stack.replace(ctx, String::from_slice(&ctx, formatted_value));
Ok(SequencePoll::Return)
}
}
let format_value: Value = stack.get(0);
stack.pop_front();
if let Value::String(format_str) = format_value {
let format_elements = parse_format_string(format_str.to_str()?)
.map_err(|_| "Printf operation failed".into_value(ctx))?;
let mut required_types: Vec<RequiredType> = vec![];
for i in 0..format_elements.len() {
match &format_elements[i] {
sprintf::FormatElement::Verbatim(_) => continue,
sprintf::FormatElement::Format(f) => {
if f.conversion_type == ConversionType::PercentSign {
continue;
}
required_types.push(match f.conversion_type {
ConversionType::DecInt
| ConversionType::OctInt
| ConversionType::HexIntLower
| ConversionType::HexIntUpper
| ConversionType::Char
| ConversionType::PercentSign => RequiredType::Integer(0),
ConversionType::SciFloatLower
| ConversionType::SciFloatUpper
| ConversionType::DecFloatLower
| ConversionType::DecFloatUpper
| ConversionType::CompactFloatLower
| ConversionType::CompactFloatUpper => {
RequiredType::Number(0.0)
}
ConversionType::String => RequiredType::String("".to_string()),
});
}
}
}
Ok(CallbackReturn::Sequence(AnySequence::new(
&ctx,
PrintSeq {
mode: Mode::Init,
values: stack.drain(..).rev().collect(),
format_string: format_str,
buffer: vec![],
required_types,
num_processed: 0,
},
)))
} else {
return Err("Expected string as first argument to string.format"
.into_value(ctx)
.into());
}
}),
)
.unwrap();
string_mt.set(ctx, "__index", index_table).unwrap();
string.set_metatable(&ctx, Some(string_mt));
ctx.state.globals.set(ctx, "string", string).unwrap();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment