1use crate::errors::*;
2use chumsky::prelude::*;
3use std::fs;
4use std::collections::HashMap;
5
6type Result<T> = std::result::Result<T, AcfError>;
8
9#[derive(Clone, Debug, PartialEq, Eq, Default)]
15pub struct Acf {
16 pub entries: Vec<Entry>,
18}
19
20#[derive(Clone, Debug, PartialEq, Eq, Default)]
22pub struct Entry {
23 pub name: String,
25
26 pub expressions: HashMap<String, String>,
28
29 pub entries: Vec<Entry>,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
37struct Expr {
38 name: String,
40
41 value: String,
43}
44
45pub fn parse_acf(path: &str) -> Result<Acf> {
50 let contents = match fs::read_to_string(path) {
51 Ok(val) => val,
52 Err(_) => return Err(AcfError::Read(path.into())),
53 };
54
55 let entries = match acf_parser().parse(&contents).into_result() {
56 Ok(val) => val,
57 Err(e) => {
58 e.into_iter()
59 .for_each(|err| println!("Parse error: {}", err));
60 return Err(AcfError::Parse(ParseError::Unknown));
61 }
62 };
63
64 Ok(Acf { entries })
65}
66
67fn acf_parser<'src>() -> impl Parser<'src, &'src str, Vec<Entry>> {
72 entry_parser()
73 .padded()
74 .repeated()
75 .collect::<Vec<_>>()
76 .then_ignore(end())
77 .map(|entries| entries)
78}
79
80fn entry_parser<'src>() -> impl Parser<'src, &'src str, Entry> {
86 recursive(|rec_parser| {
87 str_parser()
88 .padded()
89 .then_ignore(just("{").padded())
90 .then(
91 expr_parser().padded().repeated().collect::<Vec<_>>()
92 )
93 .then(rec_parser.padded().repeated().collect::<Vec<_>>())
94 .then_ignore(just("}").padded())
95 .map(|((name, expressions), entries)| Entry {
96 name,
97 expressions: {
98 let names = expressions.iter().map(|expr| expr.name.clone());
99 let values = expressions.iter().map(|expr| expr.value.clone());
100
101 names.zip(values).collect()
102 },
103 entries,
104 })
105 .boxed()
106 })
107}
108
109fn expr_parser<'src>() -> impl Parser<'src, &'src str, Expr> {
115 str_parser()
116 .padded()
117 .then(str_parser())
118 .padded()
119 .map(|(str1, str2)| Expr {
120 name: str1,
121 value: str2,
122 })
123}
124
125fn str_parser<'src>() -> impl Parser<'src, &'src str, String> {
127 just('"')
128 .ignore_then(none_of('"').repeated().to_slice())
129 .then_ignore(just('"'))
130 .padded()
131 .map(|val: &str| val.to_owned())
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn does_run() {
140 let result = parse_acf("./acfs/simple.acf");
141 assert!(result.is_ok());
142 }
143
144 #[test]
145 fn simple() {
146 let result = parse_acf("./acfs/simple.acf");
147 assert!(result.is_ok());
148 let result = result.unwrap();
149 let root_entry = &result.entries[0];
150 assert_eq!(root_entry.name, "AppState");
151 let expressions = &root_entry.expressions;
152 assert_eq!(expressions["appid"], "730");
153 }
154
155 #[test]
156 fn full() {
157 let result = parse_acf("./acfs/appmanifest_730.acf");
158 assert!(result.is_ok());
159 let result = result.unwrap();
160 let root_entry = &result.entries[0];
161 assert_eq!(root_entry.name, "AppState");
162 let expressions = &root_entry.expressions;
163 assert_eq!(expressions["appid"], "730");
164 assert_eq!(expressions["universe"], "1");
165 assert_eq!(expressions["LauncherPath"], "C:\\\\Program Files (x86)\\\\Steam\\\\steam.exe");
166 assert_eq!(expressions["name"], "Counter-Strike 2");
167 }
168}