Skip to content

Commit 21f1267

Browse files
committed
Add cond special form for multi-way branching
1 parent 04ee9fb commit 21f1267

File tree

2 files changed

+113
-2
lines changed

2 files changed

+113
-2
lines changed

src/compiler.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ impl Compiler {
690690
match sym {
691691
"quote" => return self.compile_quote(&items[1..], dest),
692692
"if" => return self.compile_if(&items[1..], dest, tail_pos),
693+
"cond" => return self.compile_cond(&items[1..], dest, tail_pos),
693694
"and" => return self.compile_and(&items[1..], dest),
694695
"or" => return self.compile_or(&items[1..], dest),
695696
"def" => return self.compile_def(&items[1..], dest),
@@ -776,6 +777,117 @@ impl Compiler {
776777
Ok(())
777778
}
778779

780+
/// Compile cond expression: (cond (test1 result1) (test2 result2) ... (else default))
781+
/// Each clause is a list where the first element is the condition and the rest is the body.
782+
/// The special condition 'else' or 'true' matches unconditionally.
783+
fn compile_cond(&mut self, clauses: &[Value], dest: Reg, tail_pos: bool) -> Result<(), String> {
784+
if clauses.is_empty() {
785+
// Empty cond returns nil
786+
self.emit(Op::load_nil(dest));
787+
return Ok(());
788+
}
789+
790+
// Track jumps to end (from successful branches)
791+
let mut jumps_to_end: Vec<usize> = Vec::new();
792+
793+
for (i, clause) in clauses.iter().enumerate() {
794+
let is_last = i == clauses.len() - 1;
795+
796+
// Each clause should be a list: (condition body...)
797+
let clause_items = clause.as_list()
798+
.ok_or_else(|| "cond clause must be a list".to_string())?;
799+
800+
if clause_items.is_empty() {
801+
return Err("cond clause cannot be empty".to_string());
802+
}
803+
804+
let condition = &clause_items[0];
805+
let body = &clause_items[1..];
806+
807+
// Check for else/true clause (unconditional)
808+
let is_else = condition.as_symbol()
809+
.map(|s| s == "else" || s == "true")
810+
.unwrap_or(false);
811+
812+
if is_else {
813+
// Else clause - just compile the body
814+
if body.is_empty() {
815+
self.emit(Op::load_nil(dest));
816+
} else if body.len() == 1 {
817+
self.compile_expr(&body[0], dest, tail_pos)?;
818+
} else {
819+
// Multiple expressions: wrap in implicit do
820+
let mut do_list = vec![Value::symbol("do")];
821+
do_list.extend(body.iter().cloned());
822+
self.compile_expr(&Value::list(do_list), dest, tail_pos)?;
823+
}
824+
// No need to jump - this is the final case
825+
break;
826+
}
827+
828+
// Compile condition
829+
self.compile_expr(condition, dest, false)?;
830+
831+
if is_last {
832+
// Last clause without else: if false, result is nil
833+
let jump_to_nil = self.emit(Op::jump_if_false(dest, 0));
834+
835+
// Compile body
836+
if body.is_empty() {
837+
// If body is empty, the result is the condition value (already in dest)
838+
// But we need to handle the nil case
839+
} else if body.len() == 1 {
840+
self.compile_expr(&body[0], dest, tail_pos)?;
841+
} else {
842+
let mut do_list = vec![Value::symbol("do")];
843+
do_list.extend(body.iter().cloned());
844+
self.compile_expr(&Value::list(do_list), dest, tail_pos)?;
845+
}
846+
847+
// Jump over nil
848+
let jump_over_nil = self.emit(Op::jump(0));
849+
850+
// Nil case
851+
let nil_pos = self.chunk.current_pos();
852+
self.chunk.patch_jump(jump_to_nil, nil_pos);
853+
self.emit(Op::load_nil(dest));
854+
855+
// Patch jump over nil
856+
let end_pos = self.chunk.current_pos();
857+
self.chunk.patch_jump(jump_over_nil, end_pos);
858+
} else {
859+
// Not the last clause: if false, jump to next clause
860+
let jump_to_next = self.emit(Op::jump_if_false(dest, 0));
861+
862+
// Compile body
863+
if body.is_empty() {
864+
// Empty body - condition value is the result (already in dest)
865+
} else if body.len() == 1 {
866+
self.compile_expr(&body[0], dest, tail_pos)?;
867+
} else {
868+
let mut do_list = vec![Value::symbol("do")];
869+
do_list.extend(body.iter().cloned());
870+
self.compile_expr(&Value::list(do_list), dest, tail_pos)?;
871+
}
872+
873+
// Jump to end (skip remaining clauses)
874+
jumps_to_end.push(self.emit(Op::jump(0)));
875+
876+
// Patch jump to next clause
877+
let next_clause_pos = self.chunk.current_pos();
878+
self.chunk.patch_jump(jump_to_next, next_clause_pos);
879+
}
880+
}
881+
882+
// Patch all jumps to end
883+
let end_pos = self.chunk.current_pos();
884+
for jump_pos in jumps_to_end {
885+
self.chunk.patch_jump(jump_pos, end_pos);
886+
}
887+
888+
Ok(())
889+
}
890+
779891
fn compile_and(&mut self, args: &[Value], dest: Reg) -> Result<(), String> {
780892
// (and) => true
781893
if args.is_empty() {

src/jit.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -986,10 +986,9 @@ fn compile_bytecode(
986986
}
987987

988988
// For opcodes we don't yet support, we need to bail out
989-
// In a real JIT, this would trigger deoptimization
990989
_ => {
991990
// For unsupported opcodes, return nil and let interpreter handle it
992-
// This is a simple "give up" strategy for Phase 1
991+
// This is a simple "give up" strategy for Phase 1 [wip]
993992
return Err(format!("Unsupported opcode: {:?}", op));
994993
}
995994
}

0 commit comments

Comments
 (0)