diff --git a/Cargo.lock b/Cargo.lock index 5e00659..f25114f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -80,6 +80,17 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -102,19 +113,16 @@ dependencies = [ ] [[package]] -name = "bitflags" -version = "2.4.1" +name = "beef" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" [[package]] -name = "block-buffer" -version = "0.10.4" +name = "bitflags" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" [[package]] name = "cc" @@ -177,15 +185,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "cpufeatures" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" -dependencies = [ - "libc", -] - [[package]] name = "crossbeam-deque" version = "0.8.3" @@ -219,32 +218,12 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - [[package]] name = "either" version = "1.9.0" @@ -281,25 +260,10 @@ dependencies = [ ] [[package]] -name = "from-pest" -version = "0.3.2" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3380d8b4f459e3bb35904036044393332e71d5316be9061d9b545c44b6064db" -dependencies = [ - "log", - "pest", - "void", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "gimli" @@ -319,6 +283,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.3.3" @@ -354,19 +327,16 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "rustix", "windows-sys", ] [[package]] -name = "itertools" -version = "0.10.5" +name = "is_ci" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "616cde7c720bb2bb5824a224687d8f77bfd38922027f01d825cd7453be5099fb" [[package]] name = "itoa" @@ -398,6 +368,38 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "logos" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c000ca4d908ff18ac99b93a062cb8958d331c3220719c52e77cb19cc6ac5d2c1" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc487311295e0002e452025d6b580b77bb17286de87b57138f3b5db711cded68" +dependencies = [ + "beef", + "fnv", + "proc-macro2", + "quote", + "regex-syntax 0.6.29", + "syn", +] + +[[package]] +name = "logos-derive" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfc0d229f1f42d790440136d941afd806bc9e949e2bcb8faa813b0f00d1267e" +dependencies = [ + "logos-codegen", +] + [[package]] name = "memchr" version = "2.6.4" @@ -428,7 +430,7 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi", + "hermit-abi 0.3.3", "libc", ] @@ -441,73 +443,13 @@ dependencies = [ "memchr", ] -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - -[[package]] -name = "pest" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest-ast" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40b5ac58ac48a503d1efdcf0ff044b442c07ac4645d179c62d4af79db89f9cda" -dependencies = [ - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_derive" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35513f630d46400a977c4cb58f78e1bfbe01434316e60c37d27b9ad6139c66d8" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc9fc1b9e7057baba189b5c626e2d6f40681ae5b6eb064dc7c7834101ec8123a" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df74e9e7ec4053ceb980e7c0c8bd3594e977fde1af91daba9c928e8e8c6708d" -dependencies = [ - "once_cell", - "pest", - "sha2", + "supports-color", ] [[package]] @@ -583,7 +525,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.8.2", ] [[package]] @@ -594,9 +536,15 @@ checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.2", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.8.2" @@ -671,17 +619,6 @@ dependencies = [ "serde", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "smallvec" version = "1.11.1" @@ -703,6 +640,16 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "supports-color" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba6faf2ca7ee42fdd458f4347ae0a9bd6bcc445ad7cb57ad82b383f18870d6f" +dependencies = [ + "atty", + "is_ci", +] + [[package]] name = "syn" version = "2.0.38" @@ -766,18 +713,6 @@ dependencies = [ "syn", ] -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - [[package]] name = "unicode-ident" version = "1.0.12" @@ -802,18 +737,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "void" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - [[package]] name = "wac" version = "0.1.0" @@ -834,35 +757,24 @@ name = "wac-parser" version = "0.1.0" dependencies = [ "anyhow", - "clap", - "from-pest", "id-arena", "indexmap", "log", - "pest", - "pest-ast", - "pest_derive", + "logos", + "owo-colors", "pretty_assertions", "pretty_env_logger", "rayon", "semver", "serde", "serde_json", - "wasmparser", + "thiserror", + "wasmparser 0.115.0", "wat", "wit-component", "wit-parser", ] -[[package]] -name = "wasm-encoder" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca90ba1b5b0a70d3d49473c5579951f3bddc78d47b59256d2f9d4922b150aca" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.36.1" @@ -874,9 +786,9 @@ dependencies = [ [[package]] name = "wasm-metadata" -version = "0.10.9" +version = "0.10.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14abc161bfda5b519aa229758b68f2a52b45a12b993808665c857d1a9a00223c" +checksum = "5621910462c61a8efc3248fdfb1739bf649bb335b0df935c27b340418105f9d8" dependencies = [ "anyhow", "indexmap", @@ -884,8 +796,8 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.35.0", - "wasmparser", + "wasm-encoder", + "wasmparser 0.116.0", ] [[package]] @@ -898,6 +810,16 @@ dependencies = [ "semver", ] +[[package]] +name = "wasmparser" +version = "0.116.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53290b1276c5c2d47d694fb1a920538c01f51690e7e261acbe1d10c5fc306ea1" +dependencies = [ + "indexmap", + "semver", +] + [[package]] name = "wast" version = "67.0.0" @@ -907,7 +829,7 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.36.1", + "wasm-encoder", ] [[package]] @@ -1018,9 +940,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "wit-component" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e87488b57a08e2cbbd076b325acbe7f8666965af174d69d5929cd373bd54547f" +checksum = "65672b7a81f9c7a4420af2ad8d0de608e27b520a6d4b68f29f5146e060a86ee4" dependencies = [ "anyhow", "bitflags", @@ -1029,9 +951,9 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.35.0", + "wasm-encoder", "wasm-metadata", - "wasmparser", + "wasmparser 0.116.0", "wit-parser", ] diff --git a/Cargo.toml b/Cargo.toml index 0af009b..ddf4716 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,17 +40,15 @@ wasmparser = "0.115.0" wit-component = "0.16.0" anyhow = "1.0.75" clap = { version = "4.4.6", features = ["derive"] } -pest = "2.7.4" -pest_derive = "2.7.4" -from-pest = "0.3.2" -pest-ast = "0.3.4" semver = "1.0.20" pretty_env_logger = "0.5.0" log = "0.4.20" tokio = { version = "1.33.0", default-features = false, features = ["macros", "rt-multi-thread"] } -owo-colors = "3.5.0" +owo-colors = { version = "3.5.0", features = ["supports-colors"] } indexmap = { version = "2.0.2", features = ["serde"] } id-arena = "2.2.1" serde = { version = "1.0.189", features = ["derive"] } serde_json = "1.0.107" wat = "1.0.77" +logos = "0.13.0" +thiserror = "1.0.50" diff --git a/README.md b/README.md index aa5fa8f..dc84731 100644 --- a/README.md +++ b/README.md @@ -1 +1,117 @@ -# wac \ No newline at end of file +# WAC grammar + +The current WAC grammar: + +```ebnf +document ::= statement* +statement ::= import-statement + | type-statement + | let-statement + | export-statement + +import-statement ::= 'import' id ('with' string)? ':' import-type ';' +import-type ::= package-path | func-type | inline-interface | id +package-path ::= package-name ('/' id)+ ('@' package-version)? +package-name ::= id (':' id)+ +package-version ::= + +type-statement ::= interface-decl | world-decl | type-decl +interface-decl ::= 'interface' id '{' interface-item* '}' +interface-item ::= use-type | item-type-decl | interface-export +use-type ::= 'use' use-path '.' '{' use-items '}' ';' +use-path ::= package-path | id +use-items ::= use-item (',' use-item)* ','? +use-item ::= id ('as' id)? +interface-export ::= id ':' func-type-ref ';' +world-decl ::= 'world' id '{' world-item* '}' +world-item ::= use-type + | item-type-decl + | world-import + | world-export + | world-include +world-import ::= 'import' world-item-path ';' +world-export ::= 'export' world-item-path ';' +world-item-path ::= named-world-item | package-path | id +named-world-item ::= id ':' extern-type +extern-type ::= func-type | inline-interface | id +inline-interface ::= 'interface' '{' interface-item* '}' +world-include ::= 'include' world-ref ('with' '{' world-include-items '}')? ';' +world-include-items ::= world-include-item (',' world-include-item)* ','? +world-include-item ::= id 'as' id +world-ref ::= package-path | id +type-decl ::= variant-decl | record-decl | flags-decl | enum-decl | type-alias +item-type-decl ::= resource-decl | type-decl +resource-decl ::= 'resource' id '{' resource-item* '}' +resource-item ::= constructor | method +constructor ::= 'constructor' param-list +method ::= id ':' 'static'? func-type-ref +variant-decl ::= 'variant' id '{' variant-cases '}' +variant-cases ::= variant-case (',' variant-case)* ','? +variant-case ::= id ('(' type ')')? +record-decl ::= 'record' id '{' fields '}' +fields ::= named-type (',' named-type)* ','? +flags-decl ::= 'flags' id '{' ids '}' +ids ::= id (',' id)* ','? +enum-decl ::= 'enum' id '{' ids '}' +type-alias ::= 'type' id '=' (func-type | type) ';' +func-type-ref ::= func-type | id +func-type ::= '(' params? ')' ('->' results)? +params ::= named-type (',' named-type)* ','? +results ::= type + | '(' named-type (',' named-type)* ','? ')' +named-type ::= id ':' type +type ::= u8 + | s8 + | u16 + | s16 + | u32 + | s32 + | u64 + | s64 + | float32 + | float64 + | char + | bool + | string + | tuple + | list + | option + | result + | borrow + | id +tuple ::= 'tuple' '<' type (',' type)* ','? '>' +list ::= 'list' '<' type '>' +option ::= 'option' '<' type '>' +result ::= 'result' + | 'result' '<' type '>' + | 'result' '<' '_' ',' type '>' + | 'result' '<' type ',' type '>' +borrow ::= 'borrow' '<' type '>' + +let-statement ::= 'let' id '=' expr ';' +expr ::= primary-expr postfix-expr* +primary-expr ::= new-expr | nested-expr | id +new-expr ::= 'new' package-name '{' instantiation-args '}' +instantiation-args ::= instantiation-arg (',' instantiation-arg)* (',' '...'?)? +instantiation-arg ::= named-instantiation-arg | id +named-instantiation-arg ::= (id | string) ':' expr +nested-expr ::= '(' expr ')' +postfix-expr ::= access-expr | named-access-expr +access-expr ::= '.' id +named-access-expr ::= '[' string ']' + +export-statement ::= 'export' expr ('with' string)? ';' + +id ::= '%'?[a-z][a-z0-9]*('-'[a-z][a-z0-9]*)* +string ::= '"' character-that-is-not-a-double-quote* '"' +``` + +Whitespace (may appear anywhere between tokens): + +```ebnf +whitespace ::= ' ' | '\n' | '\r' | '\t' | comment +comment ::= '//' character-that-is-not-a-newline* + | '/*' any-unicode-character* '*/' +``` + +Note: block comments are allowed to be nested. diff --git a/crates/wac-parser/Cargo.toml b/crates/wac-parser/Cargo.toml index 7655f0a..21784a7 100644 --- a/crates/wac-parser/Cargo.toml +++ b/crates/wac-parser/Cargo.toml @@ -11,11 +11,8 @@ repository = { workspace = true } [dependencies] anyhow = { workspace = true } -clap = { workspace = true } -pest = { workspace = true } -pest_derive = { workspace = true } -pest-ast = { workspace = true } -from-pest = { workspace = true } +logos = { workspace = true } +thiserror = { workspace = true } semver = { workspace = true } log = { workspace = true } indexmap = { workspace = true } @@ -25,6 +22,7 @@ wasmparser = { workspace = true } wit-parser = { workspace = true } wit-component = { workspace = true } wat = { workspace = true, optional = true } +owo-colors = { workspace = true } [features] default = ["wat"] diff --git a/crates/wac-parser/src/ast.rs b/crates/wac-parser/src/ast.rs index eb453a8..f8b2448 100644 --- a/crates/wac-parser/src/ast.rs +++ b/crates/wac-parser/src/ast.rs @@ -1,26 +1,16 @@ //! Module for the AST implementation. -// TODO: remove on next pest-ast release -// see: https://github.com/pest-parser/ast/pull/27 -#![allow(clippy::clone_on_copy)] - -use crate::parser::{DocumentParser, Rule}; -use anyhow::{anyhow, bail, Context}; -use from_pest::FromPest as _; -use pest::{ - error::{Error, ErrorVariant}, - Parser, Span, +use crate::{ + lexer::{self, Lexer, LexerResult, Span, Token}, + Spanned, }; -use pest_ast::FromPest; -use serde::{Serialize, Serializer}; +use serde::Serialize; use std::{fmt, path::Path}; mod export; mod expr; mod import; -pub mod keywords; mod r#let; -pub mod symbols; mod r#type; pub use export::*; @@ -29,143 +19,292 @@ pub use import::*; pub use r#let::*; pub use r#type::*; -pub(crate) fn serialize_span(span: &Span, serializer: S) -> std::result::Result -where - S: Serializer, -{ - use serde::ser::SerializeStruct; - - let mut s = serializer.serialize_struct("Span", 2)?; - s.serialize_field("str", span.as_str())?; - s.serialize_field("start", &span.start())?; - s.serialize_field("end", &span.end())?; - s.end() -} - -/// Creates a new error with the given message, file path, and span. -pub fn new_error_with_span(path: &Path, span: Span, msg: impl fmt::Display) -> anyhow::Error { - let mut e = Error::new_from_span( - ErrorVariant::CustomError:: { - message: msg.to_string(), - }, - span, - ); +struct Found(Option); - if let Some(path) = path.to_str() { - e = e.with_path(path) +impl fmt::Display for Found { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Some(t) => t.fmt(f), + None => write!(f, "end of input"), + } } - - let (line, column) = match e.line_col { - pest::error::LineColLocation::Pos((l, c)) => (l, c), - pest::error::LineColLocation::Span((l, c), _) => (l, c), - }; - - anyhow!(format!( - "{path}:{line}:{column}: {msg}\n\n{e}", - path = path.display() - )) } -/// Used to indent output when displaying AST nodes. -#[derive(Debug, Clone, Copy, Default)] -pub struct Indenter { - level: usize, - repeated: std::option::Option<&'static str>, +struct Expected<'a> { + expected: &'a [Option], + count: usize, } -impl Indenter { - /// Creates a new indenter. - /// - /// The default repeated pattern is two spaces. - pub fn new() -> Self { - Self::default() - } +impl fmt::Display for Expected<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut expected = self.expected.iter().enumerate(); + while let Some((i, Some(token))) = expected.next() { + if i > 0 { + write!(f, ", ")?; + } - /// Creates a new indenter with the given repeated pattern per-level. - pub fn new_with_repeated(repeated: &'static str) -> Self { - Self { - level: 0, - repeated: Some(repeated), + if i == self.expected.len() - 1 && self.count <= self.expected.len() { + write!(f, "or ")?; + } + + token.fmt(f)?; } - } - /// Increases the indentation level. - pub fn indent(&mut self) { - self.level = self.level.saturating_add(1); - } + if self.count > self.expected.len() { + write!(f, ", or more...")?; + } - /// Decreases the indentation level. - pub fn dedent(&mut self) { - self.level = self.level.saturating_sub(1); + Ok(()) } } -impl fmt::Display for Indenter { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let repeated = self.repeated.unwrap_or(" "); +/// Represents a parse error. +#[derive(thiserror::Error, Debug)] +pub enum Error<'a> { + /// A lexer error occurred. + #[error("{error}")] + Lexer { + /// The lexer error that occurred. + error: crate::lexer::Error, + /// The span where the error occurred. + span: Span<'a>, + }, + /// An unexpected token was encountered when a single token was expected. + #[error("expected {expected}, found {found}", found = Found(*.found))] + Expected { + /// The expected token. + expected: Token, + /// The found token (`None` for end of input). + found: Option, + /// The span of the found token. + span: Span<'a>, + }, + /// An unexpected token was encountered when either one of two tokens was expected. + #[error("expected {first} or {second}, found {found}", found = Found(*.found))] + ExpectedEither { + /// The first expected token. + first: Token, + /// The second expected token. + second: Token, + /// The found token. + found: Option, + /// The span of the found token. + span: Span<'a>, + }, + /// An unexpected token was encountered when multiple tokens were expected. + #[error("expected either {expected}, found {found}", expected = Expected { expected, count: *.count }, found = Found(*.found))] + ExpectedMultiple { + /// The tokens that were expected. + expected: [Option; 10], + /// The count of expected tokens. + count: usize, + /// The found token. + found: Option, + /// The span of the found token. + span: Span<'a>, + }, + /// An empty type was encountered. + #[error("{ty} must contain at least one {kind}")] + EmptyType { + /// The type that was empty (e.g. "record", "variant", etc.) + ty: &'static str, + /// The kind of item that was empty (e.g. "field", "case", etc.) + kind: &'static str, + /// The span of the empty type. + span: Span<'a>, + }, +} - for _ in 0..self.level { - write!(f, "{repeated}")?; +impl Spanned for Error<'_> { + fn span(&self) -> Span<'_> { + match self { + Error::Lexer { span, .. } + | Error::Expected { span, .. } + | Error::ExpectedEither { span, .. } + | Error::ExpectedMultiple { span, .. } + | Error::EmptyType { span, .. } => *span, } + } +} - Ok(()) +/// Represents a parse result. +pub type ParseResult<'a, T> = Result>; + +impl<'a> From<(lexer::Error, Span<'a>)> for Error<'a> { + fn from((e, s): (lexer::Error, Span<'a>)) -> Self { + Self::Lexer { error: e, span: s } } } -macro_rules! display { - ($id:ident) => { - impl std::fmt::Display for $id<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(self, f, &mut crate::ast::Indenter::new()) +/// Expects a given token from the lexer. +pub fn parse_token<'a>(lexer: &mut Lexer<'a>, expected: Token) -> ParseResult<'a, Span<'a>> { + let (result, span) = lexer.next().ok_or_else(|| Error::Expected { + expected, + found: None, + span: lexer.span(), + })?; + + match result { + Ok(found) if found == expected => Ok(span), + Ok(found) => Err(Error::Expected { + expected, + found: Some(found), + span, + }), + Err(e) => Err((e, span).into()), + } +} + +/// Parses an optional tokens from a lexer. +/// +/// The expected token is removed from the token stream before the callback is invoked. +pub fn parse_optional<'a, F, R>( + lexer: &mut Lexer<'a>, + expected: Token, + cb: F, +) -> ParseResult<'a, Option> +where + F: FnOnce(&mut Lexer<'a>) -> ParseResult<'a, R>, +{ + match lexer.peek() { + Some((Ok(token), _)) => { + if token == expected { + lexer.next(); + Ok(Some(cb(lexer)?)) + } else { + Ok(None) } } - }; + Some((Err(e), s)) => Err((e, s).into()), + None => Ok(None), + } } -pub(crate) use display; - -/// A trait for displaying AST nodes. +/// Used to look ahead one token in the lexer. /// -/// This differs from `fmt::Display` in that it takes an `Indenter` -/// which can be used to properly indent the output. -pub trait AstDisplay { - /// Formats the AST node. - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result; +/// The lookahead stores up to 10 attempted tokens. +pub struct Lookahead<'a> { + next: Option<(LexerResult<'a, Token>, Span<'a>)>, + source: &'a str, + attempts: [Option; 10], + count: usize, } -impl<'a> AstDisplay for Vec> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - for (i, statement) in self.iter().enumerate() { - if i > 0 { - writeln!(f)?; - } +impl<'a> Lookahead<'a> { + /// Creates a new lookahead from the given lexer. + pub fn new(lexer: &Lexer<'a>) -> Self { + Self { + next: lexer.peek(), + source: lexer.source(), + attempts: Default::default(), + count: 0, + } + } - statement.fmt(f, indenter)?; - writeln!(f)?; + /// Peeks to see if the next token matches the given token. + pub fn peek(&mut self, expected: Token) -> bool { + match &self.next { + Some((Ok(t), _)) if *t == expected => true, + _ => { + if self.count < self.attempts.len() { + self.attempts[self.count] = Some(expected); + } + + self.count += 1; + false + } } + } - Ok(()) + /// Returns an error based on the attempted tokens. + /// + /// Panics if no peeks were attempted. + pub fn error(self) -> Error<'a> { + let (found, span) = match self.next { + Some((Ok(token), span)) => (Some(token), span), + Some((Err(e), s)) => return (e, s).into(), + None => ( + None, + Span::from_span(self.source, &(self.source.len()..self.source.len())), + ), + }; + + match self.count { + 0 => unreachable!("lookahead had no attempts"), + 1 => Error::Expected { + expected: self.attempts[0].unwrap(), + found, + span, + }, + 2 => Error::ExpectedEither { + first: self.attempts[0].unwrap(), + second: self.attempts[1].unwrap(), + found, + span, + }, + _ => Error::ExpectedMultiple { + expected: self.attempts, + count: self.count, + found, + span, + }, + } } } -#[derive(Debug, Clone, Copy, FromPest)] -#[pest_ast(rule(Rule::EOI))] -struct EndOfInput; - -#[derive(Debug, Clone, FromPest)] -#[pest_ast(rule(Rule::Document))] -struct Ast<'a> { - statements: Vec>, - _eoi: EndOfInput, +pub(crate) trait Parse<'a>: Sized { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self>; } -impl AstDisplay for Ast<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.statements.fmt(f, indenter) +fn parse_delimited<'a, T: Parse<'a> + Peek>( + lexer: &mut Lexer<'a>, + until: &[Token], + with_commas: bool, +) -> ParseResult<'a, Vec> { + assert!( + !until.is_empty(), + "must have at least one token to parse until" + ); + + let mut items = Vec::new(); + loop { + let mut lookahead = Lookahead::new(lexer); + if until.iter().any(|t| lookahead.peek(*t)) { + break; + } + + if !T::peek(&mut lookahead) { + return Err(lookahead.error()); + } + + items.push(Parse::parse(lexer)?); + + if with_commas { + if let Some((Ok(Token::Comma), _)) = lexer.peek() { + lexer.next(); + } + } } + + Ok(items) +} + +trait Peek { + fn peek(lookahead: &mut Lookahead) -> bool; +} + +macro_rules! display { + ($name:ident, $method:ident) => { + impl std::fmt::Display for $name<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut printer = crate::printer::DocumentPrinter::new(f, None); + printer.$method(self) + } + } + }; } -display!(Ast); +pub(crate) use display; /// Represents a top-level WAC document. #[derive(Debug, Clone, Serialize)] @@ -175,119 +314,30 @@ pub struct Document<'a> { #[serde(skip)] pub path: &'a Path, /// The statements in the document. - pub statements: Vec>, + pub statements: Vec>, } impl<'a> Document<'a> { - /// Parses the given string as a document. + /// Parses the given source string as a document. /// /// The given path is used for error reporting. - pub fn parse(contents: &'a str, path: &'a Path) -> anyhow::Result { - Self::detect_invalid_input(contents)?; - - let mut iter = DocumentParser::parse(Rule::Document, contents).map_err(|e| { - let mut e = e.renamed_rules(|r| r.rename().to_owned()); - if let Some(path) = path.as_os_str().to_str() { - e = e.with_path(path); - } - - let msg = e.variant.message().to_string(); - anyhow!(e).context(msg) - })?; - - let document = Ast::from_pest(&mut iter).context("failed to construct AST")?; - Ok(Self { - path, - statements: document.statements, - }) - } - - fn detect_invalid_input(input: &str) -> anyhow::Result<()> { - // Disallow specific codepoints. - let mut line = 1; - for ch in input.chars() { - match ch { - '\n' => line += 1, - '\r' | '\t' => {} - - // Bidirectional override codepoints can be used to craft source code that - // appears to have a different meaning than its actual meaning. See - // [CVE-2021-42574] for background and motivation. - // - // [CVE-2021-42574]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574 - '\u{202a}' | '\u{202b}' | '\u{202c}' | '\u{202d}' | '\u{202e}' | '\u{2066}' - | '\u{2067}' | '\u{2068}' | '\u{2069}' => { - bail!( - "input contains bidirectional override codepoint {:?} at line {line}", - ch.escape_unicode(), - ); - } - - // Disallow several characters which are deprecated or discouraged in Unicode. - // - // U+149 deprecated; see Unicode 13.0.0, sec. 7.1 Latin, Compatibility Digraphs. - // U+673 deprecated; see Unicode 13.0.0, sec. 9.2 Arabic, Additional Vowel Marks. - // U+F77 and U+F79 deprecated; see Unicode 13.0.0, sec. 13.4 Tibetan, Vowels. - // U+17A3 and U+17A4 deprecated, and U+17B4 and U+17B5 discouraged; see - // Unicode 13.0.0, sec. 16.4 Khmer, Characters Whose Use Is Discouraged. - '\u{149}' | '\u{673}' | '\u{f77}' | '\u{f79}' | '\u{17a3}' | '\u{17a4}' - | '\u{17b4}' | '\u{17b5}' => { - bail!( - "codepoint {:?} at line {line} is discouraged by Unicode", - ch.escape_unicode(), - ); - } - - // Disallow control codes other than the ones explicitly recognized above, - // so that viewing a wit file on a terminal doesn't have surprising side - // effects or appear to have a different meaning than its actual meaning. - ch if ch.is_control() => { - bail!("control code '{}' at line {line}", ch.escape_unicode()); - } - - _ => {} - } - } - - Ok(()) - } -} + pub fn parse(source: &'a str, path: &'a Path) -> ParseResult<'a, Self> { + let mut lexer = Lexer::new(source).map_err(Error::from)?; + let mut statements: Vec = Default::default(); -impl AstDisplay for Document<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.statements.fmt(f, indenter) - } -} - -display!(Document); - -/// Represents a top-level statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::TopLevelStatement))] -#[serde(rename_all = "camelCase")] -pub struct TopLevelStatement<'a> { - /// The doc comments for the statement. - pub docs: Vec>, - /// The statement. - pub stmt: Statement<'a>, -} - -impl AstDisplay for TopLevelStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - for doc in &self.docs { - doc.fmt(f, indenter)?; + while lexer.peek().is_some() { + statements.push(Parse::parse(&mut lexer)?); } - self.stmt.fmt(f, indenter) + assert!(lexer.next().is_none(), "expected all tokens to be consumed"); + Ok(Self { path, statements }) } } -display!(TopLevelStatement); +display!(Document, document); /// Represents a statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Statement))] -#[serde(rename_all = "camelCase")] +#[derive(Debug, Clone, Serialize)] pub enum Statement<'a> { /// An import statement. Import(ImportStatement<'a>), @@ -299,105 +349,98 @@ pub enum Statement<'a> { Export(ExportStatement<'a>), } -impl AstDisplay for Statement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Import(import) => import.fmt(f, indenter), - Self::Type(ty) => ty.fmt(f, indenter), - Self::Let(l) => l.fmt(f, indenter), - Self::Export(export) => export.fmt(f, indenter), +impl<'a> Parse<'a> for Statement<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if ImportStatement::peek(&mut lookahead) { + Ok(Self::Import(Parse::parse(lexer)?)) + } else if LetStatement::peek(&mut lookahead) { + Ok(Self::Let(Parse::parse(lexer)?)) + } else if ExportStatement::peek(&mut lookahead) { + Ok(Self::Export(Parse::parse(lexer)?)) + } else if TypeStatement::peek(&mut lookahead) { + Ok(Self::Type(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(Statement); - /// Represents an identifier in the AST. -#[derive(Debug, Clone, Copy, Serialize, FromPest)] -#[pest_ast(rule(Rule::Ident))] +#[derive(Debug, Clone, Copy, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Ident<'a>( - #[pest_ast(outer())] - #[serde(serialize_with = "serialize_span")] - pub Span<'a>, -); - -impl<'a> Ident<'a> { - /// Gets the identifier as a string. - /// - /// This trims any leading `%` character in a raw identifier. - pub fn as_str(&self) -> &'a str { - self.0.as_str().trim_start_matches('%') - } +pub struct Ident<'a> { + /// The identifier string. + pub string: &'a str, + /// The span of the identifier. + pub span: Span<'a>, } -impl AstDisplay for Ident<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, _indenter: &mut Indenter) -> fmt::Result { - write!(f, "{s}", s = self.0.as_str()) +impl<'a> Parse<'a> for Ident<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let span = parse_token(lexer, Token::Ident)?; + let id = span.as_str(); + Ok(Self { + string: id.strip_prefix('%').unwrap_or(id), + span, + }) } } -display!(Ident); +impl Peek for Ident<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::Ident) + } +} /// Represents a string in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::String))] +#[derive(Debug, Copy, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct String<'a>( - #[pest_ast(outer())] - #[serde(serialize_with = "serialize_span")] - pub Span<'a>, -); - -impl String<'_> { - /// Gets a string representation without the surrounding quotes. - pub fn as_str(&self) -> &str { - self.0.as_str().trim_matches('"') - } +pub struct String<'a> { + /// The value of the string (without quotes). + pub value: &'a str, + /// The span of the string. + pub span: Span<'a>, } -impl AstDisplay for String<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, _indenter: &mut Indenter) -> fmt::Result { - write!(f, "{s}", s = self.0.as_str()) +impl<'a> Parse<'a> for String<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let span = parse_token(lexer, Token::String)?; + let s = span.as_str(); + Ok(Self { + value: s.strip_prefix('"').unwrap().strip_suffix('"').unwrap(), + span, + }) } } -display!(String); +impl Peek for String<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::String) + } +} /// Represents a documentation comment in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::DocComment))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct DocComment<'a>( - #[pest_ast(outer())] - #[serde(serialize_with = "serialize_span")] - pub Span<'a>, -); - -impl DocComment<'_> { - /// Gets the lines of the documentation comment. - pub fn lines(&self) -> impl Iterator { - let mut s = self.0.as_str().trim(); - - if s.starts_with("///") { - s = &s[3..]; - } else if s.starts_with("/**") { - // The string ends with "*/" so, the slice end is (3 + 2 - 1). - s = &s[3..s.len() - 4]; - } - - s.lines().map(str::trim) - } +pub struct DocComment<'a> { + /// The comment string. + pub comment: &'a str, + /// The span of the comment. + pub span: Span<'a>, } -impl AstDisplay for DocComment<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{span}", span = self.0.as_str()) +impl<'a> Parse<'a> for Vec> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Vec>> { + Ok(lexer + .comments() + .map_err(Error::from)? + .into_iter() + .map(|(comment, span)| DocComment { comment, span }) + .collect()) } } -display!(DocComment); - #[cfg(test)] mod test { use super::*; @@ -411,28 +454,19 @@ mod test { /// as the output string, since the input string may contain /// extra whitespace and comments that are not preserved in /// the AST. - pub(crate) fn roundtrip<'a, T>( - rule: T::Rule, - input: &'a str, + pub(crate) fn roundtrip<'a, T: Parse<'a> + fmt::Display>( + source: &'a str, expected: &str, - ) -> anyhow::Result<()> - where - T: from_pest::FromPest<'a, Rule = Rule> + fmt::Display, - T::FatalError: std::error::Error + Send + Sync + 'static, - { - let mut pairs = DocumentParser::parse(rule, input) - .map_err(|e| e.renamed_rules(|r| r.rename().to_owned())) - .context("failed to parse input string")?; - - let statement = T::from_pest(&mut pairs)?; - assert_eq!(statement.to_string(), expected, "unexpected AST output"); + ) -> ParseResult<'a, ()> { + let mut lexer = Lexer::new(source).map_err(Error::from)?; + let node: T = Parse::parse(&mut lexer)?; + assert_eq!(node.to_string(), expected, "unexpected AST output"); Ok(()) } #[test] fn document_roundtrip() { - roundtrip::( - Rule::Document, + let document = Document::parse( r#"/* ignore me */ /// Doc comment #1! import foo: foo:bar/baz; @@ -459,6 +493,12 @@ let x = new foo:bar { }; /// Doc comment #10! export x with "foo"; "#, + Path::new("test"), + ) + .unwrap(); + + assert_eq!( + document.to_string(), r#"/// Doc comment #1! import foo: foo:bar/baz; @@ -467,22 +507,22 @@ type a = u32; /// Doc comment #3! record r { - x: string, + x: string, } /// Doc comment #4! interface i { - /// Doc comment #5! - f: func() -> r; + /// Doc comment #5! + f: func() -> r; } /// Doc comment #6! world w { - /// Doc comment #7! - import i; + /// Doc comment #7! + import i; - /// Doc comment #8! - export f: func() -> a; + /// Doc comment #8! + export f: func() -> a; } /// Doc comment #9! @@ -490,8 +530,7 @@ let x = new foo:bar {}; /// Doc comment #10! export x with "foo"; -"#, - ) - .unwrap(); +"# + ); } } diff --git a/crates/wac-parser/src/ast/export.rs b/crates/wac-parser/src/ast/export.rs index 6565f92..1bfc8d3 100644 --- a/crates/wac-parser/src/ast/export.rs +++ b/crates/wac-parser/src/ast/export.rs @@ -1,110 +1,87 @@ use super::{ - display, keywords::Export, symbols::Semicolon, AstDisplay, Expr, Indenter, WithClause, + display, expr::Expr, parse_optional, parse_token, DocComment, Lookahead, Parse, ParseResult, + Peek, }; -use crate::parser::Rule; -use pest_ast::FromPest; +use crate::lexer::{Lexer, Token}; use serde::Serialize; -use std::fmt; /// Represents an export statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ExportStatement))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ExportStatement<'a> { - /// The export keyword in the statement. - pub keyword: Export<'a>, + /// The doc comments for the statement. + pub docs: Vec>, + /// The optional `with` string. + pub with: Option>, /// The expression to export. pub expr: Expr<'a>, - /// The optional with clause. - pub with: Option>, - /// The semicolon in the statement. - pub semicolon: Semicolon<'a>, } -impl AstDisplay for ExportStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - - self.expr.fmt(f, indenter)?; - - if let Some(with) = &self.with { - write!(f, " ")?; - with.fmt(f, indenter)?; - } +impl<'a> Parse<'a> for ExportStatement<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::ExportKeyword)?; + let expr = Parse::parse(lexer)?; + let with = parse_optional(lexer, Token::WithKeyword, Parse::parse)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, expr, with }) + } +} - write!(f, "{semi}", semi = self.semicolon) +impl Peek for ExportStatement<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::ExportKeyword) } } -display!(ExportStatement); +display!(ExportStatement, export_statement); #[cfg(test)] mod test { use super::*; - use crate::{ast::test::roundtrip, parser::Rule}; + use crate::ast::test::roundtrip; #[test] fn export_statement_roundtrip() { - roundtrip::(Rule::ExportStatement, "export y;", "export y;").unwrap(); + roundtrip::("export y;", "export y;").unwrap(); roundtrip::( - Rule::ExportStatement, "export foo.%with with \"foo\";", "export foo.%with with \"foo\";", ) .unwrap(); - roundtrip::(Rule::ExportStatement, "export y.x.z;", "export y.x.z;") + roundtrip::("export y.x.z;", "export y.x.z;").unwrap(); + roundtrip::("export y.x.z with \"baz\";", "export y.x.z with \"baz\";") .unwrap(); + roundtrip::("export y[\"x\"][\"z\"];", "export y[\"x\"][\"z\"];").unwrap(); roundtrip::( - Rule::ExportStatement, - "export y.x.z with \"baz\";", - "export y.x.z with \"baz\";", - ) - .unwrap(); - roundtrip::( - Rule::ExportStatement, - "export y[\"x\"][\"z\"];", - "export y[\"x\"][\"z\"];", - ) - .unwrap(); - roundtrip::( - Rule::ExportStatement, "export y[\"x\"][\"z\"] with \"x\";", "export y[\"x\"][\"z\"] with \"x\";", ) .unwrap(); roundtrip::( - Rule::ExportStatement, "export foo[\"bar\"].baz[\"qux\"];", "export foo[\"bar\"].baz[\"qux\"];", ) .unwrap(); roundtrip::( - Rule::ExportStatement, "export foo[\"bar\"].baz[\"qux\"] with \"qux\";", "export foo[\"bar\"].baz[\"qux\"] with \"qux\";", ) .unwrap(); - roundtrip::(Rule::ExportStatement, "export (y);", "export (y);").unwrap(); + roundtrip::("export (y);", "export (y);").unwrap(); - roundtrip::( - Rule::ExportStatement, - "export new foo:bar {};", - "export new foo:bar {};", - ) - .unwrap(); + roundtrip::("export new foo:bar {};", "export new foo:bar {};").unwrap(); roundtrip::( - Rule::ExportStatement, "export new foo:bar {} /* foo */ with \"foo\";", "export new foo:bar {} with \"foo\";", ) .unwrap(); roundtrip::( - Rule::ExportStatement, - "export new foo:bar { foo, \"bar\": (new baz:qux {...}), \"baz\": foo[\"baz\"].qux };", - "export new foo:bar {\n foo,\n \"bar\": (new baz:qux { ... }),\n \"baz\": foo[\"baz\"].qux,\n};", + "export new foo:bar { foo, \"bar\": (new baz:qux {a, ...}), \"baz\": foo[\"baz\"].qux };", + "export new foo:bar {\n foo,\n \"bar\": (new baz:qux {\n a,\n ...\n }),\n \"baz\": foo[\"baz\"].qux,\n};", ) .unwrap(); } diff --git a/crates/wac-parser/src/ast/expr.rs b/crates/wac-parser/src/ast/expr.rs index 638e925..1e5abb5 100644 --- a/crates/wac-parser/src/ast/expr.rs +++ b/crates/wac-parser/src/ast/expr.rs @@ -1,21 +1,12 @@ use super::{ - display, - keywords::New, - symbols::{ - CloseBrace, CloseBracket, CloseParen, Colon, Dot, Ellipsis, OpenBrace, OpenBracket, - OpenParen, - }, - AstDisplay, Ident, Indenter, PackageName, + display, parse_delimited, parse_optional, parse_token, Ident, Lookahead, PackageName, Parse, + ParseResult, Peek, }; -use crate::parser::Rule; -use pest::Span; -use pest_ast::FromPest; +use crate::lexer::{Lexer, Span, Token}; use serde::Serialize; -use std::fmt; /// Represents an expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Expr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct Expr<'a> { /// The primary expression. @@ -24,23 +15,31 @@ pub struct Expr<'a> { pub postfix: Vec>, } -impl AstDisplay for Expr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.primary.fmt(f, indenter)?; - - for postfix in &self.postfix { - postfix.fmt(f, indenter)?; +impl<'a> Parse<'a> for Expr<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let primary = Parse::parse(lexer)?; + // Currently, only the access expressions are supported for postfix expressions. + // As they have the same precedence, we don't need to perform climbing. + let mut postfix = Vec::new(); + while let Some((Ok(token), _)) = lexer.peek() { + match token { + Token::Dot => { + postfix.push(PostfixExpr::Access(Parse::parse(lexer)?)); + } + Token::OpenBracket => { + postfix.push(PostfixExpr::NamedAccess(Parse::parse(lexer)?)); + } + _ => break, + } } - - Ok(()) + Ok(Self { primary, postfix }) } } -display!(Expr); +display!(Expr, expr); /// Represents a primary expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::PrimaryExpr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum PrimaryExpr<'a> { /// A new expression. @@ -51,104 +50,57 @@ pub enum PrimaryExpr<'a> { Ident(Ident<'a>), } -impl AstDisplay for PrimaryExpr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - PrimaryExpr::New(new) => new.fmt(f, indenter), - PrimaryExpr::Nested(nested) => nested.fmt(f, indenter), - PrimaryExpr::Ident(ident) => ident.fmt(f, indenter), +impl<'a> Parse<'a> for PrimaryExpr<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if NewExpr::peek(&mut lookahead) { + Ok(Self::New(Parse::parse(lexer)?)) + } else if NestedExpr::peek(&mut lookahead) { + Ok(Self::Nested(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(PrimaryExpr); - /// Represents a new expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NewExpr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct NewExpr<'a> { - /// The new keyword in the expression. - pub keyword: New<'a>, /// The package name in the expression. - pub package_name: PackageName<'a>, - /// The new expression body. - pub body: NewExprBody<'a>, -} - -impl AstDisplay for NewExpr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{keyword} {package_name} ", - keyword = self.keyword, - package_name = self.package_name, - )?; - - self.body.fmt(f, indenter) - } -} - -display!(NewExpr); - -/// Represents a new expression body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NewExprBody))] -#[serde(rename_all = "camelCase")] -pub struct NewExprBody<'a> { - /// The open brace in the expression. - pub open: OpenBrace<'a>, + pub package: PackageName<'a>, /// The instantiation arguments in the expression. pub arguments: Vec>, - /// The optional trailing ellipsis in the expression. - pub ellipsis: Option>, - /// The close brace in the expression. - pub close: CloseBrace<'a>, + /// Whether or not a trailing ellipsis was in the expression. + pub ellipsis: bool, } -impl AstDisplay for NewExprBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - if self.arguments.is_empty() { - match &self.ellipsis { - Some(ellipsis) => { - write!( - f, - "{open} {ellipsis} {close}", - open = self.open, - ellipsis = ellipsis, - close = self.close - )?; - } - None => { - write!(f, "{open}{close}", open = self.open, close = self.close)?; - } - } - return Ok(()); - } - - writeln!(f, "{open}", open = self.open)?; - - indenter.indent(); - for arg in &self.arguments { - write!(f, "{indenter}")?; - arg.fmt(f, indenter)?; - writeln!(f, ",")?; - } - - if let Some(ellipsis) = &self.ellipsis { - write!(f, "{indenter}{ellipsis}")?; - } - - indenter.dedent(); - write!(f, "{close}", close = self.close) +impl<'a> Parse<'a> for NewExpr<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + parse_token(lexer, Token::NewKeyword)?; + let package = PackageName::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let arguments = parse_delimited(lexer, &[Token::CloseBrace, Token::Ellipsis], true)?; + let ellipsis = parse_optional(lexer, Token::Ellipsis, |_| Ok(true))?.unwrap_or(false); + parse_token(lexer, Token::CloseBrace)?; + Ok(Self { + package, + arguments, + ellipsis, + }) } } -display!(NewExprBody); +impl Peek for NewExpr<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::NewKeyword) + } +} /// Represents an instantiation argument in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InstantiationArgument))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum InstantiationArgument<'a> { /// The argument is a named instantiation argument. @@ -157,43 +109,55 @@ pub enum InstantiationArgument<'a> { Ident(Ident<'a>), } -impl AstDisplay for InstantiationArgument<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - InstantiationArgument::Named(named) => named.fmt(f, indenter), - InstantiationArgument::Ident(ident) => ident.fmt(f, indenter), +impl<'a> Parse<'a> for InstantiationArgument<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if NamedInstantiationArgument::peek(&mut lookahead) { + // Peek again to see if this is really a named instantiation argument. + if let Some((Ok(Token::Colon), _)) = lexer.peek2() { + Ok(Self::Named(Parse::parse(lexer)?)) + } else { + Ok(Self::Ident(Parse::parse(lexer)?)) + } + } else { + Err(lookahead.error()) } } } -display!(InstantiationArgument); +impl Peek for InstantiationArgument<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + NamedInstantiationArgument::peek(lookahead) + } +} /// Represents a named instantiation argument in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NamedInstantiationArgument))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct NamedInstantiationArgument<'a> { /// The name of the argument. pub name: InstantiationArgumentName<'a>, - /// The colon in the argument. - pub colon: Colon<'a>, /// The expression in the argument. pub expr: Expr<'a>, } -impl AstDisplay for NamedInstantiationArgument<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.name.fmt(f, indenter)?; - write!(f, "{colon} ", colon = self.colon)?; - self.expr.fmt(f, indenter) +impl<'a> Parse<'a> for NamedInstantiationArgument<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let name = Parse::parse(lexer)?; + parse_token(lexer, Token::Colon)?; + let expr = Parse::parse(lexer)?; + Ok(Self { name, expr }) } } -display!(NamedInstantiationArgument); +impl Peek for NamedInstantiationArgument<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + InstantiationArgumentName::peek(lookahead) + } +} /// Represents the argument name in an instantiation argument in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InstantiationArgumentName))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum InstantiationArgumentName<'a> { /// The argument name is an identifier. @@ -202,53 +166,57 @@ pub enum InstantiationArgumentName<'a> { String(super::String<'a>), } -impl<'a> InstantiationArgumentName<'a> { - /// Gets the span of the instantiation argument name. - pub fn span(&self) -> Span<'a> { - match self { - InstantiationArgumentName::Ident(ident) => ident.0, - InstantiationArgumentName::String(string) => string.0, +impl<'a> Parse<'a> for InstantiationArgumentName<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else if super::String::peek(&mut lookahead) { + Ok(Self::String(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -impl AstDisplay for InstantiationArgumentName<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, _indenter: &mut Indenter) -> fmt::Result { +impl Peek for InstantiationArgumentName<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Ident::peek(lookahead) || super::String::peek(lookahead) + } +} + +impl<'a> InstantiationArgumentName<'a> { + /// Gets the span of the instantiation argument name. + pub fn span(&self) -> Span<'a> { match self { - InstantiationArgumentName::Ident(ident) => ident.fmt(f, _indenter), - InstantiationArgumentName::String(string) => string.fmt(f, _indenter), + Self::Ident(ident) => ident.span, + Self::String(string) => string.span, } } } -display!(InstantiationArgumentName); - /// Represents a nested expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NestedExpr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct NestedExpr<'a> { - /// The open paren in the expression. - pub open: OpenParen<'a>, - /// The nested expression. - pub expr: Box>, - /// The close paren in the expression. - pub close: CloseParen<'a>, +pub struct NestedExpr<'a>(pub Box>); + +impl<'a> Parse<'a> for NestedExpr<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + parse_token(lexer, Token::OpenParen)?; + let expr = Box::new(Parse::parse(lexer)?); + parse_token(lexer, Token::CloseParen)?; + Ok(Self(expr)) + } } -impl AstDisplay for NestedExpr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{open}", open = self.open)?; - self.expr.fmt(f, indenter)?; - write!(f, "{close}", close = self.close) +impl Peek for NestedExpr<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::OpenParen) } } -display!(NestedExpr); - /// Represents a postfix expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::PostfixExpr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum PostfixExpr<'a> { /// The postfix expression is an access expression. @@ -259,115 +227,81 @@ pub enum PostfixExpr<'a> { impl<'a> PostfixExpr<'a> { /// Gets the span of the postfix expression. - pub fn span(&self) -> pest::Span<'a> { - match self { - PostfixExpr::Access(access) => access.span(), - PostfixExpr::NamedAccess(access) => access.span(), - } - } -} - -impl AstDisplay for PostfixExpr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { + pub fn span(&self) -> Span<'a> { match self { - PostfixExpr::Access(access) => access.fmt(f, indenter), - PostfixExpr::NamedAccess(access) => access.fmt(f, indenter), + PostfixExpr::Access(access) => access.span, + PostfixExpr::NamedAccess(access) => access.span, } } } -display!(PostfixExpr); - /// Represents an access expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::AccessExpr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct AccessExpr<'a> { - /// The dot in the expression. - pub dot: Dot<'a>, + /// The span of the access expression. + pub span: Span<'a>, /// The identifier in the expression. pub id: Ident<'a>, } -impl<'a> AccessExpr<'a> { - /// Gets the span of the access expression. - pub fn span(&self) -> Span<'a> { - Span::new(self.dot.0.get_input(), self.dot.0.start(), self.id.0.end()).unwrap() +impl<'a> Parse<'a> for AccessExpr<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut span = parse_token(lexer, Token::Dot)?; + let id: Ident = Parse::parse(lexer)?; + span.end = id.span.end; + Ok(Self { span, id }) } } -impl AstDisplay for AccessExpr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, _indenter: &mut Indenter) -> fmt::Result { - write!(f, "{dot}{id}", dot = self.dot, id = self.id) +impl Peek for AccessExpr<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::Dot) } } -display!(AccessExpr); - /// Represents a named access expression in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NamedAccessExpr))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct NamedAccessExpr<'a> { - /// The open bracket in the expression. - pub open: OpenBracket<'a>, + /// The span of the access expression. + pub span: Span<'a>, /// The name string in the expression. pub string: super::String<'a>, - /// The close bracket in the expression. - pub close: CloseBracket<'a>, } -impl<'a> NamedAccessExpr<'a> { - /// Gets the span of the access expression. - pub fn span(&self) -> Span<'a> { - Span::new( - self.open.0.get_input(), - self.open.0.start(), - self.close.0.end(), - ) - .unwrap() +impl<'a> Parse<'a> for NamedAccessExpr<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut span = parse_token(lexer, Token::OpenBracket)?; + let string = Parse::parse(lexer)?; + let closing = parse_token(lexer, Token::CloseBracket)?; + span.end = closing.end; + Ok(Self { span, string }) } } -impl AstDisplay for NamedAccessExpr<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, _indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{open}{string}{close}", - open = self.open, - string = self.string, - close = self.close - ) +impl Peek for NamedAccessExpr<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::OpenBracket) } } -display!(NamedAccessExpr); - #[cfg(test)] mod test { use super::*; - use crate::{ast::test::roundtrip, parser::Rule}; + use crate::ast::test::roundtrip; #[test] fn primary_expr_roundtrip() { - roundtrip::(Rule::Expr, "x", "x").unwrap(); - roundtrip::(Rule::Expr, "y.x.z", "y.x.z").unwrap(); - roundtrip::(Rule::Expr, "y[\"x\"][\"z\"]", "y[\"x\"][\"z\"]").unwrap(); - roundtrip::( - Rule::Expr, - "foo[\"bar\"].baz[\"qux\"]", - "foo[\"bar\"].baz[\"qux\"]", - ) - .unwrap(); - - roundtrip::(Rule::Expr, "(foo-bar-baz)", "(foo-bar-baz)").unwrap(); - - roundtrip::(Rule::Expr, "new foo:bar {}", "new foo:bar {}").unwrap(); - + roundtrip::("x", "x").unwrap(); + roundtrip::("y.x.z", "y.x.z").unwrap(); + roundtrip::("y[\"x\"][\"z\"]", "y[\"x\"][\"z\"]").unwrap(); + roundtrip::("foo[\"bar\"].baz[\"qux\"]", "foo[\"bar\"].baz[\"qux\"]").unwrap(); + roundtrip::("(foo-bar-baz)", "(foo-bar-baz)").unwrap(); + roundtrip::("new foo:bar {}", "new foo:bar {}").unwrap(); roundtrip::( - Rule::Expr, "new foo:bar { foo, \"bar\": (new baz:qux {...}), \"baz\": foo[\"baz\"].qux }", - "new foo:bar {\n foo,\n \"bar\": (new baz:qux { ... }),\n \"baz\": foo[\"baz\"].qux,\n}", + "new foo:bar {\n foo,\n \"bar\": (new baz:qux { ... }),\n \"baz\": foo[\"baz\"].qux,\n}", ) .unwrap(); } diff --git a/crates/wac-parser/src/ast/import.rs b/crates/wac-parser/src/ast/import.rs index d5d9bcc..372cde7 100644 --- a/crates/wac-parser/src/ast/import.rs +++ b/crates/wac-parser/src/ast/import.rs @@ -1,62 +1,47 @@ use super::{ - display, - keywords::{Import, With}, - serialize_span, - symbols::{At, Colon, Semicolon, Slash}, - AstDisplay, FuncType, Ident, Indenter, InlineInterface, + display, parse_optional, parse_token, DocComment, FuncType, Ident, InlineInterface, Lookahead, + Parse, ParseResult, Peek, }; -use crate::parser::Rule; -use anyhow::Result; -use pest::Span; -use pest_ast::FromPest; -use semver::Version; +use crate::lexer::{Lexer, Span, Token}; use serde::Serialize; -use std::fmt; /// Represents an import statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ImportStatement))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ImportStatement<'a> { - /// The import keyword in the statement. - pub keyword: Import<'a>, + /// The doc comments for the statement. + pub docs: Vec>, /// The identifier of the imported item. pub id: Ident<'a>, - /// The optional with clause. - pub with: Option>, - /// The colon in the statement. - pub colon: Colon<'a>, + /// The optional `with` string. + pub with: Option>, /// The type of the imported item. pub ty: ImportType<'a>, - /// The semicolon in the statement. - pub semicolon: Semicolon<'a>, } -impl AstDisplay for ImportStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{indenter}{keyword} {id}", - keyword = self.keyword, - id = self.id - )?; - - if let Some(with) = &self.with { - write!(f, " ")?; - with.fmt(f, indenter)?; - } +impl<'a> Parse<'a> for ImportStatement<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::ImportKeyword)?; + let id = Parse::parse(lexer)?; + let with = parse_optional(lexer, Token::WithKeyword, Parse::parse)?; + parse_token(lexer, Token::Colon)?; + let ty = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, id, with, ty }) + } +} - write!(f, "{colon} ", colon = self.colon)?; - self.ty.fmt(f, indenter)?; - write!(f, "{semi}", semi = self.semicolon) +impl Peek for ImportStatement<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::ImportKeyword) } } -display!(ImportStatement); +display!(ImportStatement, import_statement); /// Represents an import type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ImportType))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum ImportType<'a> { /// The import type is from a package path. @@ -69,190 +54,113 @@ pub enum ImportType<'a> { Ident(Ident<'a>), } -impl AstDisplay for ImportType<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Package(pkg) => pkg.fmt(f, indenter), - Self::Func(ty) => ty.fmt(f, indenter), - Self::Interface(ty) => ty.fmt(f, indenter), - Self::Ident(id) => id.fmt(f, indenter), +impl<'a> Parse<'a> for ImportType<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if FuncType::peek(&mut lookahead) { + Ok(Self::Func(Parse::parse(lexer)?)) + } else if InlineInterface::peek(&mut lookahead) { + Ok(Self::Interface(Parse::parse(lexer)?)) + } else if PackagePath::peek(&mut lookahead) { + Ok(Self::Package(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(ImportType); - -/// Represents a `with` clause in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WithClause))] -#[serde(rename_all = "camelCase")] -pub struct WithClause<'a> { - /// The `with` keyword in the clause. - pub keyword: With<'a>, - /// The name to import with. - pub name: super::String<'a>, -} - -impl AstDisplay for WithClause<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword,)?; - self.name.fmt(f, indenter) - } -} - -display!(WithClause); - /// Represents a package path in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::PackagePath))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct PackagePath<'a> { + /// The span of the package path. + pub span: Span<'a>, /// The name of the package. - pub name: PackageName<'a>, + pub name: &'a str, /// The path segments. - pub segments: Vec<(Slash<'a>, Ident<'a>)>, + pub segments: &'a str, /// The optional version of the package. - pub version: Option<(At<'a>, PackageVersion<'a>)>, + pub version: Option<&'a str>, } -impl PackagePath<'_> { - /// Calculates the span of the package path. - pub fn span(&self) -> Span { - let span = self.name.span(); - let end = if let Some((_, version)) = &self.version { - version.0.end() - } else { - self.segments_span().end() - }; - - Span::new(span.get_input(), span.start(), end).unwrap() - } - - /// Returns the string representation of the package path. - pub fn as_str(&self) -> &str { - self.span().as_str() +impl<'a> PackagePath<'a> { + /// Gets the span of only the package name. + pub fn package_name_span(&self) -> Span<'a> { + Span::from_span( + self.span.source(), + &(self.span.start..self.span.start + self.name.len()), + ) } - /// Gets the span of just the path segments. - pub fn segments_span(&self) -> Span { - let (_, first) = self.segments.first().unwrap(); - let (_, last) = self.segments.last().unwrap(); - Span::new(first.0.get_input(), first.0.start(), last.0.end()).unwrap() + /// Iterates over the segments of the package path. + pub fn segment_spans<'b>(&'b self) -> impl Iterator)> + 'b { + self.segments.split('/').map(|s| { + let start = s.as_ptr() as usize - self.name.as_ptr() as usize; + let end = start + s.len(); + (s, Span::from_span(self.span.source(), &(start..end))) + }) } +} - /// Gets the path segments as a string. - pub fn segments(&self) -> &str { - self.segments_span().as_str() +impl<'a> Parse<'a> for PackagePath<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let span = parse_token(lexer, Token::PackagePath)?; + let s = span.as_str(); + let slash = s.find('/').unwrap(); + let at = s.find('@'); + let name = &s[..slash]; + let segments = &s[slash + 1..at.unwrap_or(slash + s.len() - name.len())]; + let version = at.map(|at| &s[at + 1..]); + + Ok(Self { + span, + name, + segments, + version, + }) } } -impl AstDisplay for PackagePath<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.name.fmt(f, indenter)?; - - for (slash, segment) in &self.segments { - write!(f, "{slash}")?; - segment.fmt(f, indenter)?; - } - - if let Some((at, version)) = &self.version { - write!(f, "{at}")?; - version.fmt(f, indenter)?; - } - - Ok(()) +impl Peek for PackagePath<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::PackagePath) } } -display!(PackagePath); - /// Represents a package name in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::PackageName))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct PackageName<'a> { - /// The parts of the package name. - pub parts: Vec>, -} - -impl PackageName<'_> { - /// Gets the span of the package name. - pub fn span(&self) -> Span { - let span = self.parts.first().unwrap().0; - let end = self.parts.last().map(|i| i.0.end()).unwrap(); - Span::new(span.get_input(), span.start(), end).unwrap() - } - - /// Gets the package name as a string. - pub fn as_str(&self) -> &str { - self.span().as_str() - } -} - -impl AstDisplay for PackageName<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - for (i, part) in self.parts.iter().enumerate() { - if i > 0 { - write!(f, ":")?; - } - - part.fmt(f, indenter)?; - } - - Ok(()) - } -} - -display!(PackageName); - -/// Represents a package version in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::PackageVersion))] -#[serde(rename_all = "camelCase")] -pub struct PackageVersion<'a>( - #[pest_ast(outer())] - #[serde(serialize_with = "serialize_span")] - pub Span<'a>, -); - -impl PackageVersion<'_> { - /// Returns the string representation of the package version. - pub fn as_str(&self) -> &str { - self.0.as_str() - } - - /// Returns the package version as a `semver::Version`. - /// - /// Returns an error if the package version is not a valid semver version. - pub fn as_version(&self) -> Result { - Ok(Version::parse(self.as_str())?) - } + /// The name of the package. + pub name: &'a str, + /// The span of the package name, + pub span: Span<'a>, } -impl AstDisplay for PackageVersion<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, _indenter: &mut Indenter) -> fmt::Result { - write!(f, "{version}", version = self.0.as_str()) +impl<'a> Parse<'a> for PackageName<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let span = parse_token(lexer, Token::PackageName)?; + let name = span.as_str(); + Ok(Self { name, span }) } } -display!(PackageVersion); - #[cfg(test)] mod test { use super::*; - use crate::{ast::test::roundtrip, parser::Rule}; + use crate::ast::test::roundtrip; #[test] fn import_via_package_roundtrip() { roundtrip::( - Rule::ImportStatement, "import x: foo:bar:baz/qux/jam@1.2.3-preview+abc;", "import x: foo:bar:baz/qux/jam@1.2.3-preview+abc;", ) .unwrap(); roundtrip::( - Rule::ImportStatement, "import x with \"y\": foo:bar:baz/qux/jam@1.2.3-preview+abc;", "import x with \"y\": foo:bar:baz/qux/jam@1.2.3-preview+abc;", ) @@ -262,14 +170,12 @@ mod test { #[test] fn import_function_roundtrip() { roundtrip::( - Rule::ImportStatement, "import x: func(x: string) -> string;", "import x: func(x: string) -> string;", ) .unwrap(); roundtrip::( - Rule::ImportStatement, "import x with \"foo\": func(x: string) -> string;", "import x with \"foo\": func(x: string) -> string;", ) @@ -279,27 +185,23 @@ mod test { #[test] fn import_interface_roundtrip() { roundtrip::( - Rule::ImportStatement, "import x: interface { x: func(x: string) -> string; };", - "import x: interface {\n x: func(x: string) -> string;\n};", + "import x: interface {\n x: func(x: string) -> string;\n};", ) .unwrap(); roundtrip::( - Rule::ImportStatement, "import x with \"foo\": interface { x: func(x: string) -> string; };", - "import x with \"foo\": interface {\n x: func(x: string) -> string;\n};", + "import x with \"foo\": interface {\n x: func(x: string) -> string;\n};", ) .unwrap(); } #[test] fn import_via_ident_roundtrip() { - roundtrip::(Rule::ImportStatement, "import x: y;", "import x: y;") - .unwrap(); + roundtrip::("import x: y;", "import x: y;").unwrap(); roundtrip::( - Rule::ImportStatement, "import x /*foo */ with \"foo\": y;", "import x with \"foo\": y;", ) diff --git a/crates/wac-parser/src/ast/keywords.rs b/crates/wac-parser/src/ast/keywords.rs deleted file mode 100644 index e57a8c2..0000000 --- a/crates/wac-parser/src/ast/keywords.rs +++ /dev/null @@ -1,78 +0,0 @@ -//! The keywords in the AST. - -use super::serialize_span; -use crate::parser::Rule; -use pest::Span; -use pest_ast::FromPest; -use serde::Serialize; -use std::fmt; - -macro_rules! keywords { - ($(($id:ident, $rule:path, $name:literal)),* $(,)?) => { - $( - #[doc = concat!("Represents the `", $name, "` keyword in the AST.")] - #[derive(Debug, Clone, Copy, Serialize, FromPest)] - #[pest_ast(rule($rule))] - pub struct $id<'a>(#[pest_ast(outer())] #[serde(serialize_with = "serialize_span")] pub Span<'a>); - - impl fmt::Display for $id<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{span}", span = self.0.as_str()) - } - } - )* - - #[cfg(test)] - mod test { - use super::*; - - $( - #[test] - #[allow(non_snake_case)] - fn $id() { - crate::ast::test::roundtrip::<$id>($rule, $name, $name).unwrap(); - } - )* - } - }; -} - -keywords!( - (Import, Rule::ImportKeyword, "import"), - (With, Rule::WithKeyword, "with"), - (Type, Rule::TypeKeyword, "type"), - (Tuple, Rule::TupleKeyword, "tuple"), - (List, Rule::ListKeyword, "list"), - (Option, Rule::OptionKeyword, "option"), - (Result, Rule::ResultKeyword, "result"), - (Borrow, Rule::BorrowKeyword, "borrow"), - (Resource, Rule::ResourceKeyword, "resource"), - (Variant, Rule::VariantKeyword, "variant"), - (Record, Rule::RecordKeyword, "record"), - (Flags, Rule::FlagsKeyword, "flags"), - (Enum, Rule::EnumKeyword, "enum"), - (Func, Rule::FuncKeyword, "func"), - (Static, Rule::StaticKeyword, "static"), - (Constructor, Rule::ConstructorKeyword, "constructor"), - (U8, Rule::U8Keyword, "u8"), - (S8, Rule::S8Keyword, "s8"), - (U16, Rule::U16Keyword, "u16"), - (S16, Rule::S16Keyword, "s16"), - (U32, Rule::U32Keyword, "u32"), - (S32, Rule::S32Keyword, "s32"), - (U64, Rule::U64Keyword, "u64"), - (S64, Rule::S64Keyword, "s64"), - (Float32, Rule::Float32Keyword, "float32"), - (Float64, Rule::Float64Keyword, "float64"), - (Char, Rule::CharKeyword, "char"), - (Bool, Rule::BoolKeyword, "bool"), - (String, Rule::StringKeyword, "string"), - (Interface, Rule::InterfaceKeyword, "interface"), - (World, Rule::WorldKeyword, "world"), - (Export, Rule::ExportKeyword, "export"), - (New, Rule::NewKeyword, "new"), - (Let, Rule::LetKeyword, "let"), - (Use, Rule::UseKeyword, "use"), - (Include, Rule::IncludeKeyword, "include"), - (As, Rule::AsKeyword, "as"), -); diff --git a/crates/wac-parser/src/ast/let.rs b/crates/wac-parser/src/ast/let.rs index b5ec21d..7824fb4 100644 --- a/crates/wac-parser/src/ast/let.rs +++ b/crates/wac-parser/src/ast/let.rs @@ -1,84 +1,63 @@ -use super::{ - display, - keywords::Let, - symbols::{Equals, Semicolon}, - AstDisplay, Expr, Ident, Indenter, -}; -use crate::parser::Rule; -use pest_ast::FromPest; +use crate::lexer::{Lexer, Token}; + +use super::{display, parse_token, DocComment, Expr, Ident, Lookahead, Parse, ParseResult, Peek}; use serde::Serialize; -use std::fmt; /// Represents a let statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::LetStatement))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct LetStatement<'a> { - /// The let keyword in the statement. - pub keyword: Let<'a>, + /// The doc comments for the statement. + pub docs: Vec>, /// The newly bound identifier. pub id: Ident<'a>, - /// The equals sign in the statement. - pub equals: Equals<'a>, /// The expression being bound. pub expr: Expr<'a>, - /// The semicolon in the statement. - pub semicolon: Semicolon<'a>, } -impl AstDisplay for LetStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{indenter}{keyword} {id} {equals} ", - keyword = self.keyword, - id = self.id, - equals = self.equals, - )?; - - self.expr.fmt(f, indenter)?; +impl<'a> Parse<'a> for LetStatement<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::LetKeyword)?; + let id = Parse::parse(lexer)?; + parse_token(lexer, Token::Equals)?; + let expr = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, id, expr }) + } +} - write!(f, "{semi}", semi = self.semicolon) +impl Peek for LetStatement<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::LetKeyword) } } -display!(LetStatement); +display!(LetStatement, let_statement); #[cfg(test)] mod test { use super::*; - use crate::{ast::test::roundtrip, parser::Rule}; + use crate::ast::test::roundtrip; #[test] fn let_statement_roundtrip() { - roundtrip::(Rule::LetStatement, "let x= y;", "let x = y;").unwrap(); - roundtrip::(Rule::LetStatement, "let x =y.x.z;", "let x = y.x.z;").unwrap(); - roundtrip::( - Rule::LetStatement, - "let x=y[\"x\"][\"z\"];", - "let x = y[\"x\"][\"z\"];", - ) - .unwrap(); + roundtrip::("let x= y;", "let x = y;").unwrap(); + roundtrip::("let x =y.x.z;", "let x = y.x.z;").unwrap(); + roundtrip::("let x=y[\"x\"][\"z\"];", "let x = y[\"x\"][\"z\"];").unwrap(); roundtrip::( - Rule::LetStatement, "let x = foo[\"bar\"].baz[\"qux\"];", "let x = foo[\"bar\"].baz[\"qux\"];", ) .unwrap(); - roundtrip::(Rule::LetStatement, "let x = (y);", "let x = (y);").unwrap(); + roundtrip::("let x = (y);", "let x = (y);").unwrap(); - roundtrip::( - Rule::LetStatement, - "let x = new foo:bar {};", - "let x = new foo:bar {};", - ) - .unwrap(); + roundtrip::("let x = new foo:bar {};", "let x = new foo:bar {};").unwrap(); roundtrip::( - Rule::LetStatement, "let x = new foo:bar { foo, \"bar\": (new baz:qux {...}), \"baz\": foo[\"baz\"].qux };", - "let x = new foo:bar {\n foo,\n \"bar\": (new baz:qux { ... }),\n \"baz\": foo[\"baz\"].qux,\n};", + "let x = new foo:bar {\n foo,\n \"bar\": (new baz:qux { ... }),\n \"baz\": foo[\"baz\"].qux,\n};", ) .unwrap(); } diff --git a/crates/wac-parser/src/ast/symbols.rs b/crates/wac-parser/src/ast/symbols.rs deleted file mode 100644 index 12193b3..0000000 --- a/crates/wac-parser/src/ast/symbols.rs +++ /dev/null @@ -1,61 +0,0 @@ -//! The symbols in the AST. - -use super::serialize_span; -use crate::parser::Rule; -use pest::Span; -use pest_ast::FromPest; -use serde::Serialize; -use std::fmt; - -macro_rules! symbols { - ($(($id:ident, $name:literal)),* $(,)?) => { - $( - #[doc = concat!("Represents the `", $name, "` symbol in the AST.")] - #[derive(Debug, Clone, Copy, Serialize, FromPest)] - #[pest_ast(rule(Rule::$id))] - pub struct $id<'a>(#[pest_ast(outer())] #[serde(serialize_with = "serialize_span")] pub Span<'a>); - - impl fmt::Display for $id<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{span}", span = self.0.as_str()) - } - } - )* - - #[cfg(test)] - mod test { - use super::*; - - $( - #[test] - #[allow(non_snake_case)] - fn $id() { - crate::ast::test::roundtrip::<$id>(Rule::$id, $name, $name).unwrap(); - } - )* - } - }; -} - -symbols!( - (Semicolon, ";"), - (OpenBrace, "{"), - (CloseBrace, "}"), - (Colon, ":"), - (Equals, "="), - (OpenParen, "("), - (CloseParen, ")"), - (Arrow, "->"), - (OpenAngle, "<"), - (CloseAngle, ">"), - (Percent, "%"), - (Underscore, "_"), - (Hyphen, "-"), - (DoubleQuote, "\""), - (Slash, "/"), - (At, "@"), - (OpenBracket, "["), - (CloseBracket, "]"), - (Dot, "."), - (Ellipsis, "..."), -); diff --git a/crates/wac-parser/src/ast/type.rs b/crates/wac-parser/src/ast/type.rs index ebd86f9..cb83724 100644 --- a/crates/wac-parser/src/ast/type.rs +++ b/crates/wac-parser/src/ast/type.rs @@ -1,24 +1,12 @@ use super::{ - display, - keywords::{ - self, As, Bool, Enum, Export, Flags, Float32, Float64, Func, Import, Include, Interface, - Record, Resource, Static, Use, Variant, With, World, S16, S32, S64, S8, U16, U32, U64, U8, - }, - symbols::{ - Arrow, CloseAngle, CloseBrace, CloseParen, Colon, Dot, Equals, OpenAngle, OpenBrace, - OpenParen, Semicolon, Underscore, - }, - AstDisplay, DocComment, Ident, Indenter, PackagePath, + display, parse_delimited, parse_optional, parse_token, DocComment, Error, Ident, Lookahead, + PackagePath, Parse, ParseResult, Peek, }; -use crate::parser::Rule; -use pest::Span; -use pest_ast::FromPest; +use crate::lexer::{Lexer, Span, Token}; use serde::Serialize; -use std::fmt; /// Represents a type statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::TypeStatement))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum TypeStatement<'a> { /// The statement is for an interface declaration. @@ -29,27 +17,38 @@ pub enum TypeStatement<'a> { Type(TypeDecl<'a>), } -impl AstDisplay for TypeStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}")?; - - match self { - Self::Interface(interface) => interface.fmt(f, indenter), - Self::World(world) => world.fmt(f, indenter), - Self::Type(ty) => ty.fmt(f, indenter), +impl<'a> Parse<'a> for TypeStatement<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if InterfaceDecl::peek(&mut lookahead) { + Ok(Self::Interface(Parse::parse(lexer)?)) + } else if WorldDecl::peek(&mut lookahead) { + Ok(Self::World(Parse::parse(lexer)?)) + } else if TypeDecl::peek(&mut lookahead) { + Ok(Self::Type(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(TypeStatement); +impl Peek for TypeStatement<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::InterfaceKeyword) + || lookahead.peek(Token::WorldKeyword) + || TypeDecl::peek(lookahead) + } +} -/// Represents a type declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::TypeDecl))] +display!(TypeStatement, type_statement); + +/// Represents a top-level type declaration in the AST. +/// +/// Unlike tin interfaces and worlds, resources cannot +/// be declared at the top-level. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum TypeDecl<'a> { - /// The declaration is for a resource. - Resource(ResourceDecl<'a>), /// The declaration is for a variant. Variant(VariantDecl<'a>), /// The declaration is for a record. @@ -62,371 +61,312 @@ pub enum TypeDecl<'a> { Alias(TypeAlias<'a>), } -impl TypeDecl<'_> { - /// Gets the identifier of the type being declared. - pub fn id(&self) -> Ident { - match self { - Self::Resource(resource) => resource.id, - Self::Variant(variant) => variant.id, - Self::Record(record) => record.id, - Self::Flags(flags) => flags.id, - Self::Enum(e) => e.id, - Self::Alias(alias) => alias.id, +impl<'a> Parse<'a> for TypeDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::VariantKeyword) { + Ok(Self::Variant(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::RecordKeyword) { + Ok(Self::Record(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::FlagsKeyword) { + Ok(Self::Flags(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::EnumKeyword) { + Ok(Self::Enum(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::TypeKeyword) { + Ok(Self::Alias(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -impl AstDisplay for TypeDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}")?; - - match self { - Self::Resource(resource) => resource.fmt(f, indenter), - Self::Variant(variant) => variant.fmt(f, indenter), - Self::Record(record) => record.fmt(f, indenter), - Self::Flags(flags) => flags.fmt(f, indenter), - Self::Enum(e) => e.fmt(f, indenter), - Self::Alias(alias) => alias.fmt(f, indenter), - } +impl Peek for TypeDecl<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::VariantKeyword) + || lookahead.peek(Token::RecordKeyword) + || lookahead.peek(Token::FlagsKeyword) + || lookahead.peek(Token::EnumKeyword) + || lookahead.peek(Token::TypeKeyword) } } -display!(TypeDecl); - /// Represents a resource declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ResourceDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ResourceDecl<'a> { - /// The `resource` keyword in the declaration. - pub keyword: Resource<'a>, + /// The doc comments for the resource. + pub docs: Vec>, /// The identifier of the resource. pub id: Ident<'a>, - /// The body of the resource. - pub body: ResourceBody<'a>, + /// The methods of the resource. + pub methods: Vec>, } -impl AstDisplay for ResourceDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) - } -} - -display!(ResourceDecl); - -/// Represents a resource body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ResourceBody))] -#[serde(rename_all = "camelCase")] -pub enum ResourceBody<'a> { - /// The resource body is empty. - Empty(Semicolon<'a>), - /// The methods of the resource body. - Methods { - /// The opening brace of the resource body. - open: OpenBrace<'a>, - /// The methods of the resource body. - methods: Vec<(ResourceMethod<'a>, Semicolon<'a>)>, - /// The closing brace of the resource body. - close: CloseBrace<'a>, - }, -} +impl<'a> Parse<'a> for ResourceDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::ResourceKeyword)?; + let id = Ident::parse(lexer)?; + let mut lookahead = Lookahead::new(lexer); + let methods = if lookahead.peek(Token::Semicolon) { + lexer.next(); + Default::default() + } else if lookahead.peek(Token::OpenBrace) { + parse_token(lexer, Token::OpenBrace)?; + let methods = parse_delimited(lexer, &[Token::CloseBrace], false)?; + parse_token(lexer, Token::CloseBrace)?; + methods + } else { + return Err(lookahead.error()); + }; -impl AstDisplay for ResourceBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Empty(semicolon) => write!(f, "{semicolon}"), - Self::Methods { - open, - methods, - close, - } => { - writeln!(f, " {open}")?; - - indenter.indent(); - for (method, semicolon) in methods { - write!(f, "{indenter}")?; - method.fmt(f, indenter)?; - writeln!(f, "{semicolon}")?; - } - - indenter.dedent(); - write!(f, "{indenter}{close}") - } - } + Ok(Self { docs, id, methods }) } } -display!(ResourceBody); - /// Represents a variant declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::VariantDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct VariantDecl<'a> { - /// The `variant` keyword in the declaration. - pub keyword: Variant<'a>, + /// The doc comments for the variant. + pub docs: Vec>, /// The identifier of the variant. pub id: Ident<'a>, - /// The body of the variant. - pub body: VariantBody<'a>, -} - -impl AstDisplay for VariantDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) - } -} - -display!(VariantDecl); - -/// Represents a variant body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::VariantBody))] -#[serde(rename_all = "camelCase")] -pub struct VariantBody<'a> { - /// The opening brace of the variant body. - pub open: OpenBrace<'a>, - /// The cases of the variant body. + /// The cases of the variant. pub cases: Vec>, - /// The closing brace of the variant body. - pub close: CloseBrace<'a>, } -impl AstDisplay for VariantBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - writeln!(f, " {open}", open = self.open)?; - - indenter.indent(); - for case in &self.cases { - write!(f, "{indenter}")?; - case.fmt(f, indenter)?; - writeln!(f, ",")?; +impl<'a> Parse<'a> for VariantDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::VariantKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let cases = parse_delimited(lexer, &[Token::CloseBrace], true)?; + let close = parse_token(lexer, Token::CloseBrace)?; + + if cases.is_empty() { + return Err(Error::EmptyType { + ty: "variant", + kind: "case", + span: close, + }); } - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) + Ok(Self { docs, id, cases }) } } -display!(VariantBody); - /// Represents a variant case in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::VariantCase))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct VariantCase<'a> { + /// The doc comments for the case. + pub docs: Vec>, /// The identifier of the case. pub id: Ident<'a>, /// The type of the case. - pub ty: std::option::Option>, + pub ty: Option>, } -impl AstDisplay for VariantCase<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.id.fmt(f, indenter)?; - if let Some(ty) = &self.ty { - ty.fmt(f, indenter)?; - } - - Ok(()) +impl<'a> Parse<'a> for VariantCase<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let id = Ident::parse(lexer)?; + let ty = parse_optional(lexer, Token::OpenParen, |lexer| { + let ty = Parse::parse(lexer)?; + parse_token(lexer, Token::CloseParen)?; + Ok(ty) + })?; + Ok(Self { docs, id, ty }) } } -display!(VariantCase); - -/// Represents a variant type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::VariantType))] -#[serde(rename_all = "camelCase")] -pub struct VariantType<'a> { - /// The opening parenthesis of the variant type. - pub open: OpenParen<'a>, - /// The type of the variant. - pub ty: Type<'a>, - /// The closing parenthesis of the variant type. - pub close: CloseParen<'a>, -} - -impl AstDisplay for VariantType<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{open}", open = self.open)?; - self.ty.fmt(f, indenter)?; - write!(f, "{close}", close = self.close) +impl Peek for VariantCase<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::Ident) } } -display!(VariantType); - /// Represents a record declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::RecordDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct RecordDecl<'a> { - /// The `record` keyword in the declaration. - pub keyword: Record<'a>, + /// The doc comments for the record. + pub docs: Vec>, /// The identifier of the record. pub id: Ident<'a>, - /// The body of the record. - pub body: RecordBody<'a>, -} + /// The fields of the record. + pub fields: Vec>, +} + +impl<'a> Parse<'a> for RecordDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::RecordKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let fields = parse_delimited(lexer, &[Token::CloseBrace], true)?; + let close = parse_token(lexer, Token::CloseBrace)?; + + if fields.is_empty() { + return Err(Error::EmptyType { + ty: "record", + kind: "field", + span: close, + }); + } -impl AstDisplay for RecordDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) + Ok(Self { docs, id, fields }) } } -display!(RecordDecl); - -/// Represents a record body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::RecordBody))] +/// Represents a record field in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct RecordBody<'a> { - /// The opening brace of the record body. - pub open: OpenBrace<'a>, - /// The fields of the record body. - pub fields: Vec>, - /// The closing brace of the record body. - pub close: CloseBrace<'a>, -} - -impl AstDisplay for RecordBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - writeln!(f, " {open}", open = self.open)?; - - indenter.indent(); - for field in &self.fields { - write!(f, "{indenter}")?; - field.fmt(f, indenter)?; - writeln!(f, ",")?; - } +pub struct Field<'a> { + /// The docs for the field. + pub docs: Vec>, + /// The identifier of the field. + pub id: Ident<'a>, + /// The type of the field. + pub ty: Type<'a>, +} - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) +impl<'a> Parse<'a> for Field<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let named: NamedType = Parse::parse(lexer)?; + Ok(Self { + docs, + id: named.id, + ty: named.ty, + }) } } -display!(RecordBody); +impl Peek for Field<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + NamedType::peek(lookahead) + } +} /// Represents a flags declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::FlagsDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct FlagsDecl<'a> { - /// The `flags` keyword in the declaration. - pub keyword: Flags<'a>, + /// The doc comments for the flags. + pub docs: Vec>, /// The identifier of the flags. pub id: Ident<'a>, - /// The body of the flags. - pub body: FlagsBody<'a>, -} + /// The flag values. + pub flags: Vec>, +} + +impl<'a> Parse<'a> for FlagsDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::FlagsKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let flags = parse_delimited(lexer, &[Token::CloseBrace], true)?; + let close = parse_token(lexer, Token::CloseBrace)?; + + if flags.is_empty() { + return Err(Error::EmptyType { + ty: "flags", + kind: "flag", + span: close, + }); + } -impl AstDisplay for FlagsDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) + Ok(Self { docs, id, flags }) } } -display!(FlagsDecl); - -/// Represents a flags body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::FlagsBody))] +/// Represents a flag in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct FlagsBody<'a> { - /// The opening brace of the flags body. - pub open: OpenBrace<'a>, - /// The flags of the body. - pub flags: Vec>, - /// The closing brace of the flags body. - pub close: CloseBrace<'a>, -} - -impl AstDisplay for FlagsBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - writeln!(f, " {open}", open = self.open)?; - - indenter.indent(); - for flag in &self.flags { - write!(f, "{indenter}")?; - flag.fmt(f, indenter)?; - writeln!(f, ",")?; - } +pub struct Flag<'a> { + /// The doc comments for the flag. + pub docs: Vec>, + /// The identifier of the flag. + pub id: Ident<'a>, +} - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) +impl<'a> Parse<'a> for Flag<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let id = Ident::parse(lexer)?; + Ok(Self { docs, id }) } } -display!(FlagsBody); +impl Peek for Flag<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::Ident) + } +} /// Represents an enum declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::EnumDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct EnumDecl<'a> { - /// The `enum` keyword in the declaration. - pub keyword: Enum<'a>, + /// The doc comments for the enum. + pub docs: Vec>, /// The identifier of the enum. pub id: Ident<'a>, - /// The body of the enum. - pub body: EnumBody<'a>, -} + /// The cases of the enum. + pub cases: Vec>, +} + +impl<'a> Parse<'a> for EnumDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::EnumKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let cases = parse_delimited(lexer, &[Token::CloseBrace], true)?; + let close = parse_token(lexer, Token::CloseBrace)?; + + if cases.is_empty() { + return Err(Error::EmptyType { + ty: "enum", + kind: "case", + span: close, + }); + } -impl AstDisplay for EnumDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) + Ok(Self { docs, id, cases }) } } -display!(EnumDecl); - -/// Represents an enum body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::EnumBody))] +/// Represents an enum case in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct EnumBody<'a> { - /// The opening brace of the enum body. - pub open: OpenBrace<'a>, - /// The enum cases of the body. - pub cases: Vec>, - /// The closing brace of the enum body. - pub close: CloseBrace<'a>, -} - -impl AstDisplay for EnumBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - writeln!(f, " {open}", open = self.open)?; - - indenter.indent(); - for case in &self.cases { - write!(f, "{indenter}")?; - case.fmt(f, indenter)?; - writeln!(f, ",")?; - } +pub struct EnumCase<'a> { + /// The doc comments for the enum case. + pub docs: Vec>, + /// The identifier of the enum case. + pub id: Ident<'a>, +} - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) +impl<'a> Parse<'a> for EnumCase<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let id = Ident::parse(lexer)?; + Ok(Self { docs, id }) } } -display!(EnumBody); +impl Peek for EnumCase<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::Ident) + } +} /// Represents a resource method in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ResourceMethod))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum ResourceMethod<'a> { /// The method is a constructor. @@ -435,70 +375,90 @@ pub enum ResourceMethod<'a> { Method(Method<'a>), } -impl AstDisplay for ResourceMethod<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Constructor(constructor) => constructor.fmt(f, indenter), - Self::Method(method) => method.fmt(f, indenter), +impl<'a> Parse<'a> for ResourceMethod<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::ConstructorKeyword) { + Ok(Self::Constructor(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Method(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(ResourceMethod); +impl Peek for ResourceMethod<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::ConstructorKeyword) || Ident::peek(lookahead) + } +} /// Represents a resource constructor in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Constructor))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct Constructor<'a> { - /// The `constructor` keyword. - pub keyword: keywords::Constructor<'a>, + /// The doc comments for the constructor. + pub docs: Vec>, + /// The span of the constructor keyword. + pub span: Span<'a>, /// The parameters of the constructor. - pub params: ParamList<'a>, + pub params: Vec>, } -impl AstDisplay for Constructor<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword}", keyword = self.keyword)?; - self.params.fmt(f, indenter) +impl<'a> Parse<'a> for Constructor<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let span = parse_token(lexer, Token::ConstructorKeyword)?; + parse_token(lexer, Token::OpenParen)?; + let params = parse_delimited(lexer, &[Token::CloseParen], true)?; + parse_token(lexer, Token::CloseParen)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, span, params }) } } -display!(Constructor); - /// Represents a resource method in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Method))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct Method<'a> { + /// The doc comments for the method. + pub docs: Vec>, /// The identifier of the method. pub id: Ident<'a>, - /// The colon between the identifier and the type. - pub colon: Colon<'a>, - /// The static keyword that is present for static methods. - pub keyword: std::option::Option>, + /// Wether or not the method is static. + pub is_static: bool, /// The function type reference. - pub func: FuncTypeRef<'a>, + pub ty: FuncTypeRef<'a>, } -impl AstDisplay for Method<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.id.fmt(f, indenter)?; - write!(f, "{colon} ", colon = self.colon)?; +impl<'a> Parse<'a> for Method<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::Colon)?; + let is_static = lexer + .peek() + .map(|(r, _)| matches!(r, Ok(Token::StaticKeyword))) + .unwrap_or(false); - if let Some(keyword) = &self.keyword { - write!(f, "{keyword} ")?; + if is_static { + lexer.next(); } - self.func.fmt(f, indenter) + let ty = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { + docs, + id, + is_static, + ty, + }) } } -display!(Method); - /// Represents a function type reference in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::FuncTypeRef))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum FuncTypeRef<'a> { /// The reference is a function type. @@ -507,202 +467,127 @@ pub enum FuncTypeRef<'a> { Ident(Ident<'a>), } -impl AstDisplay for FuncTypeRef<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Ident(ident) => ident.fmt(f, indenter), - Self::Func(func) => func.fmt(f, indenter), +impl<'a> Parse<'a> for FuncTypeRef<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::FuncKeyword) { + Ok(Self::Func(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(FuncTypeRef); - /// Represents a function type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::FuncType))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct FuncType<'a> { - /// The `func` keyword. - pub keyword: Func<'a>, /// The parameters of the function. - pub params: ParamList<'a>, + pub params: Vec>, /// The results of the function. - pub results: std::option::Option>, + pub results: ResultList<'a>, } -impl AstDisplay for FuncType<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword}", keyword = self.keyword)?; +impl<'a> Parse<'a> for FuncType<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + parse_token(lexer, Token::FuncKeyword)?; + parse_token(lexer, Token::OpenParen)?; + let params = parse_delimited(lexer, &[Token::CloseParen], true)?; + parse_token(lexer, Token::CloseParen)?; + let results = + parse_optional(lexer, Token::Arrow, Parse::parse)?.unwrap_or(ResultList::Empty); - self.params.fmt(f, indenter)?; - - if let Some(results) = &self.results { - write!(f, " ")?; - results.fmt(f, indenter)?; - } - - Ok(()) + Ok(Self { params, results }) } } -display!(FuncType); - -/// Represents a parameter list in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ParamList))] -#[serde(rename_all = "camelCase")] -pub struct ParamList<'a> { - /// The opening parenthesis of the parameter list. - pub open: OpenParen<'a>, - /// The parameters of the function. - pub list: Vec>, - /// The closing parenthesis of the parameter list. - pub close: CloseParen<'a>, -} - -impl AstDisplay for ParamList<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{open}", open = self.open)?; - - for (i, param) in self.list.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - param.fmt(f, indenter)?; - } - - write!(f, "{close}", close = self.close) +impl Peek for FuncType<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::FuncKeyword) } } -display!(ParamList); - /// Represents a result list in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ResultList))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum ResultList<'a> { + /// The function has no results. + Empty, + /// The function returns a scalar value. + Scalar(Type<'a>), /// The function has named results. - Named { - /// The arrow between the parameters and the results. - arrow: Arrow<'a>, - /// The results of the function. - results: NamedResultList<'a>, - }, - /// The function has a single result type. - Single { - /// The arrow between the parameters and the results. - arrow: Arrow<'a>, - /// The result type. - ty: Type<'a>, - }, -} - -impl AstDisplay for ResultList<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Single { arrow, ty } => { - write!(f, "{arrow} ")?; - ty.fmt(f, indenter) - } - Self::Named { arrow, results } => { - write!(f, "{arrow} ")?; - results.fmt(f, indenter) - } + Named(Vec>), +} + +impl<'a> Parse<'a> for ResultList<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::OpenParen) { + parse_token(lexer, Token::OpenParen)?; + let results = parse_delimited(lexer, &[Token::CloseParen], true)?; + parse_token(lexer, Token::CloseParen)?; + Ok(Self::Named(results)) + } else if Type::peek(&mut lookahead) { + Ok(Self::Scalar(Parse::parse(lexer)?)) + } else { + Ok(Self::Empty) } } } -display!(ResultList); - -/// Represents a named result list in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NamedResultList))] -#[serde(rename_all = "camelCase")] -pub struct NamedResultList<'a> { - /// The opening parenthesis of the result list. - pub open: OpenParen<'a>, - /// The results of the function. - pub list: Vec>, - /// The closing parenthesis of the result list. - pub close: CloseParen<'a>, -} - -impl AstDisplay for NamedResultList<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{open}", open = self.open)?; - - for (i, result) in self.list.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - result.fmt(f, indenter)?; - } - - write!(f, "{close}", close = self.close) - } -} - -display!(NamedResultList); - /// Represents a name and an associated type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::NamedType))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct NamedType<'a> { /// The identifier of the type. pub id: Ident<'a>, - /// The colon between the identifier and the type. - pub colon: Colon<'a>, /// The type. pub ty: Type<'a>, } -impl AstDisplay for NamedType<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.id.fmt(f, indenter)?; - write!(f, "{colon} ", colon = self.colon)?; - self.ty.fmt(f, indenter) +impl<'a> Parse<'a> for NamedType<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::Colon)?; + let ty = Parse::parse(lexer)?; + Ok(Self { id, ty }) } } -display!(NamedType); +impl Peek for NamedType<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Ident::peek(lookahead) + } +} /// Represents a type alias in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::TypeAlias))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct TypeAlias<'a> { - /// The `type` keyword. - pub keyword: keywords::Type<'a>, + /// The docs for the type alias. + pub docs: Vec>, /// The identifier of the type alias. pub id: Ident<'a>, - /// The equals sign between the identifier and the type. - pub equals: Equals<'a>, /// The kind of type alias. pub kind: TypeAliasKind<'a>, - /// The semicolon at the end of the type alias. - pub semicolon: Semicolon<'a>, } -impl AstDisplay for TypeAlias<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword,)?; - self.id.fmt(f, indenter)?; - write!(f, " {equals} ", equals = self.equals)?; - self.kind.fmt(f, indenter)?; - write!(f, "{semi}", semi = self.semicolon) +impl<'a> Parse<'a> for TypeAlias<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::TypeKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::Equals)?; + let kind = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, id, kind }) } } -display!(TypeAlias); - /// Represents a type alias kind in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::TypeAliasKind))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum TypeAliasKind<'a> { /// The alias is to a function type. @@ -711,782 +596,633 @@ pub enum TypeAliasKind<'a> { Type(Type<'a>), } -impl AstDisplay for TypeAliasKind<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Func(func) => func.fmt(f, indenter), - Self::Type(ty) => ty.fmt(f, indenter), +impl<'a> Parse<'a> for TypeAliasKind<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::FuncKeyword) { + Ok(Self::Func(Parse::parse(lexer)?)) + } else if Type::peek(&mut lookahead) { + Ok(Self::Type(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(TypeAliasKind); - /// Represents a type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Type))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum Type<'a> { /// A `u8` type. - U8(U8<'a>), + U8, /// A `s8` type. - S8(S8<'a>), + S8, /// A `u16` type. - U16(U16<'a>), + U16, /// A `s16` type. - S16(S16<'a>), + S16, /// A `u32` type. - U32(U32<'a>), + U32, /// A `s32` type. - S32(S32<'a>), + S32, /// A `u64` type. - U64(U64<'a>), + U64, /// A `s64` type. - S64(S64<'a>), + S64, /// A `float32` type. - Float32(Float32<'a>), + Float32, /// A `float64` type. - Float64(Float64<'a>), + Float64, /// A `char` type. - Char(super::keywords::Char<'a>), + Char, /// A `bool` type. - Bool(Bool<'a>), + Bool, /// A `string` type. - String(super::keywords::String<'a>), + String, /// A tuple type. - Tuple(Tuple<'a>), + Tuple(Vec>), /// A list type. - List(List<'a>), + List(Box>), /// An option type. - Option(Option<'a>), + Option(Box>), /// A result type. - Result(Result<'a>), + Result { + /// The `ok` of the result type. + ok: Option>>, + /// The `err` of the result type. + err: Option>>, + }, /// A borrow type. - Borrow(Borrow<'a>), + Borrow(Ident<'a>), /// An identifier to a value type. Ident(Ident<'a>), } -impl AstDisplay for Type<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::U8(v) => write!(f, "{v}"), - Self::S8(v) => write!(f, "{v}"), - Self::U16(v) => write!(f, "{v}"), - Self::S16(v) => write!(f, "{v}"), - Self::U32(v) => write!(f, "{v}"), - Self::S32(v) => write!(f, "{v}"), - Self::U64(v) => write!(f, "{v}"), - Self::S64(v) => write!(f, "{v}"), - Self::Float32(v) => write!(f, "{v}"), - Self::Float64(v) => write!(f, "{v}"), - Self::Char(v) => write!(f, "{v}"), - Self::Bool(v) => write!(f, "{v}"), - Self::String(v) => write!(f, "{v}"), - Self::Tuple(tuple) => tuple.fmt(f, indenter), - Self::List(list) => list.fmt(f, indenter), - Self::Option(option) => option.fmt(f, indenter), - Self::Result(result) => result.fmt(f, indenter), - Self::Borrow(borrow) => borrow.fmt(f, indenter), - Self::Ident(ident) => ident.fmt(f, indenter), - } - } -} - -display!(Type); - -/// Represents a tuple type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Tuple))] -#[serde(rename_all = "camelCase")] -pub struct Tuple<'a> { - /// The `tuple` keyword. - pub keyword: keywords::Tuple<'a>, - /// The opening angle bracket of the tuple. - pub open: OpenAngle<'a>, - /// The types in the tuple. - pub types: Vec>, - /// The closing angle bracket of the tuple. - pub close: CloseAngle<'a>, -} - -impl AstDisplay for Tuple<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{keyword}{open}", - keyword = self.keyword, - open = self.open - )?; - - for (i, ty) in self.types.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; +impl<'a> Parse<'a> for Type<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::U8Keyword) { + lexer.next(); + Ok(Self::U8) + } else if lookahead.peek(Token::S8Keyword) { + lexer.next(); + Ok(Self::S8) + } else if lookahead.peek(Token::U16Keyword) { + lexer.next(); + Ok(Self::U16) + } else if lookahead.peek(Token::S16Keyword) { + lexer.next(); + Ok(Self::S16) + } else if lookahead.peek(Token::U32Keyword) { + lexer.next(); + Ok(Self::U32) + } else if lookahead.peek(Token::S32Keyword) { + lexer.next(); + Ok(Self::S32) + } else if lookahead.peek(Token::U64Keyword) { + lexer.next(); + Ok(Self::U64) + } else if lookahead.peek(Token::S64Keyword) { + lexer.next(); + Ok(Self::S64) + } else if lookahead.peek(Token::Float32Keyword) { + lexer.next(); + Ok(Self::Float32) + } else if lookahead.peek(Token::Float64Keyword) { + lexer.next(); + Ok(Self::Float64) + } else if lookahead.peek(Token::CharKeyword) { + lexer.next(); + Ok(Self::Char) + } else if lookahead.peek(Token::BoolKeyword) { + lexer.next(); + Ok(Self::Bool) + } else if lookahead.peek(Token::StringKeyword) { + lexer.next(); + Ok(Self::String) + } else if lookahead.peek(Token::TupleKeyword) { + lexer.next(); + parse_token(lexer, Token::OpenAngle)?; + + // There must be at least one type in the tuple. + let mut lookahead = Lookahead::new(lexer); + if !Type::peek(&mut lookahead) { + return Err(lookahead.error()); } - ty.fmt(f, indenter)?; + let types = parse_delimited(lexer, &[Token::CloseAngle], true)?; + assert!(!types.is_empty()); + parse_token(lexer, Token::CloseAngle)?; + Ok(Self::Tuple(types)) + } else if lookahead.peek(Token::ListKeyword) { + lexer.next(); + parse_token(lexer, Token::OpenAngle)?; + let ty = Box::new(Parse::parse(lexer)?); + parse_token(lexer, Token::CloseAngle)?; + Ok(Self::List(ty)) + } else if lookahead.peek(Token::OptionKeyword) { + lexer.next(); + parse_token(lexer, Token::OpenAngle)?; + let ty = Box::new(Parse::parse(lexer)?); + parse_token(lexer, Token::CloseAngle)?; + Ok(Self::Option(ty)) + } else if lookahead.peek(Token::ResultKeyword) { + lexer.next(); + let (ok, err) = match parse_optional(lexer, Token::OpenAngle, |lexer| { + let mut lookahead = Lookahead::new(lexer); + let ok = if lookahead.peek(Token::Underscore) { + lexer.next(); + None + } else if Type::peek(&mut lookahead) { + Some(Box::new(Parse::parse(lexer)?)) + } else { + return Err(lookahead.error()); + }; + + let err = parse_optional(lexer, Token::Comma, |lexer| { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::Underscore) { + lexer.next(); + Ok(None) + } else if Type::peek(&mut lookahead) { + Ok(Some(Box::new(Parse::parse(lexer)?))) + } else { + return Err(lookahead.error()); + } + })? + .unwrap_or(None); + + parse_token(lexer, Token::CloseAngle)?; + Ok((ok, err)) + })? { + Some((ok, err)) => (ok, err), + None => (None, None), + }; + Ok(Self::Result { ok, err }) + } else if lookahead.peek(Token::BorrowKeyword) { + lexer.next(); + parse_token(lexer, Token::OpenAngle)?; + let id = Parse::parse(lexer)?; + parse_token(lexer, Token::CloseAngle)?; + Ok(Self::Borrow(id)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } - - write!(f, "{close}", close = self.close) } } -display!(Tuple); - -/// Represents a list type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::List))] -#[serde(rename_all = "camelCase")] -pub struct List<'a> { - /// The `list` keyword. - pub keyword: keywords::List<'a>, - /// The opening angle bracket of the list. - pub open: OpenAngle<'a>, - /// The type of the list. - pub ty: Box>, - /// The closing angle bracket of the list. - pub close: CloseAngle<'a>, -} - -impl AstDisplay for List<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{keyword}{open}", - keyword = self.keyword, - open = self.open - )?; - self.ty.fmt(f, indenter)?; - write!(f, "{close}", close = self.close) - } -} - -display!(List); - -/// Represents an option type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Option))] -#[serde(rename_all = "camelCase")] -pub struct Option<'a> { - /// The `option` keyword. - pub keyword: keywords::Option<'a>, - /// The opening angle bracket of the option. - pub open: OpenAngle<'a>, - /// The type of the option. - pub ty: Box>, - /// The closing angle bracket of the option. - pub close: CloseAngle<'a>, -} - -impl AstDisplay for Option<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{keyword}{open}", - keyword = self.keyword, - open = self.open - )?; - self.ty.fmt(f, indenter)?; - write!(f, "{close}", close = self.close) - } -} - -display!(Option); - -/// Represents a result type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Result))] +impl Peek for Type<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::U8Keyword) + || lookahead.peek(Token::S8Keyword) + || lookahead.peek(Token::U16Keyword) + || lookahead.peek(Token::S16Keyword) + || lookahead.peek(Token::U32Keyword) + || lookahead.peek(Token::S32Keyword) + || lookahead.peek(Token::U64Keyword) + || lookahead.peek(Token::S64Keyword) + || lookahead.peek(Token::Float32Keyword) + || lookahead.peek(Token::Float64Keyword) + || lookahead.peek(Token::CharKeyword) + || lookahead.peek(Token::BoolKeyword) + || lookahead.peek(Token::StringKeyword) + || lookahead.peek(Token::TupleKeyword) + || lookahead.peek(Token::ListKeyword) + || lookahead.peek(Token::OptionKeyword) + || lookahead.peek(Token::ResultKeyword) + || lookahead.peek(Token::BorrowKeyword) + || Ident::peek(lookahead) + } +} + +/// Represents an interface or world type declaration in the AST. +/// +/// Unlike top-level type declarations, interfaces and worlds can +/// also declare resources. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct Result<'a> { - /// The `result` keyword. - pub keyword: keywords::Result<'a>, - /// The specified result type. - pub specified: std::option::Option>, +pub enum ItemTypeDecl<'a> { + /// The declaration is for a resource. + Resource(ResourceDecl<'a>), + /// The declaration is for a variant. + Variant(VariantDecl<'a>), + /// The declaration is for a record. + Record(RecordDecl<'a>), + /// The declaration is for a flags. + Flags(FlagsDecl<'a>), + /// The declaration is for an enum. + Enum(EnumDecl<'a>), + /// The declaration is for a type alias. + Alias(TypeAlias<'a>), } -impl AstDisplay for Result<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword}", keyword = self.keyword)?; - - if let Some(specified) = &self.specified { - specified.fmt(f, indenter)?; +impl<'a> Parse<'a> for ItemTypeDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if lookahead.peek(Token::ResourceKeyword) { + Ok(Self::Resource(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::VariantKeyword) { + Ok(Self::Variant(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::RecordKeyword) { + Ok(Self::Record(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::FlagsKeyword) { + Ok(Self::Flags(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::EnumKeyword) { + Ok(Self::Enum(Parse::parse(lexer)?)) + } else if lookahead.peek(Token::TypeKeyword) { + Ok(Self::Alias(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } - - Ok(()) } } -display!(Result); - -/// Represents a specified result in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::SpecifiedResult))] -#[serde(rename_all = "camelCase")] -pub struct SpecifiedResult<'a> { - /// The opening angle bracket of the result. - pub open: OpenAngle<'a>, - /// The ok type of the result. - pub ok: OmitType<'a>, - /// The error type of the result. - pub err: std::option::Option>>, - /// The closing angle bracket of the result. - pub close: CloseAngle<'a>, -} - -impl AstDisplay for SpecifiedResult<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{open}", open = self.open)?; - - self.ok.fmt(f, indenter)?; - - if let Some(err) = &self.err { - write!(f, ", ")?; - err.fmt(f, indenter)?; - } - - write!(f, "{close}", close = self.close) +impl Peek for ItemTypeDecl<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::ResourceKeyword) + || lookahead.peek(Token::VariantKeyword) + || lookahead.peek(Token::RecordKeyword) + || lookahead.peek(Token::FlagsKeyword) + || lookahead.peek(Token::EnumKeyword) + || lookahead.peek(Token::TypeKeyword) } } -display!(SpecifiedResult); - -/// Represents a possibly omitted type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::OmitType))] -#[serde(rename_all = "camelCase")] -pub enum OmitType<'a> { - /// The type was omitted with `_`. - Omitted(Underscore<'a>), - /// The type was specified. - Type(Box>), -} - -impl AstDisplay for OmitType<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { +impl ItemTypeDecl<'_> { + /// Gets the identifier of the type being declared. + pub fn id(&self) -> &Ident { match self { - Self::Type(ty) => ty.fmt(f, indenter), - Self::Omitted(u) => write!(f, "{u}"), + Self::Resource(resource) => &resource.id, + Self::Variant(variant) => &variant.id, + Self::Record(record) => &record.id, + Self::Flags(flags) => &flags.id, + Self::Enum(e) => &e.id, + Self::Alias(alias) => &alias.id, } } } -display!(OmitType); - -/// Represents a borrow type in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::Borrow))] -#[serde(rename_all = "camelCase")] -pub struct Borrow<'a> { - /// The `borrow` keyword. - pub keyword: keywords::Borrow<'a>, - /// The opening angle bracket of the borrow. - pub open: OpenAngle<'a>, - /// The identifier of the borrowed resource type. - pub id: Ident<'a>, - /// The closing angle bracket of the borrow. - pub close: CloseAngle<'a>, -} - -impl AstDisplay for Borrow<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!( - f, - "{keyword}{open}", - keyword = self.keyword, - open = self.open - )?; - self.id.fmt(f, indenter)?; - write!(f, "{close}", close = self.close) - } -} - -display!(Borrow); - /// Represents an interface declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InterfaceDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct InterfaceDecl<'a> { - /// The `interface` keyword. - pub keyword: Interface<'a>, + /// The doc comments for the interface. + pub docs: Vec>, /// The identifier of the interface. pub id: Ident<'a>, - /// The body of the interface. - pub body: InterfaceBody<'a>, -} - -impl AstDisplay for InterfaceDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) - } -} - -display!(InterfaceDecl); - -/// Represents an interface body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InterfaceBody))] -#[serde(rename_all = "camelCase")] -pub struct InterfaceBody<'a> { - /// The opening brace of the interface body. - pub open: OpenBrace<'a>, - /// The items of the interface body. + /// The items of the interface. pub items: Vec>, - /// The closing brace of the interface body. - pub close: CloseBrace<'a>, } -impl AstDisplay for InterfaceBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - writeln!(f, " {open}", open = self.open)?; - indenter.indent(); - - for (i, item) in self.items.iter().enumerate() { - if i > 0 { - writeln!(f)?; - } - - item.fmt(f, indenter)?; - writeln!(f)?; - } - - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) +impl<'a> Parse<'a> for InterfaceDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::InterfaceKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let items = parse_delimited(lexer, &[Token::CloseBrace], false)?; + parse_token(lexer, Token::CloseBrace)?; + Ok(Self { docs, id, items }) } } -display!(InterfaceBody); - -/// Represents an interface item in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InterfaceItem))] -#[serde(rename_all = "camelCase")] -pub struct InterfaceItem<'a> { - /// The doc comments for the interface item. - pub docs: Vec>, - /// The interface item statement. - pub stmt: InterfaceItemStatement<'a>, -} - -impl AstDisplay for InterfaceItem<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - for doc in &self.docs { - doc.fmt(f, indenter)?; - } - - self.stmt.fmt(f, indenter) +impl Peek for InterfaceDecl<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::InterfaceKeyword) } } -display!(InterfaceItem); +display!(InterfaceDecl, interface_decl); -/// Represents an interface item statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InterfaceItemStatement))] +/// Represents an interface item in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub enum InterfaceItemStatement<'a> { - /// The item is a use statement. - Use(Box>), +pub enum InterfaceItem<'a> { + /// The item is a use of other types. + Use(Box>), /// The item is a type declaration. - Type(TypeDecl<'a>), - /// The item is an interface export statement. - Export(InterfaceExportStatement<'a>), -} - -impl AstDisplay for InterfaceItemStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Use(u) => u.fmt(f, indenter), - Self::Type(ty) => ty.fmt(f, indenter), - Self::Export(e) => e.fmt(f, indenter), + Type(ItemTypeDecl<'a>), + /// The item is an interface export. + Export(InterfaceExport<'a>), +} + +impl<'a> Parse<'a> for InterfaceItem<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if Use::peek(&mut lookahead) { + Ok(Self::Use(Box::new(Parse::parse(lexer)?))) + } else if InterfaceExport::peek(&mut lookahead) { + Ok(Self::Export(Parse::parse(lexer)?)) + } else if ItemTypeDecl::peek(&mut lookahead) { + Ok(Self::Type(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(InterfaceItemStatement); - -/// Represents a use statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::UseStatement))] -#[serde(rename_all = "camelCase")] -pub struct UseStatement<'a> { - /// The use keyword in the statement. - pub keyword: Use<'a>, - /// The items being used. - pub items: UseItems<'a>, - /// The semicolon of the export statement. - pub semicolon: Semicolon<'a>, -} - -impl AstDisplay for UseStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - self.items.fmt(f, indenter)?; - write!(f, "{semi}", semi = self.semicolon) +impl Peek for InterfaceItem<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Use::peek(lookahead) || InterfaceExport::peek(lookahead) || ItemTypeDecl::peek(lookahead) } } -display!(UseStatement); - -/// Represents the items being used in a use statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::UseItems))] +/// Represents a "use" in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct UseItems<'a> { +pub struct Use<'a> { + /// The doc comments for the use. + pub docs: Vec>, /// The path to the interface or world being used. pub path: UsePath<'a>, - /// The dot in the statement. - pub dot: Dot<'a>, - /// The opening brace of the statement. - pub open: OpenBrace<'a>, /// The items being used. - pub list: Vec>, - /// The closing brace of the use items. - pub close: CloseBrace<'a>, + pub items: Vec>, } -impl AstDisplay for UseItems<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.path.fmt(f, indenter)?; - write!(f, "{dot}{open} ", dot = self.dot, open = self.open)?; - - for (i, item) in self.list.iter().enumerate() { - if i > 0 { - write!(f, ", ")?; - } - - item.fmt(f, indenter)?; - } - - write!(f, " {close}", close = self.close) +impl<'a> Parse<'a> for Use<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::UseKeyword)?; + let path = Parse::parse(lexer)?; + parse_token(lexer, Token::Dot)?; + parse_token(lexer, Token::OpenBrace)?; + let items = parse_delimited(lexer, &[Token::CloseBrace], true)?; + parse_token(lexer, Token::CloseBrace)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, path, items }) } } -display!(UseItems); +impl Peek for Use<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::UseKeyword) + } +} -/// Represents an item being used in a use statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::UseItem))] +/// Represents a use path in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct UseItem<'a> { - /// The identifier of the item. - pub id: Ident<'a>, - /// The optional as clause of the item. - pub as_clause: std::option::Option>, +pub enum UsePath<'a> { + /// The path is a package path. + Package(PackagePath<'a>), + /// The path is an identifier. + Ident(Ident<'a>), } -impl AstDisplay for UseItem<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.id.fmt(f, indenter)?; - - if let Some(as_clause) = &self.as_clause { - write!(f, " {as_clause}")?; +impl<'a> Parse<'a> for UsePath<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead: Lookahead<'_> = Lookahead::new(lexer); + if PackagePath::peek(&mut lookahead) { + Ok(Self::Package(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } - - Ok(()) } } -display!(UseItem); +impl Peek for UsePath<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + PackagePath::peek(lookahead) | Ident::peek(lookahead) + } +} -/// Represents an as clause in a use statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::UseAsClause))] +/// Represents a use item in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct UseAsClause<'a> { - /// The `as` keyword. - pub keyword: As<'a>, - /// The identifier of the as clause. +pub struct UseItem<'a> { + /// The identifier of the item. pub id: Ident<'a>, + /// The optional `as` identifier of the item. + pub as_id: Option>, } -impl AstDisplay for UseAsClause<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter) +impl<'a> Parse<'a> for UseItem<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let id = Ident::parse(lexer)?; + let as_id = parse_optional(lexer, Token::AsKeyword, Ident::parse)?; + Ok(Self { id, as_id }) } } -display!(UseAsClause); - -/// Represents a use path in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::UsePath))] -#[serde(rename_all = "camelCase")] -pub enum UsePath<'a> { - /// The path is a package path. - Package(PackagePath<'a>), - /// The path is an identifier. - Ident(Ident<'a>), -} - -impl AstDisplay for UsePath<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Package(pkg) => pkg.fmt(f, indenter), - Self::Ident(id) => id.fmt(f, indenter), - } +impl Peek for UseItem<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Ident::peek(lookahead) } } -display!(UsePath); - -/// Represents an interface export statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InterfaceExportStatement))] +/// Represents an interface export in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct InterfaceExportStatement<'a> { +pub struct InterfaceExport<'a> { + /// The doc comments for the export. + pub docs: Vec>, /// The identifier of the export. pub id: Ident<'a>, - /// The colon of the export statement. - pub colon: Colon<'a>, /// The type of the export. pub ty: FuncTypeRef<'a>, - /// The semicolon of the export statement. - pub semicolon: Semicolon<'a>, } -impl AstDisplay for InterfaceExportStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}")?; - self.id.fmt(f, indenter)?; - write!(f, "{colon} ", colon = self.colon)?; - self.ty.fmt(f, indenter)?; - write!(f, "{semi}", semi = self.semicolon) +impl<'a> Parse<'a> for InterfaceExport<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::Colon)?; + let ty = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, id, ty }) } } -display!(InterfaceExportStatement); +impl Peek for InterfaceExport<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Ident::peek(lookahead) + } +} /// Represents a world declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldDecl))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct WorldDecl<'a> { - /// The `world` keyword. - pub keyword: World<'a>, + /// The doc comments for the world. + pub docs: Vec>, /// The identifier of the world. pub id: Ident<'a>, - /// The body of the world. - pub body: WorldBody<'a>, + /// The items of the world. + pub items: Vec>, } -impl AstDisplay for WorldDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - self.id.fmt(f, indenter)?; - self.body.fmt(f, indenter) +impl<'a> Parse<'a> for WorldDecl<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::WorldKeyword)?; + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::OpenBrace)?; + let items = parse_delimited(lexer, &[Token::CloseBrace], false)?; + parse_token(lexer, Token::CloseBrace)?; + Ok(Self { docs, id, items }) } } -display!(WorldDecl); - -/// Represents a world body in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldBody))] -#[serde(rename_all = "camelCase")] -pub struct WorldBody<'a> { - /// The opening brace of the world body. - pub open: OpenBrace<'a>, - /// The items of the world body. - pub items: Vec>, - /// The closing brace of the world body. - pub close: CloseBrace<'a>, -} - -impl AstDisplay for WorldBody<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - writeln!(f, " {open}", open = self.open)?; - indenter.indent(); - - for (i, item) in self.items.iter().enumerate() { - if i > 0 { - writeln!(f)?; - } - - item.fmt(f, indenter)?; - writeln!(f)?; - } - - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) +impl Peek for WorldDecl<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::WorldKeyword) } } -display!(WorldBody); +display!(WorldDecl, world_decl); /// Represents a world item in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldItem))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct WorldItem<'a> { - /// The doc comments for the world item. - pub docs: Vec>, - /// The world item statement. - pub stmt: WorldItemStatement<'a>, -} - -impl AstDisplay for WorldItem<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - for doc in &self.docs { - doc.fmt(f, indenter)?; +pub enum WorldItem<'a> { + /// The item is a use. + Use(Use<'a>), + /// The item is a type declaration. + Type(ItemTypeDecl<'a>), + /// The item is a world export. + Import(WorldImport<'a>), + /// The item is a world export. + Export(WorldExport<'a>), + /// The item is a world include. + Include(WorldInclude<'a>), +} + +impl<'a> Parse<'a> for WorldItem<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if Use::peek(&mut lookahead) { + Ok(Self::Use(Parse::parse(lexer)?)) + } else if WorldImport::peek(&mut lookahead) { + Ok(Self::Import(Parse::parse(lexer)?)) + } else if WorldExport::peek(&mut lookahead) { + Ok(Self::Export(Parse::parse(lexer)?)) + } else if WorldInclude::peek(&mut lookahead) { + Ok(Self::Include(Parse::parse(lexer)?)) + } else if ItemTypeDecl::peek(&mut lookahead) { + Ok(Self::Type(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } - - self.stmt.fmt(f, indenter) } } -display!(WorldItem); - -/// Represents a world item statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldItemStatement))] -#[serde(rename_all = "camelCase")] -pub enum WorldItemStatement<'a> { - /// The item is a use statement. - Use(Box>), - /// The item is a type declaration. - Type(TypeDecl<'a>), - /// The item is a world export statement. - Import(WorldImportStatement<'a>), - /// The item is a world export statement. - Export(WorldExportStatement<'a>), - /// The item is a world include statement. - Include(Box>), -} - -impl AstDisplay for WorldItemStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Use(u) => u.fmt(f, indenter), - Self::Type(ty) => ty.fmt(f, indenter), - Self::Import(i) => i.fmt(f, indenter), - Self::Export(e) => e.fmt(f, indenter), - Self::Include(i) => i.fmt(f, indenter), - } +impl Peek for WorldItem<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Use::peek(lookahead) + || WorldImport::peek(lookahead) + || WorldExport::peek(lookahead) + || WorldInclude::peek(lookahead) + || ItemTypeDecl::peek(lookahead) } } -display!(WorldItemStatement); - -/// Represents a world import statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldImportStatement))] +/// Represents a world import in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct WorldImportStatement<'a> { - /// The `import` keyword in the statement. - pub keyword: Import<'a>, - /// The declaration of the imported item. - pub decl: WorldItemDecl<'a>, - /// The semicolon of the import statement. - pub semicolon: Semicolon<'a>, +pub struct WorldImport<'a> { + /// The doc comments for the world import. + pub docs: Vec>, + /// The path of the imported item. + pub path: WorldItemPath<'a>, } -impl AstDisplay for WorldImportStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - self.decl.fmt(f, indenter)?; - write!(f, "{semi}", semi = self.semicolon) +impl<'a> Parse<'a> for WorldImport<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::ImportKeyword)?; + let path = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, path }) } } -display!(WorldImportStatement); +impl Peek for WorldImport<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::ImportKeyword) + } +} -/// Represents a world export statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldExportStatement))] +/// Represents a world export in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct WorldExportStatement<'a> { - /// The `export` keyword in the statement. - pub keyword: Export<'a>, - /// The declaration of the exported item. - pub decl: WorldItemDecl<'a>, - /// The semicolon of the export statement. - pub semicolon: Semicolon<'a>, +pub struct WorldExport<'a> { + /// The doc comments for the world export. + pub docs: Vec>, + /// The path of the exported item. + pub path: WorldItemPath<'a>, } -impl AstDisplay for WorldExportStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - self.decl.fmt(f, indenter)?; - write!(f, "{semi}", semi = self.semicolon) +impl<'a> Parse<'a> for WorldExport<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::ExportKeyword)?; + let path = Parse::parse(lexer)?; + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, path }) } } -display!(WorldExportStatement); +impl Peek for WorldExport<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::ExportKeyword) + } +} -/// Represents a world item declaration in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldItemDecl))] +/// Represents a world item path in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub enum WorldItemDecl<'a> { - /// The declaration is by name. - Named(WorldNamedItem<'a>), - /// The declaration is by a reference to an interface. - Interface(InterfaceRef<'a>), +pub enum WorldItemPath<'a> { + /// The path is by name. + Named(NamedWorldItem<'a>), + /// The path is by a package path. + Package(PackagePath<'a>), + /// The path is by identifier. + Ident(Ident<'a>), } -impl AstDisplay for WorldItemDecl<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Named(n) => n.fmt(f, indenter), - Self::Interface(i) => i.fmt(f, indenter), +impl<'a> Parse<'a> for WorldItemPath<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if PackagePath::peek(&mut lookahead) { + Ok(Self::Package(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + // Peek again to see if this is a named item or an interface reference + if let Some((Ok(Token::Colon), _)) = lexer.peek2() { + Ok(Self::Named(Parse::parse(lexer)?)) + } else { + Ok(Self::Ident(Parse::parse(lexer)?)) + } + } else { + Err(lookahead.error()) } } } -display!(WorldItemDecl); - -/// Represents a world named item in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldNamedItem))] +/// Represents a named world item in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct WorldNamedItem<'a> { +pub struct NamedWorldItem<'a> { /// The identifier of the item being imported or exported. pub id: Ident<'a>, - /// The colon between the identifier and the extern type. - pub colon: Colon<'a>, /// The extern type of the item. pub ty: ExternType<'a>, } -impl AstDisplay for WorldNamedItem<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.id.fmt(f, indenter)?; - write!(f, "{colon} ", colon = self.colon)?; - self.ty.fmt(f, indenter) +impl<'a> Parse<'a> for NamedWorldItem<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let id = Ident::parse(lexer)?; + parse_token(lexer, Token::Colon)?; + let ty = Parse::parse(lexer)?; + Ok(Self { id, ty }) } } -display!(WorldNamedItem); - -/// Represents a reference to an interface in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InterfaceRef))] -#[serde(rename_all = "camelCase")] -pub enum InterfaceRef<'a> { - /// The reference is by identifier. - Ident(Ident<'a>), - /// The reference is by package path. - Path(PackagePath<'a>), -} - -impl AstDisplay for InterfaceRef<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Ident(id) => id.fmt(f, indenter), - Self::Path(path) => path.fmt(f, indenter), - } - } -} - -display!(InterfaceRef); - /// Represents the external type of a world item in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::ExternType))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum ExternType<'a> { /// The type is by identifier. @@ -1497,183 +1233,183 @@ pub enum ExternType<'a> { Interface(InlineInterface<'a>), } -impl AstDisplay for ExternType<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Ident(id) => id.fmt(f, indenter), - Self::Func(func) => func.fmt(f, indenter), - Self::Interface(interface) => interface.fmt(f, indenter), +impl<'a> Parse<'a> for ExternType<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else if FuncType::peek(&mut lookahead) { + Ok(Self::Func(Parse::parse(lexer)?)) + } else if InlineInterface::peek(&mut lookahead) { + Ok(Self::Interface(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(ExternType); - /// Represents an inline interface in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::InlineInterface))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct InlineInterface<'a> { - /// The `interface` keyword in the inline interface. - pub keyword: Interface<'a>, - /// The body of the interface. - pub body: InterfaceBody<'a>, + /// The items of the interface. + pub items: Vec>, } -impl AstDisplay for InlineInterface<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{keyword}", keyword = self.keyword)?; - self.body.fmt(f, indenter) +impl<'a> Parse<'a> for InlineInterface<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + parse_token(lexer, Token::InterfaceKeyword)?; + parse_token(lexer, Token::OpenBrace)?; + let items = parse_delimited(lexer, &[Token::CloseBrace], false)?; + parse_token(lexer, Token::CloseBrace)?; + Ok(Self { items }) } } -display!(InlineInterface); +impl Peek for InlineInterface<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::InterfaceKeyword) + } +} -/// Represents a world include statement in the AST. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldIncludeStatement))] +/// Represents a world include in the AST. +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] -pub struct WorldIncludeStatement<'a> { - /// The `include` keyword in the statement. - pub keyword: Include<'a>, +pub struct WorldInclude<'a> { + /// The doc comments for the world include. + pub docs: Vec>, /// The reference to the world to include. pub world: WorldRef<'a>, - /// The optional include-with clause. - pub with: std::option::Option>, - /// The semicolon of the include statement. - pub semicolon: Semicolon<'a>, -} - -impl AstDisplay for WorldIncludeStatement<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, "{indenter}{keyword} ", keyword = self.keyword)?; - self.world.fmt(f, indenter)?; - if let Some(with) = &self.with { - with.fmt(f, indenter)?; - } - write!(f, "{semi}", semi = self.semicolon) + /// The optional include-with items. + pub with: Vec>, +} + +impl<'a> Parse<'a> for WorldInclude<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let docs = Parse::parse(lexer)?; + parse_token(lexer, Token::IncludeKeyword)?; + let world = Parse::parse(lexer)?; + let with = parse_optional(lexer, Token::WithKeyword, |lexer| { + parse_token(lexer, Token::OpenBrace)?; + let items = parse_delimited(lexer, &[Token::CloseBrace], true)?; + parse_token(lexer, Token::CloseBrace)?; + Ok(items) + })? + .unwrap_or_default(); + parse_token(lexer, Token::Semicolon)?; + Ok(Self { docs, world, with }) } } -display!(WorldIncludeStatement); +impl Peek for WorldInclude<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + lookahead.peek(Token::IncludeKeyword) + } +} /// Represents a reference to a world in the AST (local or foreign). -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldRef))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub enum WorldRef<'a> { /// The reference is by identifier. Ident(Ident<'a>), /// The reference is by package path. - Path(PackagePath<'a>), + Package(PackagePath<'a>), } impl<'a> WorldRef<'a> { /// Gets the name of the world being referred to. - pub fn name(&self) -> &str { + pub fn name(&self) -> &'a str { match self { - Self::Ident(id) => id.as_str(), - Self::Path(path) => path.as_str(), + Self::Ident(id) => id.string, + Self::Package(path) => path.span.as_str(), } } /// Gets the span of the world reference. - pub fn span(&self) -> Span { + pub fn span(&self) -> Span<'a> { match self { - Self::Ident(id) => id.0, - Self::Path(path) => path.span(), + Self::Ident(id) => id.span, + Self::Package(path) => path.span, } } } -impl AstDisplay for WorldRef<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - match self { - Self::Ident(id) => id.fmt(f, indenter), - Self::Path(path) => path.fmt(f, indenter), +impl<'a> Parse<'a> for WorldRef<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let mut lookahead = Lookahead::new(lexer); + if PackagePath::peek(&mut lookahead) { + Ok(Self::Package(Parse::parse(lexer)?)) + } else if Ident::peek(&mut lookahead) { + Ok(Self::Ident(Parse::parse(lexer)?)) + } else { + Err(lookahead.error()) } } } -display!(WorldRef); - -/// Represents the `with` clause of the `include` statement. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldIncludeWithClause))] -#[serde(rename_all = "camelCase")] -pub struct WorldIncludeWithClause<'a> { - /// The `with` keyword in the clause. - pub keyword: With<'a>, - /// The opening brace of the body. - pub open: OpenBrace<'a>, - /// The list of names to be included. - pub names: Vec>, - /// The closing brace of the body. - pub close: CloseBrace<'a>, -} - -impl AstDisplay for WorldIncludeWithClause<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - write!(f, " {keyword} ", keyword = self.keyword)?; - writeln!(f, "{open}", open = self.open)?; - indenter.indent(); - for name in &self.names { - write!(f, "{indenter}")?; - name.fmt(f, indenter)?; - writeln!(f, ",")?; - } - indenter.dedent(); - write!(f, "{indenter}{close}", close = self.close) - } -} - -display!(WorldIncludeWithClause); - /// Represents a renaming of an included name. -#[derive(Debug, Clone, Serialize, FromPest)] -#[pest_ast(rule(Rule::WorldIncludeItem))] +#[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct WorldIncludeItem<'a> { - /// The source name include from a world. - pub name: Ident<'a>, - /// The `as` keyword. - pub keyword: As<'a>, - /// The new name given to the included name. - pub other: Ident<'a>, + /// The `from` name for the included item. + pub from: Ident<'a>, + /// The `to` name for the included item. + pub to: Ident<'a>, } -impl AstDisplay for WorldIncludeItem<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>, indenter: &mut Indenter) -> fmt::Result { - self.name.fmt(f, indenter)?; - write!(f, " {keyword} ", keyword = self.keyword)?; - self.other.fmt(f, indenter) +impl<'a> Parse<'a> for WorldIncludeItem<'a> { + fn parse(lexer: &mut Lexer<'a>) -> ParseResult<'a, Self> { + let from = Ident::parse(lexer)?; + parse_token(lexer, Token::AsKeyword)?; + let to = Ident::parse(lexer)?; + Ok(Self { from, to }) } } -display!(WorldIncludeItem); +impl Peek for WorldIncludeItem<'_> { + fn peek(lookahead: &mut Lookahead) -> bool { + Ident::peek(lookahead) + } +} #[cfg(test)] mod test { use super::*; - use crate::{ast::test::roundtrip, parser::Rule}; + use crate::ast::test::roundtrip; #[test] fn resource_roundtrip() { roundtrip::( - Rule::TypeStatement, - r#"resource foo-bar { + r#"interface i { resource foo-bar { + /** A constructor */ constructor(foo: u8, bar: u8); + /// A method foo: func() -> string; + /// A method set-foo: func(foo: string); + /// A static method id: static func() -> u32; + /// A method baz: x; -}"#, - r#"resource foo-bar { - constructor(foo: u8, bar: u8); - foo: func() -> string; - set-foo: func(foo: string); - id: static func() -> u32; - baz: x; +}}"#, + r#"interface i { + resource foo-bar { + /// A constructor + constructor(foo: u8, bar: u8); + + /// A method + foo: func() -> string; + + /// A method + set-foo: func(foo: string); + + /// A static method + id: static func() -> u32; + + /// A method + baz: x; + } }"#, ) .unwrap(); @@ -1682,18 +1418,17 @@ mod test { #[test] fn variant_roundtrip() { roundtrip::( - Rule::TypeStatement, r#"variant foo { foo, bar(u32), baz(bar), - qux(tuple), + qux(tuple) }"#, r#"variant foo { - foo, - bar(u32), - baz(bar), - qux(tuple), + foo, + bar(u32), + baz(bar), + qux(tuple), }"#, ) .unwrap(); @@ -1702,7 +1437,6 @@ mod test { #[test] fn record_roundtrip() { roundtrip::( - Rule::TypeStatement, r#"record foo-bar2-baz { foo: foo, bar-qux: list, @@ -1710,9 +1444,9 @@ mod test { jam: borrow, }"#, r#"record foo-bar2-baz { - foo: foo, - bar-qux: list, - jam: borrow, + foo: foo, + bar-qux: list, + jam: borrow, }"#, ) .unwrap(); @@ -1721,14 +1455,13 @@ mod test { #[test] fn flags_roundtrip() { roundtrip::( - Rule::TypeStatement, r#"flags %flags { foo, bar, baz }"#, r#"flags %flags { - foo, - bar, - baz, + foo, + bar, + baz, }"#, ) .unwrap(); @@ -1737,14 +1470,13 @@ mod test { #[test] fn enum_roundtrip() { roundtrip::( - Rule::TypeStatement, r#"enum foo { foo, bar, baz }"#, r#"enum foo { - foo, - bar, - baz, + foo, + bar, + baz, }"#, ) .unwrap(); @@ -1753,7 +1485,6 @@ mod test { #[test] fn func_type_alias_roundtrip() { roundtrip::( - Rule::TypeStatement, r#"type x = func(a: /* comment */ string) -> string;"#, r#"type x = func(a: string) -> string;"#, ) @@ -1763,7 +1494,6 @@ mod test { #[test] fn type_alias_roundtrip() { roundtrip::( - Rule::TypeStatement, r#"type x = tuple>, option>, result, result, result<_, string>, result, borrow, y>;"#, r#"type x = tuple>, option>, result, result, result<_, string>, result, borrow, y>;"#, ) @@ -1773,11 +1503,11 @@ mod test { #[test] fn interface_roundtrip() { roundtrip::( - Rule::InterfaceDecl, r#"interface foo { /// Type t type t = list; + /// Use x and y use foo.{ x, y, }; /// Function a @@ -1785,24 +1515,25 @@ mod test { // not a doc comment type x = func() -> list; - + /// Function b b: x; } "#, r#"interface foo { - /// Type t - type t = list; + /// Type t + type t = list; - use foo.{ x, y }; + /// Use x and y + use foo.{ x, y }; - /// Function a - a: func(a: string, b: string) -> string; + /// Function a + a: func(a: string, b: string) -> string; - type x = func() -> list; + type x = func() -> list; - /// Function b - b: x; + /// Function b + b: x; }"#, ) .unwrap(); @@ -1811,7 +1542,6 @@ mod test { #[test] fn world_roundtrip() { roundtrip::( - Rule::WorldDecl, r#"world foo { /// Type t type t = list; @@ -1873,76 +1603,76 @@ mod test { include foo-bar with { foo as bar }; }; -include myworld; +include my-world; } "#, r#"world foo { - /// Type t - type t = list; + /// Type t + type t = list; - type x = func() -> list; + type x = func() -> list; - use foo.{ y }; + use foo.{ y }; - /// Import with function type. - import a: func(a: string, b: string) -> string; + /// Import with function type. + import a: func(a: string, b: string) -> string; - /// Import with identifier. - import b: x; + /// Import with identifier. + import b: x; - /// Import with inline interface. - import c: interface { - /// Function a - a: func(a: string, b: string) -> string; - }; + /// Import with inline interface. + import c: interface { + /// Function a + a: func(a: string, b: string) -> string; + }; - /// Import with package path - import foo:bar/baz@1.0.0; + /// Import with package path + import foo:bar/baz@1.0.0; - /// Export with function type. - export a: func(a: string, b: string) -> string; + /// Export with function type. + export a: func(a: string, b: string) -> string; - /// Export with identifier. - export b: x; + /// Export with identifier. + export b: x; - /// Export with inline interface. - export c: interface { - /// Function a - a: func(a: string, b: string) -> string; - }; - - /// Export with package path - export foo:bar/baz@1.0.0; - - /// Include world from package path with 2 renames. - include foo:bar/baz with { - a as a1, - b as b1, - }; - - /// Include world from package path with 1 rename. - include foo:bar/baz with { - foo as foo1, - }; - - /// Include world from package path (spacing). - include foo:bar/baz with { - foo as foo1, - }; - - /// Include world from package path newline delimited renaming. - include foo:bar/baz with { - foo as foo1, - bar as bar1, - }; - - /// Include local world. - include foo-bar; - - /// Include local world with renaming. - include foo-bar with { - foo as bar, - }; + /// Export with inline interface. + export c: interface { + /// Function a + a: func(a: string, b: string) -> string; + }; + + /// Export with package path + export foo:bar/baz@1.0.0; + + /// Include world from package path with 2 renames. + include foo:bar/baz with { + a as a1, + b as b1, + }; + + /// Include world from package path with 1 rename. + include foo:bar/baz with { + foo as foo1, + }; + + /// Include world from package path (spacing). + include foo:bar/baz with { + foo as foo1, + }; + + /// Include world from package path newline delimited renaming. + include foo:bar/baz with { + foo as foo1, + bar as bar1, + }; + + /// Include local world. + include foo-bar; + + /// Include local world with renaming. + include foo-bar with { + foo as bar, + }; }"#, ) .unwrap(); diff --git a/crates/wac-parser/src/lexer.rs b/crates/wac-parser/src/lexer.rs new file mode 100644 index 0000000..dae9886 --- /dev/null +++ b/crates/wac-parser/src/lexer.rs @@ -0,0 +1,948 @@ +//! The module for the WAC lexer implementation. + +use logos::{Logos, SpannedIter}; +use serde::{Serialize, Serializer}; +use std::fmt; + +/// Represents a span in a source string. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Span<'a> { + source: &'a str, + /// The start of the span. + pub start: usize, + /// The end of the span. + pub end: usize, +} + +impl<'a> Span<'a> { + fn from_lexer(lexer: &logos::Lexer<'a, T>) -> Self + where + T: logos::Logos<'a, Source = str>, + { + let source = lexer.source(); + let span = lexer.span(); + Self { + source, + start: span.start, + end: span.end, + } + } + + pub(crate) fn from_span(source: &'a str, span: &logos::Span) -> Self { + Self { + source, + start: span.start, + end: span.end, + } + } + + /// Gets the source code associated with the span. + pub fn source(&self) -> &'a str { + self.source + } + + /// Returns the spanned string. + pub fn as_str(&self) -> &'a str { + &self.source[self.start..self.end] + } +} + +impl Serialize for Span<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + use serde::ser::SerializeStruct; + + let mut s = serializer.serialize_struct("Span", 3)?; + s.serialize_field("str", self.as_str())?; + s.serialize_field("start", &self.start)?; + s.serialize_field("end", &self.end)?; + s.end() + } +} + +/// Represents a lexer error. +#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum Error { + /// An unexpected token was encountered. + #[default] + #[error("unexpected token was encountered")] + UnexpectedToken, + /// An unterminated string was encountered. + #[error("an unterminated string was encountered")] + UnterminatedString, + /// An unterminated comment was encountered. + #[error("an unterminated comment was encountered")] + UnterminatedComment, + /// A disallowed bidirectional override codepoint was encountered. + #[error("disallowed bidirectional override codepoint `{c}`", c = .0.escape_unicode())] + DisallowedBidirectionalOverride(char), + /// A discouraged Unicode codepoint was encountered. + #[error("codepoint `{c}` is discouraged by Unicode", c = .0.escape_unicode())] + DiscouragedUnicodeCodepoint(char), + /// A disallowed control code was encountered. + #[error("disallowed control code '{c}'", c = .0.escape_unicode())] + DisallowedControlCode(char), +} + +impl From<()> for Error { + fn from(_: ()) -> Self { + Error::UnexpectedToken + } +} + +fn detect_invalid_input(source: &str) -> Result<(), (Error, Span)> { + for (offset, ch) in source.char_indices() { + match ch { + '\r' | '\t' | '\n' => {} + + // Bidirectional override codepoints can be used to craft source code that + // appears to have a different meaning than its actual meaning. See + // [CVE-2021-42574] for background and motivation. + // + // [CVE-2021-42574]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574 + '\u{202a}' | '\u{202b}' | '\u{202c}' | '\u{202d}' | '\u{202e}' | '\u{2066}' + | '\u{2067}' | '\u{2068}' | '\u{2069}' => { + return Err(( + Error::DisallowedBidirectionalOverride(ch), + Span::from_span(source, &(offset..offset + ch.len_utf8())), + )); + } + + // Disallow several characters which are deprecated or discouraged in Unicode. + // + // U+149 deprecated; see Unicode 13.0.0, sec. 7.1 Latin, Compatibility Digraphs. + // U+673 deprecated; see Unicode 13.0.0, sec. 9.2 Arabic, Additional Vowel Marks. + // U+F77 and U+F79 deprecated; see Unicode 13.0.0, sec. 13.4 Tibetan, Vowels. + // U+17A3 and U+17A4 deprecated, and U+17B4 and U+17B5 discouraged; see + // Unicode 13.0.0, sec. 16.4 Khmer, Characters Whose Use Is Discouraged. + '\u{149}' | '\u{673}' | '\u{f77}' | '\u{f79}' | '\u{17a3}' | '\u{17a4}' + | '\u{17b4}' | '\u{17b5}' => { + return Err(( + Error::DiscouragedUnicodeCodepoint(ch), + Span::from_span(source, &(offset..offset + ch.len_utf8())), + )); + } + + // Disallow control codes other than the ones explicitly recognized above, + // so that viewing a wit file on a terminal doesn't have surprising side + // effects or appear to have a different meaning than its actual meaning. + ch if ch.is_control() => { + return Err(( + Error::DisallowedControlCode(ch), + Span::from_span(source, &(offset..offset + ch.len_utf8())), + )); + } + + _ => {} + } + } + + Ok(()) +} + +/// Represents a WAC token. +#[derive(Logos, Debug, Clone, Copy, PartialEq, Eq)] +#[logos(error = Error)] +#[logos(skip r"[ \t\n\f]+")] +#[logos(subpattern id = r"%?[a-z][a-z0-9]*(-[a-z][a-z0-9]*)*")] +#[logos(subpattern package_name = r"(?&id)(:(?&id))+")] +#[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-]+)*))?")] +pub enum Token { + /// A comment. + #[regex(r"//[^\n]*", logos::skip)] + Comment, + + /// A block comment. + #[token(r"/*", helpers::skip_block_comment)] + BlockComment, + + /// An identifier. + #[regex(r"(?&id)")] + Ident, + + /// A string literal. + #[token("\"", helpers::string)] + String, + + /// A package name. + #[regex(r"(?&id)(:(?&id))+")] + PackageName, + + /// A package path with optional semantic version. + #[regex(r"(?&package_name)(/(?&id))+(@(?&semver))?")] + PackagePath, + + /// The `import` keyword. + #[token("import")] + ImportKeyword, + /// The `with` keyword. + #[token("with")] + WithKeyword, + /// The `type` keyword. + #[token("type")] + TypeKeyword, + /// The `tuple` keyword. + #[token("tuple")] + TupleKeyword, + /// The `list` keyword. + #[token("list")] + ListKeyword, + /// The `option` keyword. + #[token("option")] + OptionKeyword, + /// The `result` keyword. + #[token("result")] + ResultKeyword, + /// The `borrow` keyword. + #[token("borrow")] + BorrowKeyword, + /// The `resource` keyword. + #[token("resource")] + ResourceKeyword, + /// The `variant` keyword. + #[token("variant")] + VariantKeyword, + /// The `record` keyword. + #[token("record")] + RecordKeyword, + /// The `flags` keyword. + #[token("flags")] + FlagsKeyword, + /// The `enum` keyword. + #[token("enum")] + EnumKeyword, + /// The `func` keyword. + #[token("func")] + FuncKeyword, + /// The `static` keyword. + #[token("static")] + StaticKeyword, + /// The `constructor` keyword. + #[token("constructor")] + ConstructorKeyword, + /// The `u8` keyword. + #[token("u8")] + U8Keyword, + /// The `s8` keyword. + #[token("s8")] + S8Keyword, + /// The `u16` keyword. + #[token("u16")] + U16Keyword, + /// The `s16` keyword. + #[token("s16")] + S16Keyword, + /// The `u32` keyword. + #[token("u32")] + U32Keyword, + /// The `s32` keyword. + #[token("s32")] + S32Keyword, + /// The `u64` keyword. + #[token("u64")] + U64Keyword, + /// The `s64` keyword. + #[token("s64")] + S64Keyword, + /// The `float32` keyword. + #[token("float32")] + Float32Keyword, + /// The `float64` keyword. + #[token("float64")] + Float64Keyword, + /// The `char` keyword. + #[token("char")] + CharKeyword, + /// The `bool` keyword. + #[token("bool")] + BoolKeyword, + /// The `string` keyword. + #[token("string")] + StringKeyword, + /// The `interface` keyword. + #[token("interface")] + InterfaceKeyword, + /// The `world` keyword. + #[token("world")] + WorldKeyword, + /// The `export` keyword. + #[token("export")] + ExportKeyword, + /// The `new` keyword. + #[token("new")] + NewKeyword, + /// The `let` keyword. + #[token("let")] + LetKeyword, + /// The `use` keyword. + #[token("use")] + UseKeyword, + /// The `include` keyword. + #[token("include")] + IncludeKeyword, + /// The `as` keyword. + #[token("as")] + AsKeyword, + + /// The `;` symbol. + #[token(";")] + Semicolon, + /// The `{` symbol. + #[token("{")] + OpenBrace, + /// The `}` symbol. + #[token("}")] + CloseBrace, + /// The `:` symbol. + #[token(":")] + Colon, + /// The `=` symbol. + #[token("=")] + Equals, + /// The `(` symbol. + #[token("(")] + OpenParen, + /// The `)` symbol. + #[token(")")] + CloseParen, + /// The `->` symbol. + #[token("->")] + Arrow, + /// The `<` symbol. + #[token("<")] + OpenAngle, + /// The `>` symbol. + #[token(">")] + CloseAngle, + /// The `_` symbol. + #[token("_")] + Underscore, + /// The `[` symbol. + #[token("[")] + OpenBracket, + /// The `]` symbol. + #[token("]")] + CloseBracket, + /// The `.` symbol. + #[token(".")] + Dot, + /// The `...` symbol. + #[token("...")] + Ellipsis, + /// The `,` symbol. + #[token(",")] + Comma, + /// The `/` symbol. + #[token("/")] + Slash, + /// The `@` symbol. + #[token("@")] + At, +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Token::Comment | Token::BlockComment => write!(f, "comment"), + Token::Ident => write!(f, "identifier"), + Token::String => write!(f, "string literal"), + Token::PackageName => write!(f, "package name"), + Token::PackagePath => write!(f, "package path"), + Token::ImportKeyword => write!(f, "`import` keyword"), + Token::WithKeyword => write!(f, "`with` keyword"), + Token::TypeKeyword => write!(f, "`type` keyword"), + Token::TupleKeyword => write!(f, "`tuple` keyword"), + Token::ListKeyword => write!(f, "`list` keyword"), + Token::OptionKeyword => write!(f, "`option` keyword"), + Token::ResultKeyword => write!(f, "`result` keyword"), + Token::BorrowKeyword => write!(f, "`borrow` keyword"), + Token::ResourceKeyword => write!(f, "`resource` keyword"), + Token::VariantKeyword => write!(f, "`variant` keyword"), + Token::RecordKeyword => write!(f, "`record` keyword"), + Token::FlagsKeyword => write!(f, "`flags` keyword"), + Token::EnumKeyword => write!(f, "`enum` keyword"), + Token::FuncKeyword => write!(f, "`func` keyword"), + Token::StaticKeyword => write!(f, "`static` keyword"), + Token::ConstructorKeyword => write!(f, "`constructor` keyword"), + Token::U8Keyword => write!(f, "`u8` keyword"), + Token::S8Keyword => write!(f, "`s8` keyword"), + Token::U16Keyword => write!(f, "`u16` keyword"), + Token::S16Keyword => write!(f, "`s16` keyword"), + Token::U32Keyword => write!(f, "`u32` keyword"), + Token::S32Keyword => write!(f, "`s32` keyword"), + Token::U64Keyword => write!(f, "`u64` keyword"), + Token::S64Keyword => write!(f, "`s64` keyword"), + Token::Float32Keyword => write!(f, "`float32` keyword"), + Token::Float64Keyword => write!(f, "`float64` keyword"), + Token::CharKeyword => write!(f, "`char` keyword"), + Token::BoolKeyword => write!(f, "`bool` keyword"), + Token::StringKeyword => write!(f, "`string` keyword"), + Token::InterfaceKeyword => write!(f, "`interface` keyword"), + Token::WorldKeyword => write!(f, "`world` keyword"), + Token::ExportKeyword => write!(f, "`export` keyword"), + Token::NewKeyword => write!(f, "`new` keyword"), + Token::LetKeyword => write!(f, "`let` keyword"), + Token::UseKeyword => write!(f, "`use` keyword"), + Token::IncludeKeyword => write!(f, "`include` keyword"), + Token::AsKeyword => write!(f, "`as` keyword"), + Token::Semicolon => write!(f, "`;`"), + Token::OpenBrace => write!(f, "`{{`"), + Token::CloseBrace => write!(f, "`}}`"), + Token::Colon => write!(f, "`:`"), + Token::Equals => write!(f, "`=`"), + Token::OpenParen => write!(f, "`(`"), + Token::CloseParen => write!(f, "`)`"), + Token::Arrow => write!(f, "`->`"), + Token::OpenAngle => write!(f, "`<`"), + Token::CloseAngle => write!(f, "`>`"), + Token::Underscore => write!(f, "`_`"), + Token::OpenBracket => write!(f, "`[`"), + Token::CloseBracket => write!(f, "`]`"), + Token::Dot => write!(f, "`.`"), + Token::Ellipsis => write!(f, "`...`"), + Token::Comma => write!(f, "`,`"), + Token::Slash => write!(f, "`/`"), + Token::At => write!(f, "`@`"), + } + } +} + +mod helpers { + use super::{Error, Token}; + use logos::{FilterResult, Logos}; + + /// Represents a WAC comment token. + #[derive(Logos, Debug, Clone, Copy, PartialEq, Eq)] + #[logos(error = Error)] + #[logos(skip r"[ \t\n\f]+")] + pub enum CommentToken<'a> { + /// A comment. + #[regex(r"//[^\n]*")] + Comment(&'a str), + + /// A block comment. + #[token(r"/*", block_comment)] + BlockComment(&'a str), + } + + pub fn string(lex: &mut logos::Lexer) -> Result<(), Error> { + let remainder = lex.remainder(); + let len = remainder.find('"').ok_or(Error::UnterminatedString)?; + lex.bump(len + 1 /* opening quote */); + Ok(()) + } + + pub fn block_comment_length(bytes: &[u8]) -> Option { + let mut iter = bytes.iter().copied().peekable(); + let mut depth = 1; + let mut len = 0; + while depth > 0 { + len += 1; + match iter.next()? { + b'/' if iter.peek() == Some(&b'*') => { + depth += 1; + len += 1; + iter.next(); + } + b'*' if iter.peek() == Some(&b'/') => { + depth -= 1; + len += 1; + iter.next(); + } + _ => {} + } + } + + Some(len + 2 /* opening tokens */) + } + + pub fn block_comment<'a>( + lex: &mut logos::Lexer<'a, CommentToken<'a>>, + ) -> Result<&'a str, Error> { + let span = lex.span(); + match block_comment_length(lex.remainder().as_bytes()) { + Some(len) => { + let s = &lex.source()[span.start..span.start + len]; + lex.bump(len - 2 /* opening tokens */); + Ok(s) + } + None => { + lex.bump(lex.remainder().len()); + Err(Error::UnterminatedComment) + } + } + } + + pub fn skip_block_comment(lex: &mut logos::Lexer) -> FilterResult<(), Error> { + match block_comment_length(lex.remainder().as_bytes()) { + Some(len) => { + lex.bump(len - 2 /* opening tokens */); + FilterResult::Skip + } + None => { + lex.bump(lex.remainder().len()); + FilterResult::Error(Error::UnterminatedComment) + } + } + } +} + +/// The result type for the lexer. +pub type LexerResult<'a, T> = Result; + +/// Implements a WAC lexer. +pub struct Lexer<'a>(SpannedIter<'a, Token>); + +impl<'a> Lexer<'a> { + /// Creates a new lexer for the given source string. + pub fn new(source: &'a str) -> Result)> { + detect_invalid_input(source)?; + Ok(Self(Token::lexer(source).spanned())) + } + + /// Source from which this lexer is reading tokens. + pub fn source<'b>(&'b self) -> &'a str { + self.0.source() + } + + /// Gets the current span of the lexer. + pub fn span<'b>(&'b self) -> Span<'a> { + Span::from_lexer(&self.0) + } + + /// Peeks at the next token. + pub fn peek<'b>(&'b self) -> Option<(LexerResult<'a, Token>, Span<'a>)> { + let mut lexer = self.0.clone().spanned(); + lexer + .next() + .map(|(r, s)| (r, Span::from_span(self.0.source(), &s))) + } + + /// Peeks at the token after the next token. + pub fn peek2<'b>(&'b self) -> Option<(LexerResult<'a, Token>, Span<'a>)> { + let mut lexer = self.0.clone().spanned(); + lexer.next(); + lexer + .next() + .map(|(r, s)| (r, Span::from_span(self.0.source(), &s))) + } + + /// Consumes available documentation comment tokens. + pub fn comments<'b>(&'b self) -> Result)>, (Error, Span<'a>)> { + let mut comments = Vec::new(); + let mut lexer = self.0.clone().morph::().spanned(); + while let Some((Ok(token), span)) = lexer.next() { + match token { + helpers::CommentToken::Comment(c) | helpers::CommentToken::BlockComment(c) => { + let c = if let Some(c) = c.strip_prefix("///") { + c.trim() + } else if let Some(c) = c.strip_prefix("/**") { + if c == "/" { + continue; + } + c.strip_suffix("*/").unwrap().trim() + } else { + continue; + }; + comments.push(( + c, + Span { + source: self.0.source(), + start: span.start, + end: span.end, + }, + )); + } + } + } + Ok(comments) + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = (LexerResult<'a, Token>, Span<'a>); + + fn next(&mut self) -> Option { + self.0 + .next() + .map(|(r, s)| (r, Span::from_span(self.0.source(), &s))) + } +} + +#[cfg(test)] +mod test { + use super::*; + use logos::{Logos, Source}; + use std::{fmt, ops::Range}; + + //use super::*; + + #[allow(clippy::type_complexity)] + pub fn assert_lex<'a, Token>( + source: &'a Token::Source, + tokens: &[( + Result, + &'a ::Slice, + Range, + )], + ) where + Token: Logos<'a> + fmt::Debug + PartialEq, + Token::Extras: Default, + { + let mut lex = Token::lexer(source); + + for tuple in tokens { + assert_eq!( + &(lex.next().expect("unexpected end"), lex.slice(), lex.span()), + tuple + ); + } + + assert_eq!(lex.next(), None, "tokens remain"); + } + + #[test] + fn comments() { + assert_lex::( + r#" + // + // comment + /**/ + /* a block comment */ + /* a multi + line comment + */ + /* a /* /* deeply */ nested */ block comment */ + "#, + &[], + ); + } + + #[test] + fn unterminated_comment() { + let source = r#"/* /* unterminated */"#; + + assert_lex::( + source, + &[( + Err(Error::UnterminatedComment), + "/* /* unterminated */", + 0..21, + )], + ); + } + + #[test] + fn ident() { + assert_lex( + r#" + foo + foo123 + f-b + foo-bar123 + foo0123-bar0123-baz0123 + %interface + "#, + &[ + (Ok(Token::Ident), "foo", 13..16), + (Ok(Token::Ident), "foo123", 29..35), + (Ok(Token::Ident), "f-b", 48..51), + (Ok(Token::Ident), "foo-bar123", 64..74), + (Ok(Token::Ident), "foo0123-bar0123-baz0123", 87..110), + (Ok(Token::Ident), "%interface", 123..133), + ], + ); + } + + #[test] + fn string() { + assert_lex( + r#" + "" + "foo" + "foo bar" + "foo + bar" + "#, + &[ + (Ok(Token::String), "\"\"", 13..15), + (Ok(Token::String), "\"foo\"", 28..33), + (Ok(Token::String), "\"foo bar\"", 46..56), + (Ok(Token::String), "\"foo\n bar\"", 69..90), + ], + ); + } + + #[test] + fn package_path() { + assert_lex( + r#" +foo:bar/baz/qux/jam +foo:bar:baz:qux/jam +foo:bar/baz@0.0.4 +foo:bar/baz@1.2.3 +foo:bar/baz@10.20.30 +foo:bar/baz@1.1.2-prerelease+meta +foo:bar/baz@1.1.2+meta +foo:bar/baz@1.1.2+meta-valid +foo:bar/baz@1.0.0-alpha +foo:bar/baz@1.0.0-beta +foo:bar/baz@1.0.0-alpha.beta +foo:bar/baz@1.0.0-alpha.beta.1 +foo:bar/baz@1.0.0-alpha.1 +foo:bar/baz@1.0.0-alpha0.valid +foo:bar/baz@1.0.0-alpha.0valid +foo:bar/baz@1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay +foo:bar/baz@1.0.0-rc.1+build.1 +foo:bar/baz@2.0.0-rc.1+build.123 +foo:bar/baz@1.2.3-beta +foo:bar/baz@10.2.3-DEV-SNAPSHOT +foo:bar/baz@1.2.3-SNAPSHOT-123 +foo:bar/baz@1.0.0 +foo:bar/baz@2.0.0 +foo:bar/baz@1.1.7 +foo:bar/baz@2.0.0+build.1848 +foo:bar/baz@2.0.1-alpha.1227 +foo:bar/baz@1.0.0-alpha+beta +foo:bar/baz@1.2.3----RC-SNAPSHOT.12.9.1--.12+788 +foo:bar/baz@1.2.3----R-S.12.9.1--.12+meta +foo:bar/baz@1.2.3----RC-SNAPSHOT.12.9.1--.12 +foo:bar/baz@1.0.0+0.build.1-rc.10000aaa-kk-0.1 +foo:bar/baz@99999999999999999999999.999999999999999999.99999999999999999 +foo:bar/baz@1.0.0-0A.is.legal +"#, + &[ + (Ok(Token::PackagePath), "foo:bar/baz/qux/jam", 1..20), + (Ok(Token::PackagePath), "foo:bar:baz:qux/jam", 21..40), + (Ok(Token::PackagePath), "foo:bar/baz@0.0.4", 41..58), + (Ok(Token::PackagePath), "foo:bar/baz@1.2.3", 59..76), + (Ok(Token::PackagePath), "foo:bar/baz@10.20.30", 77..97), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.1.2-prerelease+meta", + 98..131, + ), + (Ok(Token::PackagePath), "foo:bar/baz@1.1.2+meta", 132..154), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.1.2+meta-valid", + 155..183, + ), + (Ok(Token::PackagePath), "foo:bar/baz@1.0.0-alpha", 184..207), + (Ok(Token::PackagePath), "foo:bar/baz@1.0.0-beta", 208..230), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha.beta", + 231..259, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha.beta.1", + 260..290, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha.1", + 291..316, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha0.valid", + 317..347, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha.0valid", + 348..378, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay", + 379..445, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-rc.1+build.1", + 446..476, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@2.0.0-rc.1+build.123", + 477..509, + ), + (Ok(Token::PackagePath), "foo:bar/baz@1.2.3-beta", 510..532), + ( + Ok(Token::PackagePath), + "foo:bar/baz@10.2.3-DEV-SNAPSHOT", + 533..564, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.2.3-SNAPSHOT-123", + 565..595, + ), + (Ok(Token::PackagePath), "foo:bar/baz@1.0.0", 596..613), + (Ok(Token::PackagePath), "foo:bar/baz@2.0.0", 614..631), + (Ok(Token::PackagePath), "foo:bar/baz@1.1.7", 632..649), + ( + Ok(Token::PackagePath), + "foo:bar/baz@2.0.0+build.1848", + 650..678, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@2.0.1-alpha.1227", + 679..707, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-alpha+beta", + 708..736, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.2.3----RC-SNAPSHOT.12.9.1--.12+788", + 737..785, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.2.3----R-S.12.9.1--.12+meta", + 786..827, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.2.3----RC-SNAPSHOT.12.9.1--.12", + 828..872, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0+0.build.1-rc.10000aaa-kk-0.1", + 873..919, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@99999999999999999999999.999999999999999999.99999999999999999", + 920..992, + ), + ( + Ok(Token::PackagePath), + "foo:bar/baz@1.0.0-0A.is.legal", + 993..1022, + ), + ], + ); + } + + #[test] + fn keywords() { + assert_lex( + r#" +import +with +type +tuple +list +option +result +borrow +resource +variant +record +flags +enum +func +static +constructor +u8 +s8 +u16 +s16 +u32 +s32 +u64 +s64 +float32 +float64 +char +bool +string +interface +world +export +new +let +use +include +as + "#, + &[ + (Ok(Token::ImportKeyword), "import", 1..7), + (Ok(Token::WithKeyword), "with", 8..12), + (Ok(Token::TypeKeyword), "type", 13..17), + (Ok(Token::TupleKeyword), "tuple", 18..23), + (Ok(Token::ListKeyword), "list", 24..28), + (Ok(Token::OptionKeyword), "option", 29..35), + (Ok(Token::ResultKeyword), "result", 36..42), + (Ok(Token::BorrowKeyword), "borrow", 43..49), + (Ok(Token::ResourceKeyword), "resource", 50..58), + (Ok(Token::VariantKeyword), "variant", 59..66), + (Ok(Token::RecordKeyword), "record", 67..73), + (Ok(Token::FlagsKeyword), "flags", 74..79), + (Ok(Token::EnumKeyword), "enum", 80..84), + (Ok(Token::FuncKeyword), "func", 85..89), + (Ok(Token::StaticKeyword), "static", 90..96), + (Ok(Token::ConstructorKeyword), "constructor", 97..108), + (Ok(Token::U8Keyword), "u8", 109..111), + (Ok(Token::S8Keyword), "s8", 112..114), + (Ok(Token::U16Keyword), "u16", 115..118), + (Ok(Token::S16Keyword), "s16", 119..122), + (Ok(Token::U32Keyword), "u32", 123..126), + (Ok(Token::S32Keyword), "s32", 127..130), + (Ok(Token::U64Keyword), "u64", 131..134), + (Ok(Token::S64Keyword), "s64", 135..138), + (Ok(Token::Float32Keyword), "float32", 139..146), + (Ok(Token::Float64Keyword), "float64", 147..154), + (Ok(Token::CharKeyword), "char", 155..159), + (Ok(Token::BoolKeyword), "bool", 160..164), + (Ok(Token::StringKeyword), "string", 165..171), + (Ok(Token::InterfaceKeyword), "interface", 172..181), + (Ok(Token::WorldKeyword), "world", 182..187), + (Ok(Token::ExportKeyword), "export", 188..194), + (Ok(Token::NewKeyword), "new", 195..198), + (Ok(Token::LetKeyword), "let", 199..202), + (Ok(Token::UseKeyword), "use", 203..206), + (Ok(Token::IncludeKeyword), "include", 207..214), + (Ok(Token::AsKeyword), "as", 215..217), + ], + ); + } + + #[test] + fn symbols() { + assert_lex( + r#";{}:=()-><>_[]. ...,/@"#, + &[ + (Ok(Token::Semicolon), ";", 0..1), + (Ok(Token::OpenBrace), "{", 1..2), + (Ok(Token::CloseBrace), "}", 2..3), + (Ok(Token::Colon), ":", 3..4), + (Ok(Token::Equals), "=", 4..5), + (Ok(Token::OpenParen), "(", 5..6), + (Ok(Token::CloseParen), ")", 6..7), + (Ok(Token::Arrow), "->", 7..9), + (Ok(Token::OpenAngle), "<", 9..10), + (Ok(Token::CloseAngle), ">", 10..11), + (Ok(Token::Underscore), "_", 11..12), + (Ok(Token::OpenBracket), "[", 12..13), + (Ok(Token::CloseBracket), "]", 13..14), + (Ok(Token::Dot), ".", 14..15), + (Ok(Token::Ellipsis), "...", 16..19), + (Ok(Token::Comma), ",", 19..20), + (Ok(Token::Slash), "/", 20..21), + (Ok(Token::At), "@", 21..22), + ], + ); + } +} diff --git a/crates/wac-parser/src/lib.rs b/crates/wac-parser/src/lib.rs index 16b373f..0ae26ca 100644 --- a/crates/wac-parser/src/lib.rs +++ b/crates/wac-parser/src/lib.rs @@ -2,6 +2,155 @@ #![deny(missing_docs)] +use anyhow::Chain; +use lexer::Span; +use owo_colors::{OwoColorize, Style}; +use std::{ + fmt::{self, Write}, + path::Path, +}; + pub mod ast; -pub mod parser; +pub mod lexer; +pub mod printer; pub mod resolution; + +/// Gets the 1-based line and column of a position within a source. +pub(crate) fn line_column(source: &str, pos: usize) -> (usize, usize) { + let mut cur = 0; + // Use split_terminator instead of lines so that if there is a `\r`, + // it is included in the offset calculation. The `+1` values below + // account for the `\n`. + for (i, line) in source.split_terminator('\n').enumerate() { + if cur + line.len() + 1 > pos { + return (i + 1, pos - cur + 1); + } + + cur += line.len() + 1; + } + + (source.lines().count() + 1, 1) +} + +/// Implemented on spanned error types. +pub trait Spanned { + /// Gets the span of the error. + fn span(&self) -> Span; +} + +/// A formatter for spanned errors. +pub struct ErrorFormatter { + path: P, + error: E, + colorize: bool, +} + +impl ErrorFormatter +where + P: AsRef, + E: std::error::Error + Spanned, +{ + /// Creates a new error formatter. + pub fn new(path: P, error: E, colorize: bool) -> Self { + Self { + path, + error, + colorize, + } + } +} + +struct Indented<'a, D> { + inner: &'a mut D, + number: Option, + started: bool, +} + +// Copied from anyhow's error formatter +// https://github.com/dtolnay/anyhow/blob/05e413219e97f101d8f39a90902e5c5d39f951fe/src/fmt.rs#L73 +impl Write for Indented<'_, T> +where + T: Write, +{ + fn write_str(&mut self, s: &str) -> fmt::Result { + for (i, line) in s.split('\n').enumerate() { + if !self.started { + self.started = true; + match self.number { + Some(number) => write!(self.inner, "{: >5}: ", number)?, + None => self.inner.write_str(" ")?, + } + } else if i > 0 { + self.inner.write_char('\n')?; + if self.number.is_some() { + self.inner.write_str(" ")?; + } else { + self.inner.write_str(" ")?; + } + } + + self.inner.write_str(line)?; + } + + Ok(()) + } +} + +impl fmt::Display for ErrorFormatter +where + P: AsRef, + E: std::error::Error + Spanned, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let span = self.error.span(); + let (line, column) = line_column(span.source(), span.start); + let snippet = span.source().lines().nth(line - 1).unwrap_or(""); + + let (bold, blue) = if self.colorize { + (Style::new().bold(), Style::new().blue().bold()) + } else { + (Style::new(), Style::new()) + }; + + write!( + f, + "{error}\n {arrow} {path}:{lineno}:{column}\n {bar}\n{line:4} {bar} {snippet}\n {bar} {marker:>0$}", + column, + error = self.error.style(bold), + arrow = "-->".style(blue), + path = self.path.as_ref().display(), + lineno = line, + bar = "|".style(blue), + line = line.style(blue), + marker = "^".style(blue), + )?; + + if let Some(s) = span.source().get(span.start..span.end) { + for _ in s.chars().skip(2) { + write!(f, "{}", "-".style(blue))?; + } + + if s.len() > 1 { + write!(f, "{}", "^".style(blue))?; + } + } + + writeln!(f)?; + + if let Some(cause) = self.error.source() { + write!(f, "\nCaused by:")?; + let multiple = cause.source().is_some(); + for (n, error) in Chain::new(cause).enumerate() { + writeln!(f)?; + let mut indented = Indented { + inner: f, + number: if multiple { Some(n) } else { None }, + started: false, + }; + write!(indented, "{}", error)?; + } + } + + Ok(()) + } +} diff --git a/crates/wac-parser/src/parser.rs b/crates/wac-parser/src/parser.rs deleted file mode 100644 index 786bd51..0000000 --- a/crates/wac-parser/src/parser.rs +++ /dev/null @@ -1,181 +0,0 @@ -//! Module for the parser implementation. - -#[allow(missing_docs)] -mod pest { - use pest_derive::Parser; - - /// Implements a WAC document parser. - #[derive(Parser)] - #[grammar = "../wac.pest"] - pub struct DocumentParser; - - impl Rule { - /// Renames a rule to a more human-readable name. - pub fn rename(&self) -> &str { - match self { - Self::EOI => "end of input", - Self::WHITESPACE | Self::DelimitingSpace => "whitespace", - Self::COMMENT => "comment", - - Self::DocComment => "documentation comment", - Self::LineComment => "line comment", - Self::BlockComment => "block comment", - - Self::Document | Self::TopLevelStatement | Self::Statement => "statement", - Self::ImportStatement => "import statement", - Self::TypeStatement => "type statement", - Self::TypeDecl => "type declaration", - Self::ResourceDecl => "resource declaration", - Self::ResourceBody => "resource body", - Self::VariantDecl => "variant declaration", - Self::VariantBody => "variant body", - Self::VariantCase => "variant case", - Self::VariantType => "variant type", - Self::RecordDecl => "record declaration", - Self::RecordBody => "record body", - Self::FlagsDecl => "flags declaration", - Self::FlagsBody => "flags body", - Self::EnumDecl => "enum declaration", - Self::EnumBody => "enum body", - Self::TypeAlias => "type alias", - Self::InterfaceDecl => "interface declaration", - Self::WorldDecl => "world declaration", - Self::LetStatement => "let statement", - Self::ExportStatement => "export statement", - - Self::ImportType => "import type", - Self::WithClause => "with clause", - Self::PackagePath => "package path", - Self::PackageName => "package name", - Self::PackageVersion => "package version", - - Self::TypeAliasKind => "type alias", - Self::FuncType => "function type", - Self::Type | Self::OmitType => "value type", - Self::Tuple => "tuple", - Self::List => "list", - Self::Option => "option", - Self::Result => "result", - Self::SpecifiedResult => "specified result", - Self::Borrow => "borrow", - - Self::ResourceMethod => "resource method", - Self::Constructor => "constructor", - Self::Method => "method", - Self::FuncTypeRef => "function type reference", - Self::ParamList => "parameter list", - Self::ResultList => "result list", - Self::NamedResultList => "named result list", - Self::NamedType => "name and type", - - Self::UseStatement => "use statement", - Self::UseItems => "use items", - Self::UseItem => "use item", - Self::UseAsClause => "use as clause", - Self::UsePath => "use path", - - Self::InterfaceBody => "interface body", - Self::InterfaceItem => "interface item", - Self::InterfaceItemStatement => "interface item statement", - Self::InterfaceExportStatement => "interface export statement", - - Self::WorldBody => "world body", - Self::WorldItem => "world item", - Self::WorldItemStatement => "world item statement", - Self::WorldImportStatement => "world import statement", - Self::WorldExportStatement => "world export statement", - Self::WorldItemDecl => "world item declaration", - Self::WorldNamedItem => "world named item", - Self::InterfaceRef => "interface reference", - Self::ExternType => "extern type", - Self::InlineInterface => "inline interface", - - Self::WorldIncludeStatement => "world include statement", - Self::WorldIncludeWithClause => "world include with clause", - Self::WorldIncludeItem => "world include item", - Self::WorldRef => "world reference", - - Self::Expr => "expression", - Self::PrimaryExpr => "primary expression", - Self::NewExpr => "new expression", - Self::NewExprBody => "new expression body", - Self::InstantiationArgument => "instantiation argument", - Self::NamedInstantiationArgument => "named instantiation argument", - Self::InstantiationArgumentName => "instantiation argument name", - Self::NestedExpr => "nested expression", - Self::PostfixExpr => "postfix expression", - Self::AccessExpr => "access expression", - Self::NamedAccessExpr => "named access expression", - - Self::RawIdent => "raw identifier", - Self::Ident => "identifier", - Self::IdentPart => "identifier part", - - Self::String => "string", - - Self::Keyword => "keyword", - Self::WithKeyword => "`with` keyword", - Self::ImportKeyword => "`import` keyword", - Self::TupleKeyword => "`tuple` keyword", - Self::ListKeyword => "`list` keyword", - Self::OptionKeyword => "`option` keyword", - Self::ResultKeyword => "`result` keyword", - Self::BorrowKeyword => "`borrow` keyword", - Self::ResourceKeyword => "`resource` keyword", - Self::VariantKeyword => "`variant` keyword", - Self::RecordKeyword => "`record` keyword", - Self::FlagsKeyword => "`flags` keyword", - Self::EnumKeyword => "`enum` keyword", - Self::FuncKeyword => "`func` keyword", - Self::StaticKeyword => "`static` keyword", - Self::ConstructorKeyword => "`constructor` keyword", - Self::TypeKeyword => "`type` keyword", - Self::U8Keyword => "`u8` keyword", - Self::S8Keyword => "`s8` keyword", - Self::U16Keyword => "`u16` keyword", - Self::S16Keyword => "`s16` keyword", - Self::U32Keyword => "`u32` keyword", - Self::S32Keyword => "`s32` keyword", - Self::U64Keyword => "`u64` keyword", - Self::S64Keyword => "`s64` keyword", - Self::Float32Keyword => "`float32` keyword", - Self::Float64Keyword => "`float64` keyword", - Self::CharKeyword => "`char` keyword", - Self::BoolKeyword => "`bool` keyword", - Self::StringKeyword => "`string` keyword", - Self::InterfaceKeyword => "`interface` keyword", - Self::WorldKeyword => "`world` keyword", - Self::ExportKeyword => "`export` keyword", - Self::NewKeyword => "`new` keyword", - Self::LetKeyword => "`let` keyword", - Self::UseKeyword => "`use` keyword", - Self::IncludeKeyword => "`include` keyword", - Self::AsKeyword => "`as` keyword", - - Self::Semicolon => "`;`", - Self::OpenBrace => "`{`", - Self::CloseBrace => "`}`", - Self::Colon => "`:`", - Self::Equals => "`=`", - Self::OpenParen => "`(`", - Self::CloseParen => "`)`", - Self::Arrow => "`->`", - Self::OpenAngle => "`<`", - Self::CloseAngle => "`>`", - Self::Percent => "`%`", - Self::Underscore => "`_`", - Self::Hyphen => "`-`", - Self::DoubleQuote => "`\"`", - Self::Slash => "`/`", - Self::At => "`@`", - Self::OpenBracket => "`[`", - Self::CloseBracket => "`]`", - Self::Dot => "`.`", - Self::Ellipsis => "`...`", - } - } - } -} - -pub use pest::DocumentParser; -pub use pest::Rule; diff --git a/crates/wac-parser/src/printer.rs b/crates/wac-parser/src/printer.rs new file mode 100644 index 0000000..f651a43 --- /dev/null +++ b/crates/wac-parser/src/printer.rs @@ -0,0 +1,752 @@ +//! Module for printing WAC documents. + +use crate::ast::*; +use std::fmt::Write; + +/// A printer for WAC documents. +pub struct DocumentPrinter { + writer: W, + space: &'static str, + indent: usize, + indented: bool, +} + +impl DocumentPrinter { + /// Creates a new document printer for the given write. + /// + /// If `space` is `None`, then the printer will use four spaces for + /// indentation. + pub fn new(writer: W, space: Option<&'static str>) -> Self { + Self { + writer, + space: space.unwrap_or(" "), + indent: 0, + indented: false, + } + } + + /// Prints the given document. + pub fn document(&mut self, doc: &Document) -> std::fmt::Result { + for (i, statement) in doc.statements.iter().enumerate() { + if i > 0 { + self.newline()?; + } + + self.statement(statement)?; + self.newline()?; + } + + Ok(()) + } + + /// Prints the given statement. + pub fn statement(&mut self, statement: &Statement) -> std::fmt::Result { + match statement { + Statement::Import(i) => self.import_statement(i), + Statement::Type(t) => self.type_statement(t), + Statement::Let(l) => self.let_statement(l), + Statement::Export(e) => self.export_statement(e), + } + } + + /// Prints the given doc comments. + pub fn docs(&mut self, docs: &[DocComment]) -> std::fmt::Result { + for doc in docs { + for line in doc.comment.lines() { + self.indent()?; + write!(self.writer, "/// {line}", line = line.trim())?; + self.newline()?; + } + } + + Ok(()) + } + + /// Prints the given import statement. + pub fn import_statement(&mut self, statement: &ImportStatement) -> std::fmt::Result { + self.docs(&statement.docs)?; + + self.indent()?; + write!(self.writer, "import {id}", id = statement.id.span.as_str())?; + + if let Some(with) = &statement.with { + write!(self.writer, " with {with}", with = with.span.as_str())?; + } + + write!(self.writer, ": ")?; + self.import_type(&statement.ty)?; + write!(self.writer, ";")?; + + Ok(()) + } + + /// Prints the given import type. + pub fn import_type(&mut self, ty: &ImportType) -> std::fmt::Result { + match ty { + ImportType::Package(p) => self.package_path(p), + ImportType::Func(f) => self.func_type(f), + ImportType::Interface(i) => self.inline_interface(i), + ImportType::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + } + } + + /// Prints the given package path. + pub fn package_path(&mut self, path: &PackagePath) -> std::fmt::Result { + write!(self.writer, "{path}", path = path.span.as_str())?; + Ok(()) + } + + /// Prints the given function type. + pub fn func_type(&mut self, ty: &FuncType) -> std::fmt::Result { + write!(self.writer, "func(")?; + self.named_types(&ty.params)?; + write!(self.writer, ")")?; + + match &ty.results { + ResultList::Empty => Ok(()), + ResultList::Scalar(ty) => { + write!(self.writer, " -> ")?; + self.ty(ty) + } + ResultList::Named(results) => { + write!(self.writer, " -> (")?; + self.named_types(results)?; + write!(self.writer, ")") + } + } + } + + fn named_types(&mut self, types: &[NamedType]) -> std::fmt::Result { + for (i, param) in types.iter().enumerate() { + if i > 0 { + write!(self.writer, ", ")?; + } + + write!(self.writer, "{id}: ", id = param.id.span.as_str())?; + self.ty(¶m.ty)?; + } + + Ok(()) + } + + /// Prints the given type. + pub fn ty(&mut self, ty: &Type) -> std::fmt::Result { + match ty { + Type::U8 => write!(self.writer, "u8"), + Type::S8 => write!(self.writer, "s8"), + Type::U16 => write!(self.writer, "u16"), + Type::S16 => write!(self.writer, "s16"), + Type::U32 => write!(self.writer, "u32"), + Type::S32 => write!(self.writer, "s32"), + Type::U64 => write!(self.writer, "u64"), + Type::S64 => write!(self.writer, "s64"), + Type::Float32 => write!(self.writer, "float32"), + Type::Float64 => write!(self.writer, "float64"), + Type::Char => write!(self.writer, "char"), + Type::Bool => write!(self.writer, "bool"), + Type::String => write!(self.writer, "string"), + Type::Tuple(types) => { + write!(self.writer, "tuple<")?; + for (i, ty) in types.iter().enumerate() { + if i > 0 { + write!(self.writer, ", ")?; + } + + self.ty(ty)?; + } + + write!(self.writer, ">") + } + Type::List(ty) => { + write!(self.writer, "list<")?; + self.ty(ty)?; + write!(self.writer, ">") + } + Type::Option(ty) => { + write!(self.writer, "option<")?; + self.ty(ty)?; + write!(self.writer, ">") + } + Type::Result { ok, err } => match (ok, err) { + (None, None) => write!(self.writer, "result"), + (None, Some(err)) => { + write!(self.writer, "result<_, ")?; + self.ty(err)?; + write!(self.writer, ">") + } + (Some(ok), None) => { + write!(self.writer, "result<")?; + self.ty(ok)?; + write!(self.writer, ">") + } + (Some(ok), Some(err)) => { + write!(self.writer, "result<")?; + self.ty(ok)?; + write!(self.writer, ", ")?; + self.ty(err)?; + write!(self.writer, ">") + } + }, + Type::Borrow(id) => { + write!(self.writer, "borrow<{id}>", id = id.span.as_str()) + } + Type::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + } + } + + /// Prints the given inline interface. + pub fn inline_interface(&mut self, iface: &InlineInterface) -> std::fmt::Result { + write!(self.writer, "interface {{")?; + self.newline()?; + + self.inc(); + for (i, item) in iface.items.iter().enumerate() { + if i > 0 { + self.newline()?; + } + + self.interface_item(item)?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given interface export. + pub fn interface_item(&mut self, item: &InterfaceItem) -> std::fmt::Result { + match item { + InterfaceItem::Use(u) => self.use_type(u), + InterfaceItem::Type(t) => self.item_type_decl(t), + InterfaceItem::Export(e) => self.interface_export(e), + } + } + + /// Prints the given use type. + pub fn use_type(&mut self, use_ty: &Use) -> std::fmt::Result { + self.docs(&use_ty.docs)?; + self.indent()?; + write!(self.writer, "use ")?; + self.use_path(&use_ty.path)?; + + write!(self.writer, ".{{ ")?; + + for (i, item) in use_ty.items.iter().enumerate() { + if i > 0 { + write!(self.writer, ", ")?; + } + + write!(self.writer, "{id}", id = item.id.span.as_str())?; + if let Some(as_id) = &item.as_id { + write!(self.writer, " as {as_id}", as_id = as_id.span.as_str())?; + } + } + + write!(self.writer, " }};") + } + + /// Prints the given use path. + pub fn use_path(&mut self, path: &UsePath) -> std::fmt::Result { + match path { + UsePath::Package(p) => self.package_path(p), + UsePath::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + } + } + + /// Prints the given type declaration. + pub fn item_type_decl(&mut self, decl: &ItemTypeDecl) -> std::fmt::Result { + match decl { + ItemTypeDecl::Resource(r) => self.resource_decl(r), + ItemTypeDecl::Variant(v) => self.variant_decl(v), + ItemTypeDecl::Record(r) => self.record_decl(r), + ItemTypeDecl::Flags(f) => self.flags_decl(f), + ItemTypeDecl::Enum(e) => self.enum_decl(e), + ItemTypeDecl::Alias(a) => self.type_alias(a), + } + } + + /// Prints the given resource declaration. + pub fn resource_decl(&mut self, decl: &ResourceDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "resource {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for (i, method) in decl.methods.iter().enumerate() { + if i > 0 { + self.newline()?; + } + + self.resource_method(method)?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given resource method. + pub fn resource_method(&mut self, method: &ResourceMethod) -> std::fmt::Result { + match method { + ResourceMethod::Constructor(c) => self.constructor(c), + ResourceMethod::Method(m) => self.method(m), + } + } + + /// Prints the given constructor. + pub fn constructor(&mut self, constructor: &Constructor) -> std::fmt::Result { + self.docs(&constructor.docs)?; + self.indent()?; + write!(self.writer, "constructor(")?; + self.named_types(&constructor.params)?; + write!(self.writer, ");") + } + + /// Prints the given method. + pub fn method(&mut self, method: &Method) -> std::fmt::Result { + self.docs(&method.docs)?; + self.indent()?; + write!(self.writer, "{id}: ", id = method.id.span.as_str())?; + + if method.is_static { + write!(self.writer, "static ")?; + } + + self.func_type_ref(&method.ty)?; + write!(self.writer, ";") + } + + /// Prints the given function type reference. + pub fn func_type_ref(&mut self, ty: &FuncTypeRef) -> std::fmt::Result { + match ty { + FuncTypeRef::Func(ty) => self.func_type(ty), + FuncTypeRef::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + } + } + + /// Prints the given variant declaration. + pub fn variant_decl(&mut self, decl: &VariantDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "variant {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for case in &decl.cases { + self.indent()?; + self.variant_case(case)?; + write!(self.writer, ",")?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given variant case. + pub fn variant_case(&mut self, case: &VariantCase) -> std::fmt::Result { + self.docs(&case.docs)?; + self.indent()?; + write!(self.writer, "{id}", id = case.id.span.as_str())?; + + if let Some(ty) = &case.ty { + write!(self.writer, "(")?; + self.ty(ty)?; + write!(self.writer, ")")?; + } + + Ok(()) + } + + /// Prints the given record declaration. + pub fn record_decl(&mut self, decl: &RecordDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "record {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for field in &decl.fields { + self.docs(&field.docs)?; + self.indent()?; + write!(self.writer, "{id}: ", id = field.id.span.as_str())?; + self.ty(&field.ty)?; + write!(self.writer, ",")?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given flags declaration. + pub fn flags_decl(&mut self, decl: &FlagsDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "flags {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for flag in &decl.flags { + self.docs(&flag.docs)?; + self.indent()?; + write!(self.writer, "{id},", id = flag.id.span.as_str())?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given enum declaration. + pub fn enum_decl(&mut self, decl: &EnumDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "enum {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for case in &decl.cases { + self.docs(&case.docs)?; + self.indent()?; + write!(self.writer, "{id},", id = case.id.span.as_str())?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given type alias. + pub fn type_alias(&mut self, alias: &TypeAlias) -> std::fmt::Result { + self.docs(&alias.docs)?; + self.indent()?; + write!(self.writer, "type {id} = ", id = alias.id.span.as_str())?; + match &alias.kind { + TypeAliasKind::Func(ty) => self.func_type(ty)?, + TypeAliasKind::Type(ty) => self.ty(ty)?, + } + + write!(self.writer, ";") + } + + /// Prints the given interface export. + pub fn interface_export(&mut self, export: &InterfaceExport) -> std::fmt::Result { + self.docs(&export.docs)?; + self.indent()?; + write!(self.writer, "{id}: ", id = export.id.span.as_str())?; + self.func_type_ref(&export.ty)?; + write!(self.writer, ";") + } + + /// Prints the given type statement. + pub fn type_statement(&mut self, stmt: &TypeStatement) -> std::fmt::Result { + match stmt { + TypeStatement::Interface(i) => self.interface_decl(i), + TypeStatement::World(w) => self.world_decl(w), + TypeStatement::Type(t) => self.type_decl(t), + } + } + + /// Prints the given interface declaration. + pub fn interface_decl(&mut self, decl: &InterfaceDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "interface {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for (i, item) in decl.items.iter().enumerate() { + if i > 0 { + self.newline()?; + } + + self.interface_item(item)?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given world declaration. + pub fn world_decl(&mut self, decl: &WorldDecl) -> std::fmt::Result { + self.docs(&decl.docs)?; + self.indent()?; + write!(self.writer, "world {id} {{", id = decl.id.span.as_str())?; + self.newline()?; + + self.inc(); + for (i, item) in decl.items.iter().enumerate() { + if i > 0 { + self.newline()?; + } + + self.world_item(item)?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given world item. + pub fn world_item(&mut self, item: &WorldItem) -> std::fmt::Result { + match item { + WorldItem::Use(u) => self.use_type(u), + WorldItem::Type(t) => self.item_type_decl(t), + WorldItem::Import(i) => self.world_import(i), + WorldItem::Export(e) => self.world_export(e), + WorldItem::Include(i) => self.world_include(i), + } + } + + /// Prints the given world import. + pub fn world_import(&mut self, import: &WorldImport) -> std::fmt::Result { + self.docs(&import.docs)?; + self.indent()?; + write!(self.writer, "import ")?; + + self.world_item_path(&import.path)?; + write!(self.writer, ";") + } + + /// Prints the given world export. + pub fn world_export(&mut self, export: &WorldExport) -> std::fmt::Result { + self.docs(&export.docs)?; + self.indent()?; + write!(self.writer, "export ")?; + + self.world_item_path(&export.path)?; + write!(self.writer, ";") + } + + /// Prints the given world item path. + pub fn world_item_path(&mut self, path: &WorldItemPath) -> std::fmt::Result { + match path { + WorldItemPath::Named(n) => self.named_world_item(n), + WorldItemPath::Package(p) => self.package_path(p), + WorldItemPath::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + } + } + + /// Prints the given named world item. + pub fn named_world_item(&mut self, item: &NamedWorldItem) -> std::fmt::Result { + write!(self.writer, "{id}: ", id = item.id.span.as_str())?; + self.extern_type(&item.ty) + } + + /// Prints the given extern type. + pub fn extern_type(&mut self, ty: &ExternType) -> std::fmt::Result { + match ty { + ExternType::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + ExternType::Func(ty) => self.func_type(ty), + ExternType::Interface(i) => self.inline_interface(i), + } + } + + /// Prints the given world include. + pub fn world_include(&mut self, include: &WorldInclude) -> std::fmt::Result { + self.docs(&include.docs)?; + self.indent()?; + write!(self.writer, "include ")?; + self.world_ref(&include.world)?; + + if !include.with.is_empty() { + write!(self.writer, " with {{")?; + self.newline()?; + self.inc(); + + for item in &include.with { + self.indent()?; + write!( + self.writer, + "{source} as {target},", + source = item.from.span.as_str(), + target = item.to.span.as_str() + )?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}")?; + } + + write!(self.writer, ";") + } + + /// Prints the given world reference. + pub fn world_ref(&mut self, reference: &WorldRef) -> std::fmt::Result { + match reference { + WorldRef::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + WorldRef::Package(p) => self.package_path(p), + } + } + + /// Prints the given type declaration. + pub fn type_decl(&mut self, decl: &TypeDecl) -> std::fmt::Result { + match decl { + TypeDecl::Variant(v) => self.variant_decl(v), + TypeDecl::Record(r) => self.record_decl(r), + TypeDecl::Flags(f) => self.flags_decl(f), + TypeDecl::Enum(e) => self.enum_decl(e), + TypeDecl::Alias(a) => self.type_alias(a), + } + } + + /// Prints the given let statement. + pub fn let_statement(&mut self, stmt: &LetStatement) -> std::fmt::Result { + self.docs(&stmt.docs)?; + self.indent()?; + write!(self.writer, "let {id} = ", id = stmt.id.span.as_str())?; + self.expr(&stmt.expr)?; + write!(self.writer, ";") + } + + /// Prints the given expression. + pub fn expr(&mut self, expr: &Expr) -> std::fmt::Result { + self.primary_expr(&expr.primary)?; + for postfix in &expr.postfix { + self.postfix_expr(postfix)?; + } + + Ok(()) + } + + /// Prints the given primary expression. + pub fn primary_expr(&mut self, expr: &PrimaryExpr) -> std::fmt::Result { + match expr { + PrimaryExpr::New(e) => self.new_expr(e), + PrimaryExpr::Nested(e) => { + write!(self.writer, "(")?; + self.expr(&e.0)?; + write!(self.writer, ")") + } + PrimaryExpr::Ident(id) => write!(self.writer, "{id}", id = id.span.as_str()), + } + } + + /// Prints the given new expression. + pub fn new_expr(&mut self, expr: &NewExpr) -> std::fmt::Result { + write!( + self.writer, + "new {name} {{", + name = expr.package.span.as_str() + )?; + + if expr.arguments.is_empty() { + if expr.ellipsis { + write!(self.writer, " ... ")?; + } + + write!(self.writer, "}}")?; + return Ok(()); + } + + self.newline()?; + self.inc(); + + for arg in &expr.arguments { + self.indent()?; + + match arg { + InstantiationArgument::Named(arg) => { + match &arg.name { + InstantiationArgumentName::Ident(id) => { + write!(self.writer, "{id}: ", id = id.span.as_str())?; + } + InstantiationArgumentName::String(s) => { + write!(self.writer, "{s}: ", s = s.span.as_str())?; + } + } + self.expr(&arg.expr)?; + } + InstantiationArgument::Ident(id) => { + write!(self.writer, "{id}", id = id.span.as_str())? + } + } + + write!(self.writer, ",")?; + self.newline()?; + } + + if expr.ellipsis { + self.indent()?; + write!(self.writer, "...")?; + self.newline()?; + } + + self.dec(); + self.indent()?; + write!(self.writer, "}}") + } + + /// Prints the given postfix expression. + pub fn postfix_expr(&mut self, expr: &PostfixExpr) -> std::fmt::Result { + match expr { + PostfixExpr::Access(a) => self.access_expr(a), + PostfixExpr::NamedAccess(a) => self.named_access_expr(a), + } + } + + /// Prints the given access expression. + pub fn access_expr(&mut self, expr: &AccessExpr) -> std::fmt::Result { + write!(self.writer, ".{id}", id = expr.id.span.as_str()) + } + + /// Prints the given named access expression. + pub fn named_access_expr(&mut self, expr: &NamedAccessExpr) -> std::fmt::Result { + write!(self.writer, "[{name}]", name = expr.string.span.as_str()) + } + + /// Prints the given export statement. + pub fn export_statement(&mut self, stmt: &ExportStatement) -> std::fmt::Result { + self.docs(&stmt.docs)?; + self.indent()?; + + write!(self.writer, "export ")?; + self.expr(&stmt.expr)?; + + if let Some(with) = &stmt.with { + write!(self.writer, " with {with}", with = with.span.as_str())?; + } + + write!(self.writer, ";") + } + + fn newline(&mut self) -> std::fmt::Result { + writeln!(self.writer)?; + self.indented = false; + Ok(()) + } + + fn indent(&mut self) -> std::fmt::Result { + if !self.indented { + for _ in 0..self.indent { + write!(self.writer, "{space}", space = self.space)?; + } + + self.indented = true; + } + + Ok(()) + } + + fn inc(&mut self) { + self.indent = self.indent.saturating_add(1); + } + + fn dec(&mut self) { + self.indent = self.indent.saturating_sub(1); + } +} diff --git a/crates/wac-parser/src/resolution.rs b/crates/wac-parser/src/resolution.rs index e18db5a..026370e 100644 --- a/crates/wac-parser/src/resolution.rs +++ b/crates/wac-parser/src/resolution.rs @@ -1,14 +1,16 @@ //! Module for resolving WAC documents. use self::package::Package; -use crate::ast::{self, new_error_with_span}; -use anyhow::{bail, Context, Result}; +use crate::{ + ast::{self, InterfaceItem, WorldItem}, + lexer::Span, + line_column, Spanned, +}; +use anyhow::Context; use id_arena::{Arena, Id}; use indexmap::{IndexMap, IndexSet}; -use pest::{Position, Span}; use serde::{Serialize, Serializer}; use std::{ - borrow::Cow, collections::{hash_map, HashMap}, fmt, fs, path::{Path, PathBuf}, @@ -75,6 +77,464 @@ where } } +/// Represents a kind of item in a world. +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +pub enum WorldItemKind { + /// The item is an import. + Import, + /// The item is an export. + Export, +} + +impl fmt::Display for WorldItemKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WorldItemKind::Import => write!(f, "import"), + WorldItemKind::Export => write!(f, "export"), + } + } +} + +struct InterfaceNameDisplay<'a>(Option<&'a str>); + +impl fmt::Display for InterfaceNameDisplay<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Some(name) => write!(f, " for interface `{name}`"), + None => Ok(()), + } + } +} + +struct ParentPathDisplay<'a>(Option<&'static str>, &'a str); + +impl fmt::Display for ParentPathDisplay<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.0 { + Some(name) => { + write!(f, "{name} `{path}` in ", path = self.1) + } + None => Ok(()), + } + } +} + +/// Represents a resolution error. +#[derive(thiserror::Error, Debug)] +pub enum Error<'a> { + /// An undefined name was encountered. + #[error("undefined name `{name}`")] + UndefinedName { + /// The name that was undefined. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate name was encountered. + #[error("`{name}` was previously defined at {path}:{line}:{column}", path = .path.display())] + DuplicateName { + /// The duplicate name. + name: &'a str, + /// The path to the source file. + path: &'a Path, + /// The line where the identifier was previously defined. + line: usize, + /// The column where the identifier was previously defined. + column: usize, + /// The span where the error occurred. + span: Span<'a>, + }, + /// Duplicate interface export. + #[error("duplicate interface export `{name}`{iface}", iface = InterfaceNameDisplay(*.interface_name))] + DuplicateInterfaceExport { + /// The name of the duplicate export. + name: &'a str, + /// The name of the interface. + interface_name: Option<&'a str>, + /// The span where the error occurred. + span: Span<'a>, + }, + /// Duplicate world item. + #[error("{kind} `{name}` conflicts with existing {kind} of the same name in world `{world}`")] + DuplicateWorldItem { + /// The kind of the item. + kind: WorldItemKind, + /// The name of the item. + name: String, + /// The name of the world. + world: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name is not a function type or interface. + #[error("`{name}` ({kind}) is not a function type or interface")] + NotFuncOrInterface { + /// The name that is not a function type or interface. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name is not an interface. + #[error("`{name}` ({kind}) is not an interface")] + NotInterface { + /// The name that is not an interface. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// Duplicate name in a world include. + #[error("duplicate `{name}` in world include `with` clause")] + DuplicateWorldIncludeName { + /// The name of the duplicate include. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name is not a world. + #[error("`{name}` ({kind}) is not a world")] + NotWorld { + /// The name that is not a world. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// Missing source item for `with` clause in world include. + #[error("world `{world}` does not have an import or export named `{name}`")] + MissingWorldInclude { + /// The name of the world. + world: &'a str, + /// The name of the missing item. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A conflict was encountered in a world include. + #[error("{kind} `{name}` from world `{from}` conflicts with {kind} of the same name in world `{to}`{hint}")] + WorldIncludeConflict { + /// The kind of the item. + kind: WorldItemKind, + /// The name of the item. + name: String, + /// The name of the source world. + from: &'a str, + /// The name of the target world. + to: &'a str, + /// The span where the error occurred. + span: Span<'a>, + /// The hint for the error. + hint: &'static str, + }, + /// A name is not a type defined in an interface. + #[error("type `{name}` is not defined in interface `{interface_name}`")] + UndefinedInterfaceType { + /// The name of the type. + name: &'a str, + /// The name of the interface. + interface_name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A name is not a value type defined in an interface. + #[error("`{name}` ({kind}) is not a value type in interface `{interface_name}`")] + NotInterfaceValueType { + /// The name that is not a value type. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The name of the interface. + interface_name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate resource constructor was encountered. + #[error("duplicate constructor for resource `{resource}`")] + DuplicateResourceConstructor { + /// The name of the resource. + resource: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate resource method was encountered. + #[error("duplicate method `{name}` for resource `{resource}`")] + DuplicateResourceMethod { + /// The name of the method. + name: &'a str, + /// The name of the resource. + resource: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate variant case was encountered. + #[error("duplicate case `{case}` for variant type `{name}`")] + DuplicateVariantCase { + /// The name of the case. + case: &'a str, + /// The name of the variant type. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate record field was encountered. + #[error("duplicate field `{field}` for record type `{name}`")] + DuplicateRecordField { + /// The name of the field. + field: &'a str, + /// The name of the record type. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate enum case was encountered. + #[error("duplicate case `{case}` for enum type `{name}`")] + DuplicateEnumCase { + /// The name of the case. + case: &'a str, + /// The name of the enum type. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate flag was encountered. + #[error("duplicate flag `{flag}` for flags type `{name}`")] + DuplicateFlag { + /// The name of the flag. + flag: &'a str, + /// The name of the flags type. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name cannot be used as an alias type. + #[error("`{name}` ({kind}) cannot be used in a type alias")] + InvalidAliasType { + /// The name that cannot be used as an alias type. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name is not a function type. + #[error("`{name}` ({kind}) is not a function type")] + NotFuncType { + /// The name that is not a function type. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name is not a resource type. + #[error("`{name}` ({kind}) is not a resource type")] + NotResourceType { + /// The name that is not a resource type. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// The name is not a value type. + #[error("`{name}` ({kind}) cannot be used as a value type")] + NotValueType { + /// The name that is not a value type. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate function parameter was encountered. + #[error("duplicate {kind} parameter `{name}`")] + DuplicateParameter { + /// The name of the parameter. + name: &'a str, + /// The kind of the function. + kind: FuncKind, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A duplicate result was encountered. + #[error("duplicate {kind} result `{name}`")] + DuplicateResult { + /// The name of the result. + name: &'a str, + /// The kind of the function. + kind: FuncKind, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A package failed to resolve. + #[error("failed to resolve package `{name}`")] + PackageResolutionFailure { + /// The name of the package. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + /// The underlying error. + #[source] + source: anyhow::Error, + }, + /// A package failed to parse. + #[error("failed to parse package `{name}`")] + PackageParseFailure { + /// The name of the package. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + /// The underlying error. + #[source] + source: anyhow::Error, + }, + /// An unknown package was encountered. + #[error("unknown package `{name}`")] + UnknownPackage { + /// The name of the package. + name: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A package is missing an export. + #[error("{prev}package `{name}` has no export named `{export}`", prev = ParentPathDisplay(*.kind, .path))] + PackageMissingExport { + /// The name of the package. + name: &'a str, + /// The name of the export. + export: &'a str, + /// The kind of the item being accessed. + kind: Option<&'static str>, + /// The path to the current item. + path: String, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A missing export in a package path was encountered. + #[error("`{name}` ({kind}) has no export named `{export}`")] + PackagePathMissingExport { + /// The name that has no matching export. + name: &'a str, + /// The kind of the item. + kind: &'static str, + /// The name of the export. + export: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A missing import on a component was encountered. + #[error("component `{package}` has no import named `{import}`")] + MissingComponentImport { + /// The name of the package. + package: &'a str, + /// The name of the import. + import: String, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A mismatched instantiation argument was encountered. + #[error("mismatched instantiation argument `{name}`")] + MismatchedInstantiationArg { + /// The name of the argument. + name: String, + /// The span where the error occurred. + span: Span<'a>, + /// The source of the error. + #[source] + source: anyhow::Error, + }, + /// A duplicate instantiation argument was encountered. + #[error("duplicate instantiation argument `{name}`")] + DuplicateInstantiationArg { + /// The name of the argument. + name: String, + /// The span where the error occurred. + span: Span<'a>, + }, + /// A missing instantiation argument was encountered. + #[error("missing instantiation argument `{name}` for package `{package}`")] + MissingInstantiationArg { + /// The name of the argument. + name: String, + /// The name of the package. + package: &'a str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// An access expression on an inaccessible value was encountered. + #[error("a {kind} cannot be accessed")] + Inaccessible { + /// The kind of the item. + kind: &'static str, + /// The span where the error occurred. + span: Span<'a>, + }, + /// An access on an interface was encountered. + #[error("an interface cannot be accessed")] + InaccessibleInterface { + /// The span where the error occurred. + span: Span<'a>, + }, + /// An instance is missing an export. + #[error("the instance has no export named `{name}`")] + MissingInstanceExport { + /// The name of the export. + name: String, + /// The span where the error occurred. + span: Span<'a>, + }, +} + +impl Spanned for Error<'_> { + fn span(&self) -> Span { + match self { + Error::UndefinedName { span, .. } + | Error::DuplicateName { span, .. } + | Error::DuplicateInterfaceExport { span, .. } + | Error::DuplicateWorldItem { span, .. } + | Error::NotFuncOrInterface { span, .. } + | Error::NotInterface { span, .. } + | Error::DuplicateWorldIncludeName { span, .. } + | Error::NotWorld { span, .. } + | Error::MissingWorldInclude { span, .. } + | Error::WorldIncludeConflict { span, .. } + | Error::UndefinedInterfaceType { span, .. } + | Error::NotInterfaceValueType { span, .. } + | Error::DuplicateResourceConstructor { span, .. } + | Error::DuplicateResourceMethod { span, .. } + | Error::DuplicateVariantCase { span, .. } + | Error::DuplicateRecordField { span, .. } + | Error::DuplicateEnumCase { span, .. } + | Error::DuplicateFlag { span, .. } + | Error::InvalidAliasType { span, .. } + | Error::NotFuncType { span, .. } + | Error::NotResourceType { span, .. } + | Error::NotValueType { span, .. } + | Error::DuplicateParameter { span, .. } + | Error::DuplicateResult { span, .. } + | Error::PackageResolutionFailure { span, .. } + | Error::PackageParseFailure { span, .. } + | Error::UnknownPackage { span, .. } + | Error::PackageMissingExport { span, .. } + | Error::PackagePathMissingExport { span, .. } + | Error::MissingComponentImport { span, .. } + | Error::MismatchedInstantiationArg { span, .. } + | Error::DuplicateInstantiationArg { span, .. } + | Error::MissingInstantiationArg { span, .. } + | Error::Inaccessible { span, .. } + | Error::InaccessibleInterface { span, .. } + | Error::MissingInstanceExport { span, .. } => *span, + } + } +} + +/// Represents a resolution result. +pub type ResolutionResult<'a, T> = std::result::Result>; + /// A trait implemented by package resolvers. /// /// This is used when resolving a document to resolve any referenced packages. @@ -82,7 +542,7 @@ pub trait PackageResolver { /// Resolves a package name to the package bytes. /// /// Returns `Ok(None)` if the package could not be found. - fn resolve(&self, name: &ast::PackageName) -> Result>>; + fn resolve(&self, name: &str) -> anyhow::Result>>; } /// Used to resolve packages from the file system. @@ -108,21 +568,21 @@ impl Default for FileSystemPackageResolver { } impl PackageResolver for FileSystemPackageResolver { - fn resolve(&self, name: &ast::PackageName) -> Result>> { - let path = if let Some(path) = self.overrides.get(name.as_str()) { + fn resolve(&self, name: &str) -> anyhow::Result>> { + let path = if let Some(path) = self.overrides.get(name) { if !path.exists() { - bail!( + anyhow::bail!( "local path `{path}` for package `{name}` does not exist", path = path.display(), - name = name.as_str() + name = name ) } path.clone() } else { let mut path = self.root.clone(); - for part in &name.parts { - path.push(part.as_str()); + for segment in name.split(':') { + path.push(segment); } // If the path is not a directory, use a `.wasm` or `.wat` extension @@ -178,7 +638,7 @@ impl PackageResolver for FileSystemPackageResolver { Ok(std::borrow::Cow::Owned(wat)) => wat, Err(mut e) => { e.set_path(path); - bail!(e); + anyhow::bail!(e); } }; @@ -210,28 +670,14 @@ pub enum ItemKind { } impl ItemKind { - pub(crate) fn display<'a>(&'a self, definitions: &'a Definitions) -> impl fmt::Display + 'a { - ItemKindDisplay { - kind: *self, - definitions, - } - } -} - -struct ItemKindDisplay<'a> { - kind: ItemKind, - definitions: &'a Definitions, -} - -impl fmt::Display for ItemKindDisplay<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.kind { - ItemKind::Func(_) => write!(f, "function"), - ItemKind::Type(ty) => ty.display(self.definitions).fmt(f), - ItemKind::Instance(_) | ItemKind::Instantiation(_) => write!(f, "instance"), - ItemKind::Component(_) => write!(f, "component"), - ItemKind::Module(_) => write!(f, "module"), - ItemKind::Value(_) => write!(f, "value"), + fn as_str(&self, definitions: &Definitions) -> &'static str { + match self { + ItemKind::Func(_) => "function", + ItemKind::Type(ty) => ty.as_str(definitions), + ItemKind::Instance(_) | ItemKind::Instantiation(_) => "instance", + ItemKind::Component(_) => "component", + ItemKind::Module(_) => "module", + ItemKind::Value(_) => "value", } } } @@ -357,11 +803,11 @@ pub struct ResolvedDocument { impl ResolvedDocument { /// Creates a new resolved document from the given document. - pub fn new( - document: &ast::Document, + pub fn new<'a>( + document: &'a ast::Document<'a>, package: impl Into, resolver: Option>, - ) -> Result { + ) -> ResolutionResult<'a, Self> { let mut scopes = Arena::new(); let root_scope = scopes.alloc(Scope { parent: None, @@ -386,7 +832,7 @@ impl ResolvedDocument { }; for stmt in &document.statements { - match &stmt.stmt { + match stmt { ast::Statement::Import(i) => resolution.import(&mut state, i)?, ast::Statement::Type(t) => resolution.type_statement(&mut state, t)?, ast::Statement::Let(l) => resolution.let_statement(&mut state, l)?, @@ -398,14 +844,14 @@ impl ResolvedDocument { } /// Encode the resolved document as a WebAssembly component. - pub fn encode(&self) -> Result> { + pub fn encode(&self) -> ResolutionResult> { todo!("implement encoding") } fn get(&self, state: &ResolutionState, id: &ast::Ident) -> Option { let mut current = &self.scopes[state.current_scope]; loop { - if let Some(item) = current.get(id.as_str()) { + if let Some(item) = current.get(id.string) { return Some(item); } @@ -413,13 +859,14 @@ impl ResolvedDocument { } } - fn require(&self, state: &ResolutionState, id: &ast::Ident) -> Result { - self.get(state, id).ok_or_else(|| { - new_error_with_span( - state.document.path, - id.0, - format!("undefined name `{id}`", id = id.as_str()), - ) + fn require<'a>( + &self, + state: &ResolutionState, + id: &ast::Ident<'a>, + ) -> ResolutionResult<'a, ItemId> { + self.get(state, id).ok_or(Error::UndefinedName { + name: id.string, + span: id.span, }) } @@ -438,43 +885,42 @@ impl ResolvedDocument { id } - fn register_name( + fn register_name<'a>( &mut self, - state: &mut ResolutionState, - id: ast::Ident, + state: &mut ResolutionState<'a>, + id: ast::Ident<'a>, item: ItemId, - ) -> Result<()> { + ) -> ResolutionResult<'a, ()> { if let Some(prev) = self.scopes[state.current_scope] .items - .insert(id.as_str().to_owned(), item) + .insert(id.string.to_owned(), item) { let offset = state.offsets[&prev]; - let (line, column) = Position::new(id.0.get_input(), offset).unwrap().line_col(); - return Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` was previously defined at {path}:{line}:{column}", - id = id.as_str(), - path = state.document.path.display(), - ), - )); + let (line, column) = line_column(id.span.source(), offset); + return Err(Error::DuplicateName { + name: id.string, + path: state.document.path, + line, + column, + span: id.span, + }); } - state.offsets.insert(item, id.0.start()); + state.offsets.insert(item, id.span.start); Ok(()) } - fn import(&mut self, state: &mut ResolutionState, stmt: &ast::ImportStatement) -> Result<()> { + fn import<'a>( + &mut self, + state: &mut ResolutionState<'a>, + stmt: &'a ast::ImportStatement<'a>, + ) -> ResolutionResult<'a, ()> { let kind = match &stmt.ty { ast::ImportType::Package(p) => self.resolve_package_export(state, p)?, - ast::ImportType::Func(ty) => ItemKind::Func(self.func_type( - state, - &ty.params, - ty.results.as_ref(), - FuncKind::Free, - )?), + ast::ImportType::Func(ty) => { + ItemKind::Func(self.func_type(state, &ty.params, &ty.results, FuncKind::Free)?) + } ast::ImportType::Interface(i) => self.inline_interface(state, i)?, ast::ImportType::Ident(id) => self.items[self.require(state, id)?].kind, }; @@ -490,18 +936,18 @@ impl ResolvedDocument { let id = self.items.alloc(Item { kind, source: ItemSource::Import { - with: stmt.with.as_ref().map(|w| w.name.as_str().to_owned()), + with: stmt.with.as_ref().map(|s| s.value.to_owned()), }, }); self.register_name(state, stmt.id, id) } - fn type_statement( + fn type_statement<'a>( &mut self, - state: &mut ResolutionState, - stmt: &ast::TypeStatement, - ) -> Result<()> { + state: &mut ResolutionState<'a>, + stmt: &'a ast::TypeStatement<'a>, + ) -> ResolutionResult<'a, ()> { match stmt { ast::TypeStatement::Interface(i) => self.interface_decl(state, i), ast::TypeStatement::World(w) => self.world_decl(state, w), @@ -512,17 +958,17 @@ impl ResolvedDocument { fn let_statement<'a>( &mut self, state: &mut ResolutionState<'a>, - stmt: &'a ast::LetStatement, - ) -> Result<()> { + stmt: &'a ast::LetStatement<'a>, + ) -> ResolutionResult<'a, ()> { let item = self.expr(state, &stmt.expr)?; self.register_name(state, stmt.id, item) } - fn inline_interface( + fn inline_interface<'a>( &mut self, - state: &mut ResolutionState, - iface: &ast::InlineInterface, - ) -> Result { + state: &mut ResolutionState<'a>, + iface: &'a ast::InlineInterface<'a>, + ) -> ResolutionResult<'a, ItemKind> { self.push_scope(state); let mut ty = Interface { @@ -531,7 +977,7 @@ impl ResolvedDocument { scope: Some(state.current_scope), }; - self.interface_body(state, None, &iface.body, &mut ty)?; + self.interface_items(state, None, &iface.items, &mut ty)?; self.pop_scope(state); @@ -540,24 +986,24 @@ impl ResolvedDocument { ))) } - fn interface_decl( + fn interface_decl<'a>( &mut self, - state: &mut ResolutionState, - decl: &ast::InterfaceDecl, - ) -> Result<()> { + state: &mut ResolutionState<'a>, + decl: &'a ast::InterfaceDecl<'a>, + ) -> ResolutionResult<'a, ()> { self.push_scope(state); let mut ty = Interface { id: Some(format!( "{pkg}/{iface}", pkg = self.package, - iface = decl.id + iface = decl.id.string, )), exports: Default::default(), scope: Some(state.current_scope), }; - self.interface_body(state, Some(decl.id.as_str()), &decl.body, &mut ty)?; + self.interface_items(state, Some(decl.id.string), &decl.items, &mut ty)?; self.pop_scope(state); @@ -571,7 +1017,11 @@ impl ResolvedDocument { self.register_name(state, decl.id, id) } - fn world_decl(&mut self, state: &mut ResolutionState, decl: &ast::WorldDecl) -> Result<()> { + fn world_decl<'a>( + &mut self, + state: &mut ResolutionState<'a>, + decl: &'a ast::WorldDecl<'a>, + ) -> ResolutionResult<'a, ()> { self.push_scope(state); let mut ty = World { @@ -580,7 +1030,7 @@ impl ResolvedDocument { scope: Some(state.current_scope), }; - self.world_body(state, decl.id.as_str(), &decl.body, &mut ty)?; + self.world_body(state, decl.id.string, &decl.items, &mut ty)?; self.pop_scope(state); @@ -594,45 +1044,35 @@ impl ResolvedDocument { self.register_name(state, decl.id, id) } - fn interface_body( + fn interface_items<'a>( &mut self, - state: &mut ResolutionState, - name: Option<&str>, - body: &ast::InterfaceBody, + state: &mut ResolutionState<'a>, + name: Option<&'a str>, + items: &'a [InterfaceItem<'a>], ty: &mut Interface, - ) -> Result<()> { - for item in &body.items { - match &item.stmt { - ast::InterfaceItemStatement::Use(u) => { - self.use_type(state, u, &mut ty.exports, false)? - } - ast::InterfaceItemStatement::Type(decl) => { - let kind = self.type_decl(state, decl)?; + ) -> ResolutionResult<'a, ()> { + for item in items { + match item { + ast::InterfaceItem::Use(u) => self.use_type(state, u, &mut ty.exports, false)?, + ast::InterfaceItem::Type(decl) => { + let kind = self.item_type_decl(state, decl)?; let prev = ty .exports - .insert(decl.id().as_str().into(), Extern::Kind(kind)); + .insert(decl.id().string.into(), Extern::Kind(kind)); assert!(prev.is_none(), "duplicate type in scope"); } - ast::InterfaceItemStatement::Export(e) => { + ast::InterfaceItem::Export(e) => { let kind = ItemKind::Func(self.func_type_ref(state, &e.ty, FuncKind::Free)?); if ty .exports - .insert(e.id.as_str().into(), Extern::Kind(kind)) + .insert(e.id.string.into(), Extern::Kind(kind)) .is_some() { - return Err(new_error_with_span( - state.document.path, - e.id.0, - match name { - Some(name) => format!( - "duplicate interface export `{id}` for interface `{name}`", - id = e.id.as_str() - ), - None => { - format!("duplicate interface export `{id}`", id = e.id.as_str()) - } - }, - )); + return Err(Error::DuplicateInterfaceExport { + name: e.id.string, + interface_name: name, + span: e.id.span, + }); } } } @@ -641,33 +1081,31 @@ impl ResolvedDocument { Ok(()) } - fn world_body( + fn world_body<'a>( &mut self, - state: &mut ResolutionState, - world: &str, - body: &ast::WorldBody, + state: &mut ResolutionState<'a>, + world: &'a str, + items: &'a [WorldItem<'a>], ty: &mut World, - ) -> Result<()> { + ) -> ResolutionResult<'a, ()> { let mut includes = Vec::new(); - for item in &body.items { - match &item.stmt { - ast::WorldItemStatement::Use(u) => { - self.use_type(state, u, &mut ty.imports, true)? - } - ast::WorldItemStatement::Type(decl) => { - let kind = self.type_decl(state, decl)?; + for item in items { + match item { + ast::WorldItem::Use(u) => self.use_type(state, u, &mut ty.imports, true)?, + ast::WorldItem::Type(decl) => { + let kind = self.item_type_decl(state, decl)?; let prev = ty .exports - .insert(decl.id().as_str().into(), Extern::Kind(kind)); + .insert(decl.id().string.into(), Extern::Kind(kind)); assert!(prev.is_none(), "duplicate type in scope"); } - ast::WorldItemStatement::Import(i) => { - self.world_item_decl(state, &i.decl, true, world, ty)? + ast::WorldItem::Import(i) => { + self.world_item_path(state, &i.path, WorldItemKind::Import, world, ty)? } - ast::WorldItemStatement::Export(e) => { - self.world_item_decl(state, &e.decl, false, world, ty)? + ast::WorldItem::Export(e) => { + self.world_item_path(state, &e.path, WorldItemKind::Export, world, ty)? } - ast::WorldItemStatement::Include(i) => { + ast::WorldItem::Include(i) => { // We delay processing includes until after all other items have been processed includes.push(i); } @@ -683,40 +1121,20 @@ impl ResolvedDocument { Ok(()) } - fn world_item_decl( + fn world_item_path<'a>( &mut self, - state: &mut ResolutionState, - decl: &ast::WorldItemDecl, - import: bool, - world: &str, + state: &mut ResolutionState<'a>, + path: &'a ast::WorldItemPath<'a>, + kind: WorldItemKind, + world: &'a str, ty: &mut World, - ) -> Result<()> { - let check_name = |name: &str, span: Span, ty: &World| { - let exists: bool = if import { - ty.imports.contains_key(name) - } else { - ty.exports.contains_key(name) - }; - if exists { - return Err(new_error_with_span( - state.document.path, - span, - format!( - "{dir} `{name}` conflicts with existing {dir} of the same name in world `{world}`", - dir = if import { "import" } else { "export" }, - ), - )); - } - - Ok(()) - }; - - let (name, kind) = match decl { - ast::WorldItemDecl::Named(named) => { - check_name(named.id.as_str(), named.id.0, ty)?; + ) -> ResolutionResult<'a, ()> { + let (k, v) = match path { + ast::WorldItemPath::Named(named) => { + check_name(named.id.string, named.id.span, ty, world, kind)?; ( - named.id.as_str().into(), + named.id.string.into(), match &named.ty { ast::ExternType::Ident(id) => { let item = &self.items[self.require(state, id)?]; @@ -728,144 +1146,139 @@ impl ResolvedDocument { ItemKind::Func(id) } _ => { - return Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) is not a function type or interface", - kind = item.kind.display(&self.definitions), - id = id.as_str(), - ), - )) + return Err(Error::NotFuncOrInterface { + name: id.string, + kind: item.kind.as_str(&self.definitions), + span: id.span, + }); } } } ast::ExternType::Func(f) => ItemKind::Func(self.func_type( state, &f.params, - f.results.as_ref(), + &f.results, FuncKind::Free, )?), ast::ExternType::Interface(i) => self.inline_interface(state, i)?, }, ) } - ast::WorldItemDecl::Interface(i) => match i { - ast::InterfaceRef::Ident(id) => { - let item = &self.items[self.require(state, id)?]; - match item.kind { - ItemKind::Type(Type::Interface(iface_ty_id)) - | ItemKind::Instance(iface_ty_id) => { - let iface_id = self.definitions.interfaces[iface_ty_id] - .id - .as_ref() - .expect("expected an interface id"); - check_name(iface_id, id.0, ty)?; - (iface_id.clone(), ItemKind::Instance(iface_ty_id)) - } - _ => { - return Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) is not an interface", - kind = item.kind.display(&self.definitions), - id = id.as_str() - ), - )) - } - } - } - ast::InterfaceRef::Path(p) => match self.resolve_package_export(state, p)? { - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { - let name = self.definitions.interfaces[id] + ast::WorldItemPath::Ident(id) => { + let item = &self.items[self.require(state, id)?]; + match item.kind { + ItemKind::Type(Type::Interface(iface_ty_id)) + | ItemKind::Instance(iface_ty_id) => { + let iface_id = self.definitions.interfaces[iface_ty_id] .id .as_ref() .expect("expected an interface id"); - check_name(name, p.span(), ty)?; - (name.clone(), ItemKind::Instance(id)) + check_name(iface_id, id.span, ty, world, kind)?; + (iface_id.clone(), ItemKind::Instance(iface_ty_id)) } - kind => { - return Err(new_error_with_span( - state.document.path, - p.span(), - format!( - "`{path}` ({kind}) is not an interface", - path = p.as_str(), - kind = kind.display(&self.definitions) - ), - )) + _ => { + return Err(Error::NotInterface { + name: id.string, + kind: item.kind.as_str(&self.definitions), + span: id.span, + }); } - }, + } + } + + ast::WorldItemPath::Package(p) => match self.resolve_package_export(state, p)? { + ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { + let name = self.definitions.interfaces[id] + .id + .as_ref() + .expect("expected an interface id"); + check_name(name, p.span, ty, world, kind)?; + (name.clone(), ItemKind::Instance(id)) + } + kind => { + return Err(Error::NotInterface { + name: p.span.as_str(), + kind: kind.as_str(&self.definitions), + span: p.span, + }); + } }, }; - if import { - ty.imports.insert(name, Extern::Kind(kind)); + if kind == WorldItemKind::Import { + ty.imports.insert(k, Extern::Kind(v)); } else { - ty.exports.insert(name, Extern::Kind(kind)); + ty.exports.insert(k, Extern::Kind(v)); } - Ok(()) + return Ok(()); + + fn check_name<'a>( + name: &str, + span: Span<'a>, + ty: &World, + world: &'a str, + kind: WorldItemKind, + ) -> ResolutionResult<'a, ()> { + let exists: bool = if kind == WorldItemKind::Import { + ty.imports.contains_key(name) + } else { + ty.exports.contains_key(name) + }; + + if exists { + return Err(Error::DuplicateWorldItem { + kind, + name: name.to_owned(), + world, + span, + }); + } + + Ok(()) + } } - fn world_include( + fn world_include<'a>( &mut self, - state: &mut ResolutionState, - stmt: &ast::WorldIncludeStatement, - world: &str, + state: &mut ResolutionState<'a>, + include: &ast::WorldInclude<'a>, + world: &'a str, ty: &mut World, - ) -> Result<()> { + ) -> ResolutionResult<'a, ()> { let mut replacements = HashMap::new(); - for item in stmt - .with - .as_ref() - .map(|w| w.names.as_slice()) - .unwrap_or(&[]) - { - let prev = replacements.insert(item.name.as_str(), item); + for item in &include.with { + let prev = replacements.insert(item.from.string, item); if prev.is_some() { - return Err(new_error_with_span( - state.document.path, - item.name.0, - format!( - "duplicate `{name}` in world include `with` clause", - name = item.name, - ), - )); + return Err(Error::DuplicateWorldIncludeName { + name: item.from.string, + span: item.from.span, + }); } } - let id = match &stmt.world { + let id = match &include.world { ast::WorldRef::Ident(id) => { let item = &self.items[self.require(state, id)?]; match item.kind { ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, kind => { - return Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) is not a world", - id = id.as_str(), - kind = kind.display(&self.definitions) - ), - )) + return Err(Error::NotWorld { + name: id.string, + kind: kind.as_str(&self.definitions), + span: id.span, + }); } } } - ast::WorldRef::Path(path) => match self.resolve_package_export(state, path)? { + ast::WorldRef::Package(path) => match self.resolve_package_export(state, path)? { ItemKind::Type(Type::World(id)) | ItemKind::Component(id) => id, kind => { - return Err(new_error_with_span( - state.document.path, - path.span(), - format!( - "`{path}` ({kind}) is not a world", - path = path.as_str(), - kind = kind.display(&self.definitions) - ), - )) + return Err(Error::NotWorld { + name: path.span.as_str(), + kind: kind.as_str(&self.definitions), + span: path.span, + }); } }, }; @@ -873,53 +1286,46 @@ impl ResolvedDocument { let other = &self.definitions.worlds[id]; for (name, item) in &other.imports { let name = replace_name( - stmt, + include, world, ty, name, - true, + WorldItemKind::Import, &mut replacements, - state.document.path, )?; ty.imports.entry(name).or_insert(*item); } for (name, item) in &other.exports { let name = replace_name( - stmt, + include, world, ty, name, - false, + WorldItemKind::Export, &mut replacements, - state.document.path, )?; ty.exports.entry(name).or_insert(*item); } if let Some(missing) = replacements.values().next() { - return Err(new_error_with_span( - state.document.path, - missing.name.0, - format!( - "world `{other}` does not have an import or export with kebab-name `{name}`", - other = stmt.world.name(), - name = missing.name.as_str(), - ), - )); + return Err(Error::MissingWorldInclude { + world: include.world.name(), + name: missing.from.string, + span: missing.from.span, + }); } return Ok(()); - fn replace_name( - stmt: &ast::WorldIncludeStatement, - world: &str, + fn replace_name<'a>( + include: &ast::WorldInclude<'a>, + world: &'a str, ty: &mut World, name: &str, - import: bool, - replacements: &mut HashMap<&str, &ast::WorldIncludeItem>, - path: &Path, - ) -> Result { + kind: WorldItemKind, + replacements: &mut HashMap<&str, &ast::WorldIncludeItem<'a>>, + ) -> ResolutionResult<'a, String> { // Check for a id, which doesn't get replaced. if name.contains(':') { return Ok(name.to_owned()); @@ -927,90 +1333,79 @@ impl ResolvedDocument { let (name, span) = replacements .remove(name) - .map(|r| (r.other.as_str(), r.other.0)) - .unwrap_or_else(|| (name, stmt.world.span())); + .map(|i| (i.to.string, i.to.span)) + .unwrap_or_else(|| (name, include.world.span())); - let exists = if import { + let exists = if kind == WorldItemKind::Import { ty.imports.contains_key(name) } else { ty.exports.contains_key(name) }; if exists { - return Err(new_error_with_span( - path, + return Err(Error::WorldIncludeConflict { + kind, + name: name.to_owned(), + from: include.world.name(), + to: world, span, - format!( - "{dir} `{name}` from world `{other}` conflicts with {dir} of the same name in world `{world}`{hint}", - dir = if import { "import" } else { "export" }, - other = stmt.world.name(), - hint = if stmt.with.is_some() { "" } else { - " (consider using a `with` clause to use a different name)" - } - ), - )); + hint: if !include.with.is_empty() { + "" + } else { + " (consider using a `with` clause to use a different name)" + }, + }); } Ok(name.to_owned()) } } - fn use_type( + fn use_type<'a>( &mut self, - state: &mut ResolutionState, - stmt: &ast::UseStatement, + state: &mut ResolutionState<'a>, + use_type: &ast::Use<'a>, externs: &mut ExternMap, in_world: bool, - ) -> Result<()> { - let (interface, name) = match &stmt.items.path { + ) -> ResolutionResult<'a, ()> { + let (interface, name) = match &use_type.path { ast::UsePath::Package(path) => match self.resolve_package_export(state, path)? { - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => (id, path.as_str()), + ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { + (id, path.span.as_str()) + } kind => { - return Err(new_error_with_span( - state.document.path, - path.span(), - format!( - "`{path}` ({kind}) is not an interface", - path = path.as_str(), - kind = kind.display(&self.definitions) - ), - )) + return Err(Error::NotInterface { + name: path.span.as_str(), + kind: kind.as_str(&self.definitions), + span: path.span, + }); } }, ast::UsePath::Ident(id) => { let item = &self.items[self.require(state, id)?]; match item.kind { ItemKind::Type(Type::Interface(iface_ty_id)) - | ItemKind::Instance(iface_ty_id) => (iface_ty_id, id.as_str()), + | ItemKind::Instance(iface_ty_id) => (iface_ty_id, id.string), kind => { - return Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) is not an interface", - id = id.as_str(), - kind = kind.display(&self.definitions) - ), - )) + return Err(Error::NotInterface { + name: id.string, + kind: kind.as_str(&self.definitions), + span: id.span, + }); } } } }; - for item in &stmt.items.list { - let ident = item.as_clause.as_ref().map(|c| c.id).unwrap_or(item.id); + for item in &use_type.items { + let ident = item.as_id.unwrap_or(item.id); let (index, _, ext) = self.definitions.interfaces[interface] .exports - .get_full(item.id.as_str()) - .ok_or_else(|| { - new_error_with_span( - state.document.path, - item.id.0, - format!( - "type `{item}` is not defined in interface `{name}`", - item = item.id.as_str() - ), - ) + .get_full(item.id.string) + .ok_or(Error::UndefinedInterfaceType { + name: item.id.string, + interface_name: name, + span: item.id.span, })?; let kind = ext.kind(); @@ -1047,7 +1442,7 @@ impl ResolvedDocument { } } - externs.insert(ident.as_str().into(), new_extern); + externs.insert(ident.string.into(), new_extern); let id = self.items.alloc(Item { kind, @@ -1056,15 +1451,12 @@ impl ResolvedDocument { self.register_name(state, ident, id)?; } _ => { - return Err(new_error_with_span( - state.document.path, - item.id.0, - format!( - "`{item}` ({kind}) is not a value type in interface `{name}`", - kind = kind.display(&self.definitions), - item = item.id.as_str() - ), - )); + return Err(Error::NotInterfaceValueType { + name: item.id.string, + kind: kind.as_str(&self.definitions), + interface_name: name, + span: item.id.span, + }); } } } @@ -1072,9 +1464,12 @@ impl ResolvedDocument { Ok(()) } - fn type_decl(&mut self, state: &mut ResolutionState, decl: &ast::TypeDecl) -> Result { + fn type_decl<'a>( + &mut self, + state: &mut ResolutionState<'a>, + decl: &'a ast::TypeDecl, + ) -> ResolutionResult<'a, ItemKind> { match decl { - ast::TypeDecl::Resource(r) => self.resource_decl(state, r), ast::TypeDecl::Variant(v) => self.variant_decl(state, v), ast::TypeDecl::Record(r) => self.record_decl(state, r), ast::TypeDecl::Flags(f) => self.flags_decl(state, f), @@ -1083,11 +1478,26 @@ impl ResolvedDocument { } } - fn resource_decl( + fn item_type_decl<'a>( &mut self, - state: &mut ResolutionState, - decl: &ast::ResourceDecl, - ) -> Result { + state: &mut ResolutionState<'a>, + decl: &'a ast::ItemTypeDecl, + ) -> ResolutionResult<'a, ItemKind> { + match decl { + ast::ItemTypeDecl::Resource(r) => self.resource_decl(state, r), + ast::ItemTypeDecl::Variant(v) => self.variant_decl(state, v), + ast::ItemTypeDecl::Record(r) => self.record_decl(state, r), + ast::ItemTypeDecl::Flags(f) => self.flags_decl(state, f), + ast::ItemTypeDecl::Enum(e) => self.enum_decl(state, e), + ast::ItemTypeDecl::Alias(a) => self.type_alias(state, a), + } + } + + fn resource_decl<'a>( + &mut self, + state: &mut ResolutionState<'a>, + decl: &ast::ResourceDecl<'a>, + ) -> ResolutionResult<'a, ItemKind> { // Resources are allowed to be self-referential, so we need to allocate the resource // before we resolve the methods. let id = self.definitions.resources.alloc(Resource { @@ -1103,89 +1513,79 @@ impl ResolvedDocument { self.register_name(state, decl.id, item_id)?; - self.definitions.resources[id].methods = match &decl.body { - ast::ResourceBody::Empty(_) => Default::default(), - ast::ResourceBody::Methods { methods, .. } => { - let mut resource_methods = IndexMap::new(); - for (method, _) in methods.iter() { - let (name, method, span) = match method { - ast::ResourceMethod::Constructor(ast::Constructor { keyword, params }) => ( - String::new(), - ResourceMethod { - kind: FuncKind::Constructor, - ty: self.func_type(state, params, None, FuncKind::Constructor)?, - }, - keyword.0, - ), - ast::ResourceMethod::Method(ast::Method { - id, func, keyword, .. - }) => ( - id.as_str().into(), - ResourceMethod { - kind: if keyword.is_some() { - FuncKind::Static - } else { - FuncKind::Method - }, - ty: self.func_type_ref(state, func, FuncKind::Method)?, - }, - id.0, - ), - }; + let mut methods: IndexMap = Default::default(); + for method in &decl.methods { + let (name, method, span) = match method { + ast::ResourceMethod::Constructor(ast::Constructor { span, params, .. }) => ( + "", + ResourceMethod { + kind: FuncKind::Constructor, + ty: self.func_type( + state, + params, + &ast::ResultList::Empty, + FuncKind::Constructor, + )?, + }, + *span, + ), + ast::ResourceMethod::Method(ast::Method { + id, is_static, ty, .. + }) => ( + id.string, + ResourceMethod { + kind: if *is_static { + FuncKind::Static + } else { + FuncKind::Method + }, + ty: self.func_type_ref(state, ty, FuncKind::Method)?, + }, + id.span, + ), + }; - let prev = resource_methods.insert(name.clone(), method); - if prev.is_some() { - return Err(new_error_with_span( - state.document.path, - span, - if name.is_empty() { - format!( - "duplicate constructor for resource `{res}`", - res = decl.id.as_str() - ) - } else { - format!( - "duplicate method `{name}` for resource `{res}`", - res = decl.id.as_str() - ) - }, - )); + let prev = methods.insert(name.to_owned(), method); + if prev.is_some() { + return Err(if !name.is_empty() { + Error::DuplicateResourceMethod { + name, + resource: decl.id.string, + span, } - } - - resource_methods + } else { + Error::DuplicateResourceConstructor { + resource: decl.id.string, + span, + } + }); } - }; + } + + self.definitions.resources[id].methods = methods; Ok(kind) } - fn variant_decl( + fn variant_decl<'a>( &mut self, - state: &mut ResolutionState, - decl: &ast::VariantDecl, - ) -> Result { + state: &mut ResolutionState<'a>, + decl: &ast::VariantDecl<'a>, + ) -> ResolutionResult<'a, ItemKind> { let mut cases = IndexMap::new(); - for case in &decl.body.cases { + for case in &decl.cases { if cases .insert( - case.id.as_str().into(), - case.ty - .as_ref() - .map(|t| self.ty(state, &t.ty)) - .transpose()?, + case.id.string.into(), + case.ty.as_ref().map(|ty| self.ty(state, ty)).transpose()?, ) .is_some() { - return Err(new_error_with_span( - state.document.path, - case.id.0, - format!( - "duplicate case `{case}` for variant type `{ty}`", - case = case.id, - ty = decl.id - ), - )); + return Err(Error::DuplicateVariantCase { + case: case.id.string, + name: decl.id.string, + span: case.id.span, + }); } } @@ -1202,26 +1602,22 @@ impl ResolvedDocument { Ok(kind) } - fn record_decl( + fn record_decl<'a>( &mut self, - state: &mut ResolutionState, - decl: &ast::RecordDecl, - ) -> Result { + state: &mut ResolutionState<'a>, + decl: &ast::RecordDecl<'a>, + ) -> ResolutionResult<'a, ItemKind> { let mut fields = IndexMap::new(); - for field in &decl.body.fields { + for field in &decl.fields { if fields - .insert(field.id.as_str().into(), self.ty(state, &field.ty)?) + .insert(field.id.string.into(), self.ty(state, &field.ty)?) .is_some() { - return Err(new_error_with_span( - state.document.path, - field.id.0, - format!( - "duplicate field `{field}` for record type `{ty}`", - field = field.id, - ty = decl.id - ), - )); + return Err(Error::DuplicateRecordField { + field: field.id.string, + name: decl.id.string, + span: field.id.span, + }); } } @@ -1238,22 +1634,19 @@ impl ResolvedDocument { Ok(kind) } - fn flags_decl( + fn flags_decl<'a>( &mut self, - state: &mut ResolutionState, - decl: &ast::FlagsDecl, - ) -> Result { + state: &mut ResolutionState<'a>, + decl: &ast::FlagsDecl<'a>, + ) -> ResolutionResult<'a, ItemKind> { let mut flags = IndexSet::new(); - for flag in &decl.body.flags { - if !flags.insert(flag.as_str().into()) { - return Err(new_error_with_span( - state.document.path, - flag.0, - format!( - "duplicate flag `{flag}` for flags type `{ty}`", - ty = decl.id - ), - )); + for flag in &decl.flags { + if !flags.insert(flag.id.string.into()) { + return Err(Error::DuplicateFlag { + flag: flag.id.string, + name: decl.id.string, + span: flag.id.span, + }); } } @@ -1270,15 +1663,19 @@ impl ResolvedDocument { Ok(kind) } - fn enum_decl(&mut self, state: &mut ResolutionState, decl: &ast::EnumDecl) -> Result { + fn enum_decl<'a>( + &mut self, + state: &mut ResolutionState<'a>, + decl: &ast::EnumDecl<'a>, + ) -> ResolutionResult<'a, ItemKind> { let mut cases = IndexSet::new(); - for case in &decl.body.cases { - if !cases.insert(case.as_str().into()) { - return Err(new_error_with_span( - state.document.path, - case.0, - format!("duplicate case `{case}` for enum type `{ty}`", ty = decl.id), - )); + for case in &decl.cases { + if !cases.insert(case.id.string.to_owned()) { + return Err(Error::DuplicateEnumCase { + case: case.id.string, + name: decl.id.string, + span: case.id.span, + }); } } @@ -1289,20 +1686,21 @@ impl ResolvedDocument { kind, source: ItemSource::Definition, }); + self.register_name(state, decl.id, id)?; Ok(kind) } - fn type_alias( + fn type_alias<'a>( &mut self, - state: &mut ResolutionState, - alias: &ast::TypeAlias, - ) -> Result { + state: &mut ResolutionState<'a>, + alias: &ast::TypeAlias<'a>, + ) -> ResolutionResult<'a, ItemKind> { let kind = match &alias.kind { ast::TypeAliasKind::Func(f) => ItemKind::Type(Type::Func(self.func_type( state, &f.params, - f.results.as_ref(), + &f.results, FuncKind::Free, )?)), ast::TypeAliasKind::Type(ty) => match ty { @@ -1318,15 +1716,11 @@ impl ResolvedDocument { ItemKind::Type(Type::Func(id)) } _ => { - return Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) cannot be used in a type alias", - kind = item.kind.display(&self.definitions), - id = id.as_str(), - ), - )) + return Err(Error::InvalidAliasType { + name: id.string, + kind: item.kind.as_str(&self.definitions), + span: id.span, + }); } } } @@ -1348,94 +1742,80 @@ impl ResolvedDocument { Ok(kind) } - fn func_type_ref( + fn func_type_ref<'a>( &mut self, - state: &ResolutionState, - r: &ast::FuncTypeRef, + state: &ResolutionState<'a>, + r: &ast::FuncTypeRef<'a>, kind: FuncKind, - ) -> Result { + ) -> ResolutionResult<'a, FuncId> { match r { - ast::FuncTypeRef::Func(ty) => { - self.func_type(state, &ty.params, ty.results.as_ref(), kind) - } + ast::FuncTypeRef::Func(ty) => self.func_type(state, &ty.params, &ty.results, kind), ast::FuncTypeRef::Ident(id) => { let item = &self.items[self.require(state, id)?]; match item.kind { ItemKind::Type(Type::Func(id)) | ItemKind::Func(id) => Ok(id), - _ => Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) is not a function type", - kind = item.kind.display(&self.definitions), - id = id.as_str() - ), - )), + _ => Err(Error::NotFuncType { + name: id.string, + kind: item.kind.as_str(&self.definitions), + span: id.span, + }), } } } } - fn ty(&mut self, state: &ResolutionState, ty: &ast::Type) -> Result { + fn ty<'a>( + &mut self, + state: &ResolutionState<'a>, + ty: &ast::Type<'a>, + ) -> ResolutionResult<'a, ValueType> { match ty { - ast::Type::U8(_) => Ok(ValueType::Primitive(PrimitiveType::U8)), - ast::Type::S8(_) => Ok(ValueType::Primitive(PrimitiveType::S8)), - ast::Type::U16(_) => Ok(ValueType::Primitive(PrimitiveType::U16)), - ast::Type::S16(_) => Ok(ValueType::Primitive(PrimitiveType::S16)), - ast::Type::U32(_) => Ok(ValueType::Primitive(PrimitiveType::U32)), - ast::Type::S32(_) => Ok(ValueType::Primitive(PrimitiveType::S32)), - ast::Type::U64(_) => Ok(ValueType::Primitive(PrimitiveType::U64)), - ast::Type::S64(_) => Ok(ValueType::Primitive(PrimitiveType::S64)), - ast::Type::Float32(_) => Ok(ValueType::Primitive(PrimitiveType::Float32)), - ast::Type::Float64(_) => Ok(ValueType::Primitive(PrimitiveType::Float64)), - ast::Type::Char(_) => Ok(ValueType::Primitive(PrimitiveType::Char)), - ast::Type::Bool(_) => Ok(ValueType::Primitive(PrimitiveType::Bool)), - ast::Type::String(_) => Ok(ValueType::Primitive(PrimitiveType::String)), + ast::Type::U8 => Ok(ValueType::Primitive(PrimitiveType::U8)), + ast::Type::S8 => Ok(ValueType::Primitive(PrimitiveType::S8)), + ast::Type::U16 => Ok(ValueType::Primitive(PrimitiveType::U16)), + ast::Type::S16 => Ok(ValueType::Primitive(PrimitiveType::S16)), + ast::Type::U32 => Ok(ValueType::Primitive(PrimitiveType::U32)), + ast::Type::S32 => Ok(ValueType::Primitive(PrimitiveType::S32)), + ast::Type::U64 => Ok(ValueType::Primitive(PrimitiveType::U64)), + ast::Type::S64 => Ok(ValueType::Primitive(PrimitiveType::S64)), + ast::Type::Float32 => Ok(ValueType::Primitive(PrimitiveType::Float32)), + ast::Type::Float64 => Ok(ValueType::Primitive(PrimitiveType::Float64)), + ast::Type::Char => Ok(ValueType::Primitive(PrimitiveType::Char)), + ast::Type::Bool => Ok(ValueType::Primitive(PrimitiveType::Bool)), + ast::Type::String => Ok(ValueType::Primitive(PrimitiveType::String)), ast::Type::Tuple(v) => { let types = v - .types .iter() .map(|ty| self.ty(state, ty)) - .collect::>()?; + .collect::>()?; Ok(ValueType::Defined( self.definitions.types.alloc(DefinedType::Tuple(types)), )) } - ast::Type::List(l) => { - let ty = self.ty(state, &l.ty)?; + ast::Type::List(ty) => { + let ty = self.ty(state, ty)?; Ok(ValueType::Defined( self.definitions.types.alloc(DefinedType::List(ty)), )) } - ast::Type::Option(o) => { - let ty = self.ty(state, &o.ty)?; + ast::Type::Option(ty) => { + let ty = self.ty(state, ty)?; Ok(ValueType::Defined( self.definitions.types.alloc(DefinedType::Option(ty)), )) } - ast::Type::Result(r) => { - let (ok, err) = match &r.specified { - Some(s) => { - let ok = match &s.ok { - ast::OmitType::Omitted(_) => None, - ast::OmitType::Type(t) => Some(self.ty(state, t)?), - }; - - let err = s.err.as_ref().map(|t| self.ty(state, t)).transpose()?; - (ok, err) - } - None => (None, None), - }; - + ast::Type::Result { ok, err } => { + let ok = ok.as_ref().map(|t| self.ty(state, t)).transpose()?; + let err = err.as_ref().map(|t| self.ty(state, t)).transpose()?; Ok(ValueType::Defined( self.definitions .types .alloc(DefinedType::Result { ok, err }), )) } - ast::Type::Borrow(b) => { - let item = &self.items[self.require(state, &b.id)?]; + ast::Type::Borrow(id) => { + let item = &self.items[self.require(state, id)?]; if let ItemKind::Type(Type::Value(id)) = item.kind { if let DefinedType::Resource(id) = &self.definitions.types[id] { return Ok(ValueType::Defined( @@ -1444,87 +1824,78 @@ impl ResolvedDocument { } } - Err(new_error_with_span( - state.document.path, - b.id.0, - format!( - "`{id}` ({kind}) is not a resource type", - kind = item.kind.display(&self.definitions), - id = b.id.as_str() - ), - )) + Err(Error::NotResourceType { + name: id.string, + kind: item.kind.as_str(&self.definitions), + span: id.span, + }) } ast::Type::Ident(id) => { let item = &self.items[self.require(state, id)?]; match item.kind { ItemKind::Type(Type::Value(id)) => Ok(ValueType::Defined(id)), - _ => Err(new_error_with_span( - state.document.path, - id.0, - format!( - "`{id}` ({kind}) cannot be used as a value type", - kind = item.kind.display(&self.definitions), - id = id.as_str(), - ), - )), + _ => Err(Error::NotValueType { + name: id.string, + kind: item.kind.as_str(&self.definitions), + span: id.span, + }), } } } } - fn func_type( + fn func_type<'a>( &mut self, - state: &ResolutionState, - func_params: &ast::ParamList, - func_results: Option<&ast::ResultList>, + state: &ResolutionState<'a>, + func_params: &[ast::NamedType<'a>], + func_results: &ast::ResultList<'a>, kind: FuncKind, - ) -> Result { + ) -> ResolutionResult<'a, FuncId> { let mut params = IndexMap::new(); - for param in &func_params.list { + for param in func_params { if params - .insert(param.id.as_str().into(), self.ty(state, ¶m.ty)?) + .insert(param.id.string.into(), self.ty(state, ¶m.ty)?) .is_some() { - return Err(new_error_with_span( - state.document.path, - param.id.0, - format!("duplicate {kind} parameter `{id}`", id = param.id.as_str()), - )); + return Err(Error::DuplicateParameter { + name: param.id.string, + kind, + span: param.id.span, + }); } } - let results = func_results - .as_ref() - .map(|r| match r { - ast::ResultList::Named { results: r, .. } => { - let mut results = IndexMap::new(); - for result in &r.list { - if results - .insert(result.id.as_str().into(), self.ty(state, &result.ty)?) - .is_some() - { - return Err(new_error_with_span( - state.document.path, - result.id.0, - format!("duplicate {kind} result `{id}`", id = result.id.as_str()), - )); - } + let results = match func_results { + ast::ResultList::Empty => None, + ast::ResultList::Named(results) => { + let mut list = IndexMap::new(); + for result in results { + if list + .insert(result.id.string.to_owned(), self.ty(state, &result.ty)?) + .is_some() + { + return Err(Error::DuplicateResult { + name: result.id.string, + kind, + span: result.id.span, + }); } - Ok(FuncResult::List(results)) } - ast::ResultList::Single { ty, .. } => Ok(FuncResult::Scalar(self.ty(state, ty)?)), - }) - .transpose()?; + Some(FuncResult::List(list)) + } + ast::ResultList::Scalar(ty) => Some(FuncResult::Scalar(self.ty(state, ty)?)), + }; Ok(self.definitions.funcs.alloc(Func { params, results })) } - fn resolve_package<'b>( + fn resolve_package<'a, 'b>( &mut self, - state: &'b mut ResolutionState, - name: &ast::PackageName, - ) -> Result<&'b Package> { - match state.packages.entry(name.as_str().to_owned()) { + state: &'b mut ResolutionState<'a>, + name: &'a str, + span: Span<'a>, + ) -> ResolutionResult<'a, &'b Package> { + match state.packages.entry(name.to_owned()) { hash_map::Entry::Occupied(e) => Ok(e.into_mut()), hash_map::Entry::Vacant(e) => { log::debug!("resolving package `{name}`"); @@ -1533,46 +1904,42 @@ impl ResolvedDocument { .as_deref() .and_then(|r| r.resolve(name).transpose()) .transpose() - .map_err(|e| { - let msg = - format!("failed to resolve package `{name}`", name = name.as_str()); - e.context(new_error_with_span(state.document.path, name.span(), msg)) + .map_err(|e| Error::PackageResolutionFailure { + name, + span, + source: e, })? { Some(bytes) => Ok(e.insert( Package::parse(&mut self.definitions, bytes).map_err(|e| { - let msg = - format!("failed to parse package `{name}`", name = name.as_str()); - e.context(new_error_with_span(state.document.path, name.span(), msg)) + Error::PackageParseFailure { + name, + span, + source: e, + } })?, )), - None => { - return Err(new_error_with_span( - state.document.path, - name.span(), - format!("unknown package `{name}`", name = name.as_str()), - )); - } + None => Err(Error::UnknownPackage { name, span }), } } } } - fn resolve_package_export( + fn resolve_package_export<'a>( &mut self, - state: &mut ResolutionState, - path: &ast::PackagePath, - ) -> Result { + state: &mut ResolutionState<'a>, + path: &ast::PackagePath<'a>, + ) -> ResolutionResult<'a, ItemKind> { // Check for reference to local item - if path.name.as_str() == self.package { + if path.name == self.package { return self.resolve_local_export(state, path); } - let pkg = self.resolve_package(state, &path.name)?; + let pkg = self.resolve_package(state, path.name, path.package_name_span())?; let mut current = 0; let mut parent_ty = None; let mut found = None; - for (i, segment) in path.segments.iter().map(|(_, s)| s.as_str()).enumerate() { + for (i, (segment, _)) in path.segment_spans().enumerate() { current = i; // Look up the first segment in the package definitions @@ -1582,32 +1949,25 @@ impl ResolvedDocument { } // Otherwise, project into the parent based on the current segment - let (ty, export) = match found.unwrap() { + let export = match found.unwrap() { // The parent is an interface or instance - ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => ( - "interface", + ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { self.definitions.interfaces[id] .exports .get(segment) - .map(Extern::kind), - ), + .map(Extern::kind) + } // The parent is a world or component or component instantiation ItemKind::Type(Type::World(id)) | ItemKind::Component(id) - | ItemKind::Instantiation(id) => ( - "world", - self.definitions.worlds[id] - .exports - .get(segment) - .map(Extern::kind), - ), - ItemKind::Func(_) => ("func", None), - ItemKind::Type(_) => ("type", None), - ItemKind::Module(_) => ("module", None), - ItemKind::Value(_) => ("value", None), + | ItemKind::Instantiation(id) => self.definitions.worlds[id] + .exports + .get(segment) + .map(Extern::kind), + _ => None, }; - parent_ty = Some(ty); + parent_ty = found.map(|kind| kind.as_str(&self.definitions)); found = export; if found.is_none() { break; @@ -1615,60 +1975,49 @@ impl ResolvedDocument { } found.ok_or_else(|| { - let segment = path.segments[current].1; - new_error_with_span( - state.document.path, - segment.0, - format!( - "{prev}package `{name}` has no export named `{segment}`", - prev = match parent_ty { - Some(parent) => { - format!( - "{parent} `{path}` in ", - path = path.segments[..current].iter().fold( - String::new(), - |mut path, (_, s)| { - if !path.is_empty() { - path.push('/'); - } - path.push_str(s.as_str()); - path - } - ) - ) - } - None => String::new(), - }, - name = path.name.as_str(), - segment = segment.as_str(), - ), - ) + let segments = path.segment_spans().enumerate(); + let mut prev_path = String::new(); + for (i, (segment, span)) in segments { + if i == current { + return Error::PackageMissingExport { + name: path.span.as_str(), + export: segment, + kind: parent_ty, + path: prev_path, + span, + }; + } + + if !prev_path.is_empty() { + prev_path.push('/'); + } + + prev_path.push_str(segment); + } + + unreachable!("path segments should never be empty") }) } - fn resolve_local_export( + fn resolve_local_export<'a>( &self, state: &ResolutionState, - path: &ast::PackagePath, - ) -> Result { - log::debug!("resolving local path `{path}`"); - - let mut segments = path.segments.iter().map(|(_, s)| s); - let id = segments.next().unwrap(); - let item = - &self.items[self.scopes[state.root_scope] - .get(id.as_str()) - .ok_or_else(|| { - new_error_with_span( - state.document.path, - id.0, - format!("undefined name `{id}`", id = id.as_str()), - ) - })?]; + path: &ast::PackagePath<'a>, + ) -> ResolutionResult<'a, ItemKind> { + log::debug!("resolving local path `{path}`", path = path.span.as_str()); + + let mut segments = path.segment_spans(); + let (segment, span) = segments.next().unwrap(); + let item = &self.items[self.scopes[state.root_scope].get(segment).ok_or({ + Error::UndefinedName { + name: segment, + span, + } + })?]; - let mut current = id; + let mut current = segment; let mut kind = item.kind; - for segment in segments { + for (segment, span) in segments { let exports = match kind { ItemKind::Type(Type::Interface(id)) | ItemKind::Instance(id) => { &self.definitions.interfaces[id].exports @@ -1677,40 +2026,35 @@ impl ResolvedDocument { &self.definitions.worlds[id].exports } _ => { - return Err(new_error_with_span( - state.document.path, - current.0, - format!( - "`{id}` ({kind}) has no exports", - kind = item.kind.display(&self.definitions), - id = current.as_str() - ), - )) + return Err(Error::PackagePathMissingExport { + name: current, + kind: kind.as_str(&self.definitions), + export: segment, + span, + }); } }; - kind = exports - .get(segment.as_str()) - .map(Extern::kind) - .ok_or_else(|| { - new_error_with_span( - state.document.path, - current.0, - format!( - "`{id}` ({kind}) has no export named `{segment}`", - kind = item.kind.display(&self.definitions), - id = current.as_str(), - segment = segment.as_str(), - ), - ) - })?; + kind = exports.get(segment).map(Extern::kind).ok_or_else(|| { + Error::PackagePathMissingExport { + name: current, + kind: kind.as_str(&self.definitions), + export: segment, + span, + } + })?; + current = segment; } Ok(kind) } - fn expr<'a>(&mut self, state: &mut ResolutionState<'a>, expr: &'a ast::Expr) -> Result { + fn expr<'a>( + &mut self, + state: &mut ResolutionState<'a>, + expr: &'a ast::Expr, + ) -> ResolutionResult<'a, ItemId> { let mut item = self.primary_expr(state, &expr.primary)?; for expr in &expr.postfix { @@ -1724,10 +2068,10 @@ impl ResolvedDocument { &mut self, state: &mut ResolutionState<'a>, expr: &'a ast::PrimaryExpr<'a>, - ) -> Result { + ) -> ResolutionResult<'a, ItemId> { match expr { - ast::PrimaryExpr::New(n) => self.new_expr(state, n), - ast::PrimaryExpr::Nested(n) => self.expr(state, &n.expr), + ast::PrimaryExpr::New(e) => self.new_expr(state, e), + ast::PrimaryExpr::Nested(e) => self.expr(state, &e.0), ast::PrimaryExpr::Ident(i) => Ok(self.require(state, i)?), } } @@ -1736,13 +2080,13 @@ impl ResolvedDocument { &mut self, state: &mut ResolutionState<'a>, expr: &'a ast::NewExpr<'a>, - ) -> Result { - let pkg = self.resolve_package(state, &expr.package_name)?; + ) -> ResolutionResult<'a, ItemId> { + let pkg = self.resolve_package(state, expr.package.name, expr.package.span)?; let ty = pkg.ty; - let require_all = expr.body.ellipsis.is_none(); + let require_all = !expr.ellipsis; let mut arguments: IndexMap = Default::default(); - for arg in &expr.body.arguments { + for arg in &expr.arguments { let (name, item, span) = match arg { ast::InstantiationArgument::Named(arg) => { self.named_instantiation_arg(state, arg, ty)? @@ -1753,34 +2097,27 @@ impl ResolvedDocument { }; let world = &self.definitions.worlds[ty]; - let expected = world.imports.get(&name).ok_or_else(|| { - new_error_with_span( - state.document.path, - span, - format!( - "component `{pkg}` has no import named `{name}`", - pkg = expr.package_name.as_str(), - ), - ) - })?; + let expected = + world + .imports + .get(&name) + .ok_or_else(|| Error::MissingComponentImport { + package: expr.package.span.as_str(), + import: name.clone(), + span, + })?; SubtypeChecker::new(&self.definitions) .is_subtype(expected.kind(), self.items[item].kind) - .map_err(|e| { - e.context(new_error_with_span( - state.document.path, - span, - format!("mismatched instantiation argument `{name}`"), - )) + .map_err(|e| Error::MismatchedInstantiationArg { + name: name.clone(), + span, + source: e, })?; let prev = arguments.insert(name.clone(), item); if prev.is_some() { - return Err(new_error_with_span( - state.document.path, - span, - format!("duplicate instantiation argument `{name}`"), - )); + return Err(Error::DuplicateInstantiationArg { name, span }); } } @@ -1791,14 +2128,11 @@ impl ResolvedDocument { .iter() .find(|(n, _)| !arguments.contains_key(*n)) { - return Err(new_error_with_span( - state.document.path, - expr.package_name.span(), - format!( - "missing instantiation argument `{missing}` for package `{pkg}`", - pkg = expr.package_name.as_str(), - ), - )); + return Err(Error::MissingInstantiationArg { + name: missing.clone(), + package: expr.package.span.as_str(), + span: expr.package.span, + }); } } @@ -1813,16 +2147,16 @@ impl ResolvedDocument { state: &mut ResolutionState<'a>, arg: &'a ast::NamedInstantiationArgument<'a>, world: WorldId, - ) -> Result<(String, ItemId, Span<'a>)> { + ) -> ResolutionResult<'a, (String, ItemId, Span<'a>)> { let item = self.expr(state, &arg.expr)?; let name = match &arg.name { ast::InstantiationArgumentName::Ident(ident) => Self::find_matching_interface_name( - ident.as_str(), + ident.string, &self.definitions.worlds[world].imports, ) - .unwrap_or_else(|| ident.as_str()), - ast::InstantiationArgumentName::String(name) => name.as_str(), + .unwrap_or(ident.string), + ast::InstantiationArgumentName::String(name) => name.value, }; Ok((name.to_owned(), item, arg.name.span())) @@ -1833,7 +2167,7 @@ impl ResolvedDocument { state: &mut ResolutionState<'a>, ident: &ast::Ident<'a>, world: WorldId, - ) -> Result<(String, ItemId, Span<'a>)> { + ) -> ResolutionResult<'a, (String, ItemId, Span<'a>)> { let item_id = self.require(state, ident)?; let item = &self.items[item_id]; let world = &self.definitions.worlds[world]; @@ -1842,7 +2176,7 @@ impl ResolvedDocument { if let ItemKind::Instance(id) = item.kind { if let Some(id) = &self.definitions.interfaces[id].id { if world.imports.contains_key(id.as_str()) { - return Ok((id.clone(), item_id, ident.0)); + return Ok((id.clone(), item_id, ident.span)); } } } @@ -1851,7 +2185,7 @@ impl ResolvedDocument { match &item.source { ItemSource::Import { with: Some(name) } | ItemSource::Alias { export: name, .. } => { if world.imports.contains_key(name) { - return Ok((name.clone(), item_id, ident.0)); + return Ok((name.clone(), item_id, ident.span)); } } _ => {} @@ -1859,12 +2193,12 @@ impl ResolvedDocument { // Fall back to searching for a matching interface name, provided it is not ambiguous // For example, match `foo:bar/baz` if `baz` is the identifier and the only match - if let Some(name) = Self::find_matching_interface_name(ident.as_str(), &world.imports) { - return Ok((name.to_owned(), item_id, ident.0)); + if let Some(name) = Self::find_matching_interface_name(ident.string, &world.imports) { + return Ok((name.to_owned(), item_id, ident.span)); } // Finally default to the id itself - Ok((ident.as_str().to_owned(), item_id, ident.0)) + Ok((ident.string.to_owned(), item_id, ident.span)) } fn find_matching_interface_name<'a>(name: &str, externs: &'a ExternMap) -> Option<&'a str> { @@ -1899,93 +2233,61 @@ impl ResolvedDocument { &mut self, state: &mut ResolutionState<'a>, item: ItemId, - expr: &'a ast::PostfixExpr<'a>, - ) -> Result { + expr: &ast::PostfixExpr<'a>, + ) -> ResolutionResult<'a, ItemId> { match expr { ast::PostfixExpr::Access(expr) => { - let exports = self.instance_exports(item, state, expr.id.0)?; - let name: Cow<'a, str> = - Self::find_matching_interface_name(expr.id.as_str(), exports) - .map(|s| s.to_string().into()) - .unwrap_or_else(|| expr.id.as_str().into()); - self.access_expr(state, item, name.as_ref(), expr.id.0) + let exports = self.instance_exports(item, expr.id.span)?; + let name = Self::find_matching_interface_name(expr.id.string, exports) + .unwrap_or(expr.id.string) + .to_owned(); + self.access_expr(state, item, name, expr.id.span) } ast::PostfixExpr::NamedAccess(expr) => { - self.access_expr(state, item, expr.string.as_str(), expr.string.0) + self.access_expr(state, item, expr.string.value.to_owned(), expr.string.span) } } } - fn instance_exports( + fn instance_exports<'a>( &self, item: ItemId, - state: &ResolutionState, - span: Span, - ) -> Result<&ExternMap> { + span: Span<'a>, + ) -> ResolutionResult<'a, &ExternMap> { match self.items[item].kind { ItemKind::Instance(id) => Ok(&self.definitions.interfaces[id].exports), ItemKind::Instantiation(id) => Ok(&self.definitions.worlds[id].exports), - ItemKind::Type(Type::Interface(_)) => Err(new_error_with_span( - state.document.path, + ItemKind::Type(Type::Interface(_)) => Err(Error::InaccessibleInterface { span }), + kind => Err(Error::Inaccessible { + kind: kind.as_str(&self.definitions), span, - "an interface cannot be accessed", - )), - ItemKind::Type(ty) => Err(new_error_with_span( - state.document.path, - span, - format!( - "a {ty} cannot be accessed", - ty = ty.display(&self.definitions) - ), - )), - ItemKind::Func(_) => Err(new_error_with_span( - state.document.path, - span, - "a function cannot be accessed", - )), - ItemKind::Component(_) => Err(new_error_with_span( - state.document.path, - span, - "a component cannot be accessed", - )), - ItemKind::Module(_) => Err(new_error_with_span( - state.document.path, - span, - "a module cannot be accessed", - )), - ItemKind::Value(_) => Err(new_error_with_span( - state.document.path, - span, - "a value cannot be accessed", - )), + }), } } - fn access_expr( + fn access_expr<'a>( &mut self, - state: &mut ResolutionState, + state: &mut ResolutionState<'a>, item: ItemId, - name: &str, - span: Span, - ) -> Result { - let exports = self.instance_exports(item, state, span)?; - let kind = exports.get(name).map(Extern::kind).ok_or_else(|| { - new_error_with_span( - state.document.path, - span, - format!("the instance has no export named `{name}`"), - ) - })?; + name: String, + span: Span<'a>, + ) -> ResolutionResult<'a, ItemId> { + let exports = self.instance_exports(item, span)?; + let kind = + exports + .get(&name) + .map(Extern::kind) + .ok_or_else(|| Error::MissingInstanceExport { + name: name.clone(), + span, + })?; - match state.aliases.entry((item, name.to_owned())) { + match state.aliases.entry((item, name.clone())) { hash_map::Entry::Occupied(e) => Ok(*e.get()), hash_map::Entry::Vacant(e) => { let id = self.items.alloc(Item { kind, - source: ItemSource::Alias { - item, - export: name.to_owned(), - }, + source: ItemSource::Alias { item, export: name }, }); Ok(*e.insert(id)) } diff --git a/crates/wac-parser/src/resolution/types.rs b/crates/wac-parser/src/resolution/types.rs index 7e14e6d..6ba7bdf 100644 --- a/crates/wac-parser/src/resolution/types.rs +++ b/crates/wac-parser/src/resolution/types.rs @@ -67,27 +67,13 @@ pub enum Type { } impl Type { - pub(crate) fn display<'a>(&'a self, definitions: &'a Definitions) -> impl fmt::Display + 'a { - TypeDisplay { - ty: self, - definitions, - } - } -} - -struct TypeDisplay<'a> { - ty: &'a Type, - definitions: &'a Definitions, -} - -impl fmt::Display for TypeDisplay<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.ty { - Type::Func(_) => write!(f, "function type"), - Type::Value(ty) => self.definitions.types[*ty].display(self.definitions).fmt(f), - Type::Interface(_) => write!(f, "interface"), - Type::World(_) => write!(f, "world"), - Type::Module(_) => write!(f, "module type"), + pub(crate) fn as_str(&self, definitions: &Definitions) -> &'static str { + match self { + Type::Func(_) => "function type", + Type::Value(ty) => definitions.types[*ty].as_str(definitions), + Type::Interface(_) => "interface", + Type::World(_) => "world", + Type::Module(_) => "module type", } } } @@ -124,22 +110,22 @@ pub enum PrimitiveType { String, } -impl fmt::Display for PrimitiveType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +impl PrimitiveType { + fn as_str(&self) -> &'static str { match self { - Self::U8 => write!(f, "u8"), - Self::S8 => write!(f, "s8"), - Self::U16 => write!(f, "u16"), - Self::S16 => write!(f, "s16"), - Self::U32 => write!(f, "u32"), - Self::S32 => write!(f, "s32"), - Self::U64 => write!(f, "u64"), - Self::S64 => write!(f, "s64"), - Self::Float32 => write!(f, "float32"), - Self::Float64 => write!(f, "float64"), - Self::Char => write!(f, "char"), - Self::Bool => write!(f, "bool"), - Self::String => write!(f, "string"), + Self::U8 => "u8", + Self::S8 => "s8", + Self::U16 => "u16", + Self::S16 => "s16", + Self::U32 => "u32", + Self::S32 => "s32", + Self::U64 => "u64", + Self::S64 => "s64", + Self::Float32 => "float32", + Self::Float64 => "float64", + Self::Char => "char", + Self::Bool => "bool", + Self::String => "string", } } } @@ -218,36 +204,22 @@ pub enum DefinedType { } impl DefinedType { - pub(crate) fn display<'a>(&'a self, definitions: &'a Definitions) -> impl fmt::Display + 'a { - DefinedTypeDisplay { - ty: self, - definitions, - } - } -} - -struct DefinedTypeDisplay<'a> { - ty: &'a DefinedType, - definitions: &'a Definitions, -} - -impl fmt::Display for DefinedTypeDisplay<'_> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self.ty { - DefinedType::Primitive(ty) => write!(f, "{ty}"), - DefinedType::Tuple(_) => write!(f, "tuple"), - DefinedType::List(_) => write!(f, "list"), - DefinedType::Option(_) => write!(f, "option"), - DefinedType::Result { .. } => write!(f, "result"), - DefinedType::Borrow(_) => write!(f, "borrow"), - DefinedType::Resource(_) => write!(f, "resource"), - DefinedType::Variant(_) => write!(f, "variant"), - DefinedType::Record(_) => write!(f, "record"), - DefinedType::Flags(_) => write!(f, "flags"), - DefinedType::Enum(_) => write!(f, "enum"), - DefinedType::Alias(ValueType::Primitive(ty)) => write!(f, "{ty}"), + fn as_str(&self, definitions: &Definitions) -> &'static str { + match self { + DefinedType::Primitive(ty) => ty.as_str(), + DefinedType::Tuple(_) => "tuple", + DefinedType::List(_) => "list", + DefinedType::Option(_) => "option", + DefinedType::Result { .. } => "result", + DefinedType::Borrow(_) => "borrow", + DefinedType::Resource(_) => "resource", + DefinedType::Variant(_) => "variant", + DefinedType::Record(_) => "record", + DefinedType::Flags(_) => "flags", + DefinedType::Enum(_) => "enum", + DefinedType::Alias(ValueType::Primitive(ty)) => ty.as_str(), DefinedType::Alias(ValueType::Defined(id)) => { - self.definitions.types[*id].display(self.definitions).fmt(f) + definitions.types[*id].as_str(definitions) } } } @@ -778,8 +750,8 @@ impl<'a> SubtypeChecker<'a> { (ItemKind::Value(a), ItemKind::Value(b)) => self.value_type(a, b), _ => bail!( "expected {a}, found {b}", - a = a.display(self.definitions), - b = b.display(self.definitions) + a = a.as_str(self.definitions), + b = b.as_str(self.definitions) ), }; @@ -803,8 +775,8 @@ impl<'a> SubtypeChecker<'a> { bail!( "expected {a}, found {b}", - a = a.display(self.definitions), - b = b.display(self.definitions) + a = a.as_str(self.definitions), + b = b.as_str(self.definitions) ) } @@ -1147,7 +1119,11 @@ impl<'a> SubtypeChecker<'a> { if let DefinedType::Primitive(b) = b { Self::primitive(a, *b) } else { - bail!("expected {a}, found {b}", b = b.display(self.definitions)); + bail!( + "expected {a}, found {b}", + a = a.as_str(), + b = b.as_str(self.definitions) + ); } } (ValueType::Defined(a), ValueType::Primitive(b)) => { @@ -1155,7 +1131,11 @@ impl<'a> SubtypeChecker<'a> { if let DefinedType::Primitive(a) = a { Self::primitive(*a, b) } else { - bail!("expected {a}, found {b}", a = a.display(self.definitions)); + bail!( + "expected {a}, found {b}", + a = a.as_str(self.definitions), + b = b.as_str() + ); } } (ValueType::Defined(a), ValueType::Defined(b)) => self.defined_type(a, b), @@ -1215,8 +1195,8 @@ impl<'a> SubtypeChecker<'a> { _ => { bail!( "expected {a}, found {b}", - a = a.display(self.definitions), - b = b.display(self.definitions) + a = a.as_str(self.definitions), + b = b.as_str(self.definitions) ) } } @@ -1349,7 +1329,7 @@ impl<'a> SubtypeChecker<'a> { // rather than actual subtyping; the reason for this is that implementing // runtimes don't yet support more complex subtyping rules. if a != b { - bail!("expected {a}, found {b}"); + bail!("expected {a}, found {b}", a = a.as_str(), b = b.as_str()); } Ok(()) diff --git a/crates/wac-parser/tests/parser.rs b/crates/wac-parser/tests/parser.rs index 799df50..4aacbd1 100644 --- a/crates/wac-parser/tests/parser.rs +++ b/crates/wac-parser/tests/parser.rs @@ -1,4 +1,5 @@ use anyhow::{bail, Context, Result}; +use owo_colors::OwoColorize; use pretty_assertions::StrComparison; use rayon::prelude::*; use std::{ @@ -9,7 +10,7 @@ use std::{ process::exit, sync::atomic::{AtomicUsize, Ordering}, }; -use wac_parser::ast::Document; +use wac_parser::{ast::Document, ErrorFormatter}; fn find_tests() -> Vec { let mut tests = Vec::new(); @@ -86,10 +87,13 @@ fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { } Err(e) => { if !should_fail { - bail!("the parse failed but it was expected to succeed: {e:?}"); + bail!( + "the parse failed but it was expected to succeed: {e}", + e = ErrorFormatter::new(test, e, false) + ); } - format!("{e:?}") + format!("{e}", e = ErrorFormatter::new(test, e, false)) } }; @@ -115,11 +119,11 @@ fn main() { .err() { Some(e) => { - println!("test {test_name} ... failed: {e}"); + println!("test {test_name} ... {failed}", failed = "failed".red()); Some((test_name, e)) } None => { - println!("test {test_name} ... ok"); + println!("test {test_name} ... {ok}", ok = "ok".green()); None } } @@ -127,10 +131,14 @@ fn main() { .collect::>(); if !errors.is_empty() { - eprintln!("\n{} test(s) failed:", errors.len()); + eprintln!( + "\n{count} test(s) {failed}:", + count = errors.len(), + failed = "failed".red() + ); for (name, msg) in errors.iter() { - eprintln!("{name}: {msg:?}"); + eprintln!("{name}: {msg:?}", msg = msg.red()); } exit(1); diff --git a/crates/wac-parser/tests/parser/export.wac.result b/crates/wac-parser/tests/parser/export.wac.result index 2c3c064..ea7e948 100644 --- a/crates/wac-parser/tests/parser/export.wac.result +++ b/crates/wac-parser/tests/parser/export.wac.result @@ -1,155 +1,129 @@ { "statements": [ { - "docs": [ - { - "str": "/// Export an item (default name)\n", - "start": 0, - "end": 34 - } - ], - "stmt": { - "export": { - "keyword": { - "str": "export", - "start": 34, - "end": 40 - }, - "expr": { - "primary": { - "ident": { + "Export": { + "docs": [ + { + "comment": "Export an item (default name)", + "span": { + "str": "/// Export an item (default name)", + "start": 0, + "end": 33 + } + } + ], + "with": null, + "expr": { + "primary": { + "ident": { + "string": "e", + "span": { "str": "e", "start": 41, "end": 42 } - }, - "postfix": [] + } }, - "with": null, - "semicolon": { - "str": ";", - "start": 42, - "end": 43 - } + "postfix": [] } } }, { - "docs": [ - { - "str": "/// Export an alias of an item (default name)\n", - "start": 45, - "end": 91 - } - ], - "stmt": { - "export": { - "keyword": { - "str": "export", - "start": 91, - "end": 97 - }, - "expr": { - "primary": { - "ident": { + "Export": { + "docs": [ + { + "comment": "Export an alias of an item (default name)", + "span": { + "str": "/// Export an alias of an item (default name)", + "start": 45, + "end": 90 + } + } + ], + "with": null, + "expr": { + "primary": { + "ident": { + "string": "e", + "span": { "str": "e", "start": 98, "end": 99 } - }, - "postfix": [ - { - "namedAccess": { - "open": { - "str": "[", - "start": 99, - "end": 100 - }, - "string": { + } + }, + "postfix": [ + { + "namedAccess": { + "span": { + "str": "[\"foo\"]", + "start": 99, + "end": 106 + }, + "string": { + "value": "foo", + "span": { "str": "\"foo\"", "start": 100, "end": 105 - }, - "close": { - "str": "]", - "start": 105, - "end": 106 } } } - ] - }, - "with": null, - "semicolon": { - "str": ";", - "start": 106, - "end": 107 - } + } + ] } } }, { - "docs": [ - { - "str": "/// Export an alias of an item with a different name\n", - "start": 109, - "end": 162 - } - ], - "stmt": { - "export": { - "keyword": { - "str": "export", - "start": 162, - "end": 168 - }, - "expr": { - "primary": { - "ident": { + "Export": { + "docs": [ + { + "comment": "Export an alias of an item with a different name", + "span": { + "str": "/// Export an alias of an item with a different name", + "start": 109, + "end": 161 + } + } + ], + "with": { + "value": "bar", + "span": { + "str": "\"bar\"", + "start": 183, + "end": 188 + } + }, + "expr": { + "primary": { + "ident": { + "string": "e", + "span": { "str": "e", "start": 169, "end": 170 } - }, - "postfix": [ - { - "namedAccess": { - "open": { - "str": "[", - "start": 170, - "end": 171 - }, - "string": { + } + }, + "postfix": [ + { + "namedAccess": { + "span": { + "str": "[\"foo\"]", + "start": 170, + "end": 177 + }, + "string": { + "value": "foo", + "span": { "str": "\"foo\"", "start": 171, "end": 176 - }, - "close": { - "str": "]", - "start": 176, - "end": 177 } } } - ] - }, - "with": { - "keyword": { - "str": "with", - "start": 178, - "end": 182 - }, - "name": { - "str": "\"bar\"", - "start": 183, - "end": 188 } - }, - "semicolon": { - "str": ";", - "start": 188, - "end": 189 - } + ] } } } diff --git a/crates/wac-parser/tests/parser/fail/bad-alias.wac.result b/crates/wac-parser/tests/parser/fail/bad-alias.wac.result index 1cb8839..60345da 100644 --- a/crates/wac-parser/tests/parser/fail/bad-alias.wac.result +++ b/crates/wac-parser/tests/parser/fail/bad-alias.wac.result @@ -1,9 +1,5 @@ -expected identifier - -Caused by: - --> tests/parser/fail/bad-alias.wac:1:6 - | - 1 | type u32 = x; - | ^--- - | - = expected identifier \ No newline at end of file +expected identifier, found `u32` keyword + --> tests/parser/fail/bad-alias.wac:1:6 + | + 1 | type u32 = x; + | ^-^ diff --git a/crates/wac-parser/tests/parser/fail/empty-enum.wac.result b/crates/wac-parser/tests/parser/fail/empty-enum.wac.result index 40e49a0..16437b7 100644 --- a/crates/wac-parser/tests/parser/fail/empty-enum.wac.result +++ b/crates/wac-parser/tests/parser/fail/empty-enum.wac.result @@ -1,9 +1,5 @@ -expected identifier - -Caused by: - --> tests/parser/fail/empty-enum.wac:1:9 - | - 1 | enum e {} - | ^--- - | - = expected identifier \ No newline at end of file +enum must contain at least one case + --> tests/parser/fail/empty-enum.wac:1:9 + | + 1 | enum e {} + | ^ diff --git a/crates/wac-parser/tests/parser/fail/empty-flags.wac.result b/crates/wac-parser/tests/parser/fail/empty-flags.wac.result index 584aec0..70c1c8b 100644 --- a/crates/wac-parser/tests/parser/fail/empty-flags.wac.result +++ b/crates/wac-parser/tests/parser/fail/empty-flags.wac.result @@ -1,9 +1,5 @@ -expected identifier - -Caused by: - --> tests/parser/fail/empty-flags.wac:1:10 - | - 1 | flags f {} - | ^--- - | - = expected identifier \ No newline at end of file +flags must contain at least one flag + --> tests/parser/fail/empty-flags.wac:1:10 + | + 1 | flags f {} + | ^ diff --git a/crates/wac-parser/tests/parser/fail/empty-record.wac.result b/crates/wac-parser/tests/parser/fail/empty-record.wac.result index 4bbbd49..09016e1 100644 --- a/crates/wac-parser/tests/parser/fail/empty-record.wac.result +++ b/crates/wac-parser/tests/parser/fail/empty-record.wac.result @@ -1,9 +1,5 @@ -expected identifier - -Caused by: - --> tests/parser/fail/empty-record.wac:1:11 - | - 1 | record r {} - | ^--- - | - = expected identifier \ No newline at end of file +record must contain at least one field + --> tests/parser/fail/empty-record.wac:1:11 + | + 1 | record r {} + | ^ diff --git a/crates/wac-parser/tests/parser/fail/empty-variant.wac.result b/crates/wac-parser/tests/parser/fail/empty-variant.wac.result index 3c188d3..4caeb48 100644 --- a/crates/wac-parser/tests/parser/fail/empty-variant.wac.result +++ b/crates/wac-parser/tests/parser/fail/empty-variant.wac.result @@ -1,9 +1,5 @@ -expected identifier - -Caused by: - --> tests/parser/fail/empty-variant.wac:1:12 - | - 1 | variant v {} - | ^--- - | - = expected identifier \ No newline at end of file +variant must contain at least one case + --> tests/parser/fail/empty-variant.wac:1:12 + | + 1 | variant v {} + | ^ diff --git a/crates/wac-parser/tests/parser/fail/expected-multiple.wac b/crates/wac-parser/tests/parser/fail/expected-multiple.wac new file mode 100644 index 0000000..5bb74ef --- /dev/null +++ b/crates/wac-parser/tests/parser/fail/expected-multiple.wac @@ -0,0 +1 @@ +type x = \ No newline at end of file diff --git a/crates/wac-parser/tests/parser/fail/expected-multiple.wac.result b/crates/wac-parser/tests/parser/fail/expected-multiple.wac.result new file mode 100644 index 0000000..a09aa3a --- /dev/null +++ b/crates/wac-parser/tests/parser/fail/expected-multiple.wac.result @@ -0,0 +1,5 @@ +expected either `func` keyword, `u8` keyword, `s8` keyword, `u16` keyword, `s16` keyword, `u32` keyword, `s32` keyword, `u64` keyword, `s64` keyword, `float32` keyword, or more..., found end of input + --> tests/parser/fail/expected-multiple.wac:1:10 + | + 1 | type x = + | ^ diff --git a/crates/wac-parser/tests/parser/fail/expected-two.wac b/crates/wac-parser/tests/parser/fail/expected-two.wac new file mode 100644 index 0000000..43c1662 --- /dev/null +++ b/crates/wac-parser/tests/parser/fail/expected-two.wac @@ -0,0 +1 @@ +record foo { diff --git a/crates/wac-parser/tests/parser/fail/expected-two.wac.result b/crates/wac-parser/tests/parser/fail/expected-two.wac.result new file mode 100644 index 0000000..5fddce0 --- /dev/null +++ b/crates/wac-parser/tests/parser/fail/expected-two.wac.result @@ -0,0 +1,5 @@ +expected `}` or identifier, found end of input + --> tests/parser/fail/expected-two.wac:2:1 + | + 2 | + | ^ diff --git a/crates/wac-parser/tests/parser/fail/missing-semi.wac.result b/crates/wac-parser/tests/parser/fail/missing-semi.wac.result index 45c927c..86cdc25 100644 --- a/crates/wac-parser/tests/parser/fail/missing-semi.wac.result +++ b/crates/wac-parser/tests/parser/fail/missing-semi.wac.result @@ -1,9 +1,5 @@ -expected `;` - -Caused by: - --> tests/parser/fail/missing-semi.wac:1:13 - | - 1 | type x = u32 - | ^--- - | - = expected `;` \ No newline at end of file +expected `;`, found end of input + --> tests/parser/fail/missing-semi.wac:1:13 + | + 1 | type x = u32 + | ^ diff --git a/crates/wac-parser/tests/parser/import.wac.result b/crates/wac-parser/tests/parser/import.wac.result index ba7d293..23df068 100644 --- a/crates/wac-parser/tests/parser/import.wac.result +++ b/crates/wac-parser/tests/parser/import.wac.result @@ -1,434 +1,229 @@ { "statements": [ { - "docs": [ - { - "str": "/// Import by func type\n", - "start": 0, - "end": 24 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 24, - "end": 30 - }, - "id": { + "Import": { + "docs": [ + { + "comment": "Import by func type", + "span": { + "str": "/// Import by func type", + "start": 0, + "end": 23 + } + } + ], + "id": { + "string": "a", + "span": { "str": "a", "start": 31, "end": 32 - }, - "with": null, - "colon": { - "str": ":", - "start": 32, - "end": 33 - }, - "ty": { - "func": { - "keyword": { - "str": "func", - "start": 34, - "end": 38 - }, - "params": { - "open": { - "str": "(", - "start": 38, - "end": 39 - }, - "list": [], - "close": { - "str": ")", - "start": 39, - "end": 40 - } - }, - "results": null - } - }, - "semicolon": { - "str": ";", - "start": 40, - "end": 41 + } + }, + "with": null, + "ty": { + "func": { + "params": [], + "results": "empty" } } } }, { - "docs": [ - { - "str": "/// Import by ident\n", - "start": 43, - "end": 63 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 63, - "end": 69 - }, - "id": { + "Import": { + "docs": [ + { + "comment": "Import by ident", + "span": { + "str": "/// Import by ident", + "start": 43, + "end": 62 + } + } + ], + "id": { + "string": "b", + "span": { "str": "b", "start": 70, "end": 71 - }, - "with": null, - "colon": { - "str": ":", - "start": 71, - "end": 72 - }, - "ty": { - "ident": { + } + }, + "with": null, + "ty": { + "ident": { + "string": "x", + "span": { "str": "x", "start": 73, "end": 74 } - }, - "semicolon": { - "str": ";", - "start": 74, - "end": 75 } } } }, { - "docs": [ - { - "str": "/// Import by package path\n", - "start": 77, - "end": 104 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 104, - "end": 110 - }, - "id": { + "Import": { + "docs": [ + { + "comment": "Import by package path", + "span": { + "str": "/// Import by package path", + "start": 77, + "end": 103 + } + } + ], + "id": { + "string": "c", + "span": { "str": "c", "start": 111, "end": 112 - }, - "with": null, - "colon": { - "str": ":", - "start": 112, - "end": 113 - }, - "ty": { - "package": { - "name": { - "parts": [ - { - "str": "foo", - "start": 114, - "end": 117 - }, - { - "str": "bar", - "start": 118, - "end": 121 - } - ] - }, - "segments": [ - [ - { - "str": "/", - "start": 121, - "end": 122 - }, - { - "str": "baz", - "start": 122, - "end": 125 - } - ] - ], - "version": null - } - }, - "semicolon": { - "str": ";", - "start": 125, - "end": 126 + } + }, + "with": null, + "ty": { + "package": { + "span": { + "str": "foo:bar/baz", + "start": 114, + "end": 125 + }, + "name": "foo:bar", + "segments": "baz", + "version": null } } } }, { - "docs": [ - { - "str": "/// Import by func type with kebab name \n", - "start": 128, - "end": 169 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 169, - "end": 175 - }, - "id": { + "Import": { + "docs": [ + { + "comment": "Import by func type with kebab name", + "span": { + "str": "/// Import by func type with kebab name ", + "start": 128, + "end": 168 + } + } + ], + "id": { + "string": "d", + "span": { "str": "d", "start": 176, "end": 177 - }, - "with": { - "keyword": { - "str": "with", - "start": 178, - "end": 182 - }, - "name": { - "str": "\"hello-world\"", - "start": 183, - "end": 196 - } - }, - "colon": { - "str": ":", - "start": 196, - "end": 197 - }, - "ty": { - "func": { - "keyword": { - "str": "func", - "start": 198, - "end": 202 - }, - "params": { - "open": { - "str": "(", - "start": 202, - "end": 203 - }, - "list": [ - { - "id": { - "str": "name", - "start": 203, - "end": 207 - }, - "colon": { - "str": ":", - "start": 207, - "end": 208 - }, - "ty": { - "string": { - "str": "string", - "start": 209, - "end": 215 - } - } + } + }, + "with": { + "value": "hello-world", + "span": { + "str": "\"hello-world\"", + "start": 183, + "end": 196 + } + }, + "ty": { + "func": { + "params": [ + { + "id": { + "string": "name", + "span": { + "str": "name", + "start": 203, + "end": 207 } - ], - "close": { - "str": ")", - "start": 215, - "end": 216 - } - }, - "results": null - } - }, - "semicolon": { - "str": ";", - "start": 216, - "end": 217 + }, + "ty": "string" + } + ], + "results": "empty" } } } }, { - "docs": [ - { - "str": "/// Import by inline interface\n", - "start": 219, - "end": 250 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 250, - "end": 256 - }, - "id": { + "Import": { + "docs": [ + { + "comment": "Import by inline interface", + "span": { + "str": "/// Import by inline interface", + "start": 219, + "end": 249 + } + } + ], + "id": { + "string": "e", + "span": { "str": "e", "start": 257, "end": 258 - }, - "with": null, - "colon": { - "str": ":", - "start": 258, - "end": 259 - }, - "ty": { - "interface": { - "keyword": { - "str": "interface", - "start": 260, - "end": 269 - }, - "body": { - "open": { - "str": "{", - "start": 270, - "end": 271 - }, - "items": [ - { - "docs": [], - "stmt": { - "export": { - "id": { - "str": "x", - "start": 276, - "end": 277 - }, - "colon": { - "str": ":", - "start": 277, - "end": 278 - }, - "ty": { - "func": { - "keyword": { - "str": "func", - "start": 279, - "end": 283 - }, - "params": { - "open": { - "str": "(", - "start": 283, - "end": 284 - }, - "list": [], - "close": { - "str": ")", - "start": 284, - "end": 285 - } - }, - "results": null - } - }, - "semicolon": { - "str": ";", - "start": 285, - "end": 286 - } - } + } + }, + "with": null, + "ty": { + "interface": { + "items": [ + { + "export": { + "docs": [], + "id": { + "string": "x", + "span": { + "str": "x", + "start": 276, + "end": 277 + } + }, + "ty": { + "func": { + "params": [], + "results": "empty" } } - ], - "close": { - "str": "}", - "start": 287, - "end": 288 } } - } - }, - "semicolon": { - "str": ";", - "start": 288, - "end": 289 + ] } } } }, { - "docs": [ - { - "str": "/// Import by package path with version\n", - "start": 291, - "end": 331 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 331, - "end": 337 - }, - "id": { + "Import": { + "docs": [ + { + "comment": "Import by package path with version", + "span": { + "str": "/// Import by package path with version", + "start": 291, + "end": 330 + } + } + ], + "id": { + "string": "f", + "span": { "str": "f", "start": 338, "end": 339 - }, - "with": null, - "colon": { - "str": ":", - "start": 339, - "end": 340 - }, - "ty": { - "package": { - "name": { - "parts": [ - { - "str": "foo", - "start": 341, - "end": 344 - }, - { - "str": "bar", - "start": 345, - "end": 348 - } - ] - }, - "segments": [ - [ - { - "str": "/", - "start": 348, - "end": 349 - }, - { - "str": "baz", - "start": 349, - "end": 352 - } - ] - ], - "version": [ - { - "str": "@", - "start": 352, - "end": 353 - }, - { - "str": "1.0.0", - "start": 353, - "end": 358 - } - ] - } - }, - "semicolon": { - "str": ";", - "start": 358, - "end": 359 + } + }, + "with": null, + "ty": { + "package": { + "span": { + "str": "foo:bar/baz@1.0.0", + "start": 341, + "end": 358 + }, + "name": "foo:bar", + "segments": "baz", + "version": "1.0.0" } } } diff --git a/crates/wac-parser/tests/parser/let.wac.result b/crates/wac-parser/tests/parser/let.wac.result index 5630fe5..32610a8 100644 --- a/crates/wac-parser/tests/parser/let.wac.result +++ b/crates/wac-parser/tests/parser/let.wac.result @@ -1,634 +1,441 @@ { "statements": [ { - "docs": [ - { - "str": "/// Instantiate without args\n", - "start": 0, - "end": 29 - } - ], - "stmt": { - "let": { - "keyword": { - "str": "let", - "start": 29, - "end": 32 - }, - "id": { + "Let": { + "docs": [ + { + "comment": "Instantiate without args", + "span": { + "str": "/// Instantiate without args", + "start": 0, + "end": 28 + } + } + ], + "id": { + "string": "a", + "span": { "str": "a", "start": 33, "end": 34 - }, - "equals": { - "str": "=", - "start": 35, - "end": 36 - }, - "expr": { - "primary": { - "new": { - "keyword": { - "str": "new", - "start": 37, - "end": 40 - }, - "packageName": { - "parts": [ - { - "str": "foo", - "start": 41, - "end": 44 - }, - { - "str": "bar", - "start": 45, - "end": 48 - } - ] - }, - "body": { - "open": { - "str": "{", - "start": 49, - "end": 50 - }, - "arguments": [], - "ellipsis": null, - "close": { - "str": "}", - "start": 50, - "end": 51 - } + } + }, + "expr": { + "primary": { + "new": { + "package": { + "name": "foo:bar", + "span": { + "str": "foo:bar", + "start": 41, + "end": 48 } - } - }, - "postfix": [] + }, + "arguments": [], + "ellipsis": false + } }, - "semicolon": { - "str": ";", - "start": 51, - "end": 52 - } + "postfix": [] } } }, { - "docs": [ - { - "str": "/// Instantiation with import-propagation.\n", - "start": 54, - "end": 97 - } - ], - "stmt": { - "let": { - "keyword": { - "str": "let", - "start": 97, - "end": 100 - }, - "id": { + "Let": { + "docs": [ + { + "comment": "Instantiation with import-propagation.", + "span": { + "str": "/// Instantiation with import-propagation.", + "start": 54, + "end": 96 + } + } + ], + "id": { + "string": "b", + "span": { "str": "b", "start": 101, "end": 102 - }, - "equals": { - "str": "=", - "start": 103, - "end": 104 - }, - "expr": { - "primary": { - "new": { - "keyword": { - "str": "new", - "start": 105, - "end": 108 - }, - "packageName": { - "parts": [ - { - "str": "foo", - "start": 109, - "end": 112 - }, - { - "str": "bar", - "start": 113, - "end": 116 - } - ] - }, - "body": { - "open": { - "str": "{", - "start": 117, - "end": 118 - }, - "arguments": [], - "ellipsis": { - "str": "...", - "start": 119, - "end": 122 - }, - "close": { - "str": "}", - "start": 123, - "end": 124 - } + } + }, + "expr": { + "primary": { + "new": { + "package": { + "name": "foo:bar", + "span": { + "str": "foo:bar", + "start": 109, + "end": 116 } - } - }, - "postfix": [] + }, + "arguments": [], + "ellipsis": true + } }, - "semicolon": { - "str": ";", - "start": 124, - "end": 125 - } + "postfix": [] } } }, { - "docs": [ - { - "str": "/// Instantiation with arguments\n", - "start": 127, - "end": 160 - } - ], - "stmt": { - "let": { - "keyword": { - "str": "let", - "start": 160, - "end": 163 - }, - "id": { + "Let": { + "docs": [ + { + "comment": "Instantiation with arguments", + "span": { + "str": "/// Instantiation with arguments", + "start": 127, + "end": 159 + } + } + ], + "id": { + "string": "c", + "span": { "str": "c", "start": 164, "end": 165 - }, - "equals": { - "str": "=", - "start": 166, - "end": 167 - }, - "expr": { - "primary": { - "new": { - "keyword": { - "str": "new", - "start": 168, - "end": 171 + } + }, + "expr": { + "primary": { + "new": { + "package": { + "name": "foo:bar", + "span": { + "str": "foo:bar", + "start": 172, + "end": 179 + } + }, + "arguments": [ + { + "ident": { + "string": "a", + "span": { + "str": "a", + "start": 182, + "end": 183 + } + } }, - "packageName": { - "parts": [ - { - "str": "foo", - "start": 172, - "end": 175 - }, - { - "str": "bar", - "start": 176, - "end": 179 + { + "ident": { + "string": "b", + "span": { + "str": "b", + "start": 185, + "end": 186 } - ] + } }, - "body": { - "open": { - "str": "{", - "start": 180, - "end": 181 - }, - "arguments": [ - { - "ident": { - "str": "a", - "start": 182, - "end": 183 - } - }, - { - "ident": { - "str": "b", - "start": 185, - "end": 186 + { + "named": { + "name": { + "string": { + "value": "c", + "span": { + "str": "\"c\"", + "start": 188, + "end": 191 + } } }, - { - "named": { - "name": { - "string": { - "str": "\"c\"", - "start": 188, - "end": 191 + "expr": { + "primary": { + "ident": { + "string": "c", + "span": { + "str": "c", + "start": 193, + "end": 194 } - }, - "colon": { - "str": ":", - "start": 191, - "end": 192 - }, - "expr": { - "primary": { - "ident": { - "str": "c", - "start": 193, - "end": 194 - } - }, - "postfix": [] } - } + }, + "postfix": [] } - ], - "ellipsis": null, - "close": { - "str": "}", - "start": 196, - "end": 197 } } - } - }, - "postfix": [] + ], + "ellipsis": false + } }, - "semicolon": { - "str": ";", - "start": 197, - "end": 198 - } + "postfix": [] } } }, { - "docs": [ - { - "str": "/// Instantiation with arguments and import-propagation.\n", - "start": 200, - "end": 257 - } - ], - "stmt": { - "let": { - "keyword": { - "str": "let", - "start": 257, - "end": 260 - }, - "id": { + "Let": { + "docs": [ + { + "comment": "Instantiation with arguments and import-propagation.", + "span": { + "str": "/// Instantiation with arguments and import-propagation.", + "start": 200, + "end": 256 + } + } + ], + "id": { + "string": "d", + "span": { "str": "d", "start": 261, "end": 262 - }, - "equals": { - "str": "=", - "start": 263, - "end": 264 - }, - "expr": { - "primary": { - "new": { - "keyword": { - "str": "new", - "start": 265, - "end": 268 - }, - "packageName": { - "parts": [ - { - "str": "foo", - "start": 269, - "end": 272 - }, - { - "str": "bar", - "start": 273, - "end": 276 + } + }, + "expr": { + "primary": { + "new": { + "package": { + "name": "foo:bar", + "span": { + "str": "foo:bar", + "start": 269, + "end": 276 + } + }, + "arguments": [ + { + "ident": { + "string": "a", + "span": { + "str": "a", + "start": 279, + "end": 280 } - ] + } }, - "body": { - "open": { - "str": "{", - "start": 277, - "end": 278 - }, - "arguments": [ - { - "ident": { - "str": "a", - "start": 279, - "end": 280 + { + "named": { + "name": { + "string": { + "value": "b", + "span": { + "str": "\"b\"", + "start": 282, + "end": 285 + } } }, - { - "named": { - "name": { - "string": { - "str": "\"b\"", - "start": 282, - "end": 285 - } - }, - "colon": { - "str": ":", - "start": 285, - "end": 286 - }, - "expr": { + "expr": { + "primary": { + "nested": { "primary": { - "nested": { - "open": { - "str": "(", - "start": 287, - "end": 288 - }, - "expr": { - "primary": { - "new": { - "keyword": { - "str": "new", - "start": 288, - "end": 291 - }, - "packageName": { - "parts": [ - { - "str": "foo", - "start": 292, - "end": 295 - }, - { - "str": "bar", - "start": 296, - "end": 299 - } - ] - }, - "body": { - "open": { - "str": "{", - "start": 300, - "end": 301 - }, - "arguments": [], - "ellipsis": null, - "close": { - "str": "}", - "start": 302, - "end": 303 - } - } - } - }, - "postfix": [] + "new": { + "package": { + "name": "foo:bar", + "span": { + "str": "foo:bar", + "start": 292, + "end": 299 + } }, - "close": { - "str": ")", - "start": 303, - "end": 304 - } + "arguments": [], + "ellipsis": false } }, "postfix": [] } + }, + "postfix": [] + } + } + }, + { + "named": { + "name": { + "ident": { + "string": "c", + "span": { + "str": "c", + "start": 306, + "end": 307 + } } }, - { - "named": { - "name": { - "ident": { + "expr": { + "primary": { + "ident": { + "string": "c", + "span": { "str": "c", - "start": 306, - "end": 307 + "start": 309, + "end": 310 } - }, - "colon": { - "str": ":", - "start": 307, - "end": 308 - }, - "expr": { - "primary": { - "ident": { - "str": "c", - "start": 309, - "end": 310 - } - }, - "postfix": [] } - } + }, + "postfix": [] } - ], - "ellipsis": { - "str": "...", - "start": 312, - "end": 315 - }, - "close": { - "str": "}", - "start": 316, - "end": 317 } } - } - }, - "postfix": [] + ], + "ellipsis": true + } }, - "semicolon": { - "str": ";", - "start": 317, - "end": 318 - } + "postfix": [] } } }, { - "docs": [ - { - "str": "/// Nested expression\n", - "start": 320, - "end": 342 - } - ], - "stmt": { - "let": { - "keyword": { - "str": "let", - "start": 342, - "end": 345 - }, - "id": { + "Let": { + "docs": [ + { + "comment": "Nested expression", + "span": { + "str": "/// Nested expression", + "start": 320, + "end": 341 + } + } + ], + "id": { + "string": "e", + "span": { "str": "e", "start": 346, "end": 347 - }, - "equals": { - "str": "=", - "start": 348, - "end": 349 - }, - "expr": { - "primary": { - "nested": { - "open": { - "str": "(", - "start": 350, - "end": 351 - }, - "expr": { - "primary": { - "ident": { - "str": "b", - "start": 351, - "end": 352 - } - }, - "postfix": [] - }, - "close": { - "str": ")", - "start": 352, - "end": 353 + } + }, + "expr": { + "primary": { + "nested": { + "primary": { + "ident": { + "string": "b", + "span": { + "str": "b", + "start": 351, + "end": 352 + } } - } - }, - "postfix": [] + }, + "postfix": [] + } }, - "semicolon": { - "str": ";", - "start": 353, - "end": 354 - } + "postfix": [] } } }, { - "docs": [ - { - "str": "/// Access expression\n", - "start": 356, - "end": 378 - } - ], - "stmt": { - "let": { - "keyword": { - "str": "let", - "start": 378, - "end": 381 - }, - "id": { + "Let": { + "docs": [ + { + "comment": "Access expression", + "span": { + "str": "/// Access expression", + "start": 356, + "end": 377 + } + } + ], + "id": { + "string": "f", + "span": { "str": "f", "start": 382, "end": 383 - }, - "equals": { - "str": "=", - "start": 384, - "end": 385 - }, - "expr": { - "primary": { - "ident": { + } + }, + "expr": { + "primary": { + "ident": { + "string": "e", + "span": { "str": "e", "start": 386, "end": 387 } - }, - "postfix": [ - { - "namedAccess": { - "open": { - "str": "[", - "start": 387, - "end": 388 - }, - "string": { + } + }, + "postfix": [ + { + "namedAccess": { + "span": { + "str": "[\"b-c\"]", + "start": 387, + "end": 394 + }, + "string": { + "value": "b-c", + "span": { "str": "\"b-c\"", "start": 388, "end": 393 - }, - "close": { - "str": "]", - "start": 393, - "end": 394 } } - }, - { - "access": { - "dot": { - "str": ".", - "start": 394, - "end": 395 - }, - "id": { + } + }, + { + "access": { + "span": { + "str": ".c", + "start": 394, + "end": 396 + }, + "id": { + "string": "c", + "span": { "str": "c", "start": 395, "end": 396 } } - }, - { - "access": { - "dot": { - "str": ".", - "start": 396, - "end": 397 - }, - "id": { + } + }, + { + "access": { + "span": { + "str": ".d", + "start": 396, + "end": 398 + }, + "id": { + "string": "d", + "span": { "str": "d", "start": 397, "end": 398 } } - }, - { - "namedAccess": { - "open": { - "str": "[", - "start": 398, - "end": 399 - }, - "string": { + } + }, + { + "namedAccess": { + "span": { + "str": "[\"foo:bar/baz\"]", + "start": 398, + "end": 413 + }, + "string": { + "value": "foo:bar/baz", + "span": { "str": "\"foo:bar/baz\"", "start": 399, "end": 412 - }, - "close": { - "str": "]", - "start": 412, - "end": 413 } } - }, - { - "access": { - "dot": { - "str": ".", - "start": 413, - "end": 414 - }, - "id": { + } + }, + { + "access": { + "span": { + "str": ".e", + "start": 413, + "end": 415 + }, + "id": { + "string": "e", + "span": { "str": "e", "start": 414, "end": 415 } } } - ] - }, - "semicolon": { - "str": ";", - "start": 415, - "end": 416 - } + } + ] } } } diff --git a/crates/wac-parser/tests/parser/resource.wac b/crates/wac-parser/tests/parser/resource.wac index 985819a..333ae44 100644 --- a/crates/wac-parser/tests/parser/resource.wac +++ b/crates/wac-parser/tests/parser/resource.wac @@ -1,14 +1,41 @@ -/// Resource without methods -resource r1; +interface foo { + /// Resource without methods + resource r1; -/// Resource with only constructor -resource r2 { - constructor(); + /// Resource with only constructor + resource r2 { + /// Constructor + constructor(); + } + + /// Resource with constructor, instance method, and static method + resource r3 { + /// Constructor + constructor(a: u32); + /// Method + a: func(b: u32); + /// Static method + b: static func(c: u32); + } } -/// Resource with constructor, instance method, and static method -resource r3 { - constructor(a: u32); - a: func(b: u32); - b: static func(c: u32); +world bar { + /// Resource without methods + resource r1; + + /// Resource with only constructor + resource r2 { + /// Constructor + constructor(); + } + + /// Resource with constructor, instance method, and static method + resource r3 { + /// Constructor + constructor(a: u32); + /// Method + a: func(b: u32); + /// Static method + b: static func(c: u32); + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/parser/resource.wac.result b/crates/wac-parser/tests/parser/resource.wac.result index a0be761..ffa0c6a 100644 --- a/crates/wac-parser/tests/parser/resource.wac.result +++ b/crates/wac-parser/tests/parser/resource.wac.result @@ -1,334 +1,458 @@ { "statements": [ { - "docs": [ - { - "str": "/// Resource without methods\n", - "start": 0, - "end": 29 - } - ], - "stmt": { - "type": { - "type": { - "resource": { - "keyword": { - "str": "resource", - "start": 29, - "end": 37 - }, - "id": { - "str": "r1", - "start": 38, - "end": 40 - }, - "body": { - "empty": { - "str": ";", - "start": 40, - "end": 41 + "Type": { + "interface": { + "docs": [], + "id": { + "string": "foo", + "span": { + "str": "foo", + "start": 10, + "end": 13 + } + }, + "items": [ + { + "type": { + "resource": { + "docs": [ + { + "comment": "Resource without methods", + "span": { + "str": "/// Resource without methods", + "start": 20, + "end": 48 + } + } + ], + "id": { + "string": "r1", + "span": { + "str": "r1", + "start": 62, + "end": 64 + } + }, + "methods": [] } } - } - } - } - } - }, - { - "docs": [ - { - "str": "/// Resource with only constructor\n", - "start": 43, - "end": 78 - } - ], - "stmt": { - "type": { - "type": { - "resource": { - "keyword": { - "str": "resource", - "start": 78, - "end": 86 - }, - "id": { - "str": "r2", - "start": 87, - "end": 89 - }, - "body": { - "methods": { - "open": { - "str": "{", - "start": 90, - "end": 91 + }, + { + "type": { + "resource": { + "docs": [ + { + "comment": "Resource with only constructor", + "span": { + "str": "/// Resource with only constructor", + "start": 71, + "end": 105 + } + } + ], + "id": { + "string": "r2", + "span": { + "str": "r2", + "start": 119, + "end": 121 + } }, "methods": [ - [ - { - "constructor": { - "keyword": { - "str": "constructor", - "start": 96, - "end": 107 - }, - "params": { - "open": { - "str": "(", - "start": 107, - "end": 108 + { + "constructor": { + "docs": [ + { + "comment": "Constructor", + "span": { + "str": "/// Constructor", + "start": 132, + "end": 147 + } + } + ], + "span": { + "str": "constructor", + "start": 156, + "end": 167 + }, + "params": [] + } + } + ] + } + } + }, + { + "type": { + "resource": { + "docs": [ + { + "comment": "Resource with constructor, instance method, and static method", + "span": { + "str": "/// Resource with constructor, instance method, and static method", + "start": 182, + "end": 247 + } + } + ], + "id": { + "string": "r3", + "span": { + "str": "r3", + "start": 261, + "end": 263 + } + }, + "methods": [ + { + "constructor": { + "docs": [ + { + "comment": "Constructor", + "span": { + "str": "/// Constructor", + "start": 274, + "end": 289 + } + } + ], + "span": { + "str": "constructor", + "start": 298, + "end": 309 + }, + "params": [ + { + "id": { + "string": "a", + "span": { + "str": "a", + "start": 310, + "end": 311 + } }, - "list": [], - "close": { - "str": ")", - "start": 108, - "end": 109 + "ty": "u32" + } + ] + } + }, + { + "method": { + "docs": [ + { + "comment": "Method", + "span": { + "str": "/// Method", + "start": 327, + "end": 337 } } + ], + "id": { + "string": "a", + "span": { + "str": "a", + "start": 346, + "end": 347 + } + }, + "isStatic": false, + "ty": { + "func": { + "params": [ + { + "id": { + "string": "b", + "span": { + "str": "b", + "start": 354, + "end": 355 + } + }, + "ty": "u32" + } + ], + "results": "empty" + } } - }, - { - "str": ";", - "start": 109, - "end": 110 } - ] - ], - "close": { - "str": "}", - "start": 111, - "end": 112 - } + }, + { + "method": { + "docs": [ + { + "comment": "Static method", + "span": { + "str": "/// Static method", + "start": 371, + "end": 388 + } + } + ], + "id": { + "string": "b", + "span": { + "str": "b", + "start": 397, + "end": 398 + } + }, + "isStatic": true, + "ty": { + "func": { + "params": [ + { + "id": { + "string": "c", + "span": { + "str": "c", + "start": 412, + "end": 413 + } + }, + "ty": "u32" + } + ], + "results": "empty" + } + } + } + } + ] } } } - } + ] } } }, { - "docs": [ - { - "str": "/// Resource with constructor, instance method, and static method\n", - "start": 114, - "end": 180 - } - ], - "stmt": { - "type": { - "type": { - "resource": { - "keyword": { - "str": "resource", - "start": 180, - "end": 188 - }, - "id": { - "str": "r3", - "start": 189, - "end": 191 - }, - "body": { - "methods": { - "open": { - "str": "{", - "start": 192, - "end": 193 + "Type": { + "world": { + "docs": [], + "id": { + "string": "bar", + "span": { + "str": "bar", + "start": 436, + "end": 439 + } + }, + "items": [ + { + "type": { + "resource": { + "docs": [ + { + "comment": "Resource without methods", + "span": { + "str": "/// Resource without methods", + "start": 446, + "end": 474 + } + } + ], + "id": { + "string": "r1", + "span": { + "str": "r1", + "start": 488, + "end": 490 + } + }, + "methods": [] + } + } + }, + { + "type": { + "resource": { + "docs": [ + { + "comment": "Resource with only constructor", + "span": { + "str": "/// Resource with only constructor", + "start": 497, + "end": 531 + } + } + ], + "id": { + "string": "r2", + "span": { + "str": "r2", + "start": 545, + "end": 547 + } }, "methods": [ - [ - { - "constructor": { - "keyword": { - "str": "constructor", - "start": 198, - "end": 209 - }, - "params": { - "open": { - "str": "(", - "start": 209, - "end": 210 + { + "constructor": { + "docs": [ + { + "comment": "Constructor", + "span": { + "str": "/// Constructor", + "start": 558, + "end": 573 + } + } + ], + "span": { + "str": "constructor", + "start": 582, + "end": 593 + }, + "params": [] + } + } + ] + } + } + }, + { + "type": { + "resource": { + "docs": [ + { + "comment": "Resource with constructor, instance method, and static method", + "span": { + "str": "/// Resource with constructor, instance method, and static method", + "start": 608, + "end": 673 + } + } + ], + "id": { + "string": "r3", + "span": { + "str": "r3", + "start": 687, + "end": 689 + } + }, + "methods": [ + { + "constructor": { + "docs": [ + { + "comment": "Constructor", + "span": { + "str": "/// Constructor", + "start": 700, + "end": 715 + } + } + ], + "span": { + "str": "constructor", + "start": 724, + "end": 735 + }, + "params": [ + { + "id": { + "string": "a", + "span": { + "str": "a", + "start": 736, + "end": 737 + } }, - "list": [ + "ty": "u32" + } + ] + } + }, + { + "method": { + "docs": [ + { + "comment": "Method", + "span": { + "str": "/// Method", + "start": 753, + "end": 763 + } + } + ], + "id": { + "string": "a", + "span": { + "str": "a", + "start": 772, + "end": 773 + } + }, + "isStatic": false, + "ty": { + "func": { + "params": [ { "id": { - "str": "a", - "start": 210, - "end": 211 - }, - "colon": { - "str": ":", - "start": 211, - "end": 212 - }, - "ty": { - "u32": { - "str": "u32", - "start": 213, - "end": 216 + "string": "b", + "span": { + "str": "b", + "start": 780, + "end": 781 } - } + }, + "ty": "u32" } ], - "close": { - "str": ")", - "start": 216, - "end": 217 - } + "results": "empty" } } - }, - { - "str": ";", - "start": 217, - "end": 218 } - ], - [ - { - "method": { - "id": { - "str": "a", - "start": 223, - "end": 224 - }, - "colon": { - "str": ":", - "start": 224, - "end": 225 - }, - "keyword": null, - "func": { - "func": { - "keyword": { - "str": "func", - "start": 226, - "end": 230 - }, - "params": { - "open": { - "str": "(", - "start": 230, - "end": 231 - }, - "list": [ - { - "id": { - "str": "b", - "start": 231, - "end": 232 - }, - "colon": { - "str": ":", - "start": 232, - "end": 233 - }, - "ty": { - "u32": { - "str": "u32", - "start": 234, - "end": 237 - } - } - } - ], - "close": { - "str": ")", - "start": 237, - "end": 238 - } - }, - "results": null + }, + { + "method": { + "docs": [ + { + "comment": "Static method", + "span": { + "str": "/// Static method", + "start": 797, + "end": 814 } } - } - }, - { - "str": ";", - "start": 238, - "end": 239 - } - ], - [ - { - "method": { - "id": { + ], + "id": { + "string": "b", + "span": { "str": "b", - "start": 244, - "end": 245 - }, - "colon": { - "str": ":", - "start": 245, - "end": 246 - }, - "keyword": { - "str": "static", - "start": 247, - "end": 253 - }, + "start": 823, + "end": 824 + } + }, + "isStatic": true, + "ty": { "func": { - "func": { - "keyword": { - "str": "func", - "start": 254, - "end": 258 - }, - "params": { - "open": { - "str": "(", - "start": 258, - "end": 259 - }, - "list": [ - { - "id": { - "str": "c", - "start": 259, - "end": 260 - }, - "colon": { - "str": ":", - "start": 260, - "end": 261 - }, - "ty": { - "u32": { - "str": "u32", - "start": 262, - "end": 265 - } - } + "params": [ + { + "id": { + "string": "c", + "span": { + "str": "c", + "start": 838, + "end": 839 } - ], - "close": { - "str": ")", - "start": 265, - "end": 266 - } - }, - "results": null - } + }, + "ty": "u32" + } + ], + "results": "empty" } } - }, - { - "str": ";", - "start": 266, - "end": 267 } - ] - ], - "close": { - "str": "}", - "start": 268, - "end": 269 - } + } + ] } } } - } + ] } } } diff --git a/crates/wac-parser/tests/parser/type-alias.wac.result b/crates/wac-parser/tests/parser/type-alias.wac.result index a51427a..764b1f6 100644 --- a/crates/wac-parser/tests/parser/type-alias.wac.result +++ b/crates/wac-parser/tests/parser/type-alias.wac.result @@ -1,671 +1,410 @@ { "statements": [ { - "docs": [ - { - "str": "/// u8\n", - "start": 0, - "end": 7 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 7, - "end": 11 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "u8", + "span": { + "str": "/// u8", + "start": 0, + "end": 6 + } + } + ], + "id": { + "string": "a", + "span": { "str": "a", "start": 12, "end": 13 - }, - "equals": { - "str": "=", - "start": 14, - "end": 15 - }, - "kind": { - "type": { - "u8": { - "str": "u8", - "start": 16, - "end": 18 - } - } - }, - "semicolon": { - "str": ";", - "start": 18, - "end": 19 } + }, + "kind": { + "type": "u8" } } } } }, { - "docs": [ - { - "str": "/// s8\n", - "start": 20, - "end": 27 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 27, - "end": 31 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "s8", + "span": { + "str": "/// s8", + "start": 20, + "end": 26 + } + } + ], + "id": { + "string": "b", + "span": { "str": "b", "start": 32, "end": 33 - }, - "equals": { - "str": "=", - "start": 34, - "end": 35 - }, - "kind": { - "type": { - "s8": { - "str": "s8", - "start": 36, - "end": 38 - } - } - }, - "semicolon": { - "str": ";", - "start": 38, - "end": 39 } + }, + "kind": { + "type": "s8" } } } } }, { - "docs": [ - { - "str": "/// u16\n", - "start": 40, - "end": 48 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 48, - "end": 52 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "u16", + "span": { + "str": "/// u16", + "start": 40, + "end": 47 + } + } + ], + "id": { + "string": "c", + "span": { "str": "c", "start": 53, "end": 54 - }, - "equals": { - "str": "=", - "start": 55, - "end": 56 - }, - "kind": { - "type": { - "u16": { - "str": "u16", - "start": 57, - "end": 60 - } - } - }, - "semicolon": { - "str": ";", - "start": 60, - "end": 61 } + }, + "kind": { + "type": "u16" } } } } }, { - "docs": [ - { - "str": "/// s16\n", - "start": 62, - "end": 70 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 70, - "end": 74 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "s16", + "span": { + "str": "/// s16", + "start": 62, + "end": 69 + } + } + ], + "id": { + "string": "d", + "span": { "str": "d", "start": 75, "end": 76 - }, - "equals": { - "str": "=", - "start": 77, - "end": 78 - }, - "kind": { - "type": { - "s16": { - "str": "s16", - "start": 79, - "end": 82 - } - } - }, - "semicolon": { - "str": ";", - "start": 82, - "end": 83 } + }, + "kind": { + "type": "s16" } } } } }, { - "docs": [ - { - "str": "/// u32\n", - "start": 84, - "end": 92 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 92, - "end": 96 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "u32", + "span": { + "str": "/// u32", + "start": 84, + "end": 91 + } + } + ], + "id": { + "string": "e", + "span": { "str": "e", "start": 97, "end": 98 - }, - "equals": { - "str": "=", - "start": 99, - "end": 100 - }, - "kind": { - "type": { - "u32": { - "str": "u32", - "start": 101, - "end": 104 - } - } - }, - "semicolon": { - "str": ";", - "start": 104, - "end": 105 } + }, + "kind": { + "type": "u32" } } } } }, { - "docs": [ - { - "str": "/// s32\n", - "start": 106, - "end": 114 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 114, - "end": 118 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "s32", + "span": { + "str": "/// s32", + "start": 106, + "end": 113 + } + } + ], + "id": { + "string": "f", + "span": { "str": "f", "start": 119, "end": 120 - }, - "equals": { - "str": "=", - "start": 121, - "end": 122 - }, - "kind": { - "type": { - "s32": { - "str": "s32", - "start": 123, - "end": 126 - } - } - }, - "semicolon": { - "str": ";", - "start": 126, - "end": 127 } + }, + "kind": { + "type": "s32" } } } } }, { - "docs": [ - { - "str": "/// u64\n", - "start": 128, - "end": 136 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 136, - "end": 140 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "u64", + "span": { + "str": "/// u64", + "start": 128, + "end": 135 + } + } + ], + "id": { + "string": "g", + "span": { "str": "g", "start": 141, "end": 142 - }, - "equals": { - "str": "=", - "start": 143, - "end": 144 - }, - "kind": { - "type": { - "u64": { - "str": "u64", - "start": 145, - "end": 148 - } - } - }, - "semicolon": { - "str": ";", - "start": 148, - "end": 149 } + }, + "kind": { + "type": "u64" } } } } }, { - "docs": [ - { - "str": "/// s64\n", - "start": 150, - "end": 158 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 158, - "end": 162 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "s64", + "span": { + "str": "/// s64", + "start": 150, + "end": 157 + } + } + ], + "id": { + "string": "h", + "span": { "str": "h", "start": 163, "end": 164 - }, - "equals": { - "str": "=", - "start": 165, - "end": 166 - }, - "kind": { - "type": { - "s64": { - "str": "s64", - "start": 167, - "end": 170 - } - } - }, - "semicolon": { - "str": ";", - "start": 170, - "end": 171 } + }, + "kind": { + "type": "s64" } } } } }, { - "docs": [ - { - "str": "/// float32\n", - "start": 172, - "end": 184 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 184, - "end": 188 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "float32", + "span": { + "str": "/// float32", + "start": 172, + "end": 183 + } + } + ], + "id": { + "string": "i", + "span": { "str": "i", "start": 189, "end": 190 - }, - "equals": { - "str": "=", - "start": 191, - "end": 192 - }, - "kind": { - "type": { - "float32": { - "str": "float32", - "start": 193, - "end": 200 - } - } - }, - "semicolon": { - "str": ";", - "start": 200, - "end": 201 } + }, + "kind": { + "type": "float32" } } } } }, { - "docs": [ - { - "str": "/// float64\n", - "start": 202, - "end": 214 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 214, - "end": 218 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "float64", + "span": { + "str": "/// float64", + "start": 202, + "end": 213 + } + } + ], + "id": { + "string": "j", + "span": { "str": "j", "start": 219, "end": 220 - }, - "equals": { - "str": "=", - "start": 221, - "end": 222 - }, - "kind": { - "type": { - "float64": { - "str": "float64", - "start": 223, - "end": 230 - } - } - }, - "semicolon": { - "str": ";", - "start": 230, - "end": 231 } + }, + "kind": { + "type": "float64" } } } } }, { - "docs": [ - { - "str": "/// bool\n", - "start": 232, - "end": 241 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 241, - "end": 245 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "bool", + "span": { + "str": "/// bool", + "start": 232, + "end": 240 + } + } + ], + "id": { + "string": "k", + "span": { "str": "k", "start": 246, "end": 247 - }, - "equals": { - "str": "=", - "start": 248, - "end": 249 - }, - "kind": { - "type": { - "bool": { - "str": "bool", - "start": 250, - "end": 254 - } - } - }, - "semicolon": { - "str": ";", - "start": 254, - "end": 255 } + }, + "kind": { + "type": "bool" } } } } }, { - "docs": [ - { - "str": "/// char\n", - "start": 256, - "end": 265 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 265, - "end": 269 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "char", + "span": { + "str": "/// char", + "start": 256, + "end": 264 + } + } + ], + "id": { + "string": "l", + "span": { "str": "l", "start": 270, "end": 271 - }, - "equals": { - "str": "=", - "start": 272, - "end": 273 - }, - "kind": { - "type": { - "char": { - "str": "char", - "start": 274, - "end": 278 - } - } - }, - "semicolon": { - "str": ";", - "start": 278, - "end": 279 } + }, + "kind": { + "type": "char" } } } } }, { - "docs": [ - { - "str": "/// string\n", - "start": 280, - "end": 291 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 291, - "end": 295 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "string", + "span": { + "str": "/// string", + "start": 280, + "end": 290 + } + } + ], + "id": { + "string": "m", + "span": { "str": "m", "start": 296, "end": 297 - }, - "equals": { - "str": "=", - "start": 298, - "end": 299 - }, - "kind": { - "type": { - "string": { - "str": "string", - "start": 300, - "end": 306 - } - } - }, - "semicolon": { - "str": ";", - "start": 306, - "end": 307 } + }, + "kind": { + "type": "string" } } } } }, { - "docs": [ - { - "str": "/// tuple\n", - "start": 308, - "end": 326 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 326, - "end": 330 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "tuple", + "span": { + "str": "/// tuple", + "start": 308, + "end": 325 + } + } + ], + "id": { + "string": "n", + "span": { "str": "n", "start": 331, "end": 332 - }, - "equals": { - "str": "=", - "start": 333, - "end": 334 - }, - "kind": { - "type": { - "tuple": { - "keyword": { - "str": "tuple", - "start": 335, - "end": 340 - }, - "open": { - "str": "<", - "start": 340, - "end": 341 - }, - "types": [ - { - "u8": { - "str": "u8", - "start": 341, - "end": 343 - } - }, - { - "s8": { - "str": "s8", - "start": 345, - "end": 347 - } - } - ], - "close": { - "str": ">", - "start": 347, - "end": 348 - } - } - } - }, - "semicolon": { - "str": ";", - "start": 348, - "end": 349 + } + }, + "kind": { + "type": { + "tuple": [ + "u8", + "s8" + ] } } } @@ -673,85 +412,34 @@ } }, { - "docs": [ - { - "str": "/// list>\n", - "start": 350, - "end": 370 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 370, - "end": 374 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "list>", + "span": { + "str": "/// list>", + "start": 350, + "end": 369 + } + } + ], + "id": { + "string": "o", + "span": { "str": "o", "start": 375, "end": 376 - }, - "equals": { - "str": "=", - "start": 377, - "end": 378 - }, - "kind": { - "type": { - "list": { - "keyword": { - "str": "list", - "start": 379, - "end": 383 - }, - "open": { - "str": "<", - "start": 383, - "end": 384 - }, - "ty": { - "tuple": { - "keyword": { - "str": "tuple", - "start": 384, - "end": 389 - }, - "open": { - "str": "<", - "start": 389, - "end": 390 - }, - "types": [ - { - "u8": { - "str": "u8", - "start": 390, - "end": 392 - } - } - ], - "close": { - "str": ">", - "start": 392, - "end": 393 - } - } - }, - "close": { - "str": ">", - "start": 393, - "end": 394 - } - } + } + }, + "kind": { + "type": { + "list": { + "tuple": [ + "u8" + ] } - }, - "semicolon": { - "str": ";", - "start": 394, - "end": 395 } } } @@ -759,64 +447,30 @@ } }, { - "docs": [ - { - "str": "/// option\n", - "start": 396, - "end": 415 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 415, - "end": 419 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "option", + "span": { + "str": "/// option", + "start": 396, + "end": 414 + } + } + ], + "id": { + "string": "p", + "span": { "str": "p", "start": 420, "end": 421 - }, - "equals": { - "str": "=", - "start": 422, - "end": 423 - }, - "kind": { - "type": { - "option": { - "keyword": { - "str": "option", - "start": 424, - "end": 430 - }, - "open": { - "str": "<", - "start": 430, - "end": 431 - }, - "ty": { - "string": { - "str": "string", - "start": 431, - "end": 437 - } - }, - "close": { - "str": ">", - "start": 437, - "end": 438 - } - } - } - }, - "semicolon": { - "str": ";", - "start": 438, - "end": 439 + } + }, + "kind": { + "type": { + "option": "string" } } } @@ -824,48 +478,33 @@ } }, { - "docs": [ - { - "str": "/// result\n", - "start": 440, - "end": 451 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 451, - "end": 455 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "result", + "span": { + "str": "/// result", + "start": 440, + "end": 450 + } + } + ], + "id": { + "string": "q", + "span": { "str": "q", "start": 456, "end": 457 - }, - "equals": { - "str": "=", - "start": 458, - "end": 459 - }, - "kind": { - "type": { - "result": { - "keyword": { - "str": "result", - "start": 460, - "end": 466 - }, - "specified": null - } + } + }, + "kind": { + "type": { + "result": { + "ok": null, + "err": null } - }, - "semicolon": { - "str": ";", - "start": 466, - "end": 467 } } } @@ -873,69 +512,33 @@ } }, { - "docs": [ - { - "str": "/// result\n", - "start": 468, - "end": 483 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 483, - "end": 487 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "result", + "span": { + "str": "/// result", + "start": 468, + "end": 482 + } + } + ], + "id": { + "string": "r", + "span": { "str": "r", "start": 488, "end": 489 - }, - "equals": { - "str": "=", - "start": 490, - "end": 491 - }, - "kind": { - "type": { - "result": { - "keyword": { - "str": "result", - "start": 492, - "end": 498 - }, - "specified": { - "open": { - "str": "<", - "start": 498, - "end": 499 - }, - "ok": { - "type": { - "u8": { - "str": "u8", - "start": 499, - "end": 501 - } - } - }, - "err": null, - "close": { - "str": ">", - "start": 501, - "end": 502 - } - } - } + } + }, + "kind": { + "type": { + "result": { + "ok": "u8", + "err": null } - }, - "semicolon": { - "str": ";", - "start": 502, - "end": 503 } } } @@ -943,73 +546,33 @@ } }, { - "docs": [ - { - "str": "/// result<_, s8>\n", - "start": 504, - "end": 522 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 522, - "end": 526 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "result<_, s8>", + "span": { + "str": "/// result<_, s8>", + "start": 504, + "end": 521 + } + } + ], + "id": { + "string": "s", + "span": { "str": "s", "start": 527, "end": 528 - }, - "equals": { - "str": "=", - "start": 529, - "end": 530 - }, - "kind": { - "type": { - "result": { - "keyword": { - "str": "result", - "start": 531, - "end": 537 - }, - "specified": { - "open": { - "str": "<", - "start": 537, - "end": 538 - }, - "ok": { - "omitted": { - "str": "_", - "start": 538, - "end": 539 - } - }, - "err": { - "s8": { - "str": "s8", - "start": 541, - "end": 543 - } - }, - "close": { - "str": ">", - "start": 543, - "end": 544 - } - } - } + } + }, + "kind": { + "type": { + "result": { + "ok": null, + "err": "s8" } - }, - "semicolon": { - "str": ";", - "start": 544, - "end": 545 } } } @@ -1017,75 +580,33 @@ } }, { - "docs": [ - { - "str": "/// result\n", - "start": 546, - "end": 569 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 569, - "end": 573 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "result", + "span": { + "str": "/// result", + "start": 546, + "end": 568 + } + } + ], + "id": { + "string": "t", + "span": { "str": "t", "start": 574, "end": 575 - }, - "equals": { - "str": "=", - "start": 576, - "end": 577 - }, - "kind": { - "type": { - "result": { - "keyword": { - "str": "result", - "start": 578, - "end": 584 - }, - "specified": { - "open": { - "str": "<", - "start": 584, - "end": 585 - }, - "ok": { - "type": { - "u8": { - "str": "u8", - "start": 585, - "end": 587 - } - } - }, - "err": { - "string": { - "str": "string", - "start": 589, - "end": 595 - } - }, - "close": { - "str": ">", - "start": 595, - "end": 596 - } - } - } + } + }, + "kind": { + "type": { + "result": { + "ok": "u8", + "err": "string" } - }, - "semicolon": { - "str": ";", - "start": 596, - "end": 597 } } } @@ -1093,62 +614,37 @@ } }, { - "docs": [ - { - "str": "/// borrow\n", - "start": 598, - "end": 612 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 612, - "end": 616 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "borrow", + "span": { + "str": "/// borrow", + "start": 598, + "end": 611 + } + } + ], + "id": { + "string": "u", + "span": { "str": "u", "start": 617, "end": 618 - }, - "equals": { - "str": "=", - "start": 619, - "end": 620 - }, - "kind": { - "type": { - "borrow": { - "keyword": { - "str": "borrow", - "start": 621, - "end": 627 - }, - "open": { - "str": "<", - "start": 627, - "end": 628 - }, - "id": { - "str": "x", - "start": 628, - "end": 629 - }, - "close": { - "str": ">", - "start": 629, - "end": 630 - } + } + }, + "kind": { + "type": { + "borrow": { + "string": "x", + "span": { + "str": "x", + "start": 628, + "end": 629 } } - }, - "semicolon": { - "str": ";", - "start": 630, - "end": 631 } } } @@ -1156,45 +652,37 @@ } }, { - "docs": [ - { - "str": "/// x\n", - "start": 632, - "end": 638 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 638, - "end": 642 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "x", + "span": { + "str": "/// x", + "start": 632, + "end": 637 + } + } + ], + "id": { + "string": "v", + "span": { "str": "v", "start": 643, "end": 644 - }, - "equals": { - "str": "=", - "start": 645, - "end": 646 - }, - "kind": { - "type": { - "ident": { + } + }, + "kind": { + "type": { + "ident": { + "string": "x", + "span": { "str": "x", "start": 647, "end": 648 } } - }, - "semicolon": { - "str": ";", - "start": 648, - "end": 649 } } } @@ -1202,45 +690,37 @@ } }, { - "docs": [ - { - "str": "/// keyword\n", - "start": 650, - "end": 662 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 662, - "end": 666 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "keyword", + "span": { + "str": "/// keyword", + "start": 650, + "end": 661 + } + } + ], + "id": { + "string": "type", + "span": { "str": "%type", "start": 667, "end": 672 - }, - "equals": { - "str": "=", - "start": 673, - "end": 674 - }, - "kind": { - "type": { - "ident": { + } + }, + "kind": { + "type": { + "ident": { + "string": "v", + "span": { "str": "v", "start": 675, "end": 676 } } - }, - "semicolon": { - "str": ";", - "start": 676, - "end": 677 } } } diff --git a/crates/wac-parser/tests/parser/types.wac b/crates/wac-parser/tests/parser/types.wac index 47145a6..2ab559b 100644 --- a/crates/wac-parser/tests/parser/types.wac +++ b/crates/wac-parser/tests/parser/types.wac @@ -1,5 +1,10 @@ /// Defining an interface interface i { + /// Defining a resource + resource res { + constructor(); + } + /// Type alias a type a = func(); /// Record type @@ -52,11 +57,6 @@ world w2 { include foo:bar/baz; } -/// Defining a resource -resource res { - constructor(); -} - /// Defining a variant variant v { a(x), diff --git a/crates/wac-parser/tests/parser/types.wac.result b/crates/wac-parser/tests/parser/types.wac.result index b6eb89c..06e1188 100644 --- a/crates/wac-parser/tests/parser/types.wac.result +++ b/crates/wac-parser/tests/parser/types.wac.result @@ -1,1425 +1,961 @@ { "statements": [ { - "docs": [ - { - "str": "/// Defining an interface\n", - "start": 0, - "end": 26 - } - ], - "stmt": { - "type": { - "interface": { - "keyword": { - "str": "interface", - "start": 26, - "end": 35 - }, - "id": { + "Type": { + "interface": { + "docs": [ + { + "comment": "Defining an interface", + "span": { + "str": "/// Defining an interface", + "start": 0, + "end": 25 + } + } + ], + "id": { + "string": "i", + "span": { "str": "i", "start": 36, "end": 37 - }, - "body": { - "open": { - "str": "{", - "start": 38, - "end": 39 - }, - "items": [ - { + } + }, + "items": [ + { + "type": { + "resource": { "docs": [ { - "str": "/// Type alias a\n", - "start": 44, - "end": 61 + "comment": "Defining a resource", + "span": { + "str": "/// Defining a resource", + "start": 44, + "end": 67 + } } ], - "stmt": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 65, - "end": 69 - }, - "id": { - "str": "a", - "start": 70, - "end": 71 - }, - "equals": { - "str": "=", - "start": 72, - "end": 73 - }, - "kind": { - "func": { - "keyword": { - "str": "func", - "start": 74, - "end": 78 - }, - "params": { - "open": { - "str": "(", - "start": 78, - "end": 79 - }, - "list": [], - "close": { - "str": ")", - "start": 79, - "end": 80 - } - }, - "results": null - } + "id": { + "string": "res", + "span": { + "str": "res", + "start": 81, + "end": 84 + } + }, + "methods": [ + { + "constructor": { + "docs": [], + "span": { + "str": "constructor", + "start": 95, + "end": 106 }, - "semicolon": { - "str": ";", - "start": 80, - "end": 81 - } + "params": [] } } - } - }, - { + ] + } + } + }, + { + "type": { + "alias": { "docs": [ { - "str": "/// Record type\n", - "start": 86, - "end": 102 + "comment": "Type alias a", + "span": { + "str": "/// Type alias a", + "start": 121, + "end": 137 + } } ], - "stmt": { - "type": { - "record": { - "keyword": { - "str": "record", - "start": 106, - "end": 112 - }, - "id": { - "str": "r", - "start": 113, - "end": 114 - }, - "body": { - "open": { - "str": "{", - "start": 115, - "end": 116 - }, - "fields": [ - { - "id": { - "str": "x", - "start": 125, - "end": 126 - }, - "colon": { - "str": ":", - "start": 126, - "end": 127 - }, - "ty": { - "u32": { - "str": "u32", - "start": 128, - "end": 131 - } - } - } - ], - "close": { - "str": "}", - "start": 136, - "end": 137 - } - } - } + "id": { + "string": "a", + "span": { + "str": "a", + "start": 147, + "end": 148 + } + }, + "kind": { + "func": { + "params": [], + "results": "empty" } } - }, - { + } + } + }, + { + "type": { + "record": { "docs": [ { - "str": "/// Export func\n", - "start": 142, - "end": 158 + "comment": "Record type", + "span": { + "str": "/// Record type", + "start": 163, + "end": 178 + } } ], - "stmt": { - "export": { + "id": { + "string": "r", + "span": { + "str": "r", + "start": 190, + "end": 191 + } + }, + "fields": [ + { + "docs": [], "id": { - "str": "x", - "start": 162, - "end": 163 - }, - "colon": { - "str": ":", - "start": 163, - "end": 164 - }, - "ty": { - "func": { - "keyword": { - "str": "func", - "start": 165, - "end": 169 - }, - "params": { - "open": { - "str": "(", - "start": 169, - "end": 170 - }, - "list": [], - "close": { - "str": ")", - "start": 170, - "end": 171 - } - }, - "results": null + "string": "x", + "span": { + "str": "x", + "start": 202, + "end": 203 } }, - "semicolon": { - "str": ";", - "start": 171, - "end": 172 - } + "ty": "u32" + } + ] + } + } + }, + { + "export": { + "docs": [ + { + "comment": "Export func", + "span": { + "str": "/// Export func", + "start": 219, + "end": 234 } } + ], + "id": { + "string": "x", + "span": { + "str": "x", + "start": 239, + "end": 240 + } }, - { - "docs": [ - { - "str": "/// Export func of type a\n", - "start": 177, - "end": 203 + "ty": { + "func": { + "params": [], + "results": "empty" + } + } + } + }, + { + "export": { + "docs": [ + { + "comment": "Export func of type a", + "span": { + "str": "/// Export func of type a", + "start": 254, + "end": 279 } - ], - "stmt": { - "export": { - "id": { - "str": "y", - "start": 207, - "end": 208 - }, - "colon": { - "str": ":", - "start": 208, - "end": 209 - }, - "ty": { - "ident": { - "str": "a", - "start": 210, - "end": 211 - } - }, - "semicolon": { - "str": ";", - "start": 211, - "end": 212 - } + } + ], + "id": { + "string": "y", + "span": { + "str": "y", + "start": 284, + "end": 285 + } + }, + "ty": { + "ident": { + "string": "a", + "span": { + "str": "a", + "start": 287, + "end": 288 } } } - ], - "close": { - "str": "}", - "start": 213, - "end": 214 } } - } + ] } } }, { - "docs": [ - { - "str": "/// Defining a second interface\n", - "start": 216, - "end": 248 - } - ], - "stmt": { - "type": { - "interface": { - "keyword": { - "str": "interface", - "start": 248, - "end": 257 - }, - "id": { + "Type": { + "interface": { + "docs": [ + { + "comment": "Defining a second interface", + "span": { + "str": "/// Defining a second interface", + "start": 293, + "end": 324 + } + } + ], + "id": { + "string": "i2", + "span": { "str": "i2", - "start": 258, - "end": 260 - }, - "body": { - "open": { - "str": "{", - "start": 261, - "end": 262 - }, - "items": [ - { - "docs": [ - { - "str": "/// Use type r from i\n", - "start": 267, - "end": 289 + "start": 335, + "end": 337 + } + }, + "items": [ + { + "use": { + "docs": [ + { + "comment": "Use type r from i", + "span": { + "str": "/// Use type r from i", + "start": 344, + "end": 365 } - ], - "stmt": { - "use": { - "keyword": { - "str": "use", - "start": 293, - "end": 296 - }, - "items": { - "path": { - "ident": { - "str": "i", - "start": 297, - "end": 298 - } - }, - "dot": { - "str": ".", - "start": 298, - "end": 299 - }, - "open": { - "str": "{", - "start": 299, - "end": 300 - }, - "list": [ - { - "id": { - "str": "r", - "start": 300, - "end": 301 - }, - "asClause": null - } - ], - "close": { - "str": "}", - "start": 301, - "end": 302 - } - }, - "semicolon": { - "str": ";", - "start": 302, - "end": 303 - } + } + ], + "path": { + "ident": { + "string": "i", + "span": { + "str": "i", + "start": 374, + "end": 375 } } }, - { - "docs": [ - { - "str": "/// Use type r from i with alias z\n", - "start": 309, - "end": 344 + "items": [ + { + "id": { + "string": "r", + "span": { + "str": "r", + "start": 377, + "end": 378 + } + }, + "asId": null + } + ] + } + }, + { + "use": { + "docs": [ + { + "comment": "Use type r from i with alias z", + "span": { + "str": "/// Use type r from i with alias z", + "start": 386, + "end": 420 } - ], - "stmt": { - "use": { - "keyword": { - "str": "use", - "start": 348, - "end": 351 - }, - "items": { - "path": { - "ident": { - "str": "i", - "start": 352, - "end": 353 - } - }, - "dot": { - "str": ".", - "start": 353, - "end": 354 - }, - "open": { - "str": "{", - "start": 354, - "end": 355 - }, - "list": [ - { - "id": { - "str": "r", - "start": 355, - "end": 356 - }, - "asClause": { - "keyword": { - "str": "as", - "start": 357, - "end": 359 - }, - "id": { - "str": "z", - "start": 360, - "end": 361 - } - } - } - ], - "close": { - "str": "}", - "start": 361, - "end": 362 - } - }, - "semicolon": { - "str": ";", - "start": 362, - "end": 363 + } + ], + "path": { + "ident": { + "string": "i", + "span": { + "str": "i", + "start": 429, + "end": 430 + } + } + }, + "items": [ + { + "id": { + "string": "r", + "span": { + "str": "r", + "start": 432, + "end": 433 + } + }, + "asId": { + "string": "z", + "span": { + "str": "z", + "start": 437, + "end": 438 } } } - } - ], - "close": { - "str": "}", - "start": 364, - "end": 365 + ] } } - } + ] } } }, { - "docs": [ - { - "str": "/// Defining a world\n", - "start": 367, - "end": 388 - } - ], - "stmt": { - "type": { - "world": { - "keyword": { - "str": "world", - "start": 388, - "end": 393 - }, - "id": { + "Type": { + "world": { + "docs": [ + { + "comment": "Defining a world", + "span": { + "str": "/// Defining a world", + "start": 444, + "end": 464 + } + } + ], + "id": { + "string": "w1", + "span": { "str": "w1", - "start": 394, - "end": 396 - }, - "body": { - "open": { - "str": "{", - "start": 397, - "end": 398 - }, - "items": [ - { - "docs": [ - { - "str": "/// Use type r from foo:bar/i\n", - "start": 403, - "end": 433 - } - ], - "stmt": { - "use": { - "keyword": { - "str": "use", - "start": 437, - "end": 440 - }, - "items": { - "path": { - "package": { - "name": { - "parts": [ - { - "str": "foo", - "start": 441, - "end": 444 - }, - { - "str": "bar", - "start": 445, - "end": 448 - } - ] - }, - "segments": [ - [ - { - "str": "/", - "start": 448, - "end": 449 - }, - { - "str": "i", - "start": 449, - "end": 450 - } - ] - ], - "version": null - } - }, - "dot": { - "str": ".", - "start": 450, - "end": 451 - }, - "open": { - "str": "{", - "start": 451, - "end": 452 - }, - "list": [ - { - "id": { - "str": "r", - "start": 452, - "end": 453 - }, - "asClause": null - } - ], - "close": { - "str": "}", - "start": 453, - "end": 454 - } - }, - "semicolon": { - "str": ";", - "start": 454, - "end": 455 - } + "start": 471, + "end": 473 + } + }, + "items": [ + { + "use": { + "docs": [ + { + "comment": "Use type r from foo:bar/i", + "span": { + "str": "/// Use type r from foo:bar/i", + "start": 480, + "end": 509 } } - }, - { - "docs": [ - { - "str": "/// Import a function\n", - "start": 461, - "end": 483 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 487, - "end": 493 - }, - "decl": { - "named": { - "id": { - "str": "a", - "start": 494, - "end": 495 - }, - "colon": { - "str": ":", - "start": 495, - "end": 496 - }, - "ty": { - "func": { - "keyword": { - "str": "func", - "start": 497, - "end": 501 - }, - "params": { - "open": { - "str": "(", - "start": 501, - "end": 502 - }, - "list": [], - "close": { - "str": ")", - "start": 502, - "end": 503 - } - }, - "results": null - } - } - } - }, - "semicolon": { - "str": ";", - "start": 503, - "end": 504 - } - } + ], + "path": { + "package": { + "span": { + "str": "foo:bar/i", + "start": 518, + "end": 527 + }, + "name": "foo:bar", + "segments": "i", + "version": null } }, - { - "docs": [ - { - "str": "/// Import an interface\n", - "start": 509, - "end": 533 - } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 537, - "end": 543 - }, - "decl": { - "interface": { - "ident": { - "str": "i", - "start": 544, - "end": 545 - } - } - }, - "semicolon": { - "str": ";", - "start": 545, - "end": 546 + "items": [ + { + "id": { + "string": "r", + "span": { + "str": "r", + "start": 529, + "end": 530 } - } + }, + "asId": null } - }, - { - "docs": [ - { - "str": "/// Import by name with type `c`\n", - "start": 551, - "end": 584 + ] + } + }, + { + "import": { + "docs": [ + { + "comment": "Import a function", + "span": { + "str": "/// Import a function", + "start": 538, + "end": 559 } - ], - "stmt": { - "import": { - "keyword": { - "str": "import", - "start": 588, - "end": 594 - }, - "decl": { - "named": { - "id": { - "str": "c", - "start": 595, - "end": 596 - }, - "colon": { - "str": ":", - "start": 596, - "end": 597 - }, - "ty": { - "ident": { - "str": "c", - "start": 598, - "end": 599 - } - } - } - }, - "semicolon": { - "str": ";", - "start": 599, - "end": 600 + } + ], + "path": { + "named": { + "id": { + "string": "a", + "span": { + "str": "a", + "start": 571, + "end": 572 + } + }, + "ty": { + "func": { + "params": [], + "results": "empty" } } } - }, - { - "docs": [ - { - "str": "/// Export an inline interface\n", - "start": 606, - "end": 637 + } + } + }, + { + "import": { + "docs": [ + { + "comment": "Import an interface", + "span": { + "str": "/// Import an interface", + "start": 586, + "end": 609 } - ], - "stmt": { - "export": { - "keyword": { - "str": "export", - "start": 641, - "end": 647 - }, - "decl": { - "named": { - "id": { - "str": "d", - "start": 648, - "end": 649 - }, - "colon": { - "str": ":", - "start": 649, - "end": 650 - }, - "ty": { - "interface": { - "keyword": { - "str": "interface", - "start": 651, - "end": 660 - }, - "body": { - "open": { - "str": "{", - "start": 661, - "end": 662 - }, - "items": [ - { - "docs": [], - "stmt": { - "export": { - "id": { - "str": "x", - "start": 671, - "end": 672 - }, - "colon": { - "str": ":", - "start": 672, - "end": 673 - }, - "ty": { - "func": { - "keyword": { - "str": "func", - "start": 674, - "end": 678 - }, - "params": { - "open": { - "str": "(", - "start": 678, - "end": 679 - }, - "list": [], - "close": { - "str": ")", - "start": 679, - "end": 680 - } - }, - "results": null - } - }, - "semicolon": { - "str": ";", - "start": 680, - "end": 681 - } - } - } - } - ], - "close": { - "str": "}", - "start": 686, - "end": 687 - } - } - } - } - } - }, - "semicolon": { - "str": ";", - "start": 687, - "end": 688 - } + } + ], + "path": { + "ident": { + "string": "i", + "span": { + "str": "i", + "start": 621, + "end": 622 } } - }, - { - "docs": [ - { - "str": "/// Export an interface\n", - "start": 693, - "end": 717 + } + } + }, + { + "import": { + "docs": [ + { + "comment": "Import by name with type `c`", + "span": { + "str": "/// Import by name with type `c`", + "start": 628, + "end": 660 } - ], - "stmt": { - "export": { - "keyword": { - "str": "export", - "start": 721, - "end": 727 - }, - "decl": { - "interface": { - "ident": { - "str": "i2", - "start": 728, - "end": 730 - } + } + ], + "path": { + "named": { + "id": { + "string": "c", + "span": { + "str": "c", + "start": 672, + "end": 673 + } + }, + "ty": { + "ident": { + "string": "c", + "span": { + "str": "c", + "start": 675, + "end": 676 } - }, - "semicolon": { - "str": ";", - "start": 730, - "end": 731 } } } - }, - { - "docs": [ - { - "str": "/// Export by name with type `f`\n", - "start": 736, - "end": 769 + } + } + }, + { + "export": { + "docs": [ + { + "comment": "Export an inline interface", + "span": { + "str": "/// Export an inline interface", + "start": 683, + "end": 713 } - ], - "stmt": { - "export": { - "keyword": { - "str": "export", - "start": 773, - "end": 779 - }, - "decl": { - "named": { - "id": { - "str": "f", - "start": 780, - "end": 781 - }, - "colon": { - "str": ":", - "start": 781, - "end": 782 - }, - "ty": { - "ident": { - "str": "f", - "start": 783, - "end": 784 + } + ], + "path": { + "named": { + "id": { + "string": "d", + "span": { + "str": "d", + "start": 725, + "end": 726 + } + }, + "ty": { + "interface": { + "items": [ + { + "export": { + "docs": [], + "id": { + "string": "x", + "span": { + "str": "x", + "start": 748, + "end": 749 + } + }, + "ty": { + "func": { + "params": [], + "results": "empty" + } + } } } - } - }, - "semicolon": { - "str": ";", - "start": 784, - "end": 785 + ] } } } } - ], - "close": { - "str": "}", - "start": 786, - "end": 787 } - } - } - } - } - }, - { - "docs": [ - { - "str": "/// Defining a second world\n", - "start": 789, - "end": 817 - } - ], - "stmt": { - "type": { - "world": { - "keyword": { - "str": "world", - "start": 817, - "end": 822 - }, - "id": { - "str": "w2", - "start": 823, - "end": 825 }, - "body": { - "open": { - "str": "{", - "start": 826, - "end": 827 - }, - "items": [ - { - "docs": [ - { - "str": "/// Include the first world\n", - "start": 832, - "end": 860 - } - ], - "stmt": { - "include": { - "keyword": { - "str": "include", - "start": 864, - "end": 871 - }, - "world": { - "ident": { - "str": "w1", - "start": 872, - "end": 874 - } - }, - "with": null, - "semicolon": { - "str": ";", - "start": 874, - "end": 875 - } + { + "export": { + "docs": [ + { + "comment": "Export an interface", + "span": { + "str": "/// Export an interface", + "start": 770, + "end": 793 } } - }, - { - "docs": [ - { - "str": "/// Include a world by path\n", - "start": 881, - "end": 909 - } - ], - "stmt": { - "include": { - "keyword": { - "str": "include", - "start": 913, - "end": 920 - }, - "world": { - "path": { - "name": { - "parts": [ - { - "str": "foo", - "start": 921, - "end": 924 - }, - { - "str": "bar", - "start": 925, - "end": 928 - } - ] - }, - "segments": [ - [ - { - "str": "/", - "start": 928, - "end": 929 - }, - { - "str": "baz", - "start": 929, - "end": 932 - } - ] - ], - "version": null - } - }, - "with": null, - "semicolon": { - "str": ";", - "start": 932, - "end": 933 - } + ], + "path": { + "ident": { + "string": "i2", + "span": { + "str": "i2", + "start": 805, + "end": 807 } } } - ], - "close": { - "str": "}", - "start": 934, - "end": 935 } - } - } - } - } - }, - { - "docs": [ - { - "str": "/// Defining a resource\n", - "start": 937, - "end": 961 - } - ], - "stmt": { - "type": { - "type": { - "resource": { - "keyword": { - "str": "resource", - "start": 961, - "end": 969 - }, - "id": { - "str": "res", - "start": 970, - "end": 973 - }, - "body": { - "methods": { - "open": { - "str": "{", - "start": 974, - "end": 975 - }, - "methods": [ - [ - { - "constructor": { - "keyword": { - "str": "constructor", - "start": 980, - "end": 991 - }, - "params": { - "open": { - "str": "(", - "start": 991, - "end": 992 - }, - "list": [], - "close": { - "str": ")", - "start": 992, - "end": 993 - } - } + }, + { + "export": { + "docs": [ + { + "comment": "Export by name with type `f`", + "span": { + "str": "/// Export by name with type `f`", + "start": 813, + "end": 845 + } + } + ], + "path": { + "named": { + "id": { + "string": "f", + "span": { + "str": "f", + "start": 857, + "end": 858 + } + }, + "ty": { + "ident": { + "string": "f", + "span": { + "str": "f", + "start": 860, + "end": 861 } - }, - { - "str": ";", - "start": 993, - "end": 994 } - ] - ], - "close": { - "str": "}", - "start": 995, - "end": 996 + } } } } } - } + ] } } }, { - "docs": [ - { - "str": "/// Defining a variant\n", - "start": 998, - "end": 1021 - } - ], - "stmt": { - "type": { - "type": { - "variant": { - "keyword": { - "str": "variant", - "start": 1021, - "end": 1028 - }, - "id": { - "str": "v", - "start": 1029, - "end": 1030 - }, - "body": { - "open": { - "str": "{", - "start": 1031, - "end": 1032 - }, - "cases": [ + "Type": { + "world": { + "docs": [ + { + "comment": "Defining a second world", + "span": { + "str": "/// Defining a second world", + "start": 866, + "end": 893 + } + } + ], + "id": { + "string": "w2", + "span": { + "str": "w2", + "start": 900, + "end": 902 + } + }, + "items": [ + { + "include": { + "docs": [ { - "id": { - "str": "a", - "start": 1037, - "end": 1038 - }, - "ty": { - "open": { - "str": "(", - "start": 1038, - "end": 1039 - }, - "ty": { - "ident": { - "str": "x", - "start": 1039, - "end": 1040 - } - }, - "close": { - "str": ")", - "start": 1040, - "end": 1041 - } + "comment": "Include the first world", + "span": { + "str": "/// Include the first world", + "start": 909, + "end": 936 } - }, - { - "id": { - "str": "b", - "start": 1047, - "end": 1048 - }, - "ty": { - "open": { - "str": "(", - "start": 1048, - "end": 1049 - }, - "ty": { - "string": { - "str": "string", - "start": 1049, - "end": 1055 - } - }, - "close": { - "str": ")", - "start": 1055, - "end": 1056 - } + } + ], + "world": { + "ident": { + "string": "w1", + "span": { + "str": "w1", + "start": 949, + "end": 951 } - }, + } + }, + "with": [] + } + }, + { + "include": { + "docs": [ { - "id": { - "str": "c", - "start": 1062, - "end": 1063 - }, - "ty": { - "open": { - "str": "(", - "start": 1063, - "end": 1064 - }, - "ty": { - "u32": { - "str": "u32", - "start": 1064, - "end": 1067 - } - }, - "close": { - "str": ")", - "start": 1067, - "end": 1068 - } + "comment": "Include a world by path", + "span": { + "str": "/// Include a world by path", + "start": 958, + "end": 985 } - }, - { - "id": { - "str": "d", - "start": 1074, - "end": 1075 - }, - "ty": null } ], - "close": { - "str": "}", - "start": 1077, - "end": 1078 - } + "world": { + "package": { + "span": { + "str": "foo:bar/baz", + "start": 998, + "end": 1009 + }, + "name": "foo:bar", + "segments": "baz", + "version": null + } + }, + "with": [] } } - } + ] } } }, { - "docs": [ - { - "str": "/// Defining a record\n", - "start": 1080, - "end": 1102 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "record": { - "keyword": { - "str": "record", - "start": 1102, - "end": 1108 - }, - "id": { - "str": "r", - "start": 1109, - "end": 1110 - }, - "body": { - "open": { - "str": "{", - "start": 1111, - "end": 1112 + "variant": { + "docs": [ + { + "comment": "Defining a variant", + "span": { + "str": "/// Defining a variant", + "start": 1014, + "end": 1036 + } + } + ], + "id": { + "string": "v", + "span": { + "str": "v", + "start": 1045, + "end": 1046 + } + }, + "cases": [ + { + "docs": [], + "id": { + "string": "a", + "span": { + "str": "a", + "start": 1053, + "end": 1054 + } }, - "fields": [ - { - "id": { + "ty": { + "ident": { + "string": "x", + "span": { "str": "x", - "start": 1117, - "end": 1118 - }, - "colon": { - "str": ":", - "start": 1118, - "end": 1119 - }, - "ty": { - "u32": { - "str": "u32", - "start": 1120, - "end": 1123 - } - } - }, - { - "id": { - "str": "y", - "start": 1129, - "end": 1130 - }, - "colon": { - "str": ":", - "start": 1130, - "end": 1131 - }, - "ty": { - "string": { - "str": "string", - "start": 1132, - "end": 1138 - } - } - }, - { - "id": { - "str": "z", - "start": 1144, - "end": 1145 - }, - "colon": { - "str": ":", - "start": 1145, - "end": 1146 - }, - "ty": { - "ident": { - "str": "v", - "start": 1147, - "end": 1148 - } + "start": 1055, + "end": 1056 } } - ], - "close": { - "str": "}", - "start": 1150, - "end": 1151 } + }, + { + "docs": [], + "id": { + "string": "b", + "span": { + "str": "b", + "start": 1063, + "end": 1064 + } + }, + "ty": "string" + }, + { + "docs": [], + "id": { + "string": "c", + "span": { + "str": "c", + "start": 1078, + "end": 1079 + } + }, + "ty": "u32" + }, + { + "docs": [], + "id": { + "string": "d", + "span": { + "str": "d", + "start": 1090, + "end": 1091 + } + }, + "ty": null } - } + ] } } } }, { - "docs": [ - { - "str": "/// Defining flags\n", - "start": 1154, - "end": 1173 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "flags": { - "keyword": { - "str": "flags", - "start": 1173, - "end": 1178 + "record": { + "docs": [ + { + "comment": "Defining a record", + "span": { + "str": "/// Defining a record", + "start": 1096, + "end": 1117 + } + } + ], + "id": { + "string": "r", + "span": { + "str": "r", + "start": 1125, + "end": 1126 + } + }, + "fields": [ + { + "docs": [], + "id": { + "string": "x", + "span": { + "str": "x", + "start": 1133, + "end": 1134 + } + }, + "ty": "u32" }, - "id": { - "str": "f", - "start": 1179, - "end": 1180 + { + "docs": [], + "id": { + "string": "y", + "span": { + "str": "y", + "start": 1145, + "end": 1146 + } + }, + "ty": "string" }, - "body": { - "open": { - "str": "{", - "start": 1181, - "end": 1182 + { + "docs": [], + "id": { + "string": "z", + "span": { + "str": "z", + "start": 1160, + "end": 1161 + } }, - "flags": [ - { + "ty": { + "ident": { + "string": "v", + "span": { + "str": "v", + "start": 1163, + "end": 1164 + } + } + } + } + ] + } + } + } + }, + { + "Type": { + "type": { + "flags": { + "docs": [ + { + "comment": "Defining flags", + "span": { + "str": "/// Defining flags", + "start": 1170, + "end": 1188 + } + } + ], + "id": { + "string": "f", + "span": { + "str": "f", + "start": 1195, + "end": 1196 + } + }, + "flags": [ + { + "docs": [], + "id": { + "string": "a", + "span": { "str": "a", - "start": 1187, - "end": 1188 - }, - { + "start": 1203, + "end": 1204 + } + } + }, + { + "docs": [], + "id": { + "string": "b", + "span": { "str": "b", - "start": 1194, - "end": 1195 - }, - { + "start": 1210, + "end": 1211 + } + } + }, + { + "docs": [], + "id": { + "string": "c", + "span": { "str": "c", - "start": 1201, - "end": 1202 + "start": 1217, + "end": 1218 } - ], - "close": { - "str": "}", - "start": 1204, - "end": 1205 } } - } + ] } } } }, { - "docs": [ - { - "str": "/// Defining an enum\n", - "start": 1207, - "end": 1228 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "enum": { - "keyword": { - "str": "enum", - "start": 1228, - "end": 1232 - }, - "id": { + "enum": { + "docs": [ + { + "comment": "Defining an enum", + "span": { + "str": "/// Defining an enum", + "start": 1223, + "end": 1243 + } + } + ], + "id": { + "string": "e", + "span": { "str": "e", - "start": 1233, - "end": 1234 - }, - "body": { - "open": { - "str": "{", - "start": 1235, - "end": 1236 - }, - "cases": [ - { + "start": 1249, + "end": 1250 + } + }, + "cases": [ + { + "docs": [], + "id": { + "string": "a", + "span": { "str": "a", - "start": 1241, - "end": 1242 - }, - { + "start": 1257, + "end": 1258 + } + } + }, + { + "docs": [], + "id": { + "string": "b", + "span": { "str": "b", - "start": 1248, - "end": 1249 - }, - { + "start": 1264, + "end": 1265 + } + } + }, + { + "docs": [], + "id": { + "string": "c", + "span": { "str": "c", - "start": 1255, - "end": 1256 + "start": 1271, + "end": 1272 } - ], - "close": { - "str": "}", - "start": 1258, - "end": 1259 } } - } + ] } } } }, { - "docs": [ - { - "str": "/// Type aliases\n", - "start": 1261, - "end": 1278 - } - ], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 1278, - "end": 1282 - }, - "id": { + "alias": { + "docs": [ + { + "comment": "Type aliases", + "span": { + "str": "/// Type aliases", + "start": 1277, + "end": 1293 + } + } + ], + "id": { + "string": "t", + "span": { "str": "t", - "start": 1283, - "end": 1284 - }, - "equals": { - "str": "=", - "start": 1285, - "end": 1286 - }, - "kind": { - "type": { - "ident": { + "start": 1299, + "end": 1300 + } + }, + "kind": { + "type": { + "ident": { + "string": "e", + "span": { "str": "e", - "start": 1287, - "end": 1288 + "start": 1303, + "end": 1304 } } - }, - "semicolon": { - "str": ";", - "start": 1288, - "end": 1289 } } } @@ -1427,147 +963,76 @@ } }, { - "docs": [], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 1290, - "end": 1294 - }, - "id": { + "alias": { + "docs": [], + "id": { + "string": "t2", + "span": { "str": "t2", - "start": 1295, - "end": 1297 - }, - "equals": { - "str": "=", - "start": 1298, - "end": 1299 - }, - "kind": { - "type": { - "string": { - "str": "string", - "start": 1300, - "end": 1306 - } - } - }, - "semicolon": { - "str": ";", - "start": 1306, - "end": 1307 + "start": 1311, + "end": 1313 } + }, + "kind": { + "type": "string" } } } } }, { - "docs": [], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 1308, - "end": 1312 - }, - "id": { + "alias": { + "docs": [], + "id": { + "string": "t3", + "span": { "str": "t3", - "start": 1313, - "end": 1315 - }, - "equals": { - "str": "=", - "start": 1316, - "end": 1317 - }, - "kind": { - "func": { - "keyword": { - "str": "func", - "start": 1318, - "end": 1322 - }, - "params": { - "open": { - "str": "(", - "start": 1322, - "end": 1323 - }, - "list": [ - { - "id": { - "str": "a", - "start": 1323, - "end": 1324 - }, - "colon": { - "str": ":", - "start": 1324, - "end": 1325 - }, - "ty": { - "u32": { - "str": "u32", - "start": 1326, - "end": 1329 - } - } - }, - { - "id": { - "str": "b", - "start": 1331, - "end": 1332 - }, - "colon": { - "str": ":", - "start": 1332, - "end": 1333 - }, - "ty": { - "ident": { - "str": "r", - "start": 1334, - "end": 1335 - } - } + "start": 1329, + "end": 1331 + } + }, + "kind": { + "func": { + "params": [ + { + "id": { + "string": "a", + "span": { + "str": "a", + "start": 1339, + "end": 1340 } - ], - "close": { - "str": ")", - "start": 1335, - "end": 1336 - } + }, + "ty": "u32" }, - "results": { - "single": { - "arrow": { - "str": "->", - "start": 1337, - "end": 1339 - }, - "ty": { - "u32": { - "str": "u32", - "start": 1340, - "end": 1343 + { + "id": { + "string": "b", + "span": { + "str": "b", + "start": 1347, + "end": 1348 + } + }, + "ty": { + "ident": { + "string": "r", + "span": { + "str": "r", + "start": 1350, + "end": 1351 } } } } + ], + "results": { + "scalar": "u32" } - }, - "semicolon": { - "str": ";", - "start": 1343, - "end": 1344 } } } @@ -1575,113 +1040,47 @@ } }, { - "docs": [], - "stmt": { + "Type": { "type": { - "type": { - "alias": { - "keyword": { - "str": "type", - "start": 1345, - "end": 1349 - }, - "id": { + "alias": { + "docs": [], + "id": { + "string": "t4", + "span": { "str": "t4", - "start": 1350, - "end": 1352 - }, - "equals": { - "str": "=", - "start": 1353, - "end": 1354 - }, - "kind": { - "func": { - "keyword": { - "str": "func", - "start": 1355, - "end": 1359 - }, - "params": { - "open": { - "str": "(", - "start": 1359, - "end": 1360 - }, - "list": [], - "close": { - "str": ")", - "start": 1360, - "end": 1361 - } - }, - "results": { - "named": { - "arrow": { - "str": "->", - "start": 1362, - "end": 1364 + "start": 1366, + "end": 1368 + } + }, + "kind": { + "func": { + "params": [], + "results": { + "named": [ + { + "id": { + "string": "a", + "span": { + "str": "a", + "start": 1382, + "end": 1383 + } }, - "results": { - "open": { - "str": "(", - "start": 1365, - "end": 1366 - }, - "list": [ - { - "id": { - "str": "a", - "start": 1366, - "end": 1367 - }, - "colon": { - "str": ":", - "start": 1367, - "end": 1368 - }, - "ty": { - "u32": { - "str": "u32", - "start": 1369, - "end": 1372 - } - } - }, - { - "id": { - "str": "b", - "start": 1374, - "end": 1375 - }, - "colon": { - "str": ":", - "start": 1375, - "end": 1376 - }, - "ty": { - "string": { - "str": "string", - "start": 1377, - "end": 1383 - } - } - } - ], - "close": { - "str": ")", - "start": 1383, - "end": 1384 + "ty": "u32" + }, + { + "id": { + "string": "b", + "span": { + "str": "b", + "start": 1390, + "end": 1391 } - } + }, + "ty": "string" } - } + ] } - }, - "semicolon": { - "str": ";", - "start": 1384, - "end": 1385 } } } diff --git a/crates/wac-parser/tests/resolution.rs b/crates/wac-parser/tests/resolution.rs index 96060fa..b16d113 100644 --- a/crates/wac-parser/tests/resolution.rs +++ b/crates/wac-parser/tests/resolution.rs @@ -1,4 +1,5 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{anyhow, bail, Context, Result}; +use owo_colors::OwoColorize; use pretty_assertions::StrComparison; use rayon::prelude::*; use std::{ @@ -12,6 +13,7 @@ use std::{ use wac_parser::{ ast::Document, resolution::{FileSystemPackageResolver, ResolvedDocument}, + ErrorFormatter, }; #[cfg(not(feature = "wat"))] @@ -82,7 +84,8 @@ fn compare_result(test: &Path, result: &str, should_fail: bool) -> Result<()> { fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { let should_fail = test.parent().map(|p| p.ends_with("fail")).unwrap_or(false); let source = std::fs::read_to_string(test)?.replace("\r\n", "\n"); - let document = Document::parse(&source, test)?; + let document = Document::parse(&source, test) + .map_err(|e| anyhow!("{e}", e = ErrorFormatter::new(test, e, false)))?; let result = match ResolvedDocument::new( &document, "test:test", @@ -100,10 +103,13 @@ fn run_test(test: &Path, ntests: &AtomicUsize) -> Result<()> { } Err(e) => { if !should_fail { - bail!("the resolution failed but it was expected to succeed: {e:?}"); + bail!( + "the resolution failed but it was expected to succeed: {e}", + e = ErrorFormatter::new(test, e, false) + ); } - format!("{e:?}") + format!("{e}", e = ErrorFormatter::new(test, e, false)) } }; @@ -129,11 +135,11 @@ fn main() { .err() { Some(e) => { - println!("test {test_name} ... failed: {e}"); + println!("test {test_name} ... {failed}", failed = "failed".red()); Some((test_name, e)) } None => { - println!("test {test_name} ... ok"); + println!("test {test_name} ... {ok}", ok = "ok".green()); None } } @@ -141,10 +147,14 @@ fn main() { .collect::>(); if !errors.is_empty() { - eprintln!("\n{} test(s) failed:", errors.len()); + eprintln!( + "\n{count} test(s) {failed}:", + count = errors.len(), + failed = "failed".red() + ); for (name, msg) in errors.iter() { - eprintln!("{name}: {msg:?}"); + eprintln!("{name}: {msg:?}", msg = msg.red()); } exit(1); diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-enum-case.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-enum-case.wac.result index 3dbaef4..df9748c 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-enum-case.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-enum-case.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-enum-case.wac:5:5: duplicate case `b` for enum type `e` - - --> tests/resolution/fail/duplicate-enum-case.wac:5:5 - | -5 | b, - | ^ - | - = duplicate case `b` for enum type `e` \ No newline at end of file +duplicate case `b` for enum type `e` + --> tests/resolution/fail/duplicate-enum-case.wac:5:5 + | + 5 | b, + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-flag.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-flag.wac.result index 1b519f7..cebe6c0 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-flag.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-flag.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-flag.wac:6:5: duplicate flag `a` for flags type `f` - - --> tests/resolution/fail/duplicate-flag.wac:6:5 - | -6 | a, - | ^ - | - = duplicate flag `a` for flags type `f` \ No newline at end of file +duplicate flag `a` for flags type `f` + --> tests/resolution/fail/duplicate-flag.wac:6:5 + | + 6 | a, + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-func-param.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-func-param.wac.result index 47ad45b..dbb2623 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-func-param.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-func-param.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-func-param.wac:1:23: duplicate function parameter `x` - - --> tests/resolution/fail/duplicate-func-param.wac:1:23 - | -1 | type f = func(x: u32, x: string); - | ^ - | - = duplicate function parameter `x` \ No newline at end of file +duplicate function parameter `x` + --> tests/resolution/fail/duplicate-func-param.wac:1:23 + | + 1 | type f = func(x: u32, x: string); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-func-result.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-func-result.wac.result index 7c9ed13..483e71e 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-func-result.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-func-result.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-func-result.wac:1:29: duplicate function result `a` - - --> tests/resolution/fail/duplicate-func-result.wac:1:29 - | -1 | type f = func() -> (a: u32, a: s8); - | ^ - | - = duplicate function result `a` \ No newline at end of file +duplicate function result `a` + --> tests/resolution/fail/duplicate-func-result.wac:1:29 + | + 1 | type f = func() -> (a: u32, a: s8); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-interface-export.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-interface-export.wac.result index 7439a5d..096c37f 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-interface-export.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-interface-export.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-interface-export.wac:3:5: duplicate interface export `x` for interface `x` - - --> tests/resolution/fail/duplicate-interface-export.wac:3:5 - | -3 | x: func(); - | ^ - | - = duplicate interface export `x` for interface `x` \ No newline at end of file +duplicate interface export `x` for interface `x` + --> tests/resolution/fail/duplicate-interface-export.wac:3:5 + | + 3 | x: func(); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-name-in-include.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-name-in-include.wac.result index 5e46768..74a1376 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-name-in-include.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-name-in-include.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-name-in-include.wac:6:31: duplicate `a` in world include `with` clause - - --> tests/resolution/fail/duplicate-name-in-include.wac:6:31 - | -6 | include w1 with { a as b, a as c}; - | ^ - | - = duplicate `a` in world include `with` clause \ No newline at end of file +duplicate `a` in world include `with` clause + --> tests/resolution/fail/duplicate-name-in-include.wac:6:31 + | + 6 | include w1 with { a as b, a as c}; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-record-field.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-record-field.wac.result index e8feb00..5660506 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-record-field.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-record-field.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-record-field.wac:7:5: duplicate field `b` for record type `r` - - --> tests/resolution/fail/duplicate-record-field.wac:7:5 - | -7 | b: string, - | ^ - | - = duplicate field `b` for record type `r` \ No newline at end of file +duplicate field `b` for record type `r` + --> tests/resolution/fail/duplicate-record-field.wac:7:5 + | + 7 | b: string, + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac index 0459727..a32cf87 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac +++ b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac @@ -1,3 +1,5 @@ -resource x { - constructor(a: u32, a: string); +interface foo { + resource x { + constructor(a: u32, a: string); + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac.result index 798db14..f7d5e0c 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor-param.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-resource-constructor-param.wac:2:25: duplicate constructor parameter `a` - - --> tests/resolution/fail/duplicate-resource-constructor-param.wac:2:25 - | -2 | constructor(a: u32, a: string); - | ^ - | - = duplicate constructor parameter `a` \ No newline at end of file +duplicate constructor parameter `a` + --> tests/resolution/fail/duplicate-resource-constructor-param.wac:3:29 + | + 3 | constructor(a: u32, a: string); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac index 11d324b..edba881 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac +++ b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac @@ -1,4 +1,6 @@ -resource x { - constructor(); - constructor(x: u32); +interface foo { + resource x { + constructor(); + constructor(x: u32); + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac.result index c85e95a..732ff90 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-resource-constructor.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-resource-constructor.wac:3:5: duplicate constructor for resource `x` - - --> tests/resolution/fail/duplicate-resource-constructor.wac:3:5 - | -3 | constructor(x: u32); - | ^---------^ - | - = duplicate constructor for resource `x` \ No newline at end of file +duplicate constructor for resource `x` + --> tests/resolution/fail/duplicate-resource-constructor.wac:4:9 + | + 4 | constructor(x: u32); + | ^---------^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac b/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac index 876558c..638425f 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac +++ b/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac @@ -1,4 +1,6 @@ -resource x { - x: func(); - x: static func(); +world foo { + resource x { + x: func(); + x: static func(); + } } \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac.result index c3f0f25..15152bf 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-resource-method.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-resource-method.wac:3:5: duplicate method `x` for resource `x` - - --> tests/resolution/fail/duplicate-resource-method.wac:3:5 - | -3 | x: static func(); - | ^ - | - = duplicate method `x` for resource `x` \ No newline at end of file +duplicate method `x` for resource `x` + --> tests/resolution/fail/duplicate-resource-method.wac:4:9 + | + 4 | x: static func(); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-variant-case.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-variant-case.wac.result index 306d42f..3351150 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-variant-case.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-variant-case.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-variant-case.wac:6:5: duplicate case `a` for variant type `x` - - --> tests/resolution/fail/duplicate-variant-case.wac:6:5 - | -6 | a, - | ^ - | - = duplicate case `a` for variant type `x` \ No newline at end of file +duplicate case `a` for variant type `x` + --> tests/resolution/fail/duplicate-variant-case.wac:6:5 + | + 6 | a, + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-world-export.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-world-export.wac.result index 812c0f9..cf126e6 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-world-export.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-world-export.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-world-export.wac:3:12: export `x` conflicts with existing export of the same name in world `w` - - --> tests/resolution/fail/duplicate-world-export.wac:3:12 - | -3 | export x: func(); - | ^ - | - = export `x` conflicts with existing export of the same name in world `w` \ No newline at end of file +export `x` conflicts with existing export of the same name in world `w` + --> tests/resolution/fail/duplicate-world-export.wac:3:12 + | + 3 | export x: func(); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/duplicate-world-import.wac.result b/crates/wac-parser/tests/resolution/fail/duplicate-world-import.wac.result index 0771dc2..271a9b0 100644 --- a/crates/wac-parser/tests/resolution/fail/duplicate-world-import.wac.result +++ b/crates/wac-parser/tests/resolution/fail/duplicate-world-import.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/duplicate-world-import.wac:3:12: import `x` conflicts with existing import of the same name in world `w` - - --> tests/resolution/fail/duplicate-world-import.wac:3:12 - | -3 | import x: func(); - | ^ - | - = import `x` conflicts with existing import of the same name in world `w` \ No newline at end of file +import `x` conflicts with existing import of the same name in world `w` + --> tests/resolution/fail/duplicate-world-import.wac:3:12 + | + 3 | import x: func(); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/expected-result-named.wac.result b/crates/wac-parser/tests/resolution/fail/expected-result-named.wac.result index 4d3ea4c..2f8d3e1 100644 --- a/crates/wac-parser/tests/resolution/fail/expected-result-named.wac.result +++ b/crates/wac-parser/tests/resolution/fail/expected-result-named.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/expected-result-named.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/expected-result-named.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/expected-result-named.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/expected-result-scalar.wac.result b/crates/wac-parser/tests/resolution/fail/expected-result-scalar.wac.result index 3dfde23..e28c4a8 100644 --- a/crates/wac-parser/tests/resolution/fail/expected-result-scalar.wac.result +++ b/crates/wac-parser/tests/resolution/fail/expected-result-scalar.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/expected-result-scalar.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/expected-result-scalar.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/expected-result-scalar.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/func-results-not-present.wac.result b/crates/wac-parser/tests/resolution/fail/func-results-not-present.wac.result index e44759f..d92fb4d 100644 --- a/crates/wac-parser/tests/resolution/fail/func-results-not-present.wac.result +++ b/crates/wac-parser/tests/resolution/fail/func-results-not-present.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/func-results-not-present.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/func-results-not-present.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/func-results-not-present.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/func-results-present.wac.result b/crates/wac-parser/tests/resolution/fail/func-results-present.wac.result index 4b1405f..5393a87 100644 --- a/crates/wac-parser/tests/resolution/fail/func-results-present.wac.result +++ b/crates/wac-parser/tests/resolution/fail/func-results-present.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/func-results-present.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/func-results-present.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/func-results-present.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/invalid-alias.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-alias.wac.result index dfca317..3f08740 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-alias.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-alias.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-alias.wac:5:10: `a` (interface) cannot be used in a type alias - - --> tests/resolution/fail/invalid-alias.wac:5:10 - | -5 | type x = a; - | ^ - | - = `a` (interface) cannot be used in a type alias \ No newline at end of file +`a` (interface) cannot be used in a type alias + --> tests/resolution/fail/invalid-alias.wac:5:10 + | + 5 | type x = a; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-borrow.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-borrow.wac.result index 33fc3cd..0ab2db2 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-borrow.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-borrow.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-borrow.wac:3:17: `x` (u32) is not a resource type - - --> tests/resolution/fail/invalid-borrow.wac:3:17 - | -3 | type y = borrow; - | ^ - | - = `x` (u32) is not a resource type \ No newline at end of file +`x` (u32) is not a resource type + --> tests/resolution/fail/invalid-borrow.wac:3:17 + | + 3 | type y = borrow; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac.result index 8f2148b..a2c6f1d 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-func-type-ref.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-func-type-ref.wac:4:8: `x` (u32) is not a function type - - --> tests/resolution/fail/invalid-func-type-ref.wac:4:8 - | -4 | x: x; - | ^ - | - = `x` (u32) is not a function type \ No newline at end of file +`x` (u32) is not a function type + --> tests/resolution/fail/invalid-func-type-ref.wac:4:8 + | + 4 | x: x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result index 551708b..da7a8fd 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-use-alias.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-use-alias.wac:7:17: `b` was previously defined at tests/resolution/fail/invalid-use-alias.wac:6:10 - - --> tests/resolution/fail/invalid-use-alias.wac:7:17 - | -7 | use a.{a as b}; - | ^ - | - = `b` was previously defined at tests/resolution/fail/invalid-use-alias.wac:6:10 \ No newline at end of file +`b` was previously defined at tests/resolution/fail/invalid-use-alias.wac:6:10 + --> tests/resolution/fail/invalid-use-alias.wac:7:17 + | + 7 | use a.{a as b}; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-use.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-use.wac.result index 26c89b2..2552af5 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-use.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-use.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-use.wac:4:9: `x` (u32) is not an interface - - --> tests/resolution/fail/invalid-use.wac:4:9 - | -4 | use x.{a}; - | ^ - | - = `x` (u32) is not an interface \ No newline at end of file +`x` (u32) is not an interface + --> tests/resolution/fail/invalid-use.wac:4:9 + | + 4 | use x.{a}; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-value-type.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-value-type.wac.result index 17e1c66..3014b30 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-value-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-value-type.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-value-type.wac:5:18: `i` (interface) cannot be used as a value type - - --> tests/resolution/fail/invalid-value-type.wac:5:18 - | -5 | type x = func(i: i); - | ^ - | - = `i` (interface) cannot be used as a value type \ No newline at end of file +`i` (interface) cannot be used as a value type + --> tests/resolution/fail/invalid-value-type.wac:5:18 + | + 5 | type x = func(i: i); + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-world-export.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-world-export.wac.result index fb40583..e7f3d52 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-world-export.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-world-export.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-world-export.wac:4:15: `x` (u32) is not a function type or interface - - --> tests/resolution/fail/invalid-world-export.wac:4:15 - | -4 | import x: x; - | ^ - | - = `x` (u32) is not a function type or interface \ No newline at end of file +`x` (u32) is not a function type or interface + --> tests/resolution/fail/invalid-world-export.wac:4:15 + | + 4 | import x: x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-world-import.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-world-import.wac.result index 5c503ed..605e4d7 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-world-import.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-world-import.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-world-import.wac:4:15: `x` (u32) is not a function type or interface - - --> tests/resolution/fail/invalid-world-import.wac:4:15 - | -4 | import x: x; - | ^ - | - = `x` (u32) is not a function type or interface \ No newline at end of file +`x` (u32) is not a function type or interface + --> tests/resolution/fail/invalid-world-import.wac:4:15 + | + 4 | import x: x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-world-include.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-world-include.wac.result index aae6fb1..74f14f9 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-world-include.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-world-include.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-world-include.wac:4:13: `x` (function type) is not a world - - --> tests/resolution/fail/invalid-world-include.wac:4:13 - | -4 | include x; - | ^ - | - = `x` (function type) is not a world \ No newline at end of file +`x` (function type) is not a world + --> tests/resolution/fail/invalid-world-include.wac:4:13 + | + 4 | include x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-world-interface-export.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-world-interface-export.wac.result index 8f1f532..002a70a 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-world-interface-export.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-world-interface-export.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-world-interface-export.wac:4:12: `x` (u32) is not an interface - - --> tests/resolution/fail/invalid-world-interface-export.wac:4:12 - | -4 | export x; - | ^ - | - = `x` (u32) is not an interface \ No newline at end of file +`x` (u32) is not an interface + --> tests/resolution/fail/invalid-world-interface-export.wac:4:12 + | + 4 | export x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/invalid-world-interface-import.wac.result b/crates/wac-parser/tests/resolution/fail/invalid-world-interface-import.wac.result index 9efc91b..419d8a5 100644 --- a/crates/wac-parser/tests/resolution/fail/invalid-world-interface-import.wac.result +++ b/crates/wac-parser/tests/resolution/fail/invalid-world-interface-import.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/invalid-world-interface-import.wac:4:12: `x` (u32) is not an interface - - --> tests/resolution/fail/invalid-world-interface-import.wac:4:12 - | -4 | import x; - | ^ - | - = `x` (u32) is not an interface \ No newline at end of file +`x` (u32) is not an interface + --> tests/resolution/fail/invalid-world-interface-import.wac:4:12 + | + 4 | import x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-enum-cases.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-enum-cases.wac.result index 8441cf7..77c269b 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-enum-cases.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-enum-cases.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-enum-cases.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-enum-cases.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-enum-cases.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `e` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-enum-count.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-enum-count.wac.result index 25c1014..e41a186 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-enum-count.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-enum-count.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-enum-count.wac:10:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-enum-count.wac:10:23 - | -10 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-enum-count.wac:10:23 + | + 10 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `e` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-err-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-err-result-type.wac.result index 79a93d1..9e884f8 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-err-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-err-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-err-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-err-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-err-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-flags-count.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-flags-count.wac.result index 2234545..ee2f770 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-flags-count.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-flags-count.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-flags-count.wac:10:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-flags-count.wac:10:23 - | -10 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-flags-count.wac:10:23 + | + 10 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-flags.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-flags.wac.result index b590fcd..6ec4575 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-flags.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-flags.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-flags.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-flags.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-flags.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-func-param-name.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-func-param-name.wac.result index fc55bb8..380ffc9 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-func-param-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-func-param-name.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-func-param-name.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-func-param-name.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-func-param-name.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-func-param-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-func-param-type.wac.result index b2ad8fe..ff217a4 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-func-param-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-func-param-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-func-param-type.wac:6:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-func-param-type.wac:6:23 - | -6 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-func-param-type.wac:6:23 + | + 6 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-func-params.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-func-params.wac.result index 3a22ecd..2f7b910 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-func-params.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-func-params.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-func-params.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-func-params.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-func-params.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-func-result-name.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-func-result-name.wac.result index 7d6659e..cad554d 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-func-result-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-func-result-name.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-func-result-name.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-func-result-name.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-func-result-name.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-func-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-func-result-type.wac.result index d81a0dc..9f43794 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-func-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-func-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-func-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-func-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-func-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-func-scalar-result.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-func-scalar-result.wac.result index b252227..8c42ffe 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-func-scalar-result.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-func-scalar-result.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-func-scalar-result.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-func-scalar-result.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-func-scalar-result.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-kind.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-kind.wac.result index 2c804e3..01e7e4d 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-kind.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-kind.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-kind.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-kind.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-kind.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `x` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-list-element.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-list-element.wac.result index 0e49637..3be5de6 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-list-element.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-list-element.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-list-element.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-list-element.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-list-element.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-ok-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-ok-result-type.wac.result index 9477fa1..c1bc679 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-ok-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-ok-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-ok-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-ok-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-ok-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-option.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-option.wac.result index 6367dfe..540b215 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-option.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-option.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-option.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-option.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-option.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-record-field-count.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-record-field-count.wac.result index 3436c93..1676ff8 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-record-field-count.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-record-field-count.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-record-field-count.wac:10:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-record-field-count.wac:10:23 - | -10 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-record-field-count.wac:10:23 + | + 10 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `r` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-record-field-name.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-record-field-name.wac.result index 8536820..d294ebd 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-record-field-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-record-field-name.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-record-field-name.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-record-field-name.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-record-field-name.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `r` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-record-field-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-record-field-type.wac.result index e423b08..973f1a7 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-record-field-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-record-field-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-record-field-type.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-record-field-type.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-record-field-type.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `r` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result index ef823cb..040d25b 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-resource-types.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-resource-types.wac:7:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-resource-types.wac:7:23 - | -7 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-resource-types.wac:7:23 + | + 7 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-tuple-size.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-tuple-size.wac.result index e2fd275..8a80e06 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-tuple-size.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-tuple-size.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-tuple-size.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-tuple-size.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-tuple-size.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `t` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-tuple-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-tuple-type.wac.result index f3a17cb..59db9f8 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-tuple-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-tuple-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-tuple-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-tuple-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-tuple-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `t` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-count.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-count.wac.result index e1a85f3..a8527f6 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-count.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-count.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-variant-case-count.wac:10:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-variant-case-count.wac:10:23 - | -10 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-variant-case-count.wac:10:23 + | + 10 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `v` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-name.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-name.wac.result index a9c006f..6a7a49a 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-name.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-variant-case-name.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-variant-case-name.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-variant-case-name.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `v` diff --git a/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-type.wac.result b/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-type.wac.result index 32f6fed..b7d7e44 100644 --- a/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/mismatched-variant-case-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/mismatched-variant-case-type.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/mismatched-variant-case-type.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/mismatched-variant-case-type.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `v` diff --git a/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result b/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result index b0839a7..b66864f 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-constructor.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/missing-constructor.wac:6:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/missing-constructor.wac:6:23 - | -6 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/missing-constructor.wac:6:23 + | + 6 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `r` diff --git a/crates/wac-parser/tests/resolution/fail/missing-err-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/missing-err-result-type.wac.result index 73a6908..1dcbbe7 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-err-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-err-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/missing-err-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/missing-err-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/missing-err-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result b/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result index 9d15b64..1ded9cd 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-interface-export.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/missing-interface-export.wac:4:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/missing-interface-export.wac:4:23 - | -4 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/missing-interface-export.wac:4:23 + | + 4 | let i = new foo:bar { x }; + | ^ Caused by: missing expected export `f` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-method.wac.result b/crates/wac-parser/tests/resolution/fail/missing-method.wac.result index 9bc498f..eba4c8e 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-method.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-method.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/missing-method.wac:6:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/missing-method.wac:6:23 - | -6 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/missing-method.wac:6:23 + | + 6 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `r` diff --git a/crates/wac-parser/tests/resolution/fail/missing-ok-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/missing-ok-result-type.wac.result index bf2db3b..c732faf 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-ok-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-ok-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/missing-ok-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/missing-ok-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/missing-ok-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result b/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result index bab54cc..0a633b2 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-static-method.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/missing-static-method.wac:6:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/missing-static-method.wac:6:23 - | -6 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/missing-static-method.wac:6:23 + | + 6 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `r` diff --git a/crates/wac-parser/tests/resolution/fail/missing-type-in-use.result b/crates/wac-parser/tests/resolution/fail/missing-type-in-use.result deleted file mode 100644 index 199e484..0000000 --- a/crates/wac-parser/tests/resolution/fail/missing-type-in-use.result +++ /dev/null @@ -1,9 +0,0 @@ -type `x` is not defined in interface `a` - -Caused by: - --> tests/resolution/fail/invalid-use.wac:6:12 - | - 6 | use a.{x}; - | ^ - | - = type `x` is not defined in interface `a` \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/missing-type-in-use.wac.result b/crates/wac-parser/tests/resolution/fail/missing-type-in-use.wac.result index a79a414..92a73c8 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-type-in-use.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-type-in-use.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/missing-type-in-use.wac:6:12: type `x` is not defined in interface `a` - - --> tests/resolution/fail/missing-type-in-use.wac:6:12 - | -6 | use a.{x}; - | ^ - | - = type `x` is not defined in interface `a` \ No newline at end of file +type `x` is not defined in interface `a` + --> tests/resolution/fail/missing-type-in-use.wac:6:12 + | + 6 | use a.{x}; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/missing-world-include-name.wac.result b/crates/wac-parser/tests/resolution/fail/missing-world-include-name.wac.result index 04087a0..ccd56cb 100644 --- a/crates/wac-parser/tests/resolution/fail/missing-world-include-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/missing-world-include-name.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/missing-world-include-name.wac:6:23: world `w1` does not have an import or export with kebab-name `a` - - --> tests/resolution/fail/missing-world-include-name.wac:6:23 - | -6 | include w1 with { a as b }; - | ^ - | - = world `w1` does not have an import or export with kebab-name `a` \ No newline at end of file +world `w1` does not have an import or export named `a` + --> tests/resolution/fail/missing-world-include-name.wac:6:23 + | + 6 | include w1 with { a as b }; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/no-err-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/no-err-result-type.wac.result index 4094974..e8ed12e 100644 --- a/crates/wac-parser/tests/resolution/fail/no-err-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/no-err-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/no-err-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/no-err-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/no-err-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/no-import.wac.result b/crates/wac-parser/tests/resolution/fail/no-import.wac.result index 8daf7cf..0b5d60a 100644 --- a/crates/wac-parser/tests/resolution/fail/no-import.wac.result +++ b/crates/wac-parser/tests/resolution/fail/no-import.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/no-import.wac:2:23: component `foo:bar` has no import named `x` - - --> tests/resolution/fail/no-import.wac:2:23 - | -2 | let i = new foo:bar { x }; - | ^ - | - = component `foo:bar` has no import named `x` \ No newline at end of file +component `foo:bar` has no import named `x` + --> tests/resolution/fail/no-import.wac:2:23 + | + 2 | let i = new foo:bar { x }; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/no-ok-result-type.wac.result b/crates/wac-parser/tests/resolution/fail/no-ok-result-type.wac.result index b14e39c..c42b262 100644 --- a/crates/wac-parser/tests/resolution/fail/no-ok-result-type.wac.result +++ b/crates/wac-parser/tests/resolution/fail/no-ok-result-type.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/no-ok-result-type.wac:5:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/no-ok-result-type.wac:5:23 - | -5 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/no-ok-result-type.wac:5:23 + | + 5 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `f` diff --git a/crates/wac-parser/tests/resolution/fail/package-invalid-wasm.wac.result b/crates/wac-parser/tests/resolution/fail/package-invalid-wasm.wac.result index f47228c..2dea473 100644 --- a/crates/wac-parser/tests/resolution/fail/package-invalid-wasm.wac.result +++ b/crates/wac-parser/tests/resolution/fail/package-invalid-wasm.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/package-invalid-wasm.wac:1:13: failed to parse package `foo:bar` - - --> tests/resolution/fail/package-invalid-wasm.wac:1:13 - | -1 | import foo: foo:bar/qux; - | ^-----^ - | - = failed to parse package `foo:bar` +failed to parse package `foo:bar` + --> tests/resolution/fail/package-invalid-wasm.wac:1:13 + | + 1 | import foo: foo:bar/qux; + | ^-----^ Caused by: unknown type 0: type index out of bounds (at offset 0xb) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result b/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result index df5bd14..90fc5d3 100644 --- a/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result +++ b/crates/wac-parser/tests/resolution/fail/package-not-component.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/package-not-component.wac:1:13: failed to parse package `foo:bar` - - --> tests/resolution/fail/package-not-component.wac:1:13 - | -1 | import foo: foo:bar/qux; - | ^-----^ - | - = failed to parse package `foo:bar` +failed to parse package `foo:bar` + --> tests/resolution/fail/package-not-component.wac:1:13 + | + 1 | import foo: foo:bar/qux; + | ^-----^ Caused by: input is not a WebAssembly component \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result b/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result index 3176f91..2b147fc 100644 --- a/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result +++ b/crates/wac-parser/tests/resolution/fail/package-not-wasm.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/package-not-wasm.wac:1:13: failed to parse package `foo:bar` - - --> tests/resolution/fail/package-not-wasm.wac:1:13 - | -1 | import foo: foo:bar/qux; - | ^-----^ - | - = failed to parse package `foo:bar` +failed to parse package `foo:bar` + --> tests/resolution/fail/package-not-wasm.wac:1:13 + | + 1 | import foo: foo:bar/qux; + | ^-----^ Caused by: magic header not detected: bad magic number (at offset 0x0) \ No newline at end of file diff --git a/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result b/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result index b580245..1b9d1db 100644 --- a/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/redefined-name.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/redefined-name.wac:2:6: `x` was previously defined at tests/resolution/fail/redefined-name.wac:1:6 - - --> tests/resolution/fail/redefined-name.wac:2:6 - | -2 | type x = string; - | ^ - | - = `x` was previously defined at tests/resolution/fail/redefined-name.wac:1:6 \ No newline at end of file +`x` was previously defined at tests/resolution/fail/redefined-name.wac:1:6 + --> tests/resolution/fail/redefined-name.wac:2:6 + | + 2 | type x = string; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/undefined-name.wac.result b/crates/wac-parser/tests/resolution/fail/undefined-name.wac.result index 68fe25e..dcd57ab 100644 --- a/crates/wac-parser/tests/resolution/fail/undefined-name.wac.result +++ b/crates/wac-parser/tests/resolution/fail/undefined-name.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/undefined-name.wac:1:10: undefined name `x` - - --> tests/resolution/fail/undefined-name.wac:1:10 - | -1 | type x = x; - | ^ - | - = undefined name `x` \ No newline at end of file +undefined name `x` + --> tests/resolution/fail/undefined-name.wac:1:10 + | + 1 | type x = x; + | ^ diff --git a/crates/wac-parser/tests/resolution/fail/unknown-package.wac.result b/crates/wac-parser/tests/resolution/fail/unknown-package.wac.result index e00654a..f2ea5c8 100644 --- a/crates/wac-parser/tests/resolution/fail/unknown-package.wac.result +++ b/crates/wac-parser/tests/resolution/fail/unknown-package.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/unknown-package.wac:1:13: unknown package `bar:baz` - - --> tests/resolution/fail/unknown-package.wac:1:13 - | -1 | import foo: bar:baz/qux; - | ^-----^ - | - = unknown package `bar:baz` \ No newline at end of file +unknown package `bar:baz` + --> tests/resolution/fail/unknown-package.wac:1:13 + | + 1 | import foo: bar:baz/qux; + | ^-----^ diff --git a/crates/wac-parser/tests/resolution/fail/variant-case-typed.wac.result b/crates/wac-parser/tests/resolution/fail/variant-case-typed.wac.result index bf4d558..238a95d 100644 --- a/crates/wac-parser/tests/resolution/fail/variant-case-typed.wac.result +++ b/crates/wac-parser/tests/resolution/fail/variant-case-typed.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/variant-case-typed.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/variant-case-typed.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/variant-case-typed.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `v` diff --git a/crates/wac-parser/tests/resolution/fail/variant-case-untyped.wac.result b/crates/wac-parser/tests/resolution/fail/variant-case-untyped.wac.result index ea4907b..a2e0d0b 100644 --- a/crates/wac-parser/tests/resolution/fail/variant-case-untyped.wac.result +++ b/crates/wac-parser/tests/resolution/fail/variant-case-untyped.wac.result @@ -1,11 +1,8 @@ -tests/resolution/fail/variant-case-untyped.wac:9:23: mismatched instantiation argument `x` - - --> tests/resolution/fail/variant-case-untyped.wac:9:23 - | -9 | let i = new foo:bar { x }; - | ^ - | - = mismatched instantiation argument `x` +mismatched instantiation argument `x` + --> tests/resolution/fail/variant-case-untyped.wac:9:23 + | + 9 | let i = new foo:bar { x }; + | ^ Caused by: 0: mismatched type for export `v` diff --git a/crates/wac-parser/tests/resolution/fail/world-include-conflict.wac.result b/crates/wac-parser/tests/resolution/fail/world-include-conflict.wac.result index 28ffbb2..7e9f698 100644 --- a/crates/wac-parser/tests/resolution/fail/world-include-conflict.wac.result +++ b/crates/wac-parser/tests/resolution/fail/world-include-conflict.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/world-include-conflict.wac:6:13: export `a` from world `w1` conflicts with export of the same name in world `w2` (consider using a `with` clause to use a different name) - - --> tests/resolution/fail/world-include-conflict.wac:6:13 - | -6 | include w1; - | ^^ - | - = export `a` from world `w1` conflicts with export of the same name in world `w2` (consider using a `with` clause to use a different name) \ No newline at end of file +export `a` from world `w1` conflicts with export of the same name in world `w2` (consider using a `with` clause to use a different name) + --> tests/resolution/fail/world-include-conflict.wac:6:13 + | + 6 | include w1; + | ^^ diff --git a/crates/wac-parser/tests/resolution/fail/world-include-with-conflict.wac.result b/crates/wac-parser/tests/resolution/fail/world-include-with-conflict.wac.result index 923a213..4909ba2 100644 --- a/crates/wac-parser/tests/resolution/fail/world-include-with-conflict.wac.result +++ b/crates/wac-parser/tests/resolution/fail/world-include-with-conflict.wac.result @@ -1,8 +1,5 @@ -tests/resolution/fail/world-include-with-conflict.wac:6:28: export `x` from world `w1` conflicts with export of the same name in world `w2` - - --> tests/resolution/fail/world-include-with-conflict.wac:6:28 - | -6 | include w1 with { a as x }; - | ^ - | - = export `x` from world `w1` conflicts with export of the same name in world `w2` \ No newline at end of file +export `x` from world `w1` conflicts with export of the same name in world `w2` + --> tests/resolution/fail/world-include-with-conflict.wac:6:28 + | + 6 | include w1 with { a as x }; + | ^ diff --git a/crates/wac-parser/tests/resolution/resource.wac b/crates/wac-parser/tests/resolution/resource.wac index 24ddefe..a3d5758 100644 --- a/crates/wac-parser/tests/resolution/resource.wac +++ b/crates/wac-parser/tests/resolution/resource.wac @@ -1,3 +1,5 @@ -resource x; - -type f = func(x: borrow); \ No newline at end of file +interface foo { + resource x; + type f = func(x: borrow); + f2: f; +} diff --git a/crates/wac-parser/tests/resolution/resource.wac.result b/crates/wac-parser/tests/resolution/resource.wac.result index 8eabc2b..4dab8db 100644 --- a/crates/wac-parser/tests/resolution/resource.wac.result +++ b/crates/wac-parser/tests/resolution/resource.wac.result @@ -20,7 +20,33 @@ "results": null } ], - "interfaces": [], + "interfaces": [ + { + "id": "test:test/foo", + "exports": { + "x": { + "kind": { + "type": { + "value": 0 + } + } + }, + "f": { + "kind": { + "type": { + "func": 0 + } + } + }, + "f2": { + "kind": { + "func": 0 + } + } + }, + "scope": 1 + } + ], "worlds": [], "modules": [] }, @@ -40,11 +66,25 @@ } }, "source": "definition" + }, + { + "kind": { + "type": { + "interface": 0 + } + }, + "source": "definition" } ], "scopes": [ { "parent": null, + "items": { + "foo": 2 + } + }, + { + "parent": 0, "items": { "x": 0, "f": 1 diff --git a/crates/wac-parser/tests/resolution/types.wac b/crates/wac-parser/tests/resolution/types.wac index 875590c..21e504f 100644 --- a/crates/wac-parser/tests/resolution/types.wac +++ b/crates/wac-parser/tests/resolution/types.wac @@ -53,11 +53,12 @@ world w2 { /// Include a world by path include foo:bar/baz; -} -/// Defining a resource -resource res { - constructor(); + /// Defining a resource + + resource res { + constructor(); + } } type x = u32; diff --git a/crates/wac-parser/tests/resolution/types.wac.result b/crates/wac-parser/tests/resolution/types.wac.result index e610f3a..fe4e0bd 100644 --- a/crates/wac-parser/tests/resolution/types.wac.result +++ b/crates/wac-parser/tests/resolution/types.wac.result @@ -319,6 +319,13 @@ } }, "exports": { + "res": { + "kind": { + "type": { + "value": 2 + } + } + }, "d": { "kind": { "type": { @@ -426,7 +433,7 @@ { "kind": { "type": { - "world": 5 + "value": 2 } }, "source": "definition" @@ -434,7 +441,7 @@ { "kind": { "type": { - "value": 2 + "world": 5 } }, "source": "definition" @@ -521,8 +528,7 @@ "c": 6, "f": 7, "w1": 9, - "w2": 10, - "res": 11, + "w2": 11, "x": 12, "v": 13, "r": 14, @@ -560,7 +566,9 @@ }, { "parent": 0, - "items": {} + "items": { + "res": 10 + } } ] } \ No newline at end of file diff --git a/crates/wac-parser/wac.pest b/crates/wac-parser/wac.pest deleted file mode 100644 index cbe540f..0000000 --- a/crates/wac-parser/wac.pest +++ /dev/null @@ -1,236 +0,0 @@ -// Top-level document rule -Document = { SOI ~ TopLevelStatement* ~ EOI } - -// Top-level statements -TopLevelStatement = { DocComment* ~ Statement } -Statement = { ImportStatement | TypeStatement | LetStatement | ExportStatement } - -// Import statement -ImportStatement = ${ ImportKeyword ~ DelimitingSpace+ ~ Ident ~ (DelimitingSpace+ ~ WithClause)? ~ DelimitingSpace* ~ Colon ~ DelimitingSpace* ~ ImportType ~ DelimitingSpace* ~ Semicolon } -WithClause = ${ WithKeyword ~ DelimitingSpace+ ~ String } -ImportType = !{ PackagePath | FuncType | InlineInterface | Ident } -PackagePath = ${ PackageName ~ (Slash ~ Ident)+ ~ (At ~ PackageVersion)? } -PackageName = ${ Ident ~ (":" ~ Ident)+ } -PackageVersion = { (ASCII_ALPHANUMERIC | "." | "-" | "+")+ } - -// Type statement -TypeStatement = { InterfaceDecl | WorldDecl | TypeDecl } -InterfaceDecl = ${ InterfaceKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ InterfaceBody } -InterfaceBody = !{ OpenBrace ~ InterfaceItem* ~ CloseBrace } -InterfaceItem = { DocComment* ~ InterfaceItemStatement } -InterfaceItemStatement = { UseStatement | TypeDecl | InterfaceExportStatement } -UseStatement = ${ UseKeyword ~ DelimitingSpace+ ~ UseItems ~ DelimitingSpace* ~ Semicolon } -UseItems = !{ UsePath ~ Dot ~ OpenBrace ~ UseItem ~ ("," ~ UseItem)* ~ ","? ~ CloseBrace } -UseItem = { Ident ~ UseAsClause? } -UseAsClause = { AsKeyword ~ Ident } -UsePath = { PackagePath | Ident } -InterfaceExportStatement = { Ident ~ Colon ~ FuncTypeRef ~ Semicolon } -WorldDecl = ${ WorldKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ WorldBody } -WorldBody = !{ OpenBrace ~ WorldItem* ~ CloseBrace } -WorldItem = { DocComment* ~ WorldItemStatement } -WorldItemStatement = { UseStatement | TypeDecl | WorldImportStatement | WorldExportStatement | WorldIncludeStatement } -WorldImportStatement = ${ ImportKeyword ~ DelimitingSpace+ ~ WorldItemDecl ~ DelimitingSpace* ~ Semicolon } -WorldExportStatement = ${ ExportKeyword ~ DelimitingSpace+ ~ WorldItemDecl ~ DelimitingSpace* ~ Semicolon } -WorldItemDecl = !{ WorldNamedItem | InterfaceRef } -WorldNamedItem = { Ident ~ Colon ~ ExternType ~ !Slash } -InterfaceRef = { PackagePath | Ident } -ExternType = { FuncType | InlineInterface | Ident } -InlineInterface = { InterfaceKeyword ~ InterfaceBody } -WorldIncludeStatement = ${ IncludeKeyword ~ DelimitingSpace+ ~ WorldRef ~ DelimitingSpace* ~ WorldIncludeWithClause? ~ DelimitingSpace* ~ Semicolon } -WorldIncludeWithClause = !{ WithKeyword ~ OpenBrace ~ WorldIncludeItem ~ ("," ~ WorldIncludeItem)* ~ ","? ~ CloseBrace } -WorldIncludeItem = ${ Ident ~ DelimitingSpace+ ~ AsKeyword ~ DelimitingSpace+ ~ Ident } -WorldRef = { PackagePath | Ident } -TypeDecl = { ResourceDecl | VariantDecl | RecordDecl | FlagsDecl | EnumDecl | TypeAlias } -ResourceDecl = ${ ResourceKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ ResourceBody } -ResourceBody = !{ Semicolon | (OpenBrace ~ (ResourceMethod ~ Semicolon)+ ~ CloseBrace) } -ResourceMethod = { Constructor | Method } -Constructor = { ConstructorKeyword ~ ParamList } -Method = { Ident ~ Colon ~ StaticKeyword? ~ FuncTypeRef } -VariantDecl = ${ VariantKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ VariantBody } -VariantBody = !{ OpenBrace ~ VariantCase ~ ("," ~ VariantCase)* ~ ","? ~ CloseBrace } -VariantCase = { Ident ~ VariantType? } -VariantType = { OpenParen ~ Type ~ CloseParen } -RecordDecl = ${ RecordKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ RecordBody } -RecordBody = !{ OpenBrace ~ NamedType ~ ("," ~ NamedType)* ~ ","? ~ CloseBrace } -FlagsDecl = ${ FlagsKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ FlagsBody } -FlagsBody = !{ OpenBrace ~ Ident ~ ("," ~ Ident)* ~ ","? ~ CloseBrace } -EnumDecl = ${ EnumKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ EnumBody } -EnumBody = !{ OpenBrace ~ Ident ~ ("," ~ Ident)* ~ ","? ~ CloseBrace } -TypeAlias = ${ TypeKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ Equals ~ DelimitingSpace* ~ TypeAliasKind ~ DelimitingSpace* ~ Semicolon } -TypeAliasKind = !{ FuncType | Type } -FuncTypeRef = { FuncType | Ident } -FuncType = { FuncKeyword ~ ParamList ~ ResultList? } -ParamList = { OpenParen ~ (NamedType ~ ("," ~ NamedType)* ~ ","?)? ~ CloseParen } -ResultList = { Arrow ~ (NamedResultList | Type) } -NamedResultList = { OpenParen ~ (NamedType ~ ("," ~ NamedType)* ~ ","?)? ~ CloseParen } -NamedType = { Ident ~ Colon ~ Type } -Type = { - U8Keyword - | S8Keyword - | U16Keyword - | S16Keyword - | U32Keyword - | S32Keyword - | U64Keyword - | S64Keyword - | Float32Keyword - | Float64Keyword - | CharKeyword - | BoolKeyword - | StringKeyword - | Tuple - | List - | Option - | Result - | Borrow - | Ident -} -Tuple = { TupleKeyword ~ OpenAngle ~ Type ~ ("," ~ Type)* ~ CloseAngle } -List = { ListKeyword ~ OpenAngle ~ Type ~ CloseAngle } -Option = { OptionKeyword ~ OpenAngle ~ Type ~ CloseAngle } -Result = { ResultKeyword ~ SpecifiedResult? } -SpecifiedResult = { OpenAngle ~ OmitType ~ ("," ~ Type)? ~ CloseAngle } -OmitType = { Underscore | Type } -Borrow = { BorrowKeyword ~ OpenAngle ~ Ident ~ CloseAngle } - -// Let statement -LetStatement = ${ LetKeyword ~ DelimitingSpace+ ~ Ident ~ DelimitingSpace* ~ Equals ~ DelimitingSpace* ~ Expr ~ DelimitingSpace* ~ Semicolon } - -// Export statement -ExportStatement = ${ ExportKeyword ~ DelimitingSpace+ ~ Expr ~ DelimitingSpace* ~ WithClause? ~ DelimitingSpace* ~ Semicolon } - -// Expressions -// Note: currently there are only two operators (postfix . and postfix []), -// and both have the same precedence, so no climbing is needed. -Expr = !{ PrimaryExpr ~ PostfixExpr* } -PrimaryExpr = { NewExpr | NestedExpr | Ident } -NewExpr = ${ NewKeyword ~ DelimitingSpace+ ~ PackageName ~ DelimitingSpace* ~ NewExprBody } -NewExprBody = !{ OpenBrace ~ (InstantiationArgument ~ ("," ~ InstantiationArgument)* ~ ","?)? ~ Ellipsis? ~ CloseBrace } -InstantiationArgument = { NamedInstantiationArgument | Ident } -NamedInstantiationArgument = { InstantiationArgumentName ~ Colon ~ Expr } -InstantiationArgumentName = { Ident | String } -NestedExpr = { OpenParen ~ Expr ~ CloseParen } -PostfixExpr = { AccessExpr | NamedAccessExpr } -AccessExpr = ${ Dot ~ Ident } -NamedAccessExpr = { OpenBracket ~ String ~ CloseBracket } - -// Identifiers -RawIdent = _{ Percent ~ IdentPart ~ (Hyphen ~ IdentPart)* } -Ident = @{ RawIdent | !(Keyword ~ !Hyphen) ~ IdentPart ~ (Hyphen ~ IdentPart)* } -IdentPart = @{ ASCII_ALPHA_LOWER ~ (ASCII_ALPHA_LOWER | ASCII_DIGIT)* } - -// Strings -String = @{ DoubleQuote ~ (!DoubleQuote ~ ANY)* ~ DoubleQuote } - -// Keywords -ImportKeyword = { "import" } -WithKeyword = { "with" } -TypeKeyword = { "type" } -TupleKeyword = { "tuple" } -ListKeyword = { "list" } -OptionKeyword = { "option" } -ResultKeyword = { "result" } -BorrowKeyword = { "borrow" } -ResourceKeyword = { "resource" } -VariantKeyword = { "variant" } -RecordKeyword = { "record" } -FlagsKeyword = { "flags" } -EnumKeyword = { "enum" } -FuncKeyword = { "func" } -StaticKeyword = { "static" } -ConstructorKeyword = { "constructor" } -U8Keyword = { "u8" } -S8Keyword = { "s8" } -U16Keyword = { "u16" } -S16Keyword = { "s16" } -U32Keyword = { "u32" } -S32Keyword = { "s32" } -U64Keyword = { "u64" } -S64Keyword = { "s64" } -Float32Keyword = { "float32" } -Float64Keyword = { "float64" } -CharKeyword = { "char" } -BoolKeyword = { "bool" } -StringKeyword = { "string" } -InterfaceKeyword = { "interface" } -WorldKeyword = { "world" } -ExportKeyword = { "export" } -NewKeyword = { "new" } -LetKeyword = { "let" } -UseKeyword = { "use" } -IncludeKeyword = { "include" } -AsKeyword = { "as" } -Keyword = _{ - ImportKeyword - | WithKeyword - | TypeKeyword - | TupleKeyword - | ListKeyword - | OptionKeyword - | ResultKeyword - | BorrowKeyword - | ResourceKeyword - | VariantKeyword - | RecordKeyword - | FlagsKeyword - | EnumKeyword - | FuncKeyword - | StaticKeyword - | ConstructorKeyword - | U8Keyword - | S8Keyword - | U16Keyword - | S16Keyword - | U32Keyword - | S32Keyword - | U64Keyword - | S64Keyword - | Float32Keyword - | Float64Keyword - | CharKeyword - | BoolKeyword - | StringKeyword - | InterfaceKeyword - | WorldKeyword - | ExportKeyword - | NewKeyword - | LetKeyword - | UseKeyword - | IncludeKeyword - | AsKeyword -} - -// Comments -DocComment = ${ (" " | "\t")* ~ (("///" ~ (!NEWLINE ~ ANY)* ~ NEWLINE) | ("/**" ~ (!"*/" ~ ANY)* ~ "*/" ~ NEWLINE)) } -LineComment = _{ ("//" ~ !"/" ~ (!NEWLINE ~ ANY)*) } -BlockComment = _{ "/**/" | "/*" ~ !"*" ~ (BlockComment | !"*/" ~ ANY)* ~ "*/" } - -// Symbols -// These exist to for explicit matching, which assists in error reporting -Semicolon = { ";" } -OpenBrace = { "{" } -CloseBrace = { "}" } -Colon = { ":" } -Equals = { "=" } -OpenParen = { "(" } -CloseParen = { ")" } -Arrow = { "->" } -OpenAngle = { "<" } -CloseAngle = { ">" } -Percent = { "%" } -Underscore = { "_" } -Hyphen = { "-" } -DoubleQuote = { "\"" } -Slash = { "/" } -At = { "@" } -OpenBracket = { "[" } -CloseBracket = { "]" } -Dot = { "." } -Ellipsis = { "..." } - -// Special values inserted at every sequence (for non-atomic rules) -WHITESPACE = _{ " " | "\t" | NEWLINE } -COMMENT = _{ LineComment | BlockComment } - -// Used to delimit certain tokens -DelimitingSpace = _{ WHITESPACE | COMMENT } diff --git a/src/bin/wac.rs b/src/bin/wac.rs index ca99359..1b84167 100644 --- a/src/bin/wac.rs +++ b/src/bin/wac.rs @@ -1,6 +1,6 @@ use anyhow::Result; use clap::Parser; -use owo_colors::OwoColorize; +use owo_colors::{OwoColorize, Stream, Style}; use wac::commands::ParseCommand; fn version() -> &'static str { @@ -27,7 +27,12 @@ async fn main() -> Result<()> { if let Err(e) = match Wac::parse() { Wac::Parse(cmd) => cmd.exec().await, } { - eprintln!("{error}: {e:?}", error = "error".red().bold()); + eprintln!( + "{error}: {e:?}", + error = "error".if_supports_color(Stream::Stderr, |text| { + text.style(Style::new().red().bold()) + }) + ); std::process::exit(1); } diff --git a/src/commands/parse.rs b/src/commands/parse.rs index cf1bd8d..6e85c22 100644 --- a/src/commands/parse.rs +++ b/src/commands/parse.rs @@ -1,10 +1,11 @@ -use anyhow::{Context, Result}; +use anyhow::{anyhow, Context, Result}; use clap::Args; use serde::Serialize; -use std::{fs, path::PathBuf}; +use std::{fs, io::IsTerminal, path::PathBuf}; use wac_parser::{ ast::Document, resolution::{FileSystemPackageResolver, ResolvedDocument}, + ErrorFormatter, }; fn parse(s: &str) -> Result<(T, U)> @@ -57,7 +58,12 @@ impl ParseCommand { let contents = fs::read_to_string(&self.path) .with_context(|| format!("failed to read file `{path}`", path = self.path.display()))?; - let document = Document::parse(&contents, &self.path)?; + let document = Document::parse(&contents, &self.path).map_err(|e| { + anyhow!( + "{e}", + e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) + ) + })?; let resolved = ResolvedDocument::new( &document, self.package, @@ -65,7 +71,13 @@ impl ParseCommand { self.deps_dir, self.deps.into_iter().collect(), ))), - )?; + ) + .map_err(|e| { + anyhow!( + "{e}", + e = ErrorFormatter::new(&self.path, e, std::io::stderr().is_terminal()) + ) + })?; serde_json::to_writer_pretty( std::io::stdout(),