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