Created
November 9, 2023 12:50
-
-
Save svermeulen/8294f6f3cddd6615d37976c3540ea65f to your computer and use it in GitHub Desktop.
Patches for piccolo project
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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], | |
}, | |
})) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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