use super::*;
use crate::emacs_core::parse_forms;
fn compile(src: &str) -> ByteCodeFunction {
let forms = parse_forms(src).expect("parse");
let mut compiler = Compiler::new(false);
compiler.compile_toplevel(&forms[0])
}
#[test]
fn compile_literal_int() {
crate::test_utils::init_test_tracing();
let func = compile("42");
assert_eq!(func.ops.len(), 2); assert!(matches!(func.ops[0], Op::Constant(0)));
assert!(matches!(func.ops[1], Op::Return));
assert_eq!(func.constants[0].as_int(), Some(42));
}
#[test]
fn compile_nil_t() {
crate::test_utils::init_test_tracing();
let func = compile("nil");
assert!(matches!(func.ops[0], Op::Nil));
let func = compile("t");
assert!(matches!(func.ops[0], Op::True));
}
#[test]
fn compile_addition() {
crate::test_utils::init_test_tracing();
let func = compile("(+ 1 2)");
assert_eq!(func.ops.len(), 4);
assert!(matches!(func.ops[2], Op::Add));
}
#[test]
fn compile_if() {
crate::test_utils::init_test_tracing();
let func = compile("(if t 1 2)");
let has_goto_nil = func.ops.iter().any(|op| matches!(op, Op::GotoIfNil(_)));
assert!(has_goto_nil);
}
#[test]
fn compile_if_else_ending_in_when() {
crate::test_utils::init_test_tracing();
let func = compile(
"(if cond
(foo)
(let ((re-narrow narrowfun))
(when re-narrow
(funcall narrowfun))))",
);
assert!(matches!(func.ops.last(), Some(Op::Return)));
}
#[test]
fn compile_if_else_ending_in_unless() {
crate::test_utils::init_test_tracing();
let func = compile(
"(if cond
(foo)
(let ((re-narrow narrowfun))
(unless re-narrow
(funcall narrowfun))))",
);
assert!(matches!(func.ops.last(), Some(Op::Return)));
}
#[test]
fn compile_let() {
crate::test_utils::init_test_tracing();
let func = compile("(let ((x 1)) x)");
let has_varbind = func.ops.iter().any(|op| matches!(op, Op::VarBind(_)));
let has_unbind = func.ops.iter().any(|op| matches!(op, Op::Unbind(_)));
assert!(has_varbind);
assert!(has_unbind);
}
#[test]
fn compile_setq() {
crate::test_utils::init_test_tracing();
let func = compile("(setq x 42)");
let has_varset = func.ops.iter().any(|op| matches!(op, Op::VarSet(_)));
assert!(has_varset);
}
#[test]
fn compile_while() {
crate::test_utils::init_test_tracing();
let func = compile("(while nil 1)");
let has_goto = func.ops.iter().any(|op| matches!(op, Op::Goto(_)));
let has_goto_nil = func.ops.iter().any(|op| matches!(op, Op::GotoIfNil(_)));
assert!(has_goto);
assert!(has_goto_nil);
}
#[test]
fn compile_lambda() {
crate::test_utils::init_test_tracing();
let func = compile("(lambda (x) (+ x 1))");
let has_constant = func.ops.iter().any(|op| matches!(op, Op::Constant(_)));
assert!(has_constant);
}
#[test]
fn compile_quote() {
crate::test_utils::init_test_tracing();
let func = compile("'(1 2 3)");
assert_eq!(func.ops.len(), 2); }
#[test]
fn compile_and_or() {
crate::test_utils::init_test_tracing();
let func = compile("(and 1 2 3)");
let has_short_circuit = func
.ops
.iter()
.any(|op| matches!(op, Op::GotoIfNilElsePop(_)));
assert!(has_short_circuit);
let func = compile("(or 1 2 3)");
let has_short_circuit = func
.ops
.iter()
.any(|op| matches!(op, Op::GotoIfNotNilElsePop(_)));
assert!(has_short_circuit);
}
#[test]
fn compile_type_predicates() {
crate::test_utils::init_test_tracing();
let func = compile("(null x)");
let has_not = func.ops.iter().any(|op| matches!(op, Op::Not));
assert!(has_not);
let func = compile("(consp x)");
let has_consp = func.ops.iter().any(|op| matches!(op, Op::Consp));
assert!(has_consp);
}
#[test]
fn compile_list_ops() {
crate::test_utils::init_test_tracing();
let func = compile("(car x)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Car)));
let func = compile("(cdr x)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Cdr)));
let func = compile("(cons 1 2)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Cons)));
}
#[test]
fn compile_progn() {
crate::test_utils::init_test_tracing();
let func = compile("(progn 1 2 3)");
assert!(matches!(func.ops.last(), Some(Op::Return)));
}
#[test]
fn disassemble_output() {
crate::test_utils::init_test_tracing();
let func = compile("(+ 1 2)");
let dis = func.disassemble();
assert!(dis.contains("add"));
assert!(dis.contains("constant"));
}
#[test]
fn compile_mapatoms_and_maphash_to_callbuiltin() {
crate::test_utils::init_test_tracing();
let mapatoms = compile("(mapatoms #'identity)");
assert!(
mapatoms
.ops
.iter()
.any(|op| matches!(op, Op::CallBuiltin(_, 1) | Op::CallBuiltin(_, 2)))
);
let maphash = compile("(maphash #'identity table)");
assert!(
maphash
.ops
.iter()
.any(|op| matches!(op, Op::CallBuiltin(_, 2)))
);
}
#[test]
fn compile_statement_position_builtins_to_fast_paths() {
crate::test_utils::init_test_tracing();
let mapatoms = compile("(let ((x 1)) (mapatoms #'identity) x)");
assert!(
mapatoms
.ops
.iter()
.any(|op| matches!(op, Op::CallBuiltin(_, 1) | Op::CallBuiltin(_, 2)))
);
let maphash = compile("(let ((x 1)) (maphash #'identity table) x)");
assert!(
maphash
.ops
.iter()
.any(|op| matches!(op, Op::CallBuiltin(_, 2)))
);
let gc = compile("(let ((x 1)) (garbage-collect) x)");
assert!(gc.ops.iter().any(|op| matches!(op, Op::CallBuiltin(_, 0))));
}
#[test]
fn compile_cond() {
crate::test_utils::init_test_tracing();
let func = compile("(cond (nil 1) (t 2))");
let has_goto_nil = func.ops.iter().any(|op| matches!(op, Op::GotoIfNil(_)));
assert!(has_goto_nil);
}
#[test]
fn compile_when() {
crate::test_utils::init_test_tracing();
let func = compile("(when t 1 2)");
let has_goto_nil = func.ops.iter().any(|op| matches!(op, Op::GotoIfNil(_)));
assert!(has_goto_nil);
}
#[test]
fn compile_unless() {
crate::test_utils::init_test_tracing();
let func = compile("(unless nil 1)");
let has_goto_not_nil = func.ops.iter().any(|op| matches!(op, Op::GotoIfNotNil(_)));
let has_goto_nil = func.ops.iter().any(|op| matches!(op, Op::GotoIfNil(_)));
assert!(has_goto_not_nil || has_goto_nil);
}
#[test]
fn compile_catch() {
crate::test_utils::init_test_tracing();
let func = compile("(catch 'tag (+ 1 2))");
let has_handler = func.ops.iter().any(|op| matches!(op, Op::PushCatch(_)));
assert!(has_handler);
let has_pop = func.ops.iter().any(|op| matches!(op, Op::PopHandler));
assert!(has_pop);
}
#[test]
fn compile_unwind_protect() {
crate::test_utils::init_test_tracing();
let func = compile("(unwind-protect 1 2)");
let has_cleanup = func.ops.iter().any(|op| matches!(op, Op::UnwindProtectPop));
assert!(has_cleanup);
let has_unbind = func.ops.iter().any(|op| matches!(op, Op::Unbind(1)));
assert!(has_unbind);
}
#[test]
fn compile_condition_case() {
crate::test_utils::init_test_tracing();
let func = compile("(condition-case err (error \"boom\") (error err))");
let has_push_cc = func
.ops
.iter()
.any(|op| matches!(op, Op::PushConditionCase(_) | Op::PushConditionCaseRaw(_)));
assert!(has_push_cc);
}
#[test]
fn compile_prog1() {
crate::test_utils::init_test_tracing();
let func = compile("(prog1 1 2 3)");
assert!(matches!(func.ops.last(), Some(Op::Return)));
}
#[test]
fn compile_defun() {
crate::test_utils::init_test_tracing();
let func = compile("(defun my-fn (x) (+ x 1))");
let has_constant = func.ops.iter().any(|op| matches!(op, Op::Constant(_)));
assert!(has_constant);
}
#[test]
fn compile_dotimes() {
crate::test_utils::init_test_tracing();
let func = compile("(dotimes (i 10) i)");
let has_goto = func.ops.iter().any(|op| matches!(op, Op::Goto(_)));
assert!(has_goto);
}
#[test]
fn compile_dolist() {
crate::test_utils::init_test_tracing();
let func = compile("(dolist (x '(1 2 3)) x)");
let has_goto = func.ops.iter().any(|op| matches!(op, Op::Goto(_)));
assert!(has_goto);
}
#[test]
fn compile_let_star() {
crate::test_utils::init_test_tracing();
let func = compile("(let* ((x 1) (y x)) y)");
let varbind_count = func
.ops
.iter()
.filter(|op| matches!(op, Op::VarBind(_)))
.count();
assert_eq!(varbind_count, 2);
}
#[test]
fn compile_save_excursion() {
crate::test_utils::init_test_tracing();
let func = compile("(save-excursion 1)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Constant(_))));
}
#[test]
fn compile_subtraction_and_multiplication() {
crate::test_utils::init_test_tracing();
let func = compile("(- 3 1)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Sub)));
let func = compile("(* 2 3)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Mul)));
}
#[test]
fn compile_comparisons() {
crate::test_utils::init_test_tracing();
let func = compile("(< 1 2)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Lss)));
let func = compile("(> 1 2)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Gtr)));
let func = compile("(= 1 1)");
assert!(func.ops.iter().any(|op| matches!(op, Op::Eqlsign)));
}