use std::env;
use std::io::ErrorKind;
use std::path::Path;
use std::process;
use std::fs;
use /*json_parser::*/parse::Parser;
fn main() {
let args = env::args().collect::<Vec<_>>();
if args.is_empty() {
eprintln!("Invoke this properly, nigger.");
process::exit(1);
}
if args.len() < 2 {
eprintln!("Usage: {} <file.json>", args[0]);
process::exit(1);
}
let path = Path::new(&args[1]);
let file_contents = match fs::read_to_string(path) {
Ok(contents) => contents,
Err(e) => {
match e.kind() {
ErrorKind::PermissionDenied => eprintln!("Error: permission denied"),
ErrorKind::NotFound => eprintln!("Error: `{}` not found.", &args[1]),
ErrorKind::InvalidData => eprintln!("Error: Data is not UTF-8."),
_ => eprintln!("Error: nigger"),
}
process::exit(1);
},
};
let file_contents = file_contents.trim_start_matches("\u{FEFF}");
let parser = Parser::new(file_contents, &args[1]);
for value in parser {
println!("{value:#?}");
}
}
pub mod parse {
use crate::str_ext::*;
use crate::token::{Token, TokenKind, Tokenizer};
use crate::error::Errors;
use crate::unescape::unescape;
use TokenKind::*;
use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::process;
const MAX_DEPTH: usize = 512;
pub struct Parser<'s> {
tokenizer: RefCell<Tokenizer<'s>>,
contents: &'s str,
errors: RefCell<Errors<'s>>,
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
enum OneOf {
Value(Value),
Token(Token),
}
impl Iterator for Parser<'_> {
type Item = Value;
fn next(&mut self) -> Option<Self::Item> {
let value = self.next_value(0);
if self.errors().had_error() {
return None;
}
value
}
}
impl<'s> Parser<'s> {
pub fn new(contents: &'s str, file_name: &'s str) -> Self {
let tokenizer = Tokenizer::new(file_name, contents);
Self {
tokenizer: tokenizer.into(),
contents,
errors: Errors::new(file_name, contents).into()
}
}
pub fn errors(&self) -> RefMut<Errors<'s>> {
self.errors.borrow_mut()
}
fn error(&self, msg: &str, start: usize, len: usize) {
self.errors.borrow_mut().push(msg, start, len);
self.errors.borrow_mut().report_all();
}
fn next_token(&self) -> Option<Token> {
self.tokenizer.borrow_mut().next()
}
pub fn next_value(&self, depth: usize) -> Option<Value> {
let first_token = self.next_token()?;
if depth >= MAX_DEPTH {
let (start, _) = first_token.pos();
let (line, col) = self.contents.pos(start);
eprintln!("Exceeds depth limit for parsing at line {line}, column {col}");
process::exit(1);
}
match first_token.kind {
Number => self.parse_number(&first_token),
OpenSquare => self.parse_array(&first_token, depth+1),
OpenCurly => self.parse_object(&first_token, depth+1),
TerminatedString => self.parse_string(&first_token),
Null => Some(Value::Null),
True => Some(Value::True),
False => Some(Value::False),
_ => {
let (start, len) = first_token.pos();
let token_str = match self.contents.substr(start, len) {
Some(s) => s,
None => {
self.error("This is le bug", start, len);
return None;
},
};
self.error(&format!("unexpected token: `{token_str}`"), start, len);
None
},
}
}
fn parse_string(&self, token: &Token) -> Option<Value> {
let (start, len) = token.pos();
let literal = self.contents.substr(start, len)?;
if literal.len() < 2 || !literal.starts_with("\"") || !literal.ends_with("\"") {
return None;
};
let literal = if literal.len() == 2 {
String::new()
} else {
let key = &literal[1..(len-1)];
match unescape(key) {
Ok(k) => k,
Err((range, err)) => {
let start = range.start + start + 1;
let len = range.end-range.start;
let msg = err.to_string();
self.error(&msg, start, len);
return None;
},
}
};
Some(Value::String(literal))
}
fn parse_number(&self, token: &Token) -> Option<Value> {
let (start, len) = token.pos();
let n_str = match self.contents.substr(start, len) {
Some(n_str) => n_str,
None => {
self.error("token is beyond the given file, or has no length.", start, len);
return None;
},
};
let n_res = n_str.parse::<f64>();
let n = match n_res {
Ok(n) => n,
Err(_) => {
self.error("could not parse number", start, len);
return None;
},
};
if n.is_nan() {
return Some(Value::Null);
}
if n.is_infinite() {
if n.is_sign_negative() {
return Some(Value::Number(f64::MIN));
} else {
return Some(Value::Number(f64::MAX));
}
}
Some(Value::Number(n))
}
fn parse_array(&self, token: &Token, depth: usize) -> Option<Value> {
if depth >= MAX_DEPTH {
let (start, _) = token.pos();
let (line, col) = self.contents.pos(start);
eprintln!("Exceeds depth limit for parsing at line {line}, column {col}");
process::exit(1);
}
let (mut start, mut len) = token.pos();
let mut array = vec![];
loop {
let value = self.expect_value_or_token(&[CloseSquare], start, len, depth)?;
match value {
OneOf::Value(v) => array.push(v),
OneOf::Token(_) => break,
}
let tok = self.expect_token(&[CloseSquare, Comma], start, len)?;
(start, len) = tok.pos();
match tok.kind {
TokenKind::CloseSquare => break,
TokenKind::Comma => continue,
_ => {
return None;
},
}
}
Some(Value::Array(array))
}
fn parse_object(&self, first_token: &Token, depth: usize) -> Option<Value> {
if depth >= MAX_DEPTH {
let (start, _) = first_token.pos();
let (line, col) = self.contents.pos(start);
eprintln!("Exceeds depth limit for parsing at line {line}, column {col}");
process::exit(1);
}
use TokenKind::*;
let (start, len) = first_token.pos();
let mut hashmap = HashMap::<String, Value>::new();
loop {
let token = self.expect_token(&[CloseCurly, TerminatedString], start, len)?;
let (start, len) = token.pos();
match token.kind {
CloseCurly => return Some(Value::Object(hashmap)),
TerminatedString => (),
_ => {
self.error("expected one of [CloseSquare, TerminatedString]", start, len);
return None;
},
}
let key = match self.parse_string(&token) {
Some(Value::String(s)) => s,
_ => {
self.error("expected TerminatedString", start, len);
return None;
},
};
let token = self.expect_token(&[Colon], start, len)?;
let (start, len) = token.pos();
let value = self.next_value(depth+1);
let value = match value {
Some(v) => v,
None => {
// no error, if this is returning None, it already errored
return None;
},
};
hashmap.insert(key, value);
let token = self.expect_token(&[CloseCurly, Comma], start, len)?;
let (start, len) = token.pos();
match token.kind {
CloseCurly => break,
Comma => continue,
_ => {
self.error("expected one of [CloseSquare, Comma]", start, len);
return None;
},
}
}
Some(Value::Object(hashmap))
}
fn expect_value_or_token(
&self,
kinds: &[TokenKind],
start: usize,
len: usize,
depth: usize,
) -> Option<OneOf> {
let target_kinds = [Number, OpenSquare, OpenCurly, TerminatedString, Null, True, False]
.iter()
.chain(kinds.iter()).cloned()
.collect::<Vec<_>>().to_owned();
let next_token = self.expect_token(&target_kinds, start, len)?;
let value = match next_token.kind {
Number => self.parse_number(&next_token),
OpenSquare => self.parse_array(&next_token, depth+1),
OpenCurly => self.parse_object(&next_token, depth+1),
TerminatedString => self.parse_string(&next_token),
Null => Some(Value::Null),
True => Some(Value::True),
False => Some(Value::False),
_ => None,
};
if self.errors().had_error() {
return None;
}
if let Some(value) = value {
return Some(OneOf::Value(value));
}
Some(OneOf::Token(next_token))
}
fn expect_token(&self, kinds: &[TokenKind], start: usize, len: usize) -> Option<Token> {
if kinds.is_empty() {
self.error("no token to expect", start, len);
return None;
}
let next_token = match self.next_token() {
Some(t) => t,
None => {
if kinds.len() > 1 {
let err = format!("expected one of {kinds:?}, got EOF");
self.error(&err, start, len);
} else {
self.error(&format!("expected {} got EOF", kinds[0]), start, len);
}
return None;
},
};
let (start, len) = next_token.pos();
let mut found = false;
for kind in kinds.iter() {
if next_token.kind == *kind {
found = true;
break;
}
}
if !found {
if kinds.len() > 1 {
let err = format!("expected one of {kinds:?}, got {}", next_token.kind);
self.error(&err, start, len);
} else {
self.error(&format!("expected {} got {}", kinds[0], next_token.kind), start, len);
}
return None;
}
Some(next_token)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
False,
Null,
True,
Object(HashMap<String, Value>),
Array(Vec<Value>),
Number(f64),
String(String),
}
}
pub mod token {
use std::fmt::Display;
use std::str::Chars;
use crate::error::Errors;
use crate::str_ext::Substr;
const EOF_CHAR: char = '\0';
#[derive(Debug, Clone, PartialEq)]
pub enum TokenKind {
// structural
OpenSquare,
CloseSquare,
OpenCurly,
CloseCurly,
Colon,
Comma,
// literals
False,
Null,
True,
TerminatedString,
UnterminatedString,
Number,
Unknown,
Eof,
}
pub fn keyword(c: char, string: &str) -> Option<TokenKind> {
use TokenKind::*;
Some(match (c, string) {
('n', "ull") => Null,
('t', "rue") => True,
('f', "alse") => False,
('I', "nfinity") => Number,
('I', "nf") => Number,
('N', "aN") => Null,
_ => return None,
})
}
impl Display for TokenKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use TokenKind::*;
match self {
Eof => write!(f, "EOF"),
t => write!(f, "{:?}", t),
}
}
}
#[derive(Debug, Clone)]
pub struct Token {
pub kind: TokenKind,
offset: u32,
len: u16,
}
impl Token {
fn new(kind: TokenKind, offset: usize, len: usize) -> Self {
Self { kind, offset: offset as u32, len: len as u16 }
}
pub(crate) fn pos(&self) -> (usize, usize) {
(self.offset as usize, self.len as usize)
}
}
pub struct Tokenizer<'s> {
chars_consumed: usize,
len_remaining: usize,
chars: Chars<'s>,
errors: Errors<'s>,
}
impl<'s> Tokenizer<'s> {
pub fn new(file_name: &'s str, string: &'s str) -> Tokenizer<'s> {
let chars = string.chars();
Tokenizer {
chars_consumed: 0,
len_remaining: string.len(),
chars,
errors: Errors::new(file_name, string),
}
}
}
impl Iterator for Tokenizer<'_> {
type Item = Token;
fn next(&mut self) -> Option<Self::Item> {
let token = self.advance_token();
if self.errors.had_error() {
// they are greedily reported as they happen
return None;
}
match token.kind {
TokenKind::Eof => {
None
},
_ => Some(token)
}
}
}
#[inline]
pub fn is_whitespace(c: char) -> bool {
matches!(
c,
'\u{0009}' // \t
| '\u{000a}' // \n
| '\u{000b}' // \v
| '\u{000c}' // form feed
| '\u{000d}' // \r
| '\u{0020}' // space
)
}
impl Tokenizer<'_> {
pub fn advance_token(&mut self) -> Token {
self.consume_whitespace();
self.reset_pos_within_token();
let first_chr = match self.bump() {
Some(c) => c,
None => {
let (start, _) = self.token_pos();
return Token::new(TokenKind::Eof, start, 0);
},
};
let token_kind = match first_chr {
'-' | '+'
if self.first().is_ascii_digit()
|| self.first() == 'I'
|| self.first() == 'N' => {
let c = self.bump();
if let Some(c) = c {
self.consume_number(c)
} else {
TokenKind::Eof
}
},
c @ '.' if self.first().is_ascii_digit() => {
self.consume_number(c)
},
c @ '0'..='9' => self.consume_number(c),
'{' => TokenKind::OpenCurly,
'}' => TokenKind::CloseCurly,
'[' => TokenKind::OpenSquare,
']' => TokenKind::CloseSquare,
':' => TokenKind::Colon,
',' => TokenKind::Comma,
'"' => {
let terminated = self.consume_string();
if !terminated {
let (start, len) = self.token_pos();
self.error("unterminated string", start, len);
TokenKind::UnterminatedString
} else {
TokenKind::TerminatedString
}
},
c if c.is_ascii_alphabetic() => {
if let Some(kw) = self.consume_keyword(c) {
kw
} else {
let (start, len) = self.token_pos();
let token_str = self.errors.source.substr(start, len).unwrap();
self.error(&format!("unexpected chars: `{token_str}`"), start, len);
self.errors.reset_errors();
TokenKind::Unknown
}
},
_ => {
let (start, len) = self.token_pos();
let token_str = self.errors.source.substr(start, len).unwrap();
self.error(&format!("unexpected chars: `{token_str}`"), start, len);
TokenKind::Unknown
}
};
let (start, len) = self.token_pos();
let res = Token::new(token_kind, start, len);
self.reset_pos_within_token();
res
}
fn consume_string(&mut self) -> bool {
while let Some(c) = self.bump() {
match c {
'"' => {
return true;
},
'\\' if self.first() == '\\' || self.first() == '"' => {
self.bump();
},
_ => (),
}
}
false
}
fn consume_keyword(&mut self, c: char) -> Option<TokenKind> {
let iter = self.chars.clone();
let count = iter.take_while(|c| c.is_ascii_alphabetic()).count();
let kword = self.chars.as_str().get(..count);
for _ in 0..count {
self.bump();
}
if let Some(kword) = kword {
return keyword(c, kword);
}
None
}
// this felt disgusting to write
fn consume_number(&mut self, first_digit: char) -> TokenKind {
let mut already_past_point = false;
if first_digit == '0' {
match self.first() {
'0'..='9' => {
self.consume_decimal_digits();
},
'.' | 'e' | 'E' => {},
_ => return TokenKind::Number,
}
} else if first_digit == '.' {
already_past_point = true;
} else if first_digit == 'N' {
if self.first() != 'a' {
return TokenKind::Unknown;
}
self.bump();
if self.first() != 'N' {
return TokenKind::Unknown;
}
self.bump();
return TokenKind::Null;
} else if first_digit == 'I' {
if self.first() != 'n' {
return TokenKind::Unknown;
}
self.bump();
if self.first() != 'f' {
return TokenKind::Unknown;
}
self.bump();
if self.first() == 'i' {
self.bump();
if self.first() != 'n' {
return TokenKind::Unknown;
}
self.bump();
if self.first() != 'i' {
return TokenKind::Unknown;
}
self.bump();
if self.first() != 't' {
return TokenKind::Unknown;
}
self.bump();
if self.first() != 'y' {
return TokenKind::Unknown;
}
self.bump();
return TokenKind::Number;
}
return TokenKind::Number;
} else {
self.consume_decimal_digits();
};
let mut peek = self.first();
if already_past_point {
peek = '.';
}
match peek {
'.' if self.second() != '.' => {
if !already_past_point {
self.bump();
}
let mut empty_exponent = false;
match self.first() {
c if c.is_ascii_digit() => {
self.consume_decimal_digits();
match self.first() {
'e' | 'E' => {
self.bump();
empty_exponent = !self.consume_float_exponent();
},
_ => (),
}
},
'e' | 'E' => {
self.bump();
empty_exponent = !self.consume_float_exponent();
},
_ => (),
}
if empty_exponent {
let (start, len) = self.token_pos();
self.error("float has empty exponent", start, len);
}
TokenKind::Number
},
'e' | 'E' => {
self.bump();
let empty_exponent = !self.consume_float_exponent();
if empty_exponent {
let (start, len) = self.token_pos();
self.error("float has empty exponent", start, len);
}
TokenKind::Number
},
_ => TokenKind::Number,
}
}
fn consume_float_exponent(&mut self) -> bool {
if self.first() == '-' || self.first() == '+' {
self.bump();
}
self.consume_decimal_digits()
}
fn consume_decimal_digits(&mut self) -> bool {
let mut has_digits = false;
while let '0'..='9' = self.first() {
has_digits = true;
self.bump();
}
has_digits
}
fn consume_whitespace(&mut self) {
self.consume_while(is_whitespace);
}
pub fn is_eof(&self) -> bool {
self.chars.as_str().is_empty()
}
fn first(&self) -> char {
self.chars.clone().next().unwrap_or(EOF_CHAR)
}
fn second(&self) -> char {
let mut iter = self.chars.clone();
iter.next();
iter.next().unwrap_or(EOF_CHAR)
}
fn pos_within_token(&self) -> usize {
self.len_remaining - self.chars.as_str().len()
}
fn token_pos(&self) -> (usize, usize) {
let start = self.chars_consumed;
let len = self.pos_within_token();
(start, len)
}
fn reset_pos_within_token(&mut self) {
self.chars_consumed += self.pos_within_token();
self.len_remaining = self.chars.as_str().len();
}
fn bump(&mut self) -> Option<char> {
self.chars.next()
}
pub fn consume_while(&mut self, mut predicate: impl FnMut(char) -> bool) {
while predicate(self.first()) && !self.is_eof() {
self.bump();
}
}
pub fn error(&mut self, msg: &str, offset: usize, len: usize) {
self.errors.push(msg, offset, len);
self.errors.report_all();
}
}
}
pub mod str_ext {
pub trait Substr<'s> {
fn substr(&'s self, start: usize, len: usize) -> Option<&'s str>;
}
impl<'s> Substr<'s> for &'s str {
fn substr(&'s self, start: usize, len: usize) -> Option<&'s str> {
if len == 0 {
return None;
}
if start + len > self.len() {
return None;
}
Some(&self[start..(start + len)])
}
}
pub trait Textwise {
fn pos(&self, n: usize) -> (usize, usize);
fn line(&self, n: usize) -> &str;
}
impl Textwise for &str {
fn pos(&self, n: usize) -> (usize, usize) {
if n >= self.chars().count() {
return (0, 0);
}
let mut line = 1;
let mut col = 0;
for (idx, c) in self.char_indices() {
if idx == n {
return (line, col);
}
if c == '\n' {
line += 1;
col = 0;
} else {
col += 1;
}
}
unreachable!()
}
fn line(&self, line_n: usize) -> &str {
if line_n == 0 {
panic!("Line number must be greater than zero.");
}
let line_n = line_n - 1;
for (idx, line) in self.lines().enumerate() {
if idx == line_n {
return line;
}
}
panic!("Index `{}` exceeds the amount of lines.", line_n + 1);
}
}
}
pub mod unescape {
use std::{fmt::Display, ops::Range, str::Chars};
pub fn unescape(literal: &str) -> Result<String, (Range<usize>, EscapeError)> {
let mut unescaped = Ok(String::with_capacity(literal.len()));
unescape_unicode(literal, &mut |range, c| {
if let Ok(b) = &mut unescaped {
match c {
Ok(c) => b.push(c),
Err(e) => unescaped = Err((range, e)),
}
}
});
unescaped
}
fn unescape_unicode<
F: FnMut(Range<usize>, Result<T, EscapeError>),
T: From<char> + From<u8>,
> (
src: &str,
callback: &mut F,
) {
let mut chars = src.chars();
while let Some(c) = chars.next() {
let start = src.len() - chars.as_str().len() - c.len_utf8();
let res = match c {
'\\' => match chars.clone().next() {
Some('\n') => {
skip_ascii_whitespace(&mut chars, start, &mut |range, err| {
callback(range, Err(err))
});
continue;
}
_ => scan_escape::<T>(&mut chars),
},
'"' => Err(EscapeError::EscapeOnlyChar),
'\r' => Err(EscapeError::BareCarriageReturn),
_ => Ok(T::from(c)),
};
let end = src.len() - chars.as_str().len();
callback(start..end, res);
}
}
fn skip_ascii_whitespace<
F: FnMut(Range<usize>, EscapeError)
> (
chars: &mut Chars<'_>,
start: usize,
callback: &mut F,
) {
let tail = chars.as_str();
let first_non_space = tail
.bytes()
.position(|b| b != b' ' && b != b'\t' && b != b'\n' && b != b'\r')
.unwrap_or(tail.len());
if tail[1..first_non_space].contains('\n') {
let end = start + first_non_space + 1;
callback(start..end, EscapeError::MultipleSkippedLinesWarning);
}
let tail = &tail[first_non_space..];
if let Some(c) = tail.chars().next() {
if c.is_whitespace() {
let end = start + first_non_space + c.len_utf8() + 1;
callback(start..end, EscapeError::UnskippedWhitespaceWarning);
}
}
*chars = tail.chars();
}
fn scan_escape<T: From<char> + From<u8>>(
chars: &mut Chars<'_>,
) -> Result<T, EscapeError> {
let res: char = match chars.next().ok_or(EscapeError::LoneSlash)? {
'"' => '"',
'n' => '\n',
'r' => '\r',
't' => '\t',
'\\' => '\\',
'\'' => '\'',
'0' => '\0',
'x' => {
let hi = chars.next().ok_or(EscapeError::TooShortHexEscape)?;
let hi = hi.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?;
let lo = chars.next().ok_or(EscapeError::TooShortHexEscape)?;
let lo = lo.to_digit(16).ok_or(EscapeError::InvalidCharInHexEscape)?;
let value = (hi * 16 + lo) as u8;
return if !value.is_ascii() {
Err(EscapeError::OutOfRangeHexEscape)
} else {
Ok(T::from(value))
};
},
'u' => return scan_unicode(chars).map(T::from),
_ => return Err(EscapeError::InvalidEscape),
};
Ok(T::from(res))
}
fn scan_unicode(chars: &mut Chars<'_>) -> Result<char, EscapeError> {
let mut n_digits = 1;
let c = chars.next().ok_or(EscapeError::UnclosedUnicodeEscape)?;
let mut value = c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?;
loop {
match chars.next() {
None => return Err(EscapeError::UnclosedUnicodeEscape),
Some(c) => {
let digit: u32 = c.to_digit(16).ok_or(EscapeError::InvalidCharInUnicodeEscape)?;
n_digits += 1;
if n_digits == 4 {
break std::char::from_u32(value).ok_or({
if value > 0x10FFFF {
EscapeError::OutOfRangeUnicodeEscape
} else {
EscapeError::LoneSurrogateUnicodeEscape
}
});
}
value = value * 16 + digit;
},
};
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum EscapeError {
LoneSlash,
InvalidEscape,
BareCarriageReturn,
EscapeOnlyChar,
TooShortHexEscape,
InvalidCharInHexEscape,
OutOfRangeHexEscape,
InvalidCharInUnicodeEscape,
UnclosedUnicodeEscape,
LoneSurrogateUnicodeEscape,
OutOfRangeUnicodeEscape,
UnskippedWhitespaceWarning,
MultipleSkippedLinesWarning,
}
impl Display for EscapeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
use EscapeError::*;
write!(f, "{}", match self {
LoneSlash => "escaped `\\` char without continuation",
InvalidEscape => "invalid escape character",
BareCarriageReturn => "raw `\\r` encountered",
EscapeOnlyChar => "unescaped char that was escaped",
TooShortHexEscape => "numeric char escape is too short",
InvalidCharInHexEscape => "invalid char in numeric escape",
OutOfRangeHexEscape => "character code in numeric escape is non-ascii",
InvalidCharInUnicodeEscape => "non-hex value in unicode escape",
UnclosedUnicodeEscape => "unclosed unicode escape",
LoneSurrogateUnicodeEscape => "invalid in-bound unicode char",
OutOfRangeUnicodeEscape => "out-of-bounds unicode char",
UnskippedWhitespaceWarning => "unskipped whitespace",
MultipleSkippedLinesWarning => "multiple skipped lines"
})
}
}
}
pub mod error {
use std::collections::VecDeque;
use crate::str_ext::*;
#[derive(Debug, Clone)]
pub struct Error {
message: String,
offset: usize,
len: usize,
}
impl Error {
pub fn new(message: String, offset: usize, len: usize) -> Self {
Self {
message,
offset,
len,
}
}
fn format(&self, file_name: &str, file_content: &str) -> String {
let (line, col) = file_content.pos(self.offset);
let substr = file_content.line(line);
assert!(col <= substr.len());
let (left, right) = substr.split_at(col);
let (r_left, r_right) = right.split_at(self.len);
let substr = format!("{}{}{}", left, r_left, r_right);
let pad_n = line.to_string().len();
let pad_l = vec![' '; pad_n].iter().collect::<String>();
let pad_r = vec![' '; col].iter().collect::<String>();
let uline = vec!['^'; self.len].iter().collect::<String>();
format!(
"error: {}\n{pad_l}--> {file_name}@{line}:{}\n{line} | {substr}\n{pad_l} {pad_r}{uline}",
self.message,
col + 1,
)
}
}
#[derive(Debug, Clone)]
pub struct Errors<'s> {
had_error: bool,
errors: VecDeque<Error>,
pub(crate) source: &'s str,
file_name: &'s str,
}
impl<'s> Errors<'s> {
pub fn new(file_name: &'s str, source: &'s str) -> Self {
Self {
had_error: false,
errors: VecDeque::new(),
source,
file_name,
}
}
pub fn push(&mut self, message: &str, offset: usize, len: usize) {
self.had_error = true;
self.errors.push_back(Error::new(message.to_string(), offset, len));
}
pub fn pop(&mut self) -> Option<Error> {
self.errors.pop_front()
}
pub fn report(&self, error: &Error) {
eprintln!("{}", error.format(self.file_name, self.source))
}
pub fn report_all(&mut self) {
while let Some(err) = self.pop() {
self.report(&err);
}
}
pub fn had_error(&self) -> bool {
self.had_error
}
pub fn reset_errors(&mut self) {
self.had_error = false;
}
}
impl Iterator for Errors<'_> {
type Item = String;
fn next(&mut self) -> Option<Self::Item> {
let err = self.errors.pop_front();
if let Some(e) = err {
return Some(e.format(self.file_name, self.source));
}
None
}
}
}