use clap::{Parser, Subcommand};
use gauss_int::{BigInt, GaussInt};
#[derive(Parser)]
#[command(name = "gauss", about = "Gaussian integer and number theory CLI")]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Add { a: String, b: String },
Sub { a: String, b: String },
Mul { a: String, b: String },
Div { a: String, b: String },
Gcd { a: String, b: String },
Norm { z: String },
Conj { z: String },
#[command(name = "is-prime")]
IsPrime { n: String },
Factor { n: String },
Totient { n: String },
Jacobi { a: String, n: String },
Crt { pairs: Vec<String> },
}
fn parse_gauss(s: &str) -> Result<GaussInt, String> {
let s = s.trim();
if s == "0" {
return Ok(GaussInt::from_i64(0, 0));
}
if let Some(before_i) = s.strip_suffix('i') {
if before_i.is_empty() {
return Ok(GaussInt::from_i64(0, 1));
}
if before_i == "+" {
return Ok(GaussInt::from_i64(0, 1));
}
if before_i == "-" {
return Ok(GaussInt::from_i64(0, -1));
}
let has_separator = before_i[1..].contains('+') || before_i[1..].contains('-');
if !has_separator {
let imag = before_i
.parse::<i64>()
.map_err(|_| format!("invalid Gaussian integer: {}", s))?;
return Ok(GaussInt::from_i64(0, imag));
}
let sep_pos = before_i[1..]
.find(['+', '-'])
.map(|pos| pos + 1) .ok_or_else(|| format!("invalid Gaussian integer: {}", s))?;
let real_str = &before_i[..sep_pos];
let imag_str = &before_i[sep_pos..];
let real: i64 = real_str
.parse()
.map_err(|_| format!("invalid real part: {}", real_str))?;
let imag: i64 = imag_str
.parse()
.map_err(|_| format!("invalid imaginary part: {}", imag_str))?;
return Ok(GaussInt::from_i64(real, imag));
}
let real: i64 = s.parse().map_err(|_| format!("invalid number: {}", s))?;
Ok(GaussInt::from_i64(real, 0))
}
fn main() {
let cli = Cli::parse();
match cli.command {
Commands::Add { a, b } => {
let z1 = parse_gauss(&a).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
let z2 = parse_gauss(&b).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
println!("{}", &z1 + &z2);
}
Commands::Sub { a, b } => {
let z1 = parse_gauss(&a).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
let z2 = parse_gauss(&b).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
println!("{}", &z1 - &z2);
}
Commands::Mul { a, b } => {
let z1 = parse_gauss(&a).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
let z2 = parse_gauss(&b).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
println!("{}", &z1 * &z2);
}
Commands::Div { a, b } => {
let z1 = parse_gauss(&a).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
let z2 = parse_gauss(&b).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
match z1.div_rem(&z2) {
Some((q, r)) => println!("quotient: {}\nremainder: {}", q, r),
None => println!("division by zero"),
}
}
Commands::Gcd { a, b } => {
let z1 = parse_gauss(&a).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
let z2 = parse_gauss(&b).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
println!("{}", z1.gcd(&z2));
}
Commands::Norm { z } => {
let z = parse_gauss(&z).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
println!("{}", z.norm());
}
Commands::Conj { z } => {
let z = parse_gauss(&z).unwrap_or_else(|e| {
eprintln!("Error: {}", e);
std::process::exit(1);
});
println!("{}", z.conjugate());
}
Commands::IsPrime { n } => {
let n = BigInt::from_string(&n).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", n);
std::process::exit(1);
});
println!("{}", gauss_int::number_theory::is_prime(&n));
}
Commands::Factor { n } => {
let n = BigInt::from_string(&n).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", n);
std::process::exit(1);
});
let factors = gauss_int::number_theory::factorize(&n);
for (p, e) in &factors {
println!("{}^{}", p, e);
}
}
Commands::Totient { n } => {
let n = BigInt::from_string(&n).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", n);
std::process::exit(1);
});
println!("{}", gauss_int::number_theory::euler_totient(&n));
}
Commands::Jacobi { a, n } => {
let a = BigInt::from_string(&a).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", a);
std::process::exit(1);
});
let n = BigInt::from_string(&n).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", n);
std::process::exit(1);
});
println!("{}", gauss_int::number_theory::jacobi_symbol(&a, &n));
}
Commands::Crt { pairs } => {
if pairs.len() < 2 || pairs.len() % 2 != 0 {
eprintln!("Error: CRT requires pairs of a m values (e.g., '2 3 3 5' for x≡2 mod3, x≡3 mod5)");
std::process::exit(1);
}
let congruences: Vec<(BigInt, BigInt)> = pairs
.chunks(2)
.map(|c| {
let a = BigInt::from_string(&c[0]).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", c[0]);
std::process::exit(1);
});
let m = BigInt::from_string(&c[1]).unwrap_or_else(|| {
eprintln!("Error: invalid number: {}", c[1]);
std::process::exit(1);
});
(a, m)
})
.collect();
match gauss_int::number_theory::crt(&congruences) {
Some(x) => println!("{}", x),
None => println!("no solution"),
}
}
}
}