1use nash_region::Located;
12use nash_source::{Exposing, Import};
13
14use crate::Parser;
15use crate::error;
16
17impl<'a> Parser<'a> {
18 pub fn import(&mut self) -> Result<&'a Import<'a>, error::Module<'a>> {
25 self.keyword_import(error::Module::ImportStart)?;
27
28 self.chomp_and_check_indent(error::Module::Space, error::Module::ImportIndentName)?;
29
30 let start = self.get_position();
32 let name = self.module_name(error::Module::ImportName)?;
33 let module_name = self.add_end(start, name);
34
35 self.chomp(error::Module::Space)?;
37
38 self.import_help(module_name)
40 }
41
42 fn import_help(
46 &mut self,
47 module_name: &'a Located<&'a str>,
48 ) -> Result<&'a Import<'a>, error::Module<'a>> {
49 if self.col == 1 {
51 let default_exposing = self.alloc(Exposing::Explicit(&[]));
52 return Ok(self.alloc(Import {
53 import: module_name,
54 alias: None,
55 exposing: default_exposing,
56 }));
57 }
58
59 self.one_of(
61 error::Module::ImportAs,
62 vec![
63 Box::new(|p: &mut Parser<'a>| p.import_as(module_name)),
65 Box::new(|p: &mut Parser<'a>| p.import_exposing(module_name, None)),
67 ],
68 )
69 }
70
71 fn import_as(
75 &mut self,
76 module_name: &'a Located<&'a str>,
77 ) -> Result<&'a Import<'a>, error::Module<'a>> {
78 self.keyword_as(error::Module::ImportAs)?;
79
80 self.chomp_and_check_indent(error::Module::Space, error::Module::ImportIndentAlias)?;
81
82 let alias = self.upper_name(error::Module::ImportAlias)?;
83
84 self.chomp(error::Module::Space)?;
86
87 if self.col == 1 {
89 let default_exposing = self.alloc(Exposing::Explicit(&[]));
91 Ok(self.alloc(Import {
92 import: module_name,
93 alias: Some(alias),
94 exposing: default_exposing,
95 }))
96 } else {
97 self.import_exposing(module_name, Some(alias))
99 }
100 }
101
102 fn import_exposing(
106 &mut self,
107 module_name: &'a Located<&'a str>,
108 alias: Option<&'a str>,
109 ) -> Result<&'a Import<'a>, error::Module<'a>> {
110 self.keyword_exposing(error::Module::ImportExposing)?;
111
112 self.chomp_and_check_indent(
113 error::Module::Space,
114 error::Module::ImportIndentExposingList,
115 )?;
116
117 let exposing = self.specialize(
119 |bump, err, row, col| error::Module::ImportExposingList(bump.alloc(err), row, col),
120 |p| p.exposing(),
121 )?;
122
123 self.chomp(error::Module::Space)?;
125 self.check_fresh_line(error::Module::ImportEnd)?;
126
127 Ok(self.alloc(Import {
128 import: module_name,
129 alias,
130 exposing: self.alloc(exposing),
131 }))
132 }
133
134 pub(crate) fn module_name<E>(
141 &mut self,
142 to_error: impl FnOnce(u16, u16) -> E,
143 ) -> Result<&'a str, E> {
144 let (row, col) = self.position();
145 let start_pos = self.pos;
146
147 match self.peek() {
149 Some(b) if b.is_ascii_uppercase() => {
150 self.advance();
151 self.chomp_inner_chars();
152 }
153 _ => return Err(to_error(row, col)),
154 }
155
156 loop {
158 if self.peek() == Some(b'.') {
159 if let Some(next) = self.peek_at(1)
161 && next.is_ascii_uppercase()
162 {
163 self.advance(); self.advance(); self.chomp_inner_chars();
166 continue;
167 }
168 }
169 break;
171 }
172
173 Ok(self.slice_from(start_pos))
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use bumpalo::Bump;
181 use indoc::indoc;
182
183 macro_rules! assert_import_snapshot {
184 ($input:expr) => {{
185 let input = indoc!($input);
186 let bump = Bump::new();
187 let src = bump.alloc_str(input);
188 let mut parser = Parser::new(&bump, src.as_bytes());
189 let result = parser.import();
190 match result {
191 Ok(ref import) => {
192 insta::with_settings!({
193 description => format!("Code:\n\n{}", input),
194 omit_expression => true,
195 }, {
196 insta::assert_debug_snapshot!(import);
197 });
198 }
199 Err(e) => {
200 panic!("Expected successful parse, got error: {:?}", e);
201 }
202 }
203 }};
204 }
205
206 #[test]
207 fn import_simple() {
208 assert_import_snapshot!("import Foo\n");
209 }
210
211 #[test]
212 fn import_dotted() {
213 assert_import_snapshot!("import Json.Decode\n");
214 }
215
216 #[test]
217 fn import_deeply_nested() {
218 assert_import_snapshot!("import Platform.Cmd.Extra\n");
219 }
220
221 #[test]
222 fn import_with_alias() {
223 assert_import_snapshot!("import Json.Decode as Decode\n");
224 }
225
226 #[test]
227 fn import_exposing_open() {
228 assert_import_snapshot!("import List exposing (..)\n");
229 }
230
231 #[test]
232 fn import_exposing_explicit() {
233 assert_import_snapshot!("import Html exposing (div, span)\n");
234 }
235
236 #[test]
237 fn import_full() {
238 assert_import_snapshot!("import Platform.Cmd as Cmd exposing (Cmd)\n");
239 }
240
241 #[test]
242 fn import_exposing_types() {
243 assert_import_snapshot!("import Maybe exposing (Maybe(..))\n");
244 }
245}