chj_rustbin/parse/
parse_error.rs

1use std::rc::Rc;
2use std::{fmt::Write, sync::Arc};
3
4// Can't do this with ToOwned, because ToOwned is auto-implemented
5// when Clone exists, and ParseableStr needs Clone, thus ParseableStr
6// cannot have a custom ToOwned, and any struct containing
7// ParseableStr and has Clone can't either. Not exactly clear about
8// the reasoning of this default impl here. So, make a new trait.
9
10// Could/should I specify that Self and Owning are the same type with
11// just a different type parameter?
12pub trait IntoOwningBacking<S: AsRef<str>> {
13    type Owning;
14    fn into_owning_backing(self) -> Self::Owning;
15}
16
17/// A Source for parsing (XX rename it to Source?), without the
18/// location info.
19pub trait Backing: AsRef<str> + ToOwned + PartialEq {}
20
21impl Backing for str {}
22impl<'t> Backing for &'t str {}
23impl Backing for String {}
24impl<'t> Backing for &'t String {}
25impl Backing for Arc<str> {}
26impl Backing for Rc<str> {}
27
28// Sigh, can't for these, 2 step, and I can't implement intermediary
29// either. Would have to newtype. Wait, but newtype would impl
30// AsRef<str>, but String does that, too, no? So what's up?
31// impl Backing for Arc<String> {}
32// impl Backing for Rc<String> {}
33
34pub trait ParseContext: PartialEq {
35    /// Shows the error with the context in the original string.
36    /// `original_input` must be the original string on which this
37    /// error is based (so that position matches up).
38    fn show_context(&self, out: &mut String);
39}
40
41#[derive(Debug, PartialEq, Clone, Copy)]
42pub struct NoContext;
43
44impl ParseContext for NoContext {
45    fn show_context(&self, out: &mut String) {
46        // still a hack?
47        out.push_str("(no context)")
48    }
49}
50
51/// A context for a parse error: the document ("backing") and position
52/// in it. Unlike ParseableStr, owns the Backing, and does not contain
53/// the parsing window.
54#[derive(Debug)]
55pub struct StringParseContext<B: Backing> {
56    pub position: usize,
57    pub backing: B,
58}
59
60// Conflicting implementation in core--aha?: C includes itself.
61// impl<C: ParseContext, B: Backing> From<C> for StringParseContext<B> {
62//     fn from(value: C) -> Self {
63//     }
64// }
65
66impl<'t, B: Backing> IntoOwningBacking<B::Owned> for StringParseContext<&'t B>
67where
68    B::Owned: Backing,
69    &'t B: Backing,
70{
71    type Owning = StringParseContext<B::Owned>;
72
73    fn into_owning_backing(self) -> Self::Owning {
74        let Self { position, backing } = self;
75        StringParseContext {
76            position,
77            backing: backing.to_owned(),
78        }
79    }
80}
81
82// Do not use derive because then StringParseContext would *always*
83// require S: Clone.
84impl<B: Backing + Clone> Clone for StringParseContext<B> {
85    fn clone(&self) -> Self {
86        Self {
87            position: self.position,
88            backing: self.backing.clone(),
89        }
90    }
91}
92
93impl<B: Backing> PartialEq for StringParseContext<B> {
94    fn eq(&self, other: &Self) -> bool {
95        self.position == other.position
96            && self.backing.as_ref() == other.backing.as_ref()
97    }
98}
99
100impl<B: Backing> ParseContext for StringParseContext<B> {
101    fn show_context(&self, out: &mut String) {
102        let remainder = &self.backing.as_ref()[self.position..];
103        if remainder.is_empty() {
104            out.push_str(" at end of input")
105        } else {
106            write!(out, " at {:?}", remainder).expect("no err on string")
107        }
108    }
109}
110
111#[derive(Debug)]
112pub struct ParseError<C: ParseContext> {
113    pub message: String,
114    pub context: C,
115    pub backtrace: Vec<FileLocation>,
116}
117
118impl<'t, B: Backing> IntoOwningBacking<B::Owned>
119    for ParseError<StringParseContext<&'t B>>
120where
121    B::Owned: Backing,
122    &'t B: Backing,
123{
124    type Owning = ParseError<StringParseContext<B::Owned>>;
125
126    fn into_owning_backing(self) -> Self::Owning {
127        let ParseError {
128            message,
129            context,
130            backtrace,
131        } = self;
132        ParseError {
133            message,
134            context: context.into_owning_backing(),
135            backtrace,
136        }
137    }
138}
139
140impl<C: ParseContext> ParseError<C> {
141    pub fn to_string_showing_location(&self, show_backtrace: bool) -> String {
142        let ParseError {
143            message, context, ..
144        } = self;
145        let mut message = message.clone();
146        context.show_context(&mut message);
147        if show_backtrace {
148            self.show_backtrace(&mut message);
149        }
150        message
151    }
152
153    /// 'Backtrace' with the leaf location at the top, one location
154    /// per line, indented by a tab, starting with a newline.
155    pub fn show_backtrace(&self, out: &mut String) {
156        for l in &self.backtrace {
157            out.push_str("\n\t");
158            out.push_str(&l.to_string());
159        }
160    }
161
162    /// Append `s` to the message in self (functionally).
163    pub fn message_append(mut self, s: &str) -> Self {
164        self.message.push_str(s);
165        self
166    }
167}
168
169/// Only the message and context are compared (this is to allow for
170/// tests without breaking due to backtrace changes; backtrace should be
171/// just debugging information)
172impl<C: ParseContext> PartialEq for ParseError<C> {
173    fn eq(&self, other: &Self) -> bool {
174        self.message == other.message && self.context == other.context
175    }
176}
177
178#[derive(Debug)]
179pub struct FileLocation {
180    pub file: &'static str,
181    pub line: u32,
182    pub column: u32,
183}
184
185impl FileLocation {
186    pub fn to_string(&self) -> String {
187        format!("{}:{}:{}", self.file, self.line, self.column)
188    }
189}
190
191#[macro_export]
192macro_rules! file_location {
193    {} => {
194        $crate::parse::parse_error::FileLocation {
195            file: file!(),
196            line: line!(),
197            column: column!(),
198        }
199    }
200}
201
202#[macro_export]
203macro_rules! parse_error {
204    { $($body:tt)* } => {
205        ParseError {
206            backtrace: vec![$crate::file_location!()],
207            $($body)*
208        }
209    }
210}
211
212/// Define the type `LocalParseErrorContext` to a type that implements
213/// `ParseContext` where using this macro!
214#[macro_export]
215macro_rules! T {
216    { $e:expr } => {
217        $e.map_err(|e| {
218            let mut e: $crate::parse::parse_error::ParseError<_> = e.into();
219            e.backtrace.push($crate::file_location!());
220            e
221        })
222    }
223}