Skip to content

Commit

Permalink
Start work on a C API
Browse files Browse the repository at this point in the history
  • Loading branch information
udoprog committed Jan 2, 2022
1 parent 9abc6c9 commit af685f4
Show file tree
Hide file tree
Showing 37 changed files with 3,358 additions and 13 deletions.
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

0 comments on commit af685f4

Please sign in to comment.