mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 14:18:42 +01:00
Add C API functions for working with lists:
- wrenEnsureSlots() Lets you go the foreign slot stack to make room for a temporary work area. - wrenSetSlotNewList() Creates a new empty list and stores it in a slot. - wrenInsertInList() Takes a value from one slot and inserts it into the list in another. Still need more functions like getting elements from a list, removing, etc. but this at least lets you create, populate, and return lists from foreign methods.
This commit is contained in:
@ -240,6 +240,14 @@ void* wrenAllocateForeign(WrenVM* vm, size_t size);
|
||||
// Returns the number of slots available to the current foreign method.
|
||||
int wrenGetSlotCount(WrenVM* vm);
|
||||
|
||||
// Ensures that the foreign method stack has at least [numSlots] available for
|
||||
// use, growing the stack if needed.
|
||||
//
|
||||
// Does not shrink the stack if it has more than enough slots.
|
||||
//
|
||||
// It is an error to call this from a finalizer.
|
||||
void wrenEnsureSlots(WrenVM* vm, int numSlots);
|
||||
|
||||
// TODO: Update docs.
|
||||
|
||||
// The following functions read one of the arguments passed to a foreign call.
|
||||
@ -327,6 +335,9 @@ void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, int length);
|
||||
// Stores the numeric [value] in [slot].
|
||||
void wrenSetSlotDouble(WrenVM* vm, int slot, double value);
|
||||
|
||||
// Stores a new empty list in [slot].
|
||||
void wrenSetSlotNewList(WrenVM* vm, int slot);
|
||||
|
||||
// Stores null in [slot].
|
||||
void wrenSetSlotNull(WrenVM* vm, int slot);
|
||||
|
||||
@ -343,4 +354,11 @@ void wrenSetSlotString(WrenVM* vm, int slot, const char* text);
|
||||
// This does not release the handle for the value.
|
||||
void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value);
|
||||
|
||||
// Takes the value stored at [elementSlot] and inserts it into the list stored
|
||||
// at [listSlot] at [index].
|
||||
//
|
||||
// As in Wren, negative indexes can be used to insert from the end. To append
|
||||
// an element, use `-1` for the index.
|
||||
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot);
|
||||
|
||||
#endif
|
||||
|
||||
139
src/vm/wren_vm.c
139
src/vm/wren_vm.c
@ -420,6 +420,48 @@ static bool checkArity(WrenVM* vm, Value value, int numArgs)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensures [fiber]'s stack has at least [needed] slots.
|
||||
static void ensureStack(WrenVM* vm, ObjFiber* fiber, int needed)
|
||||
{
|
||||
if (fiber->stackCapacity >= needed) return;
|
||||
|
||||
int capacity = wrenPowerOf2Ceil(needed);
|
||||
|
||||
Value* oldStack = fiber->stack;
|
||||
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
|
||||
sizeof(Value) * fiber->stackCapacity,
|
||||
sizeof(Value) * capacity);
|
||||
fiber->stackCapacity = capacity;
|
||||
|
||||
// If the reallocation moves the stack, then we need to shift every pointer
|
||||
// into the stack to point to its new location.
|
||||
if (fiber->stack != oldStack)
|
||||
{
|
||||
// Top of the stack.
|
||||
long offset = fiber->stack - oldStack;
|
||||
fiber->stackTop += offset;
|
||||
|
||||
// Stack pointer for each call frame.
|
||||
for (int i = 0; i < fiber->numFrames; i++)
|
||||
{
|
||||
fiber->frames[i].stackStart += offset;
|
||||
}
|
||||
|
||||
// Open upvalues.
|
||||
for (ObjUpvalue* upvalue = fiber->openUpvalues;
|
||||
upvalue != NULL;
|
||||
upvalue = upvalue->next)
|
||||
{
|
||||
upvalue->value += offset;
|
||||
}
|
||||
|
||||
if (vm->foreignStackStart != NULL)
|
||||
{
|
||||
vm->foreignStackStart += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs]
|
||||
// arguments (including the receiver) to be on the top of the stack already.
|
||||
// [function] can be an `ObjFn` or `ObjClosure`.
|
||||
@ -439,45 +481,7 @@ static inline void callFunction(
|
||||
// Grow the stack if needed.
|
||||
int stackSize = (int)(fiber->stackTop - fiber->stack);
|
||||
int needed = stackSize + wrenUpwrapClosure(function)->maxSlots;
|
||||
|
||||
if (fiber->stackCapacity < needed)
|
||||
{
|
||||
int capacity = wrenPowerOf2Ceil(needed);
|
||||
|
||||
Value* oldStack = fiber->stack;
|
||||
fiber->stack = (Value*)wrenReallocate(vm, fiber->stack,
|
||||
sizeof(Value) * fiber->stackCapacity,
|
||||
sizeof(Value) * capacity);
|
||||
fiber->stackCapacity = capacity;
|
||||
|
||||
// If the reallocation moves the stack, then we need to shift every pointer
|
||||
// into the stack to point to its new location.
|
||||
if (fiber->stack != oldStack)
|
||||
{
|
||||
// Top of the stack.
|
||||
long offset = fiber->stack - oldStack;
|
||||
fiber->stackTop += offset;
|
||||
|
||||
// Stack pointer for each call frame.
|
||||
for (int i = 0; i < fiber->numFrames; i++)
|
||||
{
|
||||
fiber->frames[i].stackStart += offset;
|
||||
}
|
||||
|
||||
// Open upvalues.
|
||||
for (ObjUpvalue* upvalue = fiber->openUpvalues;
|
||||
upvalue != NULL;
|
||||
upvalue = upvalue->next)
|
||||
{
|
||||
upvalue->value += offset;
|
||||
}
|
||||
|
||||
if (vm->foreignStackStart != NULL)
|
||||
{
|
||||
vm->foreignStackStart += offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
ensureStack(vm, fiber, needed);
|
||||
|
||||
wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
|
||||
}
|
||||
@ -1625,23 +1629,39 @@ void wrenPopRoot(WrenVM* vm)
|
||||
vm->numTempRoots--;
|
||||
}
|
||||
|
||||
// Returns true if the VM is in a foreign method that's a finalizer.
|
||||
//
|
||||
// Finalizers don't run in the context of a fiber and have a single magic stack
|
||||
// slot, so need to be handled a little specially.
|
||||
static bool isInFinalizer(WrenVM* vm)
|
||||
{
|
||||
return vm->fiber == NULL ||
|
||||
vm->foreignStackStart < vm->fiber->stack ||
|
||||
vm->foreignStackStart > vm->fiber->stackTop;
|
||||
}
|
||||
|
||||
int wrenGetSlotCount(WrenVM* vm)
|
||||
{
|
||||
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call.");
|
||||
|
||||
// If no fiber is executing or the foreign stack is not in it, we must be in
|
||||
// a finalizer, in which case the "stack" just has one object, the object
|
||||
// being finalized.
|
||||
if (vm->fiber == NULL ||
|
||||
vm->foreignStackStart < vm->fiber->stack ||
|
||||
vm->foreignStackStart > vm->fiber->stackTop)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (isInFinalizer(vm)) return 1;
|
||||
return (int)(vm->fiber->stackTop - vm->foreignStackStart);
|
||||
}
|
||||
|
||||
void wrenEnsureSlots(WrenVM* vm, int numSlots)
|
||||
{
|
||||
ASSERT(!isInFinalizer(vm), "Cannot grow the stack in a finalizer.");
|
||||
|
||||
int currentSize = (int)(vm->fiber->stackTop - vm->foreignStackStart);
|
||||
if (currentSize >= numSlots) return;
|
||||
|
||||
// Grow the stack if needed.
|
||||
int needed = (int)(vm->foreignStackStart - vm->fiber->stack) + numSlots;
|
||||
ensureStack(vm, vm->fiber, needed);
|
||||
|
||||
vm->fiber->stackTop = vm->foreignStackStart + numSlots;
|
||||
}
|
||||
|
||||
// Ensures that [slot] is a valid index into a foreign method's stack of slots.
|
||||
static void validateForeignSlot(WrenVM* vm, int slot)
|
||||
{
|
||||
@ -1723,6 +1743,11 @@ void wrenSetSlotDouble(WrenVM* vm, int slot, double value)
|
||||
setSlot(vm, slot, NUM_VAL(value));
|
||||
}
|
||||
|
||||
void wrenSetSlotNewList(WrenVM* vm, int slot)
|
||||
{
|
||||
setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
|
||||
}
|
||||
|
||||
void wrenSetSlotNull(WrenVM* vm, int slot)
|
||||
{
|
||||
setSlot(vm, slot, NULL_VAL);
|
||||
@ -1740,3 +1765,19 @@ void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value)
|
||||
|
||||
setSlot(vm, slot, value->value);
|
||||
}
|
||||
|
||||
void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot)
|
||||
{
|
||||
validateForeignSlot(vm, listSlot);
|
||||
validateForeignSlot(vm, elementSlot);
|
||||
ASSERT(IS_LIST(vm->foreignStackStart[listSlot]), "Must insert into a list.");
|
||||
|
||||
ObjList* list = AS_LIST(vm->foreignStackStart[listSlot]);
|
||||
|
||||
// Negative indices count from the end.
|
||||
if (index < 0) index = list->elements.count + 1 + index;
|
||||
|
||||
ASSERT(index <= list->elements.count, "Index out of bounds.");
|
||||
|
||||
wrenListInsert(vm, list, vm->foreignStackStart[elementSlot], index);
|
||||
}
|
||||
|
||||
46
test/api/lists.c
Normal file
46
test/api/lists.c
Normal file
@ -0,0 +1,46 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "lists.h"
|
||||
|
||||
static void newList(WrenVM* vm)
|
||||
{
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
}
|
||||
|
||||
// Helper function to store a double in a slot then insert it into the list at
|
||||
// slot zero.
|
||||
static void insertNumber(WrenVM* vm, int index, double value)
|
||||
{
|
||||
wrenSetSlotDouble(vm, 1, value);
|
||||
wrenInsertInList(vm, 0, index, 1);
|
||||
}
|
||||
|
||||
static void insert(WrenVM* vm)
|
||||
{
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
|
||||
wrenEnsureSlots(vm, 2);
|
||||
|
||||
// Appending.
|
||||
insertNumber(vm, 0, 1.0);
|
||||
insertNumber(vm, 1, 2.0);
|
||||
insertNumber(vm, 2, 3.0);
|
||||
|
||||
// Inserting.
|
||||
insertNumber(vm, 0, 4.0);
|
||||
insertNumber(vm, 1, 5.0);
|
||||
insertNumber(vm, 2, 6.0);
|
||||
|
||||
// Negative indexes.
|
||||
insertNumber(vm, -1, 7.0);
|
||||
insertNumber(vm, -2, 8.0);
|
||||
insertNumber(vm, -3, 9.0);
|
||||
}
|
||||
|
||||
WrenForeignMethodFn listsBindMethod(const char* signature)
|
||||
{
|
||||
if (strcmp(signature, "static Lists.newList()") == 0) return newList;
|
||||
if (strcmp(signature, "static Lists.insert()") == 0) return insert;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
3
test/api/lists.h
Normal file
3
test/api/lists.h
Normal file
@ -0,0 +1,3 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn listsBindMethod(const char* signature);
|
||||
10
test/api/lists.wren
Normal file
10
test/api/lists.wren
Normal file
@ -0,0 +1,10 @@
|
||||
class Lists {
|
||||
foreign static newList()
|
||||
foreign static insert()
|
||||
}
|
||||
|
||||
var list = Lists.newList()
|
||||
System.print(list is List) // expect: true
|
||||
System.print(list.count) // expect: 0
|
||||
|
||||
System.print(Lists.insert()) // expect: [4, 5, 6, 1, 2, 3, 9, 8, 7]
|
||||
@ -7,6 +7,7 @@
|
||||
#include "benchmark.h"
|
||||
#include "call.h"
|
||||
#include "foreign_class.h"
|
||||
#include "lists.h"
|
||||
#include "slots.h"
|
||||
#include "value.h"
|
||||
|
||||
@ -36,6 +37,9 @@ static WrenForeignMethodFn bindForeignMethod(
|
||||
method = foreignClassBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = listsBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = slotsBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "slots.h"
|
||||
@ -73,11 +74,38 @@ static void setSlots(WrenVM* vm)
|
||||
}
|
||||
}
|
||||
|
||||
static void ensure(WrenVM* vm)
|
||||
{
|
||||
int before = wrenGetSlotCount(vm);
|
||||
|
||||
wrenEnsureSlots(vm, 20);
|
||||
|
||||
int after = wrenGetSlotCount(vm);
|
||||
|
||||
// Use the slots to make sure they're available.
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
wrenSetSlotDouble(vm, i, i);
|
||||
}
|
||||
|
||||
int sum = 0;
|
||||
|
||||
for (int i = 0; i < 20; i++)
|
||||
{
|
||||
sum += (int)wrenGetSlotDouble(vm, i);
|
||||
}
|
||||
|
||||
char result[100];
|
||||
sprintf(result, "%d -> %d (%d)", before, after, sum);
|
||||
wrenSetSlotString(vm, 0, result);
|
||||
}
|
||||
|
||||
WrenForeignMethodFn slotsBindMethod(const char* signature)
|
||||
{
|
||||
if (strcmp(signature, "static Slots.noSet") == 0) return noSet;
|
||||
if (strcmp(signature, "static Slots.getSlots(_,_,_,_,_)") == 0) return getSlots;
|
||||
if (strcmp(signature, "static Slots.setSlots(_,_,_,_)") == 0) return setSlots;
|
||||
if (strcmp(signature, "static Slots.ensure()") == 0) return ensure;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
class Slots {
|
||||
foreign static noSet
|
||||
|
||||
foreign static getSlots(bool, num, string, bytes, value)
|
||||
|
||||
foreign static setSlots(a, b, c, d)
|
||||
foreign static ensure()
|
||||
}
|
||||
|
||||
// If nothing is set in the return slot, it retains its previous value, the
|
||||
@ -14,3 +13,5 @@ var value = ["value"]
|
||||
System.print(Slots.getSlots(true, "by\0te", 12.34, "str", value) == value) // expect: true
|
||||
|
||||
System.print(Slots.setSlots(value, 0, 0, 0) == value) // expect: true
|
||||
|
||||
System.print(Slots.ensure()) // expect: 1 -> 20 (190)
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; };
|
||||
2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; };
|
||||
29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.c */; };
|
||||
29932D541C210F8D00099DEE /* lists.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D521C210F8D00099DEE /* lists.c */; };
|
||||
29A427341BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; };
|
||||
29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; };
|
||||
29A427361BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */; };
|
||||
@ -106,6 +107,8 @@
|
||||
2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = "<group>"; };
|
||||
29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = "<group>"; };
|
||||
29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = "<group>"; };
|
||||
29932D521C210F8D00099DEE /* lists.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = "<group>"; };
|
||||
29932D531C210F8D00099DEE /* lists.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = "<group>"; };
|
||||
29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_opt_meta.c; path = ../../src/optional/wren_opt_meta.c; sourceTree = "<group>"; };
|
||||
29A4272F1BDBE435001E6E22 /* wren_opt_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = "<group>"; };
|
||||
29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = wren_opt_meta.wren.inc; path = ../../src/optional/wren_opt_meta.wren.inc; sourceTree = "<group>"; };
|
||||
@ -249,6 +252,8 @@
|
||||
293D46951BB43F9900200083 /* call.h */,
|
||||
29D009A81B7E39A8000CE58C /* foreign_class.c */,
|
||||
29D009A91B7E39A8000CE58C /* foreign_class.h */,
|
||||
29932D521C210F8D00099DEE /* lists.c */,
|
||||
29932D531C210F8D00099DEE /* lists.h */,
|
||||
29D009AA1B7E39A8000CE58C /* slots.c */,
|
||||
29D009AB1B7E39A8000CE58C /* slots.h */,
|
||||
29D009AC1B7E39A8000CE58C /* value.c */,
|
||||
@ -358,6 +363,7 @@
|
||||
files = (
|
||||
29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */,
|
||||
29729F321BA70A620099CA20 /* io.c in Sources */,
|
||||
29932D541C210F8D00099DEE /* lists.c in Sources */,
|
||||
291647C81BA5EC5E006142EE /* modules.c in Sources */,
|
||||
29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */,
|
||||
29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */,
|
||||
|
||||
Reference in New Issue
Block a user