Skip to main content

asn1_compiler/parser/asn/
module.rs

1//! ASN.1 Module Parsing functionality
2use std::collections::HashMap;
3
4use crate::tokenizer::Token;
5use anyhow::Result;
6
7use crate::parser::utils::{expect_keyword, expect_one_of_keywords, expect_token};
8
9use super::{
10    defs::parse_definition,
11    oid::parse_object_identifier,
12    structs::{
13        defs::Asn1Definition,
14        module::{Asn1Module, Asn1ModuleName, Asn1ModuleTag},
15        oid::ObjectIdentifier,
16    },
17};
18
19impl Asn1Module {
20    pub(crate) fn resolve_object_classes(
21        &mut self,
22        object_classes: &HashMap<String, Asn1Definition>,
23    ) -> Result<()> {
24        for def in self.definitions.values_mut() {
25            if !def.is_object_or_object_set() {
26                continue;
27            }
28            if let Some(class) = def.get_object_class() {
29                let classdef = object_classes.get(&class);
30                if classdef.is_none() {
31                    return Err(parse_error!(
32                        "Error Resolving Class '{}' for Definition '{}'",
33                        class,
34                        def.id()
35                    )
36                    .into());
37                }
38                let class = classdef.unwrap();
39                def.resolve_object_class(class)?;
40            }
41        }
42        Ok(())
43    }
44}
45
46pub(in crate::parser) fn parse_module(tokens: &[Token]) -> Result<(Asn1Module, usize)>
47where
48{
49    let mut consumed = 0;
50
51    // Module Name and Object Identifier
52    let (name, name_consumed) = parse_module_name(&tokens[consumed..])?;
53    consumed += name_consumed;
54    log::trace!(
55        "Parsed module name '{}'. Consumed {} tokens",
56        name.name,
57        consumed
58    );
59
60    // DEFINITIONS Keywords
61    if expect_keyword(&tokens[consumed..], "DEFINITIONS")? {
62        consumed += 1;
63    } else {
64        return Err(unexpected_token!("DEFINITIONS", tokens[consumed]).into());
65    }
66
67    let (tags, tags_consumed) = maybe_parse_header_tags(&tokens[consumed..])?;
68    consumed += tags_consumed;
69    log::trace!(
70        "Parsed Header Tags '{:?}'.  Consumed {} tokens",
71        tags,
72        consumed
73    );
74
75    // FIXME: Handle EXTENSIBILITY
76
77    if expect_token(&tokens[consumed..], Token::is_assignment)? {
78        consumed += 1;
79    } else {
80        return Err(unexpected_token!("::=", tokens[consumed]).into());
81    }
82    if expect_keyword(&tokens[consumed..], "BEGIN")? {
83        consumed += 1;
84    } else {
85        return Err(unexpected_token!("BEGIN", tokens[consumed]).into());
86    }
87
88    // Parse but ignore exports if any (by default everything is exported).
89    let (_, exports_consumed) = parse_module_maybe_exports(&tokens[consumed..])?;
90    consumed += exports_consumed;
91    log::trace!("Parsed EXPORTS. Consumed {} tokens.", consumed);
92
93    let (imports, imports_consumed) = parse_module_imports(&tokens[consumed..])?;
94    consumed += imports_consumed;
95    log::trace!(
96        "Parsed IMPORTS. Consumed {} tokens. Parsing Definitions Now",
97        consumed
98    );
99
100    let mut definitions = HashMap::new();
101    while !expect_keyword(&tokens[consumed..], "END")? {
102        let (def, definition_consumed) = parse_definition(&tokens[consumed..])?;
103        consumed += definition_consumed;
104        log::trace!(
105            "Parsed '{}' Definition. Consumed {} tokens so far",
106            def.id(),
107            consumed
108        );
109        definitions.insert(def.id(), def);
110    }
111
112    // Comes out of the loop when END is found.
113    // If 'END' was never found we'd Error out at above 'while'
114    consumed += 1;
115
116    let module = Asn1Module::default()
117        .name(name)
118        .tags(tags)
119        .imports(imports)
120        .definitions(definitions);
121    Ok((module, consumed))
122}
123
124fn parse_module_maybe_exports(tokens: &[Token]) -> Result<((), usize)> {
125    let mut consumed = 0;
126    if expect_keyword(&tokens[consumed..], "EXPORTS")? {
127        consumed += 1;
128        loop {
129            if expect_token(&tokens[consumed..], Token::is_semicolon)? {
130                consumed += 1;
131                break;
132            }
133
134            if expect_token(&tokens[consumed..], Token::is_identifier)? {
135                consumed += 1;
136            }
137
138            if expect_token(&tokens[consumed..], Token::is_comma)? {
139                consumed += 1;
140            }
141        }
142    }
143    Ok(((), consumed))
144}
145
146fn parse_module_imports(tokens: &[Token]) -> Result<(HashMap<String, Asn1ModuleName>, usize)> {
147    let mut consumed = 0;
148
149    let mut imports = HashMap::new();
150    if expect_keyword(&tokens[consumed..], "IMPORTS")? {
151        consumed += 1;
152
153        loop {
154            let mut imported_defs = vec![];
155            while !expect_keyword(&tokens[consumed..], "FROM")? {
156                if expect_token(&tokens[consumed..], Token::is_identifier)? {
157                    let definition = tokens[consumed].text.clone();
158                    imported_defs.push(definition);
159                }
160                consumed += 1;
161                if expect_token(&tokens[consumed..], Token::is_comma)? {
162                    consumed += 1;
163                }
164            }
165            consumed += 1;
166            let (module_name, module_name_consumed) = parse_module_name(&tokens[consumed..])?;
167            consumed += module_name_consumed;
168
169            for d in imported_defs {
170                if imports.contains_key(&d) {
171                    return Err(parse_error!("Definition '{}' is imported twice", d).into());
172                }
173                let _ = imports.insert(d, module_name.clone());
174            }
175
176            if expect_token(&tokens[consumed..], Token::is_semicolon)? {
177                consumed += 1;
178                break;
179            }
180        }
181    }
182
183    Ok((imports, consumed))
184}
185
186fn maybe_parse_header_tags(tokens: &[Token]) -> Result<(Asn1ModuleTag, usize)> {
187    let mut consumed = 0;
188
189    let tag =
190        if expect_one_of_keywords(&tokens[consumed..], &["EXPLICIT", "IMPLICIT", "AUTOMATIC"])? {
191            let tag: Asn1ModuleTag = match tokens[consumed].text.as_str() {
192                "EXPLICIT" => Asn1ModuleTag::Explicit,
193                "IMPLICIT" => Asn1ModuleTag::Implicit,
194                "AUTOMATIC" => Asn1ModuleTag::Automatic,
195                _ => {
196                    // Will never reach
197                    return Err(parse_error!("Should Never Reach").into());
198                }
199            };
200            consumed += 1;
201            if expect_keyword(&tokens[consumed..], "TAGS")? {
202                consumed += 1
203            } else {
204                return Err(unexpected_token!("TAGS", tokens[consumed]).into());
205            }
206            tag
207        } else {
208            Asn1ModuleTag::Explicit
209        };
210    Ok((tag, consumed))
211}
212
213fn parse_module_name(tokens: &[Token]) -> Result<(Asn1ModuleName, usize)> {
214    let mut consumed = 0;
215    // First Name
216
217    let name = if expect_token(&tokens[consumed..], Token::is_module_reference)? {
218        tokens[consumed].text.clone()
219    } else {
220        return Err(parse_error!(
221            "Module Name '{}' is not a valid Module Reference",
222            tokens[consumed].text
223        )
224        .into());
225    };
226    consumed += 1;
227
228    // Now OID
229    // Optional Object Identifier
230    let (oid, oid_consumed) = maybe_parse_object_identifer(&tokens[consumed..])?;
231    consumed += oid_consumed;
232
233    Ok((Asn1ModuleName::new(name, oid), consumed))
234}
235
236fn maybe_parse_object_identifer(tokens: &[Token]) -> Result<(Option<ObjectIdentifier>, usize)> {
237    match expect_token(tokens, Token::is_curly_begin) {
238        Ok(success) => {
239            if success {
240                match parse_object_identifier(tokens) {
241                    Ok((oid, consumed)) => Ok((Some(oid), consumed)),
242                    Err(e) => Err(e),
243                }
244            } else {
245                Ok((None, 0))
246            }
247        }
248        // This can only `Err` when we've reached End of tokens, which is a `None` object
249        // Identifier.
250        Err(_) => Ok((None, 0)),
251    }
252}
253
254#[cfg(test)]
255mod tests {
256
257    use super::*;
258    use crate::tokenizer::tokenize;
259
260    #[test]
261    fn empty_module_success() {
262        let input = "ModuleFoo DEFINITIONS ::= BEGIN END";
263        let reader = std::io::BufReader::new(std::io::Cursor::new(input));
264        let tokens = tokenize(reader);
265        assert!(tokens.is_ok());
266
267        let mut tokens = tokens.unwrap();
268        let module = parse_module(&mut tokens);
269        assert!(module.is_ok(), "{}: {:#?}", input, module.err().unwrap());
270
271        let (module, consumed) = module.unwrap();
272        assert_eq!(consumed, 5);
273
274        assert!(module.definitions.is_empty());
275        assert!(module.imports.is_empty());
276        assert_eq!(module._tags, Asn1ModuleTag::Explicit);
277    }
278    // TODO: Test Cases for imports (count), Tags (type), Definitions (count))
279    // TODO: Test Cases for missing BEGIN, END, DEFINITIONS, ::=
280
281    #[test]
282    fn parse_module_name_tests() {
283        struct ParseModuleNameTestCase<'tc> {
284            input: &'tc str,
285            success: bool,
286            consumed: usize,
287            oid_present: bool,
288        }
289
290        let test_cases = vec![
291            ParseModuleNameTestCase {
292                input: "ModuleFoo",
293                success: true,
294                consumed: 1,
295                oid_present: false,
296            },
297            ParseModuleNameTestCase {
298                input: "moduleFoo",
299                success: false,
300                consumed: 0,
301                oid_present: false,
302            },
303            ParseModuleNameTestCase {
304                input: "ModuleFoo { iso }",
305                success: true,
306                consumed: 4,
307                oid_present: true,
308            },
309            ParseModuleNameTestCase {
310                input: "ModuleFoo { iso ",
311                success: false,
312                consumed: 0,
313                oid_present: false,
314            },
315            ParseModuleNameTestCase {
316                input: "ModuleFoo iso ", // This is a success, 'iso' is ignored.
317                success: true,
318                consumed: 1,
319                oid_present: false,
320            },
321            ParseModuleNameTestCase {
322                input: "NGAP-CommonDataTypes { itu-t (0) identified-organization (4) etsi (0) mobileDomain (0) ngran-Access (22) modules (3) ngap (1) version1 (1) ngap-CommonDataTypes (3) }", success: true, consumed: 39, oid_present: true,
323            }
324        ];
325
326        for tc in test_cases {
327            let reader = std::io::BufReader::new(std::io::Cursor::new(tc.input));
328            let tokens = tokenize(reader);
329            assert!(tokens.is_ok());
330
331            let mut tokens = tokens.unwrap();
332            let module = parse_module_name(&mut tokens);
333            assert_eq!(module.is_ok(), tc.success, "{}", tc.input);
334
335            if tc.success {
336                let (module, consumed) = module.unwrap();
337                assert_eq!(consumed, tc.consumed);
338                assert_eq!(module.oid.is_some(), tc.oid_present);
339            }
340        }
341    }
342}