Skip to content

Instantly share code, notes, and snippets.

@timruffles
Created July 13, 2018 07:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timruffles/088a2b9de5c4d7eea3abceebf310a91f to your computer and use it in GitHub Desktop.
Save timruffles/088a2b9de5c4d7eea3abceebf310a91f to your computer and use it in GitHub Desktop.
Representing JsValues for JS -> C compiler
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
/**
* So, plan is to store the values for the JS compiler
* in a union, nicking Lua's idea. That way immediate
* values like numbers live inside the JSValue wrapper,
* while the compound values lived elsewhere.
*/
typedef char* TypeTag;
typedef char BooleanValue;
static const char TRUE = 'Y';
static char OBJECT[] = "object";
static char BOOLEAN[] = "boolean";
static char NUMBER[] = "number";
typedef union {
void* object;
double number;
BooleanValue boolean;
uintptr_t raw;
} JsValueContent;
typedef struct {
// to discriminate the union
TypeTag type;
JsValueContent value;
} JsValue;
/**
* So I thought I could be clever and have a single
* valueGet function that'd return the N bytes of the
* union. Now - since then I've decided that's a silly
* idea and having the JsValue module expose typed getters
* makes more sense - but I'm interested in that it wasn't doable.
*
* My first though was to both pass in and return the union
* as a uintptr_t - as it was going to be as wide as a
* pointer.
*
* I noticed two things:
* - this makes for a LOT of ugly casting to make the compiler
* stop pointing out it's a bad idea
* - it's easy to break things if you do casts that will cause
* data conversion (especially double -> uintptr_t) rather than
* simply a view of a different type of the same data
*/
JsValue* jsValueCreate(TypeTag, uintptr_t);
JsValue* jsValueCreate(TypeTag tag, uintptr_t value) {
JsValue* val = calloc(sizeof(JsValue), 1);
*val = (JsValue) {
.type = tag,
};
// Although the union elements are of different sizes
// unions' elements are all aligned to address of union,
// so we can just read the max number of bytes for every
// argument.
//
// Argument that this is ok: char is well defined as a single
// byte, so having 7 bytes of garbage after is not a problem.
//
// Pointers and doubles both take 8 bytes, so we're never
// getting any garbage anyway.
//
// jsValueCreate(.., 'Y')
//
// jsValueCreate(.., 47.0)
//
// jsValueCreate(.., somePtr)
//
//memcpy(&val->value, &value, sizeof(JsValueContent));
val->value.raw = value;
return val;
}
uintptr_t jsValueGet(JsValue*);
uintptr_t jsValueGet(JsValue* value) {
return (uintptr_t)value->value.object;
}
int main() {
double pretendObject = 4242;
double doubleVal = 47;
JsValue* val1 = jsValueCreate(NUMBER, *(uintptr_t*)(void*)&doubleVal);
JsValue* val2 = jsValueCreate(BOOLEAN, TRUE);
JsValue* val3 = jsValueCreate(OBJECT, &pretendObject);
// copy out our double value into another double variable
uintptr_t r = jsValueGet(val1);
double dblRestored = *(double*)(void*)&r;
assert(dblRestored - doubleVal < 0.0000000001);
printf("val1 %f - \n", dblRestored);
// read out the char
char readChar = (char)jsValueGet(val2);
assert(readChar == TRUE);
printf("val2 %c\n", readChar);
// read out the pointer value
void* restoredPointer = jsValueGet(val3);
assert(restoredPointer == &pretendObject);
printf("val3 %p\n", restoredPointer);
}
clang -Weverything js-values-sketch.c -o demo && ./demo
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment