use alloc::{string::String, vec::Vec};
use crate::{
Grammar,
check::Checked,
rule::{Capture, Captures, Include, Kind, Rule},
};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
pub enum Style {
#[default]
Pretty,
Compact,
}
pub(crate) fn push_decimal(out: &mut String, mut value: u64) {
let mut digits = [0u8; 20];
let mut at = digits.len();
loop {
at -= 1;
digits[at] = b'0' + (value % 10) as u8;
value /= 10;
if value == 0 {
break;
}
}
for &d in digits.get(at..).unwrap_or(&[]) {
out.push(char::from(d));
}
}
pub(crate) fn json_string(out: &mut String, parts: &[&str]) {
out.push('"');
for text in parts {
let bytes = text.as_bytes();
let mut from = 0;
let mut at = 0;
while let Some(&b) = bytes.get(at) {
let (escape, width): (&str, usize) = match b {
b'"' => ("\\\"", 1),
b'\\' => ("\\\\", 1),
b'\n' => ("\\n", 1),
b'\r' => ("\\r", 1),
b'\t' => ("\\t", 1),
0x08 => ("\\b", 1),
0x0C => ("\\f", 1),
0x00..=0x1F => {
out.push_str(text.get(from..at).unwrap_or(""));
out.push_str("\\u00");
out.push(hex(b >> 4));
out.push(hex(b & 0x0F));
at += 1;
from = at;
continue;
}
0xE2 => match bytes.get(at + 1..at + 3) {
Some([0x80, 0xA8]) => ("\\u2028", 3),
Some([0x80, 0xA9]) => ("\\u2029", 3),
_ => {
at += 1;
continue;
}
},
_ => {
at += 1;
continue;
}
};
out.push_str(text.get(from..at).unwrap_or(""));
out.push_str(escape);
at += width;
from = at;
}
out.push_str(text.get(from..).unwrap_or(""));
}
out.push('"');
}
fn hex(nibble: u8) -> char {
char::from(b"0123456789abcdef"[usize::from(nibble & 0x0F)])
}
struct Writer<'o> {
out: &'o mut String,
pretty: bool,
level: usize,
empty: bool,
}
impl Writer<'_> {
fn newline(&mut self) {
if self.pretty {
self.out.push('\n');
for _ in 0..self.level {
self.out.push_str(" ");
}
}
}
fn open(&mut self, bracket: char) {
self.out.push(bracket);
self.level += 1;
self.empty = true;
}
fn close(&mut self, bracket: char) {
self.level = self.level.saturating_sub(1);
if !self.empty {
self.newline();
}
self.out.push(bracket);
self.empty = false;
}
fn next(&mut self) {
if !self.empty {
self.out.push(',');
}
self.empty = false;
self.newline();
}
fn key(&mut self, parts: &[&str]) {
self.next();
json_string(self.out, parts);
self.out.push_str(if self.pretty { ": " } else { ":" });
}
fn string_member(&mut self, key: &str, value: &[&str]) {
self.key(&[key]);
json_string(self.out, value);
}
fn group_key(&mut self, group: u32) {
self.next();
self.out.push('"');
push_decimal(self.out, u64::from(group));
self.out.push_str(if self.pretty { "\": " } else { "\":" });
}
}
enum Task<'g> {
Rule(&'g Rule),
Capture(&'g Capture),
Str(&'static str, Option<&'g str>),
Rules(&'static str, &'g [Rule]),
Captures(&'static str, &'g Captures),
Flag(&'static str, bool),
Key(&'g str),
Group(u32),
Element,
Close(char),
}
pub(crate) fn write(grammar: &Grammar, checked: &Checked<'_>, out: &mut String, pretty: bool) {
out.reserve(256 + 160 * (grammar.patterns.len() + checked.repository.len()));
let mut emitter = Emitter {
w: Writer {
out,
pretty,
level: 0,
empty: true,
},
work: Vec::new(),
sorted: Vec::new(),
};
emitter.grammar(grammar, checked);
if pretty {
emitter.w.out.push('\n');
}
}
struct Emitter<'o, 'g> {
w: Writer<'o>,
work: Vec<Task<'g>>,
sorted: Vec<&'g (u32, Capture)>,
}
impl<'g> Emitter<'_, 'g> {
fn grammar(&mut self, g: &'g Grammar, checked: &Checked<'g>) {
self.w.open('{');
if let Some(name) = &g.name {
self.w.string_member("name", &[name]);
}
self.w.string_member("scopeName", &[&g.scope_name]);
if !g.file_types.is_empty() {
self.w.key(&["fileTypes"]);
self.w.open('[');
for file_type in &g.file_types {
self.w.next();
json_string(self.w.out, &[file_type]);
}
self.w.close(']');
}
if let Some(regex) = &g.first_line_match {
self.w.string_member("firstLineMatch", &[regex]);
}
if let Some(selector) = &g.injection_selector {
self.w.string_member("injectionSelector", &[selector]);
}
self.w.key(&["patterns"]);
self.w.open('[');
self.push_elements(&g.patterns);
self.run();
self.w.close(']');
for (key, entries) in [
("repository", &checked.repository),
("injections", &checked.injections),
] {
if entries.is_empty() {
continue;
}
self.w.key(&[key]);
self.w.open('{');
self.work.push(Task::Close('}'));
for (name, rule) in entries.iter().rev() {
self.work.push(Task::Rule(rule));
self.work.push(Task::Key(name));
}
self.run();
}
self.w.close('}');
}
fn push_elements(&mut self, rules: &'g [Rule]) {
for rule in rules.iter().rev() {
self.work.push(Task::Rule(rule));
self.work.push(Task::Element);
}
}
fn run(&mut self) {
while let Some(task) = self.work.pop() {
match task {
Task::Rule(rule) => self.rule(rule),
Task::Capture(capture) => {
self.w.open('{');
self.work.push(Task::Close('}'));
self.work.push(Task::Rules("patterns", &capture.patterns));
self.work.push(Task::Str("name", capture.scope.as_deref()));
}
Task::Str(key, Some(value)) => self.w.string_member(key, &[value]),
Task::Str(_, None) => {}
Task::Rules(key, rules) => {
if !rules.is_empty() {
self.w.key(&[key]);
self.w.open('[');
self.work.push(Task::Close(']'));
self.push_elements(rules);
}
}
Task::Captures(key, captures) => self.captures(key, captures),
Task::Flag(key, true) => {
self.w.key(&[key]);
self.w.out.push('1');
}
Task::Flag(_, false) => {}
Task::Key(name) => self.w.key(&[name]),
Task::Group(group) => self.w.group_key(group),
Task::Element => self.w.next(),
Task::Close(bracket) => self.w.close(bracket),
}
}
}
fn rule(&mut self, rule: &'g Rule) {
self.w.open('{');
self.work.push(Task::Close('}'));
match &rule.0 {
Kind::Include(include) => self.include(include),
Kind::Patterns(rules) => self.work.push(Task::Rules("patterns", rules)),
Kind::Match(m) => self.work.extend([
Task::Captures("captures", &m.captures),
Task::Str("match", Some(&m.regex)),
Task::Str("name", m.scope.as_deref()),
]),
Kind::BeginEnd(r) => self.work.extend([
Task::Rules("patterns", &r.patterns),
Task::Flag("applyEndPatternLast", r.apply_end_pattern_last),
Task::Captures("captures", &r.captures),
Task::Captures("endCaptures", &r.end_captures),
Task::Str("end", Some(&r.end)),
Task::Captures("beginCaptures", &r.begin_captures),
Task::Str("begin", Some(&r.begin)),
Task::Str("contentName", r.content_scope.as_deref()),
Task::Str("name", r.scope.as_deref()),
]),
Kind::BeginWhile(r) => self.work.extend([
Task::Rules("patterns", &r.patterns),
Task::Captures("captures", &r.captures),
Task::Captures("whileCaptures", &r.while_captures),
Task::Str("while", Some(&r.while_)),
Task::Captures("beginCaptures", &r.begin_captures),
Task::Str("begin", Some(&r.begin)),
Task::Str("contentName", r.content_scope.as_deref()),
Task::Str("name", r.scope.as_deref()),
]),
}
}
fn include(&mut self, include: &'g Include) {
let value: [&str; 3] = match include {
Include::Repository(name) => ["#", name, ""],
Include::SelfRef => ["$self", "", ""],
Include::Base => ["$base", "", ""],
Include::Scope(scope) => [scope, "", ""],
Include::ScopeEntry(scope, name) => [scope, "#", name],
};
self.w.string_member("include", &value);
}
fn captures(&mut self, key: &'static str, captures: &'g Captures) {
if captures.is_empty() {
return;
}
self.w.key(&[key]);
self.w.open('{');
self.work.push(Task::Close('}'));
self.sorted.clear();
self.sorted.extend(captures.iter());
self.sorted.sort_unstable_by_key(|(group, _)| *group);
for (group, capture) in self.sorted.drain(..).rev() {
self.work.push(Task::Capture(capture));
self.work.push(Task::Group(*group));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn quoted(text: &str) -> String {
let mut out = String::new();
json_string(&mut out, &[text]);
out
}
#[test]
fn test_json_string_escapes() {
assert_eq!(quoted("plain"), "\"plain\"");
assert_eq!(quoted(""), "\"\"");
assert_eq!(quoted(r#"a"b\c"#), r#""a\"b\\c""#);
assert_eq!(quoted("\n\r\t\u{8}\u{c}"), r#""\n\r\t\b\f""#);
assert_eq!(quoted("\u{0}\u{1}\u{1f}"), r#""\u0000\u0001\u001f""#);
assert_eq!(quoted("x\u{2028}y\u{2029}z"), r#""x\u2028y\u2029z""#);
assert_eq!(quoted("é→\u{7f}/✓"), "\"é→\u{7f}/✓\"");
let mut out = String::new();
json_string(&mut out, &["a\"", "", "#b\n"]);
assert_eq!(out, r##""a\"#b\n""##);
}
#[test]
fn test_push_decimal() {
for (value, text) in [
(0, "0"),
(7, "7"),
(10, "10"),
(u64::MAX, "18446744073709551615"),
] {
let mut out = String::new();
push_decimal(&mut out, value);
assert_eq!(out, text);
}
}
#[test]
fn test_writer_layout() {
let mut out = String::new();
let mut w = Writer {
out: &mut out,
pretty: true,
level: 0,
empty: true,
};
w.open('{');
w.key(&["a"]);
w.open('[');
w.close(']');
w.key(&["b"]);
w.open('[');
w.next();
w.out.push('1');
w.close(']');
w.close('}');
assert_eq!(out, "{\n \"a\": [],\n \"b\": [\n 1\n ]\n}");
}
}