1#![deny(warnings, missing_docs)]
2#![doc = include_str!("../README.md")]
3
4use regex::Regex;
5use std::collections::HashMap;
6
7const SPLIT_PAT: &str = "||";
8const VAR_PAT: &str = r"__\w*";
9
10pub fn calc(s: &str) -> Option<f64> {
29 let boolf64 = |b| if b { 1.0 } else { 0.0 };
30 let args: Vec<&str> = s.trim().split(' ').collect();
31 if args.len() == 3 {
32 if let Ok(n1) = args[0].parse::<f64>() {
33 if let Ok(n2) = args[2].parse::<f64>() {
34 match args[1] {
35 "+" => Some(n1 + n2),
36 "-" => Some(n1 - n2),
37 "*" => Some(n1 * n2),
38 "/" => Some(n1 / n2),
39 "%" => Some(n1 % n2),
40 "<" => Some(boolf64(n1 < n2)),
41 ">" => Some(boolf64(n1 > n2)),
42 "<=" => Some(boolf64(n1 <= n2)),
43 ">=" => Some(boolf64(n1 >= n2)),
44 "==" => Some(boolf64(n1 == n2)),
45 "!=" => Some(boolf64(n1 != n2)),
46 _ => None,
47 }
48 } else {
49 None
50 }
51 } else {
52 None
53 }
54 } else {
55 None
56 }
57}
58
59pub fn split(s: &str) -> Vec<String> {
65 s.trim().split(SPLIT_PAT).map(|s| s.to_string()).collect()
66}
67
68#[derive(Clone, Debug, Eq, PartialEq)]
70pub enum LineType {
71 End,
73 Text,
75 Label,
77 Jump,
79 Menu,
81 Variable,
83 Check,
85}
86
87#[derive(Clone, Debug, Eq, PartialEq)]
89pub struct Line {
90 pub t: LineType,
92 pub info: String,
94 pub cont: String,
96}
97
98pub fn end() -> Line {
100 Line {
101 t: LineType::End,
102 info: String::new(),
103 cont: String::new(),
104 }
105}
106
107pub fn text(info: &str, cont: &str) -> Line {
109 Line {
110 t: LineType::Text,
111 info: String::from(info),
112 cont: String::from(cont),
113 }
114}
115
116pub fn label(cont: &str) -> Line {
118 Line {
119 t: LineType::Label,
120 info: String::new(),
121 cont: String::from(cont),
122 }
123}
124
125pub fn jump(cont: &str) -> Line {
127 Line {
128 t: LineType::Jump,
129 info: String::new(),
130 cont: String::from(cont),
131 }
132}
133
134pub fn menu(info: &str, cont: &str) -> Line {
136 Line {
137 t: LineType::Menu,
138 info: String::from(info),
139 cont: String::from(cont),
140 }
141}
142
143pub fn variable(info: &str, cont: &str) -> Line {
145 Line {
146 t: LineType::Variable,
147 info: String::from(info),
148 cont: String::from(cont),
149 }
150}
151
152pub fn check(cont: &str) -> Line {
154 Line {
155 t: LineType::Check,
156 info: String::new(),
157 cont: String::from(cont),
158 }
159}
160
161#[derive(Clone, Debug)]
163pub struct Dialogue {
164 idx: usize,
165 lines: Vec<Line>,
166 labels: HashMap<String, usize>,
167 pub vars: HashMap<String, String>,
169}
170
171impl Dialogue {
172 pub fn new(lines: Vec<Line>) -> Self {
174 let mut labels = HashMap::new();
175 let mut lines = lines;
176 lines.push(end());
177 for (i, line) in lines.iter().enumerate() {
178 if line.t == LineType::Label {
179 labels.insert(line.cont.clone(), i);
180 }
181 }
182 let mut d = Dialogue {
183 idx: 0,
184 lines,
185 labels,
186 vars: HashMap::new(),
187 };
188 d.update();
189 d
190 }
191
192 fn update(&mut self) {
193 let line = self.line();
194 match line.t {
195 LineType::Label => {
196 self.next();
197 }
198
199 LineType::Jump => {
200 self.idx = match self.labels.get(&line.cont) {
201 Some(idx) => *idx,
202 None => self.idx + 1,
203 };
204 self.update();
205 }
206
207 LineType::Variable => {
208 let val = if let Some(val) = calc(&line.cont) {
209 val.to_string()
210 } else {
211 line.cont
212 };
213 self.vars.insert(line.info, val);
214 self.next();
215 }
216
217 LineType::Check => {
218 let val = if let Some(val) = calc(&line.cont) {
219 val
220 } else {
221 0.0
222 };
223 if val == 1.0 {
224 self.next();
225 } else {
226 self.idx += 2;
227 if self.idx >= self.lines.len() {
228 self.idx = self.lines.len() - 1;
229 }
230 self.update();
231 }
232 }
233
234 _ => {}
235 }
236 }
237
238 pub fn idx(&self) -> usize {
240 self.idx
241 }
242
243 pub fn lines(&self) -> &Vec<Line> {
245 &self.lines
246 }
247
248 pub fn labels(&self) -> &HashMap<String, usize> {
250 &self.labels
251 }
252
253 pub fn has_end(&self) -> bool {
255 self.lines[self.idx].t == LineType::End
256 }
257
258 pub fn has_menu(&self) -> bool {
260 self.lines[self.idx].t == LineType::Menu
261 }
262
263 pub fn reset(&mut self) {
265 self.idx = 0;
266 }
267
268 pub fn change(&mut self, lines: Vec<Line>) {
270 self.reset();
271 self.labels.clear();
272 self.lines = lines;
273 self.lines.push(end());
274 for (i, line) in self.lines.iter().enumerate() {
275 if line.t == LineType::Label {
276 self.labels.insert(line.cont.clone(), i);
277 }
278 }
279 }
280
281 pub fn line(&self) -> Line {
292 let re = Regex::new(VAR_PAT).unwrap();
293 let line = &self.lines[self.idx];
294
295 let mut result = line.clone();
296 for caps in re.captures_iter(&line.info) {
297 let word = caps.get(0).unwrap().as_str();
298 if let Some(val) = self.vars.get(&word[2..]) {
299 result.info = result.info.replace(word, val);
300 }
301 }
302 for caps in re.captures_iter(&line.cont) {
303 let word = caps.get(0).unwrap().as_str();
304 if let Some(val) = self.vars.get(&word[2..]) {
305 result.cont = result.cont.replace(word, val);
306 }
307 }
308 result
309 }
310
311 pub fn next(&mut self) {
324 self.idx += 1;
325 self.update();
326 }
327
328 pub fn jump(&mut self, label: &str) {
347 self.idx = self.labels[label];
348 self.update();
349 }
350
351 pub fn choices(&self) -> Vec<String> {
353 split(&self.line().cont)
354 }
355
356 pub fn choose(&mut self, choice: usize) {
358 self.jump(&split(&self.line().info)[choice]);
359 }
360}