Skip to content

Commit d69a3ce

Browse files
committed
Add a package declaration to the grammar.
This commit adds a `package` directive to the grammar to match what is present in WIT. As a result, the `--package` command line argument to the `parse` command was removed. Additionally, lexing of semver is relaxed so that invalid semantic versions can be detected when building the AST.
1 parent 8c6d7de commit d69a3ce

238 files changed

Lines changed: 1128 additions & 725 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ wasmparser = "0.116.1"
4040
wit-component = "0.18.0"
4141
anyhow = "1.0.75"
4242
clap = { version = "4.4.7", features = ["derive"] }
43-
semver = "1.0.20"
43+
semver = { version = "1.0.20", features = ["serde"] }
4444
pretty_env_logger = "0.5.0"
4545
log = "0.4.20"
4646
tokio = { version = "1.33.0", default-features = false, features = ["macros", "rt-multi-thread"] }

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@
33
The current WAC grammar:
44

55
```ebnf
6-
document ::= statement*
7-
statement ::= import-statement
6+
document ::= package-decl statement*
7+
statement ::= import-statement
88
| type-statement
99
| let-statement
1010
| export-statement
1111
12+
package-decl ::= `package` package-name `;`
13+
package-name ::= id (':' id)+ ('@' version)?
14+
version ::= <SEMVER>
15+
1216
import-statement ::= 'import' id ('with' string)? ':' import-type ';'
1317
import-type ::= package-path | func-type | inline-interface | id
14-
package-path ::= package-name ('/' id)+ ('@' package-version)?
15-
package-name ::= id (':' id)+
16-
package-version ::= <SEMVER>
18+
package-path ::= id (':' id)+ ('/' id)+ ('@' version)?
1719
1820
type-statement ::= interface-decl | world-decl | type-decl
1921
interface-decl ::= 'interface' id '{' interface-item* '}'

crates/wac-parser/src/ast.rs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ pub enum Error<'a> {
113113
/// The span of the empty type.
114114
span: Span<'a>,
115115
},
116+
/// An invalid semantic version was encountered.
117+
#[error("`{version}` is not a valid semantic version")]
118+
InvalidVersion {
119+
/// The invalid version.
120+
version: &'a str,
121+
/// The span of the version.
122+
span: Span<'a>,
123+
},
116124
}
117125

118126
impl Spanned for Error<'_> {
@@ -122,7 +130,8 @@ impl Spanned for Error<'_> {
122130
| Error::Expected { span, .. }
123131
| Error::ExpectedEither { span, .. }
124132
| Error::ExpectedMultiple { span, .. }
125-
| Error::EmptyType { span, .. } => *span,
133+
| Error::EmptyType { span, .. }
134+
| Error::InvalidVersion { span, .. } => *span,
126135
}
127136
}
128137
}
@@ -313,6 +322,10 @@ pub struct Document<'a> {
313322
/// The path to the document, used for error reporting.
314323
#[serde(skip)]
315324
pub path: &'a Path,
325+
/// The doc comments for the package.
326+
pub docs: Vec<DocComment<'a>>,
327+
/// The package name of the document.
328+
pub package: PackageName<'a>,
316329
/// The statements in the document.
317330
pub statements: Vec<Statement<'a>>,
318331
}
@@ -323,14 +336,25 @@ impl<'a> Document<'a> {
323336
/// The given path is used for error reporting.
324337
pub fn parse(source: &'a str, path: &'a Path) -> ParseResult<'a, Self> {
325338
let mut lexer = Lexer::new(source).map_err(Error::from)?;
326-
let mut statements: Vec<Statement> = Default::default();
327339

340+
let docs = Parse::parse(&mut lexer)?;
341+
342+
parse_token(&mut lexer, Token::PackageKeyword)?;
343+
let package = Parse::parse(&mut lexer)?;
344+
parse_token(&mut lexer, Token::Semicolon)?;
345+
346+
let mut statements: Vec<Statement> = Default::default();
328347
while lexer.peek().is_some() {
329348
statements.push(Parse::parse(&mut lexer)?);
330349
}
331350

332351
assert!(lexer.next().is_none(), "expected all tokens to be consumed");
333-
Ok(Self { path, statements })
352+
Ok(Self {
353+
path,
354+
docs,
355+
package,
356+
statements,
357+
})
334358
}
335359
}
336360

@@ -468,6 +492,8 @@ mod test {
468492
fn document_roundtrip() {
469493
let document = Document::parse(
470494
r#"/* ignore me */
495+
/// Doc comment for the package!
496+
package test:foo:bar@1.0.0;
471497
/// Doc comment #1!
472498
import foo: foo:bar/baz;
473499
/// Doc comment #2!
@@ -499,7 +525,10 @@ export x with "foo";
499525

500526
assert_eq!(
501527
document.to_string(),
502-
r#"/// Doc comment #1!
528+
r#"/// Doc comment for the package!
529+
package test:foo:bar@1.0.0;
530+
531+
/// Doc comment #1!
503532
import foo: foo:bar/baz;
504533
505534
/// Doc comment #2!

crates/wac-parser/src/ast/import.rs

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use super::{
2-
display, parse_optional, parse_token, DocComment, FuncType, Ident, InlineInterface, Lookahead,
3-
Parse, ParseResult, Peek,
2+
display, parse_optional, parse_token, DocComment, Error, FuncType, Ident, InlineInterface,
3+
Lookahead, Parse, ParseResult, Peek,
44
};
55
use crate::lexer::{Lexer, Span, Token};
6+
use semver::Version;
67
use serde::Serialize;
78

89
/// Represents an import statement in the AST.
@@ -82,7 +83,7 @@ pub struct PackagePath<'a> {
8283
/// The path segments.
8384
pub segments: &'a str,
8485
/// The optional version of the package.
85-
pub version: Option<&'a str>,
86+
pub version: Option<Version>,
8687
}
8788

8889
impl<'a> PackagePath<'a> {
@@ -112,7 +113,15 @@ impl<'a> Parse<'a> for PackagePath<'a> {
112113
let at = s.find('@');
113114
let name = &s[..slash];
114115
let segments = &s[slash + 1..at.unwrap_or(slash + s.len() - name.len())];
115-
let version = at.map(|at| &s[at + 1..]);
116+
let version = at
117+
.map(|at| {
118+
let version = &s[at + 1..];
119+
version.parse().map_err(|_| Error::InvalidVersion {
120+
version,
121+
span: Span::from_span(span.source(), &(span.start + at + 1..span.end)),
122+
})
123+
})
124+
.transpose()?;
116125

117126
Ok(Self {
118127
span,
@@ -135,15 +144,32 @@ impl Peek for PackagePath<'_> {
135144
pub struct PackageName<'a> {
136145
/// The name of the package.
137146
pub name: &'a str,
147+
/// The optional version of the package.
148+
pub version: Option<Version>,
138149
/// The span of the package name,
139150
pub span: Span<'a>,
140151
}
141152

142153
impl<'a> Parse<'a> for PackageName<'a> {
143154
fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> {
144155
let span = parse_token(lexer, Token::PackageName)?;
145-
let name = span.as_str();
146-
Ok(Self { name, span })
156+
let s = span.as_str();
157+
let at = s.find('@');
158+
let name = at.map(|at| &s[..at]).unwrap_or(s);
159+
let version = at
160+
.map(|at| {
161+
let version = &s[at + 1..];
162+
version.parse().map_err(|_| Error::InvalidVersion {
163+
version,
164+
span: Span::from_span(span.source(), &(span.start + at + 1..span.end)),
165+
})
166+
})
167+
.transpose()?;
168+
Ok(Self {
169+
name,
170+
version,
171+
span,
172+
})
147173
}
148174
}
149175

crates/wac-parser/src/lexer.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ fn detect_invalid_input(source: &str) -> Result<(), (Error, Span)> {
148148
#[logos(skip r"[ \t\n\f]+")]
149149
#[logos(subpattern id = r"%?[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*")]
150150
#[logos(subpattern package_name = r"(?&id)(:(?&id))+")]
151-
#[logos(subpattern semver = r"(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-((0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(\+([0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*))?")]
151+
#[logos(subpattern semver = r"[0-9a-zA-Z-\.\+]+")]
152152
pub enum Token {
153153
/// A comment.
154154
#[regex(r"//[^\n]*", logos::skip)]
@@ -167,7 +167,7 @@ pub enum Token {
167167
String,
168168

169169
/// A package name.
170-
#[regex(r"(?&id)(:(?&id))+")]
170+
#[regex(r"(?&package_name)(@(?&semver))?")]
171171
PackageName,
172172

173173
/// A package path with optional semantic version.
@@ -285,6 +285,9 @@ pub enum Token {
285285
/// The `as` keyword.
286286
#[token("as")]
287287
AsKeyword,
288+
/// The `package` keyword.
289+
#[token("package")]
290+
PackageKeyword,
288291

289292
/// The `;` symbol.
290293
#[token(";")]
@@ -387,6 +390,7 @@ impl fmt::Display for Token {
387390
Token::UseKeyword => write!(f, "`use` keyword"),
388391
Token::IncludeKeyword => write!(f, "`include` keyword"),
389392
Token::AsKeyword => write!(f, "`as` keyword"),
393+
Token::PackageKeyword => write!(f, "`package` keyword"),
390394
Token::Semicolon => write!(f, "`;`"),
391395
Token::OpenBrace => write!(f, "`{{`"),
392396
Token::CloseBrace => write!(f, "`}}`"),

crates/wac-parser/src/printer.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ impl<W: Write> DocumentPrinter<W> {
2727

2828
/// Prints the given document.
2929
pub fn document(&mut self, doc: &Document) -> std::fmt::Result {
30+
self.docs(&doc.docs)?;
31+
writeln!(
32+
self.writer,
33+
"package {package};",
34+
package = doc.package.span.as_str()
35+
)?;
36+
self.newline()?;
37+
3038
for (i, statement) in doc.statements.iter().enumerate() {
3139
if i > 0 {
3240
self.newline()?;

crates/wac-parser/src/resolution.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
use anyhow::Context;
1010
use id_arena::{Arena, Id};
1111
use indexmap::{IndexMap, IndexSet};
12+
use semver::Version;
1213
use serde::{Serialize, Serializer};
1314
use std::{
1415
collections::{hash_map, HashMap},
@@ -594,7 +595,7 @@ pub trait PackageResolver {
594595
/// Resolves a package name to the package bytes.
595596
///
596597
/// Returns `Ok(None)` if the package could not be found.
597-
fn resolve(&self, name: &str) -> anyhow::Result<Option<Vec<u8>>>;
598+
fn resolve(&self, name: &str, version: Option<&Version>) -> anyhow::Result<Option<Vec<u8>>>;
598599
}
599600

600601
/// Used to resolve packages from the file system.
@@ -620,7 +621,7 @@ impl Default for FileSystemPackageResolver {
620621
}
621622

622623
impl PackageResolver for FileSystemPackageResolver {
623-
fn resolve(&self, name: &str) -> anyhow::Result<Option<Vec<u8>>> {
624+
fn resolve(&self, name: &str, _version: Option<&Version>) -> anyhow::Result<Option<Vec<u8>>> {
624625
let path = if let Some(path) = self.overrides.get(name) {
625626
if !path.exists() {
626627
anyhow::bail!(
@@ -840,6 +841,8 @@ impl fmt::Display for FuncKind {
840841
pub struct ResolvedDocument {
841842
/// The name of the package being resolved.
842843
pub package: String,
844+
/// The version of the package being resolved.
845+
pub version: Option<Version>,
843846
/// The definitions in the resolution.
844847
pub definitions: Definitions,
845848
/// The items in the resolution.
@@ -860,7 +863,6 @@ impl ResolvedDocument {
860863
/// Creates a new resolved document from the given document.
861864
pub fn new<'a>(
862865
document: &'a ast::Document<'a>,
863-
package: impl Into<String>,
864866
resolver: Option<Box<dyn PackageResolver>>,
865867
) -> ResolutionResult<'a, Self> {
866868
let mut scopes = Arena::new();
@@ -870,7 +872,8 @@ impl ResolvedDocument {
870872
});
871873

872874
let mut resolution = ResolvedDocument {
873-
package: package.into(),
875+
package: document.package.name.to_owned(),
876+
version: document.package.version.clone(),
874877
definitions: Default::default(),
875878
items: Default::default(),
876879
imports: Default::default(),
@@ -2069,6 +2072,7 @@ impl ResolvedDocument {
20692072
&mut self,
20702073
state: &'b mut ResolutionState<'a>,
20712074
name: &'a str,
2075+
version: Option<&Version>,
20722076
span: Span<'a>,
20732077
) -> ResolutionResult<'a, &'b Package> {
20742078
match state.packages.entry(name.to_owned()) {
@@ -2078,7 +2082,7 @@ impl ResolvedDocument {
20782082
match state
20792083
.resolver
20802084
.as_deref()
2081-
.and_then(|r| r.resolve(name).transpose())
2085+
.and_then(|r| r.resolve(name, version).transpose())
20822086
.transpose()
20832087
.map_err(|e| Error::PackageResolutionFailure {
20842088
name,
@@ -2110,7 +2114,12 @@ impl ResolvedDocument {
21102114
return self.resolve_local_export(state, path);
21112115
}
21122116

2113-
let pkg = self.resolve_package(state, path.name, path.package_name_span())?;
2117+
let pkg = self.resolve_package(
2118+
state,
2119+
path.name,
2120+
path.version.as_ref(),
2121+
path.package_name_span(),
2122+
)?;
21142123

21152124
let mut current = 0;
21162125
let mut parent_ty = None;
@@ -2257,7 +2266,12 @@ impl ResolvedDocument {
22572266
state: &mut ResolutionState<'a>,
22582267
expr: &'a ast::NewExpr<'a>,
22592268
) -> ResolutionResult<'a, ItemId> {
2260-
let pkg = self.resolve_package(state, expr.package.name, expr.package.span)?;
2269+
let pkg = self.resolve_package(
2270+
state,
2271+
expr.package.name,
2272+
expr.package.version.as_ref(),
2273+
expr.package.span,
2274+
)?;
22612275
let ty = pkg.ty;
22622276
let require_all = !expr.ellipsis;
22632277

crates/wac-parser/tests/parser/export.wac

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
package test:comp;
2+
13
/// Export an item (default name)
24
export e;
35

0 commit comments

Comments
 (0)