#![allow(clippy::cast_possible_wrap)]
#![allow(clippy::cast_sign_loss)]
use std::{borrow::Cow, char, collections::VecDeque, error::Error, fmt};
use crate::{
char_traits::{
as_hex, is_anchor_char, is_blank_or_breakz, is_break, is_breakz, is_flow, is_hex,
is_tag_char, is_uri_char,
},
input::{Input, SkipTabs},
};
#[derive(Clone, Copy, PartialEq, Debug, Eq)]
pub enum TEncoding {
Utf8,
}
#[derive(Clone, Copy, PartialEq, Debug, Eq, Hash, PartialOrd, Ord)]
pub enum ScalarStyle {
Plain,
SingleQuoted,
DoubleQuoted,
Literal,
Folded,
}
#[derive(Clone, Copy, PartialEq, Debug, Eq, Default)]
pub struct Marker {
index: usize,
line: usize,
col: usize,
}
impl Marker {
#[must_use]
pub fn new(index: usize, line: usize, col: usize) -> Marker {
Marker { index, line, col }
}
#[must_use]
pub fn index(&self) -> usize {
self.index
}
#[must_use]
pub fn line(&self) -> usize {
self.line
}
#[must_use]
pub fn col(&self) -> usize {
self.col
}
}
#[derive(Clone, Copy, PartialEq, Debug, Eq, Default)]
pub struct Span {
pub start: Marker,
pub end: Marker,
}
impl Span {
#[must_use]
pub fn new(start: Marker, end: Marker) -> Span {
Span { start, end }
}
#[must_use]
pub fn empty(mark: Marker) -> Span {
Span {
start: mark,
end: mark,
}
}
#[must_use]
pub fn len(&self) -> usize {
self.end.index - self.start.index
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
#[derive(Clone, PartialEq, Debug, Eq)]
pub struct ScanError {
mark: Marker,
info: String,
}
impl ScanError {
#[must_use]
pub fn new(loc: Marker, info: String) -> ScanError {
ScanError { mark: loc, info }
}
#[must_use]
pub fn new_str(loc: Marker, info: &str) -> ScanError {
ScanError {
mark: loc,
info: info.to_owned(),
}
}
#[must_use]
pub fn marker(&self) -> &Marker {
&self.mark
}
#[must_use]
pub fn info(&self) -> &str {
self.info.as_ref()
}
}
impl Error for ScanError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
None
}
}
impl fmt::Display for ScanError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(
formatter,
"{} at byte {} line {} column {}",
self.info,
self.mark.index,
self.mark.line,
self.mark.col + 1,
)
}
}
#[derive(Clone, PartialEq, Debug, Eq)]
pub enum TokenType<'input> {
StreamStart(TEncoding),
StreamEnd,
VersionDirective(
u32,
u32,
),
TagDirective(
Cow<'input, str>,
Cow<'input, str>,
),
DocumentStart,
DocumentEnd,
BlockSequenceStart,
BlockMappingStart,
BlockEnd,
FlowSequenceStart,
FlowSequenceEnd,
FlowMappingStart,
FlowMappingEnd,
BlockEntry,
FlowEntry,
Key,
Value,
Alias(Cow<'input, str>),
Anchor(Cow<'input, str>),
Tag(
String,
String,
),
Scalar(ScalarStyle, Cow<'input, str>),
}
#[derive(Clone, PartialEq, Debug, Eq)]
pub struct Token<'input>(pub Span, pub TokenType<'input>);
#[derive(Clone, PartialEq, Debug, Eq)]
struct SimpleKey {
possible: bool,
required: bool,
token_number: usize,
mark: Marker,
}
impl SimpleKey {
fn new(mark: Marker) -> SimpleKey {
SimpleKey {
possible: false,
required: false,
token_number: 0,
mark,
}
}
}
#[derive(Clone, Debug, Default)]
struct Indent {
indent: isize,
needs_block_end: bool,
}
#[derive(Debug, PartialEq)]
enum ImplicitMappingState {
Possible,
Inside,
}
#[derive(Debug)]
#[allow(clippy::struct_excessive_bools)]
pub struct Scanner<'input, T> {
input: T,
mark: Marker,
tokens: VecDeque<Token<'input>>,
error: Option<ScanError>,
stream_start_produced: bool,
stream_end_produced: bool,
adjacent_value_allowed_at: usize,
simple_key_allowed: bool,
simple_keys: Vec<SimpleKey>,
indent: isize,
indents: Vec<Indent>,
flow_level: u8,
tokens_parsed: usize,
token_available: bool,
leading_whitespace: bool,
flow_mapping_started: bool,
implicit_flow_mapping_states: Vec<ImplicitMappingState>,
buf_leading_break: String,
buf_trailing_breaks: String,
buf_whitespaces: String,
}
impl<'input, T: Input> Iterator for Scanner<'input, T> {
type Item = Token<'input>;
fn next(&mut self) -> Option<Self::Item> {
if self.error.is_some() {
return None;
}
match self.next_token() {
Ok(Some(tok)) => {
debug_print!(
" \x1B[;32m\u{21B3} {:?} \x1B[;36m{:?}\x1B[;m",
tok.1,
tok.0
);
Some(tok)
}
Ok(tok) => tok,
Err(e) => {
self.error = Some(e);
None
}
}
}
}
pub type ScanResult = Result<(), ScanError>;
impl<'input, T: Input> Scanner<'input, T> {
pub fn new(input: T) -> Self {
Scanner {
input,
mark: Marker::new(0, 1, 0),
tokens: VecDeque::new(),
error: None,
stream_start_produced: false,
stream_end_produced: false,
adjacent_value_allowed_at: 0,
simple_key_allowed: true,
simple_keys: Vec::new(),
indent: -1,
indents: Vec::new(),
flow_level: 0,
tokens_parsed: 0,
token_available: false,
leading_whitespace: true,
flow_mapping_started: false,
implicit_flow_mapping_states: vec![],
buf_leading_break: String::new(),
buf_trailing_breaks: String::new(),
buf_whitespaces: String::new(),
}
}
#[inline]
pub fn get_error(&self) -> Option<ScanError> {
self.error.clone()
}
#[inline]
fn skip_blank(&mut self) {
self.input.skip();
self.mark.index += 1;
self.mark.col += 1;
}
#[inline]
fn skip_non_blank(&mut self) {
self.input.skip();
self.mark.index += 1;
self.mark.col += 1;
self.leading_whitespace = false;
}
#[inline]
fn skip_n_non_blank(&mut self, count: usize) {
self.input.skip_n(count);
self.mark.index += count;
self.mark.col += count;
self.leading_whitespace = false;
}
#[inline]
fn skip_nl(&mut self) {
self.input.skip();
self.mark.index += 1;
self.mark.col = 0;
self.mark.line += 1;
self.leading_whitespace = true;
}
#[inline]
fn skip_linebreak(&mut self) {
if self.input.next_2_are('\r', '\n') {
self.skip_blank();
self.skip_nl();
} else if self.input.next_is_break() {
self.skip_nl();
}
}
#[inline]
pub fn stream_started(&self) -> bool {
self.stream_start_produced
}
#[inline]
pub fn stream_ended(&self) -> bool {
self.stream_end_produced
}
#[inline]
pub fn mark(&self) -> Marker {
self.mark
}
#[inline]
fn read_break(&mut self, s: &mut String) {
self.skip_break();
s.push('\n');
}
#[inline]
fn skip_break(&mut self) {
let c = self.input.peek();
let nc = self.input.peek_nth(1);
debug_assert!(is_break(c));
if c == '\r' && nc == '\n' {
self.skip_blank();
}
self.skip_nl();
}
fn insert_token(&mut self, pos: usize, tok: Token<'input>) {
let old_len = self.tokens.len();
assert!(pos <= old_len);
self.tokens.insert(pos, tok);
}
fn allow_simple_key(&mut self) {
self.simple_key_allowed = true;
}
fn disallow_simple_key(&mut self) {
self.simple_key_allowed = false;
}
pub fn fetch_next_token(&mut self) -> ScanResult {
self.input.lookahead(1);
if !self.stream_start_produced {
self.fetch_stream_start();
return Ok(());
}
self.skip_to_next_token()?;
debug_print!(
" \x1B[38;5;244m\u{2192} fetch_next_token after whitespace {:?} {:?}\x1B[m",
self.mark,
self.input.peek()
);
self.stale_simple_keys()?;
let mark = self.mark;
self.unroll_indent(mark.col as isize);
self.input.lookahead(4);
if self.input.next_is_z() {
self.fetch_stream_end()?;
return Ok(());
}
if self.mark.col == 0 {
if self.input.next_char_is('%') {
return self.fetch_directive();
} else if self.input.next_is_document_start() {
return self.fetch_document_indicator(TokenType::DocumentStart);
} else if self.input.next_is_document_end() {
self.fetch_document_indicator(TokenType::DocumentEnd)?;
self.skip_ws_to_eol(SkipTabs::Yes)?;
if !self.input.next_is_breakz() {
return Err(ScanError::new_str(
self.mark,
"invalid content after document end marker",
));
}
return Ok(());
}
}
if (self.mark.col as isize) < self.indent {
return Err(ScanError::new_str(self.mark, "invalid indentation"));
}
let c = self.input.peek();
let nc = self.input.peek_nth(1);
match c {
'[' => self.fetch_flow_collection_start(TokenType::FlowSequenceStart),
'{' => self.fetch_flow_collection_start(TokenType::FlowMappingStart),
']' => self.fetch_flow_collection_end(TokenType::FlowSequenceEnd),
'}' => self.fetch_flow_collection_end(TokenType::FlowMappingEnd),
',' => self.fetch_flow_entry(),
'-' if is_blank_or_breakz(nc) => self.fetch_block_entry(),
'?' if is_blank_or_breakz(nc) => self.fetch_key(),
':' if is_blank_or_breakz(nc) => self.fetch_value(),
':' if self.flow_level > 0
&& (is_flow(nc) || self.mark.index == self.adjacent_value_allowed_at) =>
{
self.fetch_flow_value()
}
'*' => self.fetch_anchor(true),
'&' => self.fetch_anchor(false),
'!' => self.fetch_tag(),
'|' if self.flow_level == 0 => self.fetch_block_scalar(true),
'>' if self.flow_level == 0 => self.fetch_block_scalar(false),
'\'' => self.fetch_flow_scalar(true),
'"' => self.fetch_flow_scalar(false),
'-' if !is_blank_or_breakz(nc) => self.fetch_plain_scalar(),
':' | '?' if !is_blank_or_breakz(nc) && self.flow_level == 0 => {
self.fetch_plain_scalar()
}
'%' | '@' | '`' => Err(ScanError::new(
self.mark,
format!("unexpected character: `{c}'"),
)),
_ => self.fetch_plain_scalar(),
}
}
pub fn next_token(&mut self) -> Result<Option<Token<'input>>, ScanError> {
if self.stream_end_produced {
return Ok(None);
}
if !self.token_available {
self.fetch_more_tokens()?;
}
let Some(t) = self.tokens.pop_front() else {
return Err(ScanError::new_str(
self.mark,
"did not find expected next token",
));
};
self.token_available = false;
self.tokens_parsed += 1;
if let TokenType::StreamEnd = t.1 {
self.stream_end_produced = true;
}
Ok(Some(t))
}
pub fn fetch_more_tokens(&mut self) -> ScanResult {
let mut need_more;
loop {
if self.tokens.is_empty() {
need_more = true;
} else {
need_more = false;
self.stale_simple_keys()?;
for sk in &self.simple_keys {
if sk.possible && sk.token_number == self.tokens_parsed {
need_more = true;
break;
}
}
}
if !need_more {
break;
}
self.fetch_next_token()?;
}
self.token_available = true;
Ok(())
}
fn stale_simple_keys(&mut self) -> ScanResult {
for sk in &mut self.simple_keys {
if sk.possible
&& self.flow_level == 0
&& (sk.mark.line < self.mark.line || sk.mark.index + 1024 < self.mark.index)
{
if sk.required {
return Err(ScanError::new_str(self.mark, "simple key expect ':'"));
}
sk.possible = false;
}
}
Ok(())
}
fn skip_to_next_token(&mut self) -> ScanResult {
loop {
match self.input.look_ch() {
'\t' if self.is_within_block()
&& self.leading_whitespace
&& (self.mark.col as isize) < self.indent =>
{
self.skip_ws_to_eol(SkipTabs::Yes)?;
if !self.input.next_is_breakz() {
return Err(ScanError::new_str(
self.mark,
"tabs disallowed within this context (block indentation)",
));
}
}
'\t' | ' ' => self.skip_blank(),
'\n' | '\r' => {
self.input.lookahead(2);
self.skip_linebreak();
if self.flow_level == 0 {
self.allow_simple_key();
}
}
'#' => {
let comment_length = self.input.skip_while_non_breakz();
self.mark.index += comment_length;
self.mark.col += comment_length;
}
_ => break,
}
}
Ok(())
}
fn skip_yaml_whitespace(&mut self) -> ScanResult {
let mut need_whitespace = true;
loop {
match self.input.look_ch() {
' ' => {
self.skip_blank();
need_whitespace = false;
}
'\n' | '\r' => {
self.input.lookahead(2);
self.skip_linebreak();
if self.flow_level == 0 {
self.allow_simple_key();
}
need_whitespace = false;
}
'#' => {
let comment_length = self.input.skip_while_non_breakz();
self.mark.index += comment_length;
self.mark.col += comment_length;
}
_ => break,
}
}
if need_whitespace {
Err(ScanError::new_str(self.mark(), "expected whitespace"))
} else {
Ok(())
}
}
fn skip_ws_to_eol(&mut self, skip_tabs: SkipTabs) -> Result<SkipTabs, ScanError> {
let (n_bytes, result) = self.input.skip_ws_to_eol(skip_tabs);
self.mark.col += n_bytes;
self.mark.index += n_bytes;
result.map_err(|msg| ScanError::new_str(self.mark, msg))
}
fn fetch_stream_start(&mut self) {
let mark = self.mark;
self.indent = -1;
self.stream_start_produced = true;
self.allow_simple_key();
self.tokens.push_back(Token(
Span::empty(mark),
TokenType::StreamStart(TEncoding::Utf8),
));
self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0)));
}
fn fetch_stream_end(&mut self) -> ScanResult {
if self.mark.col != 0 {
self.mark.col = 0;
self.mark.line += 1;
}
for sk in &mut self.simple_keys {
if sk.required && sk.possible {
return Err(ScanError::new_str(self.mark, "simple key expected"));
}
sk.possible = false;
}
self.unroll_indent(-1);
self.remove_simple_key()?;
self.disallow_simple_key();
self.tokens
.push_back(Token(Span::empty(self.mark), TokenType::StreamEnd));
Ok(())
}
fn fetch_directive(&mut self) -> ScanResult {
self.unroll_indent(-1);
self.remove_simple_key()?;
self.disallow_simple_key();
let tok = self.scan_directive()?;
self.tokens.push_back(tok);
Ok(())
}
fn scan_directive(&mut self) -> Result<Token<'input>, ScanError> {
let start_mark = self.mark;
self.skip_non_blank();
let name = self.scan_directive_name()?;
let tok = match name.as_ref() {
"YAML" => self.scan_version_directive_value(&start_mark)?,
"TAG" => self.scan_tag_directive_value(&start_mark)?,
_ => {
let line_len = self.input.skip_while_non_breakz();
self.mark.index += line_len;
self.mark.col += line_len;
Token(
Span::new(start_mark, self.mark),
TokenType::TagDirective(Cow::default(), Cow::default()),
)
}
};
self.skip_ws_to_eol(SkipTabs::Yes)?;
if self.input.next_is_breakz() {
self.input.lookahead(2);
self.skip_linebreak();
Ok(tok)
} else {
Err(ScanError::new_str(
start_mark,
"while scanning a directive, did not find expected comment or line break",
))
}
}
fn scan_version_directive_value(&mut self, mark: &Marker) -> Result<Token<'input>, ScanError> {
let n_blanks = self.input.skip_while_blank();
self.mark.index += n_blanks;
self.mark.col += n_blanks;
let major = self.scan_version_directive_number(mark)?;
if self.input.peek() != '.' {
return Err(ScanError::new_str(
*mark,
"while scanning a YAML directive, did not find expected digit or '.' character",
));
}
self.skip_non_blank();
let minor = self.scan_version_directive_number(mark)?;
Ok(Token(
Span::new(*mark, self.mark),
TokenType::VersionDirective(major, minor),
))
}
fn scan_directive_name(&mut self) -> Result<String, ScanError> {
let start_mark = self.mark;
let mut string = String::new();
let n_chars = self.input.fetch_while_is_alpha(&mut string);
self.mark.index += n_chars;
self.mark.col += n_chars;
if string.is_empty() {
return Err(ScanError::new_str(
start_mark,
"while scanning a directive, could not find expected directive name",
));
}
if !is_blank_or_breakz(self.input.peek()) {
return Err(ScanError::new_str(
start_mark,
"while scanning a directive, found unexpected non-alphabetical character",
));
}
Ok(string)
}
fn scan_version_directive_number(&mut self, mark: &Marker) -> Result<u32, ScanError> {
let mut val = 0u32;
let mut length = 0usize;
while let Some(digit) = self.input.look_ch().to_digit(10) {
if length + 1 > 9 {
return Err(ScanError::new_str(
*mark,
"while scanning a YAML directive, found extremely long version number",
));
}
length += 1;
val = val * 10 + digit;
self.skip_non_blank();
}
if length == 0 {
return Err(ScanError::new_str(
*mark,
"while scanning a YAML directive, did not find expected version number",
));
}
Ok(val)
}
fn scan_tag_directive_value(&mut self, mark: &Marker) -> Result<Token<'input>, ScanError> {
let n_blanks = self.input.skip_while_blank();
self.mark.index += n_blanks;
self.mark.col += n_blanks;
let handle = self.scan_tag_handle(true, mark)?;
let n_blanks = self.input.skip_while_blank();
self.mark.index += n_blanks;
self.mark.col += n_blanks;
let prefix = self.scan_tag_prefix(mark)?;
self.input.lookahead(1);
if self.input.next_is_blank_or_breakz() {
Ok(Token(
Span::new(*mark, self.mark),
TokenType::TagDirective(handle.into(), prefix.into()),
))
} else {
Err(ScanError::new_str(
*mark,
"while scanning TAG, did not find expected whitespace or line break",
))
}
}
fn fetch_tag(&mut self) -> ScanResult {
self.save_simple_key();
self.disallow_simple_key();
let tok = self.scan_tag()?;
self.tokens.push_back(tok);
Ok(())
}
fn scan_tag(&mut self) -> Result<Token<'input>, ScanError> {
let start_mark = self.mark;
let mut handle = String::new();
let mut suffix;
self.input.lookahead(2);
if self.input.nth_char_is(1, '<') {
suffix = self.scan_verbatim_tag(&start_mark)?;
} else {
handle = self.scan_tag_handle(false, &start_mark)?;
if handle.len() >= 2 && handle.starts_with('!') && handle.ends_with('!') {
let is_secondary_handle = handle == "!!";
suffix =
self.scan_tag_shorthand_suffix(false, is_secondary_handle, "", &start_mark)?;
} else {
suffix = self.scan_tag_shorthand_suffix(false, false, &handle, &start_mark)?;
"!".clone_into(&mut handle);
if suffix.is_empty() {
handle.clear();
"!".clone_into(&mut suffix);
}
}
}
if is_blank_or_breakz(self.input.look_ch())
|| (self.flow_level > 0 && self.input.next_is_flow())
{
Ok(Token(
Span::new(start_mark, self.mark),
TokenType::Tag(handle, suffix),
))
} else {
Err(ScanError::new_str(
start_mark,
"while scanning a tag, did not find expected whitespace or line break",
))
}
}
fn scan_tag_handle(&mut self, directive: bool, mark: &Marker) -> Result<String, ScanError> {
let mut string = String::new();
if self.input.look_ch() != '!' {
return Err(ScanError::new_str(
*mark,
"while scanning a tag, did not find expected '!'",
));
}
string.push(self.input.peek());
self.skip_non_blank();
let n_chars = self.input.fetch_while_is_alpha(&mut string);
self.mark.index += n_chars;
self.mark.col += n_chars;
if self.input.peek() == '!' {
string.push(self.input.peek());
self.skip_non_blank();
} else if directive && string != "!" {
return Err(ScanError::new_str(
*mark,
"while parsing a tag directive, did not find expected '!'",
));
}
Ok(string)
}
fn scan_tag_prefix(&mut self, start_mark: &Marker) -> Result<String, ScanError> {
let mut string = String::new();
if self.input.look_ch() == '!' {
string.push(self.input.peek());
self.skip_non_blank();
} else if !is_tag_char(self.input.peek()) {
return Err(ScanError::new_str(
*start_mark,
"invalid global tag character",
));
} else if self.input.peek() == '%' {
string.push(self.scan_uri_escapes(start_mark)?);
} else {
string.push(self.input.peek());
self.skip_non_blank();
}
while is_uri_char(self.input.look_ch()) {
if self.input.peek() == '%' {
string.push(self.scan_uri_escapes(start_mark)?);
} else {
string.push(self.input.peek());
self.skip_non_blank();
}
}
Ok(string)
}
fn scan_verbatim_tag(&mut self, start_mark: &Marker) -> Result<String, ScanError> {
self.skip_non_blank();
self.skip_non_blank();
let mut string = String::new();
while is_uri_char(self.input.look_ch()) {
if self.input.peek() == '%' {
string.push(self.scan_uri_escapes(start_mark)?);
} else {
string.push(self.input.peek());
self.skip_non_blank();
}
}
if self.input.peek() != '>' {
return Err(ScanError::new_str(
*start_mark,
"while scanning a verbatim tag, did not find the expected '>'",
));
}
self.skip_non_blank();
Ok(string)
}
fn scan_tag_shorthand_suffix(
&mut self,
_directive: bool,
_is_secondary: bool,
head: &str,
mark: &Marker,
) -> Result<String, ScanError> {
let mut length = head.len();
let mut string = String::new();
if length > 1 {
string.extend(head.chars().skip(1));
}
while is_tag_char(self.input.look_ch()) {
if self.input.peek() == '%' {
string.push(self.scan_uri_escapes(mark)?);
} else {
string.push(self.input.peek());
self.skip_non_blank();
}
length += 1;
}
if length == 0 {
return Err(ScanError::new_str(
*mark,
"while parsing a tag, did not find expected tag URI",
));
}
Ok(string)
}
fn scan_uri_escapes(&mut self, mark: &Marker) -> Result<char, ScanError> {
let mut width = 0usize;
let mut code = 0u32;
loop {
self.input.lookahead(3);
let c = self.input.peek_nth(1);
let nc = self.input.peek_nth(2);
if !(self.input.peek() == '%' && is_hex(c) && is_hex(nc)) {
return Err(ScanError::new_str(
*mark,
"while parsing a tag, found an invalid escape sequence",
));
}
let byte = (as_hex(c) << 4) + as_hex(nc);
if width == 0 {
width = match byte {
_ if byte & 0x80 == 0x00 => 1,
_ if byte & 0xE0 == 0xC0 => 2,
_ if byte & 0xF0 == 0xE0 => 3,
_ if byte & 0xF8 == 0xF0 => 4,
_ => {
return Err(ScanError::new_str(
*mark,
"while parsing a tag, found an incorrect leading UTF-8 byte",
));
}
};
code = byte;
} else {
if byte & 0xc0 != 0x80 {
return Err(ScanError::new_str(
*mark,
"while parsing a tag, found an incorrect trailing UTF-8 byte",
));
}
code = (code << 8) + byte;
}
self.skip_n_non_blank(3);
width -= 1;
if width == 0 {
break;
}
}
match char::from_u32(code) {
Some(ch) => Ok(ch),
None => Err(ScanError::new_str(
*mark,
"while parsing a tag, found an invalid UTF-8 codepoint",
)),
}
}
fn fetch_anchor(&mut self, alias: bool) -> ScanResult {
self.save_simple_key();
self.disallow_simple_key();
let tok = self.scan_anchor(alias)?;
self.tokens.push_back(tok);
Ok(())
}
fn scan_anchor(&mut self, alias: bool) -> Result<Token<'input>, ScanError> {
let mut string = String::new();
let start_mark = self.mark;
self.skip_non_blank();
while is_anchor_char(self.input.look_ch()) {
string.push(self.input.peek());
self.skip_non_blank();
}
if string.is_empty() {
return Err(ScanError::new_str(start_mark, "while scanning an anchor or alias, did not find expected alphabetic or numeric character"));
}
let tok = if alias {
TokenType::Alias(string.into())
} else {
TokenType::Anchor(string.into())
};
Ok(Token(Span::new(start_mark, self.mark), tok))
}
fn fetch_flow_collection_start(&mut self, tok: TokenType<'input>) -> ScanResult {
self.save_simple_key();
self.roll_one_col_indent();
self.increase_flow_level()?;
self.allow_simple_key();
let start_mark = self.mark;
self.skip_non_blank();
if tok == TokenType::FlowMappingStart {
self.flow_mapping_started = true;
} else {
self.implicit_flow_mapping_states
.push(ImplicitMappingState::Possible);
}
self.skip_ws_to_eol(SkipTabs::Yes)?;
self.tokens
.push_back(Token(Span::new(start_mark, self.mark), tok));
Ok(())
}
fn fetch_flow_collection_end(&mut self, tok: TokenType<'input>) -> ScanResult {
self.remove_simple_key()?;
self.decrease_flow_level();
self.disallow_simple_key();
if matches!(tok, TokenType::FlowSequenceEnd) {
self.end_implicit_mapping(self.mark);
self.implicit_flow_mapping_states.pop();
}
let start_mark = self.mark;
self.skip_non_blank();
self.skip_ws_to_eol(SkipTabs::Yes)?;
if self.flow_level > 0 {
self.adjacent_value_allowed_at = self.mark.index;
}
self.tokens
.push_back(Token(Span::new(start_mark, self.mark), tok));
Ok(())
}
fn fetch_flow_entry(&mut self) -> ScanResult {
self.remove_simple_key()?;
self.allow_simple_key();
self.end_implicit_mapping(self.mark);
let start_mark = self.mark;
self.skip_non_blank();
self.skip_ws_to_eol(SkipTabs::Yes)?;
self.tokens.push_back(Token(
Span::new(start_mark, self.mark),
TokenType::FlowEntry,
));
Ok(())
}
fn increase_flow_level(&mut self) -> ScanResult {
self.simple_keys.push(SimpleKey::new(Marker::new(0, 0, 0)));
self.flow_level = self
.flow_level
.checked_add(1)
.ok_or_else(|| ScanError::new_str(self.mark, "recursion limit exceeded"))?;
Ok(())
}
fn decrease_flow_level(&mut self) {
if self.flow_level > 0 {
self.flow_level -= 1;
self.simple_keys.pop().unwrap();
}
}
fn fetch_block_entry(&mut self) -> ScanResult {
if self.flow_level > 0 {
return Err(ScanError::new_str(
self.mark,
r#""-" is only valid inside a block"#,
));
}
if !self.simple_key_allowed {
return Err(ScanError::new_str(
self.mark,
"block sequence entries are not allowed in this context",
));
}
if let Some(Token(span, TokenType::Anchor(..) | TokenType::Tag(..))) = self.tokens.back() {
if self.mark.col == 0 && span.start.col == 0 && self.indent > -1 {
return Err(ScanError::new_str(
span.start,
"invalid indentation for anchor",
));
}
}
let mark = self.mark;
self.skip_non_blank();
self.roll_indent(mark.col, None, TokenType::BlockSequenceStart, mark);
let found_tabs = self.skip_ws_to_eol(SkipTabs::Yes)?.found_tabs();
self.input.lookahead(2);
if found_tabs && self.input.next_char_is('-') && is_blank_or_breakz(self.input.peek_nth(1))
{
return Err(ScanError::new_str(
self.mark,
"'-' must be followed by a valid YAML whitespace",
));
}
self.skip_ws_to_eol(SkipTabs::No)?;
self.input.lookahead(1);
if self.input.next_is_break() || self.input.next_is_flow() {
self.roll_one_col_indent();
}
self.remove_simple_key()?;
self.allow_simple_key();
self.tokens
.push_back(Token(Span::empty(self.mark), TokenType::BlockEntry));
Ok(())
}
fn fetch_document_indicator(&mut self, t: TokenType<'input>) -> ScanResult {
self.unroll_indent(-1);
self.remove_simple_key()?;
self.disallow_simple_key();
let mark = self.mark;
self.skip_n_non_blank(3);
self.tokens.push_back(Token(Span::new(mark, self.mark), t));
Ok(())
}
fn fetch_block_scalar(&mut self, literal: bool) -> ScanResult {
self.save_simple_key();
self.allow_simple_key();
let tok = self.scan_block_scalar(literal)?;
self.tokens.push_back(tok);
Ok(())
}
#[allow(clippy::too_many_lines)]
fn scan_block_scalar(&mut self, literal: bool) -> Result<Token<'input>, ScanError> {
let start_mark = self.mark;
let mut chomping = Chomping::Clip;
let mut increment: usize = 0;
let mut indent: usize = 0;
let mut trailing_blank: bool;
let mut leading_blank: bool = false;
let style = if literal {
ScalarStyle::Literal
} else {
ScalarStyle::Folded
};
let mut string = String::new();
let mut leading_break = String::new();
let mut trailing_breaks = String::new();
let mut chomping_break = String::new();
self.skip_non_blank();
self.unroll_non_block_indents();
if self.input.look_ch() == '+' || self.input.peek() == '-' {
if self.input.peek() == '+' {
chomping = Chomping::Keep;
} else {
chomping = Chomping::Strip;
}
self.skip_non_blank();
self.input.lookahead(1);
if self.input.next_is_digit() {
if self.input.peek() == '0' {
return Err(ScanError::new_str(
start_mark,
"while scanning a block scalar, found an indentation indicator equal to 0",
));
}
increment = (self.input.peek() as usize) - ('0' as usize);
self.skip_non_blank();
}
} else if self.input.next_is_digit() {
if self.input.peek() == '0' {
return Err(ScanError::new_str(
start_mark,
"while scanning a block scalar, found an indentation indicator equal to 0",
));
}
increment = (self.input.peek() as usize) - ('0' as usize);
self.skip_non_blank();
self.input.lookahead(1);
if self.input.peek() == '+' || self.input.peek() == '-' {
if self.input.peek() == '+' {
chomping = Chomping::Keep;
} else {
chomping = Chomping::Strip;
}
self.skip_non_blank();
}
}
self.skip_ws_to_eol(SkipTabs::Yes)?;
self.input.lookahead(1);
if !self.input.next_is_breakz() {
return Err(ScanError::new_str(
start_mark,
"while scanning a block scalar, did not find expected comment or line break",
));
}
if self.input.next_is_break() {
self.input.lookahead(2);
self.read_break(&mut chomping_break);
}
if self.input.look_ch() == '\t' {
return Err(ScanError::new_str(
start_mark,
"a block scalar content cannot start with a tab",
));
}
if increment > 0 {
indent = if self.indent >= 0 {
(self.indent + increment as isize) as usize
} else {
increment
}
}
if indent == 0 {
self.skip_block_scalar_first_line_indent(&mut indent, &mut trailing_breaks);
} else {
self.skip_block_scalar_indent(indent, &mut trailing_breaks);
}
if self.input.next_is_z() {
let contents = match chomping {
Chomping::Strip => String::new(),
_ if self.mark.line == start_mark.line() => String::new(),
Chomping::Clip => chomping_break,
Chomping::Keep if trailing_breaks.is_empty() => chomping_break,
Chomping::Keep => trailing_breaks,
};
return Ok(Token(
Span::new(start_mark, self.mark),
TokenType::Scalar(style, contents.into()),
));
}
if self.mark.col < indent && (self.mark.col as isize) > self.indent {
return Err(ScanError::new_str(
self.mark,
"wrongly indented line in block scalar",
));
}
let mut line_buffer = String::with_capacity(100);
let start_mark = self.mark;
while self.mark.col == indent && !self.input.next_is_z() {
if indent == 0 {
self.input.lookahead(4);
if self.input.next_is_document_end() {
break;
}
}
trailing_blank = self.input.next_is_blank();
if !literal && !leading_break.is_empty() && !leading_blank && !trailing_blank {
string.push_str(&trailing_breaks);
if trailing_breaks.is_empty() {
string.push(' ');
}
} else {
string.push_str(&leading_break);
string.push_str(&trailing_breaks);
}
leading_break.clear();
trailing_breaks.clear();
leading_blank = self.input.next_is_blank();
self.scan_block_scalar_content_line(&mut string, &mut line_buffer);
self.input.lookahead(2);
if self.input.next_is_z() {
break;
}
self.read_break(&mut leading_break);
self.skip_block_scalar_indent(indent, &mut trailing_breaks);
}
if chomping != Chomping::Strip {
string.push_str(&leading_break);
if self.input.next_is_z() && self.mark.col >= indent.max(1) {
string.push('\n');
}
}
if chomping == Chomping::Keep {
string.push_str(&trailing_breaks);
}
Ok(Token(
Span::new(start_mark, self.mark),
TokenType::Scalar(style, string.into()),
))
}
fn scan_block_scalar_content_line(&mut self, string: &mut String, line_buffer: &mut String) {
while !self.input.buf_is_empty() && !self.input.next_is_breakz() {
string.push(self.input.peek());
self.skip_blank();
}
if self.input.buf_is_empty() {
while let Some(c) = self.input.raw_read_non_breakz_ch() {
line_buffer.push(c);
}
let n_chars = line_buffer.chars().count();
self.mark.col += n_chars;
self.mark.index += n_chars;
string.reserve(line_buffer.len());
string.push_str(line_buffer);
line_buffer.clear();
}
}
fn skip_block_scalar_indent(&mut self, indent: usize, breaks: &mut String) {
loop {
if indent < self.input.bufmaxlen() - 2 {
self.input.lookahead(self.input.bufmaxlen());
while self.mark.col < indent && self.input.peek() == ' ' {
self.skip_blank();
}
} else {
loop {
self.input.lookahead(self.input.bufmaxlen());
while !self.input.buf_is_empty()
&& self.mark.col < indent
&& self.input.peek() == ' '
{
self.skip_blank();
}
if self.mark.col == indent
|| (!self.input.buf_is_empty() && self.input.peek() != ' ')
{
break;
}
}
self.input.lookahead(2);
}
if self.input.next_is_break() {
self.read_break(breaks);
} else {
break;
}
}
}
fn skip_block_scalar_first_line_indent(&mut self, indent: &mut usize, breaks: &mut String) {
let mut max_indent = 0;
loop {
while self.input.look_ch() == ' ' {
self.skip_blank();
}
if self.mark.col > max_indent {
max_indent = self.mark.col;
}
if self.input.next_is_break() {
self.input.lookahead(2);
self.read_break(breaks);
} else {
break;
}
}
*indent = max_indent.max((self.indent + 1) as usize);
if self.indent > 0 {
*indent = (*indent).max(1);
}
}
fn fetch_flow_scalar(&mut self, single: bool) -> ScanResult {
self.save_simple_key();
self.disallow_simple_key();
let tok = self.scan_flow_scalar(single)?;
self.skip_to_next_token()?;
self.adjacent_value_allowed_at = self.mark.index;
self.tokens.push_back(tok);
Ok(())
}
#[allow(clippy::too_many_lines)]
fn scan_flow_scalar(&mut self, single: bool) -> Result<Token<'input>, ScanError> {
let start_mark = self.mark;
let mut string = String::new();
let mut leading_break = String::new();
let mut trailing_breaks = String::new();
let mut whitespaces = String::new();
let mut leading_blanks;
self.skip_non_blank();
loop {
self.input.lookahead(4);
if self.mark.col == 0 && self.input.next_is_document_indicator() {
return Err(ScanError::new_str(
start_mark,
"while scanning a quoted scalar, found unexpected document indicator",
));
}
if self.input.next_is_z() {
return Err(ScanError::new_str(
start_mark,
"while scanning a quoted scalar, found unexpected end of stream",
));
}
if (self.mark.col as isize) < self.indent {
return Err(ScanError::new_str(
start_mark,
"invalid indentation in quoted scalar",
));
}
leading_blanks = false;
self.consume_flow_scalar_non_whitespace_chars(
single,
&mut string,
&mut leading_blanks,
&start_mark,
)?;
match self.input.look_ch() {
'\'' if single => break,
'"' if !single => break,
_ => {}
}
while self.input.next_is_blank() || self.input.next_is_break() {
if self.input.next_is_blank() {
if leading_blanks {
if self.input.peek() == '\t' && (self.mark.col as isize) < self.indent {
return Err(ScanError::new_str(
self.mark,
"tab cannot be used as indentation",
));
}
self.skip_blank();
} else {
whitespaces.push(self.input.peek());
self.skip_blank();
}
} else {
self.input.lookahead(2);
if leading_blanks {
self.read_break(&mut trailing_breaks);
} else {
whitespaces.clear();
self.read_break(&mut leading_break);
leading_blanks = true;
}
}
self.input.lookahead(1);
}
if leading_blanks {
if leading_break.is_empty() {
string.push_str(&leading_break);
string.push_str(&trailing_breaks);
trailing_breaks.clear();
leading_break.clear();
} else {
if trailing_breaks.is_empty() {
string.push(' ');
} else {
string.push_str(&trailing_breaks);
trailing_breaks.clear();
}
leading_break.clear();
}
} else {
string.push_str(&whitespaces);
whitespaces.clear();
}
}
self.skip_non_blank();
self.skip_ws_to_eol(SkipTabs::Yes)?;
match self.input.peek() {
',' | '}' | ']' if self.flow_level > 0 => {}
c if is_breakz(c) => {}
':' if self.flow_level == 0 && start_mark.line == self.mark.line => {}
':' if self.flow_level > 0 => {}
_ => {
return Err(ScanError::new_str(
self.mark,
"invalid trailing content after double-quoted scalar",
));
}
}
let style = if single {
ScalarStyle::SingleQuoted
} else {
ScalarStyle::DoubleQuoted
};
Ok(Token(
Span::new(start_mark, self.mark),
TokenType::Scalar(style, string.into()),
))
}
fn consume_flow_scalar_non_whitespace_chars(
&mut self,
single: bool,
string: &mut String,
leading_blanks: &mut bool,
start_mark: &Marker,
) -> Result<(), ScanError> {
self.input.lookahead(2);
while !is_blank_or_breakz(self.input.peek()) {
match self.input.peek() {
'\'' if self.input.peek_nth(1) == '\'' && single => {
string.push('\'');
self.skip_n_non_blank(2);
}
'\'' if single => break,
'"' if !single => break,
'\\' if !single && is_break(self.input.peek_nth(1)) => {
self.input.lookahead(3);
self.skip_non_blank();
self.skip_linebreak();
*leading_blanks = true;
break;
}
'\\' if !single => {
string.push(self.resolve_flow_scalar_escape_sequence(start_mark)?);
}
c => {
string.push(c);
self.skip_non_blank();
}
}
self.input.lookahead(2);
}
Ok(())
}
fn resolve_flow_scalar_escape_sequence(
&mut self,
start_mark: &Marker,
) -> Result<char, ScanError> {
let mut code_length = 0usize;
let mut ret = '\0';
match self.input.peek_nth(1) {
'0' => ret = '\0',
'a' => ret = '\x07',
'b' => ret = '\x08',
't' | '\t' => ret = '\t',
'n' => ret = '\n',
'v' => ret = '\x0b',
'f' => ret = '\x0c',
'r' => ret = '\x0d',
'e' => ret = '\x1b',
' ' => ret = '\x20',
'"' => ret = '"',
'/' => ret = '/',
'\\' => ret = '\\',
'N' => ret = char::from_u32(0x85).unwrap(),
'_' => ret = char::from_u32(0xA0).unwrap(),
'L' => ret = char::from_u32(0x2028).unwrap(),
'P' => ret = char::from_u32(0x2029).unwrap(),
'x' => code_length = 2,
'u' => code_length = 4,
'U' => code_length = 8,
_ => {
return Err(ScanError::new_str(
*start_mark,
"while parsing a quoted scalar, found unknown escape character",
))
}
}
self.skip_n_non_blank(2);
if code_length > 0 {
self.input.lookahead(code_length);
let mut value = 0u32;
for i in 0..code_length {
let c = self.input.peek_nth(i);
if !is_hex(c) {
return Err(ScanError::new_str(
*start_mark,
"while parsing a quoted scalar, did not find expected hexadecimal number",
));
}
value = (value << 4) + as_hex(c);
}
let Some(ch) = char::from_u32(value) else {
return Err(ScanError::new_str(
*start_mark,
"while parsing a quoted scalar, found invalid Unicode character escape code",
));
};
ret = ch;
self.skip_n_non_blank(code_length);
}
Ok(ret)
}
fn fetch_plain_scalar(&mut self) -> ScanResult {
self.save_simple_key();
self.disallow_simple_key();
let tok = self.scan_plain_scalar()?;
self.tokens.push_back(tok);
Ok(())
}
#[allow(clippy::too_many_lines)]
fn scan_plain_scalar(&mut self) -> Result<Token<'input>, ScanError> {
self.unroll_non_block_indents();
let indent = self.indent + 1;
let start_mark = self.mark;
if self.flow_level > 0 && (start_mark.col as isize) < indent {
return Err(ScanError::new_str(
start_mark,
"invalid indentation in flow construct",
));
}
let mut string = String::with_capacity(32);
self.buf_whitespaces.clear();
self.buf_leading_break.clear();
self.buf_trailing_breaks.clear();
let mut end_mark = self.mark;
loop {
self.input.lookahead(4);
if (self.leading_whitespace && self.input.next_is_document_indicator())
|| self.input.peek() == '#'
{
break;
}
if self.flow_level > 0 && self.input.peek() == '-' && is_flow(self.input.peek_nth(1)) {
return Err(ScanError::new_str(
self.mark,
"plain scalar cannot start with '-' followed by ,[]{}",
));
}
if !self.input.next_is_blank_or_breakz()
&& self.input.next_can_be_plain_scalar(self.flow_level > 0)
{
if self.leading_whitespace {
if self.buf_leading_break.is_empty() {
string.push_str(&self.buf_leading_break);
string.push_str(&self.buf_trailing_breaks);
self.buf_trailing_breaks.clear();
self.buf_leading_break.clear();
} else {
if self.buf_trailing_breaks.is_empty() {
string.push(' ');
} else {
string.push_str(&self.buf_trailing_breaks);
self.buf_trailing_breaks.clear();
}
self.buf_leading_break.clear();
}
self.leading_whitespace = false;
} else if !self.buf_whitespaces.is_empty() {
string.push_str(&self.buf_whitespaces);
self.buf_whitespaces.clear();
}
string.push(self.input.peek());
self.skip_non_blank();
string.reserve(self.input.bufmaxlen());
let mut end = false;
while !end {
self.input.lookahead(self.input.bufmaxlen());
for _ in 0..self.input.bufmaxlen() - 1 {
if self.input.next_is_blank_or_breakz()
|| !self.input.next_can_be_plain_scalar(self.flow_level > 0)
{
end = true;
break;
}
string.push(self.input.peek());
self.skip_non_blank();
}
}
end_mark = self.mark;
}
if !(self.input.next_is_blank() || self.input.next_is_break()) {
break;
}
self.input.lookahead(2);
while self.input.next_is_blank_or_break() {
if self.input.next_is_blank() {
if !self.leading_whitespace {
self.buf_whitespaces.push(self.input.peek());
self.skip_blank();
} else if (self.mark.col as isize) < indent && self.input.peek() == '\t' {
self.skip_ws_to_eol(SkipTabs::Yes)?;
if !self.input.next_is_breakz() {
return Err(ScanError::new_str(
start_mark,
"while scanning a plain scalar, found a tab",
));
}
} else {
self.skip_blank();
}
} else {
if self.leading_whitespace {
self.skip_break();
self.buf_trailing_breaks.push('\n');
} else {
self.buf_whitespaces.clear();
self.skip_break();
self.buf_leading_break.push('\n');
self.leading_whitespace = true;
}
}
self.input.lookahead(2);
}
if self.flow_level == 0 && (self.mark.col as isize) < indent {
break;
}
}
if self.leading_whitespace {
self.allow_simple_key();
}
if string.is_empty() {
Err(ScanError::new_str(
start_mark,
"unexpected end of plain scalar",
))
} else {
Ok(Token(
Span::new(start_mark, end_mark),
TokenType::Scalar(ScalarStyle::Plain, string.into()),
))
}
}
fn fetch_key(&mut self) -> ScanResult {
let start_mark = self.mark;
if self.flow_level == 0 {
if !self.simple_key_allowed {
return Err(ScanError::new_str(
self.mark,
"mapping keys are not allowed in this context",
));
}
self.roll_indent(
start_mark.col,
None,
TokenType::BlockMappingStart,
start_mark,
);
} else {
self.flow_mapping_started = true;
}
self.remove_simple_key()?;
if self.flow_level == 0 {
self.allow_simple_key();
} else {
self.disallow_simple_key();
}
self.skip_non_blank();
self.skip_yaml_whitespace()?;
if self.input.peek() == '\t' {
return Err(ScanError::new_str(
self.mark(),
"tabs disallowed in this context",
));
}
self.tokens
.push_back(Token(Span::new(start_mark, self.mark), TokenType::Key));
Ok(())
}
fn fetch_flow_value(&mut self) -> ScanResult {
let nc = self.input.peek_nth(1);
if self.mark.index != self.adjacent_value_allowed_at && (nc == '[' || nc == '{') {
return Err(ScanError::new_str(
self.mark,
"':' may not precede any of `[{` in flow mapping",
));
}
self.fetch_value()
}
fn fetch_value(&mut self) -> ScanResult {
let sk = self.simple_keys.last().unwrap().clone();
let start_mark = self.mark;
let is_implicit_flow_mapping =
!self.implicit_flow_mapping_states.is_empty() && !self.flow_mapping_started;
if is_implicit_flow_mapping {
*self.implicit_flow_mapping_states.last_mut().unwrap() = ImplicitMappingState::Inside;
}
self.skip_non_blank();
if self.input.look_ch() == '\t'
&& !self.skip_ws_to_eol(SkipTabs::Yes)?.has_valid_yaml_ws()
&& (self.input.peek() == '-' || self.input.next_is_alpha())
{
return Err(ScanError::new_str(
self.mark,
"':' must be followed by a valid YAML whitespace",
));
}
if sk.possible {
let tok = Token(Span::empty(sk.mark), TokenType::Key);
self.insert_token(sk.token_number - self.tokens_parsed, tok);
if is_implicit_flow_mapping {
if sk.mark.line < start_mark.line {
return Err(ScanError::new_str(
start_mark,
"illegal placement of ':' indicator",
));
}
self.insert_token(
sk.token_number - self.tokens_parsed,
Token(Span::empty(sk.mark), TokenType::FlowMappingStart),
);
}
self.roll_indent(
sk.mark.col,
Some(sk.token_number),
TokenType::BlockMappingStart,
sk.mark,
);
self.roll_one_col_indent();
self.simple_keys.last_mut().unwrap().possible = false;
self.disallow_simple_key();
} else {
if is_implicit_flow_mapping {
self.tokens
.push_back(Token(Span::empty(start_mark), TokenType::FlowMappingStart));
}
if self.flow_level == 0 {
if !self.simple_key_allowed {
return Err(ScanError::new_str(
start_mark,
"mapping values are not allowed in this context",
));
}
self.roll_indent(
start_mark.col,
None,
TokenType::BlockMappingStart,
start_mark,
);
}
self.roll_one_col_indent();
if self.flow_level == 0 {
self.allow_simple_key();
} else {
self.disallow_simple_key();
}
}
self.tokens
.push_back(Token(Span::empty(start_mark), TokenType::Value));
Ok(())
}
fn roll_indent(
&mut self,
col: usize,
number: Option<usize>,
tok: TokenType<'input>,
mark: Marker,
) {
if self.flow_level > 0 {
return;
}
if self.indent <= col as isize {
if let Some(indent) = self.indents.last() {
if !indent.needs_block_end {
self.indent = indent.indent;
self.indents.pop();
}
}
}
if self.indent < col as isize {
self.indents.push(Indent {
indent: self.indent,
needs_block_end: true,
});
self.indent = col as isize;
let tokens_parsed = self.tokens_parsed;
match number {
Some(n) => self.insert_token(n - tokens_parsed, Token(Span::empty(mark), tok)),
None => self.tokens.push_back(Token(Span::empty(mark), tok)),
}
}
}
fn unroll_indent(&mut self, col: isize) {
if self.flow_level > 0 {
return;
}
while self.indent > col {
let indent = self.indents.pop().unwrap();
self.indent = indent.indent;
if indent.needs_block_end {
self.tokens
.push_back(Token(Span::empty(self.mark), TokenType::BlockEnd));
}
}
}
fn roll_one_col_indent(&mut self) {
if self.flow_level == 0 && self.indents.last().map_or(false, |x| x.needs_block_end) {
self.indents.push(Indent {
indent: self.indent,
needs_block_end: false,
});
self.indent += 1;
}
}
fn unroll_non_block_indents(&mut self) {
while let Some(indent) = self.indents.last() {
if indent.needs_block_end {
break;
}
self.indent = indent.indent;
self.indents.pop();
}
}
fn save_simple_key(&mut self) {
if self.simple_key_allowed {
let required = self.flow_level == 0
&& self.indent == (self.mark.col as isize)
&& self.indents.last().unwrap().needs_block_end;
let mut sk = SimpleKey::new(self.mark);
sk.possible = true;
sk.required = required;
sk.token_number = self.tokens_parsed + self.tokens.len();
self.simple_keys.pop();
self.simple_keys.push(sk);
}
}
fn remove_simple_key(&mut self) -> ScanResult {
let last = self.simple_keys.last_mut().unwrap();
if last.possible && last.required {
return Err(ScanError::new_str(self.mark, "simple key expected"));
}
last.possible = false;
Ok(())
}
fn is_within_block(&self) -> bool {
!self.indents.is_empty()
}
fn end_implicit_mapping(&mut self, mark: Marker) {
if let Some(implicit_mapping) = self.implicit_flow_mapping_states.last_mut() {
if *implicit_mapping == ImplicitMappingState::Inside {
self.flow_mapping_started = false;
*implicit_mapping = ImplicitMappingState::Possible;
self.tokens
.push_back(Token(Span::empty(mark), TokenType::FlowMappingEnd));
}
}
}
}
#[derive(PartialEq, Eq)]
pub enum Chomping {
Strip,
Clip,
Keep,
}
#[cfg(test)]
mod test {
#[test]
fn test_is_anchor_char() {
use super::is_anchor_char;
assert!(is_anchor_char('x'));
}
}