Add wasm async example (#1037)

This commit is contained in:
Dirk Bäumer
2024-05-29 16:13:03 +02:00
committed by GitHub
parent e3c33f9ce0
commit bdec986966
15 changed files with 3795 additions and 0 deletions

View File

@ -0,0 +1,6 @@
node_modules/**
client/node_modules/**
client/out/**
server/node_modules/**
server/out/**
bin

View File

@ -0,0 +1,21 @@
/**@type {import('eslint').Linter.Config} */
// eslint-disable-next-line no-undef
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
'semi': [2, "always"],
'@typescript-eslint/no-unused-vars': 0,
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/explicit-module-boundary-types': 0,
'@typescript-eslint/no-non-null-assertion': 0,
'@typescript-eslint/no-namespace': "off"
}
};

2
wasm-component-model-async/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
target
dist

View File

@ -0,0 +1,19 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "extensionHost",
"request": "launch",
"name": "Run Example",
"runtimeExecutable": "${execPath}",
"args": [
"--extensionDevelopmentPath=${workspaceRoot}",
],
"outFiles": [
"${workspaceRoot}/out/**/*.js"
],
"sourceMaps": true,
"preLaunchTask": "npm: build"
}
]
}

311
wasm-component-model-async/Cargo.lock generated Normal file
View File

@ -0,0 +1,311 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
[[package]]
name = "bitflags"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "calculator"
version = "0.1.0"
dependencies = [
"wit-bindgen",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "id-arena"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005"
[[package]]
name = "indexmap"
version = "2.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [
"equivalent",
"hashbrown",
"serde",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
[[package]]
name = "log"
version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
[[package]]
name = "proc-macro2"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ryu"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1"
[[package]]
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.115"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "spdx"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9"
dependencies = [
"smallvec",
]
[[package]]
name = "syn"
version = "2.0.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-xid"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "wasm-encoder"
version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9c7d2731df60006819b013f64ccc2019691deccf6e11a1804bc850cd6748f1a"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-metadata"
version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2"
dependencies = [
"anyhow",
"indexmap",
"serde",
"serde_derive",
"serde_json",
"spdx",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708"
dependencies = [
"bitflags",
"indexmap",
"semver",
]
[[package]]
name = "wit-bindgen"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbdedb8dd38c89c2cfa71e7450515f1c17f94cc2853881652d005b10f3f2559c"
dependencies = [
"bitflags",
"wit-bindgen-rt",
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen-core"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebcbf07363368a9e6e8b89c18bff176c4f35ed2dd2f2f5f9c473bb56813369b"
dependencies = [
"anyhow",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "026d24a27f6712541fa534f2954bd9e0eb66172f033c2157c0f31d106255c497"
[[package]]
name = "wit-bindgen-rust"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a4d36bf13b5ef534599d24dae792b1ae2b40fe1248c2754fd3f7343fb2ca70"
dependencies = [
"anyhow",
"heck",
"indexmap",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4bf1b15b5227d1ca9ba7fc6a7850c72f9df0fbac3c46a0855763cc454ff11a"
dependencies = [
"anyhow",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.201.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]

View File

@ -0,0 +1,12 @@
[package]
name = "calculator"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib"]
[dependencies]
wit-bindgen = "0.21.0"

View File

@ -0,0 +1,27 @@
# WASM Component Model Example
An example demonstrating how to use the component model to integrate WebAssembly code into VS Code.
## Functionality
A simple calculator that can perform add, sub, mul and div. The calculator is implemented in Rust and compiled to WebAssembly code and then call from JavaScript.
## Pre-requisites
To run the sample the following tool chains need to be installed
- [Rust](https://www.rust-lang.org/): installation instructions can be found [here](https://www.rust-lang.org/tools/install)
- [wasm-tools](https://github.com/bytecodealliance/wasm-tools): releases can be found [here](https://github.com/bytecodealliance/wasm-tools/releases). You need at least version >= 1.200 to run the example.
## Running the Sample in the Desktop
- Run `npm install` in this folder. This installs all necessary npm modules.
- Open VS Code on this folder.
- Execute the launch config `Run Example`.
## Running the Sample in the Web
As a pre-requisite follow the instructions [here](https://code.visualstudio.com/api/extension-guides/web-extensions#test-your-web-extension-in-vscode.dev) to generate necessary certificate to side load the extension into vscode.dev or insiders.vscode.dev.
Then compile the extension for the Web by running `npm run esbuild`, start a local extension server using `npm run serve`, open vscode.dev or insiders.vscode.dev in a browser and execute the command `Install Extension from Location`. As a location use `https://localhost:5000`.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
{
"name": "wasm-component-model-async",
"description": "An example demonstrating how to use the component model to integrate WebAssembly modules into VS Code.",
"author": "Microsoft Corporation",
"license": "MIT",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-extension-samples"
},
"publisher": "vscode-samples",
"categories": [],
"keywords": [
"WASM",
"Component Model"
],
"engines": {
"vscode": "^1.89.0"
},
"main": "./out/extension",
"browser": "./dist/web/extension",
"contributes": {
"commands": [
{
"command": "vscode-samples.wasm-component-model-async.run",
"category": "Samples",
"title": "Run Calc Service"
}
]
},
"dependencies": {
"@vscode/wasm-component-model": "0.1.0-pre.12"
},
"devDependencies": {
"@types/vscode": "1.89.0",
"@types/node": "^18.14.6",
"@typescript-eslint/eslint-plugin": "^7.6.0",
"@typescript-eslint/parser": "^7.6.0",
"eslint": "^8.57.0",
"typescript": "^5.4.5",
"esbuild": "^0.20.2",
"serve": "^14.2.1"
},
"scripts": {
"vscode:prepublish": "npm run compile",
"build": "npm run compile && cargo build --target wasm32-unknown-unknown",
"compile": "tsc -b",
"watch": "tsc -b -w",
"lint": "eslint ./src --ext .ts,.tsx",
"generate:model": "wit2ts --outDir ./src ./wit",
"serve": "serve --cors -l 5000 --ssl-cert $HOME/certs/localhost.pem --ssl-key $HOME/certs/localhost-key.pem"
}
}

View File

@ -0,0 +1,168 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* eslint-disable @typescript-eslint/ban-types */
import * as $wcm from '@vscode/wasm-component-model';
import type { u32, i32 } from '@vscode/wasm-component-model';
export namespace Types {
export type Operands = {
left: u32;
right: u32;
};
export namespace Operation {
export const add = 'add' as const;
export type Add = { readonly tag: typeof add; readonly value: Operands } & _common;
export function Add(value: Operands): Add {
return new VariantImpl(add, value) as Add;
}
export const sub = 'sub' as const;
export type Sub = { readonly tag: typeof sub; readonly value: Operands } & _common;
export function Sub(value: Operands): Sub {
return new VariantImpl(sub, value) as Sub;
}
export const mul = 'mul' as const;
export type Mul = { readonly tag: typeof mul; readonly value: Operands } & _common;
export function Mul(value: Operands): Mul {
return new VariantImpl(mul, value) as Mul;
}
export const div = 'div' as const;
export type Div = { readonly tag: typeof div; readonly value: Operands } & _common;
export function Div(value: Operands): Div {
return new VariantImpl(div, value) as Div;
}
export type _tt = typeof add | typeof sub | typeof mul | typeof div;
export type _vt = Operands | Operands | Operands | Operands;
type _common = Omit<VariantImpl, 'tag' | 'value'>;
export function _ctor(t: _tt, v: _vt): Operation {
return new VariantImpl(t, v) as Operation;
}
class VariantImpl {
private readonly _tag: _tt;
private readonly _value: _vt;
constructor(t: _tt, value: _vt) {
this._tag = t;
this._value = value;
}
get tag(): _tt {
return this._tag;
}
get value(): _vt {
return this._value;
}
isAdd(): this is Add {
return this._tag === Operation.add;
}
isSub(): this is Sub {
return this._tag === Operation.sub;
}
isMul(): this is Mul {
return this._tag === Operation.mul;
}
isDiv(): this is Div {
return this._tag === Operation.div;
}
}
}
export type Operation = Operation.Add | Operation.Sub | Operation.Mul | Operation.Div;
}
export type Types = {
};
export namespace calculator {
export type Operation = Types.Operation;
export type Imports = {
log: (msg: string) => void;
};
export namespace Imports {
export type Promisified = $wcm.$imports.Promisify<Imports>
}
export namespace imports {
export type Promisify<T> = $wcm.$imports.Promisify<T>;
}
export type Exports = {
calc: (o: Operation) => u32;
};
export namespace Exports {
export type Promisified = $wcm.$exports.Promisify<Exports>
}
export namespace exports {
export type Promisify<T> = $wcm.$exports.Promisify<T>;
}
}
export namespace Types.$ {
export const Operands = new $wcm.RecordType<Types.Operands>([
['left', $wcm.u32],
['right', $wcm.u32],
]);
export const Operation = new $wcm.VariantType<Types.Operation, Types.Operation._tt, Types.Operation._vt>([['add', Operands], ['sub', Operands], ['mul', Operands], ['div', Operands]], Types.Operation._ctor);
}
export namespace Types._ {
export const id = 'vscode:example/types' as const;
export const witName = 'types' as const;
export const types: Map<string, $wcm.GenericComponentModelType> = new Map<string, $wcm.GenericComponentModelType>([
['Operands', $.Operands],
['Operation', $.Operation]
]);
export type WasmInterface = {
};
}
export namespace calculator.$ {
export const Operation = Types.$.Operation;
export namespace imports {
export const log = new $wcm.FunctionType<calculator.Imports['log']>('log',[
['msg', $wcm.wstring],
], undefined);
}
export namespace exports {
export const calc = new $wcm.FunctionType<calculator.Exports['calc']>('calc',[
['o', Operation],
], $wcm.u32);
}
}
export namespace calculator._ {
export const id = 'vscode:example/calculator' as const;
export const witName = 'calculator' as const;
export type $Root = {
'log': (msg_ptr: i32, msg_len: i32) => void;
};
export namespace imports {
export const functions: Map<string, $wcm.FunctionType> = new Map([
['log', $.imports.log]
]);
export const interfaces: Map<string, $wcm.InterfaceType> = new Map<string, $wcm.InterfaceType>([
['Types', Types._]
]);
export function create(service: calculator.Imports, context: $wcm.WasmContext): Imports {
return $wcm.$imports.create<Imports>(_, service, context);
}
export function loop(service: calculator.Imports, context: $wcm.WasmContext): calculator.Imports {
return $wcm.$imports.loop(_, service, context);
}
}
export type Imports = {
'$root': $Root;
};
export namespace exports {
export const functions: Map<string, $wcm.FunctionType> = new Map([
['calc', $.exports.calc]
]);
export function bind(exports: Exports, context: $wcm.WasmContext): calculator.Exports {
return $wcm.$exports.bind<calculator.Exports>(_, exports, context);
}
}
export type Exports = {
'calc': (o_Operation_case: i32, o_Operation_0: i32, o_Operation_1: i32) => i32;
};
export function bind(service: calculator.Imports, code: $wcm.Code, context?: $wcm.ComponentModelContext): Promise<calculator.Exports>;
export function bind(service: $wcm.$imports.Promisify<calculator.Imports>, code: $wcm.Code, port: $wcm.RAL.ConnectionPort, context?: $wcm.ComponentModelContext): Promise<$wcm.$exports.Promisify<calculator.Exports>>;
export function bind(service: calculator.Imports | $wcm.$imports.Promisify<calculator.Imports>, code: $wcm.Code, portOrContext?: $wcm.RAL.ConnectionPort | $wcm.ComponentModelContext, context?: $wcm.ComponentModelContext | undefined): Promise<calculator.Exports> | Promise<$wcm.$exports.Promisify<calculator.Exports>> {
return $wcm.$main.bind(_, service, code, portOrContext, context);
}
}

View File

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Worker } from 'worker_threads';
// Import the code generated by wit2ts
import { Types, calculator } from './calculator';
export async function activate(context: vscode.ExtensionContext): Promise<void> {
// The channel for printing the result.
const channel = vscode.window.createOutputChannel('Calculator');
context.subscriptions.push(channel);
// The channel for printing the log.
const log = vscode.window.createOutputChannel('Calculator - Log', { log: true });
context.subscriptions.push(log);
// The implementation of the log function that is called from WASM
const service: calculator.Imports.Promisified = {
log: (msg: string) => {
log.info(msg);
}
};
// Load the Wasm module
const filename = vscode.Uri.joinPath(context.extensionUri, 'target', 'wasm32-unknown-unknown', 'debug', 'calculator.wasm');
const bits = await vscode.workspace.fs.readFile(filename);
const module = await WebAssembly.compile(bits);
const worker = new Worker(vscode.Uri.joinPath(context.extensionUri, './out/worker.js').fsPath);
const api = await calculator._.bind(service, module, worker);
context.subscriptions.push(vscode.commands.registerCommand('vscode-samples.wasm-component-model-async.run', async () => {
channel.show();
channel.appendLine('Running calculator example');
const add = Types.Operation.Add({ left: 1, right: 2});
channel.appendLine(`Add ${await api.calc(add)}`);
const sub = Types.Operation.Sub({ left: 10, right: 8 });
channel.appendLine(`Sub ${await api.calc(sub)}`);
const mul = Types.Operation.Mul({ left: 3, right: 7 });
channel.appendLine(`Mul ${await api.calc(mul)}`);
const div = Types.Operation.Div({ left: 10, right: 2 });
channel.appendLine(`Div ${await api.calc(div)}`);
}));
}

View File

@ -0,0 +1,26 @@
// Use a procedural macro to generate bindings for the world we specified in
// `host.wit`
wit_bindgen::generate!({
// the name of the world in the `*.wit` input file
world: "calculator",
});
struct Calculator;
impl Guest for Calculator {
fn calc(op: Operation) -> u32 {
log(&format!("Starting calculation: {:?}", op));
let result = match op {
Operation::Add(operands) => operands.left + operands.right,
Operation::Sub(operands) => operands.left - operands.right,
Operation::Mul(operands) => operands.left * operands.right,
Operation::Div(operands) => operands.left / operands.right,
};
log(&format!("Finished calculation: {:?}", op));
result
}
}
// Export the Calculator to the extension code.
export!(Calculator);

View File

@ -0,0 +1,13 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Connection, RAL } from '@vscode/wasm-component-model';
import { calculator } from './calculator';
async function main(): Promise<void> {
const connection = await Connection.createWorker(calculator._);
connection.listen();
}
main().catch(RAL().console.error);

View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"rootDir": "./src",
"skipLibCheck": true,
"lib": [
"es2022",
"webworker"
],
"types": [
"vscode",
"node"
],
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./out",
"strict": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"declaration": true,
"stripInternal": true,
"sourceMap": true,
"declarationMap": true,
"noUnusedLocals": false,
"noUnusedParameters": false
}
}

View File

@ -0,0 +1,22 @@
// wit/calculator.wit
package vscode:example;
interface types {
record operands {
left: u32,
right: u32
}
variant operation {
add(operands),
sub(operands),
mul(operands),
div(operands)
}
}
world calculator {
use types.{ operation };
import log: func(msg: string);
export calc: func(o: operation) -> u32;
}