use self::{frames::*, muds::*}; use anyhow::Error; use piccolo::{ Callback, Closure, Context, Executor, ExternError, FromValue, Function, Lua, StashedExecutor, Table, Value, Variadic, }; use yew::UseStateSetter; use crate::{id_intern::intern_id, GlobalLayoutCell, GlobalMemoCell, TermFrame}; use std::collections::VecDeque; pub struct LuaState { pub interp: Lua, pub exec: StashedExecutor, } 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: &VecDeque<&str>, ) -> 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 .iter() .map(|s| ctx.intern(s.as_bytes()).into()) .collect::>(), ), ); Ok(()) }) .map_err(|err| format!("{}", err))?; self.interp .execute::<()>(&self.exec) .map_err(|err| format!("{}", err)) } } 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"))?; }; } register_command!(echo); register_command!(echo_frame); register_command!(echo_frame_raw); register_command!(connect_mud); register_command!(delete_mud); register_command!(close_mud); register_command!(sendmud_raw); register_command!(hsplit); register_command!(panel_merge); 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 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); register_handler!(mudoutput_line); register_handler!(mudoutput_prompt); register_handler!(mudoutput_will); register_handler!(mudoutput_wont); register_handler!(mudoutput_do); register_handler!(mudoutput_dont); register_handler!(mudoutput_subnegotiation); macro_rules! register_nop_handler { ($sym: ident) => { handlers_table .set( ctx, ctx.intern_static(stringify!($sym).as_bytes()), lua_nop(ctx), ) .map_err(|_| Error::msg("Can't add handler"))?; }; } register_nop_handler!(mudoutput_break); register_nop_handler!(mudoutput_sync); register_nop_handler!(mudoutput_interrupt); register_nop_handler!(mudoutput_abort_output); register_nop_handler!(mudoutput_areyouthere); Ok(()) }); Ok(()) } pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> { Callback::from_fn(&ctx, |_ctx, _ex, _stack| { Ok(piccolo::CallbackReturn::Return) }) }