Upgrade to latest (by rev) Lua / gc-arena
This commit is contained in:
parent
243132eeec
commit
1015bcb4ad
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -246,8 +246,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gc-arena"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cd70cf88a32937834aae9614ff2569b5d9467fa0c42c5d7762fd94a8de88266"
|
||||
source = "git+https://github.com/kyren/gc-arena.git?rev=5a7534b883b703f23cfb8c3cfdf033460aa77ea9#5a7534b883b703f23cfb8c3cfdf033460aa77ea9"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"gc-arena-derive",
|
||||
@ -258,8 +257,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gc-arena-derive"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c612a69f5557a11046b77a7408d2836fe77077f842171cd211c5ef504bd3cddd"
|
||||
source = "git+https://github.com/kyren/gc-arena.git?rev=5a7534b883b703f23cfb8c3cfdf033460aa77ea9#5a7534b883b703f23cfb8c3cfdf033460aa77ea9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -813,8 +811,7 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
[[package]]
|
||||
name = "piccolo"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "003bf52de285e1ff1adcbc6572588db3849988ea660a2d55af3a2ffbc81f597f"
|
||||
source = "git+https://github.com/kyren/piccolo.git?rev=fcbaabc924292170d6549c55440ecbd0522b275a#fcbaabc924292170d6549c55440ecbd0522b275a"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
|
@ -9,7 +9,7 @@ edition = "2021"
|
||||
im = "15.1.0"
|
||||
itertools = "0.13.0"
|
||||
nom = "7.1.3"
|
||||
piccolo = "0.3.3"
|
||||
piccolo = { git = "https://github.com/kyren/piccolo.git", rev = "fcbaabc924292170d6549c55440ecbd0522b275a" }
|
||||
unicode-segmentation = "1.11.0"
|
||||
unicode-width = "0.1.13"
|
||||
wasm-bindgen = "0.2.92"
|
||||
@ -21,4 +21,4 @@ console_error_panic_hook = "0.1.7"
|
||||
anyhow = "1.0.86"
|
||||
serde = "1.0.209"
|
||||
serde_json = "1.0.127"
|
||||
gc-arena = "0.5.3"
|
||||
gc-arena = { git = "https://github.com/kyren/gc-arena.git", rev = "5a7534b883b703f23cfb8c3cfdf033460aa77ea9" }
|
||||
|
1
rustfmt.toml
Normal file
1
rustfmt.toml
Normal file
@ -0,0 +1 @@
|
||||
edition = "2021"
|
@ -1,8 +1,8 @@
|
||||
use self::{frames::*, muds::*};
|
||||
use anyhow::Error;
|
||||
use piccolo::{
|
||||
Closure, Executor, FromValue, Function, IntoValue, Lua, StashedExecutor, StaticError, Table,
|
||||
Value, Variadic,
|
||||
Callback, Closure, Context, Executor, ExternError, FromValue, Function, Lua, StashedExecutor,
|
||||
Table, Value, Variadic,
|
||||
};
|
||||
use yew::UseStateSetter;
|
||||
|
||||
@ -26,9 +26,9 @@ impl LuaState {
|
||||
Ok(LuaState { interp, exec })
|
||||
}
|
||||
|
||||
fn try_set_current_frame(&mut self, frame: &TermFrame) -> Result<(), StaticError> {
|
||||
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(ctx.intern_static(b"info")))?;
|
||||
let info_table = Table::from_value(ctx, ctx.get_global("info")?)?;
|
||||
info_table.set(
|
||||
ctx,
|
||||
ctx.intern_static(b"current_frame"),
|
||||
@ -65,9 +65,8 @@ impl LuaState {
|
||||
) -> Result<(), String> {
|
||||
self.interp
|
||||
.try_enter(|ctx| {
|
||||
let commands =
|
||||
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?;
|
||||
let command_value: Value = commands.get(ctx, ctx.intern(command.as_bytes()));
|
||||
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"))?;
|
||||
}
|
||||
@ -95,70 +94,84 @@ 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"))?;
|
||||
};
|
||||
}
|
||||
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(ctx.intern_static(b"commands").into_value(ctx), cmd_table)
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::msg("Can't set commands key"))?;
|
||||
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 info_table = Table::new(&ctx);
|
||||
ctx.set_global(ctx.intern_static(b"info").into_value(ctx), info_table)
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::msg("Can't set info key"))?;
|
||||
let muds_table = Table::new(&ctx);
|
||||
ctx.set_global(ctx.intern_static(b"muds").into_value(ctx), muds_table)
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::msg("Can't set muds key"))?;
|
||||
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);
|
||||
|
||||
let handlers_table = Table::new(&ctx);
|
||||
ctx.set_global(
|
||||
ctx.intern_static(b"handlers").into_value(ctx),
|
||||
handlers_table,
|
||||
)
|
||||
.map(|_| ())
|
||||
.map_err(|_| Error::msg("Can't set handlers key"))?;
|
||||
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);
|
||||
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"))?;
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| e.to_string())?;
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
@ -16,7 +16,12 @@ pub fn echo_frame_raw<'gc, 'a>(
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let frame: TermFrame = try_unwrap_frame(ctx, &stack.pop_front())?;
|
||||
let frame: TermFrame = try_unwrap_frame(
|
||||
ctx,
|
||||
&stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing frame"))?,
|
||||
)?;
|
||||
let message: piccolo::String = stack.from_front(ctx)?;
|
||||
let message_str = str::from_utf8(message.as_bytes())
|
||||
.map_err(|_| "Expected message to echo to be UTF-8.".into_value(ctx))?;
|
||||
@ -31,14 +36,15 @@ pub fn echo_frame<'gc>(
|
||||
_global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let commands: Table<'gc> =
|
||||
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?;
|
||||
let function = Function::from_value(ctx, commands.get(ctx, "echo_frame_raw"))?;
|
||||
let frame_no: Value = stack.pop_front();
|
||||
let commands: Table<'gc> = ctx.get_global("commands")?;
|
||||
let function: Function = commands.get(ctx, "echo_frame_raw")?;
|
||||
let frame_no: Value = stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing frame number"))?;
|
||||
let all_parts: Vec<String> = stack
|
||||
.consume::<Variadic<Vec<Value>>>(ctx)?
|
||||
.into_iter()
|
||||
.map(|v| format!("{}", v))
|
||||
.map(|v| format!("{:?}", v))
|
||||
.collect();
|
||||
stack.push_front(frame_no);
|
||||
let message = ctx.intern((all_parts.join(" ") + "\r\n").as_bytes());
|
||||
@ -56,11 +62,10 @@ pub fn echo<'gc>(
|
||||
_global_layout: &UseStateSetter<GlobalLayoutCell>,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let commands: Table<'gc> =
|
||||
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?;
|
||||
let function = Function::from_value(ctx, commands.get(ctx, "echo_frame"))?;
|
||||
let info: Table<'gc> = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"info")))?;
|
||||
let cur_frame = info.get(ctx, ctx.intern_static(b"current_frame"));
|
||||
let commands: Table<'gc> = ctx.get_global("commands")?;
|
||||
let function: Function = commands.get(ctx, "echo_frame")?;
|
||||
let info: Table<'gc> = ctx.get_global("info")?;
|
||||
let cur_frame: Value = info.get(ctx, ctx.intern_static(b"current_frame"))?;
|
||||
stack.push_front(cur_frame);
|
||||
Ok(piccolo::CallbackReturn::Call {
|
||||
function,
|
||||
|
@ -1,6 +1,9 @@
|
||||
use anyhow::Error;
|
||||
use gc_arena::{Gc, Rootable};
|
||||
use piccolo::{self, Callback, Context, FromValue, Function, Table, UserData, Value};
|
||||
use piccolo::{
|
||||
self, Callback, CallbackReturn, Context, FromValue, Function, IntoValue, StashedValue, Table,
|
||||
UserData, Value,
|
||||
};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::console;
|
||||
use yew::UseStateSetter;
|
||||
@ -8,6 +11,7 @@ use yew::UseStateSetter;
|
||||
use crate::{
|
||||
command_handler::debrace,
|
||||
id_intern::{intern_id, unintern_id},
|
||||
telnet::{parse_telnet_buf, TelnetOutput},
|
||||
websocket::{connect_websocket, send_message_to_mud, WebSocketId},
|
||||
GlobalLayoutCell, GlobalMemoCell,
|
||||
};
|
||||
@ -19,7 +23,7 @@ fn try_unwrap_socketid<'gc>(
|
||||
value: &Value<'gc>,
|
||||
) -> Result<WebSocketId, piccolo::Error<'gc>> {
|
||||
let value = if let Ok(sockname) = String::from_value(ctx, *value) {
|
||||
let ret = Table::from_value(ctx, ctx.get_global("muds"))?.get(ctx, sockname);
|
||||
let ret: Value<'gc> = ctx.get_global::<Table<'gc>>("muds")?.get(ctx, sockname)?;
|
||||
if ret.is_nil() {
|
||||
Err(Error::msg(
|
||||
"Could not find a MUD connection with that name.",
|
||||
@ -30,7 +34,7 @@ fn try_unwrap_socketid<'gc>(
|
||||
*value
|
||||
};
|
||||
Ok(UserData::from_value(ctx, value)?
|
||||
.downcast::<Rootable!['gcb => Gc<'gcb, WebSocketId>]>()?
|
||||
.downcast::<Rootable![Gc<'_, WebSocketId>]>()?
|
||||
.as_ref()
|
||||
.clone())
|
||||
}
|
||||
@ -43,9 +47,8 @@ pub fn handle_websocket_output(
|
||||
engine
|
||||
.interp
|
||||
.try_enter(|ctx| {
|
||||
let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?;
|
||||
let input_fn =
|
||||
Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudoutput")))?;
|
||||
let handlers: Table = ctx.get_global("handlers")?;
|
||||
let input_fn: Function = handlers.get(ctx, ctx.intern_static(b"mudoutput"))?;
|
||||
ctx.fetch(&engine.exec).restart(
|
||||
ctx,
|
||||
input_fn,
|
||||
@ -77,9 +80,8 @@ pub fn handle_websocket_close(socket: &WebSocketId, engine: &mut LuaState) -> Re
|
||||
engine
|
||||
.interp
|
||||
.try_enter(|ctx| {
|
||||
let handlers = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"handlers")))?;
|
||||
let input_fn =
|
||||
Function::from_value(ctx, handlers.get(ctx, ctx.intern_static(b"mudclose")))?;
|
||||
let handlers: Table = ctx.get_global("handlers")?;
|
||||
let input_fn: Function = handlers.get(ctx, ctx.intern_static(b"mudclose"))?;
|
||||
ctx.fetch(&engine.exec).restart(
|
||||
ctx,
|
||||
input_fn,
|
||||
@ -111,7 +113,12 @@ pub(super) fn sendmud_raw<'gc>(
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let mud: WebSocketId = try_unwrap_socketid(ctx, &stack.pop_front())?;
|
||||
let mud: WebSocketId = try_unwrap_socketid(
|
||||
ctx,
|
||||
&stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing MUD argument"))?,
|
||||
)?;
|
||||
let msg: piccolo::String = stack.from_front(ctx)?;
|
||||
send_message_to_mud(&mud, msg.as_bytes(), &global_memo)?;
|
||||
Ok(piccolo::CallbackReturn::Return)
|
||||
@ -136,8 +143,8 @@ pub(super) fn connect_mud<'gc>(
|
||||
};
|
||||
let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into();
|
||||
|
||||
let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?;
|
||||
if !muds.get_value(name).is_nil() {
|
||||
let muds: Table = ctx.get_global("muds")?;
|
||||
if !muds.get_value(ctx, name).is_nil() {
|
||||
Err(Error::msg(
|
||||
"Attempt to create MUD connection using name that's already taken",
|
||||
))?
|
||||
@ -170,7 +177,9 @@ pub(super) fn close_mud<'gc>(
|
||||
) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
let mud_value: Value<'gc> = stack.pop_front();
|
||||
let mud_value: Value<'gc> = stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing MUD value argument"))?;
|
||||
let socket_id = try_unwrap_socketid(ctx, &mud_value)?;
|
||||
match global_memo.ws_registry.borrow_mut().get_mut(&socket_id) {
|
||||
None => Err(Error::msg("That MUD connection doesn't exist."))?,
|
||||
@ -197,8 +206,8 @@ pub(super) fn delete_mud<'gc>(
|
||||
let name: String = stack.from_front(ctx)?;
|
||||
let name: Value<'gc> = ctx.intern(debrace(&name).as_bytes()).into();
|
||||
|
||||
let muds = Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?;
|
||||
let mud_value = muds.get_value(name);
|
||||
let muds: Table = ctx.get_global("muds")?;
|
||||
let mud_value = muds.get_value(ctx, name);
|
||||
if mud_value.is_nil() {
|
||||
Err(Error::msg(
|
||||
"Attempt to delete MUD connection that wasn't found",
|
||||
@ -208,7 +217,7 @@ pub(super) fn delete_mud<'gc>(
|
||||
let socket_id = try_unwrap_socketid(ctx, &mud_value)?;
|
||||
|
||||
// Delete the MUD data if possible.
|
||||
let _ = muds.set_value(&ctx, mud_value, Value::Nil);
|
||||
let _ = muds.set_raw(&ctx, mud_value, Value::Nil)?;
|
||||
for (k, v) in muds.iter() {
|
||||
match UserData::from_value(ctx, v) {
|
||||
Err(_) => continue,
|
||||
@ -218,7 +227,7 @@ pub(super) fn delete_mud<'gc>(
|
||||
_ => {}
|
||||
},
|
||||
}
|
||||
let _ = muds.set_value(&ctx, k, Value::Nil);
|
||||
let _ = muds.set_raw(&ctx, k, Value::Nil);
|
||||
}
|
||||
unintern_id(ctx, &socket_id);
|
||||
match global_memo.ws_registry.borrow_mut().remove(&socket_id) {
|
||||
@ -231,28 +240,124 @@ pub(super) fn delete_mud<'gc>(
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, global_memo: &GlobalMemoCell) -> Callback<'gc> {
|
||||
let global_memo = global_memo.clone();
|
||||
pub(super) fn mudoutput<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |ctx, _ex, mut stack| {
|
||||
// Temporary hack for testing... alias to echo
|
||||
let echo = Function::from_value(
|
||||
let mud: Value<'gc> = stack
|
||||
.pop_front()
|
||||
.ok_or_else(|| anyhow::Error::msg("Missing argument to mudoutput"))?;
|
||||
let output: &'gc [u8] = stack.from_front::<piccolo::String<'gc>>(ctx)?.as_bytes();
|
||||
|
||||
let conntab: Table<'gc> = Table::from_value(
|
||||
ctx,
|
||||
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"commands")))?
|
||||
.get(ctx, ctx.intern_static(b"echo")),
|
||||
Table::from_value(ctx, ctx.get_global("muds")?)?.get_value(ctx, mud),
|
||||
)?;
|
||||
|
||||
let mud: Value<'gc> = stack.pop_front();
|
||||
let output: piccolo::String<'gc> = stack.from_front(ctx)?;
|
||||
let buf: &'gc [u8] =
|
||||
piccolo::String::from_value(ctx, conntab.get(ctx, ctx.intern_static(b"buffer"))?)?
|
||||
.as_bytes();
|
||||
let mut cur_buf: Vec<u8> = [buf, output].concat();
|
||||
|
||||
let conntab =
|
||||
Table::from_value(ctx, ctx.get_global(ctx.intern_static(b"muds")))?.get_value(mud);
|
||||
if conntab.is_nil() {
|
||||
Err(Error::msg("Received input from MUD not in muds table."))?
|
||||
let handlers = Table::from_value(ctx, ctx.get_global("handlers")?)?;
|
||||
|
||||
let mut fns: Vec<(&'static [u8], Vec<StashedValue>)> = vec![];
|
||||
loop {
|
||||
match parse_telnet_buf(&cur_buf) {
|
||||
(new_buf, None) => {
|
||||
conntab.set(ctx, ctx.intern_static(b"buffer"), ctx.intern(&new_buf))?;
|
||||
|
||||
let seq = piccolo::async_sequence(&ctx, |locals, seq| async {
|
||||
Ok(piccolo::SequenceReturn::Return)
|
||||
});
|
||||
|
||||
return Ok(piccolo::CallbackReturn::Sequence(seq));
|
||||
}
|
||||
(new_buf, Some(cmd)) => {
|
||||
cur_buf = new_buf;
|
||||
match cmd {
|
||||
TelnetOutput::Line(l) => fns.push((
|
||||
b"mudoutput_line",
|
||||
vec![ctx.stash(mud), ctx.stash(ctx.intern(&l).into_value(ctx))],
|
||||
)),
|
||||
TelnetOutput::Prompt(p) => fns.push((
|
||||
b"mudoutput_prompt",
|
||||
vec![ctx.stash(mud), ctx.stash(ctx.intern(&p).into_value(ctx))],
|
||||
)),
|
||||
TelnetOutput::Nop => {}
|
||||
TelnetOutput::Break => fns.push((b"mudoutput_break", vec![ctx.stash(mud)])),
|
||||
TelnetOutput::Sync => fns.push((b"mudoutput_sync", vec![ctx.stash(mud)])),
|
||||
TelnetOutput::Interrupt => {
|
||||
fns.push((b"mudoutput_interrupt", vec![ctx.stash(mud)]))
|
||||
}
|
||||
|
||||
TelnetOutput::AbortOutput => {
|
||||
fns.push((b"mudoutput_abort_output", vec![ctx.stash(mud)]))
|
||||
}
|
||||
TelnetOutput::AreYouThere => {
|
||||
fns.push((b"mudoutput_areyouthere", vec![ctx.stash(mud)]))
|
||||
}
|
||||
TelnetOutput::Will(v) => fns.push((
|
||||
b"mudoutput_will",
|
||||
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
|
||||
)),
|
||||
TelnetOutput::Wont(v) => fns.push((
|
||||
b"mudoutput_wont",
|
||||
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
|
||||
)),
|
||||
TelnetOutput::Do(v) => fns.push((
|
||||
b"mudoutput_do",
|
||||
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
|
||||
)),
|
||||
TelnetOutput::Dont(v) => fns.push((
|
||||
b"mudoutput_dont",
|
||||
vec![ctx.stash(mud), ctx.stash(v.into_value(ctx))],
|
||||
)),
|
||||
TelnetOutput::Subnegotiation(t) => fns.push((
|
||||
b"mudoutput_subnegotiation",
|
||||
vec![ctx.stash(mud), ctx.stash(t.into_value(ctx))],
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(piccolo::CallbackReturn::Call {
|
||||
function: echo,
|
||||
then: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn mudoutput_line<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
pub(super) fn mudoutput_prompt<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
pub(super) fn mudoutput_will<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
pub(super) fn mudoutput_wont<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
pub(super) fn mudoutput_do<'gc>(ctx: Context<'gc>, _global_memo: &GlobalMemoCell) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
pub(super) fn mudoutput_dont<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
pub(super) fn mudoutput_subnegotiation<'gc>(
|
||||
ctx: Context<'gc>,
|
||||
_global_memo: &GlobalMemoCell,
|
||||
) -> Callback<'gc> {
|
||||
Callback::from_fn(&ctx, move |_ctx, _ex, _stack| Ok(CallbackReturn::Return))
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ pub mod lineengine;
|
||||
pub mod lua_engine;
|
||||
pub mod parsing;
|
||||
pub mod split_panel;
|
||||
pub mod telnet;
|
||||
pub mod term_split;
|
||||
pub mod term_view;
|
||||
pub mod websocket;
|
||||
|
288
src/telnet.rs
Normal file
288
src/telnet.rs
Normal file
@ -0,0 +1,288 @@
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::console;
|
||||
|
||||
#[derive(Eq, PartialEq, Debug)]
|
||||
pub enum TelnetOutput {
|
||||
Line(Vec<u8>),
|
||||
Prompt(Vec<u8>), // Like a line but without a newline.
|
||||
Nop,
|
||||
Break,
|
||||
Sync,
|
||||
Interrupt,
|
||||
AbortOutput,
|
||||
AreYouThere,
|
||||
Will(u8),
|
||||
Wont(u8),
|
||||
Do(u8),
|
||||
Dont(u8),
|
||||
Subnegotiation(Vec<u8>),
|
||||
}
|
||||
|
||||
const IAC: u8 = 255;
|
||||
const NOP: u8 = 241;
|
||||
const DATA_MARK: u8 = 242;
|
||||
const BREAK: u8 = 243;
|
||||
const INTERRUPT: u8 = 244;
|
||||
const ABORT: u8 = 245;
|
||||
const AYT: u8 = 246;
|
||||
const ERASECHAR: u8 = 247;
|
||||
const ERASELINE: u8 = 248;
|
||||
const GOAHEAD: u8 = 249;
|
||||
const EOR: u8 = 239;
|
||||
const STARTSUB: u8 = 250;
|
||||
const ENDSUB: u8 = 240;
|
||||
const WILL: u8 = 251;
|
||||
const WONT: u8 = 252;
|
||||
const DO: u8 = 253;
|
||||
const DONT: u8 = 254;
|
||||
|
||||
pub fn parse_telnet_buf(input: &[u8]) -> (Vec<u8>, Option<TelnetOutput>) {
|
||||
let mut ptr: &[u8] = input;
|
||||
let mut textbuf: Vec<u8> = vec![];
|
||||
loop {
|
||||
match ptr.first() {
|
||||
None => return (textbuf.to_owned(), None),
|
||||
Some(b'\n') => {
|
||||
textbuf.push(b'\n');
|
||||
return (ptr[1..].to_owned(), Some(TelnetOutput::Line(textbuf)));
|
||||
}
|
||||
Some(&IAC) => {
|
||||
ptr = &ptr[1..];
|
||||
match ptr.first() {
|
||||
None => {
|
||||
textbuf.push(IAC);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(&NOP) => {}
|
||||
Some(&DATA_MARK) => {}
|
||||
Some(&BREAK) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::Break));
|
||||
}
|
||||
Some(&INTERRUPT) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::Interrupt));
|
||||
}
|
||||
Some(&ABORT) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::AbortOutput));
|
||||
}
|
||||
Some(&AYT) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::AreYouThere));
|
||||
}
|
||||
Some(&ERASECHAR) => {
|
||||
if !textbuf.is_empty() {
|
||||
textbuf = textbuf[0..textbuf.len() - 1].to_owned();
|
||||
}
|
||||
}
|
||||
Some(&ERASELINE) => {
|
||||
textbuf = vec![];
|
||||
}
|
||||
Some(&GOAHEAD) => {
|
||||
if textbuf.len() > 1 {
|
||||
return (ptr[1..].to_owned(), Some(TelnetOutput::Prompt(textbuf)));
|
||||
}
|
||||
}
|
||||
Some(&EOR) => {
|
||||
if textbuf.len() > 1 {
|
||||
return (ptr[1..].to_owned(), Some(TelnetOutput::Prompt(textbuf)));
|
||||
}
|
||||
}
|
||||
Some(&WILL) => {
|
||||
ptr = &ptr[1..];
|
||||
match ptr.first() {
|
||||
None => {
|
||||
textbuf.push(IAC);
|
||||
textbuf.push(WILL);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(c) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::Will(*c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(&WONT) => {
|
||||
ptr = &ptr[1..];
|
||||
match ptr.first() {
|
||||
None => {
|
||||
textbuf.push(IAC);
|
||||
textbuf.push(WONT);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(c) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::Wont(*c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(&DO) => {
|
||||
ptr = &ptr[1..];
|
||||
match ptr.first() {
|
||||
None => {
|
||||
textbuf.push(IAC);
|
||||
textbuf.push(DO);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(c) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::Do(*c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(&DONT) => {
|
||||
ptr = &ptr[1..];
|
||||
match ptr.first() {
|
||||
None => {
|
||||
textbuf.push(IAC);
|
||||
textbuf.push(DONT);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(c) => {
|
||||
textbuf.extend_from_slice(&ptr[1..]);
|
||||
return (textbuf, Some(TelnetOutput::Dont(*c)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(&STARTSUB) => {
|
||||
match (1..ptr.len() - 1).find(|i| ptr[*i] == IAC && ptr[*i + 1] == ENDSUB) {
|
||||
None => {
|
||||
// Including the STARTSUB...
|
||||
textbuf.push(IAC);
|
||||
textbuf.extend_from_slice(ptr);
|
||||
return (textbuf, None);
|
||||
}
|
||||
Some(end_idx) => {
|
||||
textbuf.extend_from_slice(&ptr[end_idx + 2..]);
|
||||
return (
|
||||
textbuf,
|
||||
Some(TelnetOutput::Subnegotiation(ptr[1..end_idx].to_owned())),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(c) => {
|
||||
// Completely unexpected command. Warn and ignore.
|
||||
console::log_1(&JsValue::from_str(&format!(
|
||||
"Received unknown IAC command {}, assuming single byte and ignoring.",
|
||||
c
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(c) => textbuf.push(*c),
|
||||
}
|
||||
ptr = &ptr[1..];
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_telnet_works() {
|
||||
assert_eq!(parse_telnet_buf(b"Hello"), (b"Hello".to_vec(), None));
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello\r\n"),
|
||||
(
|
||||
b"".to_vec(),
|
||||
Some(TelnetOutput::Line(b"Hello\r\n".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello\r\nWorld"),
|
||||
(
|
||||
b"World".to_vec(),
|
||||
Some(TelnetOutput::Line(b"Hello\r\n".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello> \xff\xf9World"), // Go Ahead after prompt.
|
||||
(
|
||||
b"World".to_vec(),
|
||||
Some(TelnetOutput::Prompt(b"Hello> ".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello> \xff\xefWorld"), // End Of Record after prompt.
|
||||
(
|
||||
b"World".to_vec(),
|
||||
Some(TelnetOutput::Prompt(b"Hello> ".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xf1World\r\nWoo!"), // NOP in the middle.
|
||||
(
|
||||
b"Woo!".to_vec(),
|
||||
Some(TelnetOutput::Line(b"Hello World\r\n".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xf2World\r\nWoo!"), // DATA MARK in the middle.
|
||||
(
|
||||
b"Woo!".to_vec(),
|
||||
Some(TelnetOutput::Line(b"Hello World\r\n".to_vec()))
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xf3World\r\nWoo!"), // BREAK in the middle.
|
||||
(b"Hello World\r\nWoo!".to_vec(), Some(TelnetOutput::Break))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xf4World\r\nWoo!"), // INTERRUPT in the middle.
|
||||
(
|
||||
b"Hello World\r\nWoo!".to_vec(),
|
||||
Some(TelnetOutput::Interrupt)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff"), // Incomplete IAC command.
|
||||
(b"Hello \xff".to_vec(), None)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xf5World!"), // Abort
|
||||
(b"Hello World!".to_vec(), Some(TelnetOutput::AbortOutput))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xf6World!"), // Are You There
|
||||
(b"Hello World!".to_vec(), Some(TelnetOutput::AreYouThere))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello B\xff\xf7World!"), // Erase Char
|
||||
(b"Hello World!".to_vec(), None)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Whoops \xff\xf8Hello World!"), // Erase Line
|
||||
(b"Hello World!".to_vec(), None)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfb\x01World!"), // Will
|
||||
(b"Hello World!".to_vec(), Some(TelnetOutput::Will(1)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfc\x02World!"), // Wont
|
||||
(b"Hello World!".to_vec(), Some(TelnetOutput::Wont(2)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfd\x03World!"), // Do
|
||||
(b"Hello World!".to_vec(), Some(TelnetOutput::Do(3)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfe\x04World!"), // Dont
|
||||
(b"Hello World!".to_vec(), Some(TelnetOutput::Dont(4)))
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfablah"), // Partial subnegotiation
|
||||
(b"Hello \xff\xfablah".to_vec(), None)
|
||||
);
|
||||
assert_eq!(
|
||||
parse_telnet_buf(b"Hello \xff\xfablah\xff\xf0World!"), // Partial subnegotiation
|
||||
(
|
||||
b"Hello World!".to_vec(),
|
||||
Some(TelnetOutput::Subnegotiation(b"blah".to_vec()))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -216,36 +216,30 @@ mod tests {
|
||||
#[test]
|
||||
fn modify_at_pathstr_works() {
|
||||
use TermSplit::*;
|
||||
let mut t = Term {
|
||||
let t = Term {
|
||||
frame: TermFrame(1),
|
||||
};
|
||||
assert_eq!(
|
||||
t.modify_at_pathstr("", |v| {
|
||||
*v = Term {
|
||||
t.modify_at_pathstr("", |_v| {
|
||||
Ok(Term {
|
||||
frame: TermFrame(2),
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
t,
|
||||
Term {
|
||||
frame: TermFrame(2)
|
||||
}
|
||||
Ok(Term {
|
||||
frame: TermFrame(2),
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
t.modify_at_pathstr("tlr", |v| {
|
||||
*v = Term {
|
||||
t.modify_at_pathstr("tlr", |_v| {
|
||||
Ok(Term {
|
||||
frame: TermFrame(2),
|
||||
};
|
||||
Ok(())
|
||||
})
|
||||
}),
|
||||
Err("In split path, found trailing junk tlr after addressing terminal".to_owned())
|
||||
);
|
||||
|
||||
let mut t = Vertical {
|
||||
let t = Vertical {
|
||||
top: Horizontal {
|
||||
left: Horizontal {
|
||||
left: Term {
|
||||
@ -277,26 +271,17 @@ mod tests {
|
||||
.into(),
|
||||
};
|
||||
assert_eq!(
|
||||
t.modify_at_pathstr("tlr", |v| {
|
||||
*v = Term {
|
||||
t.modify_at_pathstr("tlr", |_v| {
|
||||
Ok(Term {
|
||||
frame: TermFrame(2),
|
||||
};
|
||||
Ok(())
|
||||
}),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
t.modify_at_pathstr("bb", |v| {
|
||||
*v = Term {
|
||||
})
|
||||
})
|
||||
.and_then(|t| t.modify_at_pathstr("bb", |_v| {
|
||||
Ok(Term {
|
||||
frame: TermFrame(3),
|
||||
};
|
||||
Ok(())
|
||||
}),
|
||||
Ok(())
|
||||
);
|
||||
assert_eq!(
|
||||
t,
|
||||
Vertical {
|
||||
})
|
||||
})),
|
||||
Ok(Vertical {
|
||||
top: Horizontal {
|
||||
left: Horizontal {
|
||||
left: Term {
|
||||
@ -326,7 +311,7 @@ mod tests {
|
||||
.into(),
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user