Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ To run the tests, you will need:
- The emcc compiler: <https://emscripten.org/docs/getting_started/downloads.html>
- An up-to-date Rust toolchain: <https://www.rust-lang.org/>
- A zig compiler, version `0.11`: <https://ziglang.org/learn/getting-started/#installing-zig>
- A tinygo compiler, version `0.32`: <https://tinygo.org/getting-started/install/>

Then, you can run the tests with `cargo test`.

Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Note that plugins require typst version `0.8` or more.

## You want to write a plugin

A plugin can be written in Rust, C, Zig, or any language than compiles to WebAssembly.
A plugin can be written in Rust, C, Zig, Go or any language than compiles to WebAssembly.

Rust plugins can use this crate to automatically implement the protocol with a macro:

Expand Down Expand Up @@ -37,6 +37,7 @@ See the example for your language:
- [Rust](examples/hello_rust/)
- [Zig](examples/hello_zig/)
- [C](examples/hello_c/)
- [Go](examples/hello_go/)

If you have all the required dependencies, you may build all examples by running `cargo test`.

Expand Down
35 changes: 27 additions & 8 deletions crates/macro/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn wasi_stub(path: String) {
.arg(&path)
.arg("-o")
.arg(&path)
.current_dir("wasi-stub")
.current_dir("../wasi-stub")
.status()
.unwrap();
if !wasi_stub.success() {
Expand Down Expand Up @@ -49,7 +49,7 @@ fn typst_compile(path: &str) {

#[test]
fn test_c() {
let dir_path = "examples/hello_c/".to_string();
let dir_path = "../../examples/hello_c/".to_string();
let build_c = Command::new("emcc")
.arg("--no-entry")
.arg("-O3")
Expand All @@ -70,7 +70,7 @@ fn test_c() {

#[test]
fn test_rust() {
let dir_path = "examples/hello_rust/".to_string();
let dir_path = "../../examples/hello_rust/".to_string();
let build_rust = Command::new("cargo")
.arg("build")
.arg("--release")
Expand All @@ -94,13 +94,13 @@ fn test_rust() {
panic!("Compiling with cargo failed");
}
std::fs::copy(
"examples/hello_rust/target/wasm32-unknown-unknown/release/hello.wasm",
"examples/hello_rust/hello.wasm",
"../../examples/hello_rust/target/wasm32-unknown-unknown/release/hello.wasm",
"../../examples/hello_rust/hello.wasm",
)
.unwrap();
std::fs::copy(
"examples/hello_rust/target/wasm32-wasi/release/hello.wasm",
"examples/hello_rust/hello-wasi.wasm",
"../../examples/hello_rust/target/wasm32-wasi/release/hello.wasm",
"../../examples/hello_rust/hello-wasi.wasm",
)
.unwrap();
wasi_stub(dir_path.clone() + "hello-wasi.wasm");
Expand All @@ -109,7 +109,7 @@ fn test_rust() {

#[test]
fn test_zig() {
let dir_path = "examples/hello_zig/".to_string();
let dir_path = "../../examples/hello_zig/".to_string();
let build_zig = Command::new("zig")
.arg("build-lib")
.arg("hello.zig")
Expand Down Expand Up @@ -144,3 +144,22 @@ fn test_zig() {
wasi_stub(dir_path.clone() + "hello-wasi.wasm");
typst_compile(&dir_path);
}

#[test]
fn test_go() {
let dir_path = "examples/hello_go/".to_string();
let build_go_wasi = Command::new("tinygo")
.arg("build")
.arg("-o")
.arg("hello.wasm")
.env("GOOS", "wasip1")
.env("GOARCH", "wasm")
.current_dir(&dir_path)
.status()
.unwrap();
if !build_go_wasi.success() {
panic!("Compiling with tinygo failed");
}
wasi_stub(dir_path.clone() + "hello.wasm");
typst_compile(&dir_path);
}
28 changes: 28 additions & 0 deletions examples/hello_go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Go wasm plugin example

This is a bare-bone typst plugin, written in Go.

## Compile

To compile this example, you need the [TinyGo compiler]. Then, run the command

```sh
GOOS="wasip1" GOARCH="wasm" tinygo build -o hello.wasm
```

[TinyGo compiler]: https://tinygo.org/getting-started/install/


This invocation of Tinygo builds with WASI[^1], so we need to stub WASI functions:

[^1]: I personally could not get a working binary with `GOOS="js"` (i.e. wasm).

```sh
pushd ../../wasi-stub
cargo run -- ../examples/hello_go/hello.wasm -o ../examples/hello_go/hello.wasm
popd
```

## Build with typst

Simply run `typst compile hello.typ`, and observe that it works !
3 changes: 3 additions & 0 deletions examples/hello_go/go.mod
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what I mean by submodule, you added a git submodule here

removing this file, adding and commiting should do the trick

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/astrale-sharp/wasm-minimal-protocol/tree/master/examples/hello_go

go 1.22.2
95 changes: 95 additions & 0 deletions examples/hello_go/hello.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package main

import (
"unsafe"
)

// ===
// functions for the protocol

//go:wasmimport typst_env wasm_minimal_protocol_write_args_to_buffer
func write_args_to_buffer(ptr int32)

func WriteArgsToBuffer(argBuf []byte) {
ptr := int32(uintptr(unsafe.Pointer(unsafe.SliceData(argBuf))))
write_args_to_buffer(ptr)
}

//go:wasmimport typst_env wasm_minimal_protocol_send_result_to_host
func send_result_to_host(ptr, size int32)

func SendResultToHost(resBuf []byte) {
size := int32(len(resBuf))
ptr := int32(uintptr(unsafe.Pointer(unsafe.SliceData(resBuf))))
send_result_to_host(ptr, size)
}

// ===

// main is required for the `wasip1` target, even if it isn't used.
func main() {}

//go:export hello
func hello() int32 {
const message = "Hello from wasm!!!"
SendResultToHost([]byte(message))
return 0
}

//go:export double_it
func doubleIt(arg1Len int32) int32 {
buf := make([]byte, arg1Len*2)
WriteArgsToBuffer(buf)

copy(buf[arg1Len:], buf[:arg1Len])
SendResultToHost(buf)
return 0
}

//go:export concatenate
func concatenate(arg1Len, arg2Len int32) int32 {
buf := make([]byte, arg1Len+arg2Len+1)
WriteArgsToBuffer(buf)

copy(buf[arg1Len+1:], buf[arg1Len:])
buf[arg1Len] = '*'
SendResultToHost(buf)
return 0
}

//go:export shuffle
func shuffle(arg1Len, arg2Len, arg3Len int) int32 {
argBuf := make([]byte, arg1Len+arg2Len+arg3Len)
arg1 := argBuf[:arg1Len]
arg2 := argBuf[arg1Len : arg1Len+arg2Len]
arg3 := argBuf[arg1Len+arg2Len:]
WriteArgsToBuffer(argBuf)

resBuf := make([]byte, 0, arg1Len+arg2Len+arg3Len+2)
resBuf = append(resBuf, arg3...)
resBuf = append(resBuf, '-')
resBuf = append(resBuf, arg1...)
resBuf = append(resBuf, '-')
resBuf = append(resBuf, arg2...)
SendResultToHost(resBuf)
return 0
}

//go:export returns_ok
func returnsOk() int32 {
const message = "This is an `Ok`"
SendResultToHost([]byte(message))
return 0
}

//go:export returns_err
func returnsErr() int32 {
const message = "This is an `Err`"
SendResultToHost([]byte(message))
return 1
}

//go:export will_panic
func willPanic() int32 {
panic("Panicking, this message will not be seen...")
}
11 changes: 11 additions & 0 deletions examples/hello_go/hello.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#{
let p = plugin("./hello.wasm")

assert.eq(str(p.hello()), "Hello from wasm!!!")
assert.eq(str(p.double_it(bytes("abc"))), "abcabc")
assert.eq(str(p.concatenate(bytes("hello"), bytes("world"))), "hello*world")
assert.eq(str(p.shuffle(bytes("s1"), bytes("s2"), bytes("s3"))), "s3-s1-s2")
assert.eq(str(p.returns_ok()), "This is an `Ok`")
// p.will_panic() // Fails compilation
// p.returns_err() // Fails compilation with an error message
}