1pub type Result<T> = std::result::Result<T, ParseError>;
6
7use std::path::PathBuf;
8
9use crate::comptime::{Script, ScriptPath};
10use crate::consts::{COMPTIME_BORDER, SEPARATOR};
11use crate::pages::{ChoicesState, Interaction, Page, ParseState};
12use crate::InteractionMap;
13
14mod context;
15mod endings;
16mod metaline;
17
18pub use crate::pages::ParseError;
19pub use context::ScriptContext;
20pub use endings::{DialogueChoice, DialogueEnding, Label};
21
22pub struct DgParser {
23 state: ParseState,
24 context: ScriptContext,
25
26 path: PathBuf,
28
29 interactions: InteractionMap,
31
32 interaction: Option<Interaction>,
35 ix_id: Option<String>,
36 comptime_script: Vec<String>,
37 page: Page,
38 pagebuf: Vec<String>,
39 page_had_ending: bool,
40}
41
42impl DgParser {
43 pub fn new(path: PathBuf) -> Self {
44 Self {
45 state: ParseState::default(),
46 context: ScriptContext::default(),
47 path,
48
49 interactions: InteractionMap::new(),
50 interaction: None,
51 ix_id: None,
52 page: Page::default(),
53 pagebuf: vec![],
54 comptime_script: vec![],
55 page_had_ending: false,
56 }
57 }
58
59 fn set_ix_id(&mut self, id: &str) -> Result<()> {
60 if self.interaction.is_some() {
61 self.push_ix()?;
62 }
63
64 self.ix_id = Some(id.to_owned());
65 self.interaction = Some(Interaction::default());
66
67 Ok(())
68 }
69
70 fn parse_comptime(&mut self, line: &str) -> Result<()> {
71 if line == SEPARATOR && self.comptime_script.last() == Some(&COMPTIME_BORDER.to_owned()) {
73 self.comptime_script.pop();
74
75 let content = self.comptime_script.join("\n");
76 let path = ScriptPath(self.path.clone());
77 let mut script = Script::new(content, path);
78 script.execute(&mut self.context)?;
79
80 self.comptime_script.clear();
83
84 self.state = match &self.state {
85 ParseState::ComptimeScript(state) => *state.clone(),
86 _ => unreachable!(),
87 };
88 } else {
89 self.comptime_script.push(line.to_owned());
90 }
91
92 Ok(())
93 }
94
95 fn parse_message(&mut self, line: &str) -> Result<()> {
96 if line.is_empty() {
99 self.state = ParseState::Choices(ChoicesState::Choices);
100 } else {
101 self.pagebuf.push(line.to_string());
102 }
103
104 Ok(())
105 }
106
107 fn push_page(&mut self) -> Result<()> {
109 self.page.content = {
113 let mut it = self.pagebuf.iter().peekable();
114
115 let mut res = String::new();
116 while let Some(line) = it.next() {
117 let to_push = if line.ends_with("\\n") {
118 line.replace("\\n", "\n")
119 } else if it.peek().is_some() {
120 format!("{} ", line)
121 } else {
122 line.clone()
123 };
124
125 res.push_str(&to_push);
126 }
127
128 res
129 };
130
131 let ix = self.interaction.as_mut().ok_or(ParseError::PushPageNoIX)?;
132
133 let ix_has_ending_yet = ix.ending != DialogueEnding::End;
136 if ix_has_ending_yet {
137 if self.page_had_ending {
138 return Err(ParseError::PageAfterEnding);
139 }
140
141 self.page_had_ending = true;
144 }
145
146 ix.pages.push(self.page.clone());
147 self.pagebuf.clear();
148 self.page = Page::default();
149
150 Ok(())
151 }
152
153 fn push_ix(&mut self) -> Result<()> {
159 let ix_id = self.ix_id.take();
160 let ix = self.interaction.take();
161 let comptime_imports = self.context.drain_interactions();
162
163 if let (Some(ix_id), Some(ix)) = (ix_id, ix) {
164 if self.interactions.contains_key(&ix_id) {
165 return Err(ParseError::PushDuplicateIX);
166 }
167
168 self.interactions.insert(ix_id, ix);
169 } else if comptime_imports.is_empty() {
170 return Err(ParseError::PushEmptyIX);
173 }
174
175 self.interactions.extend(comptime_imports);
177
178 self.page_had_ending = false;
179
180 Ok(())
181 }
182
183 pub fn parse_all(&mut self, data: &str) -> Result<InteractionMap> {
184 let lines = data.lines();
185
186 self.pagebuf.clear();
187 self.page = Page::default();
188
189 for line in lines {
190 use ParseState::*;
191
192 let line = line.trim();
193
194 match self.state {
195 ComptimeScript(_) => self.parse_comptime(line)?,
198
199 Metadata => metaline::parse(self, line)?,
200 Message => self.parse_message(line)?,
201 Choices(ChoicesState::Choices) => endings::parse_choice(self, line)?,
202 };
203 }
204
205 self.push_ix()?;
206 let res = self.interactions.clone();
207 self.interactions.clear();
208 Ok(res)
209 }
210}
211
212#[cfg(test)]
213mod tests;