First pass at implementing foreign classes.

Most of the pieces are there:

- You can declare a foreign class.
- It will call your C function to provide an allocator function.
- Whenever a foreign object is created, it calls the allocator.
- Foreign methods can access the foreign bytes of an object.
- Most of the runtime checking is in place for things like subclassing
  foreign classes.

There is still some loose ends to tie up:

- Finalizers are not called.
- Some of the error-handling could be better.
- The GC doesn't track how much memory a marked foreign object uses.
This commit is contained in:
Bob Nystrom
2015-08-15 12:07:53 -07:00
parent 7a79b8fac6
commit 48bdbc7745
33 changed files with 720 additions and 119 deletions

87
test/api/foreign_class.c Normal file
View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <string.h>
#include "foreign_class.h"
static void counterAllocate(WrenVM* vm)
{
double* value = (double*)wrenAllocateForeign(vm, sizeof(double));
*value = 0;
}
static void counterIncrement(WrenVM* vm)
{
double* value = (double*)wrenGetArgumentForeign(vm, 0);
double increment = wrenGetArgumentDouble(vm, 1);
*value += increment;
}
static void counterValue(WrenVM* vm)
{
double value = *(double*)wrenGetArgumentForeign(vm, 0);
wrenReturnDouble(vm, value);
}
static void pointAllocate(WrenVM* vm)
{
double* coordinates = (double*)wrenAllocateForeign(vm, sizeof(double[3]));
// This gets called by both constructors, so sniff the argument count to see
// which one was invoked.
if (wrenGetArgumentCount(vm) == 1)
{
coordinates[0] = 0.0;
coordinates[1] = 0.0;
coordinates[2] = 0.0;
}
else
{
coordinates[0] = wrenGetArgumentDouble(vm, 1);
coordinates[1] = wrenGetArgumentDouble(vm, 2);
coordinates[2] = wrenGetArgumentDouble(vm, 3);
}
}
static void pointTranslate(WrenVM* vm)
{
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0);
coordinates[0] += wrenGetArgumentDouble(vm, 1);
coordinates[1] += wrenGetArgumentDouble(vm, 2);
coordinates[2] += wrenGetArgumentDouble(vm, 3);
}
static void pointToString(WrenVM* vm)
{
double* coordinates = (double*)wrenGetArgumentForeign(vm, 0);
char result[100];
sprintf(result, "(%g, %g, %g)",
coordinates[0], coordinates[1], coordinates[2]);
wrenReturnString(vm, result, (int)strlen(result));
}
WrenForeignMethodFn foreignClassBindMethod(const char* signature)
{
if (strcmp(signature, "Counter.increment(_)") == 0) return counterIncrement;
if (strcmp(signature, "Counter.value") == 0) return counterValue;
if (strcmp(signature, "Point.translate(_,_,_)") == 0) return pointTranslate;
if (strcmp(signature, "Point.toString") == 0) return pointToString;
return NULL;
}
void foreignClassBindClass(
const char* className, WrenForeignClassMethods* methods)
{
if (strcmp(className, "Counter") == 0)
{
methods->allocate = counterAllocate;
return;
}
if (strcmp(className, "Point") == 0)
{
methods->allocate = pointAllocate;
return;
}
}

5
test/api/foreign_class.h Normal file
View File

@ -0,0 +1,5 @@
#include "wren.h"
WrenForeignMethodFn foreignClassBindMethod(const char* signature);
void foreignClassBindClass(
const char* className, WrenForeignClassMethods* methods);

View File

@ -0,0 +1,48 @@
// Class with a default constructor.
foreign class Counter {
foreign increment(amount)
foreign value
}
var counter = Counter.new()
IO.print(counter.value) // expect: 0
counter.increment(3.1)
IO.print(counter.value) // expect: 3.1
counter.increment(1.2)
IO.print(counter.value) // expect: 4.3
// Foreign classes can inherit a class as long as it has no fields.
class PointBase {
inherited() {
IO.print("inherited method")
}
}
// Class with non-default constructor.
foreign class Point is PointBase {
construct new() {
IO.print("default")
}
construct new(x, y, z) {
IO.print(x, ", ", y, ", ", z)
}
foreign translate(x, y, z)
foreign toString
}
var p = Point.new(1, 2, 3) // expect: 1, 2, 3
IO.print(p) // expect: (1, 2, 3)
p.translate(3, 4, 5)
IO.print(p) // expect: (4, 6, 8)
p = Point.new() // expect: default
IO.print(p) // expect: (0, 0, 0)
p.inherited() // expect: inherited method
var error = Fiber.new {
class Subclass is Point {}
}.try()
IO.print(error) // expect: Class 'Subclass' cannot inherit from foreign class 'Point'.

View File

@ -5,16 +5,23 @@
#include "vm.h"
#include "wren.h"
#include "value.h"
#include "foreign_class.h"
#include "returns.h"
#include "value.h"
#define REGISTER_TEST(name, camelCase) \
if (strcmp(testName, #name) == 0) return camelCase##BindForeign(fullName)
#define REGISTER_METHOD(name, camelCase) \
if (strcmp(testName, #name) == 0) return camelCase##BindMethod(fullName)
#define REGISTER_CLASS(name, camelCase) \
if (strcmp(testName, #name) == 0) \
{ \
camelCase##BindClass(className, &methods); \
}
// The name of the currently executing API test.
const char* testName;
static WrenForeignMethodFn bindForeign(
static WrenForeignMethodFn bindForeignMethod(
WrenVM* vm, const char* module, const char* className,
bool isStatic, const char* signature)
{
@ -29,8 +36,9 @@ static WrenForeignMethodFn bindForeign(
strcat(fullName, ".");
strcat(fullName, signature);
REGISTER_TEST(returns, returns);
REGISTER_TEST(value, value);
REGISTER_METHOD(foreign_class, foreignClass);
REGISTER_METHOD(returns, returns);
REGISTER_METHOD(value, value);
fprintf(stderr,
"Unknown foreign method '%s' for test '%s'\n", fullName, testName);
@ -38,6 +46,18 @@ static WrenForeignMethodFn bindForeign(
return NULL;
}
static WrenForeignClassMethods bindForeignClass(
WrenVM* vm, const char* module, const char* className)
{
WrenForeignClassMethods methods = { NULL, NULL };
if (strcmp(module, "main") != 0) return methods;
REGISTER_CLASS(foreign_class, foreignClass);
return methods;
}
int main(int argc, const char* argv[])
{
if (argc != 2)
@ -54,6 +74,7 @@ int main(int argc, const char* argv[])
strcat(testPath, testName);
strcat(testPath, ".wren");
runFile(bindForeign, testPath);
setForeignCallbacks(bindForeignMethod, bindForeignClass);
runFile(testPath);
return 0;
}

View File

@ -27,7 +27,7 @@ static void returnFalse(WrenVM* vm)
wrenReturnBool(vm, false);
}
WrenForeignMethodFn returnsBindForeign(const char* signature)
WrenForeignMethodFn returnsBindMethod(const char* signature)
{
if (strcmp(signature, "static Api.implicitNull") == 0) return implicitNull;
if (strcmp(signature, "static Api.returnInt") == 0) return returnInt;

View File

@ -1,3 +1,3 @@
#include "wren.h"
WrenForeignMethodFn returnsBindForeign(const char* signature);
WrenForeignMethodFn returnsBindMethod(const char* signature);

View File

@ -15,7 +15,7 @@ static void getValue(WrenVM* vm)
wrenReleaseValue(vm, value);
}
WrenForeignMethodFn valueBindForeign(const char* signature)
WrenForeignMethodFn valueBindMethod(const char* signature)
{
if (strcmp(signature, "static Api.value=(_)") == 0) return setValue;
if (strcmp(signature, "static Api.value") == 0) return getValue;

View File

@ -1,3 +1,3 @@
#include "wren.h"
WrenForeignMethodFn valueBindForeign(const char* signature);
WrenForeignMethodFn valueBindMethod(const char* signature);