rossi-cli 0.1.0

Command-line interface for the Rossi Event-B toolchain
//! Sublime Text operator table (`editors/sublime/EventB/operators.py`).
//!
//! Whole-file: a Python module exporting the same operator rows the LSP serves
//! over `rossi/operatorTable`, so the hand-written Sublime Text input method
//! (`EventB.py`) 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::sublime_operators_match_lsp_rows`]).
//!
//! Emitted shape:
//! ```python
//! OPERATOR_ROWS = [
//!     {"ascii": "...", "unicode": "...", "aliases": ["..."], "symbolic": True, "eager": True},
//!     ...
//! ]
//! ```

use eventb_lsp::server::operator_rows;

/// Render the complete `operators.py` module.
pub fn render() -> String {
    let mut out = String::new();
    out.push_str(
        "# Generated by `rossi gen-grammars` from the canonical operator table. Do not edit by hand.\n",
    );
    out.push_str("OPERATOR_ROWS = [\n");
    for row in operator_rows() {
        let aliases = row
            .aliases
            .iter()
            .map(|a| py_string(a))
            .collect::<Vec<_>>()
            .join(", ");
        out.push_str(&format!(
            "    {{\"ascii\": {}, \"unicode\": {}, \"aliases\": [{}], \"symbolic\": {}, \"eager\": {}}},\n",
            py_string(&row.ascii),
            py_string(&row.unicode),
            aliases,
            py_bool(row.symbolic),
            py_bool(row.eager),
        ));
    }
    out.push_str("]\n");
    out
}

pub(super) fn py_bool(b: bool) -> &'static str {
    if b { "True" } else { "False" }
}

/// A Python double-quoted string literal. Python's quoted strings accept `\\`
/// and `\"` escapes; control characters are escaped numerically. Operator
/// spellings never contain newlines, so this stays simple and deterministic.
pub(super) fn py_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
}