From 5991ad7fedf49058f318bfb0ae0627910cbbe988 Mon Sep 17 00:00:00 2001 From: John-John Tedro Date: Sat, 1 Jan 2022 20:07:34 +0100 Subject: [PATCH] Start work on a C API --- .gitignore | 1 + Cargo.toml | 1 + capi/Cargo.toml | 13 + capi/README.md | 32 + capi/cbindgen.toml | 34 + capi/examples/function.c | 124 +++ capi/examples/minimal.c | 72 ++ capi/examples/type_hash.c | 16 + capi/meson.build | 32 + capi/rune.h | 1313 ++++++++++++++++++++++++++ capi/src/bin/cbindgen.rs | 22 + capi/src/build.rs | 96 ++ capi/src/context.rs | 86 ++ capi/src/context_error.rs | 71 ++ capi/src/diagnostics.rs | 76 ++ capi/src/hash.rs | 44 + capi/src/lib.rs | 70 ++ capi/src/module.rs | 86 ++ capi/src/runtime_context.rs | 47 + capi/src/source.rs | 52 + capi/src/sources.rs | 55 ++ capi/src/stack.rs | 176 ++++ capi/src/standard_stream.rs | 74 ++ capi/src/types.rs | 129 +++ capi/src/unit.rs | 45 + capi/src/value.rs | 254 +++++ capi/src/vm.rs | 151 +++ capi/src/vm_error.rs | 119 +++ crates/rune/Cargo.toml | 1 + crates/rune/src/hash.rs | 2 +- crates/rune/src/runtime/call.rs | 4 +- crates/rune/src/runtime/tuple.rs | 8 + crates/rune/src/runtime/type_info.rs | 1 + crates/rune/src/runtime/vec.rs | 8 + crates/rune/src/runtime/vm.rs | 15 +- crates/rune/src/runtime/vm_call.rs | 10 +- crates/rune/src/runtime/vm_error.rs | 32 +- 37 files changed, 3359 insertions(+), 13 deletions(-) create mode 100644 capi/Cargo.toml create mode 100644 capi/README.md create mode 100644 capi/cbindgen.toml create mode 100644 capi/examples/function.c create mode 100644 capi/examples/minimal.c create mode 100644 capi/examples/type_hash.c create mode 100644 capi/meson.build create mode 100644 capi/rune.h create mode 100644 capi/src/bin/cbindgen.rs create mode 100644 capi/src/build.rs create mode 100644 capi/src/context.rs create mode 100644 capi/src/context_error.rs create mode 100644 capi/src/diagnostics.rs create mode 100644 capi/src/hash.rs create mode 100644 capi/src/lib.rs create mode 100644 capi/src/module.rs create mode 100644 capi/src/runtime_context.rs create mode 100644 capi/src/source.rs create mode 100644 capi/src/sources.rs create mode 100644 capi/src/stack.rs create mode 100644 capi/src/standard_stream.rs create mode 100644 capi/src/types.rs create mode 100644 capi/src/unit.rs create mode 100644 capi/src/value.rs create mode 100644 capi/src/vm.rs create mode 100644 capi/src/vm_error.rs diff --git a/.gitignore b/.gitignore index 2f0f0ab8e..ef680f691 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ /flamegraph.svg /perf.data /perf.data.* +/builddir diff --git a/Cargo.toml b/Cargo.toml index 02e921e58..44173c672 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/rune-macros", "crates/rune-modules", "crates/rune-wasm", + "capi", "tests", "examples", "benches", diff --git a/capi/Cargo.toml b/capi/Cargo.toml new file mode 100644 index 000000000..b6c4a26d2 --- /dev/null +++ b/capi/Cargo.toml @@ -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" diff --git a/capi/README.md b/capi/README.md new file mode 100644 index 000000000..f28e566d0 --- /dev/null +++ b/capi/README.md @@ -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 +``` diff --git a/capi/cbindgen.toml b/capi/cbindgen.toml new file mode 100644 index 000000000..6ffe27276 --- /dev/null +++ b/capi/cbindgen.toml @@ -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 diff --git a/capi/examples/function.c b/capi/examples/function.c new file mode 100644 index 000000000..47f3a3f5a --- /dev/null +++ b/capi/examples/function.c @@ -0,0 +1,124 @@ +#include +#include + +#include + +/** + * 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("", "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; +} diff --git a/capi/examples/minimal.c b/capi/examples/minimal.c new file mode 100644 index 000000000..3e520bfb8 --- /dev/null +++ b/capi/examples/minimal.c @@ -0,0 +1,72 @@ +#include +#include + +#include + +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("", "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; +} diff --git a/capi/examples/type_hash.c b/capi/examples/type_hash.c new file mode 100644 index 000000000..83c72474e --- /dev/null +++ b/capi/examples/type_hash.c @@ -0,0 +1,16 @@ +#include +#include + +#include + +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; +} diff --git a/capi/meson.build b/capi/meson.build new file mode 100644 index 000000000..7d48c65d1 --- /dev/null +++ b/capi/meson.build @@ -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) diff --git a/capi/rune.h b/capi/rune.h new file mode 100644 index 000000000..fce41c8bd --- /dev/null +++ b/capi/rune.h @@ -0,0 +1,1313 @@ +#ifndef RUNE_H +#define RUNE_H + +#include +#include +#include + +/** + * The color choice. + */ +enum rune_color_choice +#ifdef __cplusplus + : uintptr_t +#endif // __cplusplus + { + /** + * Try very hard to emit colors. This includes emitting ANSI colors on + * Windows if the console API is unavailable. + */ + RUNE_COLOR_CHOICE_ALWAYS = 1, + /** + * AlwaysAnsi is like Always, except it never tries to use anything other + * than emitting ANSI color codes. + */ + RUNE_COLOR_CHOICE_ALWAYS_ANSI = 2, + /** + * Try to use colors, but don't force the issue. If the console isn't + * available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for + * example, then don't use colors. + */ + RUNE_COLOR_CHOICE_AUTO = 3, + /** + * Never emit colors. + */ + RUNE_COLOR_CHOICE_NEVER = 4, +}; +#ifndef __cplusplus +typedef uintptr_t rune_color_choice; +#endif // __cplusplus + +/** + * A collection of sources. + */ +typedef struct { + uint8_t repr[24]; +} rune_sources; + +/** + * A context. + */ +typedef struct { + uint8_t repr[656]; +} rune_context; + +/** + * Build diagnostics. + */ +typedef struct { + uint8_t repr[32]; +} rune_diagnostics; + +/** + * Prepare a build. + */ +typedef struct { + rune_sources *sources; + rune_context *context; + rune_diagnostics *diagnostics; +} rune_build; + +/** + * A rune source file. + */ +typedef struct { + uint8_t repr[8]; +} rune_unit; + +/** + * A module with custom functions and the like. + */ +typedef struct { + uint8_t repr[408]; +} rune_module; + +/** + * An error that can be raised by a virtual machine. + * + * This must be declared with [rune_context_error_new] and must be freed with + * [rune_context_error_free]. + * + * \code{.c} + * int main() { + * rune_context_error error = rune_context_error_new(); + * + * // ... + * + * rune_context_error_free(&error); + * } + * \endcode + */ +typedef struct { + uint8_t repr[152]; +} rune_context_error; + +/** + * A runtime context. + */ +typedef struct { + uint8_t repr[8]; +} rune_runtime_context; + +/** + * A standard stream. + */ +typedef struct { + uint8_t repr[88]; +} rune_standard_stream; + +/** + * An opaque hash. + */ +typedef uint64_t rune_hash; + +/** + * A rune source file. + */ +typedef struct { + uint8_t repr[72]; +} rune_source; + +/** + * The stack of a virtual machine. + */ +typedef struct { + uint8_t repr[32]; +} rune_stack; + +/** + * A value in a virtual machine. + */ +typedef struct { + uint8_t repr[16]; +} rune_value; + +/** + * An error that can be raised by a virtual machine. + * + * This must be declared with [rune_vm_error_new] and must be freed with + * [rune_vm_error_free]. + * + * \code{.c} + * int main() { + * rune_vm_error error = rune_vm_error_new(); + * + * // ... + * + * rune_vm_error_free(&error); + * } + * \endcode + */ +typedef struct { + uint8_t repr[8]; +} rune_vm_error; + +/** + * The signature of a custom function. + * + * Where `stack` is the stack being interacted with and `count` are the number + * of arguments passed in. + */ +typedef void (*Function)(rune_stack *stack, uintptr_t count, rune_vm_error*); + +/** + * A virtual machine. + */ +typedef struct { + uint8_t repr[80]; +} rune_vm; + +typedef struct { + const void *inner; +} rune_static_type; + +/** + * The type hash of an integer. + */ +#define RUNE_INTEGER_TYPE_HASH 13490401188435821026ULL + +/** + * The type hash of a boolean. + */ +#define RUNE_BOOL_TYPE_HASH 13721341357821314905ULL + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +extern const rune_static_type RUNE_BOOL_TYPE; + +extern const rune_static_type RUNE_BYTES_TYPE; + +extern const rune_static_type RUNE_BYTE_TYPE; + +extern const rune_static_type RUNE_CHAR_TYPE; + +extern const rune_static_type RUNE_FLOAT_TYPE; + +extern const rune_static_type RUNE_FORMAT_TYPE; + +extern const rune_static_type RUNE_FUNCTION_TYPE; + +extern const rune_static_type RUNE_FUTURE_TYPE; + +extern const rune_static_type RUNE_GENERATOR_STATE_TYPE; + +extern const rune_static_type RUNE_GENERATOR_TYPE; + +extern const rune_static_type RUNE_INTEGER_TYPE; + +extern const rune_static_type RUNE_ITERATOR_TYPE; + +extern const rune_static_type RUNE_OBJECT_TYPE; + +extern const rune_static_type RUNE_OPTION_TYPE; + +extern const rune_static_type RUNE_RANGE_TYPE; + +extern const rune_static_type RUNE_RESULT_TYPE; + +extern const rune_static_type RUNE_STREAM_TYPE; + +extern const rune_static_type RUNE_STRING_TYPE; + +extern const rune_static_type RUNE_TUPLE_TYPE; + +extern const rune_static_type RUNE_TYPE; + +extern const rune_static_type RUNE_UNIT_TYPE; + +extern const rune_static_type RUNE_VEC_TYPE; + +/** + * Prepare a new build. + */ +rune_build rune_build_prepare(rune_sources *sources); + +/** + * Associate a context with the build. + * + * # Safety + * + * Must be called with a `build` argument that has been setup with + * [rune_build_prepare] and a `context` that has been allocated with + * [rune_context_new][crate::rune_context_new]. + */ +void rune_build_with_context(rune_build *build, rune_context *context); + +/** + * Associate diagnostics with the build. + * + * # Safety + * + * Must be called with a `build` argument that has been setup with + * [rune_build_prepare] and a `diagnostics` that has been allocated with + * [rune_diagnostics_new][crate::rune_diagnostics_new]. + */ +void rune_build_with_diagnostics(rune_build *build, rune_diagnostics *diagnostics); + +/** + * Perform a build. + * + * On a successful returns `true` and sets `unit` to the newly allocated unit. + * Any old unit present will be de-allocated. + * Otherwise the `unit` argument is left alone. + * + * # Safety + * + * Must be called with a `build` argument that has been setup with + * [rune_build_prepare] and a `unit` that has been allocated with + * [rune_unit_new][crate::rune_unit_new]. + */ +bool rune_build_build(rune_build *build, rune_unit *unit); + +/** + * Construct a new context. + */ +rune_context rune_context_new(void); + +/** + * Free a context. After it's been freed the context is no longer valid. + * + * # Safety + * + * Must be called with a context allocated through [rune_context_new]. + */ +void rune_context_free(rune_context *context); + +/** + * Install the given module into the current context. + * + * Returns `false` if either context or `module` is not present or if + * installation fails. + * + * # Safety + * + * The current `context` must have been allocated with [rune_context_new]. + */ +bool rune_context_install(rune_context *context, + const rune_module *module, + rune_context_error *error); + +/** + * Construct a runtime context from the current context. + * + * # Safety + * + * Function must be called with a `context` object allocated by + * [rune_context_new] and a valid `runtime` argument allocated with + * [rune_runtime_context_new][crate::rune_runtime_context_new]. + */ +bool rune_context_runtime(const rune_context *context, rune_runtime_context *runtime); + +/** + * Construct an empty [ContextError]. + */ +rune_context_error rune_context_error_new(void); + +/** + * Free the given context error. + * + * # Safety + * + * Must be called with an error that has been allocated with + * [rune_context_error_new]. + */ +void rune_context_error_free(rune_context_error *error); + +/** + * Emit diagnostics to the given stream if the error is set. If the error is + * not set nothing will be emitted. + * + * TODO: propagate I/O errors somehow. + * + * # Safety + * + * Must be called with an error that has been allocated with + * [rune_context_error_new]. + */ +bool rune_context_error_emit(const rune_context_error *error, rune_standard_stream *stream); + +/** + * Construct a new build diagnostics instance. + * + * Used with [rn_build_diagnostics][crate:rn_build_diagnostics]. + */ +rune_diagnostics rune_diagnostics_new(void); + +/** + * Free a build diagnostics instance. + * + * # Safety + * + * Function must be called with a diagnostics object allocated by + * [rune_diagnostics_new]. + */ +void rune_diagnostics_free(rune_diagnostics *diagnostics); + +/** + * Test if diagnostics is empty. Will do nothing if the diagnostics object is + * not present. + * + * # Safety + * + * Function must be called with a diagnostics object allocated by + * [rune_diagnostics_new]. + */ +bool rune_diagnostics_is_empty(const rune_diagnostics *diagnostics); + +/** + * Emit diagnostics to the given stream. + * + * TODO: propagate I/O errors somehow. + * + * # Safety + * + * Function must be called with a diagnostics object allocated by + * [rune_diagnostics_new] and a valid `stream` and `sources` argument. + */ +bool rune_diagnostics_emit(const rune_diagnostics *diagnostics, + rune_standard_stream *stream, + const rune_sources *sources); + +/** + * Construct the empty hash. + */ +rune_hash rune_hash_empty(void); + +/** + * Generate a hash corresponding to the given name. + * + * Returns an empty hash that can be tested with [rn_hash_is_empty]. + * + * # Safety + * + * Function must be called with a non-NULL `name` argument. + */ +rune_hash rune_hash_name(const char *name); + +/** + * Test if the hash is empty. + */ +bool rune_hash_is_empty(rune_hash hash); + +/** + * Construct a compile source. + * + * Returns an empty source if the name or the source is not valid UTF-8. + * + * # Safety + * + * Must be called a `name` and `source` argument that points to valid + * NULL-terminated UTF-8 strings. + */ +rune_source rune_source_new(const char *name, const char *source); + +/** + * Free a compile source. Does nothing if it has already been freed. + * + * # Safety + * + * Must be called with a `source` that has been allocation with + * [rune_source_new]. + */ +void rune_source_free(rune_source *source); + +/** + * Construct a new [rn_sources] object. + */ +rune_sources rune_sources_new(void); + +/** + * Insert a source to be compiled. Once inserted, they are part of sources + * collection and do not need to be freed. + * + * Returns `true` if the source was successfully inserted. Otherwise it means + * that the provided source was empty. + * + * # Safety + * + * Must be called with a `sources` collection allocated with + * [rune_sources_new]. + */ +bool rune_sources_insert(rune_sources *sources, rune_source *source); + +/** + * Free a sources collection. After it's been freed the collection is no longer + * valid. + * + * # Safety + * + * Must be called with a `sources` collection allocated with + * [rune_sources_new]. + */ +void rune_sources_free(rune_sources *sources); + +/** + * Construct a standard stream for stdout. + */ +rune_standard_stream rune_standard_stream_stdout(rune_color_choice color_choice); + +/** + * Construct a standard stream for stderr. + */ +rune_standard_stream rune_standard_stream_stderr(rune_color_choice color_choice); + +/** + * Free a standard stream. + * + * # Safety + * + * This must be called with a `standard_stream` that has been allocated with + * functions such as [rune_standard_stream_stdout] or + * [rune_standard_stream_stderr]. + */ +void rune_standard_stream_free(rune_standard_stream *standard_stream); + +/** + * Push a value onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push(rune_stack *stack, rune_value value); + +/** + * Push a unit value onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push_unit(rune_stack *stack); + +/** + * Push a value with the given type onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push_bool(rune_stack *stack, bool value); + +/** + * Push a value with the given type onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push_byte(rune_stack *stack, uint8_t value); + +/** + * Push a value with the given type onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push_integer(rune_stack *stack, int64_t value); + +/** + * Push a value with the given type onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push_float(rune_stack *stack, double value); + +/** + * Push a value with the given type onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +void rune_stack_push_type(rune_stack *stack, rune_hash value); + +/** + * Push a character onto the stack. This variation pushes characters. + * Characters are only valid within the ranges smaller than 0x10ffff and not + * within 0xD800 to 0xDFFF (inclusive). + * + * If the pushed value is *not* within a valid character range, this function + * returns `false` and nothing will be pushed onto the stack. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +bool rune_stack_push_char(rune_stack *stack, uint32_t value); + +/** + * Push a tuple with `count` elements onto the stack. The components of the + * tuple will be popped from the stack in the reverse order that they were + * pushed. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +bool rune_stack_push_tuple(rune_stack *stack, uintptr_t count, rune_vm_error *error); + +/** + * Push a vector with `count` elements onto the stack. The elements of the + * vector will be popped from the stack in the reverse order that they were + * pushed. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + */ +bool rune_stack_push_vec(rune_stack *stack, uintptr_t count, rune_vm_error *error); + +/** + * Pop an integer from the stack. + * + * Return a boolean indicating if a value was popped off the stack. The value + * is only populated if the popped value matched the given value. + * + * # Safety + * + * Must be called with a valid stack. Like one fetched from + * [rune_vm_stack_mut][crate:rune_vm_stack_mut]. The `value` must also have + * been allocated correctly. + */ +bool rune_stack_pop_value(rune_stack *stack, rune_value *value, rune_vm_error *error); + +/** + * Construct a new empty unit handle. + */ +rune_unit rune_unit_new(void); + +/** + * Free a unit. Calling this multiple times on the same handle is allowed. + * + * This is a reference counted object. If the reference counts goes to 0, the + * underlying object is freed. + * + * # Safety + * + * The `unit` argument must have been allocated with [rune_unit_new]. + */ +void rune_unit_free(rune_unit *unit); + +/** + * Clone the given unit and return a new handle. Cloning increases the + * reference count of the unit by one. + * + * # Safety + * + * The `unit` argument must have been allocated with [rune_unit_new]. + */ +rune_unit rune_unit_clone(const rune_unit *unit); + +/** + * Construct a new context. + */ +rune_module rune_module_new(void); + +/** + * Free the given module. + * + * # Safety + * + * The `module` argument must have been allocated with [rune_module_new]. + */ +void rune_module_free(rune_module *module); + +/** + * Register a toplevel function to the module. + * + * Returns `false` if the module is freed or the name is not valid UTF-8. + * + * # Safety + * + * The `module` argument must have been allocated with [rune_module_new] and + * `name` must be a NULL-terminated string. + */ +bool rune_module_function(rune_module *module, + const char *name, + Function function, + rune_context_error *error); + +/** + * Allocate an empty runtime context. + */ +rune_runtime_context rune_runtime_context_new(void); + +/** + * Free the given runtime context. + * + * This is a reference counted object. If the reference counts goes to 0, the + * underlying object is freed. + * + * # Safety + * + * Function must be called with a `runtime` argument that has been allocated by + * [rune_runtime_context_new]. + */ +void rune_runtime_context_free(rune_runtime_context *runtime); + +/** + * Clone the given runtime context and return a new reference. + * + * # Safety + * + * Function must be called with a `runtime` argument that has been allocated by + * [rune_runtime_context_new]. + */ +rune_runtime_context rune_runtime_context_clone(const rune_runtime_context *runtime); + +/** + * Construct a unit value. + * + * Even though not strictly necessary, it is good practice to always free your + * values with [rune_value_free]. + * + * \code{.c} + * int main() { + * rune_vm_value value = rune_value_unit(); + * + * // ... + * + * rune_value_free(&value); + * } + * \endcode + */ +rune_value rune_value_unit(void); + +/** + * Get the type hash of a value. Getting the type hash might error in case the + * value is no longer accessible. If this happens, the empty hash is returned + * and `error` is populated with the error that occured. + * + * # Safety + * + * The `value` argument must have been allocated with a function such as + * [rune_value_unit] and a valid `error`. + */ +bool rune_value_type_hash(const rune_value *value, rune_hash *output, rune_vm_error *error); + +/** + * Simplified accessor for the type hash of the value which returns an + * [rune_hash_empty][crate::rune_hash_empty] in case the type hash couldn't be + * accessed and ignores the error. + * + * # Safety + * + * The `value` argument must have been allocated with a function such as + * [rune_value_unit]`. + */ +rune_hash rune_value_type_hash_or_empty(const rune_value *value); + +/** + * Construct a value of the given type. + */ +rune_value rune_value_bool(bool value); + +/** + * Construct a value of the given type. + */ +rune_value rune_value_byte(uint8_t value); + +/** + * Construct a value of the given type. + */ +rune_value rune_value_integer(int64_t value); + +/** + * Construct a value of the given type. + */ +rune_value rune_value_float(double value); + +/** + * Construct a value of the given type. + */ +rune_value rune_value_type(rune_hash value); + +/** + * Construct a character value. + * + * Characters are only valid within the ranges smaller than 0x10ffff and not + * within 0xD800 to 0xDFFF (inclusive). + * + * If the pushed value is *not* within a valid character range, this function + * returns `false`. + * + * # Safety + * + * The caller must ensure that `output` is allocated using something like + * [rune_value_unit]. + */ +bool rune_value_char(uint32_t value, rune_value *output); + +/** + * Free the given value. + * + * Strictly speaking, values which are Copy do not need to be freed, but you + * should make a habit of freeing any value used anywhere. + * + * This function is a little bit smart and sets the value to `Value::Unit` in + * order to free it. This mitigates that subsequent calls to `rn_value_free` + * doubly frees any allocated data. + * + * # Safety + * + * The `value` argument must have been allocated with a function such as + * [rune_value_unit]. + */ +void rune_value_free(rune_value *value); + +/** + * Set the value to the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +void rune_value_set_bool(rune_value *value, bool input); + +/** + * Set the value to the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +void rune_value_set_byte(rune_value *value, uint8_t input); + +/** + * Set the value to the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +void rune_value_set_char(rune_value *value, uint32_t input); + +/** + * Set the value to the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +void rune_value_set_integer(rune_value *value, int64_t input); + +/** + * Set the value to the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +void rune_value_set_float(rune_value *value, double input); + +/** + * Set the value to the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +void rune_value_set_type(rune_value *value, rune_hash input); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_unit(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_bool(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_byte(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_char(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_integer(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_float(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_type(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_string(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_bytes(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_vec(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_tuple(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_object(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_range(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_future(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_stream(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_generator(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_generatorstate(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_option(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_result(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_unitstruct(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_tuplestruct(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_struct(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_variant(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_function(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_format(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_iterator(const rune_value *value); + +/** + * Test if the value is of the given type. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_is_any(const rune_value *value); + +/** + * Coerce value into the given type. If the coercion was successful + * returns `true` and consumed the value. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_as_bool(const rune_value *value, bool *output); + +/** + * Coerce value into the given type. If the coercion was successful + * returns `true` and consumed the value. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_as_byte(const rune_value *value, uint8_t *output); + +/** + * Coerce value into the given type. If the coercion was successful + * returns `true` and consumed the value. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_as_char(const rune_value *value, uint32_t *output); + +/** + * Coerce value into the given type. If the coercion was successful + * returns `true` and consumed the value. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_as_integer(const rune_value *value, int64_t *output); + +/** + * Coerce value into the given type. If the coercion was successful + * returns `true` and consumed the value. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_as_float(const rune_value *value, double *output); + +/** + * Coerce value into the given type. If the coercion was successful + * returns `true` and consumed the value. + * + * # Safety + * + * The `value` argument must have been allocated with a function such + * as [rune_value_unit]. + */ +bool rune_value_as_type(const rune_value *value, rune_hash *output); + +/** + * Allocate space for a virtual machine. + */ +rune_vm rune_vm_new(void); + +/** + * Set up new virtual machine and assign it to `vm`. + * + * This takes ownership of the passed in `unit` and `runtime`. If either the + * `runtime` or `unit` is not set this function will return `false`. + * + * # Safety + * + * Must be called with a `vm` that has been allocated with [rune_vm_new] and a + * valid `runtime` and `unit` argument. + */ +bool rune_vm_setup(rune_vm *vm, rune_runtime_context *runtime, rune_unit *unit); + +/** + * Run the virtual machine to completion. + * + * This will replace `value`, freeing any old value which is already in place. + * + * Returns `true` if the virtual machine was successfully run to completion. + * + * # Safety + * + * Must be called with a `vm` that has been allocated with [rune_vm_new] and a + * valid `value` and `error` argument. + */ +bool rune_vm_complete(rune_vm *vm, rune_value *value, rune_vm_error *error); + +/** + * Set the entrypoint to the given hash in the virtual machine. + * + * # Safety + * + * Must be called with a `vm` that has been allocated with [rune_vm_new] and a + * valid `error` argument. + */ +bool rune_vm_set_entrypoint(rune_vm *vm, rune_hash hash, uintptr_t args, rune_vm_error *error); + +/** + * Get the stack associated with the virtual machine. If `vm` is not set returns NULL. + * + * # Safety + * + * Must be called with a `vm` that has been allocated with [rune_vm_new]. + */ +rune_stack *rune_vm_stack_mut(rune_vm *vm); + +/** + * Free a virtual machine. + * + * # Safety + * + * Must be called with a `vm` that has been allocated with [rune_vm_new]. + */ +void rune_vm_free(rune_vm *vm); + +/** + * Construct an empty [VmError]. + */ +rune_vm_error rune_vm_error_new(void); + +/** + * Free the given virtual machine error. + * + * # Safety + * + * Must be called with an error that has been allocated with + * [rune_vm_error_new]. + */ +void rune_vm_error_free(rune_vm_error *error); + +/** + * Emit diagnostics to the given stream if the error is set. If the error is + * not set nothing will be emitted. + * + * TODO: propagate I/O errors somehow. + * + * # Safety + * + * Must be called with an error that has been allocated with + * [rune_vm_error_new]. + */ +bool rune_vm_error_emit(const rune_vm_error *error, + rune_standard_stream *stream, + const rune_sources *sources); + +/** + * Set the given error to report a bad argument count error where the `actual` + * number of arguments were provided instead of `expected`. + * + * This will replace any errors already reported. + * + * # Safety + * + * Must be called with an error that has been allocated with + * [rune_vm_error_new]. + */ +void rune_vm_error_bad_argument_count(rune_vm_error *error, uintptr_t actual, uintptr_t expected); + +/** + * Set the given error to report a bad argument at the given position, which + * did not have the `expected` type. + * + * This will replace any errors already reported. + * + * # Safety + * + * Must be called with an error that has been allocated with + * [rune_vm_error_new]. + */ +void rune_vm_error_bad_argument_at(rune_vm_error *error, + uintptr_t arg, + const rune_value *actual, + rune_static_type expected); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* RUNE_H */ diff --git a/capi/src/bin/cbindgen.rs b/capi/src/bin/cbindgen.rs new file mode 100644 index 000000000..6be85fbb3 --- /dev/null +++ b/capi/src/bin/cbindgen.rs @@ -0,0 +1,22 @@ +use std::env; +use std::error::Error; +use std::path::PathBuf; + +use cbindgen::Config; + +fn main() -> Result<(), Box> { + let crate_dir = + PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").ok_or("missing CARGO_MANIFEST_DIR")?); + let config = crate_dir.join("cbindgen.toml"); + let header = crate_dir.join("rune.h"); + + let config = Config::from_file(config)?; + + cbindgen::Builder::new() + .with_config(config) + .with_crate(crate_dir) + .generate()? + .write_to_file(header); + + Ok(()) +} diff --git a/capi/src/build.rs b/capi/src/build.rs new file mode 100644 index 000000000..835ab2d56 --- /dev/null +++ b/capi/src/build.rs @@ -0,0 +1,96 @@ +use std::ptr; +use std::sync::Arc; + +use crate::{ + Context, Diagnostics, InternalContext, InternalDiagnostics, InternalUnit, Sources, Unit, +}; + +/// Prepare a build. +#[repr(C)] +pub struct Build { + sources: *mut Sources, + context: Option>, + diagnostics: Option>, +} + +/// Prepare a new build. +#[no_mangle] +pub extern "C" fn rune_build_prepare(sources: *mut Sources) -> Build { + Build { + sources, + context: None, + diagnostics: None, + } +} + +/// Associate a context with the build. +/// +/// # Safety +/// +/// Must be called with a `build` argument that has been setup with +/// [rune_build_prepare] and a `context` that has been allocated with +/// [rune_context_new][crate::rune_context_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_build_with_context(mut build: *mut Build, context: *mut Context) { + (*build).context = ptr::NonNull::new(context); +} + +/// Associate diagnostics with the build. +/// +/// # Safety +/// +/// Must be called with a `build` argument that has been setup with +/// [rune_build_prepare] and a `diagnostics` that has been allocated with +/// [rune_diagnostics_new][crate::rune_diagnostics_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_build_with_diagnostics( + mut build: *mut Build, + diagnostics: *mut Diagnostics, +) { + (*build).diagnostics = ptr::NonNull::new(diagnostics); +} + +/// Perform a build. +/// +/// On a successful returns `true` and sets `unit` to the newly allocated unit. +/// Any old unit present will be de-allocated. +/// Otherwise the `unit` argument is left alone. +/// +/// # Safety +/// +/// Must be called with a `build` argument that has been setup with +/// [rune_build_prepare] and a `unit` that has been allocated with +/// [rune_unit_new][crate::rune_unit_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_build_build(build: *mut Build, unit: *mut Unit) -> bool { + let build = &mut *build; + let sources = &mut *(build.sources as *mut rune::Sources); + let b = rune::prepare(sources); + + let b = if let Some(context) = build + .context + .as_mut() + .and_then(|c| (*c.as_ptr().cast::()).as_mut()) + { + b.with_context(context) + } else { + b + }; + + let b = if let Some(diagnostics) = build + .diagnostics + .as_mut() + .and_then(|d| (*d.as_ptr().cast::()).as_mut()) + { + b.with_diagnostics(diagnostics) + } else { + b + }; + + if let Ok(out) = b.build() { + let _ = ptr::replace(unit as *mut InternalUnit, Some(Arc::new(out))); + true + } else { + false + } +} diff --git a/capi/src/context.rs b/capi/src/context.rs new file mode 100644 index 000000000..f146841f9 --- /dev/null +++ b/capi/src/context.rs @@ -0,0 +1,86 @@ +use std::mem; +use std::ptr; +use std::sync::Arc; + +use crate::{ + ContextError, InternalContextError, InternalModule, InternalRuntimeContext, Module, + RuntimeContext, +}; + +pub(crate) type InternalContext = Option; + +/// A context. +#[repr(C)] +pub struct Context { + repr: [u8; 656], +} + +test_size!(Context, InternalContext); + +/// Construct a new context. +#[no_mangle] +pub extern "C" fn rune_context_new() -> Context { + // Safety: this allocation is safe. + unsafe { mem::transmute(Some(rune::Context::new())) } +} + +/// Free a context. After it's been freed the context is no longer valid. +/// +/// # Safety +/// +/// Must be called with a context allocated through [rune_context_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_context_free(context: *mut Context) { + let _ = ptr::replace(context as *mut InternalContext, None); +} + +/// Install the given module into the current context. +/// +/// Returns `false` if either context or `module` is not present or if +/// installation fails. +/// +/// # Safety +/// +/// The current `context` must have been allocated with [rune_context_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_context_install( + context: *mut Context, + module: *const Module, + error: *mut ContextError, +) -> bool { + let context = &mut *(context as *mut InternalContext); + let module = &*(module as *const InternalModule); + + let (context, module) = match (context, module) { + (Some(context), Some(module)) => (context, module), + _ => return false, + }; + + if let Err(e) = context.install(module) { + let _ = ptr::replace(error as *mut InternalContextError, Some(e)); + false + } else { + true + } +} + +/// Construct a runtime context from the current context. +/// +/// # Safety +/// +/// Function must be called with a `context` object allocated by +/// [rune_context_new] and a valid `runtime` argument allocated with +/// [rune_runtime_context_new][crate::rune_runtime_context_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_context_runtime( + context: *const Context, + runtime: *mut RuntimeContext, +) -> bool { + if let Some(context) = &*(context as *const InternalContext) { + let new = Arc::new(context.runtime()); + let _ = ptr::replace(runtime as *mut InternalRuntimeContext, Some(new)); + true + } else { + false + } +} diff --git a/capi/src/context_error.rs b/capi/src/context_error.rs new file mode 100644 index 000000000..94b5776be --- /dev/null +++ b/capi/src/context_error.rs @@ -0,0 +1,71 @@ +use std::mem; +use std::ptr; + +use crate::{InternalStandardStream, StandardStream}; + +pub(crate) type InternalContextError = Option; + +/// An error that can be raised by a virtual machine. +/// +/// This must be declared with [rune_context_error_new] and must be freed with +/// [rune_context_error_free]. +/// +/// \code{.c} +/// int main() { +/// rune_context_error error = rune_context_error_new(); +/// +/// // ... +/// +/// rune_context_error_free(&error); +/// } +/// \endcode +#[repr(C)] +pub struct ContextError { + repr: [u8; 152], +} + +test_size!(ContextError, InternalContextError); + +/// Construct an empty [ContextError]. +#[no_mangle] +pub extern "C" fn rune_context_error_new() -> ContextError { + // Safety: this allocation is safe. + unsafe { mem::transmute(InternalContextError::None) } +} + +/// Free the given context error. +/// +/// # Safety +/// +/// Must be called with an error that has been allocated with +/// [rune_context_error_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_context_error_free(error: *mut ContextError) { + let _ = ptr::replace(error as *mut InternalContextError, None); +} + +/// Emit diagnostics to the given stream if the error is set. If the error is +/// not set nothing will be emitted. +/// +/// TODO: propagate I/O errors somehow. +/// +/// # Safety +/// +/// Must be called with an error that has been allocated with +/// [rune_context_error_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_context_error_emit( + error: *const ContextError, + stream: *mut StandardStream, +) -> bool { + use std::io::Write; + + let error = &*(error as *const InternalContextError); + let stream = &mut *(stream as *mut InternalStandardStream); + + if let (Some(error), Some(stream)) = (error, stream) { + writeln!(stream, "{}", error).is_ok() + } else { + false + } +} diff --git a/capi/src/diagnostics.rs b/capi/src/diagnostics.rs new file mode 100644 index 000000000..59533ecb5 --- /dev/null +++ b/capi/src/diagnostics.rs @@ -0,0 +1,76 @@ +use std::mem; +use std::ptr; + +use crate::{InternalSources, InternalStandardStream, Sources, StandardStream}; + +/// Build diagnostics internal. +pub(crate) type InternalDiagnostics = Option; + +/// Build diagnostics. +#[repr(C)] +pub struct Diagnostics { + repr: [u8; 32], +} + +test_size!(Diagnostics, InternalDiagnostics); + +/// Construct a new build diagnostics instance. +/// +/// Used with [rn_build_diagnostics][crate:rn_build_diagnostics]. +#[no_mangle] +pub extern "C" fn rune_diagnostics_new() -> Diagnostics { + // Safety: this allocation is safe. + unsafe { mem::transmute(Some(rune::Diagnostics::new())) } +} + +/// Free a build diagnostics instance. +/// +/// # Safety +/// +/// Function must be called with a diagnostics object allocated by +/// [rune_diagnostics_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_diagnostics_free(diagnostics: *mut Diagnostics) { + let _ = ptr::replace(diagnostics as *mut InternalDiagnostics, None); +} + +/// Test if diagnostics is empty. Will do nothing if the diagnostics object is +/// not present. +/// +/// # Safety +/// +/// Function must be called with a diagnostics object allocated by +/// [rune_diagnostics_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_diagnostics_is_empty(diagnostics: *const Diagnostics) -> bool { + if let Some(diagnostics) = &*(diagnostics as *mut InternalDiagnostics) { + diagnostics.is_empty() + } else { + true + } +} + +/// Emit diagnostics to the given stream. +/// +/// TODO: propagate I/O errors somehow. +/// +/// # Safety +/// +/// Function must be called with a diagnostics object allocated by +/// [rune_diagnostics_new] and a valid `stream` and `sources` argument. +#[no_mangle] +pub unsafe extern "C" fn rune_diagnostics_emit( + diagnostics: *const Diagnostics, + stream: *mut StandardStream, + sources: *const Sources, +) -> bool { + let stream = &mut *(stream as *mut InternalStandardStream); + let diagnostics = &*(diagnostics as *mut InternalDiagnostics); + + if let (Some(diagnostics), Some(stream)) = (diagnostics, stream) { + let sources = &*(sources as *const InternalSources); + diagnostics.emit(stream, sources).is_ok() + } else { + false + } +} diff --git a/capi/src/hash.rs b/capi/src/hash.rs new file mode 100644 index 000000000..49ecf5dc6 --- /dev/null +++ b/capi/src/hash.rs @@ -0,0 +1,44 @@ +use std::ffi::CStr; +use std::mem; +use std::os::raw::c_char; + +/// The type hash of an integer. +pub const RUNE_INTEGER_TYPE_HASH: Hash = 0xbb378867da3981e2; +/// The type hash of a boolean. +pub const RUNE_BOOL_TYPE_HASH: Hash = 0xbe6bff4422d0c759; + +/// An opaque hash. +pub type Hash = u64; + +test_size!(Hash, rune::Hash); + +/// Construct the empty hash. +#[no_mangle] +pub extern "C" fn rune_hash_empty() -> Hash { + unsafe { mem::transmute(rune::Hash::EMPTY) } +} + +/// Generate a hash corresponding to the given name. +/// +/// Returns an empty hash that can be tested with [rn_hash_is_empty]. +/// +/// # Safety +/// +/// Function must be called with a non-NULL `name` argument. +#[no_mangle] +pub unsafe extern "C" fn rune_hash_name(name: *const c_char) -> Hash { + let hash = if let Ok(string) = CStr::from_ptr(name).to_str() { + rune::Hash::type_hash(&[string]) + } else { + rune::Hash::EMPTY + }; + + mem::transmute(hash) +} + +/// Test if the hash is empty. +#[no_mangle] +pub extern "C" fn rune_hash_is_empty(hash: Hash) -> bool { + // Safety: Hash can inhabit all possible bit patterns. + unsafe { mem::transmute::<_, rune::Hash>(hash) == rune::Hash::EMPTY } +} diff --git a/capi/src/lib.rs b/capi/src/lib.rs new file mode 100644 index 000000000..5144bffac --- /dev/null +++ b/capi/src/lib.rs @@ -0,0 +1,70 @@ +//! FFI bindings for Rune. + +#![allow(non_camel_case_types)] + +macro_rules! test_size { + ($ty:ty, $rune_ty:ty) => { + // Compile-time assertion of the size of the value. + #[cfg(not(test))] + const _: () = assert!( + ::std::mem::size_of::<$ty>() == ::std::mem::size_of::<$rune_ty>(), + "rune value size must match" + ); + + #[test] + fn test_size() { + assert_eq!( + ::std::mem::size_of::<$ty>(), + ::std::mem::size_of::<$rune_ty>() + ); + } + }; +} + +mod types; +pub use self::types::*; + +mod build; +pub use self::build::*; + +mod context; +pub use self::context::*; + +mod context_error; +pub use self::context_error::*; + +mod diagnostics; +pub use self::diagnostics::*; + +mod hash; +pub use self::hash::*; + +mod source; +pub use self::source::*; + +mod sources; +pub use self::sources::*; + +mod standard_stream; +pub use self::standard_stream::*; + +mod stack; +pub use self::stack::*; + +mod unit; +pub use self::unit::*; + +mod module; +pub use self::module::*; + +mod runtime_context; +pub use self::runtime_context::*; + +mod value; +pub use self::value::*; + +mod vm; +pub use self::vm::*; + +mod vm_error; +pub use self::vm_error::*; diff --git a/capi/src/module.rs b/capi/src/module.rs new file mode 100644 index 000000000..0811141ef --- /dev/null +++ b/capi/src/module.rs @@ -0,0 +1,86 @@ +use std::ffi::CStr; +use std::mem; +use std::os::raw::c_char; +use std::ptr; + +use crate::{ContextError, InternalContextError, InternalVmError, Stack, VmError}; + +pub(crate) type InternalModule = Option; + +/// The signature of a custom function. +/// +/// Where `stack` is the stack being interacted with and `count` are the number +/// of arguments passed in. +pub type Function = extern "C" fn(stack: *mut Stack, count: usize, *mut VmError); + +/// A module with custom functions and the like. +#[repr(C)] +pub struct Module { + repr: [u8; 408], +} + +test_size!(Module, InternalModule); + +/// Construct a new context. +#[no_mangle] +pub extern "C" fn rune_module_new() -> Module { + // Safety: this allocation is safe. + unsafe { mem::transmute(Some(rune::Module::new())) } +} + +/// Free the given module. +/// +/// # Safety +/// +/// The `module` argument must have been allocated with [rune_module_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_module_free(module: *mut Module) { + let _ = ptr::replace(module as *mut InternalModule, None); +} + +/// Register a toplevel function to the module. +/// +/// Returns `false` if the module is freed or the name is not valid UTF-8. +/// +/// # Safety +/// +/// The `module` argument must have been allocated with [rune_module_new] and +/// `name` must be a NULL-terminated string. +#[no_mangle] +pub unsafe extern "C" fn rune_module_function( + module: *mut Module, + name: *const c_char, + function: Function, + error: *mut ContextError, +) -> bool { + let name = CStr::from_ptr(name); + + let module = match &mut *(module as *mut InternalModule) { + Some(module) => module, + None => return false, + }; + + let name = match name.to_str() { + Ok(name) => name, + Err(..) => return false, + }; + + let result = module.raw_fn(&[name], move |stack, count| { + let stack = stack as *mut _ as *mut Stack; + let mut error: VmError = mem::transmute(InternalVmError::None); + function(stack, count, &mut error); + + if let Some(error) = (&mut *(&mut error as *mut VmError).cast::()).take() { + return Err(error); + } + + Ok(()) + }); + + if let Err(e) = result { + let _ = ptr::replace(error as *mut InternalContextError, Some(e)); + return false; + } + + true +} diff --git a/capi/src/runtime_context.rs b/capi/src/runtime_context.rs new file mode 100644 index 000000000..46969f1fc --- /dev/null +++ b/capi/src/runtime_context.rs @@ -0,0 +1,47 @@ +use std::{mem, ptr, sync::Arc}; + +/// The internal runtime context. +pub(crate) type InternalRuntimeContext = Option>; + +/// A runtime context. +#[repr(C)] +pub struct RuntimeContext { + repr: [u8; 8], +} + +test_size!(RuntimeContext, InternalRuntimeContext); + +/// Allocate an empty runtime context. +#[no_mangle] +pub extern "C" fn rune_runtime_context_new() -> RuntimeContext { + // Safety: this allocation is safe. + unsafe { mem::transmute(InternalRuntimeContext::None) } +} + +/// Free the given runtime context. +/// +/// This is a reference counted object. If the reference counts goes to 0, the +/// underlying object is freed. +/// +/// # Safety +/// +/// Function must be called with a `runtime` argument that has been allocated by +/// [rune_runtime_context_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_runtime_context_free(runtime: *mut RuntimeContext) { + let _ = ptr::replace(runtime as *mut InternalRuntimeContext, None); +} + +/// Clone the given runtime context and return a new reference. +/// +/// # Safety +/// +/// Function must be called with a `runtime` argument that has been allocated by +/// [rune_runtime_context_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_runtime_context_clone( + runtime: *const RuntimeContext, +) -> RuntimeContext { + let runtime = &*(runtime as *const InternalRuntimeContext); + mem::transmute(runtime.clone()) +} diff --git a/capi/src/source.rs b/capi/src/source.rs new file mode 100644 index 000000000..a6bb35fb2 --- /dev/null +++ b/capi/src/source.rs @@ -0,0 +1,52 @@ +use std::ffi::CStr; +use std::os::raw::c_char; +use std::{mem, ptr}; + +/// A rune source file. +pub(crate) type InternalSource = Option; + +/// A rune source file. +#[repr(C)] +pub struct Source { + repr: [u8; 72], +} + +test_size!(Source, InternalSource); + +/// Construct a compile source. +/// +/// Returns an empty source if the name or the source is not valid UTF-8. +/// +/// # Safety +/// +/// Must be called a `name` and `source` argument that points to valid +/// NULL-terminated UTF-8 strings. +#[no_mangle] +pub unsafe extern "C" fn rune_source_new(name: *const c_char, source: *const c_char) -> Source { + let name = CStr::from_ptr(name); + let source = CStr::from_ptr(source); + + let name = match name.to_str() { + Ok(name) => name, + Err(..) => return mem::transmute(InternalSource::None), + }; + + let source = match source.to_str() { + Ok(source) => source, + Err(..) => return mem::transmute(InternalSource::None), + }; + + let source = rune::Source::new(name, source); + mem::transmute(Some(source)) +} + +/// Free a compile source. Does nothing if it has already been freed. +/// +/// # Safety +/// +/// Must be called with a `source` that has been allocation with +/// [rune_source_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_source_free(source: *mut Source) { + let _ = ptr::replace(source as *mut InternalSource, None); +} diff --git a/capi/src/sources.rs b/capi/src/sources.rs new file mode 100644 index 000000000..7cb6f22cd --- /dev/null +++ b/capi/src/sources.rs @@ -0,0 +1,55 @@ +use std::{mem, ptr}; + +use crate::{InternalSource, Source}; + +/// Internal sources repr. +pub(crate) type InternalSources = rune::Sources; + +/// A collection of sources. +#[repr(C)] +pub struct Sources { + repr: [u8; 24], +} + +test_size!(Sources, InternalSources); + +/// Construct a new [rn_sources] object. +#[no_mangle] +pub extern "C" fn rune_sources_new() -> Sources { + // Safety: this allocation is safe. + unsafe { mem::transmute(rune::Sources::new()) } +} + +/// Insert a source to be compiled. Once inserted, they are part of sources +/// collection and do not need to be freed. +/// +/// Returns `true` if the source was successfully inserted. Otherwise it means +/// that the provided source was empty. +/// +/// # Safety +/// +/// Must be called with a `sources` collection allocated with +/// [rune_sources_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_sources_insert(sources: *mut Sources, source: *mut Source) -> bool { + let sources = &mut *(sources as *mut InternalSources); + + if let Some(source) = ptr::replace(source as *mut InternalSource, None) { + sources.insert(source); + true + } else { + false + } +} + +/// Free a sources collection. After it's been freed the collection is no longer +/// valid. +/// +/// # Safety +/// +/// Must be called with a `sources` collection allocated with +/// [rune_sources_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_sources_free(sources: *mut Sources) { + ptr::drop_in_place(sources as *mut InternalSources); +} diff --git a/capi/src/stack.rs b/capi/src/stack.rs new file mode 100644 index 000000000..3a11c16fc --- /dev/null +++ b/capi/src/stack.rs @@ -0,0 +1,176 @@ +use std::{mem, ptr}; + +use crate::{Hash, InternalVmError, Value, VmError}; + +/// The internal representation of a stack. +pub(crate) type StackInternal = rune::runtime::Stack; + +/// The stack of a virtual machine. +#[repr(C)] +pub struct Stack { + repr: [u8; 32], +} + +test_size!(Stack, StackInternal); + +/// Push a value onto the stack. +/// +/// # Safety +/// +/// Must be called with a valid stack. Like one fetched from +/// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. +#[no_mangle] +pub unsafe extern "C" fn rune_stack_push(stack: *mut Stack, value: Value) { + let stack = &mut *(stack as *mut StackInternal); + stack.push(mem::transmute::<_, rune::Value>(value)); +} + +/// Push a unit value onto the stack. +/// +/// # Safety +/// +/// Must be called with a valid stack. Like one fetched from +/// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. +#[no_mangle] +pub unsafe extern "C" fn rune_stack_push_unit(stack: *mut Stack) { + let stack = &mut *(stack as *mut StackInternal); + stack.push(rune::Value::Unit); +} + +macro_rules! push { + ($name:ident, $variant:ident, $ty:ty) => { + push!($name, $variant, $ty, Into::into); + }; + + ($name:ident, $variant:ident, $ty:ty, $fn:path) => { + /// Push a value with the given type onto the stack. + /// + /// # Safety + /// + /// Must be called with a valid stack. Like one fetched from + /// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. + #[no_mangle] + pub unsafe extern "C" fn $name(stack: *mut Stack, value: $ty) { + let stack = &mut *(stack as *mut StackInternal); + stack.push(rune::Value::$variant($fn(value))); + } + }; +} + +push!(rune_stack_push_bool, Bool, bool); +push!(rune_stack_push_byte, Byte, u8); +push!(rune_stack_push_integer, Integer, i64); +push!(rune_stack_push_float, Float, f64); +push!(rune_stack_push_type, Type, Hash, mem::transmute); + +/// Push a character onto the stack. This variation pushes characters. +/// +/// Characters are only valid within the ranges smaller than 0x10ffff and not +/// within 0xD800 to 0xDFFF (inclusive). +/// +/// If the pushed value is *not* within a valid character range, this function +/// returns `false` and nothing will be pushed onto the stack. +/// +/// # Safety +/// +/// Must be called with a valid stack. Like one fetched from +/// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. +#[no_mangle] +pub unsafe extern "C" fn rune_stack_push_char(stack: *mut Stack, value: u32) -> bool { + let stack = &mut *(stack as *mut StackInternal); + + if let Ok(value) = char::try_from(value) { + stack.push(rune::Value::Char(value)); + true + } else { + false + } +} + +/// Push a tuple with `count` elements onto the stack. The components of the +/// tuple will be popped from the stack in the reverse order that they were +/// pushed. +/// +/// # Safety +/// +/// Must be called with a valid stack. Like one fetched from +/// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. +#[no_mangle] +pub unsafe extern "C" fn rune_stack_push_tuple( + stack: *mut Stack, + count: usize, + error: *mut VmError, +) -> bool { + let stack = &mut *(stack as *mut StackInternal); + + let it = match stack.drain(count) { + Ok(it) => it, + Err(e) => { + let _ = ptr::replace(error as *mut InternalVmError, Some(e.into())); + return false; + } + }; + + let tuple = rune::runtime::Tuple::from_iter(it); + stack.push(rune::Value::Tuple(rune::runtime::Shared::new(tuple))); + true +} + +/// Push a vector with `count` elements onto the stack. The elements of the +/// vector will be popped from the stack in the reverse order that they were +/// pushed. +/// +/// # Safety +/// +/// Must be called with a valid stack. Like one fetched from +/// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. +#[no_mangle] +pub unsafe extern "C" fn rune_stack_push_vec( + stack: *mut Stack, + count: usize, + error: *mut VmError, +) -> bool { + let stack = &mut *(stack as *mut StackInternal); + + let it = match stack.drain(count) { + Ok(it) => it, + Err(e) => { + let _ = ptr::replace(error as *mut InternalVmError, Some(e.into())); + return false; + } + }; + + let vec = rune::runtime::Vec::from_iter(it); + stack.push(rune::Value::Vec(rune::runtime::Shared::new(vec))); + true +} + +/// Pop an integer from the stack. +/// +/// Return a boolean indicating if a value was popped off the stack. The value +/// is only populated if the popped value matched the given value. +/// +/// # Safety +/// +/// Must be called with a valid stack. Like one fetched from +/// [rune_vm_stack_mut][crate:rune_vm_stack_mut]. The `value` must also have +/// been allocated correctly. +#[no_mangle] +pub unsafe extern "C" fn rune_stack_pop_value( + stack: *mut Stack, + value: *mut Value, + error: *mut VmError, +) -> bool { + let stack = &mut *(stack as *mut StackInternal); + + let v = match stack.pop() { + Ok(v) => v, + Err(e) => { + let _ = ptr::replace(error as *mut InternalVmError, Some(e.into())); + return false; + } + }; + + let _ = ptr::replace(value as *mut rune::Value, v); + true +} diff --git a/capi/src/standard_stream.rs b/capi/src/standard_stream.rs new file mode 100644 index 000000000..f2bbccde0 --- /dev/null +++ b/capi/src/standard_stream.rs @@ -0,0 +1,74 @@ +use std::mem; +use std::ptr; + +use rune::termcolor as t; + +/// Internal handle for output stream. +pub(crate) type InternalStandardStream = Option; + +/// A standard stream. +#[repr(C)] +pub struct StandardStream { + #[cfg(windows)] + repr: [u8; 88], + #[cfg(not(windows))] + repr: [u8; 56], +} + +test_size!(StandardStream, InternalStandardStream); + +/// The color choice. +#[repr(usize)] +pub enum ColorChoice { + /// Try very hard to emit colors. This includes emitting ANSI colors on + /// Windows if the console API is unavailable. + ALWAYS = 1, + /// AlwaysAnsi is like Always, except it never tries to use anything other + /// than emitting ANSI color codes. + ALWAYS_ANSI = 2, + /// Try to use colors, but don't force the issue. If the console isn't + /// available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for + /// example, then don't use colors. + AUTO = 3, + /// Never emit colors. + NEVER = 4, +} + +fn color_choice_convert(color_choice: ColorChoice) -> t::ColorChoice { + match color_choice { + ColorChoice::ALWAYS => t::ColorChoice::Always, + ColorChoice::ALWAYS_ANSI => t::ColorChoice::AlwaysAnsi, + ColorChoice::AUTO => t::ColorChoice::Auto, + _ => t::ColorChoice::Never, + } +} + +/// Construct a standard stream for stdout. +#[no_mangle] +pub extern "C" fn rune_standard_stream_stdout(color_choice: ColorChoice) -> StandardStream { + let color_choice = color_choice_convert(color_choice); + + // Safety: this allocation is safe. + unsafe { mem::transmute(Some(t::StandardStream::stdout(color_choice))) } +} + +/// Construct a standard stream for stderr. +#[no_mangle] +pub extern "C" fn rune_standard_stream_stderr(color_choice: ColorChoice) -> StandardStream { + let color_choice = color_choice_convert(color_choice); + + // Safety: this allocation is safe. + unsafe { mem::transmute(Some(t::StandardStream::stderr(color_choice))) } +} + +/// Free a standard stream. +/// +/// # Safety +/// +/// This must be called with a `standard_stream` that has been allocated with +/// functions such as [rune_standard_stream_stdout] or +/// [rune_standard_stream_stderr]. +#[no_mangle] +pub unsafe extern "C" fn rune_standard_stream_free(standard_stream: *mut StandardStream) { + let _ = ptr::replace(standard_stream as *mut InternalStandardStream, None); +} diff --git a/capi/src/types.rs b/capi/src/types.rs new file mode 100644 index 000000000..7af6e1512 --- /dev/null +++ b/capi/src/types.rs @@ -0,0 +1,129 @@ +use std::ffi::c_void; +#[repr(C)] +pub struct StaticType { + pub(crate) inner: *const c_void, +} + +unsafe impl Sync for StaticType {} + +macro_rules! decl { + ($name:ident, $type:ident, $doc:literal) => { + /// Handle for the integer type. + #[no_mangle] + #[doc = $doc] + pub static $name: StaticType = StaticType { + inner: rune::runtime::$type as *const _ as *const c_void, + }; + }; +} + +decl!( + RUNE_BOOL_TYPE, + BOOL_TYPE, + "The specialized type information for a bool type." +); +decl!( + RUNE_BYTES_TYPE, + BYTES_TYPE, + "The specialized type information for a bytes type." +); +decl!( + RUNE_BYTE_TYPE, + BYTE_TYPE, + "The specialized type information for a byte type." +); +decl!( + RUNE_CHAR_TYPE, + CHAR_TYPE, + "The specialized type information for a char type." +); +decl!( + RUNE_FLOAT_TYPE, + FLOAT_TYPE, + "The specialized type information for a float type." +); +decl!( + RUNE_FORMAT_TYPE, + FORMAT_TYPE, + "The specialized type information for a fmt spec types." +); +decl!( + RUNE_FUNCTION_TYPE, + FUNCTION_TYPE, + "The specialized type information for a function pointer type." +); +decl!( + RUNE_FUTURE_TYPE, + FUTURE_TYPE, + "The specialized type information for a future type." +); +decl!( + RUNE_GENERATOR_STATE_TYPE, + GENERATOR_STATE_TYPE, + "The specialized type information for a generator state type." +); +decl!( + RUNE_GENERATOR_TYPE, + GENERATOR_TYPE, + "The specialized type information for a generator type." +); +decl!( + RUNE_INTEGER_TYPE, + INTEGER_TYPE, + "The specialized type information for a integer type." +); +decl!( + RUNE_ITERATOR_TYPE, + ITERATOR_TYPE, + "The specialized type information for the iterator type." +); +decl!( + RUNE_OBJECT_TYPE, + OBJECT_TYPE, + "The specialized type information for an anonymous object type." +); +decl!( + RUNE_OPTION_TYPE, + OPTION_TYPE, + "The specialized type information for a option type." +); +decl!( + RUNE_RANGE_TYPE, + RANGE_TYPE, + "The specialized type information for the range type." +); +decl!( + RUNE_RESULT_TYPE, + RESULT_TYPE, + "The specialized type information for a result type." +); +decl!( + RUNE_STREAM_TYPE, + STREAM_TYPE, + "The specialized type information for the Stream type." +); +decl!( + RUNE_STRING_TYPE, + STRING_TYPE, + "The specialized type information for a string type." +); +decl!( + RUNE_TUPLE_TYPE, + TUPLE_TYPE, + "The specialized type information for an anonymous tuple type." +); +decl!( + RUNE_TYPE, + TYPE, + "The specialized type information for type objects." +); +decl!( + RUNE_UNIT_TYPE, + UNIT_TYPE, + "The specialized type information for a unit." +); +decl!( + RUNE_VEC_TYPE, + VEC_TYPE, + "The specialized type information for a vector type." +); diff --git a/capi/src/unit.rs b/capi/src/unit.rs new file mode 100644 index 000000000..e9eab05ab --- /dev/null +++ b/capi/src/unit.rs @@ -0,0 +1,45 @@ +use std::mem; +use std::ptr; +use std::sync::Arc; + +pub(crate) type InternalUnit = Option>; + +/// A rune source file. +#[repr(C)] +pub struct Unit { + repr: [u8; 8], +} + +test_size!(Unit, InternalUnit); + +/// Construct a new empty unit handle. +#[no_mangle] +pub extern "C" fn rune_unit_new() -> Unit { + // Safety: this allocation is safe. + unsafe { mem::transmute(InternalUnit::None) } +} + +/// Free a unit. Calling this multiple times on the same handle is allowed. +/// +/// This is a reference counted object. If the reference counts goes to 0, the +/// underlying object is freed. +/// +/// # Safety +/// +/// The `unit` argument must have been allocated with [rune_unit_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_unit_free(unit: *mut Unit) { + let _ = ptr::replace(unit as *mut InternalUnit, None); +} + +/// Clone the given unit and return a new handle. Cloning increases the +/// reference count of the unit by one. +/// +/// # Safety +/// +/// The `unit` argument must have been allocated with [rune_unit_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_unit_clone(unit: *const Unit) -> Unit { + let unit = &*(unit as *const InternalUnit); + mem::transmute(unit.clone()) +} diff --git a/capi/src/value.rs b/capi/src/value.rs new file mode 100644 index 000000000..a7ea02b19 --- /dev/null +++ b/capi/src/value.rs @@ -0,0 +1,254 @@ +#![allow(clippy::transmute_int_to_char)] + +use std::mem; +use std::ptr; + +use crate::{Hash, VmError}; + +/// A value in a virtual machine. +#[repr(C)] +pub struct Value { + repr: [u8; 16], +} + +test_size!(Value, rune::Value); + +/// Construct a unit value. +/// +/// Even though not strictly necessary, it is good practice to always free your +/// values with [rune_value_free]. +/// +/// \code{.c} +/// int main() { +/// rune_vm_value value = rune_value_unit(); +/// +/// // ... +/// +/// rune_value_free(&value); +/// } +/// \endcode +#[no_mangle] +pub extern "C" fn rune_value_unit() -> Value { + // Safety: this allocation is safe. + unsafe { mem::transmute(rune::Value::Unit) } +} + +/// Get the type hash of a value. Getting the type hash might error in case the +/// value is no longer accessible. If this happens, the empty hash is returned +/// and `error` is populated with the error that occured. +/// +/// # Safety +/// +/// The `value` argument must have been allocated with a function such as +/// [rune_value_unit] and a valid `error`. +#[no_mangle] +pub unsafe extern "C" fn rune_value_type_hash( + value: *const Value, + output: *mut Hash, + error: *mut VmError, +) -> bool { + let value = &*(value as *const rune::Value); + + let hash = match value.type_hash() { + Ok(hash) => hash, + Err(e) => { + if error.is_null() { + ptr::write(error, mem::transmute(e)); + } else { + let _ = ptr::replace(error, mem::transmute(e)); + } + + return false; + } + }; + + // Safety: the Hash type is Copy. + ptr::write(output as *mut rune::Hash, hash); + true +} + +/// Simplified accessor for the type hash of the value which returns an +/// [rune_hash_empty][crate::rune_hash_empty] in case the type hash couldn't be +/// accessed and ignores the error. +/// +/// # Safety +/// +/// The `value` argument must have been allocated with a function such as +/// [rune_value_unit]`. +#[no_mangle] +pub unsafe extern "C" fn rune_value_type_hash_or_empty(value: *const Value) -> Hash { + let value = &*(value as *const rune::Value); + let hash = value.type_hash().unwrap_or(rune::Hash::EMPTY); + mem::transmute(hash) +} + +macro_rules! rune_value_init { + ($name:ident, $variant:ident, $ty:ty) => { + rune_value_init!($name, $variant, $ty, Into::into); + }; + + ($name:ident, $variant:ident, $ty:ty, $fn:path) => { + /// Construct a value of the given type. + #[no_mangle] + pub extern "C" fn $name(value: $ty) -> Value { + // Safety: this allocation is safe. + unsafe { mem::transmute(rune::Value::$variant($fn(value))) } + } + }; +} + +rune_value_init!(rune_value_bool, Bool, bool); +rune_value_init!(rune_value_byte, Byte, u8); +rune_value_init!(rune_value_integer, Integer, i64); +rune_value_init!(rune_value_float, Float, f64); +rune_value_init!(rune_value_type, Type, Hash, mem::transmute); + +/// Construct a character value. +/// +/// Characters are only valid within the ranges smaller than 0x10ffff and not +/// within 0xD800 to 0xDFFF (inclusive). +/// +/// If the pushed value is *not* within a valid character range, this function +/// returns `false`. +/// +/// # Safety +/// +/// The caller must ensure that `output` is allocated using something like +/// [rune_value_unit]. +#[no_mangle] +pub unsafe extern "C" fn rune_value_char(value: u32, output: *mut Value) -> bool { + if let Ok(value) = char::try_from(value) { + let _ = ptr::replace(output as *mut rune::Value, rune::Value::Char(value)); + true + } else { + false + } +} + +/// Free the given value. +/// +/// Strictly speaking, values which are Copy do not need to be freed, but you +/// should make a habit of freeing any value used anywhere. +/// +/// This function is a little bit smart and sets the value to `Value::Unit` in +/// order to free it. This mitigates that subsequent calls to `rn_value_free` +/// doubly frees any allocated data. +/// +/// # Safety +/// +/// The `value` argument must have been allocated with a function such as +/// [rune_value_unit]. +#[no_mangle] +pub unsafe extern "C" fn rune_value_free(value: *mut Value) { + let _ = ptr::replace(value as *mut rune::Value, rune::Value::Unit); +} + +/// Generate rune_value_set_* function for a copy type. +macro_rules! rune_value_set { + ($name:ident, $variant:ident, $ty:ty) => { + rune_value_set!($name, $variant, $ty, Into::into); + }; + + ($name:ident, $variant:ident, $ty:ty, $fn:path) => { + /// Set the value to the given type. + /// + /// # Safety + /// + /// The `value` argument must have been allocated with a function such + /// as [rune_value_unit]. + #[no_mangle] + pub unsafe extern "C" fn $name(value: *mut Value, input: $ty) { + let _ = ptr::replace(value as *mut rune::Value, rune::Value::$variant($fn(input))); + } + }; +} + +rune_value_set!(rune_value_set_bool, Bool, bool); +rune_value_set!(rune_value_set_byte, Byte, u8); +rune_value_set!(rune_value_set_char, Char, u32, mem::transmute); +rune_value_set!(rune_value_set_integer, Integer, i64); +rune_value_set!(rune_value_set_float, Float, f64); +rune_value_set!(rune_value_set_type, Type, Hash, mem::transmute); + +/// Generate rune_value_is_* function for a copy type. +macro_rules! rune_value_is { + ($name:ident, $($pat:pat_param)|* $(|)?) => { + /// Test if the value is of the given type. + /// + /// # Safety + /// + /// The `value` argument must have been allocated with a function such + /// as [rune_value_unit]. + #[no_mangle] + pub unsafe extern "C" fn $name(value: *const Value) -> bool { + matches!(&*(value as *mut rune::Value), $($pat)|*) + } + }; +} + +rune_value_is!(rune_value_is_unit, rune::Value::Unit); +rune_value_is!(rune_value_is_bool, rune::Value::Bool(..)); +rune_value_is!(rune_value_is_byte, rune::Value::Byte(..)); +rune_value_is!(rune_value_is_char, rune::Value::Char(..)); +rune_value_is!(rune_value_is_integer, rune::Value::Integer(..)); +rune_value_is!(rune_value_is_float, rune::Value::Float(..)); +rune_value_is!(rune_value_is_type, rune::Value::Type(..)); +rune_value_is!( + rune_value_is_string, + rune::Value::StaticString(..) | rune::Value::String(..) +); +rune_value_is!(rune_value_is_bytes, rune::Value::Bytes(..)); +rune_value_is!(rune_value_is_vec, rune::Value::Vec(..)); +rune_value_is!(rune_value_is_tuple, rune::Value::Tuple(..)); +rune_value_is!(rune_value_is_object, rune::Value::Object(..)); +rune_value_is!(rune_value_is_range, rune::Value::Range(..)); +rune_value_is!(rune_value_is_future, rune::Value::Future(..)); +rune_value_is!(rune_value_is_stream, rune::Value::Stream(..)); +rune_value_is!(rune_value_is_generator, rune::Value::Generator(..)); +rune_value_is!( + rune_value_is_generatorstate, + rune::Value::GeneratorState(..) +); +rune_value_is!(rune_value_is_option, rune::Value::Option(..)); +rune_value_is!(rune_value_is_result, rune::Value::Result(..)); +rune_value_is!(rune_value_is_unitstruct, rune::Value::UnitStruct(..)); +rune_value_is!(rune_value_is_tuplestruct, rune::Value::TupleStruct(..)); +rune_value_is!(rune_value_is_struct, rune::Value::Struct(..)); +rune_value_is!(rune_value_is_variant, rune::Value::Variant(..)); +rune_value_is!(rune_value_is_function, rune::Value::Function(..)); +rune_value_is!(rune_value_is_format, rune::Value::Format(..)); +rune_value_is!(rune_value_is_iterator, rune::Value::Iterator(..)); +rune_value_is!(rune_value_is_any, rune::Value::Any(..)); + +/// Generate rune_value_as_* function for a copy type. +macro_rules! rune_value_as { + ($name:ident, $variant:ident, $ty:ident) => { + rune_value_as!($name, $variant, $ty, Into::into); + }; + + ($name:ident, $variant:ident, $ty:ident, $fn:path) => { + /// Coerce value into the given type. If the coercion was successful + /// returns `true` and consumed the value. + /// + /// # Safety + /// + /// The `value` argument must have been allocated with a function such + /// as [rune_value_unit]. + #[no_mangle] + pub unsafe extern "C" fn $name(value: *const Value, output: *mut $ty) -> bool { + if let rune::Value::$variant(v) = &*(value as *mut rune::Value) { + *output = $fn(*v); + true + } else { + false + } + } + }; +} + +rune_value_as!(rune_value_as_bool, Bool, bool); +rune_value_as!(rune_value_as_byte, Byte, u8); +rune_value_as!(rune_value_as_char, Char, char); +rune_value_as!(rune_value_as_integer, Integer, i64); +rune_value_as!(rune_value_as_float, Float, f64); +rune_value_as!(rune_value_as_type, Type, Hash, mem::transmute); diff --git a/capi/src/vm.rs b/capi/src/vm.rs new file mode 100644 index 000000000..f1652802c --- /dev/null +++ b/capi/src/vm.rs @@ -0,0 +1,151 @@ +use std::mem; +use std::ptr; + +use crate::{ + Hash, InternalRuntimeContext, InternalUnit, InternalVmError, RuntimeContext, Stack, Unit, + Value, VmError, +}; + +/// Internal virtual machine representation. +pub(crate) type VmInternal = Option; + +/// A virtual machine. +#[repr(C)] +pub struct Vm { + repr: [u8; 80], +} + +test_size!(Vm, VmInternal); + +/// Allocate space for a virtual machine. +#[no_mangle] +pub extern "C" fn rune_vm_new() -> Vm { + // Safety: this allocation is safe. + unsafe { mem::transmute(VmInternal::None) } +} + +/// Set up new virtual machine and assign it to `vm`. +/// +/// This takes ownership of the passed in `unit` and `runtime`. If either the +/// `runtime` or `unit` is not set this function will return `false`. +/// +/// # Safety +/// +/// Must be called with a `vm` that has been allocated with [rune_vm_new] and a +/// valid `runtime` and `unit` argument. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_setup( + vm: *mut Vm, + runtime: *mut RuntimeContext, + unit: *mut Unit, +) -> bool { + let runtime = ptr::replace(runtime as *mut InternalRuntimeContext, None); + let unit = ptr::replace(unit as *mut InternalUnit, None); + + let (runtime, unit) = match (runtime, unit) { + (Some(runtime), Some(unit)) => (runtime, unit), + _ => return false, + }; + + let _ = ptr::replace( + vm as *mut VmInternal, + VmInternal::Some(rune::Vm::new(runtime, unit)), + ); + + true +} + +/// Run the virtual machine to completion. +/// +/// This will replace `value`, freeing any old value which is already in place. +/// +/// Returns `true` if the virtual machine was successfully run to completion. +/// +/// # Safety +/// +/// Must be called with a `vm` that has been allocated with [rune_vm_new] and a +/// valid `value` and `error` argument. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_complete( + vm: *mut Vm, + value: *mut Value, + error: *mut VmError, +) -> bool { + let vm = match &mut *(vm as *mut VmInternal) { + Some(vm) => vm, + None => return false, + }; + + match vm.complete() { + Ok(v) => { + // Replace to ensure old value is freed. + let _ = ptr::replace(value as *mut rune::Value, v); + true + } + Err(e) => { + let _ = ptr::replace(error as *mut InternalVmError, Some(e)); + false + } + } +} + +/// Set the entrypoint to the given hash in the virtual machine. +/// +/// # Safety +/// +/// Must be called with a `vm` that has been allocated with [rune_vm_new] and a +/// valid `error` argument. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_set_entrypoint( + vm: *mut Vm, + hash: Hash, + args: usize, + error: *mut VmError, +) -> bool { + let vm = match &mut *(vm as *mut VmInternal) { + Some(vm) => vm, + None => { + let _ = ptr::replace( + error as *mut InternalVmError, + Some(rune::runtime::VmError::ffi_missing_vm()), + ); + return false; + } + }; + + let hash: rune::Hash = mem::transmute(hash); + + match vm.ffi_set_entrypoint(hash, args) { + Ok(()) => true, + Err(e) => { + // Replace the existing error. + let _ = ptr::replace(error as *mut InternalVmError, Some(e)); + false + } + } +} + +/// Get the stack associated with the virtual machine. If `vm` is not set returns NULL. +/// +/// # Safety +/// +/// Must be called with a `vm` that has been allocated with [rune_vm_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_stack_mut(vm: *mut Vm) -> *mut Stack { + let vm = match &mut *(vm as *mut VmInternal) { + Some(stack) => stack, + None => return ptr::null_mut(), + }; + + vm.stack_mut() as *mut _ as *mut Stack +} + +/// Free a virtual machine. +/// +/// # Safety +/// +/// Must be called with a `vm` that has been allocated with [rune_vm_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_free(vm: *mut Vm) { + let _ = ptr::replace(vm as *mut VmInternal, None); +} diff --git a/capi/src/vm_error.rs b/capi/src/vm_error.rs new file mode 100644 index 000000000..636965922 --- /dev/null +++ b/capi/src/vm_error.rs @@ -0,0 +1,119 @@ +use std::{mem, ptr}; + +use crate::{InternalSources, InternalStandardStream, Sources, StandardStream, StaticType, Value}; + +pub(crate) type InternalVmError = Option; + +/// An error that can be raised by a virtual machine. +/// +/// This must be declared with [rune_vm_error_new] and must be freed with +/// [rune_vm_error_free]. +/// +/// \code{.c} +/// int main() { +/// rune_vm_error error = rune_vm_error_new(); +/// +/// // ... +/// +/// rune_vm_error_free(&error); +/// } +/// \endcode +#[repr(C)] +pub struct VmError { + repr: [u8; 8], +} + +test_size!(VmError, InternalVmError); + +/// Construct an empty [VmError]. +#[no_mangle] +pub extern "C" fn rune_vm_error_new() -> VmError { + // Safety: this allocation is safe. + unsafe { mem::transmute(InternalVmError::None) } +} + +/// Free the given virtual machine error. +/// +/// # Safety +/// +/// Must be called with an error that has been allocated with +/// [rune_vm_error_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_error_free(error: *mut VmError) { + let _ = ptr::replace(error as *mut InternalVmError, None); +} + +/// Emit diagnostics to the given stream if the error is set. If the error is +/// not set nothing will be emitted. +/// +/// TODO: propagate I/O errors somehow. +/// +/// # Safety +/// +/// Must be called with an error that has been allocated with +/// [rune_vm_error_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_error_emit( + error: *const VmError, + stream: *mut StandardStream, + sources: *const Sources, +) -> bool { + let error = &*(error as *const InternalVmError); + let stream = &mut *(stream as *mut InternalStandardStream); + let sources = &*(sources as *const InternalSources); + + if let (Some(error), Some(stream)) = (error, stream) { + error.emit(stream, sources).is_ok() + } else { + false + } +} + +/// Set the given error to report a bad argument count error where the `actual` +/// number of arguments were provided instead of `expected`. +/// +/// This will replace any errors already reported. +/// +/// # Safety +/// +/// Must be called with an error that has been allocated with +/// [rune_vm_error_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_error_bad_argument_count( + error: *mut VmError, + actual: usize, + expected: usize, +) { + ptr::replace( + error as *mut InternalVmError, + Some(rune::runtime::VmError::bad_argument_count(actual, expected)), + ); +} + +/// Set the given error to report a bad argument at the given position, which +/// did not have the `expected` type. +/// +/// This will replace any errors already reported. +/// +/// # Safety +/// +/// Must be called with an error that has been allocated with +/// [rune_vm_error_new]. +#[no_mangle] +pub unsafe extern "C" fn rune_vm_error_bad_argument_at( + error: *mut VmError, + arg: usize, + actual: *const Value, + expected: StaticType, +) { + let actual = &*(actual as *const rune::Value); + let expected = &*(expected.inner as *const rune::runtime::StaticType); + let expected = rune::runtime::TypeInfo::StaticType(expected); + + let e = match rune::runtime::VmError::ffi_bad_argument_at(arg, actual, expected) { + Ok(e) => e, + Err(e) => e, + }; + + let _ = ptr::replace(error as *mut InternalVmError, Some(e)); +} diff --git a/crates/rune/Cargo.toml b/crates/rune/Cargo.toml index 2d7469bd1..806c09a42 100644 --- a/crates/rune/Cargo.toml +++ b/crates/rune/Cargo.toml @@ -19,6 +19,7 @@ default = ["emit"] emit = ["codespan-reporting"] bench = [] workspace = ["toml", "toml-spanned-value", "semver", "relative-path", "serde-hashkey"] +ffi = [] [dependencies] thiserror = "1.0.30" diff --git a/crates/rune/src/hash.rs b/crates/rune/src/hash.rs index b0a973da7..a33931618 100644 --- a/crates/rune/src/hash.rs +++ b/crates/rune/src/hash.rs @@ -23,7 +23,7 @@ pub struct Hash(u64); impl Hash { /// The empty hash. - pub(crate) const EMPTY: Self = Self(0); + pub const EMPTY: Self = Self(0); /// Construct a new raw hash. pub(crate) const fn new(hash: u64) -> Self { diff --git a/crates/rune/src/runtime/call.rs b/crates/rune/src/runtime/call.rs index df215f5f9..d183761c4 100644 --- a/crates/rune/src/runtime/call.rs +++ b/crates/rune/src/runtime/call.rs @@ -20,12 +20,12 @@ pub enum Call { impl Call { /// Perform the call with the given virtual machine. #[inline] - pub(crate) fn call_with_vm(self, vm: Vm) -> Result { + pub(crate) fn call_with_vm(self, mut vm: Vm) -> Result { Ok(match self { Call::Stream => Value::from(Stream::new(vm)), Call::Generator => Value::from(Generator::new(vm)), Call::Immediate => vm.complete()?, - Call::Async => Value::from(Future::new(vm.async_complete())), + Call::Async => Value::from(Future::new(async move { vm.async_complete().await })), }) } } diff --git a/crates/rune/src/runtime/tuple.rs b/crates/rune/src/runtime/tuple.rs index a17c03648..6d554368f 100644 --- a/crates/rune/src/runtime/tuple.rs +++ b/crates/rune/src/runtime/tuple.rs @@ -176,6 +176,14 @@ impl FromValue for Tuple { } } +impl FromIterator for Tuple { + fn from_iter>(iter: T) -> Self { + Self { + inner: iter.into_iter().collect::>(), + } + } +} + macro_rules! impl_tuple { () => (); diff --git a/crates/rune/src/runtime/type_info.rs b/crates/rune/src/runtime/type_info.rs index 35fe1bd10..f9a0d7030 100644 --- a/crates/rune/src/runtime/type_info.rs +++ b/crates/rune/src/runtime/type_info.rs @@ -5,6 +5,7 @@ use std::sync::Arc; /// Type information about a value, that can be printed for human consumption /// through its [Display][fmt::Display] implementation. #[derive(Debug, Clone)] +#[non_exhaustive] pub enum TypeInfo { /// The static type of a value. StaticType(&'static StaticType), diff --git a/crates/rune/src/runtime/vec.rs b/crates/rune/src/runtime/vec.rs index d2413b44d..de5ac1c6e 100644 --- a/crates/rune/src/runtime/vec.rs +++ b/crates/rune/src/runtime/vec.rs @@ -249,6 +249,14 @@ impl From> for Vec { } } +impl FromIterator for Vec { + fn from_iter>(iter: T) -> Self { + Self { + inner: iter.into_iter().collect::>(), + } + } +} + impl FromValue for Mut { fn from_value(value: Value) -> Result { Ok(value.into_vec()?.into_mut()?) diff --git a/crates/rune/src/runtime/vm.rs b/crates/rune/src/runtime/vm.rs index 71e1a5372..4de733b81 100644 --- a/crates/rune/src/runtime/vm.rs +++ b/crates/rune/src/runtime/vm.rs @@ -171,13 +171,13 @@ impl Vm { /// Run the given vm to completion. /// /// If any async instructions are encountered, this will error. - pub fn complete(self) -> Result { + pub fn complete(&mut self) -> Result { let mut execution = VmExecution::new(self); execution.complete() } /// Run the given vm to completion with support for async functions. - pub async fn async_complete(self) -> Result { + pub async fn async_complete(&mut self) -> Result { let mut execution = VmExecution::new(self); execution.async_complete().await } @@ -354,9 +354,15 @@ impl Vm { Ok(value) } + /// Exported for use with FFI. + #[cfg(feature = "ffi")] + pub fn ffi_set_entrypoint(&mut self, hash: Hash, count: usize) -> Result<(), VmError> { + self.set_entrypoint(hash, count) + } + /// Update the instruction pointer to match the function matching the given /// name and check that the number of argument matches. - fn set_entrypoint(&mut self, name: N, count: usize) -> Result<(), VmError> + pub(crate) fn set_entrypoint(&mut self, name: N, count: usize) -> Result<(), VmError> where N: IntoTypeHash, { @@ -1094,7 +1100,8 @@ impl Vm { let stack = self.stack.drain(args)?.collect::(); let mut vm = Self::with_stack(self.context.clone(), self.unit.clone(), stack); vm.ip = offset; - self.stack.push(Future::new(vm.async_complete())); + self.stack + .push(Future::new(async move { vm.async_complete().await })); Ok(()) } diff --git a/crates/rune/src/runtime/vm_call.rs b/crates/rune/src/runtime/vm_call.rs index f78881f48..c74840f0c 100644 --- a/crates/rune/src/runtime/vm_call.rs +++ b/crates/rune/src/runtime/vm_call.rs @@ -18,14 +18,16 @@ impl VmCall { where T: AsMut, { + let mut vm = self.vm; + let value = match self.call { - Call::Async => Value::from(Future::new(self.vm.async_complete())), + Call::Async => Value::from(Future::new(async move { vm.async_complete().await })), Call::Immediate => { - execution.push_vm(self.vm); + execution.push_vm(vm); return Ok(()); } - Call::Stream => Value::from(Stream::new(self.vm)), - Call::Generator => Value::from(Generator::new(self.vm)), + Call::Stream => Value::from(Stream::new(vm)), + Call::Generator => Value::from(Generator::new(vm)), }; let vm = execution.vm_mut(); diff --git a/crates/rune/src/runtime/vm_error.rs b/crates/rune/src/runtime/vm_error.rs index c450ee2a7..b7b958bba 100644 --- a/crates/rune/src/runtime/vm_error.rs +++ b/crates/rune/src/runtime/vm_error.rs @@ -27,7 +27,12 @@ impl VmError { }) } - /// Bad argument. + /// Construct a [VmError] that reports a bad argument count. + pub fn bad_argument_count(actual: usize, expected: usize) -> VmError { + Self::from(VmErrorKind::BadArgumentCount { actual, expected }) + } + + /// Construct a [VmError] that reports a bad argument. pub fn bad_argument(arg: usize, value: &Value) -> Result where T: TypeOf, @@ -39,7 +44,21 @@ impl VmError { })) } - /// Construct an expected error. + /// Construct a [VmError] that reports a bad argument at. + #[cfg(feature = "ffi")] + pub fn ffi_bad_argument_at( + arg: usize, + value: &Value, + expected: TypeInfo, + ) -> Result { + Ok(Self::from(VmErrorKind::BadArgumentAt { + arg, + expected, + actual: value.type_info()?, + })) + } + + /// Construct a [VmError] that reports a violated expectation. pub fn expected(actual: TypeInfo) -> Self where T: TypeOf, @@ -108,6 +127,12 @@ impl VmError { } } + /// Missing virtual machine. + #[cfg(feature = "ffi")] + pub fn ffi_missing_vm() -> VmError { + VmError::from(VmErrorKind::MissingVm) + } + /// Unsmuggles the vm error, returning Ok(Self) in case the error is /// critical and should be propagated unaltered. pub(crate) fn unpack_critical(self) -> Result { @@ -144,7 +169,10 @@ where /// The kind of error encountered. #[allow(missing_docs)] #[derive(Debug, Error)] +#[non_exhaustive] pub enum VmErrorKind { + #[error("missing virtual machine")] + MissingVm, /// A vm error that was propagated from somewhere else. /// /// In order to represent this, we need to preserve the instruction pointer