419 lines
16 KiB
Rust
419 lines
16 KiB
Rust
use self::{frameroutes::*, frames::*, muds::*};
|
|
use anyhow::Error;
|
|
use piccolo::{
|
|
async_callback::{AsyncSequence, Locals},
|
|
meta_ops::{self, MetaResult},
|
|
stash::Fetchable,
|
|
Callback, Closure, Context, Executor, ExternError, FromValue, Function, IntoValue, Lua,
|
|
MetaMethod, Stack, StashedError, StashedExecutor, StashedFunction, StashedValue, Table, Value,
|
|
Variadic,
|
|
};
|
|
use yew::UseStateSetter;
|
|
|
|
use crate::{
|
|
id_intern::intern_id,
|
|
match_table::{
|
|
create_match_table, match_table_add, match_table_lua_table, match_table_remove,
|
|
match_table_try_run_sub,
|
|
},
|
|
parsing::ParsedCommand,
|
|
GlobalLayoutCell, GlobalMemoCell, TermFrame,
|
|
};
|
|
|
|
pub struct LuaState {
|
|
pub interp: Lua,
|
|
pub exec: StashedExecutor,
|
|
}
|
|
|
|
mod frameroutes;
|
|
pub mod frames;
|
|
pub mod muds;
|
|
|
|
impl LuaState {
|
|
pub fn setup() -> Result<LuaState, String> {
|
|
let mut interp = Lua::core();
|
|
let exec: StashedExecutor =
|
|
interp.enter(|ctx| Ok::<StashedExecutor, String>(ctx.stash(Executor::new(ctx))))?;
|
|
|
|
Ok(LuaState { interp, exec })
|
|
}
|
|
|
|
fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), ExternError> {
|
|
self.interp.try_enter(|ctx| {
|
|
let info_table = Table::from_value(ctx, ctx.get_global("info")?)?;
|
|
info_table.set(
|
|
ctx,
|
|
ctx.intern_static(b"current_frame"),
|
|
intern_id::<TermFrame>(ctx, frame.clone()),
|
|
)?;
|
|
Ok(())
|
|
})
|
|
}
|
|
|
|
pub fn set_current_frame(&mut self, frame: &TermFrame) {
|
|
// We silently ignore errors here. Failure can happen if the Lua code does weird things
|
|
// like messes with the info global, so better to just ignore.
|
|
self.try_set_current_frame(frame).unwrap_or(())
|
|
}
|
|
|
|
pub fn execute(&mut self, command: &str) -> Result<(), String> {
|
|
self.interp
|
|
.try_enter(|ctx| {
|
|
let closure = Closure::load(ctx, None, format!("return ({})", command).as_bytes())
|
|
.or_else(|_| Closure::load(ctx, None, command.as_bytes()))?;
|
|
ctx.fetch(&self.exec).restart(ctx, closure.into(), ());
|
|
Ok(())
|
|
})
|
|
.map_err(|err| format!("{}", err))?;
|
|
self.interp
|
|
.execute::<()>(&self.exec)
|
|
.map_err(|err| format!("{}", err))
|
|
}
|
|
|
|
pub fn execute_command(
|
|
&mut self,
|
|
command: &str,
|
|
arguments: &ParsedCommand,
|
|
) -> Result<(), String> {
|
|
self.interp
|
|
.try_enter(|ctx| {
|
|
let commands = ctx.get_global::<Table>("commands")?;
|
|
let command_value: Value = commands.get(ctx, ctx.intern(command.as_bytes()))?;
|
|
if command_value.is_nil() {
|
|
Err(anyhow::Error::msg("Unknown command"))?;
|
|
}
|
|
let command_fn = Function::from_value(ctx, command_value)?;
|
|
ctx.fetch(&self.exec).restart(
|
|
ctx,
|
|
command_fn,
|
|
Variadic(
|
|
arguments
|
|
.arguments
|
|
.iter()
|
|
.map(|s| ctx.intern(s.text.as_bytes()).into())
|
|
.collect::<Vec<Value>>(),
|
|
),
|
|
);
|
|
Ok(())
|
|
})
|
|
.map_err(|err| format!("{}", err))?;
|
|
self.interp
|
|
.execute::<()>(&self.exec)
|
|
.map_err(|err| format!("{}", err))
|
|
}
|
|
|
|
pub(crate) fn dispatch_normal_command(
|
|
&mut self,
|
|
frame: &TermFrame,
|
|
command: &str,
|
|
) -> anyhow::Result<()> {
|
|
self.interp.try_enter(|ctx| {
|
|
let frames: Table = ctx.get_global("frames")?;
|
|
let frame_tab: Value = frames.get(ctx, frame.0 as i64)?;
|
|
if frame_tab.is_nil() {
|
|
Err(anyhow::Error::msg(
|
|
"Dispatching command to frame missing in Lua frames.",
|
|
))?;
|
|
}
|
|
ctx.fetch(&self.exec).restart(
|
|
ctx,
|
|
Function::Callback(send_command_to_frame(ctx)),
|
|
(frame_tab, ctx.intern(command.as_bytes())),
|
|
);
|
|
Ok(())
|
|
})?;
|
|
self.interp.execute(&self.exec)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn install_lua_globals(
|
|
global_memo: &GlobalMemoCell,
|
|
global_layout: UseStateSetter<GlobalLayoutCell>,
|
|
) -> Result<(), String> {
|
|
global_memo
|
|
.lua_engine
|
|
.borrow_mut()
|
|
.interp
|
|
.try_enter(|ctx| {
|
|
let cmd_table = Table::new(&ctx);
|
|
macro_rules! register_command {
|
|
($sym: ident) => {
|
|
cmd_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static(stringify!($sym).as_bytes()),
|
|
$sym(ctx, &global_memo, &global_layout),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add command"))?;
|
|
};
|
|
($sym: ident, $name: literal) => {
|
|
cmd_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static($name.as_bytes()),
|
|
$sym(ctx, &global_memo, &global_layout),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add command"))?;
|
|
};
|
|
}
|
|
macro_rules! register_stateless_command {
|
|
($sym: ident) => {
|
|
cmd_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static(stringify!($sym).as_bytes()),
|
|
$sym(ctx),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add command"))?;
|
|
};
|
|
($sym: ident, $name: literal) => {
|
|
cmd_table
|
|
.set(ctx, ctx.intern_static($name.as_bytes()), $sym(ctx))
|
|
.map_err(|_| Error::msg("Can't add command"))?;
|
|
};
|
|
}
|
|
register_stateless_command!(mud_trigger, "act");
|
|
register_stateless_command!(mud_trigger, "action");
|
|
register_stateless_command!(alias);
|
|
register_stateless_command!(unalias);
|
|
register_command!(close_mud);
|
|
register_command!(connect_mud);
|
|
register_stateless_command!(create_match_table);
|
|
register_command!(cmd_delete_logs, "deletelogs");
|
|
register_command!(delay);
|
|
register_command!(delete_mud);
|
|
register_command!(cmd_download_logs, "downloadlogs");
|
|
register_command!(echo);
|
|
register_command!(echo_frame);
|
|
register_command!(echo_frame_raw);
|
|
register_command!(hsplit);
|
|
register_command!(cmd_list_logs, "listlogs");
|
|
register_command!(mud_log, "log");
|
|
register_command!(panel_merge);
|
|
register_command!(sendmud_raw);
|
|
register_command!(tick);
|
|
register_stateless_command!(mud_trigger, "untrigger");
|
|
register_stateless_command!(mud_untrigger, "unact");
|
|
register_stateless_command!(mud_untrigger, "unaction");
|
|
register_command!(untick, "undelay");
|
|
register_command!(untick);
|
|
register_stateless_command!(mud_untrigger, "untrigger");
|
|
register_command!(vsplit);
|
|
ctx.set_global("commands", cmd_table);
|
|
let info_table = Table::new(&ctx);
|
|
ctx.set_global("info", info_table);
|
|
let muds_table = Table::new(&ctx);
|
|
ctx.set_global("muds", muds_table);
|
|
let frames_table = Table::new(&ctx);
|
|
ctx.set_global("frames", frames_table);
|
|
|
|
let handlers_table = Table::new(&ctx);
|
|
ctx.set_global("handlers", handlers_table);
|
|
macro_rules! register_handler {
|
|
($sym: ident) => {
|
|
handlers_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static(stringify!($sym).as_bytes()),
|
|
$sym(ctx, &global_memo),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add handler"))?;
|
|
};
|
|
}
|
|
register_handler!(mudoutput);
|
|
|
|
let classes_table = Table::new(&ctx);
|
|
ctx.set_global("classes", classes_table);
|
|
|
|
let mud_class_table = Table::new(&ctx);
|
|
classes_table.set(ctx, "mud", mud_class_table)?;
|
|
mud_class_table.set(ctx, MetaMethod::Index, mud_class_table)?;
|
|
|
|
macro_rules! register_class_function {
|
|
($class_table: ident, $sym: ident) => {
|
|
$class_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static(stringify!($sym).as_bytes()),
|
|
$sym(ctx, &global_memo),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add handler"))?;
|
|
};
|
|
($class_table: ident, $name: literal, $sym: ident) => {
|
|
$class_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static($name.as_bytes()),
|
|
$sym(ctx, &global_memo),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add handler"))?;
|
|
};
|
|
}
|
|
macro_rules! register_stateless_class_function {
|
|
($class_table: ident, $sym: ident) => {
|
|
$class_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static(stringify!($sym).as_bytes()),
|
|
$sym(ctx),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add handler"))?;
|
|
};
|
|
($class_table: ident, $name: literal, $sym: ident) => {
|
|
$class_table
|
|
.set(ctx, ctx.intern_static($name.as_bytes()), $sym(ctx))
|
|
.map_err(|_| Error::msg("Can't add handler"))?;
|
|
};
|
|
}
|
|
|
|
register_class_function!(mud_class_table, mudoutput_line);
|
|
register_class_function!(mud_class_table, mudoutput_prompt);
|
|
register_class_function!(mud_class_table, mudoutput_will);
|
|
register_class_function!(mud_class_table, mudoutput_wont);
|
|
register_class_function!(mud_class_table, mudoutput_do);
|
|
register_class_function!(mud_class_table, mudoutput_dont);
|
|
register_class_function!(mud_class_table, mudoutput_subnegotiation);
|
|
register_class_function!(mud_class_table, mudinput_line);
|
|
register_stateless_class_function!(mud_class_table, "new", new_mud);
|
|
|
|
macro_rules! register_class_nop {
|
|
($class_table: ident, $sym: ident) => {
|
|
$class_table
|
|
.set(
|
|
ctx,
|
|
ctx.intern_static(stringify!($sym).as_bytes()),
|
|
lua_nop(ctx),
|
|
)
|
|
.map_err(|_| Error::msg("Can't add handler"))?;
|
|
};
|
|
}
|
|
|
|
register_class_nop!(mud_class_table, mudoutput_break);
|
|
register_class_nop!(mud_class_table, mudoutput_sync);
|
|
register_class_nop!(mud_class_table, mudoutput_interrupt);
|
|
register_class_nop!(mud_class_table, mudoutput_abort_output);
|
|
register_class_nop!(mud_class_table, mudoutput_areyouthere);
|
|
|
|
let frame_class_table = Table::new(&ctx);
|
|
classes_table.set(ctx, "frame", frame_class_table)?;
|
|
frame_class_table.set(ctx, MetaMethod::Index, frame_class_table)?;
|
|
register_class_function!(frame_class_table, "new", new_frame);
|
|
register_class_function!(frame_class_table, "input", frame_input);
|
|
|
|
let frameroute_class_table = Table::new(&ctx);
|
|
classes_table.set(ctx, "frameroute", frameroute_class_table)?;
|
|
frameroute_class_table.set(ctx, MetaMethod::Index, frameroute_class_table)?;
|
|
register_class_function!(frameroute_class_table, "new", new_frameroute);
|
|
register_class_function!(frameroute_class_table, "route", frameroute_route);
|
|
|
|
let match_table_class_table = Table::new(&ctx);
|
|
classes_table.set(ctx, "match_table", match_table_class_table)?;
|
|
match_table_class_table.set(ctx, MetaMethod::Index, match_table_class_table)?;
|
|
register_stateless_class_function!(match_table_class_table, "add", match_table_add);
|
|
register_stateless_class_function!(
|
|
match_table_class_table,
|
|
"remove",
|
|
match_table_remove
|
|
);
|
|
register_stateless_class_function!(
|
|
match_table_class_table,
|
|
"lua_table",
|
|
match_table_lua_table
|
|
);
|
|
register_class_function!(
|
|
match_table_class_table,
|
|
"try_run_sub",
|
|
match_table_try_run_sub
|
|
);
|
|
|
|
Ok(())
|
|
})
|
|
.map_err(|e| e.to_string())?;
|
|
|
|
lua_global_initialisation(global_memo)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn lua_global_initialisation(global_memo: &GlobalMemoCell) -> Result<(), String> {
|
|
let mut lua_engine = global_memo.lua_engine.borrow_mut();
|
|
let lua_engine_ref: &mut LuaState = &mut lua_engine;
|
|
lua_engine_ref.interp.enter(|ctx| {
|
|
ctx.fetch(&lua_engine_ref.exec).restart(
|
|
ctx,
|
|
Function::Callback(ensure_frame_instance(ctx, &TermFrame(1))),
|
|
(),
|
|
);
|
|
});
|
|
lua_engine_ref
|
|
.interp
|
|
.execute(&lua_engine_ref.exec)
|
|
.map_err(|e| e.to_string())
|
|
}
|
|
|
|
pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> {
|
|
Callback::from_fn(&ctx, |_ctx, _ex, _stack| {
|
|
Ok(piccolo::CallbackReturn::Return)
|
|
})
|
|
}
|
|
|
|
// Borrowed from piccolo source.
|
|
pub fn prep_metaop_call<'gc, const N: usize>(
|
|
ctx: Context<'gc>,
|
|
mut stack: Stack<'gc, '_>,
|
|
locals: Locals<'gc, '_>,
|
|
res: MetaResult<'gc, N>,
|
|
) -> Option<StashedFunction> {
|
|
match res {
|
|
MetaResult::Value(v) => {
|
|
stack.push_back(v);
|
|
None
|
|
}
|
|
MetaResult::Call(call) => {
|
|
stack.extend(call.args);
|
|
Some(locals.stash(&ctx, call.function))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn call_checking_metatable<'gc, T: Fetchable>(
|
|
seq: &mut AsyncSequence,
|
|
obj: T,
|
|
func_name: &'static str,
|
|
arguments: &[StashedValue],
|
|
) -> Result<(), StashedError>
|
|
where
|
|
for<'gcb> <T as Fetchable>::Fetched<'gcb>: IntoValue<'gcb>,
|
|
{
|
|
let call = seq.try_enter(|ctx, locals, _execution, mut stack| {
|
|
let obj = locals.fetch(&obj);
|
|
stack.consume(ctx)?;
|
|
Ok(prep_metaop_call(
|
|
ctx,
|
|
stack,
|
|
locals,
|
|
meta_ops::index(
|
|
ctx,
|
|
obj.into_value(ctx),
|
|
ctx.intern_static(func_name.as_bytes()).into_value(ctx),
|
|
)?,
|
|
))
|
|
})?;
|
|
if let Some(call) = call {
|
|
seq.call(&call, 0).await?;
|
|
}
|
|
let call = seq.try_enter(|ctx, locals, _, mut stack| {
|
|
let value = stack
|
|
.pop_back()
|
|
.ok_or_else(|| anyhow::Error::msg("Index didn't return value"))?;
|
|
stack.consume(ctx)?;
|
|
stack.push_back(locals.fetch(&obj).into_value(ctx));
|
|
for arg in arguments {
|
|
stack.push_back(locals.fetch(arg));
|
|
}
|
|
Ok(locals.stash(&ctx, Function::from_value(ctx, value)?))
|
|
})?;
|
|
seq.call(&call, 0).await?;
|
|
Ok(())
|
|
}
|