Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start work on a C API #380

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@
/flamegraph.svg
/perf.data
/perf.data.*
/builddir
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ members = [
"crates/rune-macros",
"crates/rune-modules",
"crates/rune-wasm",
"capi",
"tests",
"examples",
"benches",
Expand Down
13 changes: 13 additions & 0 deletions capi/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "rune-capi"
version = "0.0.0"
edition = "2021"
publish = false

[lib]
name = "rune"
crate-type = ["staticlib", "cdylib"]

[dependencies]
rune = { package = "rune", path = "../crates/rune", features = ["ffi"] }
cbindgen = "0.20.0"
32 changes: 32 additions & 0 deletions capi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Rune C API

This crate is not intended to be published, but instead contains the necessary
bindings to provide a C API for Rune.

## Building and running examples

Examples are built using meson, and requires that the static library is already
built and available in `target/debug` (or `target/release`). Note that for
cbindgen to work it requires `+nightly`.

```
cargo build --package rune-capi
meson setup builddir capi
ninja -C builddir
```

After this, you can find the binaries corresponding to their names in
[`examples`](examples) in `target/builddir`.

When building and running on Windows you might have to run through the [MSVC
development
shell](https://docs.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell)
to have access to the C compiler.

## Regenerating header file

Since we use macros, the header can only be regenerated using `+nightly`.

```sh
cargo +nightly run --package rune-capi --bin cbindgen
```
34 changes: 34 additions & 0 deletions capi/cbindgen.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
language = "C"
include_guard = "RUNE_H"
no_includes = true
sys_includes = ["stdbool.h", "stdint.h", "stddef.h"]
cpp_compat = true
documentation_style = "doxy"

style = "type"

[export.rename]
"Build" = "rune_build"
"ColorChoice" = "rune_color_choice"
"Context" = "rune_context"
"ContextError" = "rune_context_error"
"Diagnostics" = "rune_diagnostics"
"Hash" = "rune_hash"
"Module" = "rune_module"
"RuntimeContext" = "rune_runtime_context"
"Source" = "rune_source"
"Sources" = "rune_sources"
"Stack" = "rune_stack"
"StandardStream" = "rune_standard_stream"
"Unit" = "rune_unit"
"Value" = "rune_value"
"Vm" = "rune_vm"
"VmError" = "rune_vm_error"
"StaticType" = "rune_static_type"

[parse.expand]
crates = ["rune-capi"]

[enum]
rename_variants = "ScreamingSnakeCase"
prefix_with_name = true
124 changes: 124 additions & 0 deletions capi/examples/function.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#include <assert.h>
#include <stdio.h>

#include <rune.h>

/**
* A custom C function that interacts with Rune. This is registered below with
* rune_module_function.
*/
void custom_function(rune_stack *stack, uintptr_t count, rune_vm_error *e) {
rune_value value = rune_value_unit();

if (count != 1) {
rune_vm_error_bad_argument_count(e, count, 1);
return;
}

// Note: Error will be automatically propagated since it's used as an output
// argument.
if (!rune_stack_pop_value(stack, &value, e)) {
return;
}

int64_t integer = 0;

if (!rune_value_as_integer(&value, &integer)) {
rune_vm_error_bad_argument_at(e, 0, &value, RUNE_INTEGER_TYPE);
return;
}

rune_stack_push_unit(stack);
rune_stack_push_integer(stack, integer * 10);
rune_stack_push_tuple(stack, 2, e);
}

int main() {
rune_context context = rune_context_new();
rune_module module = rune_module_new();
rune_runtime_context runtime = rune_runtime_context_new();
rune_sources sources = rune_sources_new();
rune_standard_stream out = rune_standard_stream_stderr(RUNE_COLOR_CHOICE_ALWAYS);
rune_unit unit = rune_unit_new();
rune_vm vm = rune_vm_new();
rune_vm_error error = rune_vm_error_new();
rune_context_error context_error = rune_context_error_new();

if (!rune_module_function(&module, "test", custom_function, &context_error)) {
rune_context_error_emit(&context_error, &out);
goto EXIT;
}

if (!rune_context_install(&context, &module, &context_error)) {
rune_context_error_emit(&context_error, &out);
goto EXIT;
}

rune_module_free(&module);

rune_source source = rune_source_new("<in>", "pub fn main(n) { test(n) }");
assert(rune_sources_insert(&sources, &source));
rune_source_free(&source);

rune_diagnostics diag = rune_diagnostics_new();

rune_build build = rune_build_prepare(&sources);
rune_build_with_diagnostics(&build, &diag);
rune_build_with_context(&build, &context);

bool ok = rune_build_build(&build, &unit);

if (!rune_diagnostics_is_empty(&diag)) {
assert(rune_diagnostics_emit(&diag, &out, &sources));
}

rune_diagnostics_free(&diag);

if (!ok) {
goto EXIT;
}

assert(rune_context_runtime(&context, &runtime));
assert(rune_vm_setup(&vm, &runtime, &unit));

rune_hash entry = rune_hash_name("main");

if (!rune_vm_set_entrypoint(&vm, entry, 1, &error)) {
assert(rune_vm_error_emit(&error, &out, &sources));
goto EXIT;
}

rune_stack_push_integer(rune_vm_stack_mut(&vm), 42);
rune_value ret = rune_value_unit();

if (!rune_vm_complete(&vm, &ret, &error)) {
assert(rune_vm_error_emit(&error, &out, &sources));
}

int64_t output = 0;

if (rune_value_as_integer(&ret, &output)) {
printf("output = %lld\n", output);
} else {
rune_hash type_hash = rune_hash_empty();

if (rune_value_type_hash(&ret, &type_hash, &error)) {
printf("output = %lld\n", type_hash);
} else {
printf("output = ?\n");
}
}

rune_value_free(&ret);

EXIT:
rune_context_free(&context);
rune_module_free(&module);
rune_runtime_context_free(&runtime);
rune_sources_free(&sources);
rune_standard_stream_free(&out);
rune_unit_free(&unit);
rune_vm_error_free(&error);
rune_vm_free(&vm);
return 0;
}
72 changes: 72 additions & 0 deletions capi/examples/minimal.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#include <assert.h>
#include <stdio.h>

#include <rune.h>

int main() {
rune_context context = rune_context_new();
rune_runtime_context runtime = rune_runtime_context_new();
rune_sources sources = rune_sources_new();
rune_standard_stream out = rune_standard_stream_stderr(RUNE_COLOR_CHOICE_ALWAYS);
rune_unit unit = rune_unit_new();
rune_vm vm = rune_vm_new();
rune_vm_error error = rune_vm_error_new();

rune_source source = rune_source_new("<in>", "pub fn add_one(n) { n / 3 }");
assert(rune_sources_insert(&sources, &source));
rune_source_free(&source);

rune_diagnostics diag = rune_diagnostics_new();

rune_build build = rune_build_prepare(&sources);
rune_build_with_diagnostics(&build, &diag);

bool ok = rune_build_build(&build, &unit);

if (!rune_diagnostics_is_empty(&diag)) {
assert(rune_diagnostics_emit(&diag, &out, &sources));
}

rune_diagnostics_free(&diag);

if (!ok) {
goto EXIT;
}

assert(rune_context_runtime(&context, &runtime));
assert(rune_vm_setup(&vm, &runtime, &unit));

rune_hash entry = rune_hash_name("add_one");

if (!rune_vm_set_entrypoint(&vm, entry, 1, &error)) {
assert(rune_vm_error_emit(&error, &out, &sources));
goto EXIT;
}

rune_stack_push_integer(rune_vm_stack_mut(&vm), 42);
rune_value ret = rune_value_unit();

if (!rune_vm_complete(&vm, &ret, &error)) {
assert(rune_vm_error_emit(&error, &out, &sources));
}

int64_t output = 0;

if (rune_value_as_integer(&ret, &output)) {
printf("output = %lld\n", output);
} else {
printf("output = ?\n");
}

rune_value_free(&ret);

EXIT:
rune_context_free(&context);
rune_runtime_context_free(&runtime);
rune_sources_free(&sources);
rune_standard_stream_free(&out);
rune_unit_free(&unit);
rune_vm_error_free(&error);
rune_vm_free(&vm);
return 0;
}
16 changes: 16 additions & 0 deletions capi/examples/type_hash.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include <assert.h>
#include <stdio.h>

#include <rune.h>

int main() {
rune_value a = rune_value_integer(42);
rune_value b = rune_value_bool(false);
rune_vm_error error = rune_vm_error_new();

assert(rune_value_type_hash_or_empty(&a) == RUNE_INTEGER_TYPE_HASH);
assert(rune_value_type_hash_or_empty(&b) == RUNE_BOOL_TYPE_HASH);

rune_vm_error_free(&error);
return 0;
}
32 changes: 32 additions & 0 deletions capi/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
project('examples', 'c')

c = meson.get_compiler('c')

inc = include_directories('.')

dirs = [
meson.current_source_dir() + '/../target/debug',
meson.current_source_dir() + '/../target/release',
]

extra = []

if host_machine.system() == 'windows'
extra = [
c.find_library('ws2_32', required: true),
c.find_library('bcrypt', required: true),
c.find_library('userenv', required: true),
]
endif

rune_dep = c.find_library('rune', dirs: dirs, required: false)

if not rune_dep.found()
error('Could not find rune library, try: cargo build --package rune-capi')
endif

deps = [rune_dep] + extra

executable('function', 'examples/function.c', dependencies: deps, include_directories: inc)
executable('minimal', 'examples/minimal.c', dependencies: deps, include_directories: inc)
executable('type_hash', 'examples/type_hash.c', dependencies: deps, include_directories: inc)
Loading