├── .gitignore ├── contrib └── vscode │ ├── .gitignore │ ├── .vscodeignore │ ├── README.md │ ├── .vscode │ └── launch.json │ ├── package.json │ ├── language-configuration.json │ ├── syntaxes │ └── wave.tmLanguage.json │ └── LICENSE ├── tests ├── types.wasm ├── ui │ ├── accept-comments.out │ ├── reject-lists.waves │ ├── reject-flags.waves │ ├── reject-comments.out │ ├── reject-flags.out │ ├── reject-enums.waves │ ├── accept-flags.waves │ ├── accept-flags.out │ ├── accept-chars.out │ ├── accept-chars.waves │ ├── reject-comments.waves │ ├── reject-enums.out │ ├── reject-lists.out │ ├── accept-enums.waves │ ├── reject-options.waves │ ├── accept-enums.out │ ├── accept-floats.waves │ ├── reject-options.out │ ├── accept-comments.waves │ ├── reject-floats.waves │ ├── accept-strings.out │ ├── accept-floats.out │ ├── reject-chars.waves │ ├── reject-results.waves │ ├── accept-strings.waves │ ├── accept-records.out │ ├── accept-records.waves │ ├── reject-floats.out │ ├── reject-records.out │ ├── reject-chars.out │ ├── reject-records.waves │ ├── reject-strings.waves │ ├── reject-results.out │ ├── README.md │ ├── reject-strings.out │ └── ui.wit ├── types.wit ├── nan.rs ├── ui.rs └── wasmtime.rs ├── src ├── wasmtime │ ├── mod.rs │ ├── core.rs │ └── component.rs ├── wasm │ ├── func.rs │ ├── mod.rs │ ├── ty.rs │ ├── fmt.rs │ └── val.rs ├── lib.rs ├── value │ ├── func.rs │ ├── tests.rs │ ├── wit.rs │ ├── convert.rs │ └── ty.rs ├── lex.rs ├── strings.rs ├── writer.rs ├── untyped.rs └── ast.rs ├── .github └── workflows │ └── rust.yml ├── Cargo.toml ├── wave_ebnf.md ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /contrib/vscode/.gitignore: -------------------------------------------------------------------------------- 1 | /*.vsix 2 | -------------------------------------------------------------------------------- /contrib/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | -------------------------------------------------------------------------------- /tests/types.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lann/wasm-wave/HEAD/tests/types.wasm -------------------------------------------------------------------------------- /src/wasmtime/mod.rs: -------------------------------------------------------------------------------- 1 | mod component; 2 | mod core; 3 | 4 | pub use component::{get_func_type, FuncType}; 5 | -------------------------------------------------------------------------------- /tests/ui/accept-comments.out: -------------------------------------------------------------------------------- 1 | basic-record(rec: {required: 1, optional: some(1)}) 2 | basic-record(rec: {required: 1, optional: some(1)}) 3 | -------------------------------------------------------------------------------- /tests/ui/reject-lists.waves: -------------------------------------------------------------------------------- 1 | list-strings(["\u{0}",,]); 2 | list-strings([,"\u{0}"]); 3 | list-strings([,]); 4 | list-strings([)); 5 | list-strings((]); 6 | list-strings([}); 7 | list-strings({]); -------------------------------------------------------------------------------- /contrib/vscode/README.md: -------------------------------------------------------------------------------- 1 | # WAVE VS Code Extension 2 | 3 | Basic syntax highlighting only. Contributions very welcome! 4 | 5 | ```console 6 | $ npx vsce package 7 | $ code --install-extension wasm-wave-0.0.1.vsix 8 | ``` 9 | -------------------------------------------------------------------------------- /tests/ui/reject-flags.waves: -------------------------------------------------------------------------------- 1 | // Duplicate flags. 2 | permission-flags({ read, read }); 3 | permission-flags({ write, write }); 4 | permission-flags({ read, write, read }); 5 | // Unrecognized flag. 6 | permission-flags({ read, write, execute }); -------------------------------------------------------------------------------- /tests/ui/reject-comments.out: -------------------------------------------------------------------------------- 1 | unexpected end of input at 32..32 2 | unexpected end of input at 33..33 3 | unexpected token: Colon at 28..29 4 | unexpected token: ParenClose at 39..40 5 | error converting Wasm value: missing field "required" at 13..52 6 | -------------------------------------------------------------------------------- /tests/ui/reject-flags.out: -------------------------------------------------------------------------------- 1 | // Duplicate flags. 2 | duplicate flag: "read" at 25..29 3 | duplicate flag: "write" at 26..31 4 | duplicate flag: "read" at 32..36 5 | // Unrecognized flag. 6 | error converting Wasm value: unknown case "execute" at 17..41 7 | -------------------------------------------------------------------------------- /tests/ui/reject-enums.waves: -------------------------------------------------------------------------------- 1 | keyword-cases-enum(true); 2 | keyword-cases-enum(false); 3 | keyword-cases-enum(some); 4 | keyword-cases-enum(none); 5 | keyword-cases-enum(ok); 6 | keyword-cases-enum(err); 7 | keyword-cases-enum(inf); 8 | keyword-cases-enum(nan); -------------------------------------------------------------------------------- /tests/ui/accept-flags.waves: -------------------------------------------------------------------------------- 1 | permission-flags({}); 2 | permission-flags({ read }); 3 | permission-flags({ read, }); 4 | permission-flags({ write }); 5 | permission-flags({ read, write }); 6 | permission-flags({ write, read }); 7 | permission-flags({ read, write, }); -------------------------------------------------------------------------------- /tests/ui/accept-flags.out: -------------------------------------------------------------------------------- 1 | permission-flags(flgs: {}) 2 | permission-flags(flgs: {read}) 3 | permission-flags(flgs: {read}) 4 | permission-flags(flgs: {write}) 5 | permission-flags(flgs: {read, write}) 6 | permission-flags(flgs: {read, write}) 7 | permission-flags(flgs: {read, write}) 8 | -------------------------------------------------------------------------------- /tests/ui/accept-chars.out: -------------------------------------------------------------------------------- 1 | list-chars(vals: ['W', 'A', 'V', 'E', '🌊']) 2 | list-chars(vals: ['\\', '\'', '\"', '\t', '\n', '\r']) 3 | list-chars(vals: ['\u{0}', '\u{0}']) 4 | list-chars(vals: ['x', 'x', 'x']) 5 | list-chars(vals: ['☃', '☃', '☃']) 6 | list-chars(vals: ['\"', '\"', '\"']) 7 | -------------------------------------------------------------------------------- /tests/ui/accept-chars.waves: -------------------------------------------------------------------------------- 1 | list-chars(['W', 'A', 'V', 'E', '🌊']); 2 | list-chars(['\\', '\'', '\"', '\t', '\n', '\r']); 3 | list-chars(['\u{0}', '\u{000000}']); 4 | list-chars(['x', '\u{78}', '\u{000078}']); 5 | list-chars(['☃', '\u{2603}', '\u{002603}']); 6 | list-chars(['"', '\"', '\u{22}']); -------------------------------------------------------------------------------- /tests/ui/reject-comments.waves: -------------------------------------------------------------------------------- 1 | basic-record(// { required: 1 }); 2 | basic-record({ required: 1 } // ); 3 | basic-record(// { 4 | required: 1, 5 | }); 6 | basic-record( 7 | { 8 | required: 1, 9 | //} 10 | ); 11 | basic-record({ 12 | // required: 1, 13 | optional: none, 14 | }); -------------------------------------------------------------------------------- /tests/ui/reject-enums.out: -------------------------------------------------------------------------------- 1 | invalid value type at 19..23 2 | invalid value type at 19..24 3 | unexpected token: ParenClose at 23..24 4 | invalid value type at 19..23 5 | invalid value type at 19..21 6 | invalid value type at 19..22 7 | invalid value type at 19..22 8 | invalid value type at 19..22 9 | -------------------------------------------------------------------------------- /tests/ui/reject-lists.out: -------------------------------------------------------------------------------- 1 | unexpected token: Comma at 22..23 2 | unexpected token: Comma at 14..15 3 | unexpected token: Comma at 14..15 4 | unexpected token: ParenClose at 14..15 5 | unexpected token: BracketClose at 14..15 6 | unexpected token: BraceClose at 14..15 7 | unexpected token: BracketClose at 14..15 8 | -------------------------------------------------------------------------------- /tests/ui/accept-enums.waves: -------------------------------------------------------------------------------- 1 | hand-enum(left); 2 | hand-enum(%left); 3 | keyword-cases-enum(%true); 4 | keyword-cases-enum(%false); 5 | keyword-cases-enum(%some); 6 | keyword-cases-enum(%none); 7 | keyword-cases-enum(%ok); 8 | keyword-cases-enum(%err); 9 | keyword-cases-enum(%inf); 10 | keyword-cases-enum(%nan); -------------------------------------------------------------------------------- /tests/ui/reject-options.waves: -------------------------------------------------------------------------------- 1 | option-u8(some); 2 | option-u8(some()); 3 | option-u8(some(some)); 4 | option-u8(some(some())); 5 | option-u8(some(some(0))); 6 | option-u8(some(some(some(0)))); 7 | option-u8(none()); 8 | option-u8(some(none())); 9 | option-u8(none(0)); 10 | option-u8(some(none(0))); 11 | option-u8(none(some(0))); -------------------------------------------------------------------------------- /tests/ui/accept-enums.out: -------------------------------------------------------------------------------- 1 | hand-enum(enm: left) 2 | hand-enum(enm: left) 3 | keyword-cases-enum(enm: %true) 4 | keyword-cases-enum(enm: %false) 5 | keyword-cases-enum(enm: %some) 6 | keyword-cases-enum(enm: %none) 7 | keyword-cases-enum(enm: %ok) 8 | keyword-cases-enum(enm: %err) 9 | keyword-cases-enum(enm: %inf) 10 | keyword-cases-enum(enm: %nan) 11 | -------------------------------------------------------------------------------- /tests/ui/accept-floats.waves: -------------------------------------------------------------------------------- 1 | list-float32([0, 0.0, 0e0, 0.0e0, 0e-1, 0e+1, 0.000e100]); 2 | list-float32([-3.1415, -100000, -0.0e-0]); 3 | list-float32([nan, inf, -inf]); 4 | // Largest normal f32 5 | float32(3.4028234664e38); 6 | // Truncated precision 7 | float32(3.4028234664123e38); 8 | // Too large; infinity 9 | float32(3.4028234664e39); 10 | // Smallest positive non-zero f32 11 | float32(1.4012984643e-45); 12 | // Too small; round to zero 13 | float32(1.4012984643e-46); -------------------------------------------------------------------------------- /tests/ui/reject-options.out: -------------------------------------------------------------------------------- 1 | unexpected token: ParenClose at 14..15 2 | unexpected token: ParenClose at 15..16 3 | unexpected token: ParenClose at 19..20 4 | unexpected token: ParenClose at 20..21 5 | invalid value type at 15..22 6 | invalid value type at 15..28 7 | unexpected token: ParenOpen at 14..15 8 | unexpected token: ParenOpen at 19..20 9 | unexpected token: ParenOpen at 14..15 10 | unexpected token: ParenOpen at 19..20 11 | unexpected token: ParenOpen at 14..15 12 | -------------------------------------------------------------------------------- /tests/ui/accept-comments.waves: -------------------------------------------------------------------------------- 1 | basic-record( // comment 2 | { // comment 3 | required: 1, // comment 4 | optional: some( // comment 5 | 1 // comment 6 | ), // comment 7 | } // comment 8 | ) // comment; 9 | basic-record( 10 | // comment 11 | { 12 | // comment 13 | required: 1, 14 | // comment 15 | optional: some(1) 16 | // comment 17 | } 18 | // comment 19 | ); -------------------------------------------------------------------------------- /tests/ui/reject-floats.waves: -------------------------------------------------------------------------------- 1 | // Reject "-nan". 2 | float32(-nan); 3 | float64(-nan); 4 | float32(NaN); 5 | float64(NaN); 6 | // Reject "infinity", "-infinity", and uppercase variations. 7 | float32(infinity); 8 | float64(-infinity); 9 | float32(INFINITY); 10 | float64(-INFINITY); 11 | float32(Infinity); 12 | float64(-Infinity); 13 | float32(Infinity); 14 | float64(-Infinity); 15 | // Reject mixed case variations of "inf" and "nan". 16 | float32(Inf); 17 | float64(-Inf); 18 | float32(INF); 19 | float64(-INF); -------------------------------------------------------------------------------- /tests/ui/accept-strings.out: -------------------------------------------------------------------------------- 1 | string(val: "") 2 | string(val: "WAVE🌊") 3 | string(val: "\\\'\"\t\n\r") 4 | string(val: "\'\'\'") 5 | string(val: "☃☃☃") 6 | // Multiline: empty 7 | string(val: "") 8 | // Multiline: escapes 9 | string(val: "Break up double quote sequences: \"\"\"\"\nOther escapes work: \\\'\"\t\n\r☃") 10 | // Multiline: indent behavior 11 | string(val: " Trailing indent\ndetermines dedent\n behavior.") 12 | // Multiline: empty lines 13 | string(val: "\nEmpty leading and trailing lines are retained\n") 14 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - uses: actions/checkout@v3 18 | 19 | - run: rustup toolchain install stable --profile minimal 20 | 21 | - uses: Swatinem/rust-cache@v2 22 | 23 | - run: cargo clippy 24 | 25 | - run: cargo test 26 | 27 | - run: cargo build --release -------------------------------------------------------------------------------- /tests/ui/accept-floats.out: -------------------------------------------------------------------------------- 1 | list-float32(vals: [0, 0, 0, 0, 0, 0, 0]) 2 | list-float32(vals: [-3.1415, -100000, -0]) 3 | list-float32(vals: [nan, inf, -inf]) 4 | // Largest normal f32 5 | float32(val: 340282350000000000000000000000000000000) 6 | // Truncated precision 7 | float32(val: 340282350000000000000000000000000000000) 8 | // Too large; infinity 9 | float32(val: inf) 10 | // Smallest positive non-zero f32 11 | float32(val: 0.000000000000000000000000000000000000000000001) 12 | // Too small; round to zero 13 | float32(val: 0) 14 | -------------------------------------------------------------------------------- /tests/ui/reject-chars.waves: -------------------------------------------------------------------------------- 1 | // Wrong length 2 | char(''); 3 | char('ab'); 4 | char('☃☃'); 5 | char('\n\n'); 6 | // Invalid escapes 7 | char('\z'); 8 | char('\x1f'); 9 | char('\u1f'); 10 | char('\u{1f'); 11 | char('\u1f}'); 12 | char('\u{}'); 13 | char('\u{x}'); 14 | char('\u{12345678}'); 15 | // Out of range 16 | char('\u{110000}'); 17 | // Unpaired surrogates 18 | char('\u{D800}'); 19 | char('\u{DFFF}'); 20 | // Invalid delimiters 21 | char('); 22 | char(x'); 23 | char('x); 24 | // Missing mandatory escapes 25 | char('\'); 26 | char('''); 27 | char(' 28 | '); -------------------------------------------------------------------------------- /tests/ui/reject-results.waves: -------------------------------------------------------------------------------- 1 | result-ok-u8(ok); 2 | result-ok-u8(ok()); 3 | result-ok-u8(o(0)); 4 | result-ok-u8(err(0)); 5 | result-err-u8(err); 6 | result-err-u8(err()); 7 | result-err-u8(e(0)); 8 | result-err-u8(ok(0)); 9 | result-no-payloads(ok()); 10 | result-no-payloads(o(0)); 11 | result-no-payloads(ok(0)); 12 | result-no-payloads(err()); 13 | result-no-payloads(e(0)); 14 | result-no-payloads(err(0)); 15 | result-both-u8(ok); 16 | result-both-u8(ok()); 17 | result-both-u8(o(0)); 18 | result-both-u8(err); 19 | result-both-u8(err()); 20 | result-both-u8(e(0)); -------------------------------------------------------------------------------- /tests/ui/accept-strings.waves: -------------------------------------------------------------------------------- 1 | string(""); 2 | string("WAVE🌊"); 3 | string("\\\'\"\t\n\r"); 4 | string("'\'\u{27}"); 5 | string("☃\u{2603}\u{002603}"); 6 | // Multiline: empty 7 | string(""" 8 | """); 9 | // Multiline: escapes 10 | string(""" 11 | Break up double quote sequences: ""\"" 12 | Other escapes work: \\\'\"\t\n\r\u{2603} 13 | """); 14 | // Multiline: indent behavior 15 | string(""" 16 | Trailing indent 17 | determines dedent 18 | behavior. 19 | """); 20 | // Multiline: empty lines 21 | string(""" 22 | 23 | Empty leading and trailing lines are retained 24 | 25 | """) -------------------------------------------------------------------------------- /contrib/vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that launches the extension inside a new window 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | { 6 | "version": "0.2.0", 7 | "configurations": [ 8 | { 9 | "name": "Extension", 10 | "type": "extensionHost", 11 | "request": "launch", 12 | "args": [ 13 | "--extensionDevelopmentPath=${workspaceFolder}" 14 | ] 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /tests/ui/accept-records.out: -------------------------------------------------------------------------------- 1 | basic-record(rec: {required: 1}) 2 | basic-record(rec: {required: 1}) 3 | basic-record(rec: {required: 1}) 4 | basic-record(rec: {required: 1}) 5 | basic-record(rec: {required: 1, optional: some(2)}) 6 | basic-record(rec: {required: 1, optional: some(2)}) 7 | optional-fields-record(rec: {:}) 8 | optional-fields-record(rec: {:}) 9 | optional-fields-record(rec: {:}) 10 | optional-fields-record(rec: {:}) 11 | optional-fields-record(rec: {a: some(1)}) 12 | optional-fields-record(rec: {b: some(2)}) 13 | optional-fields-record(rec: {a: some(1), b: some(2)}) 14 | keyword-fields-record(rec: {true: true, false: false, some: some(1), ok: ok, err: err, inf: inf, nan: nan}) 15 | -------------------------------------------------------------------------------- /tests/ui/accept-records.waves: -------------------------------------------------------------------------------- 1 | basic-record({ required: 1 }); 2 | basic-record({ required: 1, optional: none }); 3 | basic-record({ required: 1, optional: none, }); 4 | basic-record({ optional: none, required: 1 }); 5 | basic-record({ required: 1, optional: some(2) }); 6 | basic-record({ optional: some(2), required: 1, }); 7 | optional-fields-record({:}); 8 | optional-fields-record({ : }); 9 | optional-fields-record({a:none,b:none}); 10 | optional-fields-record({b:none,}); 11 | optional-fields-record({a:some(1)}); 12 | optional-fields-record({b:some(2)}); 13 | optional-fields-record({b:some(2),a:some(1)}); 14 | keyword-fields-record({true: true, false: false, some: some(1), none: none, ok: ok, err: err, inf: inf, nan: nan}); -------------------------------------------------------------------------------- /tests/ui/reject-floats.out: -------------------------------------------------------------------------------- 1 | // Reject "-nan". 2 | invalid token at 8..9 3 | invalid token at 8..9 4 | unexpected token: LabelOrKeyword at 9..10 5 | unexpected token: LabelOrKeyword at 9..10 6 | // Reject "infinity", "-infinity", and uppercase variations. 7 | invalid value type at 8..16 8 | unexpected token: LabelOrKeyword at 12..17 9 | invalid value type at 8..16 10 | invalid token at 8..9 11 | unexpected token: LabelOrKeyword at 9..16 12 | invalid token at 8..9 13 | unexpected token: LabelOrKeyword at 9..16 14 | invalid token at 8..9 15 | // Reject mixed case variations of "inf" and "nan". 16 | unexpected token: LabelOrKeyword at 9..11 17 | invalid token at 8..9 18 | invalid value type at 8..11 19 | invalid token at 8..9 20 | -------------------------------------------------------------------------------- /tests/ui/reject-records.out: -------------------------------------------------------------------------------- 1 | // Missing `required`. 2 | error converting Wasm value: missing field "required" at 13..31 3 | error converting Wasm value: missing field "required" at 13..34 4 | // Duplicate `required`. 5 | duplicate field: "required" at 28..36 6 | duplicate field: "required" at 28..36 7 | duplicate field: "required" at 28..36 8 | // Duplicate `optional`. 9 | duplicate field: "optional" at 44..52 10 | duplicate field: "optional" at 44..52 11 | duplicate field: "optional" at 47..55 12 | duplicate field: "optional" at 47..55 13 | // Bad commas. 14 | unexpected token: Comma at 27..28 15 | unexpected token: Comma at 15..16 16 | unexpected token: Comma at 15..16 17 | // Invalid empty record syntax. 18 | invalid value type at 23..25 19 | -------------------------------------------------------------------------------- /contrib/vscode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wasm-wave", 3 | "displayName": "Wave", 4 | "description": "", 5 | "version": "0.0.1", 6 | "engines": { 7 | "vscode": "^1.85.0" 8 | }, 9 | "categories": [ 10 | "Programming Languages" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/lann/wasm-wave" 15 | }, 16 | "contributes": { 17 | "languages": [{ 18 | "id": "wave", 19 | "aliases": ["WAVE", "wave"], 20 | "extensions": [".wave", ".waves"], 21 | "configuration": "./language-configuration.json" 22 | }], 23 | "grammars": [{ 24 | "language": "wave", 25 | "scopeName": "source.wave", 26 | "path": "./syntaxes/wave.tmLanguage.json" 27 | }] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tests/ui/reject-chars.out: -------------------------------------------------------------------------------- 1 | // Wrong length 2 | invalid token at 5..6 3 | invalid token at 5..9 4 | invalid token at 5..9 5 | invalid token at 5..6 6 | // Invalid escapes 7 | invalid token at 5..6 8 | invalid token at 5..6 9 | invalid token at 5..6 10 | invalid token at 5..10 11 | invalid token at 5..6 12 | invalid token at 5..6 13 | invalid token at 5..6 14 | invalid token at 5..10 15 | // Out of range 16 | invalid character escape at 5..17 17 | // Unpaired surrogates 18 | invalid character escape at 5..15 19 | invalid character escape at 5..15 20 | // Invalid delimiters 21 | invalid token at 5..6 22 | invalid token at 6..7 23 | invalid token at 5..7 24 | // Missing mandatory escapes 25 | invalid token at 5..6 26 | invalid token at 5..6 27 | invalid token at 5..6 28 | -------------------------------------------------------------------------------- /contrib/vscode/language-configuration.json: -------------------------------------------------------------------------------- 1 | { 2 | "comments": { 3 | // symbol used for single line comment. Remove this entry if your language does not support line comments 4 | "lineComment": "//" 5 | }, 6 | // symbols used as brackets 7 | "brackets": [ 8 | ["{", "}"], 9 | ["[", "]"], 10 | ["(", ")"] 11 | ], 12 | // symbols that are auto closed when typing 13 | "autoClosingPairs": [ 14 | ["{", "}"], 15 | ["[", "]"], 16 | ["(", ")"], 17 | ["\"", "\""], 18 | ["'", "'"] 19 | ], 20 | // symbols that can be used to surround a selection 21 | "surroundingPairs": [ 22 | ["{", "}"], 23 | ["[", "]"], 24 | ["(", ")"], 25 | ["\"", "\""], 26 | ["'", "'"] 27 | ] 28 | } -------------------------------------------------------------------------------- /tests/ui/reject-records.waves: -------------------------------------------------------------------------------- 1 | // Missing `required`. 2 | basic-record({ optional: none }); 3 | basic-record({ optional: some(0) }); 4 | // Duplicate `required`. 5 | basic-record({ required: 0, required: 0 }); 6 | basic-record({ required: 0, required: 0, optional: none }); 7 | basic-record({ required: 0, required: 0, optional: some(0) }); 8 | // Duplicate `optional`. 9 | basic-record({ required: 0, optional: none, optional: none }); 10 | basic-record({ required: 0, optional: none, optional: some(0) }); 11 | basic-record({ required: 0, optional: some(0), optional: none }); 12 | basic-record({ required: 0, optional: some(0), optional: some(0) }); 13 | // Bad commas. 14 | basic-record({ required: 0,, }); 15 | basic-record({ , required: 0, }); 16 | basic-record({ ,, required: 0 }); 17 | // Invalid empty record syntax. 18 | optional-fields-record({}); -------------------------------------------------------------------------------- /tests/ui/reject-strings.waves: -------------------------------------------------------------------------------- 1 | // Reject surrogates. 2 | string("\u{d800}"); 3 | string("\u{dbff}"); 4 | string("\u{dc00}"); 5 | string("\u{dcff}"); 6 | string("\u{d800}\\u{dc00}"); 7 | // Reject invalid values. 8 | string("\u{110000}"); 9 | string("\u{ffffffff}"); 10 | string("\u{80000000}"); 11 | // Reject invalid syntax. 12 | string("\u{-1}"); 13 | string("\u{+1}"); 14 | // Missing mandatory escapes 15 | string("""); 16 | string("\"); 17 | string(" 18 | "); 19 | // Multiline missing mandatory line breaks 20 | string(""""""); 21 | string(""" """); 22 | // Multiline invalid delimiters 23 | string("""" 24 | """); 25 | string(""" 26 | ""); 27 | string(""" 28 | """"); 29 | string("""extra 30 | """); 31 | string(""" 32 | extra"""); 33 | // Multiline double quote triplet in content 34 | string(""" 35 | """ 36 | """); 37 | string(""" 38 | Between """ words 39 | """); 40 | // Multiline invalid escapes 41 | string(""" 42 | Invalid surrogate: \u{d800} 43 | """); 44 | string(""" 45 | Invalid escape: \j 46 | """); -------------------------------------------------------------------------------- /tests/ui/reject-results.out: -------------------------------------------------------------------------------- 1 | error converting Wasm value: missing payload for "ok" case at 13..15 2 | unexpected token: ParenClose at 16..17 3 | invalid value type at 13..17 4 | error converting Wasm value: unexpected payload for "err" case at 13..19 5 | error converting Wasm value: missing payload for "err" case at 14..17 6 | unexpected token: ParenClose at 18..19 7 | invalid value type at 14..18 8 | error converting Wasm value: unexpected payload for "ok" case at 14..19 9 | unexpected token: ParenClose at 22..23 10 | invalid value type at 19..23 11 | error converting Wasm value: unexpected payload for "ok" case at 19..24 12 | unexpected token: ParenClose at 23..24 13 | invalid value type at 19..23 14 | error converting Wasm value: unexpected payload for "err" case at 19..25 15 | error converting Wasm value: missing payload for "ok" case at 15..17 16 | unexpected token: ParenClose at 18..19 17 | invalid value type at 15..19 18 | error converting Wasm value: missing payload for "err" case at 15..18 19 | unexpected token: ParenClose at 19..20 20 | invalid value type at 15..19 21 | -------------------------------------------------------------------------------- /tests/ui/README.md: -------------------------------------------------------------------------------- 1 | # UI ("golden files") Tests 2 | 3 | Each `.waves` (WAVE Script) file in this directory contains a set of test inputs; corresponding outputs are in the matching `.out` file (e.g. `test.wave` → `test.out`). 4 | 5 | Each test input looks like `(, ...);\n` where `` is a function defined in the `ui.wit` file. For each input the WAVE value arguments are parsed and type-checked against the function parameter types. The result of this parsing - either a re-encoded copy of the input or an error - is written to the output. 6 | 7 | Note that each test input must end with `;\n`. This is not part of WAVE itself; inputs are split on this exact substring so it may not appear anywhere else in inputs. 8 | 9 | Comments at the start of a test case are copied into the output. 10 | 11 | ## Updating Outputs 12 | 13 | By default, running the `ui` tests will check outputs against the contents of the existing `.out` files. When updating tests, the `.out` files can be overwritten by setting the environment variable `BLESS=1` when running the tests. Updated outputs should be committed and reviewed. -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-wave" 3 | version = "0.6.0" 4 | authors = ["lann.martin@fermyon.com"] 5 | description = "Web Assembly Value Encoding" 6 | license = "Apache-2.0" 7 | documentation = "https://docs.rs/wasm-wave" 8 | categories = ["wasm", "encoding", "parser-implementations"] 9 | repository = "https://github.com/lann/wasm-wave" 10 | readme = "README.md" 11 | edition = "2021" 12 | 13 | [features] 14 | default = ["wasmtime", "wit"] 15 | wasmtime = ["dep:wasmtime"] 16 | wit = ["dep:wit-parser"] 17 | 18 | [dependencies] 19 | indexmap = "2.0.0" 20 | logos = "0.14.0" 21 | thiserror = "1.0.48" 22 | wasmtime = { workspace = true, optional = true } 23 | wit-parser = { workspace = true, optional = true } 24 | 25 | [[test]] 26 | name = "ui" 27 | path = "tests/ui.rs" 28 | harness = false 29 | 30 | [dev-dependencies] 31 | snapbox = { version = "0.5.6", features = ["harness"] } 32 | wasmtime = { workspace = true, optional = false, features = ["cranelift"] } 33 | 34 | [workspace.dependencies] 35 | wasmtime = { version = "21.0.1", default-features = false, features = ["component-model", "runtime"] } 36 | wit-parser = "0.208.1" 37 | -------------------------------------------------------------------------------- /src/wasm/func.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use crate::wasm::WasmType; 4 | 5 | /// The WasmFunc trait may be implemented to represent Wasm func type 6 | /// signatures to be (de)serialized with WAVE. 7 | pub trait WasmFunc { 8 | /// A type representing types of these params and results. 9 | type Type: WasmType; 10 | 11 | /// Returns an iterator of the func's parameter types. 12 | fn params(&self) -> Box + '_>; 13 | 14 | /// Returns an iterator of the func's parameter names. Must be the same 15 | /// length as the iterator returned by `params` or empty if this WasmFunc 16 | /// impl does not support param names. 17 | fn param_names(&self) -> Box> + '_> { 18 | Box::new(std::iter::empty()) 19 | } 20 | 21 | /// Returns an iterator of the func's result types. 22 | fn results(&self) -> Box + '_>; 23 | 24 | /// Returns an iterator of the func's result names. Must be the same 25 | /// length as the iterator returned by `results` or empty if there are no 26 | /// named results or if this WasmFunc impl does not support result names. 27 | fn result_names(&self) -> Box> + '_> { 28 | Box::new(std::iter::empty()) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/ui/reject-strings.out: -------------------------------------------------------------------------------- 1 | // Reject surrogates. 2 | invalid character escape at 8..9 3 | invalid character escape at 8..9 4 | invalid character escape at 8..9 5 | invalid character escape at 8..9 6 | invalid character escape at 8..9 7 | // Reject invalid values. 8 | invalid character escape at 8..9 9 | invalid character escape at 8..9 10 | invalid character escape at 8..9 11 | // Reject invalid syntax. 12 | invalid token at 7..8 13 | invalid token at 7..8 14 | // Missing mandatory escapes 15 | invalid token at 7..10 16 | invalid token at 7..11 17 | invalid token at 7..8 18 | // Multiline missing mandatory line breaks 19 | invalid multiline string: opening """ must be followed immediately by newline at 10..11 20 | invalid multiline string: opening """ must be followed immediately by newline at 10..11 21 | // Multiline invalid delimiters 22 | invalid multiline string: opening """ must be followed immediately by newline at 10..11 23 | invalid token at 7..10 24 | invalid token at 14..16 25 | invalid multiline string: opening """ must be followed immediately by newline at 10..11 26 | invalid multiline string: closing """ must be on a line preceded only by spaces at 13..16 27 | // Multiline double quote triplet in content 28 | invalid token at 19..22 29 | unexpected token: LabelOrKeyword at 25..30 30 | // Multiline invalid escapes 31 | invalid character escape at 30..31 32 | invalid character escape at 27..28 33 | -------------------------------------------------------------------------------- /tests/types.wit: -------------------------------------------------------------------------------- 1 | // Regenerate types.wasm: 2 | // wasm-tools component embed --dummy types.wit | wasm-tools component new -o types.wasm 3 | 4 | package tests:tests 5 | 6 | world tests { 7 | export bools: func() -> tuple 8 | export sints: func() -> tuple 9 | export uints: func() -> tuple 10 | export floats: func() -> tuple 11 | export options: func() -> tuple, option>> 12 | export list-chars: func() -> list 13 | export list-strings: func() -> list 14 | export result-ok-only: func() -> result 15 | export result-err-only: func() -> result<_, s8> 16 | export result-no-payloads: func() -> result 17 | export result-both-payloads: func() -> result 18 | 19 | record record-type { 20 | required: u8, 21 | optional: option, 22 | } 23 | export %record: func() -> record-type 24 | 25 | variant variant-type { 26 | without-payload, 27 | with-payload(u8), 28 | } 29 | export %variant: func() -> variant-type 30 | 31 | enum enum-type { 32 | first, 33 | second, 34 | } 35 | export %enum: func() -> enum-type 36 | 37 | flags flags-type { 38 | read, 39 | write, 40 | } 41 | export %flags: func() -> flags-type 42 | 43 | export func-type: func(a: bool, b: enum-type) -> result 44 | } -------------------------------------------------------------------------------- /tests/ui/ui.wit: -------------------------------------------------------------------------------- 1 | package ui:tests; 2 | 3 | interface ui { 4 | %float32: func(val: f32); 5 | %list-float32: func(vals: list); 6 | %float64: func(val: f64); 7 | %list-float64: func(vals: list); 8 | %char: func(val: char); 9 | list-chars: func(vals: list); 10 | %string: func(val: string); 11 | list-strings: func(vals: list); 12 | option-u8: func(opt: option); 13 | result-ok-u8: func(res: result); 14 | result-err-u8: func(res: result<_, u8>); 15 | result-both-u8: func(res: result); 16 | result-no-payloads: func(res: result); 17 | 18 | record basic { 19 | required: u8, 20 | optional: option, 21 | } 22 | basic-record: func(rec: basic); 23 | 24 | record optional-fields { 25 | a: option, 26 | b: option, 27 | } 28 | optional-fields-record: func(rec: optional-fields); 29 | 30 | record keyword-fields { 31 | true: bool, 32 | false: bool, 33 | some: option, 34 | none: option, 35 | ok: result, 36 | err: result, 37 | inf: f32, 38 | nan: f32, 39 | } 40 | keyword-fields-record: func(rec: keyword-fields); 41 | 42 | enum hand { 43 | left, 44 | right, 45 | } 46 | hand-enum: func(enm: hand); 47 | 48 | enum keyword-cases { 49 | true, 50 | false, 51 | some, 52 | none, 53 | ok, 54 | err, 55 | inf, 56 | nan, 57 | } 58 | keyword-cases-enum: func(enm: keyword-cases); 59 | 60 | flags permissions { 61 | read, 62 | write, 63 | } 64 | permission-flags: func(flgs: permissions); 65 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Web Assembly Value Encoding 2 | //! 3 | //! WAVE is a human-oriented text encoding of WebAssembly Component Model values. 4 | //! 5 | //! For more information, see the [README](https://github.com/lann/wasm-wave#readme). 6 | #![deny(missing_docs)] 7 | 8 | pub mod ast; 9 | pub mod lex; 10 | pub mod parser; 11 | mod strings; 12 | pub mod untyped; 13 | pub mod value; 14 | pub mod wasm; 15 | pub mod writer; 16 | 17 | #[cfg(feature = "wasmtime")] 18 | /// Implementations for [`wasmtime`] types. 19 | pub mod wasmtime; 20 | 21 | use parser::Parser; 22 | use wasm::WasmValue; 23 | use writer::Writer; 24 | 25 | /// Parses a [`WasmValue`] from the given WAVE-encoded string. 26 | /// ``` 27 | /// use wasmtime::component::{Type, Val}; 28 | /// # fn main() -> Result<(), wasm_wave::parser::ParserError> { 29 | /// let val: Val = wasm_wave::from_str(&Type::Char, r"'\u{1F44B}'")?; 30 | /// assert_eq!(val, Val::Char('👋')); 31 | /// # Ok(()) 32 | /// # } 33 | /// ``` 34 | pub fn from_str(ty: &V::Type, s: &str) -> Result { 35 | let mut parser = Parser::new(s); 36 | 37 | let value = parser.parse_value(ty)?; 38 | 39 | // Ensure that we've parsed the entire string. 40 | parser.finish()?; 41 | 42 | Ok(value) 43 | } 44 | 45 | /// Returns the given [`WasmValue`] as a WAVE-encoded string. 46 | /// ``` 47 | /// use wasmtime::component::Val; 48 | /// # fn main() -> Result<(), wasm_wave::writer::WriterError> { 49 | /// let wave_str = wasm_wave::to_string(&Val::Char('\u{1F44B}'))?; 50 | /// assert_eq!(wave_str, "'👋'"); 51 | /// # Ok(()) 52 | /// # } 53 | pub fn to_string(val: &impl WasmValue) -> Result { 54 | let mut buf = vec![]; 55 | Writer::new(&mut buf).write_value(val)?; 56 | Ok(String::from_utf8(buf).unwrap_or_else(|err| panic!("invalid UTF-8: {err:?}"))) 57 | } 58 | 59 | fn canonicalize_nan32(val: f32) -> f32 { 60 | if val.is_nan() { 61 | f32::from_bits(0x7fc00000) 62 | } else { 63 | val 64 | } 65 | } 66 | 67 | fn canonicalize_nan64(val: f64) -> f64 { 68 | if val.is_nan() { 69 | f64::from_bits(0x7ff8000000000000) 70 | } else { 71 | val 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /contrib/vscode/syntaxes/wave.tmLanguage.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", 3 | "name": "WAVE", 4 | "patterns": [ 5 | { "include": "#comment" }, 6 | { "include": "#raw-keyword"}, 7 | { "include": "#number" }, 8 | { "include": "#keyword-constant" }, 9 | { "include": "#keyword-label" }, 10 | { "include": "#label" }, 11 | { "include": "#char" }, 12 | { "include": "#string" } 13 | ], 14 | "repository": { 15 | "comment": { 16 | "patterns": [{ 17 | "name": "comment.line.double-slash.wave", 18 | "match": "(//)[^\\n]*", 19 | "captures": { 20 | "1": { 21 | "name": "punctuation.definition.comment.wave" 22 | } 23 | } 24 | }] 25 | }, 26 | "raw-keyword": { 27 | "patterns": [{ 28 | "name": "variable.other.label.wave", 29 | "match": "%(true|false|some|none|ok|err|inf|nan)\\b" 30 | }] 31 | }, 32 | "number": { 33 | "patterns": [ 34 | { 35 | "name": "constant.numeric.wave", 36 | "match": "-?(0|([1-9][0-9]*))(\\.[0-9]+)?([eE][-+]?[0-9]+)?" 37 | }, 38 | { 39 | "name": "constant.numeric.wave", 40 | "match": "\\b(nan|inf|-inf)\\b" 41 | } 42 | ] 43 | }, 44 | "keyword-constant": { 45 | "patterns": [{ 46 | "name": "constant.language.wave", 47 | "match": "\\b(true|false)\\b" 48 | }] 49 | }, 50 | "keyword-label": { 51 | "patterns": [{ 52 | "name": "variable.language.label.wave", 53 | "match": "\\b(some|none|ok|err)\\b" 54 | }] 55 | }, 56 | "label": { 57 | "patterns": [{ 58 | "name": "variable.other.label.wave", 59 | "match": "\\b%?([a-z][a-z0-9]*|[A-Z][A-Z0-9]*)(-([a-z][a-z0-9]*|[A-Z][A-Z0-9]*))*\\b" 60 | }] 61 | }, 62 | "char": { 63 | "name": "constant.character.wave", 64 | "begin": "'", 65 | "end": "'", 66 | "patterns": [{ "include": "#char-escape" }] 67 | }, 68 | "string": { 69 | "name": "string.quoted.double.wave", 70 | "begin": "\"", 71 | "end": "\"", 72 | "patterns": [{ "include": "#char-escape"}] 73 | }, 74 | "char-escape": { 75 | "name": "constant.character.escape.wave", 76 | "match": "\\\\(['\"tnr\\\\]|u\\{[0-9a-fA-F]+\\})" 77 | } 78 | }, 79 | "scopeName": "source.wave" 80 | } -------------------------------------------------------------------------------- /src/value/func.rs: -------------------------------------------------------------------------------- 1 | use crate::wasm::WasmFunc; 2 | 3 | use super::{Type, WasmValueError}; 4 | 5 | /// A FuncType represents the parameter and result type(s) of a Wasm func. 6 | #[derive(Clone, PartialEq)] 7 | pub struct FuncType { 8 | params: Vec<(String, Type)>, 9 | results: Vec<(String, Type)>, 10 | } 11 | 12 | impl FuncType { 13 | /// Returns a new FuncType with the given param and result type(s). 14 | /// Returns an error if the resulting func type would be invalid in the 15 | /// component model, e.g. has any unnamed results with more than one 16 | /// result type. 17 | pub fn new( 18 | params: impl Into>, 19 | results: impl Into>, 20 | ) -> Result { 21 | let params = params.into(); 22 | if params.iter().any(|(name, _)| name.is_empty()) { 23 | return Err(WasmValueError::Other("func params must be named".into())); 24 | } 25 | let results = results.into(); 26 | if results.len() > 1 && results.iter().any(|(name, _)| name.is_empty()) { 27 | return Err(WasmValueError::Other( 28 | "funcs with more than one result must have all results named".into(), 29 | )); 30 | } 31 | Ok(Self { params, results }) 32 | } 33 | } 34 | 35 | impl WasmFunc for FuncType { 36 | type Type = Type; 37 | 38 | fn params(&self) -> Box + '_> { 39 | Box::new(self.params.iter().map(|(_, ty)| ty.clone())) 40 | } 41 | 42 | fn param_names(&self) -> Box> + '_> { 43 | Box::new(self.params.iter().map(|(name, _)| name.into())) 44 | } 45 | 46 | fn results(&self) -> Box + '_> { 47 | Box::new(self.results.iter().map(|(_, ty)| ty.clone())) 48 | } 49 | 50 | fn result_names(&self) -> Box> + '_> { 51 | Box::new( 52 | self.results 53 | .iter() 54 | .flat_map(|(name, _)| (!name.is_empty()).then_some(name.into())), 55 | ) 56 | } 57 | } 58 | 59 | impl std::fmt::Display for FuncType { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | crate::wasm::DisplayFunc(self.clone()).fmt(f) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /wave_ebnf.md: -------------------------------------------------------------------------------- 1 | # WAVE EBNF 2 | 3 | A WAVE value is defined by the `value` rule below. Many applications may allow 4 | whitespace around the value, equivalent to the `value-ws` rule. 5 | 6 | > Note that Bool, Variant, Enum, Option and Result values are combined under 7 | > the `variant-case` rule because these cannot be distinguished without type 8 | > information. 9 | 10 | ```ebnf 11 | value ::= number 12 | | char 13 | | string 14 | | variant-case 15 | | tuple 16 | | list 17 | | flags 18 | | record 19 | 20 | value-ws ::= ws value ws 21 | ws ::= ([ \t\n\r]* comment?)* 22 | comment ::= '//' [^\n]* 23 | 24 | number ::= number_finite 25 | | 'nan' 26 | | 'inf' 27 | | '-inf' 28 | number_finite ::= integer number-fraction? number-exponent? 29 | integer ::= unsigned-integer 30 | | '-' unsigned-integer 31 | unsigned-integer ::= '0' 32 | | [1-9] [0-9]* 33 | number-fraction ::= '.' [0-9]+ 34 | number-exponent ::= [eE] [+-]? unsigned-integer 35 | 36 | char ::= ['] char-char ['] 37 | char-char ::= common-char | '"' 38 | 39 | string ::= '"' string-char* '"' 40 | string-char ::= common-char | ['] 41 | 42 | multiline-string ::= '"""' line-break multiline-string-line* [ ]* '"""' 43 | multiline-string-line ::= [ ]* multiline-string-char* line-break 44 | multiline-string-char ::= common-char | ['"] 45 | 46 | line-break ::= '\r\n' | '\n' 47 | 48 | common-char ::= 49 | | '\' escape 50 | escape ::= ['"tnr\\] | escape-unicode 51 | escape-unicode ::= 'u{' [0-9a-fA-F]+ '}' 52 | 53 | variant-case ::= label ws variant-case-payload? 54 | variant-case-payload ::= '(' value-ws ')' 55 | 56 | tuple ::= '(' values-seq ','? ws ')' 57 | 58 | list ::= '[' ws ']' 59 | | '[' values-seq ','? ws ']' 60 | 61 | values-seq ::= value-ws 62 | | values ',' values-ws 63 | 64 | flags ::= '{' ws '}' 65 | | '{' flags-seq ','? ws '}' 66 | flags-seq ::= ws label ws 67 | | flags-seq ',' label 68 | 69 | record ::= '{' ws ':' ws '}' 70 | | '{' record-fields ','? ws '}' 71 | record-fields ::= ws record-field ws 72 | | record-fields ',' record-field 73 | record-field ::= label ws ':' ws value 74 | 75 | label ::= '%'? inner-label 76 | inner-label ::= word 77 | | inner-label '-' word 78 | word ::= [a-z][a-z0-9]* 79 | | [A-Z][A-Z0-9]* 80 | ``` 81 | 82 | * "`Unicode scalar value`" is defined by Unicode 83 | * `escape-unicode` must identify a valid Unicode scalar value. 84 | * `multiline-string-line` must not contain `"""` -------------------------------------------------------------------------------- /tests/nan.rs: -------------------------------------------------------------------------------- 1 | //! Test that NaN bitpatterns are not propagated through Wave values. 2 | //! 3 | //! The component-model floating-point types only have a single NaN value, to 4 | //! make it easier to exchange values with source languages and protocols where 5 | //! there is only one NaN value. To help users avoid depending on NaN bits being 6 | //! propagated, we canonicalize NaNs. 7 | 8 | use std::{f32, f64}; 9 | 10 | use wasm_wave::wasm::WasmValue; 11 | 12 | #[test] 13 | fn nan() { 14 | for bits in [ 15 | 0, 16 | i32::MIN as u32, 17 | 1.0_f32.to_bits(), 18 | (-f32::consts::TAU).to_bits(), 19 | 0xffffffff, 20 | 0x7fff0f0f, 21 | 0x8f800000, 22 | f32::NAN.to_bits(), 23 | ] { 24 | let val = f32::from_bits(bits); 25 | let expected = if val.is_nan() { 0x7fc00000 } else { bits }; 26 | 27 | { 28 | use wasm_wave::value::Value; 29 | assert_eq!( 30 | Value::make_float32(val).unwrap_float32().to_bits(), 31 | expected 32 | ); 33 | } 34 | 35 | #[cfg(feature = "wasmtime")] 36 | { 37 | use wasmtime::component::Val; 38 | 39 | let v = Val::make_float32(val); 40 | match v { 41 | Val::Float32(val) => assert_eq!(val.to_bits(), expected), 42 | _ => unreachable!(), 43 | } 44 | 45 | assert_eq!(Val::Float32(val).unwrap_float32().to_bits(), expected); 46 | } 47 | } 48 | 49 | for bits in [ 50 | 0, 51 | i64::MIN as u64, 52 | 1.0_f64.to_bits(), 53 | (-f64::consts::TAU).to_bits(), 54 | 0xffffffffffffffff, 55 | 0x7fff0f0f0f0f0f0f, 56 | 0x8ff0000000000000, 57 | f64::NAN.to_bits(), 58 | ] { 59 | let val = f64::from_bits(bits); 60 | let expected = if val.is_nan() { 61 | 0x7ff8000000000000 62 | } else { 63 | bits 64 | }; 65 | 66 | { 67 | use wasm_wave::value::Value; 68 | assert_eq!( 69 | Value::make_float64(val).unwrap_float64().to_bits(), 70 | expected 71 | ); 72 | } 73 | 74 | #[cfg(feature = "wasmtime")] 75 | { 76 | use wasmtime::component::Val; 77 | 78 | let v = Val::make_float64(val); 79 | match v { 80 | Val::Float64(val) => assert_eq!(val.to_bits(), expected), 81 | _ => unreachable!(), 82 | } 83 | 84 | assert_eq!(Val::Float64(val).unwrap_float64().to_bits(), expected); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/wasm/mod.rs: -------------------------------------------------------------------------------- 1 | //! Wasm type and value types 2 | 3 | mod fmt; 4 | mod func; 5 | mod ty; 6 | mod val; 7 | 8 | pub use fmt::{DisplayFunc, DisplayFuncArgs, DisplayFuncResults, DisplayType, DisplayValue}; 9 | pub use func::WasmFunc; 10 | pub use ty::{WasmType, WasmTypeKind}; 11 | pub use val::WasmValue; 12 | 13 | pub(crate) use ty::maybe_unwrap_type; 14 | pub(crate) use val::unwrap_2val; 15 | pub(crate) use val::unwrap_val; 16 | 17 | /// Returns an error if the given [`WasmType`] is not of the given [`WasmTypeKind`]. 18 | pub fn ensure_type_kind(ty: &impl WasmType, kind: WasmTypeKind) -> Result<(), WasmValueError> { 19 | if ty.kind() == kind { 20 | Ok(()) 21 | } else { 22 | Err(WasmValueError::WrongTypeKind { 23 | ty: DisplayType(ty).to_string(), 24 | kind, 25 | }) 26 | } 27 | } 28 | 29 | /// An error from creating a [`WasmValue`]. 30 | #[derive(Debug)] 31 | #[allow(missing_docs)] 32 | pub enum WasmValueError { 33 | MissingField(String), 34 | MissingPayload(String), 35 | UnexpectedPayload(String), 36 | UnknownCase(String), 37 | UnknownField(String), 38 | UnsupportedType(String), 39 | WrongNumberOfTupleValues { want: usize, got: usize }, 40 | WrongTypeKind { kind: WasmTypeKind, ty: String }, 41 | WrongValueType { ty: String, val: String }, 42 | Other(String), 43 | } 44 | 45 | impl WasmValueError { 46 | pub(crate) fn wrong_value_type(ty: &impl WasmType, val: &impl WasmValue) -> Self { 47 | Self::WrongValueType { 48 | ty: DisplayType(ty).to_string(), 49 | val: DisplayValue(val).to_string(), 50 | } 51 | } 52 | 53 | pub(crate) fn other(msg: impl std::fmt::Display) -> Self { 54 | Self::Other(msg.to_string()) 55 | } 56 | } 57 | 58 | impl std::fmt::Display for WasmValueError { 59 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 60 | match self { 61 | Self::MissingField(name) => { 62 | write!(f, "missing field {name:?}") 63 | } 64 | Self::MissingPayload(name) => write!(f, "missing payload for {name:?} case"), 65 | Self::UnexpectedPayload(name) => write!(f, "unexpected payload for {name:?} case"), 66 | Self::UnknownCase(name) => write!(f, "unknown case {name:?}"), 67 | Self::UnknownField(name) => write!(f, "unknown field {name:?}"), 68 | Self::UnsupportedType(ty) => write!(f, "unsupported type {ty}"), 69 | Self::WrongNumberOfTupleValues { want, got } => { 70 | write!(f, "expected {want} tuple elements; got {got}") 71 | } 72 | Self::WrongTypeKind { kind, ty } => { 73 | write!(f, "expected a {kind}; got {ty}") 74 | } 75 | Self::WrongValueType { ty, val } => { 76 | write!(f, "expected a {ty}; got {val}") 77 | } 78 | Self::Other(msg) => write!(f, "{msg}"), 79 | } 80 | } 81 | } 82 | 83 | impl std::error::Error for WasmValueError {} 84 | -------------------------------------------------------------------------------- /src/lex.rs: -------------------------------------------------------------------------------- 1 | //! Lexing types 2 | 3 | use std::fmt::Display; 4 | 5 | pub use logos::Span; 6 | 7 | /// A WAVE `logos::Lexer` 8 | pub type Lexer<'source> = logos::Lexer<'source, Token>; 9 | 10 | /// A WAVE token 11 | #[derive(Clone, Copy, Debug, PartialEq, Eq, logos::Logos)] 12 | #[logos(error = Option)] 13 | #[logos(skip r"[ \t\n\r]+")] 14 | #[logos(skip r"//[^\n]*")] 15 | #[logos(subpattern label_word = r"[a-z][a-z0-9]*|[A-Z][A-Z0-9]*")] 16 | #[logos(subpattern char_escape = r#"\\['"tnr\\]|\\u\{[0-9a-fA-F]{1,6}\}"#)] 17 | pub enum Token { 18 | /// The `{` symbol 19 | #[token("{")] 20 | BraceOpen, 21 | /// The `}` symbol 22 | #[token("}")] 23 | BraceClose, 24 | 25 | /// The `(` symbol 26 | #[token("(")] 27 | ParenOpen, 28 | /// The `)` symbol 29 | #[token(")")] 30 | ParenClose, 31 | 32 | /// The `[` symbol 33 | #[token("[")] 34 | BracketOpen, 35 | /// The `]` symbol 36 | #[token("]")] 37 | BracketClose, 38 | 39 | /// The `:` symbol 40 | #[token(":")] 41 | Colon, 42 | 43 | /// The `,` symbol 44 | #[token(",")] 45 | Comma, 46 | 47 | /// A number literal 48 | #[regex(r"-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?")] 49 | #[token("-inf")] 50 | Number, 51 | 52 | /// A label or keyword 53 | #[regex(r"%?(?&label_word)(-(?&label_word))*")] 54 | LabelOrKeyword, 55 | 56 | /// A char literal 57 | #[regex(r#"'([^\\'\n]{1,4}|(?&char_escape))'"#, validate_char)] 58 | Char, 59 | 60 | /// A string literal 61 | #[regex(r#""([^\\"\n]|(?&char_escape))*""#)] 62 | String, 63 | 64 | /// A multi-line string literal 65 | #[token(r#"""""#, lex_multiline_string)] 66 | MultilineString, 67 | } 68 | 69 | impl Display for Token { 70 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | write!(f, "{self:?}") 72 | } 73 | } 74 | 75 | fn validate_char(lex: &mut Lexer) -> Result<(), Option> { 76 | let s = &lex.slice()[1..lex.slice().len() - 1]; 77 | if s.starts_with('\\') || s.chars().count() == 1 { 78 | Ok(()) 79 | } else { 80 | Err(Some(lex.span())) 81 | } 82 | } 83 | 84 | fn lex_multiline_string(lex: &mut Lexer) -> bool { 85 | if let Some(end) = lex.remainder().find(r#"""""#) { 86 | lex.bump(end + 3); 87 | true 88 | } else { 89 | false 90 | } 91 | } 92 | 93 | /// A WAVE keyword 94 | #[derive(Clone, Copy, Debug, PartialEq)] 95 | #[allow(missing_docs)] 96 | pub enum Keyword { 97 | True, 98 | False, 99 | Some, 100 | None, 101 | Ok, 102 | Err, 103 | Inf, 104 | Nan, 105 | } 106 | 107 | impl Keyword { 108 | /// Returns any keyword exactly matching the given string. 109 | pub fn decode(raw_label: &str) -> Option { 110 | Some(match raw_label { 111 | "true" => Self::True, 112 | "false" => Self::False, 113 | "some" => Self::Some, 114 | "none" => Self::None, 115 | "ok" => Self::Ok, 116 | "err" => Self::Err, 117 | "inf" => Self::Inf, 118 | "nan" => Self::Nan, 119 | _ => return None, 120 | }) 121 | } 122 | } 123 | 124 | impl Display for Keyword { 125 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 126 | f.write_str(match self { 127 | Keyword::True => "true", 128 | Keyword::False => "false", 129 | Keyword::Some => "some", 130 | Keyword::None => "none", 131 | Keyword::Ok => "ok", 132 | Keyword::Err => "err", 133 | Keyword::Inf => "inf", 134 | Keyword::Nan => "nan", 135 | }) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /tests/ui.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashMap, 3 | fmt::Write, 4 | path::{Path, PathBuf}, 5 | sync::OnceLock, 6 | }; 7 | 8 | use snapbox::harness::{Case, Harness}; 9 | use wasm_wave::{ 10 | parser::ParserError, 11 | untyped::UntypedFuncCall, 12 | value::{resolve_wit_func_type, FuncType, Value}, 13 | wasm::{DisplayValue, WasmFunc}, 14 | }; 15 | use wit_parser::{Resolve, UnresolvedPackage}; 16 | 17 | fn setup(path: PathBuf) -> Case { 18 | let name = format!("ui::{}", path.file_stem().unwrap().to_string_lossy()); 19 | let expected = path.with_extension("out"); 20 | Case { 21 | name, 22 | fixture: path, 23 | expected, 24 | } 25 | } 26 | 27 | fn test(path: &Path) -> Result> { 28 | let filename = path.file_name().unwrap().to_string_lossy(); 29 | let inputs = std::fs::read_to_string(path)?; 30 | let mut output = String::new(); 31 | let out = &mut output; 32 | let inputs = inputs.trim_end().trim_end_matches(';'); 33 | for mut input in inputs.split(";\n") { 34 | // Copy leading comments into the output 35 | while input.starts_with("//") { 36 | let Some((comment, remainder)) = input.split_once('\n') else { 37 | break; 38 | }; 39 | input = remainder; 40 | writeln!(out, "{comment}")?; 41 | continue; 42 | } 43 | 44 | fn parse_func_call( 45 | input: &str, 46 | ) -> Result<(String, &'static FuncType, Vec), ParserError> { 47 | let untyped_call = UntypedFuncCall::parse(input)?; 48 | let func_name = untyped_call.name().to_string(); 49 | let func_type = get_func_type(&func_name).unwrap_or_else(|| { 50 | panic!("unknown test func {func_name:?}"); 51 | }); 52 | let param_types = func_type.params().collect::>(); 53 | let values = untyped_call.to_wasm_params::(¶m_types)?; 54 | Ok((func_name, func_type, values)) 55 | } 56 | 57 | match parse_func_call(input) { 58 | Ok((func_name, func_type, values)) => { 59 | assert!( 60 | !filename.starts_with("reject-"), 61 | "accepted input {input:?} in {filename}" 62 | ); 63 | write!(out, "{func_name}(")?; 64 | let mut first = true; 65 | for (name, value) in func_type.param_names().zip(values) { 66 | if first { 67 | first = false; 68 | } else { 69 | write!(out, ", ")?; 70 | } 71 | write!(out, "{name}: {value}", value = DisplayValue(&value))?; 72 | } 73 | writeln!(out, ")")?; 74 | } 75 | Err(err) => { 76 | assert!( 77 | !filename.starts_with("accept-"), 78 | "rejected input {input:?} in {filename}: {err:#}" 79 | ); 80 | writeln!(out, "{err}")?; 81 | } 82 | } 83 | } 84 | Ok(output) 85 | } 86 | 87 | fn get_func_type(func_name: &str) -> Option<&'static FuncType> { 88 | static FUNC_TYPES: OnceLock> = OnceLock::new(); 89 | FUNC_TYPES 90 | .get_or_init(|| { 91 | let path = Path::new("tests/ui/ui.wit"); 92 | let unresolved = UnresolvedPackage::parse_path(path).unwrap(); 93 | let mut resolve = Resolve::new(); 94 | resolve.push(unresolved).unwrap(); 95 | resolve 96 | .interfaces 97 | .iter() 98 | .flat_map(|(_, i)| &i.functions) 99 | .map(|(name, func)| (name.clone(), resolve_wit_func_type(&resolve, func).unwrap())) 100 | .collect::>() 101 | }) 102 | .get(func_name) 103 | } 104 | 105 | fn main() { 106 | let action = match std::env::var("BLESS").unwrap_or_default().as_str() { 107 | "" => snapbox::Action::Verify, 108 | _ => snapbox::Action::Overwrite, 109 | }; 110 | Harness::new("tests/ui", setup, test) 111 | .select(["*.waves"]) 112 | .action(action) 113 | .test(); 114 | } 115 | -------------------------------------------------------------------------------- /src/wasmtime/core.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use crate::{ 4 | canonicalize_nan32, canonicalize_nan64, 5 | wasm::{unwrap_val, WasmFunc, WasmType, WasmTypeKind, WasmValueError}, 6 | WasmValue, 7 | }; 8 | 9 | impl WasmType for wasmtime::ValType { 10 | fn kind(&self) -> WasmTypeKind { 11 | match self { 12 | Self::I32 => WasmTypeKind::S32, 13 | Self::I64 => WasmTypeKind::S64, 14 | Self::F32 => WasmTypeKind::Float32, 15 | Self::F64 => WasmTypeKind::Float64, 16 | Self::V128 => WasmTypeKind::Tuple, 17 | 18 | Self::Ref(_) => WasmTypeKind::Unsupported, 19 | } 20 | } 21 | 22 | fn tuple_element_types(&self) -> Box + '_> { 23 | match *self { 24 | Self::V128 => {} 25 | _ => panic!("tuple_element_types called on non-tuple type"), 26 | } 27 | Box::new([Self::I64, Self::I64].into_iter()) 28 | } 29 | } 30 | 31 | impl WasmValue for wasmtime::Val { 32 | type Type = wasmtime::ValType; 33 | 34 | fn kind(&self) -> WasmTypeKind { 35 | match self { 36 | Self::I32(_) => WasmTypeKind::S32, 37 | Self::I64(_) => WasmTypeKind::S64, 38 | Self::F32(_) => WasmTypeKind::Float32, 39 | Self::F64(_) => WasmTypeKind::Float64, 40 | Self::V128(_) => WasmTypeKind::Tuple, 41 | Self::FuncRef(_) => WasmTypeKind::Unsupported, 42 | Self::ExternRef(_) => WasmTypeKind::Unsupported, 43 | Self::AnyRef(_) => WasmTypeKind::Unsupported, 44 | } 45 | } 46 | 47 | fn make_s32(val: i32) -> Self { 48 | Self::I32(val) 49 | } 50 | fn make_s64(val: i64) -> Self { 51 | Self::I64(val) 52 | } 53 | fn make_float32(val: f32) -> Self { 54 | let val = canonicalize_nan32(val); 55 | Self::F32(val.to_bits()) 56 | } 57 | fn make_float64(val: f64) -> Self { 58 | let val = canonicalize_nan64(val); 59 | Self::F64(val.to_bits()) 60 | } 61 | fn make_tuple( 62 | ty: &Self::Type, 63 | vals: impl IntoIterator, 64 | ) -> Result { 65 | match *ty { 66 | Self::Type::V128 => {} 67 | _ => return Err(WasmValueError::other("tuples only used for v128 (v64x2)")), 68 | } 69 | let [l_val, h_val]: [Self; 2] = vals 70 | .into_iter() 71 | .collect::>() 72 | .try_into() 73 | .map_err(|_| WasmValueError::other("expected 2 values"))?; 74 | 75 | let (Some(l), Some(h)) = (l_val.i64(), h_val.i64()) else { 76 | return Err(WasmValueError::other("expected 2 i64s (v64x2)")); 77 | }; 78 | Ok(Self::V128(((h as u128) << 64 | (l as u128)).into())) 79 | } 80 | 81 | fn unwrap_s32(&self) -> i32 { 82 | *unwrap_val!(self, Self::I32, "s32") 83 | } 84 | 85 | fn unwrap_s64(&self) -> i64 { 86 | *unwrap_val!(self, Self::I64, "s64") 87 | } 88 | 89 | fn unwrap_float32(&self) -> f32 { 90 | let val = f32::from_bits(*unwrap_val!(self, Self::F32, "float32")); 91 | canonicalize_nan32(val) 92 | } 93 | 94 | fn unwrap_float64(&self) -> f64 { 95 | let val = f64::from_bits(*unwrap_val!(self, Self::F64, "float64")); 96 | canonicalize_nan64(val) 97 | } 98 | 99 | fn unwrap_tuple(&self) -> Box> + '_> { 100 | let v = unwrap_val!(self, Self::V128, "tuple").as_u128(); 101 | let low = v as i64; 102 | let high = (v >> 64) as i64; 103 | Box::new( 104 | [Self::I64(low), Self::I64(high)] 105 | .into_iter() 106 | .map(Cow::Owned), 107 | ) 108 | } 109 | } 110 | 111 | impl WasmFunc for wasmtime::FuncType { 112 | type Type = wasmtime::ValType; 113 | 114 | fn params(&self) -> Box + '_> { 115 | Box::new(self.params()) 116 | } 117 | 118 | fn results(&self) -> Box + '_> { 119 | Box::new(self.results()) 120 | } 121 | } 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | #[test] 126 | fn core_vals_smoke_test() { 127 | use wasmtime::Val; 128 | for (val, want) in [ 129 | (Val::I32(10), "10"), 130 | (Val::I64(-10), "-10"), 131 | (1.5f32.into(), "1.5"), 132 | (f32::NAN.into(), "nan"), 133 | (f32::INFINITY.into(), "inf"), 134 | (f32::NEG_INFINITY.into(), "-inf"), 135 | ((-1.5f64).into(), "-1.5"), 136 | (f32::NAN.into(), "nan"), 137 | (f32::INFINITY.into(), "inf"), 138 | (f32::NEG_INFINITY.into(), "-inf"), 139 | ( 140 | Val::V128(0x1234567890abcdef1122334455667788.into()), 141 | "(1234605616436508552, 1311768467294899695)", 142 | ), 143 | ] { 144 | let got = crate::to_string(&val) 145 | .unwrap_or_else(|err| panic!("failed to serialize {val:?}: {err}")); 146 | assert_eq!(got, want, "for {val:?}"); 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/wasm/ty.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, fmt::Debug}; 2 | 3 | /// The kind of a [`WasmType`]. These correspond to the value types defined by the 4 | /// [Component Model design](https://github.com/WebAssembly/component-model/blob/673d5c43c3cc0f4aeb8996a5c0931af623f16808/design/mvp/WIT.md). 5 | #[derive(Clone, Copy, Debug, PartialEq)] 6 | #[allow(missing_docs)] 7 | #[non_exhaustive] 8 | pub enum WasmTypeKind { 9 | Bool, 10 | S8, 11 | S16, 12 | S32, 13 | S64, 14 | U8, 15 | U16, 16 | U32, 17 | U64, 18 | Float32, 19 | Float64, 20 | Char, 21 | String, 22 | List, 23 | Record, 24 | Tuple, 25 | Variant, 26 | Enum, 27 | Option, 28 | Result, 29 | Flags, 30 | #[doc(hidden)] 31 | Unsupported, 32 | } 33 | 34 | impl std::fmt::Display for WasmTypeKind { 35 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 36 | f.write_str(match self { 37 | WasmTypeKind::Bool => "bool", 38 | WasmTypeKind::S8 => "s8", 39 | WasmTypeKind::S16 => "s16", 40 | WasmTypeKind::S32 => "s32", 41 | WasmTypeKind::S64 => "s64", 42 | WasmTypeKind::U8 => "u8", 43 | WasmTypeKind::U16 => "u16", 44 | WasmTypeKind::U32 => "u32", 45 | WasmTypeKind::U64 => "u64", 46 | WasmTypeKind::Float32 => "float32", 47 | WasmTypeKind::Float64 => "float64", 48 | WasmTypeKind::Char => "char", 49 | WasmTypeKind::String => "string", 50 | WasmTypeKind::List => "list", 51 | WasmTypeKind::Record => "record", 52 | WasmTypeKind::Tuple => "tuple", 53 | WasmTypeKind::Variant => "variant", 54 | WasmTypeKind::Enum => "enum", 55 | WasmTypeKind::Option => "option", 56 | WasmTypeKind::Result => "result", 57 | WasmTypeKind::Flags => "flags", 58 | WasmTypeKind::Unsupported => "<>", 59 | }) 60 | } 61 | } 62 | 63 | /// The WasmType trait may be implemented to represent types to be 64 | /// (de)serialized with WAVE, notably [`value::Type`](crate::value::Type) 65 | /// and [`wasmtime::component::Type`]. 66 | /// 67 | /// The `Self`-returning methods should be called only for corresponding 68 | /// [`WasmTypeKind`]s. 69 | pub trait WasmType: Clone + Sized { 70 | /// Returns the [`WasmTypeKind`] of this type. 71 | fn kind(&self) -> WasmTypeKind; 72 | 73 | /// Returns the list element type or `None` if `self` is not a list type. 74 | /// # Panics 75 | /// Panics if the type is not implemented (the trait default). 76 | fn list_element_type(&self) -> Option { 77 | unimplemented!() 78 | } 79 | /// Returns an iterator of the record's field names and Types. The 80 | /// iterator will be empty iff `self` is not a record type. 81 | /// # Panics 82 | /// Panics if the type is not implemented (the trait default). 83 | fn record_fields(&self) -> Box, Self)> + '_> { 84 | unimplemented!() 85 | } 86 | /// Returns an iterator of the tuple's field Types. The iterator will be 87 | /// empty iff `self` is not a tuple type. 88 | /// # Panics 89 | /// Panics if the type is not implemented (the trait default). 90 | fn tuple_element_types(&self) -> Box + '_> { 91 | unimplemented!() 92 | } 93 | /// Returns an iterator of the variant's case names and optional payload 94 | /// Types. The iterator will be empty iff `self` is not a tuple type. 95 | /// # Panics 96 | /// Panics if the type is not implemented (the trait default). 97 | fn variant_cases(&self) -> Box, Option)> + '_> { 98 | unimplemented!() 99 | } 100 | /// Returns an iterator of the enum's case names. The iterator will be 101 | /// empty iff `self` is not an enum type. 102 | /// # Panics 103 | /// Panics if the type is not implemented (the trait default). 104 | fn enum_cases(&self) -> Box> + '_> { 105 | unimplemented!() 106 | } 107 | /// Returns the option's "some" type or `None` if `self` is not an option type. 108 | /// # Panics 109 | /// Panics if the type is not implemented (the trait default). 110 | fn option_some_type(&self) -> Option { 111 | unimplemented!() 112 | } 113 | /// Returns the result's optional "ok" and "err" Types or `None` if `self` 114 | /// is not a result type. 115 | /// # Panics 116 | /// Panics if the type is not implemented (the trait default). 117 | fn result_types(&self) -> Option<(Option, Option)> { 118 | unimplemented!() 119 | } 120 | /// Returns an iterator of the flags' names. The iterator will be empty iff 121 | /// `self` is not a flags type. 122 | /// # Panics 123 | /// Panics if the type is not implemented (the trait default). 124 | fn flags_names(&self) -> Box> + '_> { 125 | unimplemented!() 126 | } 127 | } 128 | 129 | macro_rules! maybe_unwrap_type { 130 | ($ty:expr, $case:path) => { 131 | match $ty { 132 | $case(v) => Some(v), 133 | _ => None, 134 | } 135 | }; 136 | } 137 | pub(crate) use maybe_unwrap_type; 138 | -------------------------------------------------------------------------------- /src/value/tests.rs: -------------------------------------------------------------------------------- 1 | use crate::WasmValue; 2 | 3 | use super::{Type, Value}; 4 | 5 | #[test] 6 | fn simple_value_round_trips() { 7 | for val in [ 8 | Value::make_bool(true), 9 | Value::make_u8(u8::MAX), 10 | Value::make_u16(u16::MAX), 11 | Value::make_u32(u32::MAX), 12 | Value::make_u64(u64::MAX), 13 | Value::make_s8(i8::MIN), 14 | Value::make_s16(i16::MIN), 15 | Value::make_s32(i32::MIN), 16 | Value::make_s64(i64::MIN), 17 | Value::make_char('☃'), 18 | Value::make_string("str".into()), 19 | ] { 20 | test_value_round_trip(val) 21 | } 22 | } 23 | 24 | #[test] 25 | fn float_round_trips() { 26 | for (float32, float64) in [ 27 | (0.0, 0.0), 28 | (f32::MIN, f64::MIN), 29 | (f32::MIN_POSITIVE, f64::MIN_POSITIVE), 30 | (f32::MAX, f64::MAX), 31 | (f32::EPSILON, f64::EPSILON), 32 | (f32::INFINITY, f64::INFINITY), 33 | (f32::NEG_INFINITY, f64::NEG_INFINITY), 34 | ] { 35 | test_value_round_trip(Value::make_float32(float32)); 36 | test_value_round_trip(Value::make_float64(float64)); 37 | } 38 | } 39 | 40 | #[test] 41 | fn list_round_trips() { 42 | let ty = Type::list(Type::U8); 43 | test_value_round_trip(Value::make_list(&ty, []).unwrap()); 44 | test_value_round_trip(Value::make_list(&ty, [Value::make_u8(1)]).unwrap()); 45 | test_value_round_trip(Value::make_list(&ty, [Value::make_u8(1), Value::make_u8(2)]).unwrap()); 46 | } 47 | 48 | #[test] 49 | fn record_round_trip() { 50 | let option_ty = Type::option(Type::U8); 51 | let record_ty = 52 | Type::record([("field-a", Type::BOOL), ("field-b", option_ty.clone())]).unwrap(); 53 | let record_val = Value::make_record( 54 | &record_ty, 55 | [ 56 | ("field-a", Value::make_bool(true)), 57 | ("field-b", Value::make_option(&option_ty, None).unwrap()), 58 | ], 59 | ) 60 | .unwrap(); 61 | test_value_round_trip(record_val) 62 | } 63 | 64 | #[test] 65 | fn tuple_round_trip() { 66 | let ty = Type::tuple([Type::BOOL, Type::U8]).unwrap(); 67 | let val = Value::make_tuple(&ty, [Value::make_bool(true), Value::make_u8(1)]).unwrap(); 68 | test_value_round_trip(val); 69 | } 70 | 71 | #[test] 72 | fn variant_round_trips() { 73 | let ty = Type::variant([("off", None), ("on", Some(Type::U8))]).unwrap(); 74 | test_value_round_trip(Value::make_variant(&ty, "off", None).unwrap()); 75 | test_value_round_trip(Value::make_variant(&ty, "on", Some(Value::make_u8(1))).unwrap()); 76 | } 77 | 78 | #[test] 79 | fn enum_round_trips() { 80 | let ty = Type::enum_ty(["north", "east", "south", "west"]).unwrap(); 81 | test_value_round_trip(Value::make_enum(&ty, "north").unwrap()); 82 | test_value_round_trip(Value::make_enum(&ty, "south").unwrap()); 83 | } 84 | 85 | #[test] 86 | fn option_round_trips() { 87 | let ty = Type::option(Type::U8); 88 | test_value_round_trip(Value::make_option(&ty, Some(Value::make_u8(1))).unwrap()); 89 | test_value_round_trip(Value::make_option(&ty, None).unwrap()); 90 | } 91 | 92 | #[test] 93 | fn result_round_trips() { 94 | let no_payloads = Type::result(None, None); 95 | let both_payloads = Type::result(Some(Type::U8), Some(Type::STRING)); 96 | let ok_only = Type::result(Some(Type::U8), None); 97 | let err_only = Type::result(None, Some(Type::STRING)); 98 | for (ty, payload) in [ 99 | (&no_payloads, Ok(None)), 100 | (&no_payloads, Err(None)), 101 | (&both_payloads, Ok(Some(Value::make_u8(1)))), 102 | (&both_payloads, Err(Some(Value::make_string("oops".into())))), 103 | (&ok_only, Ok(Some(Value::make_u8(1)))), 104 | (&ok_only, Err(None)), 105 | (&err_only, Ok(None)), 106 | (&err_only, Err(Some(Value::make_string("oops".into())))), 107 | ] { 108 | let val = Value::make_result(ty, payload).unwrap(); 109 | test_value_round_trip(val); 110 | } 111 | } 112 | 113 | #[test] 114 | fn flags_round_trips() { 115 | let ty = Type::flags(["read", "write", "execute"]).unwrap(); 116 | test_value_round_trip(Value::make_flags(&ty, []).unwrap()); 117 | test_value_round_trip(Value::make_flags(&ty, ["write"]).unwrap()); 118 | test_value_round_trip(Value::make_flags(&ty, ["read", "execute"]).unwrap()); 119 | } 120 | 121 | fn local_ty(val: &Value) -> Type { 122 | use crate::value::{TypeEnum, ValueEnum}; 123 | match &val.0 { 124 | ValueEnum::Bool(_) => Type::BOOL, 125 | ValueEnum::S8(_) => Type::S8, 126 | ValueEnum::S16(_) => Type::S16, 127 | ValueEnum::S32(_) => Type::S32, 128 | ValueEnum::S64(_) => Type::S64, 129 | ValueEnum::U8(_) => Type::U8, 130 | ValueEnum::U16(_) => Type::U16, 131 | ValueEnum::U32(_) => Type::U32, 132 | ValueEnum::U64(_) => Type::U64, 133 | ValueEnum::Float32(_) => Type::FLOAT32, 134 | ValueEnum::Float64(_) => Type::FLOAT64, 135 | ValueEnum::Char(_) => Type::CHAR, 136 | ValueEnum::String(_) => Type::STRING, 137 | ValueEnum::List(inner) => Type(TypeEnum::List(inner.ty.clone())), 138 | ValueEnum::Record(inner) => Type(TypeEnum::Record(inner.ty.clone())), 139 | ValueEnum::Tuple(inner) => Type(TypeEnum::Tuple(inner.ty.clone())), 140 | ValueEnum::Variant(inner) => Type(TypeEnum::Variant(inner.ty.clone())), 141 | ValueEnum::Enum(inner) => Type(TypeEnum::Enum(inner.ty.clone())), 142 | ValueEnum::Option(inner) => Type(TypeEnum::Option(inner.ty.clone())), 143 | ValueEnum::Result(inner) => Type(TypeEnum::Result(inner.ty.clone())), 144 | ValueEnum::Flags(inner) => Type(TypeEnum::Flags(inner.ty.clone())), 145 | } 146 | } 147 | 148 | fn test_value_round_trip(val: Value) { 149 | let ty = local_ty(&val); 150 | let serialized = crate::to_string(&val).unwrap(); 151 | let deserialized: Value = crate::from_str(&ty, &serialized) 152 | .unwrap_or_else(|err| panic!("failed to deserialize {serialized:?}: {err}")); 153 | assert_eq!(deserialized, val, "for {val:?}"); 154 | } 155 | -------------------------------------------------------------------------------- /tests/wasmtime.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Mutex, OnceLock}; 2 | 3 | use wasm_wave::wasm::{DisplayFunc, WasmValue}; 4 | use wasmtime::{ 5 | component::{types::ComponentItem, Component, Instance, Linker, Type, Val}, 6 | Config, Engine, Store, 7 | }; 8 | 9 | #[test] 10 | fn test_round_trips_unchanged() { 11 | for (func, input) in [ 12 | ("bools", "(true, false)"), 13 | ("sints", "(-127, -32768, -2147483648, -9223372036854775808)"), 14 | ("uints", "(255, 65535, 4294967295, 18446744073709551615)"), 15 | ("floats", "(-1.5, 3.1415)"), 16 | ("floats", "(inf, inf)"), 17 | ("floats", "(-inf, -inf)"), 18 | ("floats", "(nan, nan)"), 19 | ("options", "(none, none)"), 20 | ("options", "(some(1), some(none))"), 21 | ("options", "(some(1), some(some(-1)))"), 22 | ("list-chars", "[]"), 23 | ("list-chars", "['x', '☃']"), 24 | ("list-strings", r#"["xyz", "☃☃☃", "\n\r\t"]"#), 25 | ("list-strings", r#"["\u{b}\u{c}\u{7f}\u{80}\u{85}\u{a0}"]"#), 26 | ("list-strings", r#"["\u{200d}\u{2028}\u{2029}"]"#), 27 | ("list-strings", r#"["\u{d7ff}\u{e000}"]"#), 28 | ("list-strings", r#"["\u{ffff}"]"#), 29 | ("list-strings", r#"["\u{feff}\u{100000}\u{10ffff}"]"#), 30 | ("list-strings", r#"["😂☃"]"#), 31 | ("result-ok-only", "ok(1)"), 32 | ("result-ok-only", "err"), 33 | ("result-err-only", "ok"), 34 | ("result-err-only", "err(-1)"), 35 | ("result-no-payloads", "ok"), 36 | ("result-no-payloads", "err"), 37 | ("result-both-payloads", "ok(1)"), 38 | ("result-both-payloads", "err(-1)"), 39 | ("record", "{required: 1}"), 40 | ("record", "{required: 1, optional: some(2)}"), 41 | ("variant", "without-payload"), 42 | ("variant", "with-payload(1)"), 43 | ("enum", "first"), 44 | ("enum", "second"), 45 | ("flags", "{}"), 46 | ("flags", "{read}"), 47 | ("flags", "{read, write}"), 48 | ] { 49 | assert_round_trip(func, input, input); 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_round_trip_variations() { 55 | for (func, input, output) in [ 56 | ("bools", "(true, false, )", "(true, false)"), 57 | ("bools", " ( true ,false ,) ", "(true, false)"), 58 | ("floats", "(1e+10, -5.5e-5)", "(10000000000, -0.000055)"), 59 | ("floats", "(0.00e100, 1.0e0)", "(0, 1)"), 60 | ("options", "(1, some(-1))", "(some(1), some(some(-1)))"), 61 | ("list-strings", "[\"b\rc\td\",]", r#"["b\rc\td"]"#), 62 | ("result-ok-only", "1", "ok(1)"), 63 | ("record", "{required: 1 ,optional: none ,}", "{required: 1}"), 64 | ("flags", "{read,}", "{read}"), 65 | ] { 66 | assert_round_trip(func, input, output); 67 | } 68 | } 69 | 70 | #[test] 71 | fn test_wasmtime_get_func_type() { 72 | let func = with_instance_and_store(|instance, store| { 73 | let func = instance 74 | .exports(&mut *store) 75 | .root() 76 | .func("func-type") 77 | .unwrap(); 78 | wasm_wave::wasmtime::get_func_type(&func, store) 79 | }); 80 | 81 | assert_eq!( 82 | func.to_string(), 83 | "func(bool, enum { first, second }) -> result" 84 | ); 85 | } 86 | 87 | #[test] 88 | fn test_wasmtime_component_func_type() { 89 | let engine = Engine::new(Config::new().wasm_component_model(true)).expect("engine"); 90 | let component = Component::from_file(&engine, "tests/types.wasm").expect("component"); 91 | let linker = Linker::<()>::new(&engine); 92 | let component_type = linker.substituted_component_type(&component).unwrap(); 93 | let func_item = component_type 94 | .get_export(&engine, "func-type") 95 | .expect("missing export"); 96 | let ComponentItem::ComponentFunc(func_type) = func_item else { 97 | panic!("incorrect item type {func_item:?}"); 98 | }; 99 | assert_eq!( 100 | DisplayFunc(func_type).to_string(), 101 | "func(bool, enum { first, second }) -> result" 102 | ); 103 | } 104 | 105 | #[test] 106 | fn test_wasmtime_make_list() { 107 | let ty = get_type("list-chars"); 108 | let val = Val::make_list(&ty, [Val::make_char('x')]).unwrap(); 109 | assert_eq!(val.unwrap_list().next().unwrap().unwrap_char(), 'x'); 110 | } 111 | 112 | #[test] 113 | fn test_wasmtime_make_list_invalid() { 114 | let ty = get_type("list-chars"); 115 | Val::make_list(&ty, [Val::make_bool(false)]) 116 | .expect_err("make_list with wrong value type should fail"); 117 | } 118 | 119 | fn assert_round_trip(type_name: &str, input: &str, output: &str) { 120 | let ty = get_type(type_name); 121 | let deserialized: Val = wasm_wave::from_str(&ty, input) 122 | .unwrap_or_else(|err| panic!("failed to deserialize {input:?}: {err}")); 123 | let reserialized = wasm_wave::to_string(&deserialized) 124 | .unwrap_or_else(|err| panic!("failed to serialize {deserialized:?}: {err}")); 125 | assert_eq!(reserialized, output); 126 | } 127 | 128 | fn with_instance_and_store(f: impl Fn(&Instance, &mut Store<()>) -> T) -> T { 129 | static INSTANCE_AND_STORE: OnceLock<(Instance, Mutex>)> = OnceLock::new(); 130 | let (instance, store) = INSTANCE_AND_STORE.get_or_init(|| { 131 | let engine = Engine::new(Config::new().wasm_component_model(true)).expect("engine"); 132 | let component = Component::from_file(&engine, "tests/types.wasm").expect("component"); 133 | let linker = Linker::new(&engine); 134 | let mut store = Store::new(&engine, ()); 135 | let instance = linker 136 | .instantiate(&mut store, &component) 137 | .expect("instance"); 138 | (instance, Mutex::new(store)) 139 | }); 140 | let mut store = store.lock().unwrap(); 141 | f(instance, &mut store) 142 | } 143 | 144 | fn get_type(name: &str) -> Type { 145 | with_instance_and_store(|instance, store| { 146 | let func = instance 147 | .exports(&mut *store) 148 | .root() 149 | .func(name) 150 | .unwrap_or_else(|| panic!("export func named {name:?}")); 151 | func.results(&*store)[0].clone() 152 | }) 153 | } 154 | -------------------------------------------------------------------------------- /src/strings.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, str::Split}; 2 | 3 | use crate::parser::{ParserError, ParserErrorKind}; 4 | 5 | /// An iterator over parsed string "parts". 6 | pub enum StringPartsIter<'a> { 7 | Normal(StringParts<'a>), 8 | Multiline(MultilineStringParts<'a>), 9 | } 10 | 11 | impl<'a> StringPartsIter<'a> { 12 | /// Returns an iterator over string parts for the given substring containing 13 | /// only the _inner_ contents (between quotes) of a single-line string. 14 | /// The given pos is the source position of this substring for error reporting. 15 | pub fn new(src: &'a str, pos: usize) -> Self { 16 | Self::Normal(StringParts { src, pos }) 17 | } 18 | 19 | /// Returns an iterator over string parts for the given substring containing 20 | /// only the _inner_ contents (between quotes) of a multiline string. 21 | /// The given pos is the source position of this substring for error reporting. 22 | pub fn new_multiline(src: &'a str, pos: usize) -> Result { 23 | Ok(Self::Multiline(MultilineStringParts::new(src, pos)?)) 24 | } 25 | } 26 | 27 | impl<'a> Iterator for StringPartsIter<'a> { 28 | type Item = Result, ParserError>; 29 | 30 | fn next(&mut self) -> Option { 31 | match self { 32 | StringPartsIter::Normal(parts) => parts.next(), 33 | StringPartsIter::Multiline(parts) => parts.next(), 34 | } 35 | } 36 | } 37 | 38 | pub struct StringParts<'a> { 39 | src: &'a str, 40 | pos: usize, 41 | } 42 | 43 | impl<'a> StringParts<'a> { 44 | fn next_part(&mut self) -> Result, ParserError> { 45 | let (part, consumed) = match self.src.find('\\') { 46 | Some(0) => { 47 | let (esc_char, esc_len) = unescape(self.src).ok_or_else(|| { 48 | ParserError::new(ParserErrorKind::InvalidEscape, self.pos..self.pos + 1) 49 | })?; 50 | (Cow::Owned(esc_char.to_string()), esc_len) 51 | } 52 | Some(next_esc) => { 53 | let esc_prefix = &self.src[..next_esc]; 54 | (Cow::Borrowed(esc_prefix), esc_prefix.len()) 55 | } 56 | None => (Cow::Borrowed(self.src), self.src.len()), 57 | }; 58 | self.src = &self.src[consumed..]; 59 | self.pos += consumed; 60 | Ok(part) 61 | } 62 | } 63 | 64 | impl<'a> Iterator for StringParts<'a> { 65 | type Item = Result, ParserError>; 66 | 67 | fn next(&mut self) -> Option { 68 | if self.src.is_empty() { 69 | return None; 70 | } 71 | Some(self.next_part()) 72 | } 73 | } 74 | 75 | /// An iterator over parsed string "parts", each of which is either a literal 76 | /// substring of the source or a decoded escape sequence. 77 | pub struct MultilineStringParts<'a> { 78 | curr: StringParts<'a>, 79 | lines: Split<'a, char>, 80 | next_pos: usize, 81 | indent: &'a str, 82 | } 83 | 84 | impl<'a> MultilineStringParts<'a> { 85 | fn new(src: &'a str, pos: usize) -> Result { 86 | let end = pos + src.len(); 87 | 88 | // Strip leading carriage return as part of first newline 89 | let (src, next_pos) = match src.strip_prefix('\r') { 90 | Some(src) => (src, pos + 2), 91 | None => (src, pos + 1), 92 | }; 93 | 94 | let mut lines = src.split('\n'); 95 | 96 | // Remove mandatory final line, using trailing spaces as indent 97 | let indent = lines.next_back().unwrap(); 98 | if indent.contains(|ch| ch != ' ') { 99 | return Err(ParserError::with_detail( 100 | ParserErrorKind::InvalidMultilineString, 101 | end - 3..end, 102 | r#"closing """ must be on a line preceded only by spaces"#, 103 | )); 104 | } 105 | 106 | // Validate mandatory initial empty line 107 | if lines.next() != Some("") { 108 | return Err(ParserError::with_detail( 109 | ParserErrorKind::InvalidMultilineString, 110 | pos..pos + 1, 111 | r#"opening """ must be followed immediately by newline"#, 112 | )); 113 | } 114 | 115 | let mut parts = Self { 116 | curr: StringParts { src: "", pos: 0 }, 117 | lines, 118 | next_pos, 119 | indent, 120 | }; 121 | 122 | // Skip first newline 123 | if let Some(nl) = parts.next().transpose()? { 124 | debug_assert_eq!(nl, "\n"); 125 | } 126 | 127 | Ok(parts) 128 | } 129 | } 130 | 131 | impl<'a> Iterator for MultilineStringParts<'a> { 132 | type Item = Result, ParserError>; 133 | 134 | fn next(&mut self) -> Option { 135 | if self.curr.src.is_empty() { 136 | let next = self.lines.next()?; 137 | 138 | // Update next line position 139 | let pos = self.next_pos; 140 | self.next_pos += next.len() + 1; 141 | 142 | // Strip indent 143 | let Some(src) = next.strip_prefix(self.indent) else { 144 | return Some(Err(ParserError::with_detail( 145 | ParserErrorKind::InvalidMultilineString, 146 | pos..pos + 1, 147 | r#"lines must be indented at least as much as closing """"#, 148 | ))); 149 | }; 150 | let pos = pos + self.indent.len(); 151 | 152 | // Strip trailing carriage return for `\r\n` newlines 153 | let src = src.strip_suffix('\r').unwrap_or(src); 154 | 155 | self.curr = StringParts { src, pos }; 156 | 157 | Some(Ok("\n".into())) 158 | } else { 159 | Some(self.curr.next_part()) 160 | } 161 | } 162 | } 163 | 164 | // Given a substring starting with an escape sequence, returns the decoded char 165 | // and length of the sequence, or None if the sequence is invalid. 166 | pub fn unescape(src: &str) -> Option<(char, usize)> { 167 | let mut chars = src.chars(); 168 | if chars.next() != Some('\\') { 169 | return None; 170 | } 171 | Some(match chars.next()? { 172 | '\\' => ('\\', 2), 173 | '\'' => ('\'', 2), 174 | '"' => ('"', 2), 175 | 't' => ('\t', 2), 176 | 'n' => ('\n', 2), 177 | 'r' => ('\r', 2), 178 | 'u' => { 179 | if chars.next()? != '{' { 180 | return None; 181 | } 182 | let mut val = 0; 183 | let mut digits = 0; 184 | loop { 185 | let ch = chars.next()?; 186 | if ch == '}' { 187 | if digits == 0 { 188 | return None; 189 | } 190 | break; 191 | } 192 | val = (val << 4) | ch.to_digit(16)?; 193 | digits += 1; 194 | if digits > 6 { 195 | return None; 196 | } 197 | } 198 | (char::from_u32(val)?, digits + 4) 199 | } 200 | _ => return None, 201 | }) 202 | } 203 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | //! WAVE writer 2 | 3 | use std::{fmt::Debug, io::Write}; 4 | 5 | use thiserror::Error; 6 | 7 | use crate::{ 8 | lex::Keyword, 9 | wasm::{WasmTypeKind, WasmValue}, 10 | }; 11 | 12 | /// A Web Assembly Value Encoding writer. 13 | /// 14 | /// Writes to the wrapped `W` writer. 15 | pub struct Writer { 16 | inner: W, 17 | } 18 | 19 | impl Writer { 20 | /// Returns a new Writer for the given [`std::io::Write`]. 21 | pub fn new(w: W) -> Self { 22 | Self { inner: w } 23 | } 24 | 25 | /// WAVE-encodes and writes the given [`WasmValue`] to the underlying writer. 26 | pub fn write_value(&mut self, val: &V) -> Result<(), WriterError> 27 | where 28 | V: WasmValue, 29 | { 30 | match val.kind() { 31 | WasmTypeKind::Bool => self.write_str(if val.unwrap_bool() { "true" } else { "false" }), 32 | WasmTypeKind::S8 => self.write_display(val.unwrap_s8()), 33 | WasmTypeKind::S16 => self.write_display(val.unwrap_s16()), 34 | WasmTypeKind::S32 => self.write_display(val.unwrap_s32()), 35 | WasmTypeKind::S64 => self.write_display(val.unwrap_s64()), 36 | WasmTypeKind::U8 => self.write_display(val.unwrap_u8()), 37 | WasmTypeKind::U16 => self.write_display(val.unwrap_u16()), 38 | WasmTypeKind::U32 => self.write_display(val.unwrap_u32()), 39 | WasmTypeKind::U64 => self.write_display(val.unwrap_u64()), 40 | WasmTypeKind::Float32 => { 41 | let f = val.unwrap_float32(); 42 | if f.is_nan() { 43 | self.write_str("nan") // Display is "NaN" 44 | } else { 45 | self.write_display(f) 46 | } 47 | } 48 | WasmTypeKind::Float64 => { 49 | let f = val.unwrap_float64(); 50 | if f.is_nan() { 51 | self.write_str("nan") // Display is "NaN" 52 | } else { 53 | self.write_display(f) 54 | } 55 | } 56 | WasmTypeKind::Char => { 57 | self.write_str("'")?; 58 | self.write_char(val.unwrap_char())?; 59 | self.write_str("'") 60 | } 61 | WasmTypeKind::String => { 62 | self.write_str("\"")?; 63 | for ch in val.unwrap_string().chars() { 64 | self.write_char(ch)?; 65 | } 66 | self.write_str("\"") 67 | } 68 | WasmTypeKind::List => { 69 | self.write_str("[")?; 70 | for (idx, val) in val.unwrap_list().enumerate() { 71 | if idx != 0 { 72 | self.write_str(", ")?; 73 | } 74 | self.write_value(&*val)?; 75 | } 76 | self.write_str("]") 77 | } 78 | WasmTypeKind::Record => { 79 | self.write_str("{")?; 80 | let mut first = true; 81 | for (name, val) in val.unwrap_record() { 82 | if !matches!(val.kind(), WasmTypeKind::Option) || val.unwrap_option().is_some() 83 | { 84 | if first { 85 | first = false; 86 | } else { 87 | self.write_str(", ")?; 88 | } 89 | self.write_str(name)?; 90 | self.write_str(": ")?; 91 | self.write_value(&*val)?; 92 | } 93 | } 94 | if first { 95 | self.write_str(":")?; 96 | } 97 | self.write_str("}") 98 | } 99 | WasmTypeKind::Tuple => { 100 | self.write_str("(")?; 101 | for (idx, val) in val.unwrap_tuple().enumerate() { 102 | if idx != 0 { 103 | self.write_str(", ")?; 104 | } 105 | self.write_value(&*val)?; 106 | } 107 | self.write_str(")") 108 | } 109 | WasmTypeKind::Variant => { 110 | let (name, val) = val.unwrap_variant(); 111 | if Keyword::decode(&name).is_some() { 112 | self.write_char('%')?; 113 | } 114 | self.write_str(name)?; 115 | if let Some(val) = val { 116 | self.write_str("(")?; 117 | self.write_value(&*val)?; 118 | self.write_str(")")?; 119 | } 120 | Ok(()) 121 | } 122 | WasmTypeKind::Enum => { 123 | let case = val.unwrap_enum(); 124 | if Keyword::decode(&case).is_some() { 125 | self.write_char('%')?; 126 | } 127 | self.write_str(case) 128 | } 129 | WasmTypeKind::Option => match val.unwrap_option() { 130 | Some(val) => { 131 | self.write_str("some(")?; 132 | self.write_value(&*val)?; 133 | self.write_str(")") 134 | } 135 | None => self.write_str("none"), 136 | }, 137 | WasmTypeKind::Result => { 138 | let (name, val) = match val.unwrap_result() { 139 | Ok(val) => ("ok", val), 140 | Err(val) => ("err", val), 141 | }; 142 | self.write_str(name)?; 143 | if let Some(val) = val { 144 | self.write_str("(")?; 145 | self.write_value(&*val)?; 146 | self.write_str(")")?; 147 | } 148 | Ok(()) 149 | } 150 | WasmTypeKind::Flags => { 151 | self.write_str("{")?; 152 | for (idx, name) in val.unwrap_flags().enumerate() { 153 | if idx != 0 { 154 | self.write_str(", ")?; 155 | } 156 | self.write_str(name)?; 157 | } 158 | self.write_str("}")?; 159 | Ok(()) 160 | } 161 | WasmTypeKind::Unsupported => panic!("unsupported value type"), 162 | } 163 | } 164 | 165 | fn write_str(&mut self, s: impl AsRef) -> Result<(), WriterError> { 166 | self.inner.write_all(s.as_ref().as_bytes())?; 167 | Ok(()) 168 | } 169 | 170 | fn write_display(&mut self, d: impl std::fmt::Display) -> Result<(), WriterError> { 171 | write!(self.inner, "{d}")?; 172 | Ok(()) 173 | } 174 | 175 | fn write_char(&mut self, ch: char) -> Result<(), WriterError> { 176 | if "\\\"\'\t\r\n".contains(ch) { 177 | write!(self.inner, "{}", ch.escape_default())?; 178 | } else if ch.is_control() { 179 | write!(self.inner, "{}", ch.escape_unicode())?; 180 | } else { 181 | write!(self.inner, "{}", ch.escape_debug())?; 182 | } 183 | Ok(()) 184 | } 185 | } 186 | 187 | impl AsMut for Writer { 188 | fn as_mut(&mut self) -> &mut W { 189 | &mut self.inner 190 | } 191 | } 192 | 193 | /// A Writer error. 194 | #[derive(Debug, Error)] 195 | #[non_exhaustive] 196 | pub enum WriterError { 197 | /// An error from the underlying writer 198 | #[error("write failed: {0}")] 199 | Io(#[from] std::io::Error), 200 | } 201 | -------------------------------------------------------------------------------- /src/untyped.rs: -------------------------------------------------------------------------------- 1 | //! Untyped value 2 | 3 | use std::borrow::Cow; 4 | 5 | use crate::{ast::Node, lex::Keyword, parser::ParserError, Parser, WasmValue}; 6 | 7 | /// An UntypedValue is a parsed but not type-checked WAVE value. 8 | #[derive(Clone, Debug)] 9 | pub struct UntypedValue<'source> { 10 | source: Cow<'source, str>, 11 | node: Node, 12 | } 13 | 14 | impl<'source> UntypedValue<'source> { 15 | pub(crate) fn new(source: impl Into>, node: Node) -> Self { 16 | Self { 17 | source: source.into(), 18 | node, 19 | } 20 | } 21 | 22 | /// Parses an untyped value from WAVE. 23 | pub fn parse(source: &'source str) -> Result { 24 | let mut parser = Parser::new(source); 25 | let val = parser.parse_raw_value()?; 26 | parser.finish()?; 27 | Ok(val) 28 | } 29 | 30 | /// Creates an owned value, copying the entire source string if necessary. 31 | pub fn into_owned(self) -> UntypedValue<'static> { 32 | UntypedValue::new(self.source.into_owned(), self.node) 33 | } 34 | 35 | /// Returns the source this value was parsed from. 36 | pub fn source(&self) -> &str { 37 | &self.source 38 | } 39 | 40 | /// Returns this value's root node. 41 | pub fn node(&self) -> &Node { 42 | &self.node 43 | } 44 | 45 | /// Converts this untyped value into the given typed value. 46 | pub fn to_wasm_value(&self, ty: &V::Type) -> Result { 47 | self.node.to_wasm_value(ty, &self.source) 48 | } 49 | } 50 | 51 | impl std::fmt::Display for UntypedValue<'_> { 52 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 53 | fmt_node(f, &self.node, &self.source) 54 | } 55 | } 56 | 57 | /// An UntypedFuncCall is a parsed but not type-checked WAVE function call. 58 | /// 59 | /// WAVE function calls have the form `()`. 60 | pub struct UntypedFuncCall<'source> { 61 | source: Cow<'source, str>, 62 | name: Node, 63 | params: Node, 64 | } 65 | 66 | impl<'source> UntypedFuncCall<'source> { 67 | pub(crate) fn new(source: impl Into>, name: Node, params: Node) -> Self { 68 | Self { 69 | source: source.into(), 70 | name, 71 | params, 72 | } 73 | } 74 | 75 | /// Parses an untyped function call from WAVE. 76 | pub fn parse(source: &'source str) -> Result { 77 | let mut parser = Parser::new(source); 78 | let call = parser.parse_raw_func_call()?; 79 | parser.finish()?; 80 | Ok(call) 81 | } 82 | 83 | /// Creates an owned function call, copying the entire source string if necessary. 84 | pub fn into_owned(self) -> UntypedFuncCall<'static> { 85 | UntypedFuncCall::new( 86 | self.source.into_owned(), 87 | self.name.clone(), 88 | self.params.clone(), 89 | ) 90 | } 91 | 92 | /// Returns the source this function call was parsed from. 93 | pub fn source(&self) -> &str { 94 | &self.source 95 | } 96 | 97 | /// Returns the function name node. 98 | pub fn name_node(&self) -> &Node { 99 | &self.name 100 | } 101 | 102 | /// Returns the function parameters node. 103 | pub fn params_node(&self) -> &Node { 104 | &self.params 105 | } 106 | 107 | /// Returns the function name. 108 | pub fn name(&self) -> &str { 109 | self.name.slice(&self.source) 110 | } 111 | 112 | /// Converts the untyped parameters into the given types. 113 | /// 114 | /// Any number of trailing option-typed values may be omitted; those will 115 | /// be returned as `none` values. 116 | pub fn to_wasm_params<'types, V: WasmValue + 'static>( 117 | &self, 118 | types: impl IntoIterator, 119 | ) -> Result, ParserError> { 120 | self.params.to_wasm_params(types, self.source()) 121 | } 122 | } 123 | 124 | impl std::fmt::Display for UntypedFuncCall<'_> { 125 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 126 | f.write_str(self.name.slice(&self.source))?; 127 | fmt_node(f, &self.params, &self.source) 128 | } 129 | } 130 | 131 | fn fmt_node(f: &mut impl std::fmt::Write, node: &Node, src: &str) -> std::fmt::Result { 132 | use crate::ast::NodeType::*; 133 | match node.ty() { 134 | BoolTrue | BoolFalse | Number | Char | String | MultilineString | Label => { 135 | f.write_str(node.slice(src)) 136 | } 137 | Tuple => fmt_sequence(f, '(', ')', node.as_tuple()?, src), 138 | List => fmt_sequence(f, '[', ']', node.as_list()?, src), 139 | Record => { 140 | let fields = node.as_record(src)?; 141 | if fields.len() == 0 { 142 | return f.write_str("{:}"); 143 | } 144 | f.write_char('{')?; 145 | for (idx, (name, value)) in node.as_record(src)?.enumerate() { 146 | if idx != 0 { 147 | f.write_str(", ")?; 148 | } 149 | write!(f, "{name}: ")?; 150 | fmt_node(f, value, src)?; 151 | } 152 | f.write_char('}') 153 | } 154 | VariantWithPayload => { 155 | let (label, payload) = node.as_variant(src)?; 156 | if Keyword::decode(label).is_some() { 157 | f.write_char('%')?; 158 | } 159 | fmt_variant(f, label, payload, src) 160 | } 161 | OptionSome => fmt_variant(f, "some", node.as_option()?, src), 162 | OptionNone => fmt_variant(f, "none", None, src), 163 | ResultOk => fmt_variant(f, "ok", node.as_result()?.unwrap(), src), 164 | ResultErr => fmt_variant(f, "err", node.as_result()?.unwrap_err(), src), 165 | Flags => { 166 | f.write_char('{')?; 167 | for (idx, flag) in node.as_flags(src)?.enumerate() { 168 | if idx != 0 { 169 | f.write_str(", ")?; 170 | } 171 | f.write_str(flag)?; 172 | } 173 | f.write_char('}') 174 | } 175 | } 176 | } 177 | 178 | fn fmt_sequence<'a>( 179 | f: &mut impl std::fmt::Write, 180 | open: char, 181 | close: char, 182 | nodes: impl Iterator, 183 | src: &str, 184 | ) -> std::fmt::Result { 185 | f.write_char(open)?; 186 | for (idx, node) in nodes.enumerate() { 187 | if idx != 0 { 188 | f.write_str(", ")?; 189 | } 190 | fmt_node(f, node, src)?; 191 | } 192 | f.write_char(close) 193 | } 194 | 195 | fn fmt_variant( 196 | f: &mut impl std::fmt::Write, 197 | case: &str, 198 | payload: Option<&Node>, 199 | src: &str, 200 | ) -> std::fmt::Result { 201 | f.write_str(case)?; 202 | if let Some(node) = payload { 203 | f.write_char('(')?; 204 | fmt_node(f, node, src)?; 205 | f.write_char(')')?; 206 | } 207 | Ok(()) 208 | } 209 | 210 | impl From for std::fmt::Error { 211 | fn from(_: ParserError) -> Self { 212 | Self 213 | } 214 | } 215 | 216 | #[cfg(test)] 217 | mod tests { 218 | use super::*; 219 | 220 | #[test] 221 | fn round_trips() { 222 | for src in [ 223 | "true", 224 | "18446744073709551616", 225 | "-9223372036854775808", 226 | "[-3.1415, 0, inf, nan, -inf]", 227 | "['☃', '\\n']", 228 | r#""☃☃☃""#, 229 | "(1, false)", 230 | "{:}", 231 | "{code: red}", 232 | "left(1)", 233 | "[some(1), none]", 234 | "[ok(1), err(2)]", 235 | "[ok, err]", 236 | "%inf(inf)", 237 | "%some", 238 | "%none(none)", 239 | ] { 240 | let val = UntypedValue::parse(src).unwrap(); 241 | let encoded = val.to_string(); 242 | assert_eq!(encoded, src); 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/value/wit.rs: -------------------------------------------------------------------------------- 1 | use wit_parser::{ 2 | Enum, Flags, Function, Record, Resolve, Result_, Tuple, Type, TypeDefKind, TypeId, Variant, 3 | }; 4 | 5 | use crate::{value, wasm::WasmValueError}; 6 | 7 | /// Resolves a [`value::Type`] from the given [`wit_parser::Resolve`] and [`TypeId`]. 8 | /// # Panics 9 | /// Panics if `type_id` is not valid in `resolve`. 10 | pub fn resolve_wit_type(resolve: &Resolve, type_id: TypeId) -> Result { 11 | TypeResolver { resolve }.resolve_type_id(type_id) 12 | } 13 | 14 | /// Resolves a [`value::FuncType`] from the given [`wit_parser::Resolve`] and [`Function`]. 15 | /// # Panics 16 | /// Panics if `function`'s types are not valid in `resolve`. 17 | pub fn resolve_wit_func_type( 18 | resolve: &Resolve, 19 | function: &Function, 20 | ) -> Result { 21 | let resolver = TypeResolver { resolve }; 22 | let params = resolver.resolve_params(&function.params)?; 23 | let results = match &function.results { 24 | wit_parser::Results::Named(results) => resolver.resolve_params(results)?, 25 | wit_parser::Results::Anon(ty) => vec![("".into(), resolver.resolve_type(*ty)?)], 26 | }; 27 | value::FuncType::new(params, results) 28 | } 29 | 30 | struct TypeResolver<'a> { 31 | resolve: &'a Resolve, 32 | } 33 | 34 | type ValueResult = Result; 35 | 36 | impl<'a> TypeResolver<'a> { 37 | fn resolve_type_id(&self, type_id: TypeId) -> ValueResult { 38 | self.resolve(&self.resolve.types.get(type_id).unwrap().kind) 39 | } 40 | 41 | fn resolve_type(&self, ty: Type) -> ValueResult { 42 | self.resolve(&TypeDefKind::Type(ty)) 43 | } 44 | 45 | fn resolve_params( 46 | &self, 47 | params: &[(String, Type)], 48 | ) -> Result, WasmValueError> { 49 | params 50 | .iter() 51 | .map(|(name, ty)| { 52 | let ty = self.resolve_type(*ty)?; 53 | Ok((name.clone(), ty)) 54 | }) 55 | .collect() 56 | } 57 | 58 | fn resolve(&self, mut kind: &'a TypeDefKind) -> ValueResult { 59 | // Recursively resolve any type defs. 60 | while let &TypeDefKind::Type(Type::Id(id)) = kind { 61 | kind = &self.resolve.types.get(id).unwrap().kind; 62 | } 63 | 64 | match kind { 65 | TypeDefKind::Record(record) => self.resolve_record(record), 66 | TypeDefKind::Flags(flags) => self.resolve_flags(flags), 67 | TypeDefKind::Tuple(tuple) => self.resolve_tuple(tuple), 68 | TypeDefKind::Variant(variant) => self.resolve_variant(variant), 69 | TypeDefKind::Enum(enum_) => self.resolve_enum(enum_), 70 | TypeDefKind::Option(some_type) => self.resolve_option(some_type), 71 | TypeDefKind::Result(result) => self.resolve_result(result), 72 | TypeDefKind::List(element_type) => self.resolve_list(element_type), 73 | TypeDefKind::Type(Type::Bool) => Ok(value::Type::BOOL), 74 | TypeDefKind::Type(Type::U8) => Ok(value::Type::U8), 75 | TypeDefKind::Type(Type::U16) => Ok(value::Type::U16), 76 | TypeDefKind::Type(Type::U32) => Ok(value::Type::U32), 77 | TypeDefKind::Type(Type::U64) => Ok(value::Type::U64), 78 | TypeDefKind::Type(Type::S8) => Ok(value::Type::S8), 79 | TypeDefKind::Type(Type::S16) => Ok(value::Type::S16), 80 | TypeDefKind::Type(Type::S32) => Ok(value::Type::S32), 81 | TypeDefKind::Type(Type::S64) => Ok(value::Type::S64), 82 | TypeDefKind::Type(Type::F32) => Ok(value::Type::FLOAT32), 83 | TypeDefKind::Type(Type::F64) => Ok(value::Type::FLOAT64), 84 | TypeDefKind::Type(Type::Char) => Ok(value::Type::CHAR), 85 | TypeDefKind::Type(Type::String) => Ok(value::Type::STRING), 86 | TypeDefKind::Type(Type::Id(_)) => unreachable!(), 87 | other => Err(WasmValueError::UnsupportedType(other.as_str().into())), 88 | } 89 | } 90 | 91 | fn resolve_record(&self, record: &Record) -> ValueResult { 92 | let fields = record 93 | .fields 94 | .iter() 95 | .map(|f| Ok((f.name.as_str(), self.resolve_type(f.ty)?))) 96 | .collect::, _>>()?; 97 | Ok(value::Type::record(fields).unwrap()) 98 | } 99 | 100 | fn resolve_flags(&self, flags: &Flags) -> ValueResult { 101 | let names = flags.flags.iter().map(|f| f.name.as_str()); 102 | Ok(value::Type::flags(names).unwrap()) 103 | } 104 | 105 | fn resolve_tuple(&self, tuple: &Tuple) -> ValueResult { 106 | let types = tuple 107 | .types 108 | .iter() 109 | .map(|ty| self.resolve_type(*ty)) 110 | .collect::, _>>()?; 111 | Ok(value::Type::tuple(types).unwrap()) 112 | } 113 | 114 | fn resolve_variant(&self, variant: &Variant) -> ValueResult { 115 | let cases = variant 116 | .cases 117 | .iter() 118 | .map(|case| { 119 | Ok(( 120 | case.name.as_str(), 121 | case.ty.map(|ty| self.resolve_type(ty)).transpose()?, 122 | )) 123 | }) 124 | .collect::, _>>()?; 125 | Ok(value::Type::variant(cases).unwrap()) 126 | } 127 | 128 | fn resolve_enum(&self, enum_: &Enum) -> ValueResult { 129 | let cases = enum_.cases.iter().map(|c| c.name.as_str()); 130 | Ok(value::Type::enum_ty(cases).unwrap()) 131 | } 132 | 133 | fn resolve_option(&self, some_type: &Type) -> ValueResult { 134 | let some = self.resolve_type(*some_type)?; 135 | Ok(value::Type::option(some)) 136 | } 137 | 138 | fn resolve_result(&self, result: &Result_) -> ValueResult { 139 | let ok = result.ok.map(|ty| self.resolve_type(ty)).transpose()?; 140 | let err = result.err.map(|ty| self.resolve_type(ty)).transpose()?; 141 | Ok(value::Type::result(ok, err)) 142 | } 143 | 144 | fn resolve_list(&self, element_type: &Type) -> ValueResult { 145 | let element_type = self.resolve_type(*element_type)?; 146 | Ok(value::Type::list(element_type)) 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use wit_parser::UnresolvedPackage; 153 | 154 | use super::*; 155 | 156 | #[test] 157 | fn resolve_wit_type_smoke_test() { 158 | let unresolved = UnresolvedPackage::parse( 159 | "test.wit".as_ref(), 160 | r#" 161 | package test:types; 162 | interface types { 163 | type uint8 = u8; 164 | } 165 | "#, 166 | ) 167 | .unwrap(); 168 | let mut resolve = Resolve::new(); 169 | resolve.push(unresolved).unwrap(); 170 | 171 | let (type_id, _) = resolve.types.iter().next().unwrap(); 172 | let ty = resolve_wit_type(&resolve, type_id).unwrap(); 173 | assert_eq!(ty, value::Type::U8); 174 | } 175 | 176 | #[test] 177 | fn resolve_wit_func_type_smoke_test() { 178 | let unresolved = UnresolvedPackage::parse( 179 | "test.wit".as_ref(), 180 | r#" 181 | package test:types; 182 | interface types { 183 | type uint8 = u8; 184 | no-results: func(a: uint8, b: string); 185 | one-result: func(c: uint8, d: string) -> uint8; 186 | named-results: func(e: uint8, f: string) -> (x: u8, y: string); 187 | } 188 | "#, 189 | ) 190 | .unwrap(); 191 | let mut resolve = Resolve::new(); 192 | resolve.push(unresolved).unwrap(); 193 | 194 | for (func_name, expected_display) in [ 195 | ("no-results", "func(a: u8, b: string)"), 196 | ("one-result", "func(c: u8, d: string) -> u8"), 197 | ( 198 | "named-results", 199 | "func(e: u8, f: string) -> (x: u8, y: string)", 200 | ), 201 | ] { 202 | let function = resolve 203 | .interfaces 204 | .iter() 205 | .flat_map(|(_, i)| &i.functions) 206 | .find_map(|(name, function)| (name == func_name).then_some(function)) 207 | .unwrap(); 208 | let ty = resolve_wit_func_type(&resolve, function).unwrap(); 209 | assert_eq!(ty.to_string(), expected_display, "for {function:?}"); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/wasm/fmt.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::{ 4 | wasm::{WasmFunc, WasmType, WasmTypeKind}, 5 | writer::Writer, 6 | WasmValue, 7 | }; 8 | 9 | /// Implements a WAVE-formatted [`Display`] for a [`WasmType`]. 10 | pub struct DisplayType<'a, T: WasmType>(pub &'a T); 11 | 12 | impl<'a, T: WasmType> Display for DisplayType<'a, T> { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | let ty = self.0; 15 | match ty.kind() { 16 | WasmTypeKind::List => { 17 | write!(f, "list<{}>", DisplayType(&ty.list_element_type().unwrap())) 18 | } 19 | WasmTypeKind::Record => { 20 | f.write_str("record { ")?; 21 | for (idx, (name, field_type)) in ty.record_fields().enumerate() { 22 | if idx != 0 { 23 | f.write_str(", ")?; 24 | } 25 | write!(f, "{name}: {}", DisplayType(&field_type))?; 26 | } 27 | f.write_str(" }") 28 | } 29 | WasmTypeKind::Tuple => { 30 | f.write_str("tuple<")?; 31 | for (idx, ty) in ty.tuple_element_types().enumerate() { 32 | if idx != 0 { 33 | f.write_str(", ")?; 34 | } 35 | write!(f, "{}", DisplayType(&ty))?; 36 | } 37 | f.write_str(">") 38 | } 39 | WasmTypeKind::Variant => { 40 | f.write_str("variant { ")?; 41 | for (idx, (name, payload)) in ty.variant_cases().enumerate() { 42 | if idx != 0 { 43 | f.write_str(", ")?; 44 | } 45 | f.write_str(name.as_ref())?; 46 | if let Some(ty) = payload { 47 | write!(f, "({})", DisplayType(&ty))?; 48 | } 49 | } 50 | f.write_str(" }") 51 | } 52 | WasmTypeKind::Enum => { 53 | f.write_str("enum { ")?; 54 | for (idx, name) in ty.enum_cases().enumerate() { 55 | if idx != 0 { 56 | f.write_str(", ")?; 57 | } 58 | f.write_str(name.as_ref())?; 59 | } 60 | f.write_str(" }") 61 | } 62 | WasmTypeKind::Option => { 63 | write!( 64 | f, 65 | "option<{}>", 66 | DisplayType(&ty.option_some_type().unwrap()) 67 | ) 68 | } 69 | WasmTypeKind::Result => { 70 | f.write_str("result")?; 71 | match ty.result_types().unwrap() { 72 | (None, None) => Ok(()), 73 | (None, Some(err)) => write!(f, "<_, {}>", DisplayType(&err)), 74 | (Some(ok), None) => write!(f, "<{}>", DisplayType(&ok)), 75 | (Some(ok), Some(err)) => { 76 | write!(f, "<{}, {}>", DisplayType(&ok), DisplayType(&err)) 77 | } 78 | } 79 | } 80 | WasmTypeKind::Flags => { 81 | f.write_str("flags { ")?; 82 | for (idx, name) in ty.flags_names().enumerate() { 83 | if idx != 0 { 84 | f.write_str(", ")?; 85 | } 86 | f.write_str(name.as_ref())?; 87 | } 88 | f.write_str(" }") 89 | } 90 | simple => Display::fmt(&simple, f), 91 | } 92 | } 93 | } 94 | 95 | /// Implements a WAVE-formatted [`Display`] for a [`WasmValue`]. 96 | pub struct DisplayValue<'a, T: WasmValue>(pub &'a T); 97 | 98 | impl<'a, T: WasmValue> Display for DisplayValue<'a, T> { 99 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 100 | let mut buf = vec![]; 101 | Writer::new(&mut buf) 102 | .write_value(self.0) 103 | .map_err(|_| std::fmt::Error)?; 104 | f.write_str(String::from_utf8_lossy(&buf).as_ref()) 105 | } 106 | } 107 | 108 | /// Implements a WAVE-formatted [`Display`] for a [`WasmFunc`]. 109 | pub struct DisplayFunc(pub T); 110 | 111 | impl Display for DisplayFunc { 112 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 113 | f.write_str("func(")?; 114 | let mut param_names = self.0.param_names(); 115 | for (idx, ty) in self.0.params().enumerate() { 116 | if idx != 0 { 117 | f.write_str(", ")?; 118 | } 119 | if let Some(name) = param_names.next() { 120 | write!(f, "{name}: ")?; 121 | } 122 | DisplayType(&ty).fmt(f)? 123 | } 124 | f.write_str(")")?; 125 | 126 | let results = self.0.results().collect::>(); 127 | if results.is_empty() { 128 | return Ok(()); 129 | } 130 | 131 | let mut result_names = self.0.result_names(); 132 | if results.len() == 1 { 133 | let ty = DisplayType(&results.into_iter().next().unwrap()).to_string(); 134 | if let Some(name) = result_names.next() { 135 | write!(f, " -> ({name}: {ty})") 136 | } else { 137 | write!(f, " -> {ty}") 138 | } 139 | } else { 140 | f.write_str(" -> (")?; 141 | for (idx, ty) in results.into_iter().enumerate() { 142 | if idx != 0 { 143 | f.write_str(", ")?; 144 | } 145 | if let Some(name) = result_names.next() { 146 | write!(f, "{name}: ")?; 147 | } 148 | DisplayType(&ty).fmt(f)?; 149 | } 150 | f.write_str(")") 151 | } 152 | } 153 | } 154 | 155 | /// Implements a WAVE-formatted [`Display`] for [`WasmValue`] func arguments. 156 | pub struct DisplayFuncArgs<'a, T: WasmValue>(pub &'a [T]); 157 | 158 | impl<'a, T: WasmValue> Display for DisplayFuncArgs<'a, T> { 159 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 160 | f.write_str("(")?; 161 | for (idx, v) in self.0.iter().enumerate() { 162 | if idx != 0 { 163 | f.write_str(", ")?; 164 | } 165 | DisplayValue(v).fmt(f)?; 166 | } 167 | f.write_str(")") 168 | } 169 | } 170 | 171 | /// Implements a WAVE-formatted [`Display`] for [`WasmValue`] func results. 172 | pub struct DisplayFuncResults<'a, T: WasmValue>(pub &'a [T]); 173 | 174 | impl<'a, T: WasmValue> Display for DisplayFuncResults<'a, T> { 175 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 176 | if self.0.len() == 1 { 177 | DisplayValue(&self.0[0]).fmt(f) 178 | } else { 179 | DisplayFuncArgs(self.0).fmt(f) 180 | } 181 | } 182 | } 183 | 184 | #[cfg(test)] 185 | mod tests { 186 | use crate::value::Type; 187 | 188 | #[test] 189 | fn test_type_display() { 190 | for (ty, expected) in [ 191 | (Type::U8, "u8"), 192 | (Type::list(Type::U8), "list"), 193 | ( 194 | Type::record([("a", Type::U8), ("b", Type::STRING)]).unwrap(), 195 | "record { a: u8, b: string }", 196 | ), 197 | ( 198 | Type::tuple([Type::U8, Type::BOOL]).unwrap(), 199 | "tuple", 200 | ), 201 | ( 202 | Type::variant([("off", None), ("on", Some(Type::U8))]).unwrap(), 203 | "variant { off, on(u8) }", 204 | ), 205 | ( 206 | Type::enum_ty(["east", "west"]).unwrap(), 207 | "enum { east, west }", 208 | ), 209 | (Type::option(Type::U8), "option"), 210 | (Type::result(None, None), "result"), 211 | (Type::result(Some(Type::U8), None), "result"), 212 | (Type::result(None, Some(Type::STRING)), "result<_, string>"), 213 | ( 214 | Type::result(Some(Type::U8), Some(Type::STRING)), 215 | "result", 216 | ), 217 | ( 218 | Type::flags(["read", "write"]).unwrap(), 219 | "flags { read, write }", 220 | ), 221 | ] { 222 | assert_eq!(ty.to_string(), expected); 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /src/value/convert.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | value::ValueEnum, 3 | wasm::{WasmType, WasmTypeKind}, 4 | WasmValue, 5 | }; 6 | 7 | use super::{Type, Value}; 8 | 9 | pub fn from_wasm_type(ty: &impl WasmType) -> Option { 10 | if let Some(ty) = Type::simple(ty.kind()) { 11 | return Some(ty); 12 | } 13 | Some(match ty.kind() { 14 | WasmTypeKind::List => Type::list(from_wasm_type(&ty.list_element_type()?)?), 15 | WasmTypeKind::Record => Type::record( 16 | ty.record_fields() 17 | .map(|(name, ty)| Some((name, from_wasm_type(&ty)?))) 18 | .collect::>>()?, 19 | )?, 20 | WasmTypeKind::Tuple => Type::tuple( 21 | ty.tuple_element_types() 22 | .map(|ty| from_wasm_type(&ty)) 23 | .collect::>>()?, 24 | )?, 25 | WasmTypeKind::Variant => Type::variant( 26 | ty.variant_cases() 27 | .map(|(name, payload)| Some((name, from_optional_wasm_type(payload)?))) 28 | .collect::>>()?, 29 | )?, 30 | WasmTypeKind::Enum => Type::enum_ty(ty.enum_cases())?, 31 | WasmTypeKind::Option => Type::option(from_wasm_type(&ty.option_some_type()?)?), 32 | WasmTypeKind::Result => { 33 | let (ok, err) = ty.result_types()?; 34 | Type::result(from_optional_wasm_type(ok)?, from_optional_wasm_type(err)?) 35 | } 36 | WasmTypeKind::Flags => Type::flags(ty.flags_names())?, 37 | _ => return None, 38 | }) 39 | } 40 | 41 | fn from_optional_wasm_type(ty: Option) -> Option> { 42 | Some(match ty { 43 | Some(ty) => Some(from_wasm_type(&ty)?), 44 | None => None, 45 | }) 46 | } 47 | 48 | trait ValueTyped { 49 | fn value_type() -> Type; 50 | } 51 | 52 | macro_rules! impl_primitives { 53 | ($Self:ty, $(($case:ident, $ty:ty)),*) => { 54 | $( 55 | impl ValueTyped for $ty { 56 | fn value_type() -> Type { 57 | Type::must_simple(WasmTypeKind::$case) 58 | } 59 | } 60 | 61 | impl From<$ty> for $Self { 62 | fn from(value: $ty) -> Self { 63 | Self(ValueEnum::$case(value)) 64 | } 65 | } 66 | )* 67 | }; 68 | } 69 | 70 | impl_primitives!( 71 | Value, 72 | (Bool, bool), 73 | (S8, i8), 74 | (S16, i16), 75 | (S32, i32), 76 | (S64, i64), 77 | (U8, u8), 78 | (U16, u16), 79 | (U32, u32), 80 | (U64, u64), 81 | (Float32, f32), 82 | (Float64, f64), 83 | (Char, char) 84 | ); 85 | 86 | impl ValueTyped for String { 87 | fn value_type() -> Type { 88 | Type::STRING 89 | } 90 | } 91 | 92 | impl From for Value { 93 | fn from(value: String) -> Self { 94 | Self(ValueEnum::String(value.into())) 95 | } 96 | } 97 | 98 | impl<'a> ValueTyped for &'a str { 99 | fn value_type() -> Type { 100 | String::value_type() 101 | } 102 | } 103 | 104 | impl<'a> From<&'a str> for Value { 105 | fn from(value: &'a str) -> Self { 106 | value.to_string().into() 107 | } 108 | } 109 | 110 | impl ValueTyped for [T; N] { 111 | fn value_type() -> Type { 112 | Type::list(T::value_type()) 113 | } 114 | } 115 | 116 | impl> From<[T; N]> for Value { 117 | fn from(values: [T; N]) -> Self { 118 | let ty = Vec::::value_type(); 119 | let values = values.into_iter().map(Into::into); 120 | Value::make_list(&ty, values).unwrap() 121 | } 122 | } 123 | 124 | impl ValueTyped for Vec { 125 | fn value_type() -> Type { 126 | Type::list(T::value_type()) 127 | } 128 | } 129 | 130 | impl> From> for Value { 131 | fn from(values: Vec) -> Self { 132 | let ty = Vec::::value_type(); 133 | let values = values.into_iter().map(Into::into); 134 | Value::make_list(&ty, values).unwrap() 135 | } 136 | } 137 | 138 | impl ValueTyped for Option { 139 | fn value_type() -> Type { 140 | Type::option(T::value_type()) 141 | } 142 | } 143 | 144 | impl> From> for Value { 145 | fn from(value: Option) -> Self { 146 | let ty = Option::::value_type(); 147 | Value::make_option(&ty, value.map(Into::into)).unwrap() 148 | } 149 | } 150 | 151 | impl ValueTyped for Result { 152 | fn value_type() -> Type { 153 | Type::result(Some(T::value_type()), Some(U::value_type())) 154 | } 155 | } 156 | 157 | impl, U: ValueTyped + Into> From> for Value { 158 | fn from(value: Result) -> Self { 159 | let ty = Result::::value_type(); 160 | let value = match value { 161 | Ok(ok) => Ok(Some(ok.into())), 162 | Err(err) => Err(Some(err.into())), 163 | }; 164 | Value::make_result(&ty, value).unwrap() 165 | } 166 | } 167 | 168 | macro_rules! impl_tuple { 169 | ($(($($var:ident),*)),*) => { 170 | $( 171 | impl<$($var: ValueTyped),*> ValueTyped for ($($var),*,) { 172 | fn value_type() -> Type { 173 | Type::tuple(vec![$($var::value_type()),*]).unwrap() 174 | } 175 | } 176 | 177 | #[allow(non_snake_case)] 178 | impl<$($var: ValueTyped + Into),*> From<($($var),*,)> for Value { 179 | fn from(($($var),*,): ($($var),*,)) -> Value { 180 | let ty = <($($var),*,)>::value_type(); 181 | $( 182 | let $var = $var.into(); 183 | )* 184 | Value::make_tuple(&ty, vec![$($var),*]).unwrap() 185 | } 186 | } 187 | 188 | )* 189 | }; 190 | } 191 | 192 | impl_tuple!( 193 | (T1), 194 | (T1, T2), 195 | (T1, T2, T3), 196 | (T1, T2, T3, T4), 197 | (T1, T2, T3, T4, T5), 198 | (T1, T2, T3, T4, T5, T6), 199 | (T1, T2, T3, T4, T5, T6, T7), 200 | (T1, T2, T3, T4, T5, T6, T7, T8), 201 | (T1, T2, T3, T4, T5, T6, T7, T8, T9), 202 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), 203 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), 204 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), 205 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), 206 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), 207 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), 208 | (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) 209 | ); 210 | 211 | #[cfg(test)] 212 | mod tests { 213 | use crate::value::{Type, Value}; 214 | 215 | #[test] 216 | fn type_conversion_round_trips() { 217 | for ty in [ 218 | Type::BOOL, 219 | Type::U8, 220 | Type::FLOAT32, 221 | Type::STRING, 222 | Type::list(Type::BOOL), 223 | Type::record([("a", Type::BOOL)]).unwrap(), 224 | Type::tuple([Type::BOOL]).unwrap(), 225 | Type::variant([("a", None), ("b", Some(Type::BOOL))]).unwrap(), 226 | Type::enum_ty(["north", "south"]).unwrap(), 227 | Type::option(Type::BOOL), 228 | Type::result(Some(Type::BOOL), None), 229 | Type::flags(["read", "write"]).unwrap(), 230 | ] { 231 | let got = Type::from_wasm_type(&ty).unwrap(); 232 | assert_eq!(got, ty); 233 | } 234 | } 235 | 236 | #[test] 237 | fn value_conversions() { 238 | for (val, expect) in [ 239 | (1u8.into(), "1"), 240 | ((-123i8).into(), "-123"), 241 | (f32::NAN.into(), "nan"), 242 | (f64::NEG_INFINITY.into(), "-inf"), 243 | ('x'.into(), "'x'"), 244 | ("str".into(), "\"str\""), 245 | (vec![1, 2, 3].into(), "[1, 2, 3]"), 246 | ([1, 2, 3].into(), "[1, 2, 3]"), 247 | (['a'; 3].into(), "['a', 'a', 'a']"), 248 | (Some(1).into(), "some(1)"), 249 | (None::.into(), "none"), 250 | (Ok::(1).into(), "ok(1)"), 251 | (Err::("oops".into()).into(), "err(\"oops\")"), 252 | ((1,).into(), "(1)"), 253 | ((1, "str", [9; 2]).into(), "(1, \"str\", [9, 9])"), 254 | ] { 255 | let val: Value = val; 256 | let got = crate::to_string(&val).unwrap(); 257 | assert_eq!(got, expect); 258 | } 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | | ⚠️ Migrated to https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-wave ⚠️ | 2 | |--| 3 | 4 | 5 | # WAVE: Web Assembly Value Encoding 6 | 7 | WAVE is a human-oriented text encoding of WebAssembly Component Model values. It is designed to be consistent with the 8 | [WIT IDL format](https://github.com/WebAssembly/component-model/blob/673d5c43c3cc0f4aeb8996a5c0931af623f16808/design/mvp/WIT.md). 9 | 10 | |Type|Example Values 11 | |---|--- 12 | |Bools|`true`, `false` 13 | |Integers|`123`, `-9` 14 | |Floats|`3.14`, `6.022e+23`, `nan`, `-inf` 15 | |Chars|`'x'`, `'☃︎'`, `'\''`, `'\u{0}'` 16 | |Strings|`"abc\t123"` 17 | |Tuples|`("abc", 123)` 18 | |Lists|`[1, 2, 3]` 19 | |Records|`{field-a: 1, field-b: "two"}` 20 | |Variants|`days(30)`, `forever` 21 | |Enums|`south`, `west` 22 | |Options|`"flat some"`, `some("explicit some")`, `none` 23 | |Results|`"flat ok"`, `ok("explicit ok")`, `err("oops")` 24 | |Flags|`{read, write}`, `{}` 25 | 26 | ## Usage 27 | 28 | ```rust 29 | use wasmtime::component::{Type, Val}; 30 | 31 | let val: Val = wasm_wave::from_str(&Type::String, "\"👋 Hello, world! 👋\"").unwrap(); 32 | println!("{}", wasm_wave::to_string(&val).unwrap()); 33 | ``` 34 | 35 | → `"👋 Hello, world! 👋"` 36 | 37 | ## Encoding 38 | 39 | Values are encoded as Unicode text. UTF-8 should be used wherever an interoperable binary encoding is required. 40 | 41 | ### Whitespace 42 | 43 | Whitespace is _insignificant between tokens_ and _significant within tokens_: keywords, labels, chars, and strings. 44 | 45 | ### Comments 46 | 47 | Comments start with `//` and run to the end of the line. 48 | 49 | ### Keywords 50 | 51 | Several tokens are reserved WAVE keywords: `true`, `false`, `inf`, `nan`, `some`, `none`, `ok`, `err`. Variant or enum cases that match one of these keywords must be prefixed with `%`. 52 | 53 | ### Labels 54 | 55 | Kebab-case labels are used for record fields, variant cases, enum cases, and flags. Labels use ASCII alphanumeric characters and hyphens, following the [Wit identifier syntax](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#identifiers): 56 | 57 | - Labels consist of one or more hypen-separated words. 58 | - `one`, `two-words` 59 | - Words consist of one ASCII letter followed by any number of ASCII alphanumeric characters. 60 | - `q`, `abc123` 61 | - Each word can contain lowercase or uppercase characters but not both; each word in a label can use a different (single) case. 62 | - `HTTP3`, `method-GET` 63 | - Any label may be prefixed with `%`; this is not part of the label itself but allows for representing labels that would otherwise be parsed as keywords. 64 | - `%err`, `%non-keyword` 65 | 66 | ### Bools 67 | 68 | Bools are encoded as one of the keywords `false` or `true`. 69 | 70 | ### Integers 71 | 72 | Integers are encoded as base-10 numbers. 73 | 74 | > TBD: hex/bin repr? e.g. `0xab`, `0b1011` 75 | 76 | ### Floats 77 | 78 | Floats are encoded as JSON numbers or one of the keywords `nan`, (not a number) `inf` (infinity), or `-inf` (negative infinity). 79 | 80 | ### Chars 81 | 82 | Chars are encoded as `''`, where `` is one of: 83 | 84 | - a single [Unicode Scalar Value](https://unicode.org/glossary/#unicode_scalar_value) 85 | - one of these escapes: 86 | - `\'` → `'` 87 | - `\"` → `"` 88 | - `\\` → `\` 89 | - `\t` → U+9 (HT) 90 | - `\n` → U+A (LF) 91 | - `\r` → U+D (CR) 92 | - `\u{···}` → U+··· (where `···` is a hexadecimal Unicode Scalar Value) 93 | 94 | Escaping newline (`\n`), `\`, and `'` is mandatory for chars. 95 | 96 | ### Strings 97 | 98 | Strings are encoded as a double-quote-delimited sequence of ``s (as for [Chars](#chars)). 99 | 100 | Escaping newline (`\n`), `\`, and `"` is mandatory for strings. 101 | 102 | ### Multiline Strings 103 | 104 | A multiline string begins with `"""` followed immediately by a line break (`\n` or `\r\n`) and ends with a line break, zero or more spaces, and `"""`. The number of spaces immediately preceding the ending `"""` determines the indent level of the entire multiline string. Every other line break in the string must be followed by at least this many spaces which are then omitted ("dedented") from the decoded string. 105 | 106 | Each line break in the encoded string except for the first and last is decoded as a newline character (`\n`). 107 | 108 | Escaping `\` is mandatory for multiline strings. Escaping carriage return (`\r`) is mandatory immediately before a literal newline character (`\n`) if it is to be retained. Escaping `"` is mandatory where necessary to break up any sequence of `"""` within a string, even if the first `"` is escaped (i.e. `\"""` is prohibited). 109 | 110 | ```python 111 | """ 112 | A single line 113 | """ 114 | ``` 115 | → `"A single line"` 116 | 117 | ```python 118 | """ 119 | Indentation determined 120 | by ending delimiter 121 | """ 122 | ``` 123 | → 124 | ```clike 125 | " Indentation determined\n by ending delimiter" 126 | ``` 127 | 128 | ```python 129 | """ 130 | Must escape carriage return at end of line: \r 131 | Must break up double quote triplets: ""\"" 132 | """ 133 | ``` 134 | → 135 | ```clike 136 | "Must escape carriage return at end of line: \r\nMust break up double quote triplets: \"\"\"\"" 137 | ``` 138 | 139 | ### Tuples 140 | 141 | Tuples are encoded as a parenthesized sequence of comma-separated values. Trailing commas are permitted. 142 | 143 | `tuple` → `(123, "abc")` 144 | 145 | ### Lists 146 | 147 | Lists are encoded as a square-braketed sequence of comma-separated values. Trailing commas are permitted. 148 | 149 | `list` → `[]`, `['a', 'b', 'c']` 150 | 151 | ### Records 152 | 153 | Records are encoded as curly-braced set of comma-separated record entries. Trailing commas are permitted. Each record entry consists of a field label, a colon, and a value. Fields may be present in any order. Record entries with the `option`-typed value `none` may be omitted; if all of a record's fields are omitted in this way the "empty" record must be encoded as `{:}` (to disambiguate from an empty `flags` value). 154 | 155 | ```clike 156 | record example { 157 | must-have: u8, 158 | optional: option, 159 | } 160 | ``` 161 | 162 | → `{must-have: 123}` = `{must-have: 123, optional: none,}` 163 | 164 | ```clike 165 | record all-optional { 166 | optional: option, 167 | } 168 | ``` 169 | 170 | → `{:}` = `{optional: none}` 171 | 172 | > Note: Field labels _may_ be prefixed with `%` but this is never required. 173 | 174 | ### Variants 175 | 176 | Variants are encoded as a case label. If the case has a payload, the label is followed by the parenthesized case payload value. 177 | 178 | If a variant case matches a WAVE keyword it must be prefixed with `%`. 179 | 180 | ```clike 181 | variant response { 182 | empty, 183 | body(list), 184 | err(string), 185 | } 186 | ``` 187 | 188 | → `empty`, `body([79, 75])`, `%err("oops")` 189 | 190 | ### Enums 191 | 192 | Enums are encoded as a case label. 193 | 194 | If an enum case matches a WAVE keyword it must be prefixed with `%`. 195 | 196 | `enum status { ok, not-found }` → `%ok`, `not-found` 197 | 198 | ### Options 199 | 200 | Options may be encoded in their variant form (e.g. `some(...)` or `none`). A `some` value may also be encoded as the "flat" payload value itself, but only if the payload is not an option or result type. 201 | 202 | - `option` → `123` = `some(123)` 203 | 204 | ### Results 205 | 206 | Results may be encoded in their variant form (e.g. `ok(...)`, `err("oops")`). An `ok` value may also be encoded as the "flat" payload value itself, but only if it has a payload which is not an option or result type. 207 | 208 | - `result` → `123` = `ok(123)` 209 | - `result<_, string>` → `ok`, `err("oops")` 210 | - `result` → `ok`, `err` 211 | 212 | ### Flags 213 | 214 | Flags are encoded as a curly-braced set of comma-separated flag labels in any order. Trailing commas are permitted. 215 | 216 | `flags perms { read, write, exec }` → `{write, read,}` 217 | 218 | > Note: Flags _may_ be prefixed with `%` but this is never required. 219 | 220 | > TBD: Allow record form? `{read: true, write: true, exec: false}` 221 | 222 | ### Resources 223 | 224 | > TBD (`()`?) 225 | 226 | ## Appendix: Function calls 227 | 228 | Some applications may benefit from a standard way to encode function calls and/or results, described here. 229 | 230 | Function calls can be encoded as some application-dependent function identifier (such as a kebab-case label) followed by parenthesized function arguments. 231 | 232 | If function results need to be encoded along with the call, they can be separated from the call by `->`. 233 | 234 | ```clike 235 | my-func("param") 236 | 237 | with-result() -> ok("result") 238 | ``` 239 | 240 | ### Function arguments 241 | 242 | Arguments are encoded as a sequence of comma-separated values. 243 | 244 | Any number of `option` `none` values at the end of the sequence may be omitted. 245 | 246 | ```clike 247 | // f: func(a: option, b: option, c: option) 248 | // all equivalent: 249 | f(some(1)) 250 | f(some(1), none) 251 | f(some(1), none, none) 252 | ``` 253 | 254 | > TBD: Named-parameter encoding? e.g. 255 | > `my-func(named-param: 1)` 256 | > Could allow omitting "middle" `none` params. 257 | 258 | ### Function results 259 | 260 | Results are encoded in one of several ways depending on the number of result values: 261 | 262 | - Any number of result values may be encoded as a parenthesized sequence of comma-separated result entries. Each result entry consists of a label (for named results) or zero-based index number, a colon, and the result value. Result entry ordering must match the function definition. 263 | - Zero result values are encoded as `()` or omitted entirely. 264 | - A single result value may be encoded as the "flat" result value itself. 265 | 266 | ```clike 267 | -> () 268 | 269 | -> some("single result") 270 | // or 271 | -> (0: some("single result")) 272 | 273 | -> (result-a: "abc", result-b: 123) 274 | ``` 275 | 276 | --- 277 | 278 | :ocean: 279 | -------------------------------------------------------------------------------- /src/value/ty.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, sync::Arc}; 2 | 3 | use crate::wasm::{maybe_unwrap_type, WasmType, WasmTypeKind}; 4 | 5 | /// The [`WasmType`] of a [`Value`](super::Value). 6 | #[derive(Clone, Debug, PartialEq)] 7 | pub struct Type(pub(super) TypeEnum); 8 | 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub(super) enum TypeEnum { 11 | Simple(SimpleType), 12 | List(Arc), 13 | Record(Arc), 14 | Tuple(Arc), 15 | Variant(Arc), 16 | Enum(Arc), 17 | Option(Arc), 18 | Result(Arc), 19 | Flags(Arc), 20 | } 21 | 22 | #[allow(missing_docs)] 23 | impl Type { 24 | pub const BOOL: Self = Self::must_simple(WasmTypeKind::Bool); 25 | pub const S8: Self = Self::must_simple(WasmTypeKind::S8); 26 | pub const S16: Self = Self::must_simple(WasmTypeKind::S16); 27 | pub const S32: Self = Self::must_simple(WasmTypeKind::S32); 28 | pub const S64: Self = Self::must_simple(WasmTypeKind::S64); 29 | pub const U8: Self = Self::must_simple(WasmTypeKind::U8); 30 | pub const U16: Self = Self::must_simple(WasmTypeKind::U16); 31 | pub const U32: Self = Self::must_simple(WasmTypeKind::U32); 32 | pub const U64: Self = Self::must_simple(WasmTypeKind::U64); 33 | pub const FLOAT32: Self = Self::must_simple(WasmTypeKind::Float32); 34 | pub const FLOAT64: Self = Self::must_simple(WasmTypeKind::Float64); 35 | pub const CHAR: Self = Self::must_simple(WasmTypeKind::Char); 36 | pub const STRING: Self = Self::must_simple(WasmTypeKind::String); 37 | 38 | /// Returns the simple type of the given `kind`. Returns None if the kind 39 | /// represents a parameterized type. 40 | pub fn simple(kind: WasmTypeKind) -> Option { 41 | is_simple(kind).then_some(Self(TypeEnum::Simple(SimpleType(kind)))) 42 | } 43 | 44 | #[doc(hidden)] 45 | pub const fn must_simple(kind: WasmTypeKind) -> Self { 46 | if !is_simple(kind) { 47 | panic!("kind is not simple"); 48 | } 49 | Self(TypeEnum::Simple(SimpleType(kind))) 50 | } 51 | 52 | /// Returns a list type with the given element type. 53 | pub fn list(element_type: impl Into) -> Self { 54 | let element = element_type.into(); 55 | Self(TypeEnum::List(Arc::new(ListType { element }))) 56 | } 57 | 58 | /// Returns a record type with the given field types. Returns None if 59 | /// `fields` is empty. 60 | pub fn record>>( 61 | field_types: impl IntoIterator, 62 | ) -> Option { 63 | let fields = field_types 64 | .into_iter() 65 | .map(|(name, ty)| (name.into(), ty)) 66 | .collect::>(); 67 | if fields.is_empty() { 68 | return None; 69 | } 70 | Some(Self(TypeEnum::Record(Arc::new(RecordType { fields })))) 71 | } 72 | 73 | /// Returns a tuple type with the given element types. Returns None if 74 | /// `element_types` is empty. 75 | pub fn tuple(element_types: impl Into>) -> Option { 76 | let elements = element_types.into(); 77 | if elements.is_empty() { 78 | return None; 79 | } 80 | Some(Self(TypeEnum::Tuple(Arc::new(TupleType { elements })))) 81 | } 82 | 83 | /// Returns a variant type with the given case names and optional payloads. 84 | /// Returns None if `cases` is empty. 85 | pub fn variant>>( 86 | cases: impl IntoIterator)>, 87 | ) -> Option { 88 | let cases = cases 89 | .into_iter() 90 | .map(|(name, ty)| (name.into(), ty)) 91 | .collect::>(); 92 | if cases.is_empty() { 93 | return None; 94 | } 95 | Some(Self(TypeEnum::Variant(Arc::new(VariantType { cases })))) 96 | } 97 | 98 | /// Returns an enum type with the given case names. Returns None if `cases` 99 | /// is empty. 100 | pub fn enum_ty>>(cases: impl IntoIterator) -> Option { 101 | let cases = cases.into_iter().map(Into::into).collect::>(); 102 | if cases.is_empty() { 103 | return None; 104 | } 105 | Some(Self(TypeEnum::Enum(Arc::new(EnumType { cases })))) 106 | } 107 | 108 | /// Returns an option type with the given "some" type. 109 | pub fn option(some: Self) -> Self { 110 | Self(TypeEnum::Option(Arc::new(OptionType { some }))) 111 | } 112 | 113 | /// Returns a result type with the given optional "ok" and "err" payloads. 114 | pub fn result(ok: Option, err: Option) -> Self { 115 | Self(TypeEnum::Result(Arc::new(ResultType { ok, err }))) 116 | } 117 | 118 | /// Returns a flags type with the given flag names. Returns None if `flags` 119 | /// is empty. 120 | pub fn flags>>(flags: impl IntoIterator) -> Option { 121 | let flags = flags.into_iter().map(Into::into).collect::>(); 122 | if flags.is_empty() { 123 | return None; 124 | } 125 | Some(Self(TypeEnum::Flags(Arc::new(FlagsType { flags })))) 126 | } 127 | 128 | /// Returns a [`Type`] matching the given [`WasmType`]. Returns None if the 129 | /// given type is unsupported or otherwise invalid. 130 | pub fn from_wasm_type(ty: &impl WasmType) -> Option { 131 | super::convert::from_wasm_type(ty) 132 | } 133 | } 134 | 135 | #[derive(Debug, Clone, Copy, PartialEq)] 136 | pub struct SimpleType(WasmTypeKind); 137 | 138 | const fn is_simple(kind: WasmTypeKind) -> bool { 139 | use WasmTypeKind::*; 140 | matches!( 141 | kind, 142 | Bool | S8 | S16 | S32 | S64 | U8 | U16 | U32 | U64 | Float32 | Float64 | Char | String 143 | ) 144 | } 145 | 146 | #[derive(Debug, Clone, PartialEq)] 147 | pub struct ListType { 148 | pub(super) element: Type, 149 | } 150 | 151 | #[derive(Debug, PartialEq)] 152 | pub struct RecordType { 153 | pub(super) fields: Box<[(Box, Type)]>, 154 | } 155 | 156 | #[derive(Debug, PartialEq)] 157 | pub struct TupleType { 158 | pub(super) elements: Box<[Type]>, 159 | } 160 | 161 | #[derive(Debug, PartialEq)] 162 | pub struct VariantType { 163 | pub(super) cases: Box<[(Box, Option)]>, 164 | } 165 | 166 | #[derive(Debug, PartialEq)] 167 | pub struct EnumType { 168 | pub(super) cases: Box<[Box]>, 169 | } 170 | 171 | #[derive(Debug, PartialEq)] 172 | pub struct OptionType { 173 | pub(super) some: Type, 174 | } 175 | 176 | #[derive(Debug, PartialEq)] 177 | pub struct ResultType { 178 | pub(super) ok: Option, 179 | pub(super) err: Option, 180 | } 181 | 182 | #[derive(Debug, PartialEq)] 183 | pub struct FlagsType { 184 | pub(super) flags: Box<[Box]>, 185 | } 186 | 187 | impl WasmType for Type { 188 | fn kind(&self) -> WasmTypeKind { 189 | match self.0 { 190 | TypeEnum::Simple(simple) => simple.0, 191 | TypeEnum::List(_) => WasmTypeKind::List, 192 | TypeEnum::Record(_) => WasmTypeKind::Record, 193 | TypeEnum::Tuple(_) => WasmTypeKind::Tuple, 194 | TypeEnum::Variant(_) => WasmTypeKind::Variant, 195 | TypeEnum::Enum(_) => WasmTypeKind::Enum, 196 | TypeEnum::Option(_) => WasmTypeKind::Option, 197 | TypeEnum::Result(_) => WasmTypeKind::Result, 198 | TypeEnum::Flags(_) => WasmTypeKind::Flags, 199 | } 200 | } 201 | 202 | fn list_element_type(&self) -> Option { 203 | let list = maybe_unwrap_type!(&self.0, TypeEnum::List)?; 204 | Some(list.element.clone()) 205 | } 206 | 207 | fn record_fields(&self) -> Box, Self)> + '_> { 208 | let TypeEnum::Record(record) = &self.0 else { 209 | return Box::new(std::iter::empty()); 210 | }; 211 | Box::new( 212 | record 213 | .fields 214 | .iter() 215 | .map(|(name, ty)| (name.as_ref().into(), ty.clone())), 216 | ) 217 | } 218 | 219 | fn tuple_element_types(&self) -> Box + '_> { 220 | let TypeEnum::Tuple(tuple) = &self.0 else { 221 | return Box::new(std::iter::empty()); 222 | }; 223 | Box::new(tuple.elements.iter().cloned()) 224 | } 225 | 226 | fn variant_cases(&self) -> Box, Option)> + '_> { 227 | let TypeEnum::Variant(variant) = &self.0 else { 228 | return Box::new(std::iter::empty()); 229 | }; 230 | Box::new( 231 | variant 232 | .cases 233 | .iter() 234 | .map(|(name, ty)| (name.as_ref().into(), ty.clone())), 235 | ) 236 | } 237 | 238 | fn enum_cases(&self) -> Box> + '_> { 239 | let TypeEnum::Enum(enum_) = &self.0 else { 240 | return Box::new(std::iter::empty()); 241 | }; 242 | Box::new(enum_.cases.iter().map(|name| name.as_ref().into())) 243 | } 244 | 245 | fn option_some_type(&self) -> Option { 246 | let option = maybe_unwrap_type!(&self.0, TypeEnum::Option)?; 247 | Some(option.some.clone()) 248 | } 249 | 250 | fn result_types(&self) -> Option<(Option, Option)> { 251 | let result = maybe_unwrap_type!(&self.0, TypeEnum::Result)?; 252 | Some((result.ok.clone(), result.err.clone())) 253 | } 254 | 255 | fn flags_names(&self) -> Box> + '_> { 256 | let TypeEnum::Flags(flags) = &self.0 else { 257 | return Box::new(std::iter::empty()); 258 | }; 259 | Box::new(flags.flags.iter().map(|name| name.as_ref().into())) 260 | } 261 | } 262 | 263 | impl std::fmt::Display for Type { 264 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 265 | crate::wasm::DisplayType(self).fmt(f) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /contrib/vscode/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/wasm/val.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use crate::wasm::{WasmType, WasmTypeKind, WasmValueError}; 4 | 5 | /// The WasmValue trait may be implemented to represent values to be 6 | /// (de)serialized with WAVE, notably [`value::Value`](crate::value::Value) 7 | /// and [`wasmtime::component::Val`]. 8 | /// 9 | /// The `make_*` and `unwrap_*` methods should be called only for corresponding 10 | /// [`WasmTypeKind`](crate::wasm::WasmTypeKind)s. 11 | #[allow(unused_variables)] 12 | pub trait WasmValue: Clone + Sized { 13 | /// A type representing types of these values. 14 | type Type: WasmType; 15 | 16 | /// The kind of type of this value. 17 | fn kind(&self) -> WasmTypeKind; 18 | 19 | /// Returns a new WasmValue of the given type. 20 | /// # Panics 21 | /// Panics if the type is not implemented (the trait default). 22 | fn make_bool(val: bool) -> Self { 23 | unimplemented!() 24 | } 25 | /// Returns a new WasmValue of the given type. 26 | /// # Panics 27 | /// Panics if the type is not implemented (the trait default). 28 | fn make_s8(val: i8) -> Self { 29 | unimplemented!() 30 | } 31 | /// Returns a new WasmValue of the given type. 32 | /// # Panics 33 | /// Panics if the type is not implemented (the trait default). 34 | fn make_s16(val: i16) -> Self { 35 | unimplemented!() 36 | } 37 | /// Returns a new WasmValue of the given type. 38 | /// # Panics 39 | /// Panics if the type is not implemented (the trait default). 40 | fn make_s32(val: i32) -> Self { 41 | unimplemented!() 42 | } 43 | /// Returns a new WasmValue of the given type. 44 | /// # Panics 45 | /// Panics if the type is not implemented (the trait default). 46 | fn make_s64(val: i64) -> Self { 47 | unimplemented!() 48 | } 49 | /// Returns a new WasmValue of the given type. 50 | /// # Panics 51 | /// Panics if the type is not implemented (the trait default). 52 | fn make_u8(val: u8) -> Self { 53 | unimplemented!() 54 | } 55 | /// Returns a new WasmValue of the given type. 56 | /// # Panics 57 | /// Panics if the type is not implemented (the trait default). 58 | fn make_u16(val: u16) -> Self { 59 | unimplemented!() 60 | } 61 | /// Returns a new WasmValue of the given type. 62 | /// # Panics 63 | /// Panics if the type is not implemented (the trait default). 64 | fn make_u32(val: u32) -> Self { 65 | unimplemented!() 66 | } 67 | /// Returns a new WasmValue of the given type. 68 | /// # Panics 69 | /// Panics if the type is not implemented (the trait default). 70 | fn make_u64(val: u64) -> Self { 71 | unimplemented!() 72 | } 73 | /// Returns a new WasmValue of the given type. 74 | /// 75 | /// The Rust `f32` type has many distinct NaN bitpatterns, however the 76 | /// component-model `float32` type only has a single NaN value, so this 77 | /// function does not preserve NaN bitpatterns. 78 | /// 79 | /// # Panics 80 | /// Panics if the type is not implemented (the trait default). 81 | fn make_float32(val: f32) -> Self { 82 | unimplemented!() 83 | } 84 | /// Returns a new WasmValue of the given type. 85 | /// 86 | /// The Rust `f64` type has many distinct NaN bitpatterns, however the 87 | /// component-model `float64` type only has a single NaN value, so this 88 | /// function does not preserve NaN bitpatterns. 89 | /// 90 | /// # Panics 91 | /// Panics if the type is not implemented (the trait default). 92 | fn make_float64(val: f64) -> Self { 93 | unimplemented!() 94 | } 95 | /// Returns a new WasmValue of the given type. 96 | /// # Panics 97 | /// Panics if the type is not implemented (the trait default). 98 | fn make_char(val: char) -> Self { 99 | unimplemented!() 100 | } 101 | /// Returns a new WasmValue of the given type. 102 | /// # Panics 103 | /// Panics if the type is not implemented (the trait default). 104 | fn make_string(val: Cow) -> Self { 105 | unimplemented!() 106 | } 107 | /// Returns a new WasmValue of the given type. 108 | /// # Panics 109 | /// Panics if the type is not implemented (the trait default). 110 | fn make_list( 111 | ty: &Self::Type, 112 | vals: impl IntoIterator, 113 | ) -> Result { 114 | unimplemented!() 115 | } 116 | /// Returns a new WasmValue of the given type. 117 | /// 118 | /// The fields provided by `fields` are not necessarily sorted; the callee 119 | /// should perform sorting itself if needed. 120 | /// 121 | /// # Panics 122 | /// Panics if the type is not implemented (the trait default). 123 | fn make_record<'a>( 124 | ty: &Self::Type, 125 | fields: impl IntoIterator, 126 | ) -> Result { 127 | unimplemented!() 128 | } 129 | /// Returns a new WasmValue of the given type. 130 | /// # Panics 131 | /// Panics if the type is not implemented (the trait default). 132 | fn make_tuple( 133 | ty: &Self::Type, 134 | vals: impl IntoIterator, 135 | ) -> Result { 136 | unimplemented!() 137 | } 138 | /// Returns a new WasmValue of the given type. 139 | /// # Panics 140 | /// Panics if the type is not implemented (the trait default). 141 | fn make_variant( 142 | ty: &Self::Type, 143 | case: &str, 144 | val: Option, 145 | ) -> Result { 146 | unimplemented!() 147 | } 148 | /// Returns a new WasmValue of the given type. 149 | /// # Panics 150 | /// Panics if the type is not implemented (the trait default). 151 | fn make_enum(ty: &Self::Type, case: &str) -> Result { 152 | unimplemented!() 153 | } 154 | /// Returns a new WasmValue of the given type. 155 | /// # Panics 156 | /// Panics if the type is not implemented (the trait default). 157 | fn make_option(ty: &Self::Type, val: Option) -> Result { 158 | unimplemented!() 159 | } 160 | /// Returns a new WasmValue of the given type. 161 | /// # Panics 162 | /// Panics if the type is not implemented (the trait default). 163 | fn make_result( 164 | ty: &Self::Type, 165 | val: Result, Option>, 166 | ) -> Result { 167 | unimplemented!() 168 | } 169 | /// Returns a new WasmValue of the given type. 170 | /// 171 | /// The strings provided by `names` are not necessarily sorted; the callee 172 | /// should perform sorting itself if needed. 173 | /// 174 | /// # Panics 175 | /// Panics if the type is not implemented (the trait default). 176 | fn make_flags<'a>( 177 | ty: &Self::Type, 178 | names: impl IntoIterator, 179 | ) -> Result { 180 | unimplemented!() 181 | } 182 | 183 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 184 | /// # Panics 185 | /// Panics if `self` is not of the right type. 186 | fn unwrap_bool(&self) -> bool { 187 | unimplemented!() 188 | } 189 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 190 | /// # Panics 191 | /// Panics if `self` is not of the right type. 192 | fn unwrap_s8(&self) -> i8 { 193 | unimplemented!() 194 | } 195 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 196 | /// # Panics 197 | /// Panics if `self` is not of the right type. 198 | fn unwrap_s16(&self) -> i16 { 199 | unimplemented!() 200 | } 201 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 202 | /// # Panics 203 | /// Panics if `self` is not of the right type. 204 | fn unwrap_s32(&self) -> i32 { 205 | unimplemented!() 206 | } 207 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 208 | /// # Panics 209 | /// Panics if `self` is not of the right type. 210 | fn unwrap_s64(&self) -> i64 { 211 | unimplemented!() 212 | } 213 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 214 | /// # Panics 215 | /// Panics if `self` is not of the right type. 216 | fn unwrap_u8(&self) -> u8 { 217 | unimplemented!() 218 | } 219 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 220 | /// # Panics 221 | /// Panics if `self` is not of the right type. 222 | fn unwrap_u16(&self) -> u16 { 223 | unimplemented!() 224 | } 225 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 226 | /// # Panics 227 | /// Panics if `self` is not of the right type. 228 | fn unwrap_u32(&self) -> u32 { 229 | unimplemented!() 230 | } 231 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 232 | /// # Panics 233 | /// Panics if `self` is not of the right type. 234 | fn unwrap_u64(&self) -> u64 { 235 | unimplemented!() 236 | } 237 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 238 | /// 239 | /// The Rust `f32` type has many distinct NaN bitpatterns, however the 240 | /// component-model `float64` type only has a single NaN value, so this 241 | /// function does not preserve NaN bitpatterns. 242 | /// 243 | /// # Panics 244 | /// Panics if `self` is not of the right type. 245 | fn unwrap_float32(&self) -> f32 { 246 | unimplemented!() 247 | } 248 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 249 | /// 250 | /// The Rust `f64` type has many distinct NaN bitpatterns, however the 251 | /// component-model `float64` type only has a single NaN value, so this 252 | /// function does not preserve NaN bitpatterns. 253 | /// 254 | /// # Panics 255 | /// Panics if `self` is not of the right type. 256 | fn unwrap_float64(&self) -> f64 { 257 | unimplemented!() 258 | } 259 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 260 | /// # Panics 261 | /// Panics if `self` is not of the right type. 262 | fn unwrap_char(&self) -> char { 263 | unimplemented!() 264 | } 265 | /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. 266 | /// # Panics 267 | /// Panics if `self` is not of the right type. 268 | fn unwrap_string(&self) -> Cow { 269 | unimplemented!() 270 | } 271 | /// Returns an iterator of the element Vals of the list. 272 | /// # Panics 273 | /// Panics if `self` is not of the right type. 274 | fn unwrap_list(&self) -> Box> + '_> { 275 | unimplemented!() 276 | } 277 | /// Returns an iterator of the field names and Vals of the record. 278 | /// # Panics 279 | /// Panics if `self` is not of the right type. 280 | fn unwrap_record(&self) -> Box, Cow)> + '_> { 281 | unimplemented!() 282 | } 283 | /// Returns an iterator of the field Vals of the tuple. 284 | /// # Panics 285 | /// Panics if `self` is not of the right type. 286 | fn unwrap_tuple(&self) -> Box> + '_> { 287 | unimplemented!() 288 | } 289 | /// Returns the variant case name and optional payload WasmValue of the variant. 290 | /// # Panics 291 | /// Panics if `self` is not of the right type. 292 | fn unwrap_variant(&self) -> (Cow, Option>) { 293 | unimplemented!() 294 | } 295 | /// Returns the case name of the enum. 296 | /// # Panics 297 | /// Panics if `self` is not of the right type. 298 | fn unwrap_enum(&self) -> Cow { 299 | unimplemented!() 300 | } 301 | /// Returns the optional WasmValue. 302 | /// # Panics 303 | /// Panics if `self` is not of the right type. 304 | fn unwrap_option(&self) -> Option> { 305 | unimplemented!() 306 | } 307 | /// Returns Ok(_) or Err(_) with the optional payload WasmValue. 308 | /// # Panics 309 | /// Panics if `self` is not of the right type. 310 | fn unwrap_result(&self) -> Result>, Option>> { 311 | unimplemented!() 312 | } 313 | /// Returns an iterator of the names of the flags WasmValue. 314 | /// # Panics 315 | /// Panics if `self` is not of the right type. 316 | fn unwrap_flags(&self) -> Box> + '_> { 317 | unimplemented!() 318 | } 319 | } 320 | 321 | macro_rules! unwrap_val { 322 | ($val:expr, $case:path, $name:expr) => { 323 | match $val { 324 | $case(v) => v, 325 | _ => panic!("called unwrap_{name} on non-{name} value", name = $name), 326 | } 327 | }; 328 | } 329 | macro_rules! unwrap_2val { 330 | ($val:expr, $case:path, $name:expr) => { 331 | match $val { 332 | $case(n, v) => (n, v), 333 | _ => panic!("called unwrap_{name} on non-{name} value", name = $name), 334 | } 335 | }; 336 | } 337 | pub(crate) use unwrap_2val; 338 | pub(crate) use unwrap_val; 339 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | //! Abstract syntax tree types 2 | 3 | use std::{borrow::Cow, collections::HashMap, str::FromStr}; 4 | 5 | use crate::{ 6 | lex::Span, 7 | parser::{ParserError, ParserErrorKind}, 8 | strings::{unescape, StringPartsIter}, 9 | wasm::{WasmType, WasmTypeKind, WasmValue, WasmValueError}, 10 | }; 11 | 12 | /// A WAVE AST node. 13 | #[derive(Clone, Debug)] 14 | pub struct Node { 15 | ty: NodeType, 16 | span: Span, 17 | children: Vec, 18 | } 19 | 20 | impl Node { 21 | pub(crate) fn new( 22 | ty: NodeType, 23 | span: impl Into, 24 | children: impl IntoIterator, 25 | ) -> Self { 26 | Self { 27 | ty, 28 | span: span.into(), 29 | children: Vec::from_iter(children), 30 | } 31 | } 32 | 33 | /// Returns this node's type. 34 | pub fn ty(&self) -> NodeType { 35 | self.ty 36 | } 37 | 38 | /// Returns this node's span. 39 | pub fn span(&self) -> Span { 40 | self.span.clone() 41 | } 42 | 43 | /// Returns a bool value if this node represents a bool. 44 | pub fn as_bool(&self) -> Result { 45 | match self.ty { 46 | NodeType::BoolTrue => Ok(true), 47 | NodeType::BoolFalse => Ok(false), 48 | _ => Err(self.error(ParserErrorKind::InvalidType)), 49 | } 50 | } 51 | 52 | /// Returns a number value of the given type (integer or float) if this node 53 | /// can represent a number of that type. 54 | pub fn as_number(&self, src: &str) -> Result { 55 | self.ensure_type(NodeType::Number)?; 56 | self.slice(src) 57 | .parse() 58 | .map_err(|_| self.error(ParserErrorKind::InvalidValue)) 59 | } 60 | 61 | /// Returns a char value if this node represents a valid char. 62 | pub fn as_char(&self, src: &str) -> Result { 63 | self.ensure_type(NodeType::Char)?; 64 | let inner = &src[self.span.start + 1..self.span.end - 1]; 65 | let (ch, len) = if inner.starts_with('\\') { 66 | unescape(inner).ok_or_else(|| self.error(ParserErrorKind::InvalidEscape))? 67 | } else { 68 | let ch = inner.chars().next().unwrap(); 69 | (ch, ch.len_utf8()) 70 | }; 71 | // Verify length 72 | if len != inner.len() { 73 | return Err(self.error(ParserErrorKind::MultipleChars)); 74 | } 75 | Ok(ch) 76 | } 77 | 78 | /// Returns a str value if this node represents a valid string. 79 | pub fn as_str<'src>(&self, src: &'src str) -> Result, ParserError> { 80 | let mut parts = self.iter_str(src)?; 81 | let Some(first) = parts.next().transpose()? else { 82 | return Ok("".into()); 83 | }; 84 | match parts.next().transpose()? { 85 | // Single part may be borrowed 86 | None => Ok(first), 87 | // Multiple parts must be collected into a single owned String 88 | Some(second) => { 89 | let s: String = [Ok(first), Ok(second)] 90 | .into_iter() 91 | .chain(parts) 92 | .collect::>()?; 93 | Ok(s.into()) 94 | } 95 | } 96 | } 97 | 98 | /// Returns an iterator of string "parts" which together form a decoded 99 | /// string value if this node represents a valid string. 100 | pub fn iter_str<'src>( 101 | &self, 102 | src: &'src str, 103 | ) -> Result, ParserError>>, ParserError> { 104 | match self.ty { 105 | NodeType::String => { 106 | let span = self.span.start + 1..self.span.end - 1; 107 | Ok(StringPartsIter::new(&src[span.clone()], span.start)) 108 | } 109 | NodeType::MultilineString => { 110 | let span = self.span.start + 3..self.span.end - 3; 111 | Ok(StringPartsIter::new_multiline( 112 | &src[span.clone()], 113 | span.start, 114 | )?) 115 | } 116 | _ => Err(self.error(ParserErrorKind::InvalidType)), 117 | } 118 | } 119 | 120 | /// Returns an iterator of value nodes if this node represents a tuple. 121 | pub fn as_tuple(&self) -> Result, ParserError> { 122 | self.ensure_type(NodeType::Tuple)?; 123 | Ok(self.children.iter()) 124 | } 125 | 126 | /// Returns an iterator of value nodes if this node represents a list. 127 | pub fn as_list(&self) -> Result, ParserError> { 128 | self.ensure_type(NodeType::List)?; 129 | Ok(self.children.iter()) 130 | } 131 | 132 | /// Returns an iterator of field name and value node pairs if this node 133 | /// represents a record value. 134 | pub fn as_record<'this, 'src>( 135 | &'this self, 136 | src: &'src str, 137 | ) -> Result, ParserError> { 138 | self.ensure_type(NodeType::Record)?; 139 | Ok(self 140 | .children 141 | .chunks(2) 142 | .map(|chunk| (chunk[0].as_label(src).unwrap(), &chunk[1]))) 143 | } 144 | 145 | /// Returns a variant label and optional payload if this node can represent 146 | /// a variant value. 147 | pub fn as_variant<'this, 'src>( 148 | &'this self, 149 | src: &'src str, 150 | ) -> Result<(&'src str, Option<&'this Node>), ParserError> { 151 | match self.ty { 152 | NodeType::Label => Ok((self.as_label(src)?, None)), 153 | NodeType::VariantWithPayload => { 154 | let label = self.children[0].as_label(src)?; 155 | let value = &self.children[1]; 156 | Ok((label, Some(value))) 157 | } 158 | _ => Err(self.error(ParserErrorKind::InvalidType)), 159 | } 160 | } 161 | 162 | /// Returns an enum value label if this node represents a label. 163 | pub fn as_enum<'src>(&self, src: &'src str) -> Result<&'src str, ParserError> { 164 | self.as_label(src) 165 | } 166 | 167 | /// Returns an option value if this node represents an option. 168 | pub fn as_option(&self) -> Result, ParserError> { 169 | match self.ty { 170 | NodeType::OptionSome => Ok(Some(&self.children[0])), 171 | NodeType::OptionNone => Ok(None), 172 | _ => Err(self.error(ParserErrorKind::InvalidType)), 173 | } 174 | } 175 | 176 | /// Returns a result value with optional payload value if this node 177 | /// represents a result. 178 | pub fn as_result(&self) -> Result, Option<&Node>>, ParserError> { 179 | let payload = self.children.first(); 180 | match self.ty { 181 | NodeType::ResultOk => Ok(Ok(payload)), 182 | NodeType::ResultErr => Ok(Err(payload)), 183 | _ => Err(self.error(ParserErrorKind::InvalidType)), 184 | } 185 | } 186 | 187 | /// Returns an iterator of flag labels if this node represents flags. 188 | pub fn as_flags<'this, 'src: 'this>( 189 | &'this self, 190 | src: &'src str, 191 | ) -> Result + 'this, ParserError> { 192 | self.ensure_type(NodeType::Flags)?; 193 | Ok(self.children.iter().map(|node| { 194 | debug_assert_eq!(node.ty, NodeType::Label); 195 | node.slice(src) 196 | })) 197 | } 198 | 199 | fn as_label<'src>(&self, src: &'src str) -> Result<&'src str, ParserError> { 200 | self.ensure_type(NodeType::Label)?; 201 | let label = self.slice(src); 202 | let label = label.strip_prefix('%').unwrap_or(label); 203 | Ok(label) 204 | } 205 | 206 | /// Converts this node into the given typed value from the given input source. 207 | pub fn to_wasm_value(&self, ty: &V::Type, src: &str) -> Result { 208 | Ok(match ty.kind() { 209 | WasmTypeKind::Bool => V::make_bool(self.as_bool()?), 210 | WasmTypeKind::S8 => V::make_s8(self.as_number(src)?), 211 | WasmTypeKind::S16 => V::make_s16(self.as_number(src)?), 212 | WasmTypeKind::S32 => V::make_s32(self.as_number(src)?), 213 | WasmTypeKind::S64 => V::make_s64(self.as_number(src)?), 214 | WasmTypeKind::U8 => V::make_u8(self.as_number(src)?), 215 | WasmTypeKind::U16 => V::make_u16(self.as_number(src)?), 216 | WasmTypeKind::U32 => V::make_u32(self.as_number(src)?), 217 | WasmTypeKind::U64 => V::make_u64(self.as_number(src)?), 218 | WasmTypeKind::Float32 => V::make_float32(self.as_number(src)?), 219 | WasmTypeKind::Float64 => V::make_float64(self.as_number(src)?), 220 | WasmTypeKind::Char => V::make_char(self.as_char(src)?), 221 | WasmTypeKind::String => V::make_string(self.as_str(src)?), 222 | WasmTypeKind::List => self.to_wasm_list(ty, src)?, 223 | WasmTypeKind::Record => self.to_wasm_record(ty, src)?, 224 | WasmTypeKind::Tuple => self.to_wasm_tuple(ty, src)?, 225 | WasmTypeKind::Variant => self.to_wasm_variant(ty, src)?, 226 | WasmTypeKind::Enum => self.to_wasm_enum(ty, src)?, 227 | WasmTypeKind::Option => self.to_wasm_option(ty, src)?, 228 | WasmTypeKind::Result => self.to_wasm_result(ty, src)?, 229 | WasmTypeKind::Flags => self.to_wasm_flags(ty, src)?, 230 | other => { 231 | return Err( 232 | self.wasm_value_error(WasmValueError::UnsupportedType(other.to_string())) 233 | ) 234 | } 235 | }) 236 | } 237 | 238 | /// Converts this node into the given types. 239 | /// See [`crate::untyped::UntypedFuncCall::to_wasm_params`]. 240 | pub fn to_wasm_params<'types, V: WasmValue + 'static>( 241 | &self, 242 | types: impl IntoIterator, 243 | src: &str, 244 | ) -> Result, ParserError> { 245 | let mut types = types.into_iter(); 246 | let mut values = self 247 | .as_tuple()? 248 | .map(|node| { 249 | let ty = types.next().ok_or_else(|| { 250 | ParserError::with_detail( 251 | ParserErrorKind::InvalidParams, 252 | node.span().clone(), 253 | "more param(s) than expected", 254 | ) 255 | })?; 256 | node.to_wasm_value::(ty, src) 257 | }) 258 | .collect::, _>>()?; 259 | // Handle trailing optional fields 260 | for ty in types { 261 | if ty.kind() == WasmTypeKind::Option { 262 | values.push(V::make_option(ty, None).map_err(|err| self.wasm_value_error(err))?); 263 | } else { 264 | return Err(ParserError::with_detail( 265 | ParserErrorKind::InvalidParams, 266 | self.span.end - 1..self.span.end, 267 | "missing required param(s)", 268 | )); 269 | } 270 | } 271 | Ok(values) 272 | } 273 | 274 | fn to_wasm_list(&self, ty: &V::Type, src: &str) -> Result { 275 | let element_type = ty.list_element_type().unwrap(); 276 | let elements = self 277 | .as_list()? 278 | .map(|node| node.to_wasm_value(&element_type, src)) 279 | .collect::, _>>()?; 280 | V::make_list(ty, elements).map_err(|err| self.wasm_value_error(err)) 281 | } 282 | 283 | fn to_wasm_record(&self, ty: &V::Type, src: &str) -> Result { 284 | let values = self.as_record(src)?.collect::>(); 285 | let record_fields = ty.record_fields().collect::>(); 286 | let fields = record_fields 287 | .iter() 288 | .map(|(name, field_type)| { 289 | let value = match (values.get(name.as_ref()), field_type.kind()) { 290 | (Some(node), _) => node.to_wasm_value(field_type, src)?, 291 | (None, WasmTypeKind::Option) => V::make_option(field_type, None) 292 | .map_err(|err| self.wasm_value_error(err))?, 293 | _ => { 294 | return Err( 295 | self.wasm_value_error(WasmValueError::MissingField(name.to_string())) 296 | ); 297 | } 298 | }; 299 | Ok((name.as_ref(), value)) 300 | }) 301 | .collect::, _>>()?; 302 | V::make_record(ty, fields).map_err(|err| self.wasm_value_error(err)) 303 | } 304 | 305 | fn to_wasm_tuple(&self, ty: &V::Type, src: &str) -> Result { 306 | let types = ty.tuple_element_types().collect::>(); 307 | let values = self.as_tuple()?; 308 | if types.len() != values.len() { 309 | return Err( 310 | self.wasm_value_error(WasmValueError::WrongNumberOfTupleValues { 311 | want: types.len(), 312 | got: values.len(), 313 | }), 314 | ); 315 | } 316 | let values = ty 317 | .tuple_element_types() 318 | .zip(self.as_tuple()?) 319 | .map(|(ty, node)| node.to_wasm_value(&ty, src)) 320 | .collect::, _>>()?; 321 | V::make_tuple(ty, values).map_err(|err| self.wasm_value_error(err)) 322 | } 323 | 324 | fn to_wasm_variant(&self, ty: &V::Type, src: &str) -> Result { 325 | let (label, payload) = self.as_variant(src)?; 326 | let payload_type = ty 327 | .variant_cases() 328 | .find_map(|(case, payload)| (case == label).then_some(payload)) 329 | .ok_or_else(|| self.wasm_value_error(WasmValueError::UnknownCase(label.into())))?; 330 | let payload_value = self.to_wasm_maybe_payload(label, &payload_type, payload, src)?; 331 | V::make_variant(ty, label, payload_value).map_err(|err| self.wasm_value_error(err)) 332 | } 333 | 334 | fn to_wasm_enum(&self, ty: &V::Type, src: &str) -> Result { 335 | V::make_enum(ty, self.as_enum(src)?).map_err(|err| self.wasm_value_error(err)) 336 | } 337 | 338 | fn to_wasm_option(&self, ty: &V::Type, src: &str) -> Result { 339 | let payload_type = ty.option_some_type().unwrap(); 340 | let value = match self.ty { 341 | NodeType::OptionSome => { 342 | self.to_wasm_maybe_payload("some", &Some(payload_type), self.as_option()?, src)? 343 | } 344 | NodeType::OptionNone => { 345 | self.to_wasm_maybe_payload("none", &None, self.as_option()?, src)? 346 | } 347 | _ if flattenable(payload_type.kind()) => Some(self.to_wasm_value(&payload_type, src)?), 348 | _ => { 349 | return Err(self.error(ParserErrorKind::InvalidType)); 350 | } 351 | }; 352 | V::make_option(ty, value).map_err(|err| self.wasm_value_error(err)) 353 | } 354 | 355 | fn to_wasm_result(&self, ty: &V::Type, src: &str) -> Result { 356 | let (ok_type, err_type) = ty.result_types().unwrap(); 357 | let value = match self.ty { 358 | NodeType::ResultOk => { 359 | Ok(self.to_wasm_maybe_payload("ok", &ok_type, self.as_result()?.unwrap(), src)?) 360 | } 361 | NodeType::ResultErr => Err(self.to_wasm_maybe_payload( 362 | "err", 363 | &err_type, 364 | self.as_result()?.unwrap_err(), 365 | src, 366 | )?), 367 | _ => match ok_type { 368 | Some(ty) if flattenable(ty.kind()) => Ok(Some(self.to_wasm_value(&ty, src)?)), 369 | _ => return Err(self.error(ParserErrorKind::InvalidType)), 370 | }, 371 | }; 372 | V::make_result(ty, value).map_err(|err| self.wasm_value_error(err)) 373 | } 374 | 375 | fn to_wasm_flags(&self, ty: &V::Type, src: &str) -> Result { 376 | V::make_flags(ty, self.as_flags(src)?).map_err(|err| self.wasm_value_error(err)) 377 | } 378 | 379 | fn to_wasm_maybe_payload( 380 | &self, 381 | case: &str, 382 | payload_type: &Option, 383 | payload: Option<&Node>, 384 | src: &str, 385 | ) -> Result, ParserError> { 386 | match (payload_type.as_ref(), payload) { 387 | (Some(ty), Some(node)) => Ok(Some(node.to_wasm_value(ty, src)?)), 388 | (None, None) => Ok(None), 389 | (Some(_), None) => { 390 | Err(self.wasm_value_error(WasmValueError::MissingPayload(case.into()))) 391 | } 392 | (None, Some(_)) => { 393 | Err(self.wasm_value_error(WasmValueError::UnexpectedPayload(case.into()))) 394 | } 395 | } 396 | } 397 | 398 | fn wasm_value_error(&self, err: WasmValueError) -> ParserError { 399 | ParserError::with_source(ParserErrorKind::WasmValueError, self.span(), err) 400 | } 401 | 402 | pub(crate) fn slice<'src>(&self, src: &'src str) -> &'src str { 403 | &src[self.span()] 404 | } 405 | 406 | fn ensure_type(&self, ty: NodeType) -> Result<(), ParserError> { 407 | if self.ty == ty { 408 | Ok(()) 409 | } else { 410 | Err(self.error(ParserErrorKind::InvalidType)) 411 | } 412 | } 413 | 414 | fn error(&self, kind: ParserErrorKind) -> ParserError { 415 | ParserError::new(kind, self.span()) 416 | } 417 | } 418 | 419 | fn flattenable(kind: WasmTypeKind) -> bool { 420 | // TODO: Consider wither to allow flattening an option in a result or vice-versa. 421 | !matches!(kind, WasmTypeKind::Option | WasmTypeKind::Result) 422 | } 423 | 424 | /// The type of a WAVE AST [`Node`]. 425 | #[derive(Clone, Copy, Debug, PartialEq)] 426 | pub enum NodeType { 427 | /// Boolean `true` 428 | BoolTrue, 429 | /// Boolean `false` 430 | BoolFalse, 431 | /// Number 432 | /// May be an integer or float, including `nan`, `inf`, `-inf` 433 | Number, 434 | /// Char 435 | /// Span includes delimiters. 436 | Char, 437 | /// String 438 | /// Span includes delimiters. 439 | String, 440 | /// Multiline String 441 | /// Span includes delimiters. 442 | MultilineString, 443 | /// Tuple 444 | /// Child nodes are the tuple values. 445 | Tuple, 446 | /// List 447 | /// Child nodes are the list values. 448 | List, 449 | /// Record 450 | /// Child nodes are field Label, value pairs, e.g. 451 | /// `[, , , , ...]` 452 | Record, 453 | /// Label 454 | /// In value position may represent an enum value or variant case (without payload). 455 | Label, 456 | /// Variant case with payload 457 | /// Child nodes are variant case Label and payload value. 458 | VariantWithPayload, 459 | /// Option `some` 460 | /// Child node is the payload value. 461 | OptionSome, 462 | /// Option `none` 463 | OptionNone, 464 | /// Result `ok` 465 | /// Has zero or one child node for the payload value. 466 | ResultOk, 467 | /// Result `err` 468 | /// Has zero or one child node for the payload value. 469 | ResultErr, 470 | /// Flags 471 | /// Child nodes are flag Labels. 472 | Flags, 473 | } 474 | -------------------------------------------------------------------------------- /src/wasmtime/component.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | use wasmtime::component; 4 | 5 | use crate::{ 6 | canonicalize_nan32, canonicalize_nan64, 7 | wasm::{ 8 | ensure_type_kind, maybe_unwrap_type, unwrap_2val, unwrap_val, DisplayFunc, DisplayValue, 9 | WasmFunc, WasmType, WasmTypeKind, WasmValueError, 10 | }, 11 | WasmValue, 12 | }; 13 | 14 | impl WasmType for component::Type { 15 | fn kind(&self) -> WasmTypeKind { 16 | match self { 17 | Self::Bool => WasmTypeKind::Bool, 18 | Self::S8 => WasmTypeKind::S8, 19 | Self::U8 => WasmTypeKind::U8, 20 | Self::S16 => WasmTypeKind::S16, 21 | Self::U16 => WasmTypeKind::U16, 22 | Self::S32 => WasmTypeKind::S32, 23 | Self::U32 => WasmTypeKind::U32, 24 | Self::S64 => WasmTypeKind::S64, 25 | Self::U64 => WasmTypeKind::U64, 26 | Self::Float32 => WasmTypeKind::Float32, 27 | Self::Float64 => WasmTypeKind::Float64, 28 | Self::Char => WasmTypeKind::Char, 29 | Self::String => WasmTypeKind::String, 30 | Self::List(_) => WasmTypeKind::List, 31 | Self::Record(_) => WasmTypeKind::Record, 32 | Self::Tuple(_) => WasmTypeKind::Tuple, 33 | Self::Variant(_) => WasmTypeKind::Variant, 34 | Self::Enum(_) => WasmTypeKind::Enum, 35 | Self::Option(_) => WasmTypeKind::Option, 36 | Self::Result(_) => WasmTypeKind::Result, 37 | Self::Flags(_) => WasmTypeKind::Flags, 38 | 39 | Self::Own(_) | Self::Borrow(_) => WasmTypeKind::Unsupported, 40 | } 41 | } 42 | 43 | fn list_element_type(&self) -> Option { 44 | Some(maybe_unwrap_type!(self, Self::List)?.ty()) 45 | } 46 | 47 | fn record_fields(&self) -> Box, Self)> + '_> { 48 | let Self::Record(record) = self else { 49 | return Box::new(std::iter::empty()); 50 | }; 51 | Box::new(record.fields().map(|f| (f.name.into(), f.ty.clone()))) 52 | } 53 | 54 | fn tuple_element_types(&self) -> Box + '_> { 55 | let Self::Tuple(tuple) = self else { 56 | return Box::new(std::iter::empty()); 57 | }; 58 | Box::new(tuple.types()) 59 | } 60 | 61 | fn variant_cases(&self) -> Box, Option)> + '_> { 62 | let Self::Variant(variant) = self else { 63 | return Box::new(std::iter::empty()); 64 | }; 65 | Box::new(variant.cases().map(|case| (case.name.into(), case.ty))) 66 | } 67 | 68 | fn enum_cases(&self) -> Box> + '_> { 69 | let Self::Enum(enum_) = self else { 70 | return Box::new(std::iter::empty()); 71 | }; 72 | Box::new(enum_.names().map(Into::into)) 73 | } 74 | 75 | fn option_some_type(&self) -> Option { 76 | maybe_unwrap_type!(self, Self::Option).map(|o| o.ty()) 77 | } 78 | 79 | fn result_types(&self) -> Option<(Option, Option)> { 80 | let result = maybe_unwrap_type!(self, Self::Result)?; 81 | Some((result.ok(), result.err())) 82 | } 83 | 84 | fn flags_names(&self) -> Box> + '_> { 85 | let Self::Flags(flags) = self else { 86 | return Box::new(std::iter::empty()); 87 | }; 88 | Box::new(flags.names().map(Into::into)) 89 | } 90 | } 91 | 92 | macro_rules! impl_primitives { 93 | ($Self:ident, $(($case:ident, $ty:ty, $make:ident, $unwrap:ident)),*) => { 94 | $( 95 | fn $make(val: $ty) -> $Self { 96 | $Self::$case(val) 97 | } 98 | 99 | fn $unwrap(&self) -> $ty { 100 | *unwrap_val!(self, $Self::$case, stringify!($case)) 101 | } 102 | )* 103 | }; 104 | } 105 | 106 | impl WasmValue for component::Val { 107 | type Type = component::Type; 108 | 109 | fn kind(&self) -> WasmTypeKind { 110 | match self { 111 | Self::Bool(_) => WasmTypeKind::Bool, 112 | Self::S8(_) => WasmTypeKind::S8, 113 | Self::U8(_) => WasmTypeKind::U8, 114 | Self::S16(_) => WasmTypeKind::S16, 115 | Self::U16(_) => WasmTypeKind::U16, 116 | Self::S32(_) => WasmTypeKind::S32, 117 | Self::U32(_) => WasmTypeKind::U32, 118 | Self::S64(_) => WasmTypeKind::S64, 119 | Self::U64(_) => WasmTypeKind::U64, 120 | Self::Float32(_) => WasmTypeKind::Float32, 121 | Self::Float64(_) => WasmTypeKind::Float64, 122 | Self::Char(_) => WasmTypeKind::Char, 123 | Self::String(_) => WasmTypeKind::String, 124 | Self::List(_) => WasmTypeKind::List, 125 | Self::Record(_) => WasmTypeKind::Record, 126 | Self::Tuple(_) => WasmTypeKind::Tuple, 127 | Self::Variant(..) => WasmTypeKind::Variant, 128 | Self::Enum(_) => WasmTypeKind::Enum, 129 | Self::Option(_) => WasmTypeKind::Option, 130 | Self::Result(_) => WasmTypeKind::Result, 131 | Self::Flags(_) => WasmTypeKind::Flags, 132 | Self::Resource(_) => WasmTypeKind::Unsupported, 133 | } 134 | } 135 | 136 | impl_primitives!( 137 | Self, 138 | (Bool, bool, make_bool, unwrap_bool), 139 | (S8, i8, make_s8, unwrap_s8), 140 | (S16, i16, make_s16, unwrap_s16), 141 | (S32, i32, make_s32, unwrap_s32), 142 | (S64, i64, make_s64, unwrap_s64), 143 | (U8, u8, make_u8, unwrap_u8), 144 | (U16, u16, make_u16, unwrap_u16), 145 | (U32, u32, make_u32, unwrap_u32), 146 | (U64, u64, make_u64, unwrap_u64), 147 | (Char, char, make_char, unwrap_char) 148 | ); 149 | 150 | fn make_float32(val: f32) -> Self { 151 | let val = canonicalize_nan32(val); 152 | Self::Float32(val) 153 | } 154 | fn make_float64(val: f64) -> Self { 155 | let val = canonicalize_nan64(val); 156 | Self::Float64(val) 157 | } 158 | fn make_string(val: Cow) -> Self { 159 | Self::String(val.into()) 160 | } 161 | fn make_list( 162 | ty: &Self::Type, 163 | vals: impl IntoIterator, 164 | ) -> Result { 165 | ensure_type_kind(ty, WasmTypeKind::List)?; 166 | let val = Self::List(vals.into_iter().collect()); 167 | ensure_type_val(ty, &val)?; 168 | Ok(val) 169 | } 170 | fn make_record<'a>( 171 | ty: &Self::Type, 172 | fields: impl IntoIterator, 173 | ) -> Result { 174 | ensure_type_kind(ty, WasmTypeKind::Record)?; 175 | let values: Vec<(String, Self)> = fields 176 | .into_iter() 177 | .map(|(name, val)| (name.to_string(), val)) 178 | .collect(); 179 | let val = Self::Record(values); 180 | ensure_type_val(ty, &val)?; 181 | Ok(val) 182 | } 183 | fn make_tuple( 184 | ty: &Self::Type, 185 | vals: impl IntoIterator, 186 | ) -> Result { 187 | ensure_type_kind(ty, WasmTypeKind::Tuple)?; 188 | let val = Self::Tuple(vals.into_iter().collect()); 189 | ensure_type_val(ty, &val)?; 190 | Ok(val) 191 | } 192 | fn make_variant( 193 | ty: &Self::Type, 194 | case: &str, 195 | val: Option, 196 | ) -> Result { 197 | ensure_type_kind(ty, WasmTypeKind::Variant)?; 198 | let val = Self::Variant(case.to_string(), val.map(Box::new)); 199 | ensure_type_val(ty, &val)?; 200 | Ok(val) 201 | } 202 | fn make_enum(ty: &Self::Type, case: &str) -> Result { 203 | ensure_type_kind(ty, WasmTypeKind::Enum)?; 204 | let val = Self::Enum(case.to_string()); 205 | ensure_type_val(ty, &val)?; 206 | Ok(val) 207 | } 208 | fn make_option(ty: &Self::Type, val: Option) -> Result { 209 | ensure_type_kind(ty, WasmTypeKind::Option)?; 210 | let val = Self::Option(val.map(Box::new)); 211 | ensure_type_val(ty, &val)?; 212 | Ok(val) 213 | } 214 | fn make_result( 215 | ty: &Self::Type, 216 | val: Result, Option>, 217 | ) -> Result { 218 | ensure_type_kind(ty, WasmTypeKind::Result)?; 219 | let val = match val { 220 | Ok(val) => Self::Result(Ok(val.map(Box::new))), 221 | Err(val) => Self::Result(Err(val.map(Box::new))), 222 | }; 223 | ensure_type_val(ty, &val)?; 224 | Ok(val) 225 | } 226 | fn make_flags<'a>( 227 | ty: &Self::Type, 228 | names: impl IntoIterator, 229 | ) -> Result { 230 | ensure_type_kind(ty, WasmTypeKind::Flags)?; 231 | let val = Self::Flags(names.into_iter().map(|n| n.to_string()).collect()); 232 | ensure_type_val(ty, &val)?; 233 | Ok(val) 234 | } 235 | 236 | fn unwrap_float32(&self) -> f32 { 237 | let val = *unwrap_val!(self, Self::Float32, "float32"); 238 | canonicalize_nan32(val) 239 | } 240 | fn unwrap_float64(&self) -> f64 { 241 | let val = *unwrap_val!(self, Self::Float64, "float64"); 242 | canonicalize_nan64(val) 243 | } 244 | fn unwrap_string(&self) -> Cow { 245 | unwrap_val!(self, Self::String, "string").into() 246 | } 247 | fn unwrap_list(&self) -> Box> + '_> { 248 | let list = unwrap_val!(self, Self::List, "list"); 249 | Box::new(list.iter().map(cow)) 250 | } 251 | fn unwrap_record(&self) -> Box, Cow)> + '_> { 252 | let record = unwrap_val!(self, Self::Record, "record"); 253 | Box::new(record.iter().map(|(name, val)| (name.into(), cow(val)))) 254 | } 255 | fn unwrap_tuple(&self) -> Box> + '_> { 256 | let tuple = unwrap_val!(self, Self::Tuple, "tuple"); 257 | Box::new(tuple.iter().map(cow)) 258 | } 259 | fn unwrap_variant(&self) -> (Cow, Option>) { 260 | let (discriminant, payload) = unwrap_2val!(self, Self::Variant, "variant"); 261 | (discriminant.into(), payload.as_deref().map(cow)) 262 | } 263 | fn unwrap_enum(&self) -> Cow { 264 | unwrap_val!(self, Self::Enum, "enum").into() 265 | } 266 | fn unwrap_option(&self) -> Option> { 267 | unwrap_val!(self, Self::Option, "option") 268 | .as_deref() 269 | .map(cow) 270 | } 271 | fn unwrap_result(&self) -> Result>, Option>> { 272 | match unwrap_val!(self, Self::Result, "result") { 273 | Ok(t) => Ok(t.as_deref().map(cow)), 274 | Err(e) => Err(e.as_deref().map(cow)), 275 | } 276 | } 277 | fn unwrap_flags(&self) -> Box> + '_> { 278 | let flags = unwrap_val!(self, Self::Flags, "flags"); 279 | Box::new(flags.iter().map(Into::into)) 280 | } 281 | } 282 | 283 | // Returns an error if the given component::Val is not of the given component::Type. 284 | // 285 | // The component::Val::Resource(_) variant results in an unsupported error at this time. 286 | fn ensure_type_val(ty: &component::Type, val: &component::Val) -> Result<(), WasmValueError> { 287 | let wrong_value_type = 288 | || -> Result<(), WasmValueError> { Err(WasmValueError::wrong_value_type(ty, val)) }; 289 | 290 | if ty.kind() != val.kind() { 291 | return wrong_value_type(); 292 | } 293 | 294 | match val { 295 | component::Val::List(vals) => { 296 | let list_type = ty.unwrap_list().ty(); 297 | for val in vals { 298 | ensure_type_val(&list_type, val)?; 299 | } 300 | } 301 | component::Val::Record(vals) => { 302 | let record_handle = ty.unwrap_record(); 303 | // Check that every non option field type is found in the Vec 304 | for field in record_handle.fields() { 305 | if !matches!(field.ty, component::Type::Option(_)) 306 | && !vals.iter().any(|(n, _)| n == field.name) 307 | { 308 | return wrong_value_type(); 309 | } 310 | } 311 | // Check that every (String, Val) of the given Vec is a correct field_type 312 | for (name, field_val) in vals.iter() { 313 | // N.B. The `fields` call in each iteration is non-trivial, perhaps a cleaner way 314 | // using the loop above will present itself. 315 | if let Some(field) = record_handle.fields().find(|field| field.name == name) { 316 | ensure_type_val(&field.ty, field_val)?; 317 | } else { 318 | return wrong_value_type(); 319 | } 320 | } 321 | } 322 | component::Val::Tuple(vals) => { 323 | let field_types = ty.unwrap_tuple().types(); 324 | if field_types.len() != vals.len() { 325 | return wrong_value_type(); 326 | } 327 | for (ty, val) in field_types.into_iter().zip(vals.iter()) { 328 | ensure_type_val(&ty, val)?; 329 | } 330 | } 331 | component::Val::Variant(name, optional_payload) => { 332 | if let Some(case) = ty.unwrap_variant().cases().find(|case| case.name == name) { 333 | match (optional_payload, case.ty) { 334 | (None, None) => {} 335 | (Some(payload), Some(payload_ty)) => ensure_type_val(&payload_ty, payload)?, 336 | _ => return wrong_value_type(), 337 | } 338 | } else { 339 | return wrong_value_type(); 340 | } 341 | } 342 | component::Val::Enum(name) => { 343 | if !ty.unwrap_enum().names().any(|n| n == name) { 344 | return wrong_value_type(); 345 | } 346 | } 347 | component::Val::Option(Some(some_val)) => { 348 | ensure_type_val(&ty.unwrap_option().ty(), some_val.as_ref())?; 349 | } 350 | component::Val::Result(res_val) => { 351 | let result_handle = ty.unwrap_result(); 352 | match res_val { 353 | Ok(ok) => match (ok, result_handle.ok()) { 354 | (None, None) => {} 355 | (Some(ok_val), Some(ok_ty)) => ensure_type_val(&ok_ty, ok_val.as_ref())?, 356 | _ => return wrong_value_type(), 357 | }, 358 | Err(err) => match (err, result_handle.err()) { 359 | (None, None) => {} 360 | (Some(err_val), Some(err_ty)) => ensure_type_val(&err_ty, err_val.as_ref())?, 361 | _ => return wrong_value_type(), 362 | }, 363 | } 364 | } 365 | component::Val::Flags(flags) => { 366 | let flags_handle = ty.unwrap_flags(); 367 | for flag in flags { 368 | if !flags_handle.names().any(|n| n == flag) { 369 | return wrong_value_type(); 370 | } 371 | } 372 | } 373 | component::Val::Resource(_) => { 374 | return Err(WasmValueError::UnsupportedType( 375 | DisplayValue(val).to_string(), 376 | )) 377 | } 378 | 379 | // Any leaf variant type has already had its kind compared above; nothing further to check. 380 | // Likewise, the component::Option(None) arm would have nothing left to check. 381 | _ => {} 382 | } 383 | Ok(()) 384 | } 385 | 386 | impl WasmFunc for component::types::ComponentFunc { 387 | type Type = component::Type; 388 | 389 | fn params(&self) -> Box + '_> { 390 | Box::new(self.params()) 391 | } 392 | 393 | fn results(&self) -> Box + '_> { 394 | Box::new(self.results()) 395 | } 396 | } 397 | 398 | /// Represents a [`wasmtime::component::Func`] type. 399 | #[derive(Clone)] 400 | pub struct FuncType { 401 | /// The func's parameters. 402 | pub params: Box<[component::Type]>, 403 | /// The func's results. 404 | pub results: Box<[component::Type]>, 405 | } 406 | 407 | impl WasmFunc for FuncType { 408 | type Type = component::Type; 409 | 410 | fn params(&self) -> Box + '_> { 411 | Box::new(self.params.iter().cloned()) 412 | } 413 | 414 | fn results(&self) -> Box + '_> { 415 | Box::new(self.results.iter().cloned()) 416 | } 417 | } 418 | 419 | impl std::fmt::Display for FuncType { 420 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 421 | DisplayFunc(self.clone()).fmt(f) 422 | } 423 | } 424 | 425 | /// Returns a [`FuncType`] for the given `func` and `store`. 426 | /// # Panics 427 | /// Panics if `func` doesn't belong to `store`. 428 | pub fn get_func_type(func: &component::Func, store: &impl wasmtime::AsContext) -> FuncType { 429 | FuncType { 430 | params: func.params(store), 431 | results: func.results(store), 432 | } 433 | } 434 | 435 | fn cow(t: &T) -> Cow { 436 | Cow::Borrowed(t) 437 | } 438 | 439 | #[cfg(test)] 440 | mod tests { 441 | #[test] 442 | fn component_vals_smoke_test() { 443 | use wasmtime::component::Val; 444 | for (val, want) in [ 445 | (Val::Bool(false), "false"), 446 | (Val::Bool(true), "true"), 447 | (Val::S8(10), "10"), 448 | (Val::S16(-10), "-10"), 449 | (Val::S32(1_000_000), "1000000"), 450 | (Val::S64(0), "0"), 451 | (Val::U8(255), "255"), 452 | (Val::U16(0), "0"), 453 | (Val::U32(1_000_000), "1000000"), 454 | (Val::U64(9), "9"), 455 | (Val::Float32(1.5), "1.5"), 456 | (Val::Float32(f32::NAN), "nan"), 457 | (Val::Float32(f32::INFINITY), "inf"), 458 | (Val::Float32(f32::NEG_INFINITY), "-inf"), 459 | (Val::Float64(-1.5e-10), "-0.00000000015"), 460 | (Val::Float64(f64::NAN), "nan"), 461 | (Val::Float64(f64::INFINITY), "inf"), 462 | (Val::Float64(f64::NEG_INFINITY), "-inf"), 463 | (Val::Char('x'), "'x'"), 464 | (Val::Char('☃'), "'☃'"), 465 | (Val::Char('\''), r"'\''"), 466 | (Val::Char('\0'), r"'\u{0}'"), 467 | (Val::Char('\x1b'), r"'\u{1b}'"), 468 | (Val::Char('😂'), r"'😂'"), 469 | (Val::String("abc".into()), r#""abc""#), 470 | (Val::String(r#"\☃""#.into()), r#""\\☃\"""#), 471 | (Val::String("\t\r\n\0".into()), r#""\t\r\n\u{0}""#), 472 | ] { 473 | let got = crate::to_string(&val) 474 | .unwrap_or_else(|err| panic!("failed to serialize {val:?}: {err}")); 475 | assert_eq!(got, want, "for {val:?}"); 476 | } 477 | } 478 | 479 | #[test] 480 | fn test_round_trip_floats() { 481 | use std::fmt::Debug; 482 | use wasmtime::component::{Type, Val}; 483 | 484 | fn round_trip(ty: &V::Type, val: &V) { 485 | let val_str = crate::to_string(val).unwrap(); 486 | let result: V = crate::from_str::(ty, &val_str).unwrap(); 487 | assert_eq!(val, &result); 488 | } 489 | 490 | for i in 0..100 { 491 | for j in 0..100 { 492 | round_trip(&Type::Float32, &Val::Float32(i as f32 / j as f32)); 493 | round_trip(&Type::Float64, &Val::Float64(i as f64 / j as f64)); 494 | } 495 | } 496 | 497 | round_trip(&Type::Float32, &Val::Float32(f32::EPSILON)); 498 | round_trip(&Type::Float64, &Val::Float64(f64::EPSILON)); 499 | } 500 | } 501 | --------------------------------------------------------------------------------