use std::io::Read;
use std::path::PathBuf;
use logicaffeine_language::{compile::*, CompileOptions, OutputFormat};
use crate::ui::{self, CliError};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, clap::ValueEnum)]
pub enum LogicFormat {
#[default]
Unicode,
Latex,
Ascii,
Kripke,
}
impl From<LogicFormat> for OutputFormat {
fn from(f: LogicFormat) -> Self {
match f {
LogicFormat::Unicode => OutputFormat::Unicode,
LogicFormat::Latex => OutputFormat::LaTeX,
LogicFormat::Ascii => OutputFormat::SimpleFOL,
LogicFormat::Kripke => OutputFormat::Kripke,
}
}
}
pub(crate) fn cmd_logic(
sentence: Option<String>,
file: Option<PathBuf>,
format: LogicFormat,
all_readings: bool,
pragmatic: bool,
discourse: bool,
) -> Result<(), Box<dyn std::error::Error>> {
let input = read_input(sentence, file)?;
let options = CompileOptions { format: format.into(), pragmatic };
if discourse {
let sentences: Vec<&str> = input
.lines()
.map(str::trim)
.filter(|l| !l.is_empty())
.collect();
let fol = compile_discourse_with_options(&sentences, options)
.map_err(|e| parse_failure(&e, &input))?;
println!("{fol}");
return Ok(());
}
if all_readings {
let readings = collect_readings(&input, options);
if readings.is_empty() {
return match compile_with_options(&input, options) {
Ok(fol) => {
println!("1. {fol}");
Ok(())
}
Err(e) => Err(parse_failure(&e, &input).into()),
};
}
for (i, reading) in readings.iter().enumerate() {
println!("{}. {}", i + 1, reading);
}
return Ok(());
}
let fol = compile_with_options(&input, options).map_err(|e| parse_failure(&e, &input))?;
println!("{fol}");
Ok(())
}
pub(crate) fn collect_readings(input: &str, options: CompileOptions) -> Vec<String> {
let mut readings: Vec<String> = Vec::new();
for r in compile_all_scopes_with_options(input, options).unwrap_or_default() {
if !readings.contains(&r) {
readings.push(r);
}
}
for r in compile_forest_with_options(input, options) {
if !readings.contains(&r) {
readings.push(r);
}
}
readings
}
fn read_input(
sentence: Option<String>,
file: Option<PathBuf>,
) -> Result<String, Box<dyn std::error::Error>> {
let raw = if let Some(s) = sentence {
s
} else if let Some(path) = file {
std::fs::read_to_string(&path)
.map_err(|e| CliError::new(format!("cannot read {}: {e}", path.display())))?
} else {
use std::io::IsTerminal;
if std::io::stdin().is_terminal() {
return Err(CliError::with_hint(
"no input to compile",
"pass a sentence (largo logic \"Every cat sleeps.\"), a --file, or pipe text on stdin — or use `largo repl --logic` for an interactive session",
)
.exit_code(ui::EXIT_USAGE)
.into());
}
let mut buf = String::new();
std::io::stdin().read_to_string(&mut buf)?;
buf
};
let input = raw.trim().to_string();
if input.is_empty() {
return Err(CliError::with_hint(
"no input to compile",
"pass a sentence (largo logic \"Every cat sleeps.\"), a --file, or pipe text on stdin",
)
.exit_code(ui::EXIT_USAGE)
.into());
}
Ok(input)
}
fn parse_failure(e: &logicaffeine_language::ParseError, source: &str) -> CliError {
CliError::new(format!("could not parse the sentence\n{}", e.display_with_source(source)))
}