rossi-cli 0.1.0

Command-line interface for the Rossi Event-B toolchain
//! Neovim operator table (`editors/neovim/lua/eventb/operators.lua`).
//!
//! Whole-file: a Lua module returning the same operator rows the LSP serves over
//! `rossi/operatorTable`, so the hand-written Neovim input method
//! (`lua/eventb/input.lua`) can drive its `\name` leader and as-you-type
//! substitution from one table without re-encoding the ASCII↔Unicode mapping.
//!
//! The rows come from [`eventb_lsp::server::operator_rows`] verbatim — the same
//! `ascii != unicode` filter and the same `symbolic`/`eager` policy the LSP and
//! the VS Code extension use — so editors and the language server can never
//! disagree (guarded by [`super::tests::nvim_operators_match_lsp_rows`]).
//!
//! Emitted shape:
//! ```lua
//! return {
//!   rows = {
//!     { ascii = "...", unicode = "...", aliases = { "..." }, symbolic = <bool>, eager = <bool> },
//!     ...
//!   },
//! }
//! ```

use eventb_lsp::server::operator_rows;

const NOTICE: &str =
    "Generated by `rossi gen-grammars` from the canonical operator table. Do not edit by hand.";

/// Render the complete `operators.lua` module.
pub fn render() -> String {
    let mut out = String::new();
    out.push_str(&format!("-- {NOTICE}\n"));
    out.push_str("return {\n");
    out.push_str("  rows = {\n");
    for row in operator_rows() {
        let aliases = row
            .aliases
            .iter()
            .map(|a| lua_string(a))
            .collect::<Vec<_>>()
            .join(", ");
        out.push_str(&format!(
            "    {{ ascii = {}, unicode = {}, aliases = {{{}}}, symbolic = {}, eager = {} }},\n",
            lua_string(&row.ascii),
            lua_string(&row.unicode),
            if aliases.is_empty() {
                aliases
            } else {
                format!(" {aliases} ")
            },
            lua_bool(row.symbolic),
            lua_bool(row.eager),
        ));
    }
    out.push_str("  },\n");
    out.push_str("}\n");
    out
}

fn lua_bool(b: bool) -> &'static str {
    if b { "true" } else { "false" }
}

/// A Lua double-quoted string literal. Lua's quoted strings accept the same
/// `\\` and `\"` escapes; control characters are escaped numerically. Operator
/// spellings never contain newlines, so this stays simple and deterministic.
pub(super) fn lua_string(s: &str) -> String {
    let mut out = String::with_capacity(s.len() + 2);
    out.push('"');
    for c in s.chars() {
        match c {
            '\\' => out.push_str("\\\\"),
            '"' => out.push_str("\\\""),
            '\n' => out.push_str("\\n"),
            '\r' => out.push_str("\\r"),
            '\t' => out.push_str("\\t"),
            _ => out.push(c),
        }
    }
    out.push('"');
    out
}