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 { let mut interp = Lua::core(); let exec: StashedExecutor = interp.enter(|ctx| Ok::(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::(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::("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::>(), ), ); 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, ) -> 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"))?; }; } 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!(delete_mud); register_command!(echo); register_command!(echo_frame); register_command!(echo_frame_raw); register_command!(hsplit); register_command!(panel_merge); register_command!(sendmud_raw); register_stateless_command!(mud_trigger, "untrigger"); register_stateless_command!(mud_untrigger, "unact"); register_stateless_command!(mud_untrigger, "unaction"); 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 { 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> ::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(()) }