Skip to content

Commit 53de2ca

Browse files
committed
Start work on a C API
1 parent 9abc6c9 commit 53de2ca

37 files changed

+3342
-13
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
/flamegraph.svg
1010
/perf.data
1111
/perf.data.*
12+
/builddir

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ members = [
66
"crates/rune-macros",
77
"crates/rune-modules",
88
"crates/rune-wasm",
9+
"capi",
910
"tests",
1011
"examples",
1112
"benches",

capi/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "rune-capi"
3+
version = "0.0.0"
4+
edition = "2021"
5+
publish = false
6+
7+
[lib]
8+
name = "rune"
9+
crate-type = ["staticlib", "cdylib"]
10+
11+
[dependencies]
12+
rune = { package = "rune", path = "../crates/rune", features = ["ffi"] }
13+
cbindgen = "0.20.0"

capi/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Rune C API
2+
3+
This crate is not intended to be published, but instead contains the necessary
4+
bindings to provide a C API for Rune.
5+
6+
## Building and running examples
7+
8+
Examples are built using meson, and requires that the static library is already
9+
built and available in `target/debug` (or `target/release`). Note that for
10+
cbindgen to work it requires `+nightly`.
11+
12+
```
13+
cargo build --package rune-capi
14+
meson setup builddir capi
15+
ninja -C builddir
16+
```
17+
18+
After this, you can find the binaries corresponding to their names in
19+
[`examples`](examples) in `target/builddir`.
20+
21+
When building and running on Windows you might have to run through the [MSVC
22+
development
23+
shell](https://docs.microsoft.com/en-us/visualstudio/ide/reference/command-prompt-powershell)
24+
to have access to the C compiler.
25+
26+
## Regenerating header file
27+
28+
Since we use macros, the header can only be regenerated using `+nightly`.
29+
30+
```sh
31+
cargo +nightly run --package rune-capi --bin cbindgen
32+
```

capi/cbindgen.toml

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
language = "C"
2+
include_guard = "RUNE_H"
3+
no_includes = true
4+
sys_includes = ["stdbool.h", "stdint.h", "stddef.h"]
5+
cpp_compat = true
6+
documentation_style = "doxy"
7+
8+
style = "type"
9+
10+
[export.rename]
11+
"Build" = "rune_build"
12+
"ColorChoice" = "rune_color_choice"
13+
"Context" = "rune_context"
14+
"ContextError" = "rune_context_error"
15+
"Diagnostics" = "rune_diagnostics"
16+
"Hash" = "rune_hash"
17+
"Module" = "rune_module"
18+
"RuntimeContext" = "rune_runtime_context"
19+
"Source" = "rune_source"
20+
"Sources" = "rune_sources"
21+
"Stack" = "rune_stack"
22+
"StandardStream" = "rune_standard_stream"
23+
"Unit" = "rune_unit"
24+
"Value" = "rune_value"
25+
"Vm" = "rune_vm"
26+
"VmError" = "rune_vm_error"
27+
"StaticType" = "rune_static_type"
28+
29+
[parse.expand]
30+
crates = ["rune-capi"]
31+
32+
[enum]
33+
rename_variants = "ScreamingSnakeCase"
34+
prefix_with_name = true

capi/examples/function.c

+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
4+
#include <rune.h>
5+
6+
/**
7+
* A custom C function that interacts with Rune. This is registered below with
8+
* rune_module_function.
9+
*/
10+
void custom_function(rune_stack *stack, uintptr_t count, rune_vm_error *e) {
11+
rune_value value = rune_value_unit();
12+
13+
if (count != 1) {
14+
vm_error_bad_argument_count(e, count, 1);
15+
return;
16+
}
17+
18+
// Note: Error will be automatically propagated since it's used as an output
19+
// argument.
20+
if (!rune_stack_pop_value(stack, &value, e)) {
21+
return;
22+
}
23+
24+
int64_t integer = 0;
25+
26+
if (!rune_value_as_integer(&value, &integer)) {
27+
vm_error_bad_argument_at(e, 0, &value, RUNE_INTEGER_TYPE);
28+
return;
29+
}
30+
31+
rune_stack_push_unit(stack);
32+
rune_stack_push_integer(stack, integer * 10);
33+
rune_stack_push_tuple(stack, 2, e);
34+
}
35+
36+
int main() {
37+
rune_context context = rune_context_new();
38+
rune_module module = rune_module_new();
39+
rune_runtime_context runtime = rune_runtime_context_new();
40+
rune_sources sources = rune_sources_new();
41+
rune_standard_stream out = rune_standard_stream_stderr(RUNE_COLOR_CHOICE_ALWAYS);
42+
rune_unit unit = rune_unit_new();
43+
rune_vm vm = rune_vm_new();
44+
rune_vm_error error = rune_vm_error_new();
45+
rune_context_error context_error = rune_context_error_new();
46+
47+
if (!rune_module_function(&module, "test", custom_function, &context_error)) {
48+
rune_context_error_emit(&context_error, &out);
49+
goto EXIT;
50+
}
51+
52+
if (!rune_context_install(&context, &module, &context_error)) {
53+
rune_context_error_emit(&context_error, &out);
54+
goto EXIT;
55+
}
56+
57+
rune_module_free(&module);
58+
59+
rune_source source = rune_source_new("<in>", "pub fn main(n) { test(n) }");
60+
assert(rune_sources_insert(&sources, &source));
61+
rune_source_free(&source);
62+
63+
rune_diagnostics diag = rune_diagnostics_new();
64+
65+
rune_build build = rune_build_prepare(&sources);
66+
rune_build_with_diagnostics(&build, &diag);
67+
rune_build_with_context(&build, &context);
68+
69+
bool ok = rune_build_build(&build, &unit);
70+
71+
if (!rune_diagnostics_is_empty(&diag)) {
72+
assert(rune_diagnostics_emit(&diag, &out, &sources));
73+
}
74+
75+
rune_diagnostics_free(&diag);
76+
77+
if (!ok) {
78+
goto EXIT;
79+
}
80+
81+
assert(rune_context_runtime(&context, &runtime));
82+
assert(rune_vm_setup(&vm, &runtime, &unit));
83+
84+
rune_hash entry = rune_hash_name("main");
85+
86+
if (!rune_vm_set_entrypoint(&vm, entry, 1, &error)) {
87+
assert(rune_vm_error_emit(&error, &out, &sources));
88+
goto EXIT;
89+
}
90+
91+
rune_stack_push_integer(rune_vm_stack_mut(&vm), 42);
92+
rune_value ret = rune_value_unit();
93+
94+
if (!rune_vm_complete(&vm, &ret, &error)) {
95+
assert(rune_vm_error_emit(&error, &out, &sources));
96+
}
97+
98+
int64_t output = 0;
99+
100+
if (rune_value_as_integer(&ret, &output)) {
101+
printf("output = %lld\n", output);
102+
} else {
103+
rune_hash type_hash = rune_hash_empty();
104+
105+
if (rune_value_type_hash(&ret, &type_hash, &error)) {
106+
printf("output = %lld\n", type_hash);
107+
} else {
108+
printf("output = ?\n");
109+
}
110+
}
111+
112+
rune_value_free(&ret);
113+
114+
EXIT:
115+
rune_context_free(&context);
116+
rune_module_free(&module);
117+
rune_runtime_context_free(&runtime);
118+
rune_sources_free(&sources);
119+
rune_standard_stream_free(&out);
120+
rune_unit_free(&unit);
121+
rune_vm_error_free(&error);
122+
rune_vm_free(&vm);
123+
return 0;
124+
}

capi/examples/minimal.c

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
4+
#include <rune.h>
5+
6+
int main() {
7+
rune_context context = rune_context_new();
8+
rune_runtime_context runtime = rune_runtime_context_new();
9+
rune_sources sources = rune_sources_new();
10+
rune_standard_stream out = rune_standard_stream_stderr(RUNE_COLOR_CHOICE_ALWAYS);
11+
rune_unit unit = rune_unit_new();
12+
rune_vm vm = rune_vm_new();
13+
rune_vm_error error = rune_vm_error_new();
14+
15+
rune_source source = rune_source_new("<in>", "pub fn add_one(n) { n / 3 }");
16+
assert(rune_sources_insert(&sources, &source));
17+
rune_source_free(&source);
18+
19+
rune_diagnostics diag = rune_diagnostics_new();
20+
21+
rune_build build = rune_build_prepare(&sources);
22+
rune_build_with_diagnostics(&build, &diag);
23+
24+
bool ok = rune_build_build(&build, &unit);
25+
26+
if (!rune_diagnostics_is_empty(&diag)) {
27+
assert(rune_diagnostics_emit(&diag, &out, &sources));
28+
}
29+
30+
rune_diagnostics_free(&diag);
31+
32+
if (!ok) {
33+
goto EXIT;
34+
}
35+
36+
assert(rune_context_runtime(&context, &runtime));
37+
assert(rune_vm_setup(&vm, &runtime, &unit));
38+
39+
rune_hash entry = rune_hash_name("add_one");
40+
41+
if (!rune_vm_set_entrypoint(&vm, entry, 1, &error)) {
42+
assert(rune_vm_error_emit(&error, &out, &sources));
43+
goto EXIT;
44+
}
45+
46+
rune_stack_push_integer(rune_vm_stack_mut(&vm), 42);
47+
rune_value ret = rune_value_unit();
48+
49+
if (!rune_vm_complete(&vm, &ret, &error)) {
50+
assert(rune_vm_error_emit(&error, &out, &sources));
51+
}
52+
53+
int64_t output = 0;
54+
55+
if (rune_value_as_integer(&ret, &output)) {
56+
printf("output = %lld\n", output);
57+
} else {
58+
printf("output = ?\n");
59+
}
60+
61+
rune_value_free(&ret);
62+
63+
EXIT:
64+
rune_context_free(&context);
65+
rune_runtime_context_free(&runtime);
66+
rune_sources_free(&sources);
67+
rune_standard_stream_free(&out);
68+
rune_unit_free(&unit);
69+
rune_vm_error_free(&error);
70+
rune_vm_free(&vm);
71+
return 0;
72+
}

capi/examples/type_hash.c

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include <assert.h>
2+
#include <stdio.h>
3+
4+
#include <rune.h>
5+
6+
int main() {
7+
rune_value a = rune_value_integer(42);
8+
rune_value b = rune_value_bool(false);
9+
rune_vm_error error = rune_vm_error_new();
10+
11+
assert(rune_value_type_hash_or_empty(&a) == RUNE_INTEGER_TYPE_HASH);
12+
assert(rune_value_type_hash_or_empty(&b) == RUNE_BOOL_TYPE_HASH);
13+
14+
rune_vm_error_free(&error);
15+
return 0;
16+
}

capi/meson.build

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
project('examples', 'c')
2+
3+
c = meson.get_compiler('c')
4+
5+
inc = include_directories('.')
6+
7+
dirs = [
8+
meson.current_source_dir() + '/../target/debug',
9+
meson.current_source_dir() + '/../target/release',
10+
]
11+
12+
extra = []
13+
14+
if host_machine.system() == 'windows'
15+
extra = [
16+
c.find_library('ws2_32', required: true),
17+
c.find_library('bcrypt', required: true),
18+
c.find_library('userenv', required: true),
19+
]
20+
endif
21+
22+
rune_dep = c.find_library('rune', dirs: dirs, required: false)
23+
24+
if not rune_dep.found()
25+
error('Could not find rune library, try: cargo build --package rune-capi')
26+
endif
27+
28+
deps = [rune_dep] + extra
29+
30+
executable('function', 'examples/function.c', dependencies: deps, include_directories: inc)
31+
executable('minimal', 'examples/minimal.c', dependencies: deps, include_directories: inc)
32+
executable('type_hash', 'examples/type_hash.c', dependencies: deps, include_directories: inc)

0 commit comments

Comments
 (0)