mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-17 01:05:02 +00:00
Add roc code to ts-interop example
This commit is contained in:
parent
7ce4d3b22f
commit
ca900550a2
3 changed files with 292 additions and 6 deletions
|
@ -1,10 +1,13 @@
|
|||
## Running the example
|
||||
# TypeScript Interop
|
||||
|
||||
This is an example of calling Roc code from [TypeScript](https://www.typescriptlang.org/) on [Node.js](https://nodejs.org/en/).
|
||||
|
||||
## Installation
|
||||
|
||||
You'll need to have a C compiler installed, but most operating systems will have one already.
|
||||
(e.g. macOS has `clang` installed by default, Linux usually has GCC by default, etc.)
|
||||
All of these commands should be run from the same directory as this README file.
|
||||
|
||||
### Setup before first build
|
||||
|
||||
First, run this to install Node dependencies and generate the Makefile that will be
|
||||
used by future commands. (You should only need to run this once.)
|
||||
|
@ -14,7 +17,15 @@ npm install
|
|||
npx node-gyp configure
|
||||
```
|
||||
|
||||
### Build
|
||||
## Building the Roc library
|
||||
|
||||
First, `cd` into this directory and run this in your terminal:
|
||||
|
||||
```
|
||||
roc build --lib
|
||||
```
|
||||
|
||||
This compiles your Roc code into a binary library in the current directory. The library's filename will be libhello plus an OS-specific extension (e.g. libhello.dylib on macOS).
|
||||
|
||||
Next, run this to rebuild the C sources.
|
||||
|
||||
|
@ -34,10 +45,20 @@ You can verify that TypeScript sees the correct types with:
|
|||
npx tsc hello.ts
|
||||
```
|
||||
|
||||
### Run
|
||||
### Try it out!
|
||||
|
||||
Now you should be able to run the example with:
|
||||
Now that everything is built, you should be able to run the example with:
|
||||
|
||||
```
|
||||
npx ts-node hello.ts
|
||||
```
|
||||
```
|
||||
|
||||
To rebuild after changing either the `demo.`c file or any `.roc` files, run:
|
||||
|
||||
```
|
||||
roc build --lib && npx node-gyp build
|
||||
```
|
||||
|
||||
## About this example
|
||||
|
||||
This was created by following the [NodeJS addons](https://nodejs.org/dist/latest/docs/api/addons.html) tutorial and switching from C++ to C, then creating the `addon.d.ts` file to add types to the generated native Node module.
|
252
examples/typescript-interop/demo.c
Normal file
252
examples/typescript-interop/demo.c
Normal file
|
@ -0,0 +1,252 @@
|
|||
#include <errno.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <node_api.h>
|
||||
|
||||
void *roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
|
||||
|
||||
void *roc_realloc(void *ptr, size_t new_size, size_t old_size,
|
||||
unsigned int alignment)
|
||||
{
|
||||
return realloc(ptr, new_size);
|
||||
}
|
||||
|
||||
void roc_dealloc(void *ptr, unsigned int alignment) { free(ptr); }
|
||||
|
||||
__attribute__((noreturn)) void roc_panic(void *ptr, unsigned int alignment)
|
||||
{
|
||||
rb_raise(rb_eException, "%s", (char *)ptr);
|
||||
}
|
||||
|
||||
void *roc_memcpy(void *dest, const void *src, size_t n)
|
||||
{
|
||||
return memcpy(dest, src, n);
|
||||
}
|
||||
|
||||
void *roc_memset(void *str, int c, size_t n) { return memset(str, c, n); }
|
||||
|
||||
// Reference counting
|
||||
|
||||
// If the refcount is set to this, that means the allocation is
|
||||
// stored in readonly memory in the binary, and we must not
|
||||
// attempt to increment or decrement it; if we do, we'll segfault!
|
||||
const ssize_t REFCOUNT_READONLY = 0;
|
||||
const ssize_t REFCOUNT_ONE = (ssize_t)PTRDIFF_MIN;
|
||||
const size_t MASK = (size_t)PTRDIFF_MIN;
|
||||
|
||||
// Increment reference count, given a pointer to the first element in a collection.
|
||||
// We don't need to check for overflow because in order to overflow a usize worth of refcounts,
|
||||
// you'd need to somehow have more pointers in memory than the OS's virtual address space can hold.
|
||||
void incref(uint8_t* bytes, uint32_t alignment)
|
||||
{
|
||||
ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1;
|
||||
ssize_t refcount = *refcount_ptr;
|
||||
|
||||
if (refcount != REFCOUNT_READONLY) {
|
||||
*refcount_ptr = refcount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement reference count, given a pointer to the first element in a collection.
|
||||
// Then call roc_dealloc if nothing is referencing this collection anymore.
|
||||
void decref(uint8_t* bytes, uint32_t alignment)
|
||||
{
|
||||
if (bytes == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t extra_bytes = (sizeof(size_t) >= (size_t)alignment) ? sizeof(size_t) : (size_t)alignment;
|
||||
ssize_t *refcount_ptr = ((ssize_t *)bytes) - 1;
|
||||
ssize_t refcount = *refcount_ptr;
|
||||
|
||||
if (refcount != REFCOUNT_READONLY) {
|
||||
*refcount_ptr = refcount - 1;
|
||||
|
||||
if (refcount == REFCOUNT_ONE) {
|
||||
void *original_allocation = (void *)(refcount_ptr - (extra_bytes - sizeof(size_t)));
|
||||
|
||||
roc_dealloc(original_allocation, alignment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RocBytes (List U8)
|
||||
|
||||
struct RocBytes
|
||||
{
|
||||
uint8_t *bytes;
|
||||
size_t len;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct RocBytes init_rocbytes(uint8_t *bytes, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
{
|
||||
struct RocBytes ret = {
|
||||
.len = 0,
|
||||
.bytes = NULL,
|
||||
.capacity = MASK,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
struct RocBytes ret;
|
||||
size_t refcount_size = sizeof(size_t);
|
||||
uint8_t *new_content = (uint8_t *)roc_alloc(len + refcount_size, alignof(size_t)) + refcount_size;
|
||||
|
||||
memcpy(new_content, bytes, len);
|
||||
|
||||
ret.bytes = new_content;
|
||||
ret.len = len;
|
||||
ret.capacity = len;
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
// RocStr
|
||||
|
||||
struct RocStr
|
||||
{
|
||||
uint8_t *bytes;
|
||||
size_t len;
|
||||
size_t capacity;
|
||||
};
|
||||
|
||||
struct RocStr init_rocstr(uint8_t *bytes, size_t len)
|
||||
{
|
||||
if (len == 0)
|
||||
{
|
||||
struct RocStr ret = {
|
||||
.len = 0,
|
||||
.bytes = NULL,
|
||||
.capacity = MASK,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
else if (len < sizeof(struct RocStr))
|
||||
{
|
||||
// Start out with zeroed memory, so that
|
||||
// if we end up comparing two small RocStr values
|
||||
// for equality, we won't risk memory garbage resulting
|
||||
// in two equal strings appearing unequal.
|
||||
struct RocStr ret = {
|
||||
.len = 0,
|
||||
.bytes = NULL,
|
||||
.capacity = MASK,
|
||||
};
|
||||
|
||||
// Copy the bytes into the stack allocation
|
||||
memcpy(&ret, bytes, len);
|
||||
|
||||
// Record the string's length in the last byte of the stack allocation
|
||||
((uint8_t *)&ret)[sizeof(struct RocStr) - 1] = (uint8_t)len | 0b10000000;
|
||||
|
||||
return ret;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A large RocStr is the same as a List U8 (aka RocBytes) in memory.
|
||||
struct RocBytes roc_bytes = init_rocbytes(bytes, len);
|
||||
|
||||
struct RocStr ret = {
|
||||
.len = roc_bytes.len,
|
||||
.bytes = roc_bytes.bytes,
|
||||
.capacity = roc_bytes.capacity,
|
||||
};
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_small_str(struct RocStr str) { return ((ssize_t)str.capacity) < 0; }
|
||||
|
||||
// Determine the length of the string, taking into
|
||||
// account the small string optimization
|
||||
size_t roc_str_len(struct RocStr str)
|
||||
{
|
||||
uint8_t *bytes = (uint8_t *)&str;
|
||||
uint8_t last_byte = bytes[sizeof(str) - 1];
|
||||
uint8_t last_byte_xored = last_byte ^ 0b10000000;
|
||||
size_t small_len = (size_t)(last_byte_xored);
|
||||
size_t big_len = str.len;
|
||||
|
||||
// Avoid branch misprediction costs by always
|
||||
// determining both small_len and big_len,
|
||||
// so this compiles to a cmov instruction.
|
||||
if (is_small_str(str))
|
||||
{
|
||||
return small_len;
|
||||
}
|
||||
else
|
||||
{
|
||||
return big_len;
|
||||
}
|
||||
}
|
||||
|
||||
extern void roc__mainForHost_1_exposed_generic(struct RocBytes *ret, struct RocBytes *arg);
|
||||
|
||||
// Receive a value from Ruby, JSON serialized it and pass it to Roc as a List U8
|
||||
// (at which point the Roc platform will decode it and crash if it's invalid,
|
||||
// which roc_panic will translate into a Ruby exception), then get some JSON back from Roc
|
||||
// - also as a List U8 - and have Ruby JSON.parse it into a plain Ruby value to return.
|
||||
VALUE call_roc(VALUE self, VALUE rb_arg)
|
||||
{
|
||||
// This must be required before the to_json method will exist on String.
|
||||
rb_require("json");
|
||||
|
||||
// Turn the given Ruby value into a JSON string.
|
||||
// TODO should we defensively encode it as UTF-8 first?
|
||||
VALUE json_arg = rb_funcall(rb_arg, rb_intern("to_json"), 0);
|
||||
|
||||
struct RocBytes arg = init_rocbytes((uint8_t *)RSTRING_PTR(json_arg), RSTRING_LEN(json_arg));
|
||||
struct RocBytes ret;
|
||||
|
||||
// Call the Roc function to populate `ret`'s bytes.
|
||||
roc__mainForHost_1_exposed_generic(&ret, &arg);
|
||||
|
||||
// Create a rb_utf8_str from the heap-allocated JSON bytes the Roc function returned.
|
||||
VALUE returned_json = rb_utf8_str_new((char *)ret.bytes, ret.len);
|
||||
|
||||
// Now that we've created our Ruby JSON string, we're no longer referencing the RocBytes.
|
||||
decref((void *)&ret, alignof(uint8_t *));
|
||||
|
||||
return rb_funcall(rb_define_module("JSON"), rb_intern("parse"), 1, returned_json);
|
||||
}
|
||||
|
||||
void Init_demo()
|
||||
{
|
||||
VALUE roc_app = rb_define_module("RocApp");
|
||||
rb_define_module_function(roc_app, "call_roc", &call_roc, 1);
|
||||
}
|
||||
|
||||
napi_value Method(napi_env env, napi_callback_info args) {
|
||||
napi_value greeting;
|
||||
napi_status status;
|
||||
|
||||
status = napi_create_string_utf8(env, "World!", NAPI_AUTO_LENGTH, &greeting);
|
||||
if (status != napi_ok) return NULL;
|
||||
return greeting;
|
||||
}
|
||||
|
||||
napi_value init(napi_env env, napi_value exports) {
|
||||
napi_status status;
|
||||
napi_value fn;
|
||||
|
||||
status = napi_create_function(env, NULL, 0, Method, NULL, &fn);
|
||||
if (status != napi_ok) return NULL;
|
||||
|
||||
status = napi_set_named_property(env, exports, "hello", fn);
|
||||
if (status != napi_ok) return NULL;
|
||||
return exports;
|
||||
}
|
||||
|
||||
NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
|
13
examples/typescript-interop/main.roc
Normal file
13
examples/typescript-interop/main.roc
Normal file
|
@ -0,0 +1,13 @@
|
|||
app "libhello"
|
||||
packages { pf: "platform/main.roc" }
|
||||
imports []
|
||||
provides [main] to pf
|
||||
|
||||
main : U64 -> Str
|
||||
main = \num ->
|
||||
if num == 0 then
|
||||
"I need a positive number here!"
|
||||
else
|
||||
str = Num.toStr num
|
||||
|
||||
"The number was \(str), OH YEAH!!! 🤘🤘"
|
Loading…
Add table
Add a link
Reference in a new issue