use crate::ported::lint::markedjson::error::MarkedError;
use crate::ported::lint::markedjson::nodes::Mark;
use std::collections::{HashMap, HashSet};
use std::sync::OnceLock;
pub fn hexdigits_set() -> &'static HashSet<char> {
static S: OnceLock<HashSet<char>> = OnceLock::new();
S.get_or_init(|| {
let mut s = HashSet::new();
for c in "0123456789abcdefABCDEF".chars() {
s.insert(c);
}
s
})
}
#[derive(Debug, Clone)]
pub struct ScannerError(pub MarkedError);
impl std::fmt::Display for ScannerError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&self.0, f)
}
}
impl std::error::Error for ScannerError {}
#[derive(Debug, Clone)]
pub struct SimpleKey {
pub token_number: usize,
pub index: usize,
pub line: usize,
pub column: usize,
pub mark: Option<Mark>,
}
impl SimpleKey {
pub fn new(
token_number: usize,
index: usize,
line: usize,
column: usize,
mark: Option<Mark>,
) -> Self {
Self {
token_number,
index,
line,
column,
mark,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FetchKind {
StreamEnd,
FlowSequenceStart,
FlowMappingStart,
FlowSequenceEnd,
FlowMappingEnd,
FlowEntry,
Value,
Double,
Plain,
}
pub fn dispatch_fetch_for(ch: char, flow_level: u32) -> Option<FetchKind> {
if ch == '\0' {
return Some(FetchKind::StreamEnd);
}
if ch == '[' {
return Some(FetchKind::FlowSequenceStart);
}
if ch == '{' {
return Some(FetchKind::FlowMappingStart);
}
if ch == ']' {
return Some(FetchKind::FlowSequenceEnd);
}
if ch == '}' {
return Some(FetchKind::FlowMappingEnd);
}
if ch == ',' {
return Some(FetchKind::FlowEntry);
}
if ch == ':' && flow_level > 0 {
return Some(FetchKind::Value);
}
if ch == '"' {
return Some(FetchKind::Double);
}
if check_plain(ch) {
return Some(FetchKind::Plain);
}
None
}
pub fn check_plain(ch: char) -> bool {
!"\0[]{},:\"".contains(ch)
}
pub struct Scanner {
pub done: bool,
pub flow_level: u32,
pub tokens: Vec<String>,
pub tokens_taken: usize,
pub allow_simple_key: bool,
pub possible_simple_keys: HashMap<u32, SimpleKey>,
}
impl Default for Scanner {
fn default() -> Self {
Self::new()
}
}
impl Scanner {
pub fn new() -> Self {
let mut s = Self {
done: false,
flow_level: 0,
tokens: Vec::new(),
tokens_taken: 0,
allow_simple_key: true,
possible_simple_keys: HashMap::new(),
};
s.fetch_stream_start();
s
}
pub fn fetch_stream_start(&mut self) {
self.tokens.push("StreamStartToken".to_string());
}
pub fn fetch_stream_end(&mut self) {
self.done = true;
self.possible_simple_keys.clear();
self.tokens.push("StreamEndToken".to_string());
}
pub fn check_token(&self, choices: &[&str]) -> bool {
match self.tokens.first() {
None => false,
Some(t) => {
if choices.is_empty() {
true
} else {
choices.iter().any(|c| *c == t)
}
}
}
}
pub fn peek_token(&self) -> Option<&str> {
self.tokens.first().map(String::as_str)
}
pub fn get_token(&mut self) -> Option<String> {
if self.tokens.is_empty() {
return None;
}
self.tokens_taken += 1;
Some(self.tokens.remove(0))
}
pub fn need_more_tokens(&self) -> bool {
if self.done {
return false;
}
if self.tokens.is_empty() {
return true;
}
matches!(self.next_possible_simple_key(), Some(n) if n == self.tokens_taken)
}
pub fn next_possible_simple_key(&self) -> Option<usize> {
self.possible_simple_keys
.values()
.map(|k| k.token_number)
.min()
}
pub fn save_possible_simple_key(&mut self, index: usize, line: usize, column: usize) {
if !self.allow_simple_key {
return;
}
let token_number = self.tokens_taken + self.tokens.len();
let key = SimpleKey::new(token_number, index, line, column, None);
self.possible_simple_keys.insert(self.flow_level, key);
}
pub fn remove_possible_simple_key(&mut self) {
self.possible_simple_keys.remove(&self.flow_level);
}
pub fn fetch_flow_collection_start(&mut self, token_name: &str) {
self.flow_level += 1;
self.tokens.push(token_name.to_string());
}
pub fn fetch_flow_collection_end(&mut self, token_name: &str) {
if self.flow_level > 0 {
self.flow_level -= 1;
}
self.tokens.push(token_name.to_string());
}
pub fn fetch_value(&mut self) {
self.tokens.push("ValueToken".to_string());
}
pub fn fetch_flow_entry(&mut self) {
self.tokens.push("FlowEntryToken".to_string());
}
pub fn fetch_flow_sequence_start(&mut self) {
self.fetch_flow_collection_start("FlowSequenceStartToken");
}
pub fn fetch_flow_mapping_start(&mut self) {
self.fetch_flow_collection_start("FlowMappingStartToken");
}
pub fn fetch_flow_sequence_end(&mut self) {
self.fetch_flow_collection_end("FlowSequenceEndToken");
}
pub fn fetch_flow_mapping_end(&mut self) {
self.fetch_flow_collection_end("FlowMappingEndToken");
}
pub fn fetch_double(&mut self, index: usize, line: usize, column: usize) {
self.save_possible_simple_key(index, line, column);
self.allow_simple_key = false;
self.tokens.push("ScalarToken".to_string());
}
pub fn fetch_plain(&mut self, index: usize, line: usize, column: usize) {
self.save_possible_simple_key(index, line, column);
self.allow_simple_key = false;
self.tokens.push("ScalarToken".to_string());
}
pub fn fetch_more_tokens(&mut self) {
}
pub fn stale_possible_simple_keys(&mut self, current_line: usize) {
let stale: Vec<u32> = self
.possible_simple_keys
.iter()
.filter_map(|(k, v)| {
if v.line != current_line {
Some(*k)
} else {
None
}
})
.collect();
for k in stale {
self.possible_simple_keys.remove(&k);
}
}
}
pub fn scan_flow_scalar(buffer: &str, quote: char) -> Option<(String, usize)> {
let mut chunks = String::new();
let mut pos = 0usize;
let bytes = buffer.as_bytes();
while pos < bytes.len() {
let ch = bytes[pos] as char;
if ch == quote {
return Some((chunks, pos + 1));
}
let (s, ds) = scan_flow_scalar_spaces(&buffer[pos..]);
chunks.push_str(&s);
pos += ds;
if pos >= bytes.len() {
break;
}
let (ns, dns) = scan_flow_scalar_non_spaces(&buffer[pos..]);
chunks.push_str(&ns);
pos += dns;
if ds == 0 && dns == 0 {
break;
}
}
None
}
pub fn scan_flow_scalar_non_spaces(buffer: &str) -> (String, usize) {
let mut out = String::new();
let mut pos = 0usize;
let bytes = buffer.as_bytes();
while pos < bytes.len() {
let ch = bytes[pos] as char;
if matches!(ch, '"' | '\\' | '\0' | ' ' | '\t' | '\n') {
if ch == '\\' && pos + 1 < bytes.len() {
let next = bytes[pos + 1] as char;
match next {
'b' => out.push('\x08'),
't' => out.push('\t'),
'n' => out.push('\n'),
'f' => out.push('\x0c'),
'r' => out.push('\r'),
'"' => out.push('"'),
'\\' => out.push('\\'),
'u' => {
if pos + 6 <= bytes.len() {
let hex = &buffer[pos + 2..pos + 6];
if let Ok(code) = u32::from_str_radix(hex, 16) {
if let Some(c) = char::from_u32(code) {
out.push(c);
}
}
pos += 6;
continue;
}
return (out, pos);
}
other => out.push(other),
}
pos += 2;
continue;
}
break;
}
out.push(ch);
pos += 1;
}
(out, pos)
}
pub fn scan_flow_scalar_spaces(buffer: &str) -> (String, usize) {
let mut pos = 0usize;
let bytes = buffer.as_bytes();
while pos < bytes.len() {
let ch = bytes[pos] as char;
if ch == ' ' || ch == '\t' {
pos += 1;
} else {
break;
}
}
(" ".repeat(if pos > 0 { 1 } else { 0 }), pos)
}
pub fn scan_plain(buffer: &str) -> (String, usize) {
let mut out = String::new();
let mut pos = 0usize;
for (i, ch) in buffer.char_indices() {
if !check_plain(ch) {
break;
}
out.push(ch);
pos = i + ch.len_utf8();
}
(out, pos)
}
pub fn scan_to_next_token(buffer: &str) -> usize {
buffer
.bytes()
.take_while(|&b| b == b' ' || b == b'\t' || b == b'\n')
.count()
}
pub fn fetch_more_tokens(buffer: &str, flow_level: u32) -> (usize, Option<FetchKind>) {
let whitespace = scan_to_next_token(buffer);
let peeked = buffer[whitespace..].chars().next().unwrap_or('\0');
(whitespace, dispatch_fetch_for(peeked, flow_level))
}
pub fn stale_possible_simple_keys(current_line: usize, keys: &HashMap<u32, SimpleKey>) -> Vec<u32> {
keys.iter()
.filter_map(|(level, k)| {
if k.line < current_line {
Some(*level)
} else {
None
}
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hexdigits_set_contains_all_hex_chars() {
let s = hexdigits_set();
for c in "0123456789abcdefABCDEF".chars() {
assert!(s.contains(&c), "missing: {}", c);
}
assert!(!s.contains(&'g'));
}
#[test]
fn hexdigits_set_has_22_entries() {
let s = hexdigits_set();
assert_eq!(s.len(), 22);
}
#[test]
fn scanner_error_implements_error_traits() {
let me = MarkedError::new(Some("ctx"), None, Some("prob"), None, None);
let e = ScannerError(me);
let _: &dyn std::error::Error = &e;
assert!(e.to_string().contains("ctx"));
}
#[test]
fn simple_key_stores_state() {
let k = SimpleKey::new(5, 10, 2, 3, None);
assert_eq!(k.token_number, 5);
assert_eq!(k.index, 10);
assert_eq!(k.line, 2);
assert_eq!(k.column, 3);
}
#[test]
fn dispatch_fetch_for_null_returns_stream_end() {
assert_eq!(dispatch_fetch_for('\0', 0), Some(FetchKind::StreamEnd));
}
#[test]
fn dispatch_fetch_for_bracket_returns_flow_sequence_start() {
assert_eq!(
dispatch_fetch_for('[', 0),
Some(FetchKind::FlowSequenceStart)
);
}
#[test]
fn dispatch_fetch_for_brace_returns_flow_mapping_start() {
assert_eq!(
dispatch_fetch_for('{', 0),
Some(FetchKind::FlowMappingStart)
);
}
#[test]
fn dispatch_fetch_for_close_bracket_returns_flow_sequence_end() {
assert_eq!(dispatch_fetch_for(']', 1), Some(FetchKind::FlowSequenceEnd));
}
#[test]
fn dispatch_fetch_for_close_brace_returns_flow_mapping_end() {
assert_eq!(dispatch_fetch_for('}', 1), Some(FetchKind::FlowMappingEnd));
}
#[test]
fn dispatch_fetch_for_comma_returns_flow_entry() {
assert_eq!(dispatch_fetch_for(',', 1), Some(FetchKind::FlowEntry));
}
#[test]
fn dispatch_fetch_for_colon_in_flow_returns_value() {
assert_eq!(dispatch_fetch_for(':', 1), Some(FetchKind::Value));
}
#[test]
fn dispatch_fetch_for_colon_outside_flow_returns_plain() {
assert_eq!(dispatch_fetch_for(':', 0), None);
}
#[test]
fn dispatch_fetch_for_quote_returns_double() {
assert_eq!(dispatch_fetch_for('"', 0), Some(FetchKind::Double));
}
#[test]
fn dispatch_fetch_for_alpha_returns_plain() {
assert_eq!(dispatch_fetch_for('a', 0), Some(FetchKind::Plain));
assert_eq!(dispatch_fetch_for('1', 0), Some(FetchKind::Plain));
}
#[test]
fn check_plain_excludes_structural_chars() {
assert!(!check_plain('\0'));
assert!(!check_plain('['));
assert!(!check_plain(']'));
assert!(!check_plain('{'));
assert!(!check_plain('}'));
assert!(!check_plain(','));
assert!(!check_plain(':'));
assert!(!check_plain('"'));
}
#[test]
fn check_plain_accepts_alphanumeric() {
assert!(check_plain('a'));
assert!(check_plain('Z'));
assert!(check_plain('5'));
assert!(check_plain('_'));
}
#[test]
fn scanner_new_emits_stream_start_token() {
let s = Scanner::new();
assert_eq!(s.tokens.len(), 1);
assert_eq!(s.tokens[0], "StreamStartToken");
assert_eq!(s.flow_level, 0);
assert_eq!(s.tokens_taken, 0);
assert!(!s.done);
assert!(s.allow_simple_key);
}
#[test]
fn fetch_stream_end_sets_done_and_appends_token() {
let mut s = Scanner::new();
s.fetch_stream_end();
assert!(s.done);
assert_eq!(s.tokens[1], "StreamEndToken");
}
#[test]
fn check_token_no_choices_returns_true_when_token_available() {
let s = Scanner::new();
assert!(s.check_token(&[]));
}
#[test]
fn check_token_matches_specific_name() {
let s = Scanner::new();
assert!(s.check_token(&["StreamStartToken"]));
assert!(!s.check_token(&["StreamEndToken"]));
}
#[test]
fn check_token_returns_false_when_no_tokens() {
let mut s = Scanner {
done: false,
flow_level: 0,
tokens: Vec::new(),
tokens_taken: 0,
allow_simple_key: true,
possible_simple_keys: HashMap::new(),
};
s.tokens.clear();
assert!(!s.check_token(&[]));
}
#[test]
fn peek_token_returns_first_without_consuming() {
let s = Scanner::new();
assert_eq!(s.peek_token(), Some("StreamStartToken"));
assert_eq!(s.tokens_taken, 0);
}
#[test]
fn get_token_pops_and_increments_taken() {
let mut s = Scanner::new();
let t = s.get_token();
assert_eq!(t.as_deref(), Some("StreamStartToken"));
assert_eq!(s.tokens_taken, 1);
assert!(s.tokens.is_empty());
}
#[test]
fn need_more_tokens_false_when_done() {
let mut s = Scanner::new();
s.done = true;
s.tokens.clear();
assert!(!s.need_more_tokens());
}
#[test]
fn need_more_tokens_true_when_queue_empty_and_not_done() {
let mut s = Scanner::new();
s.tokens.clear();
assert!(s.need_more_tokens());
}
#[test]
fn save_possible_simple_key_records_when_allowed() {
let mut s = Scanner::new();
s.save_possible_simple_key(10, 1, 2);
assert!(s.possible_simple_keys.contains_key(&0));
let k = &s.possible_simple_keys[&0];
assert_eq!(k.index, 10);
assert_eq!(k.line, 1);
assert_eq!(k.column, 2);
}
#[test]
fn save_possible_simple_key_skipped_when_disallowed() {
let mut s = Scanner::new();
s.allow_simple_key = false;
s.save_possible_simple_key(10, 1, 2);
assert!(s.possible_simple_keys.is_empty());
}
#[test]
fn remove_possible_simple_key_clears_current_level() {
let mut s = Scanner::new();
s.save_possible_simple_key(10, 1, 2);
s.remove_possible_simple_key();
assert!(s.possible_simple_keys.is_empty());
}
#[test]
fn fetch_flow_collection_start_increments_level() {
let mut s = Scanner::new();
s.fetch_flow_collection_start("FlowSequenceStartToken");
assert_eq!(s.flow_level, 1);
assert_eq!(s.tokens.last().unwrap(), "FlowSequenceStartToken");
}
#[test]
fn fetch_flow_collection_end_decrements_level() {
let mut s = Scanner::new();
s.fetch_flow_collection_start("FlowSequenceStartToken");
s.fetch_flow_collection_end("FlowSequenceEndToken");
assert_eq!(s.flow_level, 0);
assert_eq!(s.tokens.last().unwrap(), "FlowSequenceEndToken");
}
#[test]
fn fetch_flow_collection_end_does_not_underflow() {
let mut s = Scanner::new();
s.fetch_flow_collection_end("FlowSequenceEndToken");
assert_eq!(s.flow_level, 0);
}
#[test]
fn fetch_value_appends_value_token() {
let mut s = Scanner::new();
s.fetch_value();
assert_eq!(s.tokens.last().unwrap(), "ValueToken");
}
#[test]
fn fetch_flow_entry_appends_entry_token() {
let mut s = Scanner::new();
s.fetch_flow_entry();
assert_eq!(s.tokens.last().unwrap(), "FlowEntryToken");
}
#[test]
fn next_possible_simple_key_returns_min_token_number() {
let mut s = Scanner::new();
s.possible_simple_keys
.insert(0, SimpleKey::new(5, 0, 0, 0, None));
s.possible_simple_keys
.insert(1, SimpleKey::new(3, 0, 0, 0, None));
assert_eq!(s.next_possible_simple_key(), Some(3));
}
#[test]
fn next_possible_simple_key_none_when_empty() {
let s = Scanner::new();
assert!(s.next_possible_simple_key().is_none());
}
#[test]
fn stale_possible_simple_keys_filters_keys_below_current_line() {
let mut keys = HashMap::new();
keys.insert(0, SimpleKey::new(1, 0, 1, 0, None));
keys.insert(1, SimpleKey::new(2, 0, 5, 0, None));
let stale = stale_possible_simple_keys(3, &keys);
assert_eq!(stale, vec![0]);
}
#[test]
fn fetch_flow_sequence_start_delegates_to_collection_start() {
let mut s = Scanner::new();
let before = s.flow_level;
s.fetch_flow_sequence_start();
assert_eq!(s.flow_level, before + 1);
assert!(s.tokens.contains(&"FlowSequenceStartToken".to_string()));
}
#[test]
fn fetch_flow_mapping_start_delegates_to_collection_start() {
let mut s = Scanner::new();
s.fetch_flow_mapping_start();
assert!(s.tokens.contains(&"FlowMappingStartToken".to_string()));
}
#[test]
fn fetch_flow_sequence_end_delegates_to_collection_end() {
let mut s = Scanner::new();
s.flow_level = 1;
s.fetch_flow_sequence_end();
assert_eq!(s.flow_level, 0);
assert!(s.tokens.contains(&"FlowSequenceEndToken".to_string()));
}
#[test]
fn fetch_flow_mapping_end_delegates_to_collection_end() {
let mut s = Scanner::new();
s.flow_level = 1;
s.fetch_flow_mapping_end();
assert!(s.tokens.contains(&"FlowMappingEndToken".to_string()));
}
#[test]
fn fetch_double_disallows_simple_key_after() {
let mut s = Scanner::new();
s.allow_simple_key = true;
s.fetch_double(0, 0, 0);
assert!(!s.allow_simple_key);
assert!(s.tokens.contains(&"ScalarToken".to_string()));
}
#[test]
fn fetch_plain_disallows_simple_key_after() {
let mut s = Scanner::new();
s.allow_simple_key = true;
s.fetch_plain(0, 0, 0);
assert!(!s.allow_simple_key);
assert!(s.tokens.contains(&"ScalarToken".to_string()));
}
#[test]
fn scan_to_next_token_skips_leading_whitespace() {
assert_eq!(scan_to_next_token(" abc"), 3);
assert_eq!(scan_to_next_token("\t\nabc"), 2);
assert_eq!(scan_to_next_token("abc"), 0);
}
#[test]
fn scan_to_next_token_empty_returns_zero() {
assert_eq!(scan_to_next_token(""), 0);
}
#[test]
fn fetch_more_tokens_dispatches_via_dispatch_fetch_for() {
let (ws, kind) = fetch_more_tokens(" [", 0);
assert_eq!(ws, 2);
assert_eq!(kind, Some(FetchKind::FlowSequenceStart));
}
#[test]
fn fetch_more_tokens_empty_buffer_returns_stream_end() {
let (ws, kind) = fetch_more_tokens("", 0);
assert_eq!(ws, 0);
assert_eq!(kind, Some(FetchKind::StreamEnd));
}
#[test]
fn fetch_more_tokens_whitespace_only_returns_stream_end() {
let (ws, kind) = fetch_more_tokens(" ", 0);
assert_eq!(ws, 3);
assert_eq!(kind, Some(FetchKind::StreamEnd));
}
#[test]
fn scan_flow_scalar_non_spaces_returns_run_until_quote() {
let (s, n) = scan_flow_scalar_non_spaces("hello\"world");
assert_eq!(s, "hello");
assert_eq!(n, 5);
}
#[test]
fn scan_flow_scalar_non_spaces_decodes_basic_escapes() {
let (s, _) = scan_flow_scalar_non_spaces("hi\\n\"");
assert_eq!(s, "hi\n");
}
#[test]
fn scan_flow_scalar_non_spaces_decodes_unicode_escape() {
let (s, _) = scan_flow_scalar_non_spaces("\\u0041\"");
assert_eq!(s, "A");
}
#[test]
fn scan_flow_scalar_spaces_collapses_run() {
let (s, n) = scan_flow_scalar_spaces(" hello");
assert_eq!(s, " ");
assert_eq!(n, 3);
}
#[test]
fn scan_flow_scalar_spaces_empty_run_yields_empty_string() {
let (s, n) = scan_flow_scalar_spaces("hello");
assert_eq!(s, "");
assert_eq!(n, 0);
}
#[test]
fn scan_flow_scalar_round_trips_simple_string() {
let r = scan_flow_scalar("hello\"", '"');
assert_eq!(r, Some(("hello".to_string(), 6)));
}
#[test]
fn scan_flow_scalar_returns_none_when_no_closing_quote() {
let r = scan_flow_scalar("hello", '"');
assert!(r.is_none());
}
#[test]
fn scan_plain_stops_at_structural_chars() {
let (s, n) = scan_plain("foo:bar");
assert_eq!(s, "foo");
assert_eq!(n, 3);
}
#[test]
fn scan_plain_empty_input_returns_empty() {
let (s, n) = scan_plain("");
assert_eq!(s, "");
assert_eq!(n, 0);
}
#[test]
fn scan_plain_runs_to_end_when_all_plain() {
let (s, n) = scan_plain("abc123");
assert_eq!(s, "abc123");
assert_eq!(n, 6);
}
}