Skip to main content

miden_parsing/
parser.rs

1use std::error::Error;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4
5use miden_diagnostics::*;
6
7use super::{FileMapSource, Source};
8
9/// [Parser] is used to provide a simple interface for implementations
10/// of the [Parse] trait.
11///
12/// It provides a [miden_diagnostics::CodeMap], and an instance of the
13/// configuration type used by the underlying [Parse] implementation.
14pub struct Parser<C> {
15    /// The configuration provided to the parser
16    pub config: C,
17    /// The underlying [miden_diagnostics::CodeMap] used by the parser
18    pub codemap: Arc<CodeMap>,
19}
20impl<C: Default> Default for Parser<C> {
21    fn default() -> Self {
22        Self::new(Default::default(), Arc::new(CodeMap::new()))
23    }
24}
25impl<C> Parser<C> {
26    /// Create a new [Parser] from the given configuration and [CodeMap]
27    pub fn new(config: C, codemap: Arc<CodeMap>) -> Self {
28        Self { config, codemap }
29    }
30
31    /// Parse a [T] from the given [SourceFile]
32    ///
33    /// Requires a [DiagnosticsHandler] to be provided for use by the [Parse] implementation
34    pub fn parse<T, E>(
35        &self,
36        diagnostics: &DiagnosticsHandler,
37        source: Arc<SourceFile>,
38    ) -> Result<T, E>
39    where
40        E: Error + ToDiagnostic,
41        T: Parse<Config = C, Error = E>,
42    {
43        <T as Parse<T>>::parse(self, diagnostics, FileMapSource::new(source))
44    }
45
46    /// Parse a [T] from the given string.
47    ///
48    /// Requires a [DiagnosticsHandler] to be provided for use by the [Parse] implementation
49    pub fn parse_string<T, S, E>(&self, diagnostics: &DiagnosticsHandler, source: S) -> Result<T, E>
50    where
51        E: Error + ToDiagnostic,
52        T: Parse<Config = C, Error = E>,
53        S: AsRef<str>,
54    {
55        let id = self.codemap.add("nofile", source.as_ref().to_string());
56        let file = self.codemap.get(id).unwrap();
57        self.parse(diagnostics, file)
58    }
59
60    /// Parse a [T] from the given file path.
61    ///
62    /// Requires a [DiagnosticsHandler] to be provided for use by the [Parse] implementation
63    pub fn parse_file<T, S, E>(&self, diagnostics: &DiagnosticsHandler, source: S) -> Result<T, E>
64    where
65        E: Error + ToDiagnostic,
66        T: Parse<Config = C, Error = E>,
67        S: AsRef<Path>,
68    {
69        let path = source.as_ref();
70        match std::fs::read_to_string(path) {
71            Err(err) => Err(<T as Parse<T>>::root_file_error(err, path.to_owned())),
72            Ok(content) => {
73                let id = self.codemap.add(path, content);
74                let file = self.codemap.get(id).unwrap();
75                self.parse(diagnostics, file)
76            }
77        }
78    }
79}
80
81/// The [Parse] trait abstracts over the common machinery used to parse some type [T].
82pub trait Parse<T = Self> {
83    /// The concrete type of the parser implementation
84    ///
85    /// For example, if using LALRPOP, this would correspond to the specific
86    /// generated parser type, e.g. `grammar::FooParser`.
87    type Parser;
88    /// The concrete type of errors which are produced by the parser
89    ///
90    /// To better interact with our diagnostics infrastructure, it is
91    /// required that this type implement [ToDiagnostic].
92    type Error: Error + ToDiagnostic;
93    /// The concrete type representing the parser configuration.
94    ///
95    /// For many use cases, no configuration is needed, in which case you should use `()`.
96    type Config;
97    /// The concrete type of the lexical token consumed by the parser
98    ///
99    /// For example, if using LALRPOP, this would correspond go the token type used
100    /// in the LALRPOP grammar.
101    ///
102    /// This crate is built under the assumption that you are using a custom lexer
103    /// for greater control in the parser, and to associate a [miden_diagnostic::SourceSpan]
104    /// to each token produced by the lexer. If you are building a parser without
105    /// a lexer, you are better off using the underlying primitives directly as you
106    /// see fit.
107    type Token;
108
109    /// Constructs an instance of [Self::Error] when a [std:::io::Error] is raised
110    ///
111    /// This allows us to handle the machinery of reading files from disk without
112    /// having to know anything about the specific error type produced by a parser.
113    fn root_file_error(err: std::io::Error, path: PathBuf) -> Self::Error;
114
115    /// Parses a [T] from the given [Source].
116    ///
117    /// Internally, this is expected to construct a token stream from `source`,
118    /// typically by constructing a lexer which implements `Iterator` for the
119    /// expected token type, and then invoke `parse_tokens` to handle the actual parsing.
120    fn parse<S>(
121        parser: &Parser<Self::Config>,
122        diagnostics: &DiagnosticsHandler,
123        source: S,
124    ) -> Result<T, Self::Error>
125    where
126        S: Source;
127
128    /// Parses a [T] from the given token stream.
129    ///
130    /// If using LALRPOP, this is where you would invoke the generated parser,
131    /// passing it the token iterator.
132    fn parse_tokens<S>(
133        diagnostics: &DiagnosticsHandler,
134        codemap: Arc<CodeMap>,
135        tokens: S,
136    ) -> Result<T, Self::Error>
137    where
138        S: IntoIterator<Item = Self::Token>;
139}