ck3_history_extractor/parser/
mod.rs

1/// A submodule that provides [jomini] abstractions.
2/// This allows us to create an intermediate parsing interface with objects like
3/// [SaveFile] and [Section] that hold these abstractions, however hiding if the
4/// save file is binary or text. Thanks to this a user doesn't have to write
5/// boilerplate code to handle both types of save files.
6mod types;
7
8use derive_more::From;
9use std::{
10    error,
11    fmt::{self, Debug, Display},
12    num::ParseIntError,
13};
14
15/// A submodule that provides the parser output objects.
16/// The parser uses [GameObject](crate::parser::game_object::SaveFileObject) to
17/// store the parsed data and structures in [structures](crate::structures) are
18/// initialized from these objects. This is our workaround for the lack of
19/// reflection in Rust, and it puts one more layer of abstraction between the
20/// parser and the structures. Jomini style would be to have the structures
21/// directly initialized from the token tape, but that wouldn't play well with
22/// the way we store everything in a central [GameState] object.
23mod game_object;
24pub use game_object::{
25    ConversionError, GameObjectCollection, GameObjectMap, GameObjectMapping, KeyError,
26    SaveFileObject, SaveFileValue, SaveObjectError,
27};
28
29/// A submodule that provides the [SaveFile] object, which is used to store the
30/// entire save file. This is essentially the front-end of the parser, handling
31/// the IO and the such.
32mod save_file;
33use jomini::common::DateError;
34pub use save_file::{SaveFile, SaveFileError};
35
36/// A submodule that provides the [Section] object, which allows the user to
37/// choose which sections should be parsed.
38mod section;
39pub use section::{Section, SectionError};
40
41/// A submodule that provides the [yield_section] function, which is used to
42/// iterate over the save file and return the next section.
43mod section_reader;
44pub use section_reader::yield_section;
45
46/// A submodule that provides the [GameState] object, which is used as a sort of
47/// a dictionary. CK3 save files have a myriad of different objects that
48/// reference each other, and in order to allow for centralized storage and easy
49/// access, the [GameState] object is used.
50mod game_state;
51pub use game_state::{GameRef, GameState};
52use section_reader::SectionReaderError;
53
54/// A submodule providing ironman token declarations.
55mod tokens;
56
57/// An error that occurred somewhere within the broadly defined parsing process.
58#[derive(Debug, From)]
59pub enum ParsingError {
60    /// An error that occurred while parsing a section.
61    SectionError(SectionError),
62    /// An error that occurred while processing [SaveFileValue] objects.
63    StructureError(SaveObjectError),
64    /// An error that occurred while creating [Section] objects.
65    ReaderError(String),
66    /// An error that occurred during low level tape processing
67    JominiError(jomini::Error),
68    DateError(DateError),
69}
70
71impl From<ConversionError> for ParsingError {
72    fn from(value: ConversionError) -> Self {
73        ParsingError::StructureError(SaveObjectError::ConversionError(value))
74    }
75}
76
77impl From<KeyError> for ParsingError {
78    fn from(value: KeyError) -> Self {
79        ParsingError::StructureError(SaveObjectError::KeyError(value))
80    }
81}
82
83impl From<ParseIntError> for ParsingError {
84    fn from(err: ParseIntError) -> Self {
85        ParsingError::StructureError(SaveObjectError::ConversionError(err.into()))
86    }
87}
88
89impl<'a, 'b: 'a> From<SectionReaderError<'b>> for ParsingError {
90    fn from(value: SectionReaderError<'b>) -> Self {
91        ParsingError::ReaderError(format!("{:?}", value))
92    }
93}
94
95impl<'a> Display for ParsingError {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            Self::ReaderError(err) => write!(f, "error during section reading: {}", err),
99            val => write!(f, "{}", val),
100        }
101    }
102}
103
104impl<'a> error::Error for ParsingError {
105    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
106        match self {
107            Self::SectionError(err) => Some(err),
108            Self::StructureError(err) => Some(err),
109            Self::JominiError(err) => Some(err),
110            Self::DateError(err) => Some(err),
111            _ => None,
112        }
113    }
114}
115
116use super::{
117    structures::{FromGameObject, Player},
118    types::{GameId, HashMap},
119};
120
121/// A function that processes a section of the save file.
122/// Based on the given section, it will update the [GameState] object and the
123/// [Player] vector. The [GameState] object is used to store all the data from
124/// the save file, while the [Player] vector is used to store the player data.
125/// Essentially the fasade of the parser, that makes the choices on which
126/// sections to parse and how to parse them.
127pub fn process_section(
128    i: &mut Section,
129    game_state: &mut GameState,
130    players: &mut Vec<Player>,
131) -> Result<(), ParsingError> {
132    match i.get_name() {
133        "meta_data" => {
134            let parsed = i.parse()?;
135            let map = parsed.as_map()?;
136            game_state
137                .set_current_date(map.get_date("meta_date")?, map.get_date("meta_real_date")?);
138        }
139        //the order is kept consistent with the order in the save file
140        "traits_lookup" => {
141            let lookup: Result<Vec<_>, _> = i
142                .parse()?
143                .as_array()?
144                .into_iter()
145                .map(|x| x.as_string())
146                .collect();
147            game_state.add_lookup(lookup?);
148        }
149        "landed_titles" => {
150            let parsed = i.parse()?;
151            let map = parsed.as_map()?;
152            for (key, v) in map.get_object("landed_titles")?.as_map()? {
153                if let SaveFileValue::Object(o) = v {
154                    game_state.add_title(&key.parse::<GameId>()?, o.as_map()?)?;
155                }
156            }
157        }
158        "county_manager" => {
159            let parsed = i.parse()?;
160            let map = parsed.as_map()?;
161            // we create an association between the county key and the faith and culture of the county
162            // this is so that we can easily add the faith and culture to the title, so O(n) instead of O(n^2)
163            let mut key_assoc = HashMap::default();
164            for (key, p) in map.get_object("counties")?.as_map()? {
165                let p = p.as_object()?.as_map()?;
166                let faith = game_state.get_faith(&p.get_game_id("faith")?);
167                let culture = game_state.get_culture(&p.get_game_id("culture")?);
168                key_assoc.insert(key.to_owned(), (faith, culture));
169            }
170            game_state.add_county_data(key_assoc);
171        }
172        "dynasties" => {
173            let m = i.parse()?;
174            let m = m.as_map()?;
175            for (key, house) in m.get("dynasty_house").unwrap().as_object()?.as_map()? {
176                if let SaveFileValue::Object(o) = house {
177                    game_state.add_house(&key.parse::<GameId>()?, o.as_map()?)?;
178                }
179            }
180            for (key, dynasty) in m.get("dynasties").unwrap().as_object()?.as_map()? {
181                if let SaveFileValue::Object(o) = dynasty {
182                    game_state.add_dynasty(&key.parse::<GameId>()?, o.as_map()?)?;
183                }
184            }
185        }
186        "character_lookup" => {
187            let parsed = i.parse()?;
188            let mut transform = HashMap::new();
189            for (key, val) in parsed.as_map()? {
190                if let Ok(key) = key.parse::<GameId>() {
191                    transform.insert(key, val.as_id()?);
192                }
193            }
194            game_state.add_character_transform(transform);
195        }
196        "living" => {
197            for (key, l) in i.parse()?.as_map()? {
198                match l {
199                    SaveFileValue::Object(o) => {
200                        game_state.add_character(&key.parse::<GameId>()?, o.as_map()?)?;
201                    }
202                    _ => {
203                        continue;
204                    }
205                }
206            }
207        }
208        "dead_unprunable" => {
209            for (key, d) in i.parse()?.as_map()? {
210                if let SaveFileValue::Object(o) = d {
211                    game_state.add_character(&key.parse::<GameId>()?, o.as_map()?)?;
212                }
213            }
214        }
215        "characters" => {
216            if let Some(dead_prunable) = i.parse()?.as_map()?.get("dead_prunable") {
217                for (key, d) in dead_prunable.as_object()?.as_map()? {
218                    match d {
219                        SaveFileValue::Object(o) => {
220                            game_state.add_character(&key.parse::<GameId>()?, o.as_map()?)?;
221                        }
222                        _ => {
223                            continue;
224                        }
225                    }
226                }
227            }
228        }
229        "vassal_contracts" => {
230            let r = i.parse()?;
231            let map = r.as_map()?;
232            // if version <= 1.12 then the key is active, otherwise it is database, why paradox?
233            for (key, contract) in map
234                .get("database")
235                .or(map.get("active"))
236                .ok_or_else(|| KeyError::MissingKey("database".to_string(), map.clone()))?
237                .as_object()?
238                .as_map()?
239            {
240                if let SaveFileValue::Object(val) = contract {
241                    let val = val.as_map()?;
242                    game_state
243                        .add_contract(&key.parse::<GameId>().unwrap(), &val.get_game_id("vassal")?)
244                }
245            }
246        }
247        "religion" => {
248            let parsed = i.parse()?;
249            let map = parsed.as_map()?;
250            for (key, f) in map.get_object("faiths")?.as_map()? {
251                game_state.add_faith(&key.parse::<GameId>()?, f.as_object()?.as_map()?)?;
252            }
253        }
254        "culture_manager" => {
255            let parsed = i.parse()?;
256            let map = parsed.as_map()?;
257            let cultures = map.get_object("cultures")?.as_map()?;
258            for (key, c) in cultures {
259                game_state.add_culture(&key.parse::<GameId>()?, c.as_object()?.as_map()?)?;
260            }
261        }
262        "character_memory_manager" => {
263            let parsed = i.parse()?;
264            let map = parsed.as_map()?;
265            for (key, d) in map.get_object("database")?.as_map()? {
266                if let SaveFileValue::Object(o) = d {
267                    game_state.add_memory(&key.parse::<GameId>()?, o.as_map()?)?;
268                }
269            }
270        }
271        "played_character" => {
272            let p = Player::from_game_object(i.parse()?.as_map()?, game_state)?;
273            players.push(p);
274        }
275        "artifacts" => {
276            let parsed = i.parse()?;
277            let map = parsed.as_map()?;
278            for (key, a) in map.get_object("artifacts")?.as_map()?.into_iter() {
279                if let SaveFileValue::Object(o) = a {
280                    game_state.add_artifact(&key.parse::<GameId>()?, o.as_map()?)?;
281                }
282            }
283        }
284        _ => {
285            i.skip()?;
286        }
287    }
288    return Ok(());
289}
290
291#[cfg(test)]
292mod tests {
293
294    use jomini::{self, text::TokenReader};
295
296    use super::{types::Tape, *};
297
298    fn get_test_obj(contents: &str) -> Result<Tape, jomini::Error> {
299        Ok(Tape::Text(TokenReader::from_slice(contents.as_bytes())))
300    }
301
302    #[test]
303    fn test_save_file() -> Result<(), Box<dyn std::error::Error>> {
304        let mut tape = get_test_obj(
305            "
306            test={
307                test2={
308                    test3=1
309                }
310            }
311        ",
312        )?;
313        let mut section = yield_section(&mut tape).unwrap().unwrap();
314        assert_eq!(section.get_name(), "test");
315        let object = section.parse().unwrap();
316        let test2 = object
317            .as_map()?
318            .get("test2")
319            .unwrap()
320            .as_object()?
321            .as_map()?;
322        assert_eq!(test2.get("test3").unwrap().as_integer()?, 1);
323        return Ok(());
324    }
325
326    #[test]
327    fn test_save_file_array() -> Result<(), Box<dyn std::error::Error>> {
328        let mut tape = get_test_obj(
329            "
330            test={
331                test2={
332                    1
333                    2
334                    3
335                }
336                test3={ 1 2 3}
337            }
338        ",
339        )?;
340        let mut section = yield_section(&mut tape).unwrap()?;
341        assert_eq!(section.get_name(), "test");
342        let object = section.parse().unwrap();
343        let test2 = object.as_map()?.get("test2").unwrap().as_object()?;
344        let test2_val = test2.as_array()?;
345        assert_eq!(test2_val.get_index(0)?.as_integer()?, 1);
346        assert_eq!(test2_val.get_index(1)?.as_integer()?, 2);
347        assert_eq!(test2_val.get_index(2)?.as_integer()?, 3);
348        let test3 = object.as_map()?.get("test3").unwrap().as_object()?;
349        let test3_val = test3.as_array()?;
350        assert_eq!(test3_val.get_index(0)?.as_integer()?, 1);
351        assert_eq!(test3_val.get_index(1)?.as_integer()?, 2);
352        assert_eq!(test3_val.get_index(2)?.as_integer()?, 3);
353        Ok(())
354    }
355
356    #[test]
357    fn test_weird_syntax() -> Result<(), Box<dyn std::error::Error>> {
358        let mut tape = get_test_obj(
359            "
360            test={
361                test2={1=2
362                    3=4}
363                test3={1 2 
364                    3}
365                test4={1 2 3}
366                test5=42
367            }
368        ",
369        )
370        .unwrap();
371        let mut section = yield_section(&mut tape).unwrap().unwrap();
372        assert_eq!(section.get_name(), "test");
373        let object = section.parse().unwrap();
374        let test2 = object
375            .as_map()?
376            .get("test2")
377            .unwrap()
378            .as_object()?
379            .as_map()?;
380        assert_eq!(test2.get("1").unwrap().as_integer()?, 2);
381        assert_eq!(test2.get("3").unwrap().as_integer()?, 4);
382        Ok(())
383    }
384
385    #[test]
386    fn test_array_syntax() -> Result<(), Box<dyn std::error::Error>> {
387        let mut tape = get_test_obj(
388            "
389            test={
390                test2={ 1 2 3 }
391            }
392        ",
393        )
394        .unwrap();
395        let mut section = yield_section(&mut tape).unwrap().unwrap();
396        assert_eq!(section.get_name(), "test");
397        let object = section.parse().unwrap();
398        let test2 = object
399            .as_map()?
400            .get("test2")
401            .unwrap()
402            .as_object()?
403            .as_array()?;
404        assert_eq!(test2.get_index(0)?.as_integer()?, 1);
405        assert_eq!(test2.get_index(1)?.as_integer()?, 2);
406        assert_eq!(test2.get_index(2)?.as_integer()?, 3);
407        assert_eq!(test2.len(), 3);
408        Ok(())
409    }
410
411    #[test]
412    fn test_unnamed_obj() -> Result<(), Box<dyn std::error::Error>> {
413        let mut tape = get_test_obj(
414            "
415        3623={
416            name=\"dynn_Sao\"
417            variables={
418                data={ 
419                        {
420                            flag=\"ai_random_harm_cooldown\"
421                            tick=7818
422                            data={
423                                type=boolean
424                                identity=1
425                            }
426                        }
427                        {
428                            something_else=\"test\"
429                        }
430                    }
431                }
432            }
433        }
434        ",
435        )
436        .unwrap();
437        let object = yield_section(&mut tape).unwrap().unwrap().parse().unwrap();
438        let variables = object
439            .as_map()?
440            .get("variables")
441            .unwrap()
442            .as_object()?
443            .as_map()?;
444        let data = variables.get("data").unwrap().as_object()?.as_array()?;
445        assert_ne!(data.len(), 0);
446        Ok(())
447    }
448
449    #[test]
450    fn test_example_1() -> Result<(), Box<dyn std::error::Error>> {
451        let mut tape = get_test_obj("
452        3623={
453            name=\"dynn_Sao\"
454            variables={
455                data={ {
456                        flag=\"ai_random_harm_cooldown\"
457                        tick=7818
458                        data={
459                            type=boolean
460                            identity=1
461                        }
462        
463                    }
464         }
465            }
466            found_date=750.1.1
467            head_of_house=83939093
468            dynasty=3623
469            historical={ 4440 5398 6726 10021 33554966 50385988 77977 33583389 50381158 50425637 16880568 83939093 }
470            motto={
471                key=\"motto_with_x_I_seek_y\"
472                variables={ {
473                        key=\"1\"
474                        value=\"motto_the_sword_word\"
475                    }
476         {
477                        key=\"2\"
478                        value=\"motto_bravery\"
479                    }
480         }
481            }
482            artifact_claims={ 83888519 }
483        }").unwrap();
484        let mut section = yield_section(&mut tape).unwrap().unwrap();
485        assert_eq!(section.get_name(), "3623");
486        let object = section.parse().unwrap();
487        assert_eq!(
488            *(object.as_map()?.get("name").unwrap().as_string()?),
489            "dynn_Sao".to_string()
490        );
491        let historical = object
492            .as_map()?
493            .get("historical")
494            .unwrap()
495            .as_object()?
496            .as_array()?;
497        assert_eq!(historical.get_index(0)?.as_integer()?, 4440);
498        assert_eq!(historical.get_index(1)?.as_integer()?, 5398);
499        assert_eq!(historical.get_index(2)?.as_integer()?, 6726);
500        assert_eq!(historical.get_index(3)?.as_integer()?, 10021);
501        assert_eq!(historical.get_index(4)?.as_integer()?, 33554966);
502        assert_eq!(historical.len(), 12);
503        Ok(())
504    }
505
506    #[test]
507    fn test_space() -> Result<(), Box<dyn std::error::Error>> {
508        let mut tape = get_test_obj(
509            "
510        test = {
511            test2 = {
512                test3 = 1
513            }
514            test4 = { a b c}
515        }
516        ",
517        )
518        .unwrap();
519        let mut section = yield_section(&mut tape).unwrap().unwrap();
520        assert_eq!(section.get_name(), "test");
521        let object = section.parse().unwrap();
522        let test2 = object
523            .as_map()?
524            .get("test2")
525            .unwrap()
526            .as_object()?
527            .as_map()?;
528        assert_eq!(test2.get("test3").unwrap().as_integer()?, 1);
529        let test4 = object.as_map()?.get("test4").unwrap().as_object()?;
530        let test4_val = test4.as_array()?;
531        assert_eq!(*(test4_val.get_index(0)?.as_string()?), "a".to_string());
532        assert_eq!(*(test4_val.get_index(1)?.as_string()?), "b".to_string());
533        assert_eq!(*(test4_val.get_index(2)?.as_string()?), "c".to_string());
534        Ok(())
535    }
536
537    #[test]
538    fn test_landed() -> Result<(), Box<dyn std::error::Error>> {
539        let mut tape = get_test_obj(
540            "
541        c_derby = {
542            color = { 255 50 20 }
543
544            cultural_names = {
545                name_list_norwegian = cn_djuraby
546                name_list_danish = cn_djuraby
547                name_list_swedish = cn_djuraby
548                name_list_norse = cn_djuraby
549            }
550
551            b_derby = {
552                province = 1621
553
554                color = { 255 89 89 }
555
556                cultural_names = {
557                    name_list_norwegian = cn_djuraby
558                    name_list_danish = cn_djuraby
559                    name_list_swedish = cn_djuraby
560                    name_list_norse = cn_djuraby
561                }
562            }
563            b_chesterfield = {
564                province = 1622
565
566                color = { 255 50 20 }
567            }
568            b_castleton = {
569                province = 1623
570
571                color = { 255 50 20 }
572            }
573        }
574        ",
575        )
576        .unwrap();
577        let mut section = yield_section(&mut tape).unwrap().unwrap();
578        assert_eq!(section.get_name(), "c_derby");
579        let object = section.parse().unwrap();
580        let b_derby = object
581            .as_map()?
582            .get("b_derby")
583            .unwrap()
584            .as_object()?
585            .as_map()?;
586        assert_eq!(b_derby.get("province").unwrap().as_integer()?, 1621);
587        let b_chesterfield = object
588            .as_map()?
589            .get("b_chesterfield")
590            .unwrap()
591            .as_object()?
592            .as_map()?;
593        assert_eq!(b_chesterfield.get("province").unwrap().as_integer()?, 1622);
594        let b_castleton = object
595            .as_map()?
596            .get("b_castleton")
597            .unwrap()
598            .as_object()?
599            .as_map()?;
600        assert_eq!(b_castleton.get("province").unwrap().as_integer()?, 1623);
601        Ok(())
602    }
603
604    #[test]
605    fn test_invalid_line() -> Result<(), Box<dyn std::error::Error>> {
606        let mut tape = get_test_obj(
607            "
608            nonsense=idk
609            test={
610                test2={
611                    test3=1
612                }
613            }
614        ",
615        )
616        .unwrap();
617        let mut section = yield_section(&mut tape).unwrap().unwrap();
618        assert_eq!(section.get_name(), "test");
619        let object = section.parse().unwrap();
620        let test2 = object
621            .as_map()?
622            .get("test2")
623            .unwrap()
624            .as_object()?
625            .as_map()?;
626        assert_eq!(test2.get("test3").unwrap().as_integer()?, 1);
627        Ok(())
628    }
629
630    #[test]
631    fn test_empty() {
632        let mut tape = get_test_obj(
633            "
634            test={
635            }
636        ",
637        )
638        .unwrap();
639        let object = yield_section(&mut tape).unwrap().unwrap();
640        assert_eq!(object.get_name(), "test");
641    }
642
643    #[test]
644    fn test_arr_index() {
645        let mut tape = get_test_obj(
646            "
647            duration={ 2 0=7548 1=2096 }
648        ",
649        )
650        .unwrap();
651        let mut section = yield_section(&mut tape).unwrap().unwrap();
652        assert_eq!(section.get_name(), "duration");
653        let object = section.parse().unwrap();
654        let arr = object.as_array().unwrap();
655        assert_eq!(arr.len(), 3);
656        assert_eq!(arr[0].as_id().unwrap(), 7548);
657    }
658
659    #[test]
660    fn test_multi_key() -> Result<(), Box<dyn std::error::Error>> {
661        let mut tape = get_test_obj(
662            "
663        test={
664            a=hello
665            a=world
666        }
667        ",
668        )
669        .unwrap();
670        let object = yield_section(&mut tape).unwrap().unwrap().parse().unwrap();
671        let arr = object.as_map()?.get("a").unwrap().as_object()?.as_array()?;
672        assert_eq!(arr.len(), 2);
673        Ok(())
674    }
675
676    /*
677    #[test]
678    fn test_invalid_syntax_1() {
679        let mut tape = get_test_obj(
680            "
681        test={
682            a=hello
683            b
684        }
685        ",
686        )
687        .unwrap();
688        let object = yield_section(&mut tape).unwrap();
689        assert!(object.unwrap().parse().is_ok())
690    }
691
692    #[test]
693    fn test_invalid_syntax_2() {
694        let mut tape = get_test_obj(
695            "
696        test={
697            b
698            a=hello
699        }
700        ",
701        )
702        .unwrap();
703        let object = yield_section(&mut tape).unwrap();
704        assert!(object.unwrap().parse().is_err())
705    }
706    */
707}