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}