use crate::error::{ErrorCode, PResult};
use crate::json::parser::{MAX_DEPTH, is_ws, scalar_byte, skip_comment};
use crate::stream::{Framer, Split};
use crate::swar::{escape_mask, find_byte, first_match, load_u64};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Mode {
Values,
Lines,
Array,
}
struct Scanner {
pos: usize,
depth: u32,
in_string: bool,
escaped: bool,
scalar: bool,
comment: Comment,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Comment {
No,
Line,
Block,
BlockStar,
}
impl Comment {
fn begin(buf: &[u8], at: usize) -> Option<Comment> {
match buf.get(at + 1)? {
b'/' => Some(Comment::Line),
b'*' => Some(Comment::Block),
_ => None,
}
}
fn step(&mut self, buf: &[u8], i: usize) -> Option<usize> {
match *self {
Comment::No => Some(i),
Comment::Line => {
let end = find_byte(buf, i, b'\n')?;
*self = Comment::No;
Some(end)
}
Comment::Block | Comment::BlockStar => {
let mut i = i;
while i < buf.len() {
let c = buf[i];
i += 1;
if *self == Comment::BlockStar && c == b'/' {
*self = Comment::No;
return Some(i);
}
*self = if c == b'*' {
Comment::BlockStar
} else {
Comment::Block
};
}
None
}
}
}
}
fn line_has_value(buf: &[u8], start: usize, end: usize, comments: bool) -> bool {
if !comments {
return start != end;
}
let line = &buf[..end];
let mut i = start;
while i < end {
match line[i] {
c if is_ws(c) => i += 1,
b'/' => match skip_comment(line, i) {
Some(next) => i = next,
None => return true,
},
_ => return true,
}
}
false
}
impl Scanner {
const fn new() -> Self {
Scanner {
pos: 0,
depth: 0,
in_string: false,
escaped: false,
scalar: false,
comment: Comment::No,
}
}
fn restart(&mut self, at: usize) {
*self = Scanner {
pos: at,
..Scanner::new()
};
}
fn advance(&mut self, buf: &[u8], comments: bool) -> PResult<Option<usize>> {
let n = buf.len();
let mut i = self.pos;
while i < n {
if self.comment != Comment::No {
match self.comment.step(buf, i) {
Some(next) => i = next,
None => {
i = n;
break;
}
}
continue;
}
if self.in_string {
if self.escaped {
self.escaped = false;
i += 1;
continue;
}
while i + 8 <= n {
let m = escape_mask(unsafe { load_u64(buf, i) });
if m != 0 {
i += first_match(m);
break;
}
i += 8;
}
if i >= n {
break;
}
match buf[i] {
b'\\' => {
self.escaped = true;
i += 1;
}
b'"' => {
self.in_string = false;
i += 1;
if self.depth == 0 {
self.pos = i;
return Ok(Some(i));
}
}
_ => i += 1,
}
continue;
}
let c = buf[i];
if self.scalar {
if scalar_byte(c) {
i += 1;
continue;
}
self.scalar = false;
self.pos = i;
return Ok(Some(i));
}
if comments && c == b'/' {
match Comment::begin(buf, i) {
Some(kind) => {
self.comment = kind;
i += 2;
continue;
}
None if i + 1 >= n => break,
None => {}
}
}
match c {
_ if is_ws(c) => i += 1,
b'"' => {
self.in_string = true;
i += 1;
}
b'{' | b'[' => {
self.depth += 1;
if self.depth > MAX_DEPTH {
self.pos = i;
return Err(ErrorCode::ExceededMaxDepth);
}
i += 1;
}
b'}' | b']' => {
if self.depth == 0 {
self.pos = i;
return Err(ErrorCode::UnexpectedCharacter);
}
self.depth -= 1;
i += 1;
if self.depth == 0 {
self.pos = i;
return Ok(Some(i));
}
}
_ if self.depth == 0 => {
if !scalar_byte(c) {
self.pos = i;
return Err(ErrorCode::UnexpectedCharacter);
}
self.scalar = true;
i += 1;
}
_ => i += 1,
}
}
self.pos = i;
Ok(None)
}
fn at_eof(&mut self, standalone: bool) -> PResult<usize> {
if self.scalar && standalone {
self.scalar = false;
return Ok(self.pos);
}
Err(ErrorCode::UnexpectedEnd)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
BeforeOpen,
FirstItem,
BeforeItem,
InItem,
AfterItem,
Done,
}
pub(crate) struct Splitter {
mode: Mode,
scan: Scanner,
state: State,
cursor: usize,
probe: usize,
comments: bool,
comment: Comment,
}
impl Splitter {
pub(crate) fn new(mode: Mode) -> Self {
Splitter {
mode,
scan: Scanner::new(),
state: match mode {
Mode::Array => State::BeforeOpen,
_ => State::BeforeItem,
},
cursor: 0,
probe: 0,
comments: false,
comment: Comment::No,
}
}
pub(crate) fn set_comments(&mut self, on: bool) {
self.comments = on;
self.comment = Comment::No;
self.scan.comment = Comment::No;
}
fn next_line(&mut self, buf: &[u8], eof: bool) -> PResult<Split> {
loop {
let (line_end, next) = match find_byte(buf, self.probe.max(self.cursor), b'\n') {
Some(p) => (p, p + 1),
None if eof => (buf.len(), buf.len()),
None => {
self.probe = buf.len();
return Ok(Split::Need);
}
};
let (start, end) = trim_ws(buf, self.cursor, line_end);
self.cursor = next;
self.probe = next;
if line_has_value(buf, start, end, self.comments) {
return Ok(Split::Item { start, end });
}
if eof && next >= buf.len() {
return Ok(Split::End);
}
}
}
fn next_scanned(&mut self, buf: &[u8], eof: bool) -> PResult<Split> {
loop {
match self.state {
State::BeforeOpen => {
let Some(c) = self.skip_ws(buf, eof)? else {
return if eof { Ok(Split::End) } else { Ok(Split::Need) };
};
if c != b'[' {
return Err(ErrorCode::ExpectedBracket);
}
self.cursor += 1;
self.state = State::FirstItem;
}
State::FirstItem => {
let Some(c) = self.skip_ws(buf, eof)? else {
return if eof {
Err(ErrorCode::UnexpectedEnd)
} else {
Ok(Split::Need)
};
};
if c == b']' {
self.cursor += 1;
self.state = State::Done;
continue;
}
self.scan.restart(self.cursor);
self.state = State::InItem;
}
State::BeforeItem => {
if self.skip_ws(buf, eof)?.is_none() {
return match (eof, self.mode) {
(true, Mode::Values) => Ok(Split::End),
(true, _) => Err(ErrorCode::UnexpectedEnd),
(false, _) => Ok(Split::Need),
};
}
self.scan.restart(self.cursor);
self.state = State::InItem;
}
State::InItem => {
let end = match self.scan.advance(buf, self.comments)? {
Some(end) => end,
None if eof => self.scan.at_eof(self.mode == Mode::Values)?,
None => return Ok(Split::Need),
};
let start = self.cursor;
self.cursor = end;
self.state = if self.mode == Mode::Array {
State::AfterItem
} else {
State::BeforeItem
};
return Ok(Split::Item { start, end });
}
State::AfterItem => {
let Some(c) = self.skip_ws(buf, eof)? else {
return if eof {
Err(ErrorCode::UnexpectedEnd)
} else {
Ok(Split::Need)
};
};
match c {
b',' => {
self.cursor += 1;
self.state = State::BeforeItem;
}
b']' => {
self.cursor += 1;
self.state = State::Done;
}
_ => return Err(ErrorCode::ExpectedComma),
}
}
State::Done => {
return match self.skip_ws(buf, eof)? {
Some(_) => Err(ErrorCode::TrailingContent),
None if eof => Ok(Split::End),
None => Ok(Split::Need),
};
}
}
}
}
fn skip_ws(&mut self, buf: &[u8], eof: bool) -> PResult<Option<u8>> {
let mut i = self.cursor;
loop {
if self.comment != Comment::No {
match self.comment.step(buf, i) {
Some(next) => i = next,
None => {
if eof && self.comment != Comment::Line {
return Err(ErrorCode::UnexpectedEnd);
}
self.cursor = buf.len();
return Ok(None);
}
}
continue;
}
match buf.get(i) {
Some(&c) if is_ws(c) => i += 1,
Some(b'/') if self.comments => match Comment::begin(buf, i) {
Some(kind) => {
self.comment = kind;
i += 2;
}
None if !eof && i + 1 >= buf.len() => {
self.cursor = i;
return Ok(None);
}
None => break,
},
_ => break,
}
}
self.cursor = i;
Ok(buf.get(i).copied())
}
}
impl Framer for Splitter {
fn next(&mut self, buf: &[u8], eof: bool) -> PResult<Split> {
match self.mode {
Mode::Lines => self.next_line(buf, eof),
_ => self.next_scanned(buf, eof),
}
}
#[inline]
fn consumed(&self) -> usize {
self.cursor
}
fn rebase(&mut self) {
let shift = self.cursor;
self.cursor = 0;
self.probe = self.probe.saturating_sub(shift);
self.scan.pos = self.scan.pos.saturating_sub(shift);
}
#[inline]
fn position(&self) -> usize {
self.cursor.max(self.scan.pos)
}
}
fn trim_ws(buf: &[u8], mut start: usize, mut end: usize) -> (usize, usize) {
while start < end && is_ws(buf[start]) {
start += 1;
}
while end > start && is_ws(buf[end - 1]) {
end -= 1;
}
(start, end)
}