///|
/// Quote one value without changing its Unicode contents. The delimiter selects
/// nonterminal names (double quote) versus terminal kinds/labels (single quote).
fn grammar_export_quote(
out : StringBuilder,
text : String,
quote : Char,
) -> Unit {
out.write_char(quote)
let hex = "0123456789abcdef"
for c in text {
if c == quote {
out.write_char('\\')
out.write_char(c)
continue
}
match c {
'\\' => out.write_string("\\\\")
'\n' => out.write_string("\\n")
'\r' => out.write_string("\\r")
'\t' => out.write_string("\\t")
_ => {
let value = c.to_int()
if value < 32 || value == 127 {
out.write_string("\\u")
for shift in [12, 8, 4, 0] {
out.write_view(
hex[(value >> shift) & 15:((value >> shift) & 15) + 1],
)
}
} else {
out.write_char(c)
}
}
}
}
out.write_char(quote)
}
///|
/// Canonical human CFG: explicit start, one production per line, all labels
/// explicit, every nonterminal double quoted, and a final newline. Rule and RHS
/// order are preserved, not sorted: they identify parser alternatives. Exporting
/// is read-only and returns the same string on every call for the same grammar.
/// Import using parse_grammar with sufficient FrontendLimits for this output;
/// export is not restricted to the importer's default budgets. No Lark claim.
pub fn export_grammar(grammar : Grammar) -> String {
let out = StringBuilder()
out.write_string("@start ")
grammar_export_quote(out, grammar.start(), '"')
out.write_string(";\n")
for rule in grammar.rules() {
grammar_export_quote(out, rule.lhs, '"')
out.write_string(" -> [")
grammar_export_quote(out, rule.label, '\'')
out.write_char(']')
for symbol in rule.rhs {
out.write_char(' ')
match symbol {
Terminal(kind) => grammar_export_quote(out, kind, '\'')
Nonterminal(name) => grammar_export_quote(out, name, '"')
}
}
out.write_string(";\n")
}
out.to_string()
}