#[cfg(target_arch = "wasm32")]
compile_error!("the qs-factor CLI is unavailable on wasm32 targets");
use rusqsieve::{Natural, engine};
use std::io::{self, IsTerminal, Read, Write};
use std::time::Instant;
#[derive(Clone, Copy, Eq, PartialEq)]
enum ProgressMode {
Auto,
Always,
Never,
}
struct Options {
progress: ProgressMode,
threads: Option<usize>,
}
fn main() {
if let Err(error) = run() {
eprintln!("qs-factor: {error}");
std::process::exit(2)
}
}
fn run() -> Result<(), String> {
let started = Instant::now();
let options = parse_options()?;
let mut text = String::new();
io::stdin()
.read_to_string(&mut text)
.map_err(|error| format!("cannot read stdin: {error}"))?;
let input = text.trim();
if input.is_empty() || input.bytes().any(|byte| !byte.is_ascii_digit()) {
return Err("input must be exactly one unsigned decimal integer".into());
}
let natural = Natural::<16>::from_decimal(input).map_err(|error| error.to_string())?;
if natural.is_zero() {
return Err("zero has no prime factorization".into());
}
if natural.is_one() {
return Ok(());
}
if natural.bit_len() > 512 {
return Err(format!(
"input is {} bits; qs-factor's SIQS engine supports at most 512 bits",
natural.bit_len()
));
}
let show_progress = options.progress == ProgressMode::Always
|| options.progress == ProgressMode::Auto && io::stderr().is_terminal();
let thread_count = options.threads.unwrap_or_else(default_parallelism);
if show_progress {
eprintln!(
"Factoring {}-bit input with {thread_count} worker{}",
natural.bit_len(),
if thread_count == 1 { "" } else { "s" }
);
}
let factors = engine::factor(natural.clone(), thread_count, |snapshot| {
if show_progress {
match snapshot.phase {
engine::EnginePhase::Preprocessing => eprint!("\npreprocessing"),
engine::EnginePhase::BuildingFactorBase => eprint!("\nbuilding factor base"),
engine::EnginePhase::Sieving => eprint!(
"\nsieving: {}/~{} relations, {} polynomials, {} workers",
snapshot.relations, snapshot.target, snapshot.polynomials, snapshot.workers
),
engine::EnginePhase::LinearAlgebra => {
eprint!("\nlinear algebra: {} relations", snapshot.relations)
}
engine::EnginePhase::Extracting => eprint!("\nextracting factors"),
}
let _ = io::stderr().flush();
}
})
.map_err(|error| format!("factor engine failed: {error}"))?;
if show_progress {
eprintln!();
}
let mut verified_product = Natural::<16>::ONE;
let mut rendered = Vec::with_capacity(factors.len());
for value in factors {
let decimal = value.to_string();
verified_product = verified_product
.checked_mul(&value)
.ok_or_else(|| "factor product exceeded input capacity".to_string())?;
rendered.push(decimal);
}
if verified_product != natural {
return Err("factor engine returned an incomplete factorization".into());
}
let stdout = io::stdout();
let mut output = stdout.lock();
for factor in rendered {
if let Err(error) = writeln!(output, "{factor}") {
if error.kind() == io::ErrorKind::BrokenPipe {
return Ok(());
}
return Err(format!("cannot write stdout: {error}"));
}
}
if show_progress {
eprintln!("elapsed: {:.3} s", started.elapsed().as_secs_f64());
}
Ok(())
}
fn default_parallelism() -> usize {
std::thread::available_parallelism().map_or(1, usize::from)
}
fn parse_options() -> Result<Options, String> {
let mut progress = ProgressMode::Auto;
let mut threads = None;
let mut args = std::env::args().skip(1);
while let Some(argument) = args.next() {
match argument.as_str() {
"--progress" => {
progress = match args.next().as_deref() {
Some("auto") => ProgressMode::Auto,
Some("always") => ProgressMode::Always,
Some("never") => ProgressMode::Never,
_ => return Err("--progress requires auto, always, or never".into()),
}
}
"--threads" => {
let value = args
.next()
.ok_or_else(|| "--threads requires auto or a positive integer".to_string())?;
threads = if value == "auto" {
None
} else {
let count = value
.parse::<usize>()
.map_err(|_| "--threads requires auto or a positive integer".to_string())?;
if count == 0 {
return Err("--threads must be greater than zero".into());
}
Some(count)
};
}
"-h" | "--help" => {
println!(
"Usage: qs-factor [OPTIONS]\n\
Reads one unsigned decimal integer from standard input.\n\n\
Options:\n \
--progress auto|always|never\n \
--threads auto|N\n \
-h, --help"
);
std::process::exit(0)
}
_ => return Err(format!("unknown argument: {argument}")),
}
}
Ok(Options { progress, threads })
}