diff options
| author | Brian Cully <bjc@spork.org> | 2025-08-25 11:01:18 -0400 |
|---|---|---|
| committer | Brian Cully <bjc@spork.org> | 2025-08-25 11:01:18 -0400 |
| commit | 79832fd6d423eb98536fcaab54cea774caf7e04c (patch) | |
| tree | 57ebcb5c5cf41e571e7a1a55389527e288400482 /src/forth | |
| parent | 275ee10822d8d5108e34166dbaf486ddb5a56a59 (diff) | |
| download | automathon-79832fd6d423eb98536fcaab54cea774caf7e04c.tar.gz automathon-79832fd6d423eb98536fcaab54cea774caf7e04c.zip | |
put tail call substitution in an fn and call it inside if/then
Diffstat (limited to 'src/forth')
| -rw-r--r-- | src/forth/compiler.rs | 84 |
1 files changed, 66 insertions, 18 deletions
diff --git a/src/forth/compiler.rs b/src/forth/compiler.rs index 89964e1..b3f5f08 100644 --- a/src/forth/compiler.rs +++ b/src/forth/compiler.rs @@ -113,6 +113,30 @@ impl<'a> Compiler<'a> { self.anno_mut().push(anno); } + // substitute previous call/if with tail version + // + // only use where we might by pushing `OpCode::Ret`. honestly, + // should todo: put this in bc_push so it's always applied + // appropriately. + fn sub_tcall(&mut self, anno: Annotation) { + match self.bc().0.last() { + Some(OpCode::Call(i)) => { + let k = *i; + self.bc_mut().0.pop(); + self.bc_mut().0.push(OpCode::TCall(k)); + }, + Some(OpCode::If(t, f)) => { + let t = *t; + let f = *f; + self.bc_mut().0.pop(); + self.bc_mut().0.push(OpCode::TIf(t, f)); + // technically only needed if âfâ is None, but whatever. + self.bc_push(OpCode::Ret, anno); + }, + _ => self.bc_push(OpCode::Ret, anno), + } + } + pub fn compile(&mut self) -> CompileResult<()> { while let Some((word, start, end)) = self.next_word() { let anno = Annotation { loc: (start, end) }; @@ -137,22 +161,7 @@ impl<'a> Compiler<'a> { self.annotations.push(vec![]); }, ";" => { - match self.bc().0.last() { - Some(OpCode::Call(i)) => { - let k = *i; - self.bc_mut().0.pop(); - self.bc_mut().0.push(OpCode::TCall(k)); - }, - Some(OpCode::If(t, f)) => { - let t = *t; - let f = *f; - self.bc_mut().0.pop(); - self.bc_mut().0.push(OpCode::TIf(t, f)); - // technically only needed if âfâ is None, but whatever. - self.bc_push(OpCode::Ret, anno); - }, - _ => self.bc_push(OpCode::Ret, anno), - } + self.sub_tcall(anno); self.defstack.pop().ok_or(CompileError::DefStackEmpty)?; }, "if" => { @@ -164,7 +173,7 @@ impl<'a> Compiler<'a> { self.if_stack.push((IfClauses::True(i), anno)); }, "else" => { - self.bc_push(OpCode::Ret, anno); + self.sub_tcall(anno); self.defstack.pop(); let i = self.wordlist.0.len(); @@ -180,7 +189,7 @@ impl<'a> Compiler<'a> { self.if_stack.push((IfClauses::TrueFalse(true_clause, i), anno)); }, "then" => { - self.bc_push(OpCode::Ret, anno); + self.sub_tcall(anno); self.defstack.pop(); match self.if_stack.pop() { @@ -361,4 +370,43 @@ mod tests { let foo = &comp.wordlist.0[1]; assert_eq!(foo, vec![OpCode::TCall(1)]); } + + #[test] + fn tcall_if_then() { + let prog = r#" +: foo + dup + 1 > if + foo + then + ; +"#; + let comp = compiler_for(prog); + eprintwordlist(&comp.wordlist); + let tbranch = &comp.wordlist.0[2]; + eprintln!("true: {tbranch:?}"); + assert_eq!(tbranch.0[0], OpCode::TCall(1)); + } + + #[test] + fn tcall_if_else_then() { + let prog = r#" +: foo + dup + 1 > if + foo + else + foo + then + ; +"#; + let comp = compiler_for(prog); + eprintwordlist(&comp.wordlist); + let tbranch = &comp.wordlist.0[2]; + eprintln!("true: {tbranch:?}"); + assert_eq!(tbranch.0[0], OpCode::TCall(1)); + let fbranch = &comp.wordlist.0[3]; + eprintln!("false: {fbranch:?}"); + assert_eq!(tbranch.0[0], OpCode::TCall(1)); + } } |
