diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index f4f89cc..3fa6c8d 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -12,6 +12,7 @@ on: env: CARGO_TERM_COLOR: always + RUSTFLAGS: "-Dwarnings" jobs: build-and-test: @@ -21,8 +22,12 @@ jobs: steps: - name: Install Emacs run: sudo apt-get install -y emacs - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + - name: Formatting + run: cargo fmt --check - name: Build run: cargo build --verbose + - name: Linting + run: cargo clippy --all-targets --all-features - name: Run tests run: cargo test --verbose -- --nocapture diff --git a/examples/native-json-parser.rs b/examples/native-json-parser.rs index 188e268..15613bf 100644 --- a/examples/native-json-parser.rs +++ b/examples/native-json-parser.rs @@ -1,4 +1,4 @@ -use emacs::{defun, Env, Result, Value, IntoLisp}; +use emacs::{defun, Env, IntoLisp, Result, Value}; use serde_json as json; // Emacs won't load the module without this. @@ -11,37 +11,37 @@ fn init(env: &Env) -> Result> { } fn json_to_lisp<'a>(env: &'a Env, val: &json::Value) -> Result> { - match val { - &json::Value::Null | &json::Value::Bool(false) => - ().into_lisp(env), - &json::Value::Bool(true) => - true.into_lisp(env), - &json::Value::Number(ref num) => + match &val { + json::Value::Null | &json::Value::Bool(false) => ().into_lisp(env), + json::Value::Bool(true) => true.into_lisp(env), + json::Value::Number(num) => { if num.is_f64() { num.as_f64().unwrap().into_lisp(env) } else if num.is_i64() { num.as_i64().unwrap().into_lisp(env) } else { num.as_u64().unwrap().into_lisp(env) - }, - &json::Value::String(ref s) => - s.into_lisp(env), - &json::Value::Array(ref arr) => { - let vals = arr.iter().map(|x| json_to_lisp(env, x)).collect::>>()?; + } + } + json::Value::String(s) => s.into_lisp(env), + json::Value::Array(arr) => { + let vals = arr + .iter() + .map(|x| json_to_lisp(env, x)) + .collect::>>()?; env.vector(&vals) - }, - &json::Value::Object(ref map) => { + } + json::Value::Object(map) => { let mut vals: Vec> = Vec::new(); for (k, v) in map { vals.push(env.intern(&format!(":{}", k))?); vals.push(json_to_lisp(env, v)?); } env.list(&vals) - }, + } } } - #[defun] fn parse(env: &Env, s: String) -> Result> { let start = std::time::Instant::now(); diff --git a/src/app.rs b/src/app.rs index f140898..32e2972 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,15 +1,23 @@ -use std::sync::{mpsc, Arc, atomic::{AtomicI32, self}}; +use std::sync::{ + atomic::{self, AtomicI32}, + mpsc, Arc, +}; -use log::{warn, info, debug}; -use anyhow::{Result, Context}; +use anyhow::{Context, Result}; +use log::{debug, info, warn}; use serde_json as json; -use crate::{rpcio, bytecode::{self, BytecodeOptions}}; use crate::lsp_message::{LspRequest, LspResponse, LspResponseError}; +use crate::{ + bytecode::{self, BytecodeOptions}, + rpcio, +}; -fn process_channel_to_writer(channel_sub: mpsc::Receiver, - channel_counter: Option>, - writer: impl std::io::Write) -> Result<()> { +fn process_channel_to_writer( + channel_sub: mpsc::Receiver, + channel_counter: Option>, + writer: impl std::io::Write, +) -> Result<()> { let mut bufwriter = std::io::BufWriter::new(writer); for msg in channel_sub.iter() { if let Some(ref channel_counter) = channel_counter { @@ -24,23 +32,27 @@ const MAX_PENDING_MSG_COUNT: i32 = 128; // Read from client, write to server. // Or, if server is blocked, reject the request and fake reply when possible -fn process_client_reader(reader: impl std::io::Read, - server_channel_pub: mpsc::Sender, - server_channel_counter: Arc, - client_channel_pub: mpsc::Sender) -> Result<()> { +fn process_client_reader( + reader: impl std::io::Read, + server_channel_pub: mpsc::Sender, + server_channel_counter: Arc, + client_channel_pub: mpsc::Sender, +) -> Result<()> { let mut bufreader = std::io::BufReader::new(reader); loop { let msg = rpcio::rpc_read(&mut bufreader)?; if msg.is_empty() { - break + break; } if server_channel_counter.load(atomic::Ordering::Acquire) >= MAX_PENDING_MSG_COUNT { let lsp_request: LspRequest = json::from_str(&msg)?; // only cancel when it's not notification if !lsp_request.is_notification() { - warn!("Buffer full, rejecting request: {} (id={:?})", - lsp_request.method, lsp_request.id); + warn!( + "Buffer full, rejecting request: {} (id={:?})", + lsp_request.method, lsp_request.id + ); let resp = LspResponse { jsonrpc: lsp_request.jsonrpc, id: lsp_request.id.unwrap(), @@ -62,27 +74,32 @@ fn process_client_reader(reader: impl std::io::Read, Ok(()) } -fn process_server_reader(reader: impl std::io::Read, - channel_pub: mpsc::Sender, - bytecode_options: Option) -> Result<()> { +fn process_server_reader( + reader: impl std::io::Read, + channel_pub: mpsc::Sender, + bytecode_options: Option, +) -> Result<()> { let mut bufreader = std::io::BufReader::new(reader); loop { let msg = rpcio::rpc_read(&mut bufreader)?; if msg.is_empty() { - break + break; } if let Some(ref bytecode_options) = bytecode_options { let json_val = json::from_str(&msg)?; match bytecode::generate_bytecode_repl(&json_val, bytecode_options.clone()) { Ok(bytecode_str) => { - debug!("server->client: json {} bytes; converted to bytecode, {} bytes", - msg.len(), bytecode_str.len()); + debug!( + "server->client: json {} bytes; converted to bytecode, {} bytes", + msg.len(), + bytecode_str.len() + ); channel_pub.send(bytecode_str)?; - continue - }, + continue; + } Err(err) => { warn!("Failed to convert json to bytecode: {}", err); - }, + } } } debug!("server->client: json {} bytes; forward as-is", msg.len()); @@ -96,13 +113,18 @@ pub struct AppOptions { pub bytecode_options: Option, } -pub fn run_app_forever(client_reader: impl std::io::Read + Send + 'static, - client_writer: impl std::io::Write + Send + 'static, - mut server_cmd: std::process::Command, - options: AppOptions) -> Result { +pub fn run_app_forever( + client_reader: impl std::io::Read + Send + 'static, + client_writer: impl std::io::Write + Send + 'static, + mut server_cmd: std::process::Command, + options: AppOptions, +) -> Result { info!("Running server {:?}", server_cmd); if let Some(ref bytecode_options) = options.bytecode_options { - info!("Will convert server json to bytecode! bytecode options: {:?}", bytecode_options); + info!( + "Will convert server json to bytecode! bytecode options: {:?}", + bytecode_options + ); } else { info!("Bytecode disabled! Will forward server json as-is.") } @@ -149,9 +171,13 @@ pub fn run_app_forever(client_reader: impl std::io::Read + Send + 'static, std::thread::spawn(move || { debug!("Started client->server read thread"); process_client_reader( - client_reader, c2s_channel_pub, c2s_channel_counter, s2c_channel_pub) - .with_context(|| "Client->server read thread failed") - .unwrap(); + client_reader, + c2s_channel_pub, + c2s_channel_counter, + s2c_channel_pub, + ) + .with_context(|| "Client->server read thread failed") + .unwrap(); debug!("Finished client->server read thread"); }); diff --git a/src/bytecode.rs b/src/bytecode.rs index 796a88e..4be31fc 100644 --- a/src/bytecode.rs +++ b/src/bytecode.rs @@ -1,11 +1,10 @@ use std::collections::BTreeMap; use std::str::FromStr; -use anyhow::{Result, bail}; +use anyhow::{bail, Result}; use serde_json as json; use smallvec::smallvec; - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub enum LispObject { Symbol(String), @@ -13,7 +12,7 @@ pub enum LispObject { UnibyteStr(Vec), Str(String), Int(i64), - Float(String), // use string for Eq and Ord + Float(String), // use string for Eq and Ord Nil, T, Vector(Vec), @@ -22,12 +21,13 @@ pub enum LispObject { impl FromStr for LispObject { type Err = anyhow::Error; + #[allow(clippy::manual_strip)] fn from_str(s: &str) -> Result { if s == "nil" { Ok(Self::Nil) } else if s == "t" { Ok(Self::T) - } else if s.starts_with(":") { + } else if s.starts_with(':') { Ok(Self::Keyword(s[1..].to_string())) } else { bail!("Supported LispObject: {}", s) @@ -58,7 +58,7 @@ impl LispObject { } result.push('"'); result - }, + } LispObject::UnibyteStr(vec) => { let mut result = String::new(); result.reserve(vec.len() * 4 + 2); @@ -77,35 +77,40 @@ impl LispObject { 127 => result += "\\d", 27 => result += "\\e", // NOTE: do not use 0..=7 in this branch, because it takes one more byte than the next branch - 8..=26 => { // \^@ \^A \^B ... \^Z + 14..=26 => { + // \^@ \^A \^B ... \^Z result += &format!("\\^{}", (*c as u32 + 64) as u8 as char); - }, - 0..=7 | 27..=31 | 128..=255 | 34 | 92 => { // oct, for unprintable and '"' and '\\' + } + 0..=7 | 27..=31 | 128..=255 | 34 | 92 => { + // oct, for unprintable and '"' and '\\' let oct_s = format!("\\{:o}", *c as u32); if oct_s.len() < 4 { oct_escape_not_full = true; } result += &oct_s; - }, - _ => { // printable + } + _ => { + // printable // https://www.gnu.org/software/emacs/manual/html_node/elisp/Non_002dASCII-in-St if last_oct_escape_not_full && ('0'..='7').contains(&(*c as char)) { result += "\\ "; } result.push(*c as char); - }, + } } last_oct_escape_not_full = oct_escape_not_full; } result.push('"'); result - }, + } LispObject::Int(i) => i.to_string(), LispObject::Float(s) => s.clone(), LispObject::Nil => "nil".into(), LispObject::T => "t".into(), - LispObject::Vector(v) => - format!("[{}]", v.iter().map(|x| x.to_repl()).collect::>().join(" ")) + LispObject::Vector(v) => format!( + "[{}]", + v.iter().map(|x| x.to_repl()).collect::>().join(" ") + ), } } } @@ -122,8 +127,9 @@ const CV_TWO_LEVEL_IDX_BEGIN: u32 = CV_NORMAL_SLOT_COUNT; const CV_TWO_LEVEL_DATA_BEGIN: u32 = CV_NORMAL_SLOT_COUNT + CV_TWO_LEVEL_VECTOR_SIZE; #[allow(dead_code)] +#[derive(Copy, Clone)] enum Op { - PushConstant(u32), // support more than u16, will expand to multiple ops + PushConstant(u32), // support more than u16, will expand to multiple ops Call(u16), StackRef(u16), List(u8), @@ -137,59 +143,65 @@ enum Op { impl Op { fn get_stack_delta(&self) -> i32 { match self { - &Self::PushConstant(_) => 1, - &Self::Call(n) => -(n as i32 + 1) + 1, - &Self::StackRef(_) => 1, - &Self::List(n) => -(n as i32) + 1, - &Self::Discard => -1, - &Self::ASet => -3 + 1, - &Self::Add1 => 0, - &Self::Cons => -2 + 1, - &Self::Return => -1, + Self::PushConstant(_) => 1, + Self::Call(n) => -(*n as i32 + 1) + 1, + Self::StackRef(_) => 1, + Self::List(n) => -(*n as i32) + 1, + Self::Discard => -1, + Self::ASet => -3 + 1, + Self::Add1 => 0, + Self::Cons => -2 + 1, + Self::Return => -1, } } - fn to_code(&self) -> Result> { + fn to_code(self) -> Result> { match self { - &Self::PushConstant(v) if v < 64 => - Ok(smallvec![(192 + v) as u8]), - &Self::PushConstant(v) if v < CV_NORMAL_SLOT_COUNT => - Ok(smallvec![129, (v & 0xff) as u8, (v >> 8) as u8]), - &Self::PushConstant(v) if v < (CV_NORMAL_SLOT_COUNT + CV_TWO_LEVEL_VECTOR_SIZE * CV_TWO_LEVEL_VECTOR_SIZE) => { + Self::PushConstant(v) if v < 64 => Ok(smallvec![(192 + v) as u8]), + Self::PushConstant(v) if v < CV_NORMAL_SLOT_COUNT => { + Ok(smallvec![129, (v & 0xff) as u8, (v >> 8) as u8]) + } + Self::PushConstant(v) + if v < (CV_NORMAL_SLOT_COUNT + + CV_TWO_LEVEL_VECTOR_SIZE * CV_TWO_LEVEL_VECTOR_SIZE) => + { let mut result = smallvec![]; let two_level_i = (v - CV_NORMAL_SLOT_COUNT) / CV_TWO_LEVEL_VECTOR_SIZE; let two_level_j = (v - CV_NORMAL_SLOT_COUNT) % CV_TWO_LEVEL_VECTOR_SIZE; // get vector let index_for_i = two_level_i + CV_TWO_LEVEL_DATA_BEGIN; - result.extend_from_slice(&[129, (index_for_i & 0xff) as u8, (index_for_i >> 8) as u8]); + result.extend_from_slice(&[ + 129, + (index_for_i & 0xff) as u8, + (index_for_i >> 8) as u8, + ]); // get index let index_for_j = two_level_j + CV_TWO_LEVEL_IDX_BEGIN; - result.extend_from_slice(&[129, (index_for_j & 0xff) as u8, (index_for_j >> 8) as u8]); + result.extend_from_slice(&[ + 129, + (index_for_j & 0xff) as u8, + (index_for_j >> 8) as u8, + ]); // aref result.push(72); Ok(result) - }, - &Self::PushConstant(v) => - bail!("Too many constants! {}", v), - &Self::Call(v) if v <= 5 => - Ok(smallvec![(32 + v) as u8]), - &Self::Call(v) if v < (1 << 8) => - Ok(smallvec![(32 + 6) as u8, v as u8]), - &Self::Call(v) => - Ok(smallvec![(32 + 7) as u8, (v & 0xff) as u8, (v >> 8) as u8]), - &Self::StackRef(v) if (1..=4).contains(&v) => - Ok(smallvec![v as u8]), - &Self::StackRef(_) => unimplemented!(), - &Self::List(v) if v == 0 => unreachable!(), - &Self::List(v) if (1..=4).contains(&v) => Ok(smallvec![66 + v]), - &Self::List(v) => Ok(smallvec![175, v]), - &Self::Discard => Ok(smallvec![136]), - &Self::ASet => Ok(smallvec![73]), - &Self::Add1 => Ok(smallvec![84]), - &Self::Cons => Ok(smallvec![66]), - &Self::Return => Ok(smallvec![135]), + } + Self::PushConstant(v) => bail!("Too many constants! {}", v), + Self::Call(v) if v <= 5 => Ok(smallvec![(32 + v) as u8]), + Self::Call(v) if v < (1 << 8) => Ok(smallvec![(32 + 6) as u8, v as u8]), + Self::Call(v) => Ok(smallvec![(32 + 7) as u8, (v & 0xff) as u8, (v >> 8) as u8]), + Self::StackRef(v) if (1..=4).contains(&v) => Ok(smallvec![v as u8]), + Self::StackRef(_) => unimplemented!(), + Self::List(0) => unreachable!(), + Self::List(v) if (1..=4).contains(&v) => Ok(smallvec![66 + v]), + Self::List(v) => Ok(smallvec![175, v]), + Self::Discard => Ok(smallvec![136]), + Self::ASet => Ok(smallvec![73]), + Self::Add1 => Ok(smallvec![84]), + Self::Cons => Ok(smallvec![66]), + Self::Return => Ok(smallvec![135]), } } } @@ -224,7 +236,7 @@ struct BytecodeCompiler { options: BytecodeOptions, ops: Vec, - constants: BTreeMap, // (index, count) + constants: BTreeMap, // (index, count) } impl BytecodeCompiler { @@ -272,10 +284,14 @@ impl BytecodeCompiler { } } - fn compile_value_map_plist_or_alist(&mut self, map: &json::Map, alist: bool) { + fn compile_value_map_plist_or_alist( + &mut self, + map: &json::Map, + alist: bool, + ) { let list_len = if alist { map.len() } else { map.len() * 2 }; // see below - if list_len < (1 << 16) && list_len >= (1 << 8) { + if ((1 << 8)..(1 << 16)).contains(&list_len) { self.compile_constant_op(LispObject::Symbol("list".into())); } @@ -325,35 +341,35 @@ impl BytecodeCompiler { fn compile_value(&mut self, value: &json::Value) { match value { - &json::Value::Null => { + json::Value::Null => { self.compile_constant_op(self.options.null_value.clone()); - }, - &json::Value::Bool(false) => { + } + json::Value::Bool(false) => { self.compile_constant_op(self.options.false_value.clone()); - }, - &json::Value::Bool(true) => { + } + json::Value::Bool(true) => { self.compile_constant_op(LispObject::T); - }, - &json::Value::Number(ref num) => { + } + json::Value::Number(ref num) => { if num.is_f64() { self.compile_constant_op(LispObject::Float(num.to_string())); } else { self.compile_constant_op(LispObject::Int(num.as_i64().unwrap())); } - }, - &json::Value::String(ref s) => { + } + json::Value::String(ref s) => { self.compile_constant_op(LispObject::Str(s.clone())); - }, - &json::Value::Array(ref arr) => { - self.compile_value_array(&arr); - }, - &json::Value::Object(ref map) => { + } + json::Value::Array(ref arr) => { + self.compile_value_array(arr); + } + json::Value::Object(ref map) => { match self.options.object_type { - ObjectType::Plist => self.compile_value_map_plist_or_alist(&map, false), - ObjectType::Alist => self.compile_value_map_plist_or_alist(&map, true), - ObjectType::Hashtable => self.compile_value_map_hashtable(&map), + ObjectType::Plist => self.compile_value_map_plist_or_alist(map, false), + ObjectType::Alist => self.compile_value_map_plist_or_alist(map, true), + ObjectType::Hashtable => self.compile_value_map_hashtable(map), }; - }, + } } } @@ -368,18 +384,25 @@ impl BytecodeCompiler { let mut constants_array = self.constants.into_iter().collect::>(); constants_array.sort_by_key( // if count is same, still sort by the old idx, to increase locality - |(_, idx_and_count)| (-(idx_and_count.1 as i32), idx_and_count.0)); - let index_remap = constants_array.iter().enumerate() + |(_, idx_and_count)| (-(idx_and_count.1 as i32), idx_and_count.0), + ); + let index_remap = constants_array + .iter() + .enumerate() .map(|(new_idx, (_, idx_and_count))| (idx_and_count.0, new_idx as u32)) .collect::>(); - let mut constants_array = constants_array.into_iter().map(|(obj, _)| obj).collect::>(); + let mut constants_array = constants_array + .into_iter() + .map(|(obj, _)| obj) + .collect::>(); // rearrange constants let mut two_level_vectors: Vec = Vec::new(); // collect two level vectors from the end (reverse order) while constants_array.len() > CV_NORMAL_SLOT_COUNT as usize { let len = { - let remaining = (constants_array.len() - CV_NORMAL_SLOT_COUNT as usize) % CV_TWO_LEVEL_VECTOR_SIZE as usize; + let remaining = (constants_array.len() - CV_NORMAL_SLOT_COUNT as usize) + % CV_TWO_LEVEL_VECTOR_SIZE as usize; if remaining == 0 { CV_TWO_LEVEL_VECTOR_SIZE as usize } else { @@ -387,7 +410,10 @@ impl BytecodeCompiler { } }; let v = constants_array - .splice((constants_array.len() - len)..constants_array.len(), Vec::new()) + .splice( + (constants_array.len() - len)..constants_array.len(), + Vec::new(), + ) .collect(); two_level_vectors.push(LispObject::Vector(v)); } @@ -422,10 +448,12 @@ impl BytecodeCompiler { fn into_repl(self) -> Result { let (code, constants, max_stack_size) = self.into_bytecode()?; - Ok(format!("#[0 {} {} {}]", - LispObject::UnibyteStr(code).to_repl(), - LispObject::Vector(constants).to_repl(), - max_stack_size)) + Ok(format!( + "#[0 {} {} {}]", + LispObject::UnibyteStr(code).to_repl(), + LispObject::Vector(constants).to_repl(), + max_stack_size + )) } } @@ -439,7 +467,6 @@ pub fn generate_bytecode_repl(value: &json::Value, options: BytecodeOptions) -> compiler.into_repl() } - #[test] fn test_string_repl() { assert_eq!(LispObject::UnibyteStr("\x00".into()).to_repl(), r#""\0""#); diff --git a/src/lib.rs b/src/lib.rs index af4f2ce..d36d7bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ +pub mod app; pub mod bytecode; -pub mod rpcio; mod lsp_message; -pub mod app; +pub mod rpcio; diff --git a/src/lsp_message.rs b/src/lsp_message.rs index d14d7a5..4fcb8de 100644 --- a/src/lsp_message.rs +++ b/src/lsp_message.rs @@ -1,5 +1,5 @@ +use serde::{Deserialize, Serialize}; use serde_json as json; -use serde::{ Deserialize, Serialize }; // or notification #[derive(Serialize, Deserialize)] @@ -48,7 +48,7 @@ mod test { assert_eq!(req.jsonrpc, "2.0"); assert_eq!(req.id, Some(1)); assert_eq!(req.method, "initialize"); - assert_eq!(req.is_notification(), false); + assert!(!req.is_notification()); } #[test] @@ -61,7 +61,7 @@ mod test { let req: LspRequest = json::from_str(json_str).unwrap(); assert_eq!(req.id, None); assert_eq!(req.method, "initialized"); - assert_eq!(req.is_notification(), true); + assert!(req.is_notification()); } #[test] @@ -76,6 +76,9 @@ mod test { }), }; let resp_str = json::to_string(&resp).unwrap(); - assert_eq!(resp_str, r#"{"jsonrpc":"2.0","id":1,"result":null,"error":{"code":123,"message":"asdf"}}"#); + assert_eq!( + resp_str, + r#"{"jsonrpc":"2.0","id":1,"result":null,"error":{"code":123,"message":"asdf"}}"# + ); } } diff --git a/src/main.rs b/src/main.rs index 19d3b0a..f5cc66c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,6 @@ use clap::Parser; use emacs_lsp_booster::app; use emacs_lsp_booster::bytecode; - #[derive(Parser)] #[command(long_about = None, about = None, arg_required_else_help = true, after_help = "For backward compatibility, `emacs-lsp-booster ...` (without any options) is also supported" )] @@ -16,26 +15,40 @@ struct Cli { #[arg(last = true)] server_cmd: Vec, - #[arg(short = 'n', long, - help = "Disable bytecode generation. Simply forward server json as-is. Useful for debugging or benchmarking.")] + #[arg( + short = 'n', + long, + help = "Disable bytecode generation. Simply forward server json as-is. Useful for debugging or benchmarking." + )] disable_bytecode: bool, - #[arg(long, default_value = "plist", - help = "Lisp type used to represent a JSON object. Plist is the most performant one.\nMust match what lsp client expects.\n")] + #[arg( + long, + default_value = "plist", + help = "Lisp type used to represent a JSON object. Plist is the most performant one.\nMust match what lsp client expects.\n" + )] json_object_type: bytecode::ObjectType, - #[arg(long, default_value = "nil", - help = "Which lisp value is used to represent a JSON null value. Support :keyword or nil.\nMust match what lsp client expects.\n")] + #[arg( + long, + default_value = "nil", + help = "Which lisp value is used to represent a JSON null value. Support :keyword or nil.\nMust match what lsp client expects.\n" + )] json_null_value: bytecode::LispObject, - #[arg(long, default_value = "nil", - help = "Which lisp value is used to represent a JSON false value. Support :keyword or nil.\nMust match what lsp client expects.\n")] + #[arg( + long, + default_value = "nil", + help = "Which lisp value is used to represent a JSON false value. Support :keyword or nil.\nMust match what lsp client expects.\n" + )] json_false_value: bytecode::LispObject, } fn parse_args(args: T) -> Cli -where T: IntoIterator, - S: Into { +where + T: IntoIterator, + S: Into, +{ let args = args.into_iter().map(|x| x.into()).collect::>(); // backward compatible. support `emacs-lsp-booster server_cmd args...` directly if args.len() > 1 && !args[1].starts_with('-') && !args.contains(&"--".into()) { @@ -49,7 +62,9 @@ where T: IntoIterator, fn main() -> Result<()> { let cli = parse_args(std::env::args()); - env_logger::Builder::new().filter_level(cli.verbose.log_level_filter()).init(); + env_logger::Builder::new() + .filter_level(cli.verbose.log_level_filter()) + .init(); if cli.server_cmd.is_empty() { bail!("Please specify the server command"); @@ -73,14 +88,22 @@ fn main() -> Result<()> { let mut cmd = std::process::Command::new(&server_cmd_prog); cmd.args(&cli.server_cmd[1..]); - let exit_status = app::run_app_forever(std::io::stdin(), std::io::stdout(), cmd, app::AppOptions { - bytecode_options: if !cli.disable_bytecode { - Some(bytecode::BytecodeOptions { - object_type: cli.json_object_type, - null_value: cli.json_null_value, - false_value: cli.json_false_value, - }) } else { None }, - })?; + let exit_status = app::run_app_forever( + std::io::stdin(), + std::io::stdout(), + cmd, + app::AppOptions { + bytecode_options: if !cli.disable_bytecode { + Some(bytecode::BytecodeOptions { + object_type: cli.json_object_type, + null_value: cli.json_null_value, + false_value: cli.json_false_value, + }) + } else { + None + }, + }, + )?; std::process::exit(exit_status.code().unwrap_or(1)) } @@ -93,14 +116,28 @@ fn test_parse_args() { let cli = parse_args(vec!["emacs-lsp-booster", "--", "server_cmd", "arg1"]); assert_eq!(cli.server_cmd, vec!["server_cmd", "arg1"]); - let cli = parse_args(vec!["emacs-lsp-booster", "-v", - "--json-object-type", "hashtable", - "--json-null-value", ":null", - "--json-false-value", ":json-false", - "--", "server_cmd", "arg1"]); + let cli = parse_args(vec![ + "emacs-lsp-booster", + "-v", + "--json-object-type", + "hashtable", + "--json-null-value", + ":null", + "--json-false-value", + ":json-false", + "--", + "server_cmd", + "arg1", + ]); assert_eq!(cli.verbose.log_level_filter(), log::LevelFilter::Debug); assert_eq!(cli.server_cmd, vec!["server_cmd", "arg1"]); assert_eq!(cli.json_object_type, bytecode::ObjectType::Hashtable); - assert_eq!(cli.json_null_value, bytecode::LispObject::Keyword("null".into())); - assert_eq!(cli.json_false_value, bytecode::LispObject::Keyword("json-false".into())); + assert_eq!( + cli.json_null_value, + bytecode::LispObject::Keyword("null".into()) + ); + assert_eq!( + cli.json_false_value, + bytecode::LispObject::Keyword("json-false".into()) + ); } diff --git a/src/rpcio.rs b/src/rpcio.rs index 303024f..65c74a6 100644 --- a/src/rpcio.rs +++ b/src/rpcio.rs @@ -1,7 +1,7 @@ -use std::{str::FromStr, io::Write}; +use std::{io::Write, str::FromStr}; +use anyhow::{bail, Result}; use log::trace; -use anyhow::{Result, bail}; // return empty string on EOF pub fn rpc_read(reader: &mut impl std::io::BufRead) -> Result { @@ -32,7 +32,9 @@ pub fn rpc_read(reader: &mut impl std::io::BufRead) -> Result { } pub fn rpc_write(writer: &mut std::io::BufWriter, content: &str) -> Result<()> -where T: std::io::Write { +where + T: std::io::Write, +{ write!(writer, "Content-Length: {}\r\n", content.len())?; write!(writer, "\r\n")?; writer.write_all(content.as_bytes())?; diff --git a/tests/app_test.rs b/tests/app_test.rs index fdb9be2..7646422 100644 --- a/tests/app_test.rs +++ b/tests/app_test.rs @@ -1,6 +1,4 @@ use anyhow::Result; -use tempfile; -use env_logger; use emacs_lsp_booster::{app, rpcio}; @@ -23,12 +21,17 @@ fn test_app_with_echo_server() -> Result<()> { }); let mut cmd = std::process::Command::new("timeout"); - cmd.args(&["1", "cat"]); - - let exit_status = app::run_app_forever(input_pair_out, output_file, cmd, app::AppOptions { - bytecode_options: Some(Default::default()), - })?; - assert!(!exit_status.success()); // timeout kill + cmd.args(["1", "cat"]); + + let exit_status = app::run_app_forever( + input_pair_out, + output_file, + cmd, + app::AppOptions { + bytecode_options: Some(Default::default()), + }, + )?; + assert!(!exit_status.success()); // timeout kill let output = std::fs::read_to_string(tmpdir.path().join("output.txt"))?; assert_eq!(output.chars().filter(|x| *x == '#').count(), 10); diff --git a/tests/bytecode_test.rs b/tests/bytecode_test.rs index dad11ac..81d5c80 100644 --- a/tests/bytecode_test.rs +++ b/tests/bytecode_test.rs @@ -1,21 +1,25 @@ -use serde_json as json; use anyhow::Result; -use tempfile; +use serde_json as json; use emacs_lsp_booster::bytecode; - fn run_one_test(json_str: &str, object_type: bytecode::ObjectType) -> Result<()> { let json_value: json::Value = json::from_str(json_str)?; let json_str_nowhitespaces = json_value.to_string(); - let bytecode = bytecode::generate_bytecode_repl(&json_value, bytecode::BytecodeOptions { - object_type: object_type.clone(), - ..Default::default() - })?; - - eprintln!("Json: {} bytes, Bytecode: {} bytes, ratio={}", - json_str_nowhitespaces.len(), bytecode.len(), - bytecode.len() as f64 / json_str_nowhitespaces.len() as f64); + let bytecode = bytecode::generate_bytecode_repl( + &json_value, + bytecode::BytecodeOptions { + object_type, + ..Default::default() + }, + )?; + + eprintln!( + "Json: {} bytes, Bytecode: {} bytes, ratio={}", + json_str_nowhitespaces.len(), + bytecode.len(), + bytecode.len() as f64 / json_str_nowhitespaces.len() as f64 + ); let tmpdir = tempfile::tempdir()?; let json_file = tmpdir.path().join("data.json"); @@ -25,19 +29,22 @@ fn run_one_test(json_str: &str, object_type: bytecode::ObjectType) -> Result<()> std::fs::write(&bytecode_file, bytecode.as_bytes())?; let elisp_file = tmpdir.path().join("script.el"); - let elisp_code = format!(include_str!("./benchmark_and_compare.template.el"), - json_file.display(), - bytecode_file.display(), - match object_type { - bytecode::ObjectType::Plist => "plist", - bytecode::ObjectType::Hashtable => "hash-table", - bytecode::ObjectType::Alist => "alist", - }); + let elisp_code = format!( + include_str!("./benchmark_and_compare.template.el"), + json_file.display(), + bytecode_file.display(), + match object_type { + bytecode::ObjectType::Plist => "plist", + bytecode::ObjectType::Hashtable => "hash-table", + bytecode::ObjectType::Alist => "alist", + } + ); std::fs::write(&elisp_file, elisp_code.as_bytes())?; let mut child = std::process::Command::new("emacs") .arg("--batch") - .arg("-l").arg(elisp_file.as_os_str()) + .arg("-l") + .arg(elisp_file.as_os_str()) .stdout(std::process::Stdio::inherit()) .stderr(std::process::Stdio::inherit()) .spawn()?; @@ -50,32 +57,54 @@ fn run_one_test(json_str: &str, object_type: bytecode::ObjectType) -> Result<()> #[test] fn test_bytecode() { // unicode test - run_one_test(r#"{"a":"ÀÁÂÃÄÅÆÇÈÉÊËÌ abcd \n 你好世界"}"#, bytecode::ObjectType::Plist).unwrap(); - - for object_type in vec![bytecode::ObjectType::Plist, - bytecode::ObjectType::Alist, - bytecode::ObjectType::Hashtable] { - eprintln!("Testing completion.json (~100KB), object type = {:?}", object_type); + run_one_test( + r#"{"a":"ÀÁÂÃÄÅÆÇÈÉÊËÌ abcd \n 你好世界"}"#, + bytecode::ObjectType::Plist, + ) + .unwrap(); + + for object_type in [ + bytecode::ObjectType::Plist, + bytecode::ObjectType::Alist, + bytecode::ObjectType::Hashtable, + ] { + eprintln!( + "Testing completion.json (~100KB), object type = {:?}", + object_type + ); run_one_test(include_str!("./data/completion.json"), object_type).unwrap(); - eprintln!("Testing completion2.json (~100KB), object type = {:?}", object_type); + eprintln!( + "Testing completion2.json (~100KB), object type = {:?}", + object_type + ); run_one_test(include_str!("./data/completion2.json"), object_type).unwrap(); - eprintln!("Testing completion3.json (~4KB), object type = {:?}", object_type); + eprintln!( + "Testing completion3.json (~4KB), object type = {:?}", + object_type + ); run_one_test(include_str!("./data/completion3.json"), object_type).unwrap(); - eprintln!("Testing publishDiagnostics.json (~12KB), object type = {:?}", object_type); + eprintln!( + "Testing publishDiagnostics.json (~12KB), object type = {:?}", + object_type + ); run_one_test(include_str!("./data/publishDiagnostics.json"), object_type).unwrap(); - eprintln!("Testing publishDiagnostics2.json (~12KB), object type = {:?}", object_type); + eprintln!( + "Testing publishDiagnostics2.json (~12KB), object type = {:?}", + object_type + ); run_one_test(include_str!("./data/publishDiagnostics2.json"), object_type).unwrap(); } { eprintln!("Testing huge array (100000 elements)"); let value = json::Value::Array( - (0..100000).map(|x| json::Value::String(format!("{}", x))) - .collect() + (0..100000) + .map(|x| json::Value::String(format!("{}", x))) + .collect(), ); run_one_test(&value.to_string(), bytecode::ObjectType::Plist).unwrap(); } @@ -83,9 +112,9 @@ fn test_bytecode() { { eprintln!("Testing huge map (100000 elements)"); let value = json::Value::Object( - (0..100000).map(|x| (format!("x{}", x), - json::Value::Number(x.into()))) - .collect() + (0..100000) + .map(|x| (format!("x{}", x), json::Value::Number(x.into()))) + .collect(), ); run_one_test(&value.to_string(), bytecode::ObjectType::Plist).unwrap(); }