feat: add foreign class support with allocation, construct opcodes, and CLI callback wiring

Implement the initial infrastructure for foreign classes in the Wren VM, including new opcodes (FOREIGN_CONSTRUCT, FOREIGN_CLASS), a WrenBindForeignClassFn callback type in the public API, and updated CLI vm.c to store and forward foreign binding callbacks via setForeignCallbacks(). The compiler now tracks whether a class is foreign via a new isForeign flag in ClassCompiler, emits CODE_FOREIGN_CONSTRUCT instead of CODE_CONSTRUCT for foreign class constructors, and errors on field definitions inside foreign classes. The debug dump and opcode header gain entries for the new opcodes, and wrenBindSuperclass handles the -1 numFields sentinel for foreign classes to prevent field inheritance from superclasses.
This commit is contained in:
Bob Nystrom
2015-08-15 19:07:53 +00:00
parent 7551fa8c9b
commit 2dc209e585
33 changed files with 720 additions and 119 deletions
+87
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
View File
@@ -0,0 +1,5 @@
#include "wren.h"
WrenForeignMethodFn foreignClassBindMethod(const char* signature);
void foreignClassBindClass(
const char* className, WrenForeignClassMethods* methods);
+48
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'.
+28 -7
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;
}
+1 -1
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;
+1 -1
View File
@@ -1,3 +1,3 @@
#include "wren.h"
WrenForeignMethodFn returnsBindForeign(const char* signature);
WrenForeignMethodFn returnsBindMethod(const char* signature);
+1 -1
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;
+1 -1
View File
@@ -1,3 +1,3 @@
#include "wren.h"
WrenForeignMethodFn valueBindForeign(const char* signature);
WrenForeignMethodFn valueBindMethod(const char* signature);
@@ -0,0 +1,9 @@
foreign class Foo {
bar {
// Can't read a field.
IO.print(_bar) // expect error
// Or write one.
_bar = "value" // expect error
}
}
@@ -0,0 +1,7 @@
class Foo {
method() {
_field = "value"
}
}
foreign class Bar is Foo {} // expect runtime error: Foreign class 'Bar' may not inherit from a class with fields.
@@ -0,0 +1,2 @@
// Missing "class".
foreign blah A {} // expect error
@@ -1 +1 @@
class Subclass is Class {} // expect runtime error: Subclass cannot inherit from Class.
class Subclass is Class {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Class'.
@@ -5,4 +5,4 @@ var ClosureType
ClosureType = Fn.new { IO.print(a) }.type
}
class Subclass is ClosureType {} // expect runtime error: Subclass cannot inherit from Fn.
class Subclass is ClosureType {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Fn'.
@@ -1 +1 @@
class Subclass is Fiber {} // expect runtime error: Subclass cannot inherit from Fiber.
class Subclass is Fiber {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Fiber'.
@@ -1 +1 @@
class Subclass is Fn {} // expect runtime error: Subclass cannot inherit from Fn.
class Subclass is Fn {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Fn'.
@@ -1 +1 @@
class Subclass is List {} // expect runtime error: Subclass cannot inherit from List.
class Subclass is List {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'List'.
@@ -1 +1 @@
class Subclass is Map {} // expect runtime error: Subclass cannot inherit from Map.
class Subclass is Map {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Map'.
@@ -1 +1 @@
class Foo is 123 {} // expect runtime error: Must inherit from a class.
class Foo is 123 {} // expect runtime error: Class 'Foo' cannot inherit from a non-class object.
@@ -1 +1 @@
class Subclass is Range {} // expect runtime error: Subclass cannot inherit from Range.
class Subclass is Range {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'Range'.
@@ -1 +1 @@
class Subclass is String {} // expect runtime error: Subclass cannot inherit from String.
class Subclass is String {} // expect runtime error: Class 'Subclass' cannot inherit from built-in class 'String'.
@@ -22,5 +22,3 @@ bar.method(1, 2) // expect: bar
bar.method(1, 2, 3) // expect: foo
bar.method(1, 2, 3, 4) // expect: bar
bar.override // expect: bar
// TODO: Prevent extending built-in types.