Skip to main content

deepwoken_reqparse/parse/
reqfile.rs

1use std::collections::{HashMap, HashSet};
2use std::path::Path;
3
4use crate::model::req::{ReqVecExt, Requirement, Timing};
5use crate::util::reqtree::ReqTree;
6use crate::error::{Result, ReqparseError};
7use crate::model::{opt::OptionalGroup};
8use winnow::ascii::{digit1, multispace0};
9use winnow::combinator::{alt, separated};
10use winnow::prelude::*;
11
12use super::req::{identifier, requirement};
13
14enum BaseReqfileLine {
15    Requirement(Requirement),
16    DepedencyWithIdentifier {
17        prereqs: Vec<String>,
18        dependent: String,
19    },
20}
21
22/// A full reqfile line.
23/// Note a required requirement cannot have optional prereqs.
24enum ReqfileLine {
25    /// The regular requirement line.
26    Unspecified(BaseReqfileLine),
27    /// A line with the prefix '+', that forces it and its dependents to all be required. 
28    /// Used to force a prereq of an optional req to be required. 
29    ForceRequired(BaseReqfileLine),
30    /// A line with the prefix 'n ;', where n is an integer from 0-5. Marks the req as optional
31    /// and assigns n as the weight. Recursively marks all prereqs as optional and ties their obtainment
32    /// to each other.  
33    Optional { base: BaseReqfileLine, weight: i64 }
34}
35
36/// The parsed representation of a reqfile
37#[derive(Debug)]
38pub struct Reqfile {
39    pub general: Vec<Requirement>,
40    pub post: Vec<Requirement>,
41
42    pub optional: Vec<OptionalGroup>
43}
44
45impl ReqfileLine {
46    pub fn base(&self) -> &BaseReqfileLine {
47        match self {
48            ReqfileLine::Unspecified(base)
49            | ReqfileLine::ForceRequired(base) 
50            | ReqfileLine::Optional { base, .. } => base,
51        }
52    }
53
54    pub fn base_mut(&mut self) -> &mut BaseReqfileLine {
55        match self {
56            ReqfileLine::Unspecified(base)
57            | ReqfileLine::ForceRequired(base) 
58            | ReqfileLine::Optional { base, .. } => base,
59        }
60    }
61
62    pub fn is_explicit_optional(&self) -> bool {
63        match self {
64            ReqfileLine::Optional { .. } => true,
65            _ => false
66        }
67    }
68}
69
70fn parse_reqfile_line(input: &str) -> std::result::Result<ReqfileLine, String> {
71    let input = input.trim();
72    reqfile_line
73        .parse(&input)
74        .map_err(|e| format!("Parse error: {}", e))
75}
76
77fn reqfile_line(input: &mut &str) -> ModalResult<ReqfileLine> {
78    let _ = multispace0.parse_next(input)?;
79    alt((
80        optional_line,
81        force_required_line,
82        base_reqfile_line.map(ReqfileLine::Unspecified),
83    ))
84    .parse_next(input)
85}
86
87// optional_line = weight ';' base_reqfile_line
88fn optional_line(input: &mut &str) -> ModalResult<ReqfileLine> {
89    let weight = 
90        digit1.try_map(|s: &str| s.parse::<i64>())
91        .verify(|&n| (1..=20).contains(&n))
92        .parse_next(input)?;
93
94    let _ = (multispace0, ';', multispace0).parse_next(input)?;
95    let base = base_reqfile_line.parse_next(input)?;
96    Ok(ReqfileLine::Optional { base, weight })
97}
98
99// force_reqfile_line = '+' base_reqfile_line
100fn force_required_line(input: &mut &str) -> ModalResult<ReqfileLine> {
101    let _ = ('+', multispace0).parse_next(input)?;
102    let base = base_reqfile_line.parse_next(input)?;
103    Ok(ReqfileLine::ForceRequired(base))
104}
105
106// base_reqfile_line = dependency_statement | requirement
107fn base_reqfile_line(input: &mut &str) -> ModalResult<BaseReqfileLine> {
108    let _ = multispace0.parse_next(input)?;
109
110    alt((
111        dependency_statement,
112        requirement.map(BaseReqfileLine::Requirement),
113    ))
114    .parse_next(input)
115}
116
117// dependency_statement = identifier (',' identifier)* '=>' (requirement | identifier)
118fn dependency_statement(input: &mut &str) -> ModalResult<BaseReqfileLine> {
119    let prereqs: Vec<String> =
120        separated(1.., identifier, (multispace0, ',', multispace0)).parse_next(input)?;
121
122    let _ = multispace0.parse_next(input)?;
123    let _ = "=>".parse_next(input)?;
124    let _ = multispace0.parse_next(input)?;
125
126    alt((
127        |i: &mut &str| {
128            let mut req = requirement.parse_next(i)?;
129            req.prereqs = prereqs.clone();
130            Ok(BaseReqfileLine::Requirement(req))
131        },
132        |i: &mut &str| {
133            let dependent = identifier.parse_next(i)?;
134            Ok(BaseReqfileLine::DepedencyWithIdentifier {
135                prereqs: prereqs.clone(),
136                dependent,
137            })
138        },
139    ))
140    .parse_next(input)
141}
142
143struct ParsedLine {
144    rf_line: ReqfileLine,
145    line_num: usize,
146    timing: Timing
147}
148
149fn validate_and_transform(mut lines: Vec<ParsedLine>) -> Result<Reqfile> {
150    // store indices of all the named requirements for referencing later
151    let mut named: HashMap<String, usize> = HashMap::new();
152
153    // store indices of all the reqs using their str representation if none
154    let str_to_idx: HashMap<String, usize> = lines.iter().enumerate()
155        .filter_map(|(i, l)| {
156        match l.rf_line.base() {
157            BaseReqfileLine::Requirement(req) 
158                => Some((req.name_or_default(), i)),
159            _ => None
160        }
161    }).collect();
162
163    // a list of all reqs with prereq identifiers (prereqs, req, line_num)
164    let mut dependent_ids: Vec<(Vec<String>, String, u64)> = vec![];
165
166    // collect the dependence statements and push all named identifiers
167    // into a map
168    for (vec_idx, line) in lines.iter().enumerate() {
169        let line_num = line.line_num;
170
171        let base = line.rf_line.base();
172
173        match base {
174            BaseReqfileLine::DepedencyWithIdentifier { prereqs, dependent } 
175            => {
176                // TODO! DependencyWithId should actually be a top level enum variatn.
177                // since its not affected by required, forced, unmarked semantics
178                // so yea for now we error if the user misuses the api (FOR NOW)
179                if let ReqfileLine::Unspecified(_) = &line.rf_line {
180                    
181                } else {
182                    return Err(ReqparseError::Reqfile {
183                        line: line.line_num, 
184                        message: "Optional annotations '+' or ';' must be used \
185                        at the requirement definition, not in a dependency statement, unless \
186                        the definition is in the dependency statement itself.".into()
187                    })
188                };
189
190                dependent_ids.push(
191                    (prereqs.clone(), dependent.clone(), line_num as u64)
192                );
193            },
194            BaseReqfileLine::Requirement(req) => {
195                // update the named reqs map
196                if let Some(name) = &req.name {
197                    // if its a duplicate identifier, err
198                    if named.insert(name.clone(), vec_idx).is_some() {
199                        return Err(ReqparseError::Reqfile {
200                            line: (line_num + 1) as usize,
201                            message: format!("Duplicate identifier: {}", name),
202                        }); 
203                    }
204                }
205            }
206        };
207    }
208
209    // sanity check: an anonymous req with prereqs is not equal 
210    // to another anonymous req anywhere (more specifically their string forms). 
211    // (since this may fuck up the tree creation. force the user to identify them uniquely)
212    for line in &lines {
213        let base = line.rf_line.base(); 
214
215        if let BaseReqfileLine::Requirement(req) = base {
216            // only lf anon reqs 
217            if req.name.is_some() { continue }
218
219            let other_anon = lines.iter().map(|line| line.rf_line.base())
220            .find(|other| {
221                if let BaseReqfileLine::Requirement(other_req) = other {
222                    other_req.name.is_none()
223                    && other_req.name_or_default() == req.name_or_default()
224                    // if any one of them has prereqs, we want to raise this err
225                    && (!other_req.prereqs.is_empty() || !req.prereqs.is_empty())
226                    && other_req != req
227                } else {
228                    false 
229                }
230            });
231
232            if other_anon.is_some() {
233                return Err(ReqparseError::Reqfile { 
234                    line: line.line_num, 
235                    message: format!(
236                        "You may not have duplicate anonymous requirements if either of them have prerequisites: {}",
237                        req.name_or_default()
238                    )
239                })
240            }
241        }
242    }
243
244    // resolve dependency statements within the parsed lines vec
245    for (prereqs, name, line_num) in dependent_ids {
246        match named.get(&name) {
247            Some(vec_idx) => {
248                // quick validation loop to check if
249                // the prereqs do in fact exist
250                for prereq in &prereqs {
251                    if !named.contains_key(prereq) {
252                        return Err(ReqparseError::Reqfile { 
253                            line: line_num as usize, 
254                            message: format!("Prerequisite: no variable named '{name}'.")
255                        })
256                    }
257                }
258
259                let line = &mut lines[*vec_idx];
260                
261                let base: &mut BaseReqfileLine = line.rf_line.base_mut();
262
263                match base {
264                    BaseReqfileLine::Requirement(req) => {
265                        if !req.prereqs.is_empty() {
266                            return Err(ReqparseError::Reqfile { 
267                                line: line_num as usize, 
268                                message: format!("'{name}' has multiple prerequisite assignments.")
269                            })
270                        }
271
272                        req.prereqs = prereqs;
273                    },
274                    _ => {}
275                };
276            },
277            None => {
278                return Err(ReqparseError::Reqfile { 
279                    line: line_num as usize, 
280                    message: format!("Dependent: no variable named '{name}'.")
281                })
282            }
283        }
284    }
285
286    let mut tree = ReqTree::new();
287
288    // build the requirement tree
289    for line in &lines {
290        let base = line.rf_line.base();
291
292        match base {
293            BaseReqfileLine::Requirement(req) => {
294                tree.insert(req.clone());
295            },
296            _ => {}
297        }
298    }
299
300    // sanity check: why tf would we have a cycle 
301    if let Some(cycle) = tree.find_cycle() {
302        return Err(ReqparseError::Reqfile {
303            line: 0, 
304            message: format!(
305                "Prereqs cannot be dependent on each other. Found cycle: {}", 
306                cycle.join(" => ")
307            ) 
308        })
309    }
310
311    // sanity check: a required req cannot have an optional prereq
312    let check_opt_prereq = || {
313        for line in &lines {
314            match &line.rf_line {
315                // check dependents of Optional reqs to see if dependents are strict, 
316                // faster and simpler bc they must be named to have dependents by the standard
317                // (yes the reqfile standard trust me bro)
318                ReqfileLine::Optional { base, .. } => {
319                    if let BaseReqfileLine::Requirement(req) = base {
320                        if let Some(name) = &req.name {
321                            for dependent in tree.all_dependents(name) {
322                                let vec_idx = str_to_idx[&dependent];
323
324                                let dependent_line = &lines[vec_idx];
325     
326                                if !dependent_line.rf_line.is_explicit_optional() {
327                                    return Err(ReqparseError::Reqfile { 
328                                        line: line.line_num, 
329                                        message: format!(
330                                            "'{}' was declared as optional, however one of its \
331                                            dependents are required: '{} at line {}'.\n\
332                                            Try marking '{}' as optional instead.",
333                                            name, 
334                                            dependent,
335                                            dependent_line.line_num,
336                                            dependent
337                                        )
338                                    })
339                                }
340                            }
341                        }
342                    }
343                },
344                _ => {}
345            }
346        }
347
348        Ok(())
349    };
350
351    check_opt_prereq()?;
352
353    // payload is validated, now build payload
354    
355    // mark all transitive prereqs of an optional as optional, while
356
357    let mut optional: Vec<OptionalGroup> = vec![];
358
359    let mut marked_opt: HashSet<String> = HashSet::new();
360
361    for line in &lines {
362        match &line.rf_line {
363            ReqfileLine::Optional { base, weight } => {
364                // explicitly marked optional, so mark all previous
365                
366                if let BaseReqfileLine::Requirement(req) = base {
367                    let mut group = OptionalGroup {
368                        general: HashSet::new(),
369                        post: HashSet::new(),
370                        weight: *weight,
371                    };
372
373                    // build the subtree
374
375                    for req in tree
376                        .all_prereqs(&req.name_or_default())
377                        .iter().chain(&[req.name_or_default()]) {
378
379                        let vec_idx = str_to_idx[req];
380
381                        let req_line = &lines[vec_idx];
382
383                        match req_line.rf_line.base() {
384                            BaseReqfileLine::Requirement(req) => {
385                                group.get_set(req_line.timing)
386                                    .insert(req.clone());
387                            },
388                            _ => {}
389                        }
390
391                        marked_opt.insert(req.clone());
392                    }
393
394                    optional.push(group)
395                }
396            },
397            _ => {}
398        }
399    }
400
401    // mark all forced required reqs and their transitive prereqs
402
403    for line in &lines {
404        match &line.rf_line {
405            ReqfileLine::ForceRequired(base) => {
406                if let BaseReqfileLine::Requirement(req) = base {
407                    // go thru the optional vec and remove it and its prereqs
408                    // wherever possible
409
410                    for req in tree
411                        .all_prereqs(&req.name_or_default())
412                        .iter().chain(&[req.name_or_default()]) {
413
414                        let vec_idx = str_to_idx[req];
415
416                        let req_line = &lines[vec_idx];
417
418                        match req_line.rf_line.base() {
419                            BaseReqfileLine::Requirement(req) => {
420                                for group in &mut optional {
421                                    group.get_set(req_line.timing).remove(req);
422                                }
423                            },
424                            _ => {}
425                        }
426
427                        // remove mark
428                        marked_opt.remove(req);
429                    }                    
430                }
431            },
432            _ => {}
433        }
434    }
435
436    // dump the rest of the reqs into their respective free/post bucket
437    let mut general: Vec<Requirement> = vec![];
438    let mut post: Vec<Requirement> = vec![];
439
440    for line in &lines {
441        // dont have to match on anyting specific, because
442        // marked_opt tells us if it's final state was optional or not
443        let base = line.rf_line.base();
444        if let BaseReqfileLine::Requirement(req) = base {
445            if marked_opt.contains(&req.name_or_default()) { continue }
446
447            match line.timing {
448                Timing::Free => general.push(req.clone()),
449                Timing::Post => post.push(req.clone()),
450            }
451        }
452    }
453
454   Ok( Reqfile {
455        general,
456        post,
457        optional
458    })
459}
460
461// TODO! this should really be the only entry point to create a Reqfile, 
462// since it also validates if the payload will be semantically correct
463pub fn parse_reqfile_str(content: &String) -> Result<Reqfile> {
464    let mut lines: Vec<ParsedLine> = vec![];
465
466    let mut current = Timing::Free;
467
468    for (i, line) in content.lines().enumerate() {
469        let line = line.trim();
470        if line.is_empty() || line.starts_with('#') || line.starts_with("//") {
471            continue;
472        }
473
474        if line.to_uppercase().starts_with("FREE") {
475            current = Timing::Free;
476            continue;
477        }
478
479        if line.to_uppercase().starts_with("POST") {
480            current = Timing::Post;
481            continue;
482        }
483
484        let parsed = parse_reqfile_line(&line).map_err(|e| ReqparseError::Reqfile {
485            line: i + 1,
486            message: e.to_string(),
487        })?;
488
489        lines.push(ParsedLine {
490            rf_line: parsed, 
491            line_num: i, 
492            timing: current 
493        });
494    }
495
496    validate_and_transform(lines)
497}
498
499/// Parse '.req' files into a SolverPayload
500pub fn parse_reqfile(path: &Path) -> Result<Reqfile> {
501    use std::fs;
502
503    let content = fs::read_to_string(path)?;
504
505    parse_reqfile_str(&content)
506}
507
508pub fn gen_reqfile(payload: &Reqfile) -> String {
509    let mut output = String::new();
510
511    output.push_str("# Auto-generated reqfile\n\n");
512    output.push_str("Free:\n");
513
514    // remove spaces from names
515    //
516    // we also give anonymous reqs with prereqs an identifier
517    // (we don't assign names to potentially unnammed prereqs bc
518    // it is a requirement that prereqs are already named)
519
520    let clean_name = |name: &str| {
521        name.replace(" ", "_")
522            .replace("[", "")
523            .replace("]", "")
524            .replace("'", "")
525            .replace(":", "")
526            .replace("(", "")
527            .replace(")", "")
528    };
529
530    let mut i = 0;
531
532    let mut general = payload
533        .general
534        .iter()
535        .map(|req: &Requirement| {
536            i += 1;
537
538            let mut req = req.clone();
539
540            req.name = req.name.clone().or_else(|| {
541                if !req.prereqs.is_empty() {
542                    Some(format!("id_{}", i))
543                } else {
544                    None
545                }
546            });
547
548            req
549        })
550        .collect::<Vec<_>>();
551
552    let mut post = payload
553        .post
554        .iter()
555        .map(|req: &Requirement| {
556            i += 1;
557
558            let mut req = req.clone();
559
560            req.name = req.name.clone().or_else(|| {
561                if !req.prereqs.is_empty() {
562                    Some(format!("id_{}", i))
563                } else {
564                    None
565                }
566            });
567
568            req
569        })
570        .collect::<Vec<_>>();
571
572    general.map_names(clean_name);
573
574    post.map_names(clean_name);
575
576    for req in &general {
577        output.push_str(&format!("{}\n", req));
578    }
579
580    if !post.is_empty() {
581        output.push_str("\nPost:\n");
582
583        for req in &post {
584            output.push_str(&format!("{}\n", req));
585        }
586    }
587
588    output
589}