ck3_history_extractor/parser/
section_reader.rs1use std::{
2 error,
3 fmt::{self, Debug, Display},
4};
5
6use derive_more::From;
7use jomini::{
8 binary::{ReaderError as BinaryReaderError, Token as BinaryToken, TokenResolver},
9 text::{Operator, ReaderError as TextReaderError, Token as TextToken},
10};
11
12use super::{
13 section::Section,
14 tokens::TOKEN_TRANSLATOR,
15 types::{Tape, Token},
16};
17
18#[derive(Debug, From)]
20pub enum SectionReaderError<'err> {
21 UnexpectedToken(usize, Token<'err>, &'static str),
23 UnknownToken(u16),
25 TextReaderError(TextReaderError),
26 BinaryReaderError(BinaryReaderError),
27}
28
29impl<'err> Display for SectionReaderError<'err> {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::UnexpectedToken(pos, tok, desc) => {
33 write!(
34 f,
35 "reader encountered an unexpected token {:?} at {}: {}",
36 tok, pos, desc
37 )
38 }
39 Self::UnknownToken(token) => {
40 write!(f, "reader encountered an unknown token {}", token)
41 }
42 Self::TextReaderError(e) => {
43 write!(f, "text reader encountered an error: {}", e)
44 }
45 Self::BinaryReaderError(e) => {
46 write!(f, "binary reader encountered an error: {}", e)
47 }
48 }
49 }
50}
51
52impl<'err> error::Error for SectionReaderError<'err> {
53 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
54 match self {
55 Self::TextReaderError(e) => Some(e),
56 Self::BinaryReaderError(e) => Some(e),
57 _ => None,
58 }
59 }
60}
61
62pub fn yield_section<'tape, 'data: 'tape>(
66 tape: &'tape mut Tape<'data>,
67) -> Option<Result<Section<'tape, 'data>, SectionReaderError<'data>>> {
68 let mut potential_key = None;
69 let mut past_eq = false;
70 match tape {
71 Tape::Text(text) => {
72 while let Some(res) = text.next().transpose() {
73 match res {
74 Err(e) => {
75 return Some(Err(e.into()));
76 }
77 Ok(tok) => match tok {
78 TextToken::Open => {
79 if past_eq {
80 if let Some(key) = potential_key {
81 return Some(Ok(Section::new(tape, key)));
82 }
83 }
84 }
85 TextToken::Close => {
86 return Some(Err(SectionReaderError::UnexpectedToken(
87 text.position(),
88 TextToken::Close.into(),
89 "unexpected close token",
90 )))
91 }
92 TextToken::Operator(op) => {
93 if op == Operator::Equal {
94 past_eq = true;
95 } else {
96 past_eq = false;
97 }
98 }
99 TextToken::Unquoted(scalar) => {
100 potential_key = Some(scalar.to_string());
101 }
102 _ => {
103 past_eq = false;
104 potential_key = None;
105 }
106 },
107 }
108 }
109 }
110 Tape::Binary(binary) => {
111 while let Some(res) = binary.next().transpose() {
112 match res {
113 Err(e) => {
114 return Some(Err(e.into()));
115 }
116 Ok(tok) => match tok {
117 BinaryToken::Open => {
118 if past_eq {
119 if let Some(key) = potential_key {
120 return Some(Ok(Section::new(tape, key)));
121 }
122 }
123 }
124 BinaryToken::Close => {
125 return Some(Err(SectionReaderError::UnexpectedToken(
126 tape.position(),
127 BinaryToken::Close.into(),
128 "unexpected close token",
129 )))
130 }
131 BinaryToken::Unquoted(token) => potential_key = Some(token.to_string()),
132 BinaryToken::Id(token) => match TOKEN_TRANSLATOR.resolve(token) {
133 Some(key) => {
134 potential_key = Some(key.to_string());
135 }
136 None => {
137 return Some(Err(SectionReaderError::UnknownToken(token)));
138 }
139 },
140 BinaryToken::Equal => {
141 past_eq = true;
142 }
143 _ => {
144 past_eq = false;
145 potential_key = None;
146 }
147 },
148 }
149 }
150 }
151 }
152 return None;
153}
154
155#[cfg(test)]
156mod tests {
157 use jomini::text::TokenReader;
158
159 use super::*;
160
161 #[test]
162 fn test_empty() {
163 let mut tape = Tape::Text(TokenReader::from_slice(b""));
164 assert!(yield_section(&mut tape).is_none());
165 }
166
167 #[test]
168 fn test_single_section() {
169 let mut tape = Tape::Text(TokenReader::from_slice(b"test={a=1}"));
170 let section = yield_section(&mut tape).unwrap().unwrap();
171 assert_eq!(section.get_name(), "test");
172 }
173
174 #[test]
175 fn test_single_section_messy() {
176 let mut tape = Tape::Text(TokenReader::from_slice(b" \t\r test={a=1} \t\r "));
177 let mut section = yield_section(&mut tape).unwrap().unwrap();
178 assert_eq!(section.get_name(), "test");
179 section.skip().unwrap();
180 assert!(yield_section(&mut tape).is_none());
181 }
182
183 #[test]
184 fn test_multiple_sections() {
185 let mut tape = Tape::Text(TokenReader::from_slice(b"test={a=1}test2={b=2}test3={c=3}"));
186 let mut section = yield_section(&mut tape).unwrap().unwrap();
187 assert_eq!(section.get_name(), "test");
188 section.skip().unwrap();
189 let mut section = yield_section(&mut tape).unwrap().unwrap();
190 assert_eq!(section.get_name(), "test2");
191 section.skip().unwrap();
192 let mut section = yield_section(&mut tape).unwrap().unwrap();
193 assert_eq!(section.get_name(), "test3");
194 section.skip().unwrap();
195 assert!(yield_section(&mut tape).is_none());
196 }
197
198 #[test]
199 fn test_non_ascii_key() {
200 let mut tape = Tape::Text(TokenReader::from_slice(b"test={\x80=1}"));
201 let section = yield_section(&mut tape).unwrap().unwrap();
202 assert_eq!(section.get_name(), "test");
203 }
204
205 #[test]
206 fn test_mixed() {
207 let mut tape = Tape::Text(TokenReader::from_slice(
208 b"a\na=b\ntest={a=1}test2={b=2}test3={c=3}",
209 ));
210 let mut section = yield_section(&mut tape).unwrap().unwrap();
211 assert_eq!(section.get_name(), "test");
212 section.skip().unwrap();
213 let mut section = yield_section(&mut tape).unwrap().unwrap();
214 assert_eq!(section.get_name(), "test2");
215 section.skip().unwrap();
216 let mut section = yield_section(&mut tape).unwrap().unwrap();
217 assert_eq!(section.get_name(), "test3");
218 section.skip().unwrap();
219 }
220}