feat: make String iterable over code points with iterate/iteratorValue primitives

Add String as a subclass of Sequence in core.wren and implement the iterator protocol for strings in wren_core.c. The string_iterate native advances through UTF-8 code point boundaries, while string_iteratorValue extracts the full code point at a given byte index. Includes comprehensive test coverage for iteration, edge cases (empty strings, mid-sequence positions), and error handling for invalid iterator types and out-of-bounds indices. Also updates documentation for String, List, and Sequence classes with iterator protocol details and usage examples.
This commit is contained in:
Bob Nystrom
2015-01-23 04:58:22 +00:00
parent 7e52e36a1c
commit 488fb27b91
14 changed files with 141 additions and 21 deletions
+56 -16
View File
@@ -87,6 +87,8 @@ static const char* libSource =
"\n"
"}\n"
"\n"
"class String is Sequence {}\n"
"\n"
"class List is Sequence {\n"
" addAll(other) {\n"
" for (element in other) {\n"
@@ -1027,6 +1029,42 @@ DEF_NATIVE(string_indexOf)
RETURN_NUM(firstOccurrence ? firstOccurrence - string->value : -1);
}
DEF_NATIVE(string_iterate)
{
ObjString* string = AS_STRING(args[0]);
// If we're starting the iteration, return the first index.
if (IS_NULL(args[1]))
{
if (string->length == 0) RETURN_FALSE;
RETURN_NUM(0);
}
if (!validateInt(vm, args, 1, "Iterator")) return PRIM_ERROR;
int index = (int)AS_NUM(args[1]);
if (index < 0) RETURN_FALSE;
// Advance to the beginning of the next UTF-8 sequence.
do
{
index++;
if (index >= string->length) RETURN_FALSE;
} while ((string->value[index] & 0xc0) == 0x80);
RETURN_NUM(index);
}
DEF_NATIVE(string_iteratorValue)
{
ObjString* string = AS_STRING(args[0]);
int index = validateIndex(vm, args, string->length, 1, "Iterator");
// TODO: Test.
if (index == -1) return PRIM_ERROR;
RETURN_VAL(wrenStringCodePointAt(vm, string, index));
}
DEF_NATIVE(string_startsWith)
{
if (!validateString(vm, args, 1, "Argument")) return PRIM_ERROR;
@@ -1261,7 +1299,9 @@ void wrenInitializeCore(WrenVM* vm)
NATIVE(vm->numClass, "== ", num_eqeq);
NATIVE(vm->numClass, "!= ", num_bangeq);
vm->stringClass = defineClass(vm, "String");
wrenInterpret(vm, "", libSource);
vm->stringClass = AS_CLASS(findGlobal(vm, "String"));
NATIVE(vm->stringClass, "+ ", string_plus);
NATIVE(vm->stringClass, "== ", string_eqeq);
NATIVE(vm->stringClass, "!= ", string_bangeq);
@@ -1270,24 +1310,11 @@ void wrenInitializeCore(WrenVM* vm)
NATIVE(vm->stringClass, "count", string_count);
NATIVE(vm->stringClass, "endsWith ", string_endsWith);
NATIVE(vm->stringClass, "indexOf ", string_indexOf);
NATIVE(vm->stringClass, "iterate ", string_iterate);
NATIVE(vm->stringClass, "iteratorValue ", string_iteratorValue);
NATIVE(vm->stringClass, "startsWith ", string_startsWith);
NATIVE(vm->stringClass, "toString", string_toString);
// When the base classes are defined, we allocate string objects for their
// names. However, we haven't created the string class itself yet, so those
// all have NULL class pointers. Now that we have a string class, go back and
// fix them up.
vm->objectClass->name->obj.classObj = vm->stringClass;
vm->classClass->name->obj.classObj = vm->stringClass;
vm->boolClass->name->obj.classObj = vm->stringClass;
vm->fiberClass->name->obj.classObj = vm->stringClass;
vm->fnClass->name->obj.classObj = vm->stringClass;
vm->nullClass->name->obj.classObj = vm->stringClass;
vm->numClass->name->obj.classObj = vm->stringClass;
vm->stringClass->name->obj.classObj = vm->stringClass;
wrenInterpret(vm, "", libSource);
vm->listClass = AS_CLASS(findGlobal(vm, "List"));
NATIVE(vm->listClass->obj.classObj, " instantiate", list_instantiate);
NATIVE(vm->listClass, "[ ]", list_subscript);
@@ -1309,4 +1336,17 @@ void wrenInitializeCore(WrenVM* vm)
NATIVE(vm->rangeClass, "iterate ", range_iterate);
NATIVE(vm->rangeClass, "iteratorValue ", range_iteratorValue);
NATIVE(vm->rangeClass, "toString", range_toString);
// While bootstrapping the core types and running the core library, a number
// string objects have been created, many of which were instantiated before
// stringClass was stored in the VM. Some of them *must* be created first:
// the ObjClass for string itself has a reference to the ObjString for its
// name.
//
// These all currently a NULL classObj pointer, so go back and assign them
// now that the string class is known.
for (Obj* obj = vm->first; obj != NULL; obj = obj->next)
{
if (obj->type == OBJ_STRING) obj->classObj = vm->stringClass;
}
}