diff --git a/hblang/examples/if_statement.hb b/hblang/examples/if_statement.hb index f4e3784e..022f4951 100644 --- a/hblang/examples/if_statement.hb +++ b/hblang/examples/if_statement.hb @@ -6,6 +6,7 @@ main := ||: int { fib := |x: int|: int { if x <= 2 { return 1; + } else { + return fib(x - 1) + fib(x - 2); } - return fib(x - 1) + fib(x - 2); } diff --git a/hblang/examples/loops.hb b/hblang/examples/loops.hb new file mode 100644 index 00000000..dbc9b207 --- /dev/null +++ b/hblang/examples/loops.hb @@ -0,0 +1,19 @@ +main := ||: int { + return fib(10); +} + +fib := |n: int|: int { + a := 0; + b := 1; + loop { + c := a + b; + a = b; + b = c; + n = n - 1; + if n == 0 { + break; + } + continue; + } + return a; +} diff --git a/hblang/src/codegen.rs b/hblang/src/codegen.rs index c0da7544..d18d21eb 100644 --- a/hblang/src/codegen.rs +++ b/hblang/src/codegen.rs @@ -179,6 +179,11 @@ struct RetReloc { size: u16, } +struct Loop { + offset: u32, + relocs: Vec, +} + pub struct Codegen<'a> { path: &'a std::path::Path, ret: Expr<'a>, @@ -190,6 +195,7 @@ pub struct Codegen<'a> { vars: Vec>, stack_relocs: Vec, ret_relocs: Vec, + loops: Vec, } impl<'a> Codegen<'a> { @@ -206,6 +212,7 @@ impl<'a> Codegen<'a> { stack_relocs: Default::default(), ret_relocs: Default::default(), + loops: Default::default(), } } @@ -368,7 +375,7 @@ impl<'a> Codegen<'a> { ty: expeted.unwrap_or(Expr::Ident { name: "int" }), loc: Loc::Imm(value), }), - E::If { cond, then } => { + E::If { cond, then, else_ } => { let cond = self.expr(cond, Some(Expr::Ident { name: "bool" })).unwrap(); let reg = self.loc_to_reg(cond.loc); let jump_offset = self.code.code.len() as u32; @@ -377,13 +384,74 @@ impl<'a> Codegen<'a> { self.gpa.free(reg); self.expr(then, None); - let jump = self.code.code.len() as i16 - jump_offset as i16; + + let jump; + + if let Some(else_) = else_ { + let else_jump_offset = self.code.code.len() as u32; + println!("jump_offset: {:02x}", jump_offset); + self.code.encode(instrs::jmp(0)); + + jump = self.code.code.len() as i16 - jump_offset as i16; + + self.expr(else_, None); + + let jump = self.code.code.len() as i32 - else_jump_offset as i32; + println!("jump: {:02x}", jump); + self.code.code[else_jump_offset as usize + 1..][..4] + .copy_from_slice(&jump.to_ne_bytes()); + } else { + jump = self.code.code.len() as i16 - jump_offset as i16; + } + println!("jump: {:02x}", jump); self.code.code[jump_offset as usize + 3..][..2] .copy_from_slice(&jump.to_ne_bytes()); None } + E::Loop { body } => { + let loop_start = self.code.code.len() as u32; + self.loops.push(Loop { + offset: loop_start, + relocs: Default::default(), + }); + self.expr(body, None); + + let loop_end = self.code.code.len(); + self.code + .encode(instrs::jmp(loop_start as i32 - loop_end as i32)); + let loop_end = self.code.code.len() as u32; + + let loop_ = self.loops.pop().unwrap(); + for reloc in loop_.relocs { + let dest = &mut self.code.code + [reloc.offset as usize + reloc.instr_offset as usize..] + [..reloc.size as usize]; + let offset = loop_end as i32 - reloc.offset as i32; + dest.copy_from_slice(&offset.to_ne_bytes()); + } + + None + } + E::Break => { + let loop_ = self.loops.last_mut().unwrap(); + let offset = self.code.code.len() as u32; + self.code.encode(instrs::jmp(0)); + loop_.relocs.push(RetReloc { + offset, + instr_offset: 1, + size: 4, + }); + None + } + E::Continue => { + let loop_ = self.loops.last().unwrap(); + let offset = self.code.code.len() as u32; + self.code + .encode(instrs::jmp(loop_.offset as i32 - offset as i32)); + None + } E::BinOp { left, op, right } => { let left = self.expr(left, expeted).unwrap(); let right = self.expr(right, Some(left.ty)).unwrap(); @@ -404,6 +472,16 @@ impl<'a> Codegen<'a> { loc: Loc::Reg(lhs), }); } + T::Eq => { + self.code.encode(instrs::cmpu(lhs, lhs, rhs)); + self.gpa.free(rhs); + self.code.encode(instrs::cmpui(lhs, lhs, 0)); + self.code.encode(instrs::not(lhs, lhs)); + return Some(Value { + ty: Expr::Ident { name: "bool" }, + loc: Loc::Reg(lhs), + }); + } T::FSlash => |reg0, reg1, reg2| instrs::diru64(reg0, ZERO, reg1, reg2), T::Assign => return self.assign(left, right), _ => unimplemented!("{:#?}", op), @@ -635,5 +713,6 @@ mod tests { variables => include_str!("../examples/variables.hb"); functions => include_str!("../examples/functions.hb"); if_statements => include_str!("../examples/if_statement.hb"); + loops => include_str!("../examples/loops.hb"); } } diff --git a/hblang/src/lexer.rs b/hblang/src/lexer.rs index 7518f47d..5c9d1748 100644 --- a/hblang/src/lexer.rs +++ b/hblang/src/lexer.rs @@ -30,11 +30,16 @@ pub enum TokenKind { Bor, Or, Le, + Eq, Semi, Colon, Comma, Return, If, + Else, + Loop, + Break, + Continue, Eof, Error, } @@ -60,11 +65,16 @@ impl std::fmt::Display for TokenKind { T::Bor => "|", T::Or => "||", T::Le => "<=", + T::Eq => "==", T::Semi => ";", T::Colon => ":", T::Comma => ",", T::Return => "return", T::If => "if", + T::Else => "else", + T::Loop => "loop", + T::Break => "break", + T::Continue => "continue", T::Eof => "", T::Error => "", }; @@ -76,7 +86,7 @@ impl TokenKind { pub fn precedence(&self) -> Option { Some(match self { Self::Assign => 1, - Self::Le => 21, + Self::Le | Self::Eq => 21, Self::Plus | Self::Minus => 23, Self::Star | Self::FSlash => 24, _ => return None, @@ -167,6 +177,10 @@ impl<'a> Iterator for Lexer<'a> { match ident { b"return" => T::Return, b"if" => T::If, + b"else" => T::Else, + b"loop" => T::Loop, + b"break" => T::Break, + b"continue" => T::Continue, _ => T::Ident, } } @@ -176,7 +190,10 @@ impl<'a> Iterator for Lexer<'a> { }, b',' => T::Comma, b';' => T::Semi, - b'=' => T::Assign, + b'=' => match self.advance_if(b'=') { + true => T::Eq, + false => T::Assign, + }, b'<' => match self.advance_if(b'=') { true => T::Le, false => T::Error, diff --git a/hblang/src/parser.rs b/hblang/src/parser.rs index 8bbaa27f..23a652be 100644 --- a/hblang/src/parser.rs +++ b/hblang/src/parser.rs @@ -84,8 +84,14 @@ impl<'a, 'b> Parser<'a, 'b> { TokenKind::If => { let cond = self.ptr_expr(); let then = self.ptr_expr(); - Expr::If { cond, then } + let else_ = self.advance_if(TokenKind::Else).then(|| self.ptr_expr()); + Expr::If { cond, then, else_ } } + TokenKind::Loop => Expr::Loop { + body: self.ptr_expr(), + }, + TokenKind::Break => Expr::Break, + TokenKind::Continue => Expr::Continue, TokenKind::Return => Expr::Return { val: (self.token.kind != TokenKind::Semi).then(|| self.ptr_expr()), }, @@ -189,6 +195,8 @@ impl<'a, 'b> Parser<'a, 'b> { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Expr<'a> { + Break, + Continue, Decl { name: &'a str, val: &'a Expr<'a>, @@ -220,8 +228,12 @@ pub enum Expr<'a> { right: &'a Expr<'a>, }, If { - cond: &'a Expr<'a>, - then: &'a Expr<'a>, + cond: &'a Expr<'a>, + then: &'a Expr<'a>, + else_: Option<&'a Expr<'a>>, + }, + Loop { + body: &'a Expr<'a>, }, } @@ -232,7 +244,16 @@ impl<'a> std::fmt::Display for Expr<'a> { } match *self { - Self::If { cond, then } => write!(f, "if {} {}", cond, then), + Self::Break => write!(f, "break;"), + Self::Continue => write!(f, "continue;"), + Self::If { cond, then, else_ } => { + write!(f, "if {} {}", cond, then)?; + if let Some(else_) = else_ { + write!(f, " else {}", else_)?; + } + Ok(()) + } + Self::Loop { body } => write!(f, "loop {}", body), Self::Decl { name, val } => write!(f, "{} := {}", name, val), Self::Closure { ret, body, args } => { write!(f, "|")?; diff --git a/hblang/src/tests.rs b/hblang/src/tests.rs index 0faaa292..0e58b484 100644 --- a/hblang/src/tests.rs +++ b/hblang/src/tests.rs @@ -12,7 +12,7 @@ pub fn run_test(name: &'static str, input: &'static str, test: fn(&'static str, test(input, &mut output); let mut root = PathBuf::from(std::env::var("PT_TEST_ROOT").unwrap_or("tests".to_string())); - root.push(name); + root.push(name.replace("::", "_")); root.set_extension("txt"); let expected = std::fs::read_to_string(&root).unwrap_or_default(); diff --git a/hblang/test.bin b/hblang/test.bin index e9cab906..47de0c8a 100644 Binary files a/hblang/test.bin and b/hblang/test.bin differ diff --git a/hblang/tests/hblang::codegen::tests::if_statements.txt b/hblang/tests/hblang::codegen::tests::if_statements.txt deleted file mode 100644 index e69de29b..00000000 diff --git a/hblang/tests/hblang::codegen::tests::arithmetic.txt b/hblang/tests/hblang_codegen_tests_arithmetic.txt similarity index 100% rename from hblang/tests/hblang::codegen::tests::arithmetic.txt rename to hblang/tests/hblang_codegen_tests_arithmetic.txt diff --git a/hblang/tests/hblang::codegen::tests::example.txt b/hblang/tests/hblang_codegen_tests_example.txt similarity index 100% rename from hblang/tests/hblang::codegen::tests::example.txt rename to hblang/tests/hblang_codegen_tests_example.txt diff --git a/hblang/tests/hblang::codegen::tests::functions.txt b/hblang/tests/hblang_codegen_tests_functions.txt similarity index 100% rename from hblang/tests/hblang::codegen::tests::functions.txt rename to hblang/tests/hblang_codegen_tests_functions.txt diff --git a/hblang/tests/hblang_codegen_tests_if_statements.txt b/hblang/tests/hblang_codegen_tests_if_statements.txt new file mode 100644 index 00000000..9437c76f --- /dev/null +++ b/hblang/tests/hblang_codegen_tests_if_statements.txt @@ -0,0 +1,2 @@ +ret: 55 +status: Ok(()) diff --git a/hblang/tests/hblang_codegen_tests_loops.txt b/hblang/tests/hblang_codegen_tests_loops.txt new file mode 100644 index 00000000..9437c76f --- /dev/null +++ b/hblang/tests/hblang_codegen_tests_loops.txt @@ -0,0 +1,2 @@ +ret: 55 +status: Ok(()) diff --git a/hblang/tests/hblang::codegen::tests::variables.txt b/hblang/tests/hblang_codegen_tests_variables.txt similarity index 100% rename from hblang/tests/hblang::codegen::tests::variables.txt rename to hblang/tests/hblang_codegen_tests_variables.txt diff --git a/hblang/tests/hblang::lexer::tests::arithmetic.txt b/hblang/tests/hblang_lexer_tests_arithmetic.txt similarity index 100% rename from hblang/tests/hblang::lexer::tests::arithmetic.txt rename to hblang/tests/hblang_lexer_tests_arithmetic.txt diff --git a/hblang/tests/hblang::lexer::tests::empty.txt b/hblang/tests/hblang_lexer_tests_empty.txt similarity index 100% rename from hblang/tests/hblang::lexer::tests::empty.txt rename to hblang/tests/hblang_lexer_tests_empty.txt diff --git a/hblang/tests/hblang::lexer::tests::example.txt b/hblang/tests/hblang_lexer_tests_example.txt similarity index 100% rename from hblang/tests/hblang::lexer::tests::example.txt rename to hblang/tests/hblang_lexer_tests_example.txt diff --git a/hblang/tests/hblang::lexer::tests::whitespace.txt b/hblang/tests/hblang_lexer_tests_whitespace.txt similarity index 100% rename from hblang/tests/hblang::lexer::tests::whitespace.txt rename to hblang/tests/hblang_lexer_tests_whitespace.txt diff --git a/hblang/tests/hblang::parser::tests::arithmetic.txt b/hblang/tests/hblang_parser_tests_arithmetic.txt similarity index 100% rename from hblang/tests/hblang::parser::tests::arithmetic.txt rename to hblang/tests/hblang_parser_tests_arithmetic.txt diff --git a/hblang/tests/hblang::parser::tests::example.txt b/hblang/tests/hblang_parser_tests_example.txt similarity index 100% rename from hblang/tests/hblang::parser::tests::example.txt rename to hblang/tests/hblang_parser_tests_example.txt