rasn_compiler/
lib.rs

1#[doc = include_str!("../README.md")]
2pub(crate) mod common;
3mod error;
4mod generator;
5mod input;
6pub mod intermediate;
7mod lexer;
8#[cfg(test)]
9mod tests;
10mod validator;
11
12#[cfg(feature = "cli")]
13pub mod cli;
14
15use std::{
16    cell::RefCell,
17    collections::BTreeMap,
18    fs::{self, read_to_string},
19    path::PathBuf,
20    rc::Rc,
21    vec,
22};
23
24use error::CompilerError;
25use generator::Backend;
26use intermediate::ToplevelDefinition;
27use lexer::{asn_spec, error::LexerError};
28use prelude::{GeneratorError, GeneratorErrorType};
29use validator::Validator;
30
31pub mod prelude {
32    //! Convenience module that collects all necessary imports for
33    //! using and customizing the compiler.
34    pub use super::{
35        error::CompilerError, CompileResult, Compiler, CompilerMissingParams, CompilerOutputSet,
36        CompilerReady, CompilerSourcesSet,
37    };
38    pub use crate::generator::{
39        error::*,
40        rasn::{Config as RasnConfig, Rasn as RasnBackend},
41        typescript::{Config as TsConfig, Typescript as TypescriptBackend},
42        Backend, GeneratedModule,
43    };
44
45    pub use crate::intermediate::{
46        ExtensibilityEnvironment, TaggingEnvironment, ToplevelDefinition,
47    };
48
49    pub use crate::lexer::error::{LexerError, LexerErrorType, ReportData};
50    pub use crate::validator::error::{LinkerError, LinkerErrorType};
51
52    pub mod ir {
53        pub use crate::intermediate::{
54            constraints::*,
55            encoding_rules::{per_visible::*, *},
56            error::*,
57            information_object::*,
58            parameterization::*,
59            types::*,
60            *,
61        };
62    }
63}
64
65#[cfg(target_family = "wasm")]
66use wasm_bindgen::prelude::*;
67
68#[cfg(target_family = "wasm")]
69#[wasm_bindgen(inspectable, getter_with_clone)]
70pub struct Generated {
71    pub rust: String,
72    pub warnings: String,
73}
74
75#[cfg(target_family = "wasm")]
76#[wasm_bindgen]
77pub fn compile_to_typescript(asn1: &str) -> Result<Generated, JsValue> {
78    Compiler::<crate::prelude::TypescriptBackend, _>::new()
79        .add_asn_literal(asn1)
80        .compile_to_string()
81        .map(|result| Generated {
82            rust: result.generated,
83            warnings: result
84                .warnings
85                .into_iter()
86                .fold(String::new(), |mut acc, w| {
87                    acc += &w.to_string();
88                    acc += "\n";
89                    acc
90                }),
91        })
92        .map_err(|e| JsValue::from(e.to_string()))
93}
94
95#[cfg(target_family = "wasm")]
96#[wasm_bindgen]
97pub fn compile_to_rust(
98    asn1: &str,
99    config: crate::prelude::RasnConfig,
100) -> Result<Generated, JsValue> {
101    Compiler::<crate::prelude::RasnBackend, _>::new_with_config(config)
102        .add_asn_literal(asn1)
103        .compile_to_string()
104        .map(|result| Generated {
105            rust: result.generated,
106            warnings: result
107                .warnings
108                .into_iter()
109                .fold(String::new(), |mut acc, w| {
110                    acc += &w.to_string();
111                    acc += "\n";
112                    acc
113                }),
114        })
115        .map_err(|e| JsValue::from(e.to_string()))
116}
117
118/// The rasn compiler
119pub struct Compiler<B: Backend, S: CompilerState> {
120    state: S,
121    backend: B,
122}
123
124/// Typestate representing compiler with missing parameters
125pub struct CompilerMissingParams;
126
127impl Default for CompilerMissingParams {
128    fn default() -> Self {
129        Self
130    }
131}
132
133/// Typestate representing compiler that is ready to compile
134pub struct CompilerReady {
135    sources: Vec<AsnSource>,
136    output_path: PathBuf,
137}
138
139/// Typestate representing compiler that has the output path set, but is missing ASN1 sources
140pub struct CompilerOutputSet {
141    output_path: PathBuf,
142}
143
144/// Typestate representing compiler that knows about ASN1 sources, but doesn't have an output path set
145pub struct CompilerSourcesSet {
146    sources: Vec<AsnSource>,
147}
148
149/// State of the rasn compiler
150pub trait CompilerState {}
151impl CompilerState for CompilerReady {}
152impl CompilerState for CompilerOutputSet {}
153impl CompilerState for CompilerSourcesSet {}
154impl CompilerState for CompilerMissingParams {}
155
156#[derive(Debug)]
157pub struct CompileResult {
158    pub generated: String,
159    pub warnings: Vec<CompilerError>,
160}
161
162impl CompileResult {
163    fn fmt<B: Backend>(mut self) -> Self {
164        self.generated = B::format_bindings(&self.generated).unwrap_or(self.generated);
165        self
166    }
167}
168
169#[derive(Debug, PartialEq)]
170enum AsnSource {
171    Path(PathBuf),
172    Literal(String),
173}
174
175impl<B: Backend> Default for Compiler<B, CompilerMissingParams> {
176    fn default() -> Self {
177        Self::new()
178    }
179}
180
181impl<B: Backend, S: CompilerState> Compiler<B, S> {
182    pub fn with_backend<B2: Backend>(self, backend: B2) -> Compiler<B2, S> {
183        Compiler {
184            state: self.state,
185            backend,
186        }
187    }
188}
189
190impl<B: Backend> Compiler<B, CompilerMissingParams> {
191    /// Provides a Builder for building rasn compiler commands
192    pub fn new() -> Compiler<B, CompilerMissingParams> {
193        Compiler {
194            state: CompilerMissingParams,
195            backend: B::default(),
196        }
197    }
198
199    /// Provides a Builder for building rasn compiler commands
200    pub fn new_with_config(config: B::Config) -> Compiler<B, CompilerMissingParams> {
201        Compiler {
202            state: CompilerMissingParams,
203            backend: B::from_config(config),
204        }
205    }
206}
207
208impl<B: Backend> Compiler<B, CompilerMissingParams> {
209    /// Add an ASN1 source to the compile command by path
210    /// * `path_to_source` - path to ASN1 file to include
211    pub fn add_asn_by_path(
212        self,
213        path_to_source: impl Into<PathBuf>,
214    ) -> Compiler<B, CompilerSourcesSet> {
215        Compiler {
216            state: CompilerSourcesSet {
217                sources: vec![AsnSource::Path(path_to_source.into())],
218            },
219            backend: self.backend,
220        }
221    }
222
223    /// Add several ASN1 sources by path to the compile command
224    /// * `path_to_source` - iterator of paths to the ASN1 files to be included
225    pub fn add_asn_sources_by_path(
226        self,
227        paths_to_sources: impl Iterator<Item = impl Into<PathBuf>>,
228    ) -> Compiler<B, CompilerSourcesSet> {
229        Compiler {
230            state: CompilerSourcesSet {
231                sources: paths_to_sources
232                    .map(|p| AsnSource::Path(p.into()))
233                    .collect(),
234            },
235            backend: self.backend,
236        }
237    }
238
239    /// Add a literal ASN1 source to the compile command
240    /// * `literal` - literal ASN1 statement to include
241    /// ```rust
242    /// # use rasn_compiler::prelude::*;
243    /// Compiler::<RasnBackend, _>::new().add_asn_literal(format!(
244    ///     "TestModule DEFINITIONS AUTOMATIC TAGS::= BEGIN {} END",
245    ///     "My-test-integer ::= INTEGER (1..128)"
246    /// )).compile_to_string();
247    /// ```
248    pub fn add_asn_literal(self, literal: impl Into<String>) -> Compiler<B, CompilerSourcesSet> {
249        Compiler {
250            state: CompilerSourcesSet {
251                sources: vec![AsnSource::Literal(literal.into())],
252            },
253            backend: self.backend,
254        }
255    }
256
257    /// Set the output path for the generated rust representation.
258    /// * `output_path` - path to an output file or directory, if path indicates
259    ///                   a directory, the output file is named `rasn_generated.rs`
260    pub fn set_output_path(
261        self,
262        output_path: impl Into<PathBuf>,
263    ) -> Compiler<B, CompilerOutputSet> {
264        let mut path: PathBuf = output_path.into();
265        if path.is_dir() {
266            path.set_file_name("rasn_generated.rs");
267        }
268        Compiler {
269            state: CompilerOutputSet { output_path: path },
270            backend: self.backend,
271        }
272    }
273}
274
275impl<B: Backend> Compiler<B, CompilerOutputSet> {
276    /// Add an ASN1 source to the compile command by path
277    /// * `path_to_source` - path to ASN1 file to include
278    pub fn add_asn_by_path(self, path_to_source: impl Into<PathBuf>) -> Compiler<B, CompilerReady> {
279        Compiler {
280            state: CompilerReady {
281                sources: vec![AsnSource::Path(path_to_source.into())],
282                output_path: self.state.output_path,
283            },
284            backend: self.backend,
285        }
286    }
287
288    /// Add several ASN1 sources by path to the compile command
289    /// * `path_to_source` - iterator of paths to the ASN1 files to be included
290    pub fn add_asn_sources_by_path(
291        self,
292        paths_to_sources: impl Iterator<Item = impl Into<PathBuf>>,
293    ) -> Compiler<B, CompilerReady> {
294        Compiler {
295            state: CompilerReady {
296                sources: paths_to_sources
297                    .map(|p| AsnSource::Path(p.into()))
298                    .collect(),
299                output_path: self.state.output_path,
300            },
301            backend: self.backend,
302        }
303    }
304
305    /// Add a literal ASN1 source to the compile command
306    /// * `literal` - literal ASN1 statement to include
307    /// ```rust
308    /// # use rasn_compiler::prelude::*;
309    /// Compiler::<RasnBackend, _>::new().add_asn_literal(format!(
310    ///     "TestModule DEFINITIONS AUTOMATIC TAGS::= BEGIN {} END",
311    ///     "My-test-integer ::= INTEGER (1..128)"
312    /// )).compile_to_string();
313    /// ```
314    pub fn add_asn_literal(self, literal: impl Into<String>) -> Compiler<B, CompilerReady> {
315        Compiler {
316            state: CompilerReady {
317                sources: vec![AsnSource::Literal(literal.into())],
318                output_path: self.state.output_path,
319            },
320            backend: self.backend,
321        }
322    }
323}
324
325impl<B: Backend> Compiler<B, CompilerSourcesSet> {
326    /// Add an ASN1 source to the compile command by path
327    /// * `path_to_source` - path to ASN1 file to include
328    pub fn add_asn_by_path(
329        self,
330        path_to_source: impl Into<PathBuf>,
331    ) -> Compiler<B, CompilerSourcesSet> {
332        let mut sources: Vec<AsnSource> = self.state.sources;
333        sources.push(AsnSource::Path(path_to_source.into()));
334        Compiler {
335            state: CompilerSourcesSet { sources },
336            backend: self.backend,
337        }
338    }
339
340    /// Add several ASN1 sources by path to the compile command
341    /// * `path_to_source` - iterator of paths to the ASN1 files to be included
342    pub fn add_asn_sources_by_path(
343        self,
344        paths_to_sources: impl Iterator<Item = impl Into<PathBuf>>,
345    ) -> Compiler<B, CompilerSourcesSet> {
346        let mut sources: Vec<AsnSource> = self.state.sources;
347        sources.extend(paths_to_sources.map(|p| AsnSource::Path(p.into())));
348        Compiler {
349            state: CompilerSourcesSet { sources },
350            backend: self.backend,
351        }
352    }
353
354    /// Add a literal ASN1 source to the compile command
355    /// * `literal` - literal ASN1 statement to include
356    /// ```rust
357    /// # use rasn_compiler::prelude::*;
358    /// Compiler::<RasnBackend, _>::new().add_asn_literal(format!(
359    ///     "TestModule DEFINITIONS AUTOMATIC TAGS::= BEGIN {} END",
360    ///     "My-test-integer ::= INTEGER (1..128)"
361    /// )).compile_to_string();
362    /// ```
363    pub fn add_asn_literal(self, literal: impl Into<String>) -> Compiler<B, CompilerSourcesSet> {
364        let mut sources: Vec<AsnSource> = self.state.sources;
365        sources.push(AsnSource::Literal(literal.into()));
366        Compiler {
367            state: CompilerSourcesSet { sources },
368            backend: self.backend,
369        }
370    }
371
372    /// Set the output path for the generated rust representation.
373    /// * `output_path` - path to an output file or directory, if path points to
374    ///                   a directory, the compiler will generate a file for every ASN.1 module.
375    ///                   If the path points to a file, all modules will be written to that file.
376    pub fn set_output_path(self, output_path: impl Into<PathBuf>) -> Compiler<B, CompilerReady> {
377        Compiler {
378            state: CompilerReady {
379                sources: self.state.sources,
380                output_path: output_path.into(),
381            },
382            backend: self.backend,
383        }
384    }
385
386    /// Runs the rasn compiler command and returns stringified Rust.
387    /// Returns a Result wrapping a compilation result:
388    /// * _Ok_  - tuple containing the stringified bindings for the ASN1 spec as well as a vector of warnings raised during the compilation
389    /// * _Err_ - Unrecoverable error, no rust representations were generated
390    pub fn compile_to_string(mut self) -> Result<CompileResult, CompilerError> {
391        self.internal_compile().map(CompileResult::fmt::<B>)
392    }
393
394    fn internal_compile(&mut self) -> Result<CompileResult, CompilerError> {
395        let mut generated_modules = vec![];
396        let mut warnings = Vec::<CompilerError>::new();
397        let mut modules: Vec<ToplevelDefinition> = vec![];
398        for src in &self.state.sources {
399            let stringified_src = match src {
400                AsnSource::Path(p) => read_to_string(p).map_err(LexerError::from)?,
401                AsnSource::Literal(l) => l.clone(),
402            };
403            modules.append(
404                &mut asn_spec(&stringified_src)?
405                    .into_iter()
406                    .flat_map(|(header, tlds)| {
407                        let header_ref = Rc::new(RefCell::new(header));
408                        tlds.into_iter().enumerate().map(move |(index, mut tld)| {
409                            tld.apply_tagging_environment(&header_ref.borrow().tagging_environment);
410                            tld.set_index(header_ref.clone(), index);
411                            tld
412                        })
413                    })
414                    .collect(),
415            );
416        }
417        let (valid_items, mut validator_errors) = Validator::new(modules).validate()?;
418        let modules = valid_items.into_iter().fold(
419            BTreeMap::<String, Vec<ToplevelDefinition>>::new(),
420            |mut modules, tld| {
421                let key = tld
422                    .get_index()
423                    .map_or(<_>::default(), |(module, _)| module.borrow().name.clone());
424                match modules.entry(key) {
425                    std::collections::btree_map::Entry::Vacant(v) => {
426                        v.insert(vec![tld]);
427                    }
428                    std::collections::btree_map::Entry::Occupied(ref mut e) => {
429                        e.get_mut().push(tld)
430                    }
431                }
432                modules
433            },
434        );
435        for (_, module) in modules {
436            let mut generated_module = self.backend.generate_module(module)?;
437            if let Some(m) = generated_module.generated {
438                generated_modules.push(m);
439            }
440            warnings.append(&mut generated_module.warnings);
441        }
442        warnings.append(&mut validator_errors);
443
444        Ok(CompileResult {
445            generated: generated_modules.join("\n"),
446            warnings,
447        })
448    }
449}
450
451impl<B: Backend> Compiler<B, CompilerReady> {
452    /// Add an ASN1 source to the compile command by path
453    /// * `path_to_source` - path to ASN1 file to include
454    pub fn add_asn_by_path(self, path_to_source: impl Into<PathBuf>) -> Compiler<B, CompilerReady> {
455        let mut sources: Vec<AsnSource> = self.state.sources;
456        sources.push(AsnSource::Path(path_to_source.into()));
457        Compiler {
458            state: CompilerReady {
459                output_path: self.state.output_path,
460                sources,
461            },
462            backend: self.backend,
463        }
464    }
465
466    /// Add several ASN1 sources by path to the compile command
467    /// * `path_to_source` - iterator of paths to the ASN1 files to be included
468    pub fn add_asn_sources_by_path(
469        self,
470        paths_to_sources: impl Iterator<Item = impl Into<PathBuf>>,
471    ) -> Compiler<B, CompilerReady> {
472        let mut sources: Vec<AsnSource> = self.state.sources;
473        sources.extend(paths_to_sources.map(|p| AsnSource::Path(p.into())));
474        Compiler {
475            state: CompilerReady {
476                sources,
477                output_path: self.state.output_path,
478            },
479            backend: self.backend,
480        }
481    }
482
483    /// Add a literal ASN1 source to the compile command
484    /// * `literal` - literal ASN1 statement to include
485    /// ```rust
486    /// # use rasn_compiler::prelude::*;
487    /// Compiler::<RasnBackend, _>::new().add_asn_literal(format!(
488    ///     "TestModule DEFINITIONS AUTOMATIC TAGS::= BEGIN {} END",
489    ///     "My-test-integer ::= INTEGER (1..128)"
490    /// )).compile_to_string();
491    /// ```
492    pub fn add_asn_literal(self, literal: impl Into<String>) -> Compiler<B, CompilerReady> {
493        let mut sources: Vec<AsnSource> = self.state.sources;
494        sources.push(AsnSource::Literal(literal.into()));
495        Compiler {
496            state: CompilerReady {
497                output_path: self.state.output_path,
498                sources,
499            },
500            backend: self.backend,
501        }
502    }
503
504    /// Runs the rasn compiler command and returns stringified Rust.
505    /// Returns a Result wrapping a compilation result:
506    /// * _Ok_  - tuple containing the stringified bindings for the ASN1 spec as well as a vector of warnings raised during the compilation
507    /// * _Err_ - Unrecoverable error, no rust representations were generated
508    pub fn compile_to_string(self) -> Result<CompileResult, CompilerError> {
509        Compiler {
510            state: CompilerSourcesSet {
511                sources: self.state.sources,
512            },
513            backend: self.backend,
514        }
515        .compile_to_string()
516    }
517
518    /// Runs the rasn compiler command.
519    /// Returns a Result wrapping a compilation result:
520    /// * _Ok_  - Vector of warnings raised during the compilation
521    /// * _Err_ - Unrecoverable error, no rust representations were generated
522    pub fn compile(self) -> Result<Vec<CompilerError>, CompilerError> {
523        let result = Compiler {
524            state: CompilerSourcesSet {
525                sources: self.state.sources,
526            },
527            backend: self.backend,
528        }
529        .internal_compile()?
530        .fmt::<B>();
531        fs::write(
532            self.state
533                .output_path
534                .is_dir()
535                .then(|| {
536                    self.state
537                        .output_path
538                        .join(format!("generated{}", B::FILE_EXTENSION))
539                })
540                .unwrap_or(self.state.output_path),
541            result.generated,
542        )
543        .map_err(|e| GeneratorError {
544            top_level_declaration: None,
545            details: e.to_string(),
546            kind: GeneratorErrorType::IO,
547        })?;
548
549        Ok(result.warnings)
550    }
551}