#![allow(clippy::needless_lifetimes)]
use super::array::parse_array;
use super::lex::{skip_ellipsis, skip_word_markers, skip_ws_and_comments};
use super::number::parse_number_token;
use super::strings::{
emit_json_string_from_lit, parse_one_string_key_strict, parse_string_literal_concat_fast,
};
use crate::emit::{Emitter, JRResult};
use crate::options::Options;
use crate::parser::parse_regex_literal;
use crate::parser::parse_symbol_or_unquoted_string;
use memchr::memchr2;
fn skip_line_comment_preserving_rbrace(input: &mut &str, opts: &Options) {
if input.is_empty() {
return;
}
let s = *input;
let after_ws = s.trim_start_matches([' ', '\t']);
let bytes = after_ws.as_bytes();
if bytes.starts_with(b"//") || (opts.tolerate_hash_comments && bytes.first() == Some(&b'#')) {
let (_skip, rest) = if bytes.starts_with(b"//") {
(2usize, &after_ws[2..])
} else {
(1usize, &after_ws[1..])
};
let rbytes = rest.as_bytes();
let mut i = 0usize;
while i < rbytes.len() {
let b = rbytes[i];
if b == b'\n' || b == b'\r' || b == b'}' {
break;
}
i += 1;
}
if i < rbytes.len() {
let mut new_view = &rest[i..];
if new_view.starts_with('\n') || new_view.starts_with('\r') {
new_view = &new_view[1..];
}
*input = new_view;
} else {
*input = "";
}
}
}
pub fn parse_object<'i, E: Emitter>(
input: &mut &'i str,
opts: &Options,
out: &mut E,
logger: &mut crate::parser::Logger,
) -> JRResult<()> {
if !input.starts_with('{') {
return Ok(());
}
*input = &input[1..];
out.emit_char('{')?;
if let Some('}') = fast_ws_to_only_rbrace(input) {
out.emit_char('}')?;
return Ok(());
}
skip_ws_and_comments(input, opts);
let mut first = true;
loop {
skip_ws_and_comments(input, opts);
if input.is_empty() {
out.emit_char('}')?;
break;
}
if input.starts_with(']') {
out.emit_char('}')?;
break;
}
if input.starts_with('}') {
*input = &input[1..];
out.emit_char('}')?;
break;
}
skip_word_markers(input, &opts.word_comment_markers);
while skip_ellipsis(input) {
skip_ws_and_comments(input, opts);
}
if let Some(delim) = fast_ws_to_comma_or_rbrace(input) {
match delim {
',' => { }
'}' => {
out.emit_char('}')?;
break;
}
_ => unreachable!(),
}
} else {
if input.starts_with(',') {
*input = &input[1..];
}
if input.starts_with('}') {
*input = &input[1..];
out.emit_char('}')?;
break;
}
}
if !first {
out.emit_char(',')?;
}
first = false;
skip_ws_and_comments(input, opts);
if input.is_empty() {
out.emit_char('}')?;
break;
}
let key_str = if input.starts_with('"') || input.starts_with('\'') {
let k = parse_one_string_key_strict(input)?;
emit_json_string_from_lit(out, &k, opts.ensure_ascii)?;
k
} else {
let key = take_key_until_delim_fast(input)
.unwrap_or_else(|| take_until_delim(input, &[':', '}', ',']));
let k = key.trim();
emit_json_string_from_lit(out, k, opts.ensure_ascii)?;
k.to_string()
};
skip_ws_and_comments(input, opts);
if input.starts_with(':') {
*input = &input[1..];
out.emit_char(':')?;
} else {
out.emit_char(':')?; }
skip_ws_and_comments(input, opts);
if input.is_empty() {
out.emit_char('}')?;
break;
}
skip_word_markers(input, &opts.word_comment_markers);
while skip_ellipsis(input) {
skip_ws_and_comments(input, opts);
}
logger.push_key(key_str);
let c = input.chars().next().unwrap();
match c {
'{' => super::object::parse_object(input, opts, out, logger)?,
'[' => parse_array(input, opts, out, logger)?,
'"' | '\'' => {
if c == '"' {
let s_val = *input;
let mut i = 1usize;
let mut escape = false;
let mut first_comma: Option<usize> = None;
let mut close_pos: Option<usize> = None;
while i < s_val.len() {
let ch = s_val[i..].chars().next().unwrap();
let l = ch.len_utf8();
i += l;
if escape {
escape = false;
continue;
}
if ch == '\\' {
escape = true;
continue;
}
if ch == ',' && first_comma.is_none() {
first_comma = Some(i - l);
}
if ch == '"' {
close_pos = Some(i - l);
break;
}
}
if let Some(cp) = close_pos {
let mut look = &s_val[cp + 1..];
super::lex::skip_ws_and_comments(&mut look, opts);
let after_ok = look.is_empty()
|| look.starts_with(',')
|| look.starts_with('}')
|| look.starts_with(']');
if !after_ok {
if let Some(comma_i) = first_comma {
let content = &s_val[1..comma_i];
emit_json_string_from_lit(out, content, opts.ensure_ascii)?;
*input = &s_val[comma_i..];
logger.pop_key();
if let Some(delim) =
super::object::fast_ws_to_comma_or_rbrace(input)
{
match delim {
',' => { }
'}' => {
out.emit_char('}')?;
return Ok(());
}
_ => {}
}
}
continue;
}
}
}
}
parse_string_literal_concat_fast(input, opts, out)?
}
'/' => parse_regex_literal(input, opts, out)?,
c if c == '-' || c == '.' || c.is_ascii_digit() => {
parse_number_token(input, opts, out)?
}
_ => parse_symbol_or_unquoted_string(input, opts, out, logger)?,
}
logger.pop_key();
if let Some(delim) = fast_ws_to_comma_or_rbrace(input) {
match delim {
',' => { }
'}' => {
out.emit_char('}')?;
break;
}
_ => unreachable!(),
}
} else {
skip_line_comment_preserving_rbrace(input, opts);
skip_ws_and_comments(input, opts);
if input.starts_with('}') {
*input = &input[1..];
out.emit_char('}')?;
break;
}
if input.starts_with(',') {
*input = &input[1..];
}
}
}
Ok(())
}
fn take_until_delim<'i>(input: &mut &'i str, delims: &[char]) -> &'i str {
let s = *input;
let mut end = 0usize;
for (i, ch) in s.char_indices() {
if delims.contains(&ch) || ch == '\n' || ch == '\r' {
break;
}
end = i + ch.len_utf8();
}
*input = &s[end..];
&s[..end]
}
#[inline]
fn take_key_until_delim_fast<'i>(input: &mut &'i str) -> Option<&'i str> {
let s = *input;
if s.is_empty() {
return Some("");
}
let b = s.as_bytes();
let mut i = 0usize;
while i < b.len() {
match b[i] {
b' ' | b'\t' | b'\n' | b'\r' | b',' | b'{' | b'}' | b'[' | b']' | b'(' | b')'
| b':' | b'"' | b'\'' => break,
b'/' => {
if i + 1 < b.len() && (b[i + 1] == b'/' || b[i + 1] == b'*') {
break;
}
i += 1;
}
_ => i += 1,
}
}
let key = &s[..i];
*input = &s[i..];
Some(key)
}
#[inline]
fn fast_ws_to_only_rbrace(input: &mut &str) -> Option<char> {
let s = *input;
if s.is_empty() {
return None;
}
let bytes = s.as_bytes();
if let Some(pos) = memchr2(b',', b'}', bytes) {
if bytes[pos] == b',' {
return None;
}
for &b in &bytes[..pos] {
match b {
b' ' | b'\t' | b'\n' | b'\r' => {}
_ => return None,
}
}
*input = &s[pos + 1..];
Some('}')
} else {
None
}
}
#[inline]
fn fast_ws_to_comma_or_rbrace(input: &mut &str) -> Option<char> {
let s = *input;
if s.is_empty() {
return None;
}
let bytes = s.as_bytes();
if let Some(pos) = memchr2(b',', b'}', bytes) {
for &b in &bytes[..pos] {
match b {
b' ' | b'\t' | b'\n' | b'\r' => {}
_ => return None,
}
}
let delim = bytes[pos] as char;
*input = &s[pos + 1..];
Some(delim)
} else {
None
}
}