mirror of https://codeberg.org/topola/topola.git
specctra: propagate line/column info to returned error type
This commit is contained in:
parent
b7027ae603
commit
479646d503
|
|
@ -14,7 +14,7 @@ pub fn impl_read(input: &DeriveInput) -> TokenStream {
|
|||
quote! {
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for #name {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>)
|
||||
-> Result<Self, ParseError>
|
||||
-> Result<Self, ParseErrorContext>
|
||||
{
|
||||
#body
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub enum LoadingError {
|
|||
/// File parsing errors containing information about unexpected end of file,
|
||||
/// or any other parsing issues with provided DSN file
|
||||
#[error(transparent)]
|
||||
Parse(#[from] read::ParseError),
|
||||
Parse(#[from] read::ParseErrorContext),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -15,12 +15,58 @@ pub enum ParseError {
|
|||
ExpectedStartOfList(&'static str),
|
||||
}
|
||||
|
||||
impl ParseError {
|
||||
pub fn add_context(self, context: (usize, usize)) -> ParseErrorContext {
|
||||
ParseErrorContext {
|
||||
error: self,
|
||||
context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
#[error("line {}, column {}: {error}", .context.0, .context.1)]
|
||||
pub struct ParseErrorContext {
|
||||
error: ParseError,
|
||||
context: (usize, usize),
|
||||
}
|
||||
|
||||
pub struct InputToken {
|
||||
pub token: ListToken,
|
||||
context: (usize, usize),
|
||||
}
|
||||
|
||||
impl InputToken {
|
||||
pub fn new(token: ListToken, context: (usize, usize)) -> Self {
|
||||
Self {
|
||||
token,
|
||||
context,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_start(self, name: &'static str) -> Result<(), ParseErrorContext> {
|
||||
self.token.expect_start(name).map_err(|err| err.add_context(self.context))
|
||||
}
|
||||
|
||||
pub fn expect_any_start(self) -> Result<String, ParseErrorContext> {
|
||||
self.token.expect_any_start().map_err(|err| err.add_context(self.context))
|
||||
}
|
||||
|
||||
pub fn expect_leaf(self) -> Result<String, ParseErrorContext> {
|
||||
self.token.expect_leaf().map_err(|err| err.add_context(self.context))
|
||||
}
|
||||
|
||||
pub fn expect_end(self) -> Result<(), ParseErrorContext> {
|
||||
self.token.expect_end().map_err(|err| err.add_context(self.context))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadDsn<R: std::io::BufRead>: Sized {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError>;
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext>;
|
||||
}
|
||||
// custom impl feeding the read values back into the tokenizer
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for Parser {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(Self {
|
||||
string_quote: tokenizer
|
||||
.read_optional("string_quote")?
|
||||
|
|
@ -35,86 +81,86 @@ impl<R: std::io::BufRead> ReadDsn<R> for Parser {
|
|||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for String {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(tokenizer.consume_token()?.expect_leaf()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for char {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
let string = tokenizer.consume_token()?.expect_leaf()?;
|
||||
if string.chars().count() == 1 {
|
||||
Ok(string.chars().next().unwrap())
|
||||
} else {
|
||||
Err(ParseError::Expected("a single character"))
|
||||
Err(tokenizer.add_context(ParseError::Expected("a single character")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for bool {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
match tokenizer.consume_token()?.expect_leaf()?.as_str() {
|
||||
"on" => Ok(true),
|
||||
"off" => Ok(false),
|
||||
_ => Err(ParseError::Expected("boolean")),
|
||||
_ => Err(tokenizer.add_context(ParseError::Expected("boolean"))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for i32 {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(tokenizer
|
||||
.consume_token()?
|
||||
.expect_leaf()?
|
||||
.parse()
|
||||
.map_err(|_| ParseError::Expected("i32"))?)
|
||||
.map_err(|_| tokenizer.add_context(ParseError::Expected("i32")))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for u32 {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(tokenizer
|
||||
.consume_token()?
|
||||
.expect_leaf()?
|
||||
.parse()
|
||||
.map_err(|_| ParseError::Expected("u32"))?)
|
||||
.map_err(|_| tokenizer.add_context(ParseError::Expected("u32")))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for usize {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(tokenizer
|
||||
.consume_token()?
|
||||
.expect_leaf()?
|
||||
.parse()
|
||||
.map_err(|_| ParseError::Expected("usize"))?)
|
||||
.map_err(|_| tokenizer.add_context(ParseError::Expected("usize")))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for f32 {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(tokenizer
|
||||
.consume_token()?
|
||||
.expect_leaf()?
|
||||
.parse()
|
||||
.map_err(|_| ParseError::Expected("f32"))?)
|
||||
.map_err(|_| tokenizer.add_context(ParseError::Expected("f32")))?)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for f64 {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
Ok(tokenizer
|
||||
.consume_token()?
|
||||
.expect_leaf()?
|
||||
.parse()
|
||||
.map_err(|_| ParseError::Expected("f64"))?)
|
||||
.map_err(|_| tokenizer.add_context(ParseError::Expected("f64")))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ListTokenizer<R: std::io::BufRead> {
|
||||
reader: R,
|
||||
peeked_char: Option<char>,
|
||||
cached_token: Option<ListToken>,
|
||||
cached_token: Option<InputToken>,
|
||||
space_in_quoted: bool,
|
||||
quote_char: Option<char>,
|
||||
line: usize,
|
||||
|
|
@ -134,12 +180,33 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
}
|
||||
}
|
||||
|
||||
fn next_char(&mut self) -> Result<char, ParseError> {
|
||||
pub fn context(&self) -> (usize, usize) {
|
||||
(self.line, self.column)
|
||||
}
|
||||
|
||||
fn add_context(&self, error: ParseError) -> ParseErrorContext {
|
||||
ParseErrorContext {
|
||||
error,
|
||||
context: (self.line, self.column),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_context<T>(&self, result: Result<T, ParseError>)
|
||||
-> Result<T, ParseErrorContext>
|
||||
{
|
||||
result.map_err(|err| self.add_context(err))
|
||||
}
|
||||
|
||||
fn next_char(&mut self) -> Result<char, ParseErrorContext> {
|
||||
let return_chr = if let Some(chr) = self.peeked_char {
|
||||
self.peeked_char = None;
|
||||
chr
|
||||
} else {
|
||||
self.reader.chars().next().ok_or(ParseError::Eof)??
|
||||
self.reader
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or(self.add_context(ParseError::Eof))?
|
||||
.map_err(|err| self.add_context(err.into()))?
|
||||
};
|
||||
|
||||
if return_chr == '\n' {
|
||||
|
|
@ -152,17 +219,21 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
Ok(return_chr)
|
||||
}
|
||||
|
||||
fn peek_char(&mut self) -> Result<char, ParseError> {
|
||||
fn peek_char(&mut self) -> Result<char, ParseErrorContext> {
|
||||
if let Some(chr) = self.peeked_char {
|
||||
Ok(chr)
|
||||
} else {
|
||||
let chr = self.reader.chars().next().ok_or(ParseError::Eof)??;
|
||||
let chr = self.reader
|
||||
.chars()
|
||||
.next()
|
||||
.ok_or(self.add_context(ParseError::Eof))?
|
||||
.map_err(|err| self.add_context(err.into()))?;
|
||||
self.peeked_char = Some(chr);
|
||||
Ok(chr)
|
||||
}
|
||||
}
|
||||
|
||||
fn skip_whitespace(&mut self) -> Result<(), ParseError> {
|
||||
fn skip_whitespace(&mut self) -> Result<(), ParseErrorContext> {
|
||||
loop {
|
||||
let chr = self.peek_char()?;
|
||||
if chr == ' ' || chr == '\r' || chr == '\n' {
|
||||
|
|
@ -173,7 +244,7 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
}
|
||||
}
|
||||
|
||||
fn read_string(&mut self) -> Result<String, ParseError> {
|
||||
fn read_string(&mut self) -> Result<String, ParseErrorContext> {
|
||||
if let Some(chr) = self.quote_char {
|
||||
if chr == self.peek_char()? {
|
||||
return self.read_quoted();
|
||||
|
|
@ -182,7 +253,7 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
self.read_unquoted()
|
||||
}
|
||||
|
||||
fn read_unquoted(&mut self) -> Result<String, ParseError> {
|
||||
fn read_unquoted(&mut self) -> Result<String, ParseErrorContext> {
|
||||
let mut string = String::new();
|
||||
|
||||
loop {
|
||||
|
|
@ -194,13 +265,13 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
}
|
||||
|
||||
if string.is_empty() {
|
||||
Err(ParseError::Expected("string (unquoted)"))
|
||||
Err(self.add_context(ParseError::Expected("string (unquoted)")))
|
||||
} else {
|
||||
Ok(string)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_quoted(&mut self) -> Result<String, ParseError> {
|
||||
fn read_quoted(&mut self) -> Result<String, ParseErrorContext> {
|
||||
let mut string = String::new();
|
||||
|
||||
if self.next_char().unwrap() != self.quote_char.unwrap() {
|
||||
|
|
@ -225,7 +296,7 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
// the following two methods effectively allow 1 token of lookahead
|
||||
|
||||
// returns next token, either a cached one returned earlier or a newly read one
|
||||
pub fn consume_token(&mut self) -> Result<ListToken, ParseError> {
|
||||
pub fn consume_token(&mut self) -> Result<InputToken, ParseErrorContext> {
|
||||
// move out of cache if not empty, otherwise consume input
|
||||
// always leaves cache empty
|
||||
if let Some(token) = self.cached_token.take() {
|
||||
|
|
@ -237,14 +308,17 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
}
|
||||
|
||||
// puts a token back into cache, to be consumed by something else
|
||||
pub fn return_token(&mut self, token: ListToken) {
|
||||
pub fn return_token(&mut self, token: InputToken) {
|
||||
self.cached_token = Some(token);
|
||||
}
|
||||
|
||||
fn read_token(&mut self) -> Result<ListToken, ParseError> {
|
||||
fn read_token(&mut self) -> Result<InputToken, ParseErrorContext> {
|
||||
self.skip_whitespace()?;
|
||||
let context = self.context();
|
||||
|
||||
let chr = self.peek_char()?;
|
||||
Ok(if chr == '(' {
|
||||
Ok(InputToken::new(
|
||||
if chr == '(' {
|
||||
self.next_char().unwrap();
|
||||
self.skip_whitespace()?;
|
||||
ListToken::Start {
|
||||
|
|
@ -257,14 +331,16 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
ListToken::Leaf {
|
||||
value: self.read_string()?,
|
||||
}
|
||||
})
|
||||
},
|
||||
context,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn read_value<T: ReadDsn<R>>(&mut self) -> Result<T, ParseError> {
|
||||
pub fn read_value<T: ReadDsn<R>>(&mut self) -> Result<T, ParseErrorContext> {
|
||||
T::read_dsn(self)
|
||||
}
|
||||
|
||||
pub fn read_named<T: ReadDsn<R>>(&mut self, name: &'static str) -> Result<T, ParseError> {
|
||||
pub fn read_named<T: ReadDsn<R>>(&mut self, name: &'static str) -> Result<T, ParseErrorContext> {
|
||||
self.consume_token()?.expect_start(name)?;
|
||||
let value = self.read_value::<T>()?;
|
||||
self.consume_token()?.expect_end()?;
|
||||
|
|
@ -274,35 +350,35 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
pub fn read_optional<T: ReadDsn<R>>(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
) -> Result<Option<T>, ParseError> {
|
||||
let token = self.consume_token()?;
|
||||
) -> Result<Option<T>, ParseErrorContext> {
|
||||
let input = self.consume_token()?;
|
||||
if let ListToken::Start {
|
||||
name: ref actual_name,
|
||||
} = token
|
||||
} = input.token
|
||||
{
|
||||
if actual_name == name {
|
||||
let value = self.read_value::<T>()?;
|
||||
self.consume_token()?.expect_end()?;
|
||||
Ok(Some(value))
|
||||
} else {
|
||||
self.return_token(token);
|
||||
self.return_token(input);
|
||||
Ok(None)
|
||||
}
|
||||
} else {
|
||||
self.return_token(token);
|
||||
self.return_token(input);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_array<T: ReadDsn<R>>(&mut self) -> Result<Vec<T>, ParseError> {
|
||||
pub fn read_array<T: ReadDsn<R>>(&mut self) -> Result<Vec<T>, ParseErrorContext> {
|
||||
let mut array = Vec::<T>::new();
|
||||
loop {
|
||||
let token = self.consume_token()?;
|
||||
if let ListToken::Leaf { .. } = token {
|
||||
self.return_token(token);
|
||||
let input = self.consume_token()?;
|
||||
if let ListToken::Leaf { .. } = input.token {
|
||||
self.return_token(input);
|
||||
array.push(self.read_value::<T>()?);
|
||||
} else {
|
||||
self.return_token(token);
|
||||
self.return_token(input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -312,24 +388,24 @@ impl<R: std::io::BufRead> ListTokenizer<R> {
|
|||
pub fn read_named_array<T: ReadDsn<R>>(
|
||||
&mut self,
|
||||
name: &'static str,
|
||||
) -> Result<Vec<T>, ParseError> {
|
||||
) -> Result<Vec<T>, ParseErrorContext> {
|
||||
let mut array = Vec::<T>::new();
|
||||
loop {
|
||||
let token = self.consume_token()?;
|
||||
let input = self.consume_token()?;
|
||||
if let ListToken::Start {
|
||||
name: ref actual_name,
|
||||
} = token
|
||||
} = input.token
|
||||
{
|
||||
if actual_name == name {
|
||||
let value = self.read_value::<T>()?;
|
||||
self.consume_token()?.expect_end()?;
|
||||
array.push(value);
|
||||
} else {
|
||||
self.return_token(token);
|
||||
self.return_token(input);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
self.return_token(token);
|
||||
self.return_token(input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use super::common::ListToken;
|
||||
use super::read::ReadDsn;
|
||||
use super::read::{ListTokenizer, ParseError};
|
||||
use super::read::{ListTokenizer, ParseError, ParseErrorContext};
|
||||
use super::write::ListWriter;
|
||||
use super::write::WriteSes;
|
||||
use specctra_derive::ReadDsn;
|
||||
|
|
@ -237,14 +237,15 @@ pub enum Shape {
|
|||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for Shape {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
let ctx = tokenizer.context();
|
||||
let name = tokenizer.consume_token()?.expect_any_start()?;
|
||||
let value = match name.as_str() {
|
||||
"circle" => Ok(Shape::Circle(tokenizer.read_value()?)),
|
||||
"rect" => Ok(Shape::Rect(tokenizer.read_value()?)),
|
||||
"path" => Ok(Shape::Path(tokenizer.read_value()?)),
|
||||
"polygon" => Ok(Shape::Polygon(tokenizer.read_value()?)),
|
||||
_ => Err(ParseError::Expected("a different keyword")),
|
||||
_ => Err(ParseError::Expected("a different keyword").add_context(ctx)),
|
||||
};
|
||||
tokenizer.consume_token()?.expect_end()?;
|
||||
value
|
||||
|
|
@ -345,16 +346,16 @@ pub struct Point {
|
|||
|
||||
// Custom impl for the case described above
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for Vec<Point> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
let mut array = Vec::<Point>::new();
|
||||
loop {
|
||||
let token = tokenizer.consume_token()?;
|
||||
if let ListToken::Leaf { value: ref x } = token {
|
||||
let input = tokenizer.consume_token()?;
|
||||
if let ListToken::Leaf { value: ref x } = input.token {
|
||||
let x = x.parse::<f64>().unwrap();
|
||||
let y = tokenizer.read_value::<f64>()?;
|
||||
array.push(Point { x, y });
|
||||
} else {
|
||||
tokenizer.return_token(token);
|
||||
tokenizer.return_token(input);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -363,14 +364,14 @@ impl<R: std::io::BufRead> ReadDsn<R> for Vec<Point> {
|
|||
}
|
||||
|
||||
impl<R: std::io::BufRead> ReadDsn<R> for Option<Point> {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseError> {
|
||||
let token = tokenizer.consume_token()?;
|
||||
if let ListToken::Leaf { value: ref x } = token {
|
||||
fn read_dsn(tokenizer: &mut ListTokenizer<R>) -> Result<Self, ParseErrorContext> {
|
||||
let input = tokenizer.consume_token()?;
|
||||
if let ListToken::Leaf { value: ref x } = input.token {
|
||||
let x = x.parse::<f64>().unwrap();
|
||||
let y = tokenizer.read_value::<f64>()?;
|
||||
Ok(Some(Point { x, y }))
|
||||
} else {
|
||||
tokenizer.return_token(token);
|
||||
tokenizer.return_token(input);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue