use crate::scan::{is_newline, is_unicode_space, Scanner};
use crate::{KdlError, KdlErrorKind};
pub(crate) fn skip_single_line_comment(s: &mut Scanner<'_>) -> Result<(), KdlError> {
while let Some(ch) = s.peek() {
if is_newline(ch) {
break;
}
s.advance();
}
Ok(())
}
pub(crate) fn skip_block_comment(s: &mut Scanner<'_>) -> Result<(), KdlError> {
let mut depth: usize = 1;
while depth > 0 {
match s.advance() {
Some('/') => {
if s.peek() == Some('*') {
s.advance();
depth += 1;
}
}
Some('*') => {
if s.peek() == Some('/') {
s.advance();
depth -= 1;
}
}
Some(_) => {}
None => return Err(s.make_error(KdlErrorKind::UnclosedBlockComment)),
}
}
Ok(())
}
pub(crate) fn skip_ws(s: &mut Scanner<'_>) -> Result<(), KdlError> {
loop {
match s.peek() {
Some(ch) if is_unicode_space(ch) => {
s.advance();
}
Some('/') if s.peek_next() == Some('*') => {
s.advance(); s.advance(); skip_block_comment(s)?;
}
_ => break,
}
}
Ok(())
}
pub(crate) fn skip_line_space(s: &mut Scanner<'_>) -> Result<(), KdlError> {
loop {
match s.peek() {
Some(ch) if is_unicode_space(ch) => {
s.advance();
}
Some(ch) if is_newline(ch) => {
s.consume_newline();
}
Some('/') => match s.peek_next() {
Some('/') => {
s.advance(); s.advance(); skip_single_line_comment(s)?;
}
Some('*') => {
s.advance(); s.advance(); skip_block_comment(s)?;
}
_ => break,
},
Some('\\') => {
if try_skip_escline(s)? {
continue;
}
break;
}
_ => break,
}
}
Ok(())
}
pub(crate) fn skip_node_space(s: &mut Scanner<'_>) -> Result<bool, KdlError> {
let start = s.byte_offset();
skip_ws(s)?;
let had_ws = s.byte_offset() != start;
let mut had_escline = false;
while try_skip_escline(s)? {
had_escline = true;
skip_ws(s)?;
}
Ok(had_ws || had_escline)
}
fn try_skip_escline(s: &mut Scanner<'_>) -> Result<bool, KdlError> {
if s.peek() != Some('\\') {
return Ok(false);
}
let saved = s.save();
s.advance(); skip_ws(s)?;
match s.peek() {
Some('/') if s.peek_next() == Some('/') => {
s.advance();
s.advance();
skip_single_line_comment(s)?;
s.consume_newline();
Ok(true)
}
Some(ch) if is_newline(ch) => {
s.consume_newline();
Ok(true)
}
None => {
Ok(true)
}
_ => {
s.restore(saved);
Ok(false)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_line_comment() {
let mut s = Scanner::new("hello\nworld");
skip_single_line_comment(&mut s).unwrap();
assert_eq!(s.peek(), Some('\n'));
}
#[test]
fn block_comment_simple() {
let mut s = Scanner::new(" comment */ rest");
skip_block_comment(&mut s).unwrap();
assert_eq!(s.peek(), Some(' '));
}
#[test]
fn block_comment_nested() {
let mut s = Scanner::new(" outer /* inner */ still */ rest");
skip_block_comment(&mut s).unwrap();
assert_eq!(s.peek(), Some(' '));
}
#[test]
fn block_comment_deeply_nested() {
let mut s = Scanner::new(" a /* b /* c */ */ */x");
skip_block_comment(&mut s).unwrap();
assert_eq!(s.peek(), Some('x'));
}
#[test]
fn unclosed_block_comment() {
let mut s = Scanner::new(" unclosed");
let err = skip_block_comment(&mut s).unwrap_err();
assert_eq!(err.kind, KdlErrorKind::UnclosedBlockComment);
}
#[test]
fn ws_skips_space_and_block_comments() {
let mut s = Scanner::new(" /* comment */ x");
skip_ws(&mut s).unwrap();
assert_eq!(s.peek(), Some('x'));
}
#[test]
fn escline_backslash_newline() {
let mut s = Scanner::new("\\\nrest");
let consumed = skip_node_space(&mut s).unwrap();
assert!(consumed);
assert_eq!(s.peek(), Some('r'));
}
#[test]
fn escline_backslash_comment_newline() {
let mut s = Scanner::new("\\ // comment\nrest");
let consumed = skip_node_space(&mut s).unwrap();
assert!(consumed);
assert_eq!(s.peek(), Some('r'));
}
}