From 0163d0b76415907e1558065560d738d0b66a167b Mon Sep 17 00:00:00 2001 From: Ryu <114303361+ryuapp@users.noreply.github.com> Date: Wed, 11 Mar 2026 00:51:42 +0900 Subject: [PATCH] feat: add `%` (modulo) operator --- src/codegen/zig/mod.rs | 6 ++++-- src/lexer.rs | 5 +++++ src/parser.rs | 2 ++ src/tests/codegen.rs | 12 +++++++++++ src/tests/fixtures/modulo.zy | 3 +++ ...napshots__codegen_snapshots@modulo.zy.snap | 9 ++++++++ src/tests/typechecker.rs | 21 +++++++++++++++++++ src/typechecker.rs | 2 +- 8 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 src/tests/fixtures/modulo.zy create mode 100644 src/tests/snapshots/zyre__tests__snapshots__codegen_snapshots@modulo.zy.snap diff --git a/src/codegen/zig/mod.rs b/src/codegen/zig/mod.rs index e2ab57d..53cca07 100644 --- a/src/codegen/zig/mod.rs +++ b/src/codegen/zig/mod.rs @@ -520,7 +520,11 @@ impl ZigBackend { } ExprKind::BinOp { op, lhs, rhs } => { + let lhs_s = self.gen_expr(lhs); + let rhs_s = self.gen_expr(rhs); let op_str = match op { + // Zig requires @rem for signed integer remainder (% only works on unsigned) + BinOp::Mod => return format!("@rem({}, {})", lhs_s, rhs_s), BinOp::Add => "+", BinOp::Sub => "-", BinOp::Mul => "*", @@ -534,8 +538,6 @@ impl ZigBackend { BinOp::And => "and", BinOp::Or => "or", }; - let lhs_s = self.gen_expr(lhs); - let rhs_s = self.gen_expr(rhs); format!("({} {} {})", lhs_s, op_str, rhs_s) } diff --git a/src/lexer.rs b/src/lexer.rs index 2f58850..0d42c0c 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -36,6 +36,7 @@ pub enum Token { Minus, // - Star, // * Slash, // / + Percent, // % And, // && Or, // || Bang, // ! @@ -204,6 +205,10 @@ fn tokenize_raw(source: &str) -> Vec<(Token, Span)> { chars.next(); tokens.push((Token::Star, (pos, end_pos!()))); } + '%' => { + chars.next(); + tokens.push((Token::Percent, (pos, end_pos!()))); + } '.' => { chars.next(); tokens.push((Token::Dot, (pos, end_pos!()))); diff --git a/src/parser.rs b/src/parser.rs index b849d7e..f571c7f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -96,6 +96,7 @@ pub enum BinOp { Sub, Mul, Div, + Mod, Eq, NotEq, Lt, @@ -706,6 +707,7 @@ impl Parser { let op = match self.peek() { Token::Star => BinOp::Mul, Token::Slash => BinOp::Div, + Token::Percent => BinOp::Mod, _ => break, }; self.advance(); diff --git a/src/tests/codegen.rs b/src/tests/codegen.rs index 8b021df..ee6c2e0 100644 --- a/src/tests/codegen.rs +++ b/src/tests/codegen.rs @@ -281,3 +281,15 @@ fn test_codegen_export_const_hoisted() { let main_start = out.find("pub fn main").unwrap_or(out.len()); assert!(!out[main_start..].contains("const c ="), "got:\n{}", out); } + +#[test] +fn test_codegen_modulo() { + let out = compile( + r#" + export fn rem(a: i32, b: i32): i32 { + return a % b; + } + "#, + ); + assert!(out.contains("@rem(a, b)"), "got:\n{}", out); +} diff --git a/src/tests/fixtures/modulo.zy b/src/tests/fixtures/modulo.zy new file mode 100644 index 0000000..e768797 --- /dev/null +++ b/src/tests/fixtures/modulo.zy @@ -0,0 +1,3 @@ +export fn rem(a: i32, b: i32): i32 { + return a % b; +} diff --git a/src/tests/snapshots/zyre__tests__snapshots__codegen_snapshots@modulo.zy.snap b/src/tests/snapshots/zyre__tests__snapshots__codegen_snapshots@modulo.zy.snap new file mode 100644 index 0000000..0345f07 --- /dev/null +++ b/src/tests/snapshots/zyre__tests__snapshots__codegen_snapshots@modulo.zy.snap @@ -0,0 +1,9 @@ +--- +source: src/tests/snapshots.rs +assertion_line: 10 +expression: output +input_file: src/tests/fixtures/modulo.zy +--- +pub fn rem(a: i32, b: i32) i32 { + return @rem(a, b); +} diff --git a/src/tests/typechecker.rs b/src/tests/typechecker.rs index 476124a..e5a154b 100644 --- a/src/tests/typechecker.rs +++ b/src/tests/typechecker.rs @@ -257,3 +257,24 @@ fn test_if_expr_branch_type_mismatch() { "if branches have different types", ); } + +#[test] +fn test_modulo_ok() { + ok(r#" + export fn rem(a: i32, b: i32): i32 { return a % b; } + "#); +} + +#[test] +fn test_modulo_type_mismatch() { + err( + r#" + fn main(): void { + const x: i32 = 10; + const y: f64 = 3.0; + const _z = x % y; + } + "#, + "Binary op type mismatch", + ); +} diff --git a/src/typechecker.rs b/src/typechecker.rs index 6b9dff9..aa31f22 100644 --- a/src/typechecker.rs +++ b/src/typechecker.rs @@ -609,7 +609,7 @@ impl TypeChecker { let lhs_ty = self.check_expr(lhs, scope); let rhs_ty = self.check_expr(rhs, scope); match op { - BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div => { + BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Mod => { if lhs_ty != rhs_ty { self.error(format!( "Binary op type mismatch: '{}' vs '{}'",