Skip to content

Commit

Permalink
support hashtable in bytecode
Browse files Browse the repository at this point in the history
  • Loading branch information
blahgeek committed Jan 2, 2024
1 parent 9c43320 commit 1cc9621
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 32 deletions.
2 changes: 1 addition & 1 deletion src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ fn process_server_reader(reader: impl std::io::Read,
break
}
let json_val = json::from_str(&msg)?;
match bytecode::generate_bytecode_repl(&json_val) {
match bytecode::generate_bytecode_repl(&json_val, bytecode::BytecodeOptions::default()) {
Ok(bytecode_str) => {
channel_pub.send(bytecode_str)?;
},
Expand Down
64 changes: 56 additions & 8 deletions src/bytecode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use smallvec::smallvec;


#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum LispObject {
pub enum LispObject {
Symbol(String),
Keyword(String),
Str(String),
Expand Down Expand Up @@ -139,9 +139,34 @@ impl Op {
}
}

#[derive(Clone, Copy)]
pub enum ObjectType {
Plist,
Hashtable,
// TODO: Alist,
}

pub struct BytecodeOptions {
pub object_type: ObjectType,
// TODO: array_type
pub null_object: LispObject,
pub false_object: LispObject,
}

impl Default for BytecodeOptions {
fn default() -> Self {
Self {
object_type: ObjectType::Plist,
null_object: LispObject::Nil,
false_object: LispObject::Nil,
}
}
}

// Only for generating json. Sequential execution only.
struct BytecodeCompiler {
options: BytecodeOptions,

ops: Vec<Op>,
constants: BTreeMap<LispObject, (u32, u32)>, // (index, count)
}
Expand Down Expand Up @@ -191,7 +216,7 @@ impl BytecodeCompiler {
}
}

fn compile_value_map(&mut self, map: &json::Map<String, json::Value>) {
fn compile_value_map_plist(&mut self, map: &json::Map<String, json::Value>) {
let list_len = map.len() * 2;
// see below
if list_len < (1 << 16) && list_len >= (1 << 8) {
Expand All @@ -218,12 +243,31 @@ impl BytecodeCompiler {
}
}

// current only support:
// object-type: plist, null-object: nil, false-object: nil, array-type: vector
fn compile_value_map_hashtable(&mut self, map: &json::Map<String, json::Value>) {
self.compile_constant_op(LispObject::Symbol("make-hash-table".into()));
self.compile_constant_op(LispObject::Keyword("test".into()));
self.compile_constant_op(LispObject::Symbol("equal".into()));
self.compile_constant_op(LispObject::Keyword("size".into()));
self.compile_constant_op(LispObject::Int(map.len() as i64));
self.ops.push(Op::Call(4));

for (key, value) in map {
self.compile_constant_op(LispObject::Symbol("puthash".into()));
self.compile_constant_op(LispObject::Str(key.clone()));
self.compile_value(value);
self.ops.push(Op::StackRef(3));
self.ops.push(Op::Call(3));
self.ops.push(Op::Discard);
}
}

fn compile_value(&mut self, value: &json::Value) {
match value {
&json::Value::Null | &json::Value::Bool(false) => {
self.compile_constant_op(LispObject::Nil);
&json::Value::Null => {
self.compile_constant_op(self.options.null_object.clone());
},
&json::Value::Bool(false) => {
self.compile_constant_op(self.options.false_object.clone());
},
&json::Value::Bool(true) => {
self.compile_constant_op(LispObject::T);
Expand All @@ -242,7 +286,10 @@ impl BytecodeCompiler {
self.compile_value_array(&arr);
},
&json::Value::Object(ref map) => {
self.compile_value_map(&map);
match self.options.object_type {
ObjectType::Plist => self.compile_value_map_plist(&map),
ObjectType::Hashtable => self.compile_value_map_hashtable(&map),
};
},
}
}
Expand Down Expand Up @@ -319,8 +366,9 @@ impl BytecodeCompiler {
}
}

pub fn generate_bytecode_repl(value: &json::Value) -> Result<String> {
pub fn generate_bytecode_repl(value: &json::Value, options: BytecodeOptions) -> Result<String> {
let mut compiler = BytecodeCompiler {
options,
ops: Vec::new(),
constants: BTreeMap::new(),
};
Expand Down
40 changes: 27 additions & 13 deletions tests/benchmark_and_compare.template.el
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@
(setq p (cddr p)))
(sort res (lambda (a b) (string< (symbol-name (car a)) (symbol-name (car b)))))))

(defun -hashtable-to-sorted-alist (h)
(let (res)
(maphash (lambda (k v) (push (cons k v) res)) h)
(sort res (lambda (a b) (string< (car a) (car b))))))

(defun -check-equal-sorted-alist (a-alist b-alist)
(let ((res t))
(while a-alist
(unless (and (equal (caar a-alist) (caar b-alist))
(json-equal (cdar a-alist) (cdar b-alist)))
(setq res nil))
(setq a-alist (cdr a-alist)
b-alist (cdr b-alist)))
res))

(defun json-equal (a b)
(pcase a
((or
Expand All @@ -36,16 +51,13 @@
((pred plistp)
(and (plistp b)
(equal (length a) (length b))
(let ((a-alist (-plist-to-sorted-alist a))
(b-alist (-plist-to-sorted-alist b))
(res t))
(while a-alist
(unless (and (equal (caar a-alist) (caar b-alist))
(json-equal (cdar a-alist) (cdar b-alist)))
(setq res nil))
(setq a-alist (cdr a-alist)
b-alist (cdr b-alist)))
res)))
(-check-equal-sorted-alist (-plist-to-sorted-alist a)
(-plist-to-sorted-alist b))))
((pred hash-table-p)
(and (hash-table-p b)
(equal (hash-table-count a) (hash-table-count b))
(-check-equal-sorted-alist (-hashtable-to-sorted-alist a)
(-hashtable-to-sorted-alist b))))
(_ nil)))

(let ((json-str (with-temp-buffer
Expand All @@ -54,15 +66,17 @@
(bytecode-str (with-temp-buffer
(insert-file-contents "{}")
(buffer-string)))
(object-type (if (equal "{}" "plist") 'plist 'hash-table))
json-val bytecode-val)
(message "Object-type: %s" object-type)
(unless (json-equal (setq json-val
(json-parse-string json-str :object-type 'plist :null-object nil :false-object nil))
(json-parse-string json-str :object-type object-type :null-object nil :false-object nil))
(setq bytecode-val
(funcall (read bytecode-str))))
(error "NOT EQUAL!"))
(message "Benchmark json-parse-string 100 times: %s"
(benchmark-run 100
(json-parse-string json-str :object-type 'plist :null-object nil :false-object nil)))
(json-parse-string json-str :object-type object-type :null-object nil :false-object nil)))
(message "Benchmark read-bytecode 100 times: %s"
(benchmark-run 100
(read bytecode-str)))
Expand All @@ -73,5 +87,5 @@
(message "Benchmark read-lisp-data 100 times: %s"
(benchmark-run 100
(read lisp-str))))

(message "PASS!"))
29 changes: 19 additions & 10 deletions tests/bytecode_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ use tempfile;
use emacs_lsp_booster::bytecode;


fn run_one_test(json_str: &str) -> Result<()> {
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)?;
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(),
Expand All @@ -23,7 +26,12 @@ fn run_one_test(json_str: &str) -> Result<()> {

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());
json_file.display(),
bytecode_file.display(),
match object_type {
bytecode::ObjectType::Plist => "plist",
bytecode::ObjectType::Hashtable => "hashtable",
});
std::fs::write(&elisp_file, elisp_code.as_bytes())?;

let mut child = std::process::Command::new("emacs")
Expand All @@ -44,7 +52,7 @@ fn test_huge_array() {
(0..100000).map(|x| json::Value::String(format!("{}", x)))
.collect()
);
run_one_test(&value.to_string()).unwrap();
run_one_test(&value.to_string(), bytecode::ObjectType::Plist).unwrap();
}

#[test]
Expand All @@ -54,30 +62,31 @@ fn test_huge_object() {
json::Value::Number(x.into())))
.collect()
);
run_one_test(&value.to_string()).unwrap();
run_one_test(&value.to_string(), bytecode::ObjectType::Plist).unwrap();
}

#[test]
fn test_completion_100k() {
run_one_test(include_str!("./data/completion.json")).unwrap();
run_one_test(include_str!("./data/completion.json"), bytecode::ObjectType::Plist).unwrap();
run_one_test(include_str!("./data/completion.json"), bytecode::ObjectType::Hashtable).unwrap();
}

#[test]
fn test_completion_100k_2() {
run_one_test(include_str!("./data/completion2.json")).unwrap();
run_one_test(include_str!("./data/completion2.json"), bytecode::ObjectType::Plist).unwrap();
}

#[test]
fn test_completion_4k() {
run_one_test(include_str!("./data/completion3.json")).unwrap();
run_one_test(include_str!("./data/completion3.json"), bytecode::ObjectType::Plist).unwrap();
}

#[test]
fn test_diagnostics_12k() {
run_one_test(include_str!("./data/publishDiagnostics.json")).unwrap();
run_one_test(include_str!("./data/publishDiagnostics.json"), bytecode::ObjectType::Plist).unwrap();
}

#[test]
fn test_diagnostics_12k_2() {
run_one_test(include_str!("./data/publishDiagnostics2.json")).unwrap();
run_one_test(include_str!("./data/publishDiagnostics2.json"), bytecode::ObjectType::Plist).unwrap();
}

0 comments on commit 1cc9621

Please sign in to comment.