wac_parser/ast/
import.rs

1use super::{
2    parse_optional, parse_token, DocComment, Error, FuncType, Ident, InlineInterface, Lookahead,
3    Parse, ParseResult, Peek,
4};
5use crate::lexer::{Lexer, Token};
6use miette::SourceSpan;
7use semver::Version;
8use serde::Serialize;
9
10/// Represents an extern name following an `as` clause in the AST.
11#[derive(Debug, Clone, Serialize)]
12#[serde(rename_all = "camelCase")]
13pub enum ExternName<'a> {
14    /// The "as" clause is an identifier.
15    Ident(Ident<'a>),
16    /// The "as" clause is a string.
17    String(super::String<'a>),
18}
19
20impl ExternName<'_> {
21    /// Gets the span of the extern name.
22    pub fn span(&self) -> SourceSpan {
23        match self {
24            Self::Ident(ident) => ident.span,
25            Self::String(string) => string.span,
26        }
27    }
28
29    /// Gets the string value of the extern name.
30    pub fn as_str(&self) -> &str {
31        match self {
32            Self::Ident(ident) => ident.string,
33            Self::String(string) => string.value,
34        }
35    }
36}
37
38impl Peek for ExternName<'_> {
39    fn peek(lookahead: &mut Lookahead) -> bool {
40        Ident::peek(lookahead) || super::String::peek(lookahead)
41    }
42}
43
44impl<'a> Parse<'a> for ExternName<'a> {
45    fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self> {
46        let mut lookahead = Lookahead::new(lexer);
47        if Ident::peek(&mut lookahead) {
48            Ok(Self::Ident(Parse::parse(lexer)?))
49        } else if super::String::peek(&mut lookahead) {
50            Ok(Self::String(Parse::parse(lexer)?))
51        } else {
52            Err(lookahead.error())
53        }
54    }
55}
56
57/// Represents an import statement in the AST.
58#[derive(Debug, Clone, Serialize)]
59#[serde(rename_all = "camelCase")]
60pub struct ImportStatement<'a> {
61    /// The doc comments for the statement.
62    pub docs: Vec<DocComment<'a>>,
63    /// The identifier of the imported item.
64    pub id: Ident<'a>,
65    /// The optional import name.
66    pub name: Option<ExternName<'a>>,
67    /// The type of the imported item.
68    pub ty: ImportType<'a>,
69}
70
71impl<'a> Parse<'a> for ImportStatement<'a> {
72    fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self> {
73        let docs = Parse::parse(lexer)?;
74        parse_token(lexer, Token::ImportKeyword)?;
75        let id = Parse::parse(lexer)?;
76        let name = parse_optional(lexer, Token::AsKeyword, Parse::parse)?;
77        parse_token(lexer, Token::Colon)?;
78        let ty = Parse::parse(lexer)?;
79        parse_token(lexer, Token::Semicolon)?;
80        Ok(Self { docs, id, name, ty })
81    }
82}
83
84impl Peek for ImportStatement<'_> {
85    fn peek(lookahead: &mut Lookahead) -> bool {
86        lookahead.peek(Token::ImportKeyword)
87    }
88}
89
90/// Represents an import type in the AST.
91#[derive(Debug, Clone, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub enum ImportType<'a> {
94    /// The import type is from a package path.
95    Package(PackagePath<'a>),
96    /// The import type is a function.
97    Func(FuncType<'a>),
98    /// The import type is an interface.
99    Interface(InlineInterface<'a>),
100    /// The import type is an identifier.
101    Ident(Ident<'a>),
102}
103
104impl<'a> Parse<'a> for ImportType<'a> {
105    fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self> {
106        let mut lookahead = Lookahead::new(lexer);
107        if FuncType::peek(&mut lookahead) {
108            Ok(Self::Func(Parse::parse(lexer)?))
109        } else if InlineInterface::peek(&mut lookahead) {
110            Ok(Self::Interface(Parse::parse(lexer)?))
111        } else if PackagePath::peek(&mut lookahead) {
112            Ok(Self::Package(Parse::parse(lexer)?))
113        } else if Ident::peek(&mut lookahead) {
114            Ok(Self::Ident(Parse::parse(lexer)?))
115        } else {
116            Err(lookahead.error())
117        }
118    }
119}
120
121/// AST representation of a path to an item such as a world in a package (e.g. `foo:bar/qux`).
122#[derive(Debug, Clone, Serialize)]
123#[serde(rename_all = "camelCase")]
124pub struct PackagePath<'a> {
125    /// The span of the package path.
126    pub span: SourceSpan,
127    /// The entire path string.
128    pub string: &'a str,
129    /// The name of the package.
130    pub name: &'a str,
131    /// The path segments.
132    pub segments: &'a str,
133    /// The optional version of the package.
134    pub version: Option<Version>,
135}
136
137impl<'a> PackagePath<'a> {
138    /// Gets the span of only the package name.
139    pub fn package_name_span(&self) -> SourceSpan {
140        SourceSpan::new(self.span.offset().into(), self.name.len())
141    }
142
143    /// Iterates over the segments of the package path.
144    pub fn segment_spans<'b>(&'b self) -> impl Iterator<Item = (&'a str, SourceSpan)> + 'b {
145        self.segments.split('/').map(|s| {
146            let start = self.span.offset() + s.as_ptr() as usize - self.name.as_ptr() as usize;
147            (s, SourceSpan::new(start.into(), s.len()))
148        })
149    }
150}
151
152impl<'a> Parse<'a> for PackagePath<'a> {
153    fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self> {
154        let span = parse_token(lexer, Token::PackagePath)?;
155        let s = lexer.source(span);
156        let slash = s.find('/').unwrap();
157        let at = s.find('@');
158        let name = &s[..slash];
159        let segments = &s[slash + 1..at.unwrap_or(slash + s.len() - name.len())];
160        let version = at
161            .map(|at| {
162                let version = &s[at + 1..];
163                let start = span.offset() + at + 1;
164                version.parse().map_err(|_| Error::InvalidVersion {
165                    version: version.to_owned(),
166                    span: SourceSpan::new(start.into(), (span.offset() + span.len()) - start),
167                })
168            })
169            .transpose()?;
170
171        Ok(Self {
172            span,
173            string: lexer.source(span),
174            name,
175            segments,
176            version,
177        })
178    }
179}
180
181impl Peek for PackagePath<'_> {
182    fn peek(lookahead: &mut Lookahead) -> bool {
183        lookahead.peek(Token::PackagePath)
184    }
185}
186
187/// Represents a package name in the AST.
188#[derive(Debug, Clone, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct PackageName<'a> {
191    /// The entire package name as a string.
192    pub string: &'a str,
193    /// The name of the package.
194    pub name: &'a str,
195    /// The optional version of the package.
196    pub version: Option<Version>,
197    /// The span of the package name,
198    pub span: SourceSpan,
199}
200
201impl<'a> Parse<'a> for PackageName<'a> {
202    fn parse(lexer: &mut Lexer<'a>) -> ParseResult<Self> {
203        let span = parse_token(lexer, Token::PackageName)?;
204        let s = lexer.source(span);
205        let at = s.find('@');
206        let name = at.map(|at| &s[..at]).unwrap_or(s);
207        let version = at
208            .map(|at| {
209                let version = &s[at + 1..];
210                let start = span.offset() + at + 1;
211                version.parse().map_err(|_| Error::InvalidVersion {
212                    version: version.to_string(),
213                    span: SourceSpan::new(start.into(), (span.offset() + span.len()) - start),
214                })
215            })
216            .transpose()?;
217        Ok(Self {
218            string: lexer.source(span),
219            name,
220            version,
221            span,
222        })
223    }
224}
225
226#[cfg(test)]
227mod test {
228    use crate::ast::test::roundtrip;
229
230    #[test]
231    fn import_via_package_roundtrip() {
232        roundtrip(
233            "package foo:bar; import x: foo:bar:baz/qux/jam@1.2.3-preview+abc;",
234            "package foo:bar;\n\nimport x: foo:bar:baz/qux/jam@1.2.3-preview+abc;\n",
235        )
236        .unwrap();
237
238        roundtrip(
239            "package foo:bar; import x as \"y\": foo:bar:baz/qux/jam@1.2.3-preview+abc;",
240            "package foo:bar;\n\nimport x as \"y\": foo:bar:baz/qux/jam@1.2.3-preview+abc;\n",
241        )
242        .unwrap();
243    }
244
245    #[test]
246    fn import_function_roundtrip() {
247        roundtrip(
248            "package foo:bar; import x: func(x: string) -> string;",
249            "package foo:bar;\n\nimport x: func(x: string) -> string;\n",
250        )
251        .unwrap();
252
253        roundtrip(
254            "package foo:bar; import x as \"foo\": func(x: string) -> string;",
255            "package foo:bar;\n\nimport x as \"foo\": func(x: string) -> string;\n",
256        )
257        .unwrap();
258    }
259
260    #[test]
261    fn import_interface_roundtrip() {
262        roundtrip(
263            "package foo:bar; import x: interface { x: func(x: string) -> string; };",
264            "package foo:bar;\n\nimport x: interface {\n    x: func(x: string) -> string;\n};\n",
265        )
266        .unwrap();
267
268        roundtrip(
269            "package foo:bar; import x as \"foo\": interface { x: func(x: string) -> string; };",
270            "package foo:bar;\n\nimport x as \"foo\": interface {\n    x: func(x: string) -> string;\n};\n",
271        )
272        .unwrap();
273    }
274
275    #[test]
276    fn import_via_ident_roundtrip() {
277        roundtrip(
278            "package foo:bar; import x: y;",
279            "package foo:bar;\n\nimport x: y;\n",
280        )
281        .unwrap();
282
283        roundtrip(
284            "package foo:bar; import x /*foo */ as    \"foo\": y;",
285            "package foo:bar;\n\nimport x as \"foo\": y;\n",
286        )
287        .unwrap();
288    }
289}