Upgrade to latest (by rev) Lua / gc-arena

This commit is contained in:
Condorra 2024-09-12 20:42:41 +10:00
parent 243132eeec
commit 1015bcb4ad
9 changed files with 551 additions and 156 deletions

9
Cargo.lock generated
View File

@ -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",

View File

@ -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
View File

@ -0,0 +1 @@
edition = "2021"

View File

@ -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,11 +94,7 @@ pub fn install_lua_globals(
global_memo: &GlobalMemoCell,
global_layout: UseStateSetter<GlobalLayoutCell>,
) -> Result<(), String> {
global_memo
.lua_engine
.borrow_mut()
.interp
.try_enter(|ctx| {
global_memo.lua_engine.borrow_mut().interp.try_enter(|ctx| {
let cmd_table = Table::new(&ctx);
macro_rules! register_command {
($sym: ident) => {
@ -123,26 +118,14 @@ pub fn install_lua_globals(
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"))?;
ctx.set_global("commands", cmd_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"))?;
ctx.set_global("info", info_table);
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"))?;
ctx.set_global("muds", muds_table);
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"))?;
ctx.set_global("handlers", handlers_table);
macro_rules! register_handler {
($sym: ident) => {
handlers_table
@ -155,10 +138,40 @@ pub fn install_lua_globals(
};
}
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(())
})
.map_err(|e| e.to_string())?;
});
Ok(())
}
pub fn lua_nop(ctx: Context<'_>) -> Callback<'_> {
Callback::from_fn(&ctx, |_ctx, _ex, _stack| {
Ok(piccolo::CallbackReturn::Return)
})
}

View File

@ -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,

View File

@ -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)]))
}
Ok(piccolo::CallbackReturn::Call {
function: echo,
then: None,
})
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))],
)),
}
}
}
}
})
}
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))
}

View File

@ -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
View 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()))
)
);
}
}

View File

@ -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(),
}
})
);
}
}