use super::{Error, Function, Lua, LuaOptions, NonZeroU32, Observer, Result, StdLib, detail};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct LuaProgram {
source: String,
pub(crate) bytecode: Vec<u8>,
location: String,
source_line: NonZeroU32,
}
impl LuaProgram {
pub(crate) fn compile(
source: &str,
location: &str,
source_line: NonZeroU32,
execution: &str,
observer: &dyn Observer,
section: &str,
) -> Result<Self> {
observer.observe(execution, section, detail::LUA_COMPILATION_STARTED);
let lua = match Lua::new_with(
StdLib::STRING | StdLib::TABLE | StdLib::MATH,
LuaOptions::default(),
) {
Ok(lua) => lua,
Err(error) => {
observer.observe(execution, section, detail::LUA_COMPILATION_FAILED);
return Err(Error::lua(error));
}
};
let function = match lua.load(source).set_name(location).into_function() {
Ok(function) => function,
Err(error) => {
observer.observe(execution, section, detail::LUA_COMPILATION_FAILED);
return Err(Error::LuaCompile {
location: location.to_owned(),
source_line: source_line.get(),
lua_source: source.to_owned(),
message: error.to_string(),
source: Box::new(error),
});
}
};
let bytecode = function.dump(false);
observer.observe(execution, section, detail::LUA_COMPILATION_SUCCEEDED);
Ok(Self {
source: source.to_owned(),
bytecode,
location: location.to_owned(),
source_line,
})
}
#[must_use]
pub fn source(&self) -> &str {
&self.source
}
#[must_use]
pub fn source_line(&self) -> NonZeroU32 {
self.source_line
}
pub(crate) fn load(&self, lua: &Lua) -> Result<Function> {
lua.load(self.bytecode.as_slice())
.into_function()
.map_err(Error::lua)
}
pub(crate) fn map_runtime_error(&self, error: &mlua::Error) -> Error {
if crate::cancel::is_cancelled() {
return Error::Interrupted;
}
let raw = error.to_string();
if let Some(resource) = quota_resource(&raw) {
return Error::LuaQuota { resource };
}
let mapped = map_chunk_line_to_absolute(&raw, self.source_line, self.location());
Error::LuaRuntime {
message: mapped,
source: Box::new(error.clone()),
}
}
#[must_use]
pub fn location(&self) -> &str {
&self.location
}
}
pub(crate) fn quota_resource(raw: &str) -> Option<&'static str> {
use crate::error::lua_quota;
if raw.contains(lua_quota::LOG_EVENT) {
Some("log event")
} else if raw.contains(lua_quota::LOG_BYTE) {
Some("log byte")
} else if raw.contains(lua_quota::INSTRUCTION) {
Some("instruction")
} else {
None
}
}
pub(crate) fn map_chunk_line_to_absolute(
message: &str,
source_line: NonZeroU32,
location: &str,
) -> String {
if location.is_empty() {
return message.to_owned();
}
let marker = format!("[string \"{location}\"]:");
let mut result = String::with_capacity(message.len() + 64);
let mut rest = message;
let mut first_absolute: Option<u32> = None;
while let Some(start) = rest.find(&marker) {
result.push_str(&rest[..start]);
result.push_str(&marker);
let after = &rest[start + marker.len()..];
let digit_end = after
.find(|c: char| !c.is_ascii_digit())
.unwrap_or(after.len());
if digit_end == 0 {
rest = after;
continue;
}
if let Ok(chunk_line) = after[..digit_end].parse::<u32>() {
let absolute = source_line
.get()
.checked_add(chunk_line)
.and_then(|sum| sum.checked_sub(1));
match absolute {
Some(absolute) => {
if first_absolute.is_none() {
first_absolute = Some(absolute);
}
result.push_str(&absolute.to_string());
}
None => result.push_str(&after[..digit_end]),
}
rest = &after[digit_end..];
} else {
rest = after;
}
}
result.push_str(rest);
if let Some(absolute) = first_absolute {
format!("{location}:{absolute}: {result}")
} else {
result
}
}