aboutsummaryrefslogtreecommitdiff
path: root/src/parse.rs
blob: b16fc24f4d20e8f25b844b918ca000f14c7a96f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
enum Context<'a> {
    HalfIncludeGuard(&'a str),
    IncludeGuard(&'a str),
    If(&'a str),
    Ifndef(&'a str),
}

impl<'a> Context<'a> {
    fn modality(&self) -> Option<Modality<'a>> {
        match self {
            Context::HalfIncludeGuard(_) => None,
            Context::IncludeGuard(_) => None,
            Context::If(x) => Some(Modality::If(x)),
            Context::Ifndef(x) => Some(Modality::Ifndef(x)),
        }
    }
}

#[derive(Debug)]
enum Modality<'a> {
    If(&'a str),
    Ifndef(&'a str),

    And(Vec<Self>),
}

impl<'a> Modality<'a> {
    fn and(a: Option<Self>, b: Option<Self>) -> Option<Self> {
        match (a, b) {
            (Some(a), Some(b)) => Some(Self::And(vec![a, b])),
            (Some(a), None) => Some(a),
            (None, Some(b)) => Some(b),
            (None, None) => None,
        }
    }
}

trait VecContextExt<'a> {
    fn modality(&self) -> Option<Modality<'a>>;
}

impl<'a> VecContextExt<'a> for Vec<Context<'a>> {
    fn modality(&self) -> Option<Modality<'a>> {
        self.iter()
            .rev()
            .map(Context::modality)
            .fold(None, Modality::and)
    }
}

#[derive(Debug)]
enum FileItem<'a> {
    LineComment(&'a str),
    IncludeLocal(&'a str),
    IncludeGlobal(&'a str),
}

#[derive(Debug)]
pub struct FullFileItem<'a> {
    data: FileItem<'a>,
    modality: Option<Modality<'a>>,
}

pub fn parse(file: &str) -> Vec<FullFileItem> {
    let mut context: Vec<Context> = vec![];
    let mut result = vec![];
    for line in file.lines() {
        if line.starts_with("//") {
            result.push(FullFileItem {
                data: FileItem::LineComment(line.strip_prefix("//").unwrap()),
                modality: context.modality(),
            });
        } else if line.trim().is_empty() {
            // don't do anything
        } else if line.starts_with("#ifndef ") && context.is_empty() {
            // this is probably the include guard
            context.push(Context::HalfIncludeGuard(
                line.strip_prefix("#ifndef ").unwrap(),
            ));
        } else if line.starts_with("#define ") {
            match context.last() {
                Some(Context::HalfIncludeGuard(expected_guard)) => {
                    // this is the other half of the include guard
                    let actual_guard = line.strip_prefix("#define ").unwrap();
                    assert_eq!(
                        actual_guard, *expected_guard,
                        "weird other half of include guard"
                    );
                    context.pop();
                    context.push(Context::IncludeGuard(actual_guard));
                }
                _ => {
                    todo!("handle #define in non-include guard contexts: {:?}", line);
                }
            }
        } else if line.trim().starts_with("#include ") {
            let good_part = line.trim().strip_prefix("#include ").unwrap();
            let item = if line.ends_with("\"") {
                FileItem::IncludeLocal(good_part.trim_matches('"'))
            } else if line.ends_with(">") {
                FileItem::IncludeGlobal(good_part.trim_start_matches('<').trim_end_matches('>'))
            } else {
                todo!("handle weird includes {:?}", line);
            };
            result.push(FullFileItem {
                data: item,
                modality: context.modality(),
            });
        } else if line.starts_with("#if ") {
            let condition = line.strip_prefix("#if ").unwrap();
            context.push(Context::If(condition));
        } else if line.starts_with("#ifndef ") {
            let condition = line.strip_prefix("#ifndef ").unwrap();
            context.push(Context::Ifndef(condition));
        } else if line.starts_with("#endif") {
            context.pop();
        } else {
            todo!("handle line {:?}", line);
        }
    }
    result
}