1#![allow(clippy::result_unit_err)]
2
3use std::{cell::RefCell, collections::HashMap};
4
5use regex::Regex;
6use thiserror::Error;
7
8#[cfg(feature = "debug-logs")]
9const PARSE_DEBUG: bool = true;
10#[cfg(not(feature = "debug-logs"))]
11const PARSE_DEBUG: bool = false;
12
13#[derive(Debug, Copy, Clone, Hash, PartialEq)]
14pub struct FileLocation {
15 pub line_number: usize,
16 pub position: usize,
17 pub index: usize,
18}
19
20impl FileLocation {
21 pub fn start() -> Self {
22 Self {
23 line_number: 1,
24 position: 0,
25 index: 0,
26 }
27 }
28
29 pub fn advanced_by(mut self, text: &str) -> Self {
30 let lines = text.chars().filter(|c| *c == '\n').count();
31 self.line_number += lines;
32 self.index += text.len();
33
34 if lines > 0 {
35 let last_newline = text.rfind('\n').unwrap();
36 self.position = text.len() - last_newline;
37 } else {
38 self.position += text.len();
39 }
40
41 self
42 }
43}
44
45impl std::fmt::Display for FileLocation {
46 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
47 write!(f, "line {}:{}", self.line_number, self.position)
48 }
49}
50
51#[derive(Debug, Clone, Hash, PartialEq)]
52pub struct TextSlice<'a> {
53 pub text: &'a str,
54 pub location: FileLocation,
55}
56
57impl<'a> TextSlice<'a> {
58 pub fn new(text: &'a str, location: FileLocation) -> Self {
59 Self { text, location }
60 }
61}
62
63#[derive(Error, Debug, Clone)]
64pub struct ParseError {
65 pub message: &'static str,
66 pub at: FileLocation,
67 pub child_errors: Vec<ParseError>,
68}
69
70impl ParseError {
71 pub fn new(message: &'static str, at: FileLocation) -> Self {
72 Self {
73 message,
74 at,
75 child_errors: Vec::new(),
76 }
77 }
78
79 pub fn with_child(message: &'static str, at: FileLocation, child: ParseError) -> Self {
80 if child.child_errors.len() == 1 {
81 Self {
83 message,
84 at,
85 child_errors: child.child_errors,
86 }
87 } else {
88 Self {
89 message,
90 at,
91 child_errors: vec![child],
92 }
93 }
94 }
95
96 pub fn with_children(
97 message: &'static str,
98 at: FileLocation,
99 children: Vec<ParseError>,
100 ) -> Self {
101 Self {
102 message,
103 at,
104 child_errors: children,
105 }
106 }
107}
108
109impl std::fmt::Display for ParseError {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 writeln!(f, "{}", ParseErrorFmtDepth(self, 0))?;
112 Ok(())
113 }
114}
115
116struct ParseErrorFmtDepth<'a>(&'a ParseError, usize);
117
118impl std::fmt::Display for ParseErrorFmtDepth<'_> {
119 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120 let err = self.0;
121 let depth = self.1;
122
123 for _ in 0..depth {
124 write!(f, " ")?;
125 }
126 writeln!(f, "{} at {}", err.message, err.at)?;
127
128 for child in &err.child_errors {
129 write!(f, "{}", ParseErrorFmtDepth(child, depth + 1))?;
130 }
131 Ok(())
132 }
133}
134
135#[derive(Debug, Clone, Copy)]
136pub struct StringParser<'a> {
137 pub text: &'a str,
138 pub pos: FileLocation,
139}
140
141impl<'a> StringParser<'a> {
142 pub fn new(input: &'a str) -> Self {
143 Self {
144 text: input,
145 pos: FileLocation::start(),
146 }
147 }
148
149 pub fn split_at(&self, pos: usize) -> (TextSlice<'a>, Self) {
150 let (left, right) = self.text.split_at(pos);
151 let forked = Self {
152 text: right,
153 pos: self.pos.advanced_by(left),
154 };
155 let left = TextSlice::new(left, self.pos);
156 (left, forked)
157 }
158
159 pub fn is_empty(&self) -> bool {
160 self.text.is_empty()
161 }
162}
163
164thread_local! {
165 pub static REGEXES: RefCell<HashMap<String,Regex>> = RefCell::new(HashMap::new());
166}
167
168pub fn parse_string_lit<'a>(
169 input: StringParser<'a>,
170 lit: &'static str,
171) -> Result<(TextSlice<'a>, StringParser<'a>), ()> {
172 if input.text.starts_with(lit) {
173 if PARSE_DEBUG {
174 println!("Matched string literal: {:?} at {}", lit, input.pos);
175 }
176 Ok(input.split_at(lit.len()))
177 } else {
178 if PARSE_DEBUG {
179 println!("Failed to match string literal: {:?} at {}", lit, input.pos);
180 }
181 Err(())
182 }
183}
184
185fn get_regex(regex: &str) -> Regex {
186 REGEXES.with(|regexes| {
187 let mut regexes = regexes.borrow_mut();
188 if let Some(regex) = regexes.get(regex) {
189 regex.clone()
190 } else {
191 let new_regex = Regex::new(&format!("^{}", regex)).unwrap();
192 regexes.insert(regex.to_string(), new_regex.clone());
193 new_regex
194 }
195 })
196}
197
198pub fn parse_string_regex<'a>(
199 input: StringParser<'a>,
200 regex_str: &'static str,
201) -> Result<(TextSlice<'a>, StringParser<'a>), ()> {
202 let regex = get_regex(regex_str);
203
204 if let Some(captures) = regex.find_at(input.text, 0) {
205 if PARSE_DEBUG {
206 println!("Matched regex: {:?} at {}", regex_str, input.pos);
207 }
208 let capture = captures;
209 Ok(input.split_at(capture.end()))
210 } else {
211 if PARSE_DEBUG {
212 println!("Failed to match regex: {:?} at {}", regex_str, input.pos);
213 }
214 Err(())
215 }
216}