pnc 0.2.1

A fast, fully offline, terminal-only reverse Polish notation (RPN) calculator
use pnc_engine::Value;
use pnc_engine::parse;
use serde::Serialize;
use std::io;
use std::process::ExitCode;

#[derive(Serialize)]
struct JsonOutput {
    input: String,
    result: Option<Value>,
    stack: Vec<Value>,
    error: Option<String>,
}

pub fn eval_tokens(tokens: Vec<String>, json: bool) -> ExitCode {
    let mut stack = Vec::new();
    let line = tokens.join(" ");
    match parse::eval_line(&mut stack, &line) {
        Ok(()) => {
            let top = stack.last().cloned();
            if json {
                let out = JsonOutput {
                    input: line.clone(),
                    result: top,
                    stack: stack.clone(),
                    error: None,
                };
                match serde_json::to_string(&out) {
                    Ok(s) => println!("{}", s),
                    Err(_) => return ExitCode::from(1),
                }
            } else if let Some(v) = stack.last() {
                println!("{}", v);
            }
            ExitCode::SUCCESS
        }
        Err(e) => {
            if json {
                let out = JsonOutput {
                    input: line.clone(),
                    result: None,
                    stack: stack.clone(),
                    error: Some(e.to_string()),
                };
                match serde_json::to_string(&out) {
                    Ok(s) => println!("{}", s),
                    Err(_) => return ExitCode::from(1),
                }
            } else {
                eprintln!("{}", e);
            }
            ExitCode::from(1)
        }
    }
}

pub fn eval_stdin(json: bool) -> ExitCode {
    let mut line = String::new();
    if io::stdin().read_line(&mut line).is_err() {
        if json {
            let out = JsonOutput {
                input: String::new(),
                result: None,
                stack: Vec::new(),
                error: Some("io error: failed to read stdin".to_string()),
            };
            if let Ok(s) = serde_json::to_string(&out) {
                println!("{}", s);
            }
        } else {
            eprintln!("failed to read stdin");
        }
        return ExitCode::from(1);
    }
    // Normalize: strip single trailing newline (and preceding \r for CRLF).
    let mut input = line;
    if input.ends_with('\n') {
        input.pop();
        if input.ends_with('\r') {
            input.pop();
        }
    }
    let mut stack = Vec::new();
    match parse::eval_line(&mut stack, &input) {
        Ok(()) => {
            let top = stack.last().cloned();
            if json {
                let out = JsonOutput {
                    input: input.clone(),
                    result: top,
                    stack: stack.clone(),
                    error: None,
                };
                match serde_json::to_string(&out) {
                    Ok(s) => println!("{}", s),
                    Err(_) => return ExitCode::from(1),
                }
            } else if let Some(v) = stack.last() {
                println!("{}", v);
            }
            ExitCode::SUCCESS
        }
        Err(e) => {
            if json {
                let out = JsonOutput {
                    input: input.clone(),
                    result: None,
                    stack: stack.clone(),
                    error: Some(e.to_string()),
                };
                match serde_json::to_string(&out) {
                    Ok(s) => println!("{}", s),
                    Err(_) => return ExitCode::from(1),
                }
            } else {
                eprintln!("{}", e);
            }
            ExitCode::from(1)
        }
    }
}