feat: add async/await keywords, Num/String extensions, and Jinja markdown example

Add `async` and `await` token support to the Wren compiler with scheduler resolution logic, extend `Num` with `e` constant, hyperbolic trig, base conversion, and new methods (`isZero`, `gcd`, `lcm`, `digits`, etc.), extend `String` with `lower`, `upper`, `capitalize`, `title`, and regenerate `wren_core.wren.inc`. Include `Makefile` for multi-platform builds, rewrite `README.md` with updated build instructions, and add `example/await_demo.wren` and `example/jinja_markdown.wren` demonstrating new features.
This commit is contained in:
2026-01-25 03:58:39 +00:00
parent 74ef5cbc11
commit 46fc2e2470
139 changed files with 5078 additions and 73 deletions
+61
View File
@@ -108,6 +108,8 @@ typedef enum
TOKEN_TRUE,
TOKEN_VAR,
TOKEN_WHILE,
TOKEN_ASYNC,
TOKEN_AWAIT,
TOKEN_FIELD,
TOKEN_STATIC_FIELD,
@@ -619,6 +621,8 @@ static Keyword keywords[] =
{"true", 4, TOKEN_TRUE},
{"var", 3, TOKEN_VAR},
{"while", 5, TOKEN_WHILE},
{"async", 5, TOKEN_ASYNC},
{"await", 5, TOKEN_AWAIT},
{NULL, 0, TOKEN_EOF} // Sentinel to mark the end of the array.
};
@@ -1746,6 +1750,8 @@ static void expression(Compiler* compiler);
static void statement(Compiler* compiler);
static void definition(Compiler* compiler);
static void parsePrecedence(Compiler* compiler, Precedence precedence);
static void async_(Compiler* compiler, bool canAssign);
static void await_(Compiler* compiler, bool canAssign);
// Replaces the placeholder argument for a previous CODE_JUMP or CODE_JUMP_IF
// instruction with an offset that jumps to the current end of bytecode.
@@ -2499,6 +2505,59 @@ static void this_(Compiler* compiler, bool canAssign)
loadThis(compiler);
}
static int resolveScheduler(Compiler* compiler)
{
int symbol = wrenSymbolTableFind(
&compiler->parser->module->variableNames,
"Scheduler", 9);
if (symbol == -1)
{
symbol = wrenDeclareVariable(compiler->parser->vm,
compiler->parser->module,
"Scheduler", 9,
compiler->parser->previous.line);
if (symbol == -2)
{
error(compiler, "Too many module variables defined.");
return -1;
}
}
return symbol;
}
static void await_(Compiler* compiler, bool canAssign)
{
int schedulerSymbol = resolveScheduler(compiler);
if (schedulerSymbol == -1) return;
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
ignoreNewlines(compiler);
expression(compiler);
callMethod(compiler, 1, "await_(_)", 9);
}
static void async_(Compiler* compiler, bool canAssign)
{
int schedulerSymbol = resolveScheduler(compiler);
if (schedulerSymbol == -1) return;
emitShortArg(compiler, CODE_LOAD_MODULE_VAR, schedulerSymbol);
consume(compiler, TOKEN_LEFT_BRACE, "Expect '{' after 'async'.");
Compiler fnCompiler;
initCompiler(&fnCompiler, compiler->parser, compiler, false);
fnCompiler.fn->arity = 0;
finishBody(&fnCompiler);
endCompiler(&fnCompiler, "[async]", 7);
callMethod(compiler, 1, "async_(_)", 9);
}
// Subscript or "array indexing" operator like `foo[bar]`.
static void subscript(Compiler* compiler, bool canAssign)
{
@@ -2799,6 +2858,8 @@ GrammarRule rules[] =
/* TOKEN_TRUE */ PREFIX(boolean),
/* TOKEN_VAR */ UNUSED,
/* TOKEN_WHILE */ UNUSED,
/* TOKEN_ASYNC */ PREFIX(async_),
/* TOKEN_AWAIT */ PREFIX(await_),
/* TOKEN_FIELD */ PREFIX(field),
/* TOKEN_STATIC_FIELD */ PREFIX(staticField),
/* TOKEN_NAME */ { name, NULL, namedSignature, PREC_NONE, NULL },
+148
View File
@@ -2,6 +2,7 @@
#include <errno.h>
#include <float.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
@@ -647,6 +648,7 @@ DEF_NUM_CONSTANT(smallest, DBL_MIN)
DEF_NUM_CONSTANT(maxSafeInteger, 9007199254740991.0)
DEF_NUM_CONSTANT(minSafeInteger, -9007199254740991.0)
DEF_NUM_CONSTANT(e, 2.71828182845904523536)
// Defines a primitive on Num that calls infix [op] and returns [type].
#define DEF_NUM_INFIX(name, op, type) \
@@ -704,6 +706,10 @@ DEF_NUM_FN(tan, tan)
DEF_NUM_FN(log, log)
DEF_NUM_FN(log2, log2)
DEF_NUM_FN(exp, exp)
DEF_NUM_FN(log10, log10)
DEF_NUM_FN(sinh, sinh)
DEF_NUM_FN(cosh, cosh)
DEF_NUM_FN(tanh, tanh)
DEF_PRIMITIVE(num_mod)
{
@@ -843,6 +849,65 @@ DEF_PRIMITIVE(num_truncate)
RETURN_NUM(integer);
}
DEF_PRIMITIVE(num_toBase)
{
if (!validateInt(vm, args[1], "Radix")) return false;
int radix = (int)AS_NUM(args[1]);
if (radix < 2 || radix > 36)
{
RETURN_ERROR("Radix must be between 2 and 36.");
}
double raw = AS_NUM(args[0]);
if (isnan(raw) || isinf(raw))
{
RETURN_ERROR("Cannot convert NaN or Infinity to a base string.");
}
int64_t value = (int64_t)trunc(raw);
bool negative = value < 0;
if (negative) value = -value;
static const char lookup[] = "0123456789abcdefghijklmnopqrstuvwxyz";
char buf[66];
int len = 0;
if (value == 0)
{
buf[len++] = '0';
}
else
{
while (value > 0)
{
buf[len++] = lookup[value % radix];
value = value / radix;
}
}
if (negative) buf[len++] = '-';
for (int i = 0; i < len / 2; i++)
{
char tmp = buf[i];
buf[i] = buf[len - 1 - i];
buf[len - 1 - i] = tmp;
}
buf[len] = '\0';
RETURN_VAL(wrenNewStringLength(vm, buf, len));
}
DEF_PRIMITIVE(num_format)
{
if (!validateInt(vm, args[1], "Decimal places")) return false;
int decimals = (int)AS_NUM(args[1]);
if (decimals < 0 || decimals > 20)
{
RETURN_ERROR("Decimal places must be between 0 and 20.");
}
char buf[64];
int len = snprintf(buf, sizeof(buf), "%.*f", decimals, AS_NUM(args[0]));
if (len < 0 || len >= (int)sizeof(buf))
{
RETURN_ERROR("Formatting failed.");
}
RETURN_VAL(wrenNewStringLength(vm, buf, (size_t)len));
}
DEF_PRIMITIVE(object_same)
{
RETURN_BOOL(wrenValuesEqual(args[1], args[2]));
@@ -1167,6 +1232,46 @@ DEF_PRIMITIVE(string_plus)
RETURN_VAL(wrenStringFormat(vm, "@@", args[0], args[1]));
}
DEF_PRIMITIVE(string_lt)
{
if (!validateString(vm, args[1], "Right operand")) return false;
ObjString* left = AS_STRING(args[0]);
ObjString* right = AS_STRING(args[1]);
uint32_t minLen = left->length < right->length ? left->length : right->length;
int cmp = memcmp(left->value, right->value, minLen);
RETURN_BOOL(cmp < 0 || (cmp == 0 && left->length < right->length));
}
DEF_PRIMITIVE(string_gt)
{
if (!validateString(vm, args[1], "Right operand")) return false;
ObjString* left = AS_STRING(args[0]);
ObjString* right = AS_STRING(args[1]);
uint32_t minLen = left->length < right->length ? left->length : right->length;
int cmp = memcmp(left->value, right->value, minLen);
RETURN_BOOL(cmp > 0 || (cmp == 0 && left->length > right->length));
}
DEF_PRIMITIVE(string_lte)
{
if (!validateString(vm, args[1], "Right operand")) return false;
ObjString* left = AS_STRING(args[0]);
ObjString* right = AS_STRING(args[1]);
uint32_t minLen = left->length < right->length ? left->length : right->length;
int cmp = memcmp(left->value, right->value, minLen);
RETURN_BOOL(cmp < 0 || (cmp == 0 && left->length <= right->length));
}
DEF_PRIMITIVE(string_gte)
{
if (!validateString(vm, args[1], "Right operand")) return false;
ObjString* left = AS_STRING(args[0]);
ObjString* right = AS_STRING(args[1]);
uint32_t minLen = left->length < right->length ? left->length : right->length;
int cmp = memcmp(left->value, right->value, minLen);
RETURN_BOOL(cmp > 0 || (cmp == 0 && left->length >= right->length));
}
DEF_PRIMITIVE(string_subscript)
{
ObjString* string = AS_STRING(args[0]);
@@ -1197,6 +1302,36 @@ DEF_PRIMITIVE(string_toString)
RETURN_VAL(args[0]);
}
DEF_PRIMITIVE(string_lower)
{
ObjString* str = AS_STRING(args[0]);
char* buf = ALLOCATE_ARRAY(vm, char, str->length + 1);
for (uint32_t i = 0; i < str->length; i++)
{
uint8_t c = (uint8_t)str->value[i];
buf[i] = (c >= 'A' && c <= 'Z') ? (char)(c + 32) : (char)c;
}
buf[str->length] = '\0';
Value result = wrenNewStringLength(vm, buf, str->length);
DEALLOCATE(vm, buf);
RETURN_VAL(result);
}
DEF_PRIMITIVE(string_upper)
{
ObjString* str = AS_STRING(args[0]);
char* buf = ALLOCATE_ARRAY(vm, char, str->length + 1);
for (uint32_t i = 0; i < str->length; i++)
{
uint8_t c = (uint8_t)str->value[i];
buf[i] = (c >= 'a' && c <= 'z') ? (char)(c - 32) : (char)c;
}
buf[str->length] = '\0';
Value result = wrenNewStringLength(vm, buf, str->length);
DEALLOCATE(vm, buf);
RETURN_VAL(result);
}
DEF_PRIMITIVE(system_clock)
{
RETURN_NUM((double)clock() / CLOCKS_PER_SEC);
@@ -1358,6 +1493,7 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->numClass->obj.classObj, "smallest", num_smallest);
PRIMITIVE(vm->numClass->obj.classObj, "maxSafeInteger", num_maxSafeInteger);
PRIMITIVE(vm->numClass->obj.classObj, "minSafeInteger", num_minSafeInteger);
PRIMITIVE(vm->numClass->obj.classObj, "e", num_e);
PRIMITIVE(vm->numClass, "-(_)", num_minus);
PRIMITIVE(vm->numClass, "+(_)", num_plus);
PRIMITIVE(vm->numClass, "*(_)", num_multiply);
@@ -1403,6 +1539,12 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->numClass, "sign", num_sign);
PRIMITIVE(vm->numClass, "toString", num_toString);
PRIMITIVE(vm->numClass, "truncate", num_truncate);
PRIMITIVE(vm->numClass, "log10", num_log10);
PRIMITIVE(vm->numClass, "sinh", num_sinh);
PRIMITIVE(vm->numClass, "cosh", num_cosh);
PRIMITIVE(vm->numClass, "tanh", num_tanh);
PRIMITIVE(vm->numClass, "toBase(_)", num_toBase);
PRIMITIVE(vm->numClass, "format(_)", num_format);
// These are defined just so that 0 and -0 are equal, which is specified by
// IEEE 754 even though they have different bit representations.
@@ -1413,6 +1555,10 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->stringClass->obj.classObj, "fromCodePoint(_)", string_fromCodePoint);
PRIMITIVE(vm->stringClass->obj.classObj, "fromByte(_)", string_fromByte);
PRIMITIVE(vm->stringClass, "+(_)", string_plus);
PRIMITIVE(vm->stringClass, "<(_)", string_lt);
PRIMITIVE(vm->stringClass, ">(_)", string_gt);
PRIMITIVE(vm->stringClass, "<=(_)", string_lte);
PRIMITIVE(vm->stringClass, ">=(_)", string_gte);
PRIMITIVE(vm->stringClass, "[_]", string_subscript);
PRIMITIVE(vm->stringClass, "byteAt_(_)", string_byteAt);
PRIMITIVE(vm->stringClass, "byteCount_", string_byteCount);
@@ -1426,6 +1572,8 @@ void wrenInitializeCore(WrenVM* vm)
PRIMITIVE(vm->stringClass, "iteratorValue(_)", string_iteratorValue);
PRIMITIVE(vm->stringClass, "startsWith(_)", string_startsWith);
PRIMITIVE(vm->stringClass, "toString", string_toString);
PRIMITIVE(vm->stringClass, "lower_", string_lower);
PRIMITIVE(vm->stringClass, "upper_", string_upper);
vm->listClass = AS_CLASS(wrenFindVariable(vm, coreModule, "List"));
PRIMITIVE(vm->listClass->obj.classObj, "filled(_,_)", list_filled);
+281 -2
View File
@@ -2,7 +2,62 @@ class Bool {}
class Fiber {}
class Fn {}
class Null {}
class Num {}
class Num {
isZero { this == 0 }
isPositive { this > 0 }
isNegative { this < 0 }
isFinite { !isInfinity && !isNan }
isEven { isInteger && this % 2 == 0 }
isOdd { isInteger && this % 2 != 0 }
isBetween(min, max) { this >= min && this <= max }
toDegrees { this * 180 / Num.pi }
toRadians { this * Num.pi / 180 }
toChar { String.fromCodePoint(this) }
toHex { toBase(16) }
toBinary { toBase(2) }
toOctal { toBase(8) }
gcd(other) {
var a = this.abs
var b = other.abs
while (b != 0) {
var t = b
b = a % b
a = t
}
return a
}
lcm(other) {
if (this == 0 && other == 0) return 0
return (this * other).abs / gcd(other)
}
digits {
if (!isInteger) Fiber.abort("Value must be an integer.")
var n = this.abs
if (n == 0) {
var z = List.new()
z.add(0)
return z
}
var result = List.new()
while (n > 0) {
result.add(n % 10)
n = (n / 10).floor
}
var reversed = List.new()
var i = result.count - 1
while (i >= 0) {
reversed.add(result[i])
i = i - 1
}
return reversed
}
}
class Sequence {
all(f) {
@@ -289,6 +344,230 @@ class String is Sequence {
}
return result
}
lower { lower_ }
upper { upper_ }
capitalize {
if (isEmpty) return this
return this[0].upper + this[1..-1].lower
}
title {
if (isEmpty) return this
var result = ""
var capitalizeNext = true
for (c in this) {
if (c == " " || c == "\t" || c == "\n" || c == "\r") {
result = result + c
capitalizeNext = true
} else if (capitalizeNext) {
result = result + c.upper
capitalizeNext = false
} else {
result = result + c.lower
}
}
return result
}
swapCase {
var result = ""
for (c in this) {
var cp = c.codePoints.toList[0]
if (cp >= 65 && cp <= 90) {
result = result + c.lower
} else if (cp >= 97 && cp <= 122) {
result = result + c.upper
} else {
result = result + c
}
}
return result
}
lastIndexOf(search) {
var last = -1
var index = 0
var size = byteCount_
var searchSize = search.byteCount_
if (searchSize > size) return -1
while (index <= size - searchSize) {
var found = indexOf(search, index)
if (found == -1) break
last = found
index = found + 1
}
return last
}
lastIndexOf(search, start) {
var last = -1
var index = 0
var searchSize = search.byteCount_
if (searchSize > start + searchSize) return -1
while (index <= start) {
var found = indexOf(search, index)
if (found == -1 || found > start) break
last = found
index = found + 1
}
return last
}
isLower {
if (isEmpty) return false
var hasAlpha = false
for (b in bytes) {
if (b >= 65 && b <= 90) return false
if (b >= 97 && b <= 122) hasAlpha = true
}
return hasAlpha
}
isUpper {
if (isEmpty) return false
var hasAlpha = false
for (b in bytes) {
if (b >= 97 && b <= 122) return false
if (b >= 65 && b <= 90) hasAlpha = true
}
return hasAlpha
}
isDigit {
if (isEmpty) return false
for (b in bytes) {
if (b < 48 || b > 57) return false
}
return true
}
isAlpha {
if (isEmpty) return false
for (b in bytes) {
if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122))) return false
}
return true
}
isAlphaNumeric {
if (isEmpty) return false
for (b in bytes) {
if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122) || (b >= 48 && b <= 57))) return false
}
return true
}
isSpace {
if (isEmpty) return false
for (b in bytes) {
if (b != 32 && b != 9 && b != 10 && b != 13 && b != 12 && b != 11) return false
}
return true
}
isAscii {
for (b in bytes) {
if (b >= 128) return false
}
return true
}
reverse {
var list = []
for (c in this) {
list.insert(0, c)
}
return list.join("")
}
center(width) { center(width, " ") }
center(width, char) {
if (!(width is Num) || !width.isInteger) {
Fiber.abort("Width must be an integer.")
}
if (!(char is String) || char.isEmpty) {
Fiber.abort("Fill character must be a non-empty string.")
}
var len = count
if (len >= width) return this
var total = width - len
var left = (total / 2).floor
var right = total - left
return (char * (left + 1))[0...left] + this + (char * (right + 1))[0...right]
}
lpad(width, char) {
if (!(width is Num) || !width.isInteger) {
Fiber.abort("Width must be an integer.")
}
if (!(char is String) || char.isEmpty) {
Fiber.abort("Fill character must be a non-empty string.")
}
var len = count
if (len >= width) return this
var pad = width - len
return (char * (pad + 1))[0...pad] + this
}
rpad(width, char) {
if (!(width is Num) || !width.isInteger) {
Fiber.abort("Width must be an integer.")
}
if (!(char is String) || char.isEmpty) {
Fiber.abort("Fill character must be a non-empty string.")
}
var len = count
if (len >= width) return this
var pad = width - len
return this + (char * (pad + 1))[0...pad]
}
zfill(width) {
if (!(width is Num) || !width.isInteger) {
Fiber.abort("Width must be an integer.")
}
var len = count
if (len >= width) return this
if (startsWith("-") || startsWith("+")) {
return this[0] + ("0" * (width - len)) + this[1..-1]
}
return ("0" * (width - len)) + this
}
removePrefix(prefix) {
if (!(prefix is String)) {
Fiber.abort("Prefix must be a string.")
}
if (startsWith(prefix)) return this[prefix.byteCount_..-1]
return this
}
removeSuffix(suffix) {
if (!(suffix is String)) {
Fiber.abort("Suffix must be a string.")
}
if (endsWith(suffix)) {
var end = byteCount_ - suffix.byteCount_ - 1
if (end < 0) return ""
return this[0..end]
}
return this
}
splitLines { replace("\r\n", "\n").replace("\r", "\n").split("\n") }
chars {
var result = []
for (c in this) {
result.add(c)
}
return result
}
toNum { Num.fromString(this) }
}
class StringByteSequence is Sequence {
@@ -480,4 +759,4 @@ class ClassAttributes {
_methods = methods
}
toString { "attributes:%(_attributes) methods:%(_methods)" }
}
}
+289 -9
View File
@@ -1,10 +1,67 @@
// Generated automatically from src/vm/wren_core.wren. Do not edit.
// Please do not edit this file. It has been generated automatically
// from `deps/wren/src/vm/wren_core.wren` using `util/wren_to_c_string.py`
static const char* coreModuleSource =
"class Bool {}\n"
"class Fiber {}\n"
"class Fn {}\n"
"class Null {}\n"
"class Num {}\n"
"class Num {\n"
" isZero { this == 0 }\n"
" isPositive { this > 0 }\n"
" isNegative { this < 0 }\n"
" isFinite { !isInfinity && !isNan }\n"
" isEven { isInteger && this % 2 == 0 }\n"
" isOdd { isInteger && this % 2 != 0 }\n"
"\n"
" isBetween(min, max) { this >= min && this <= max }\n"
"\n"
" toDegrees { this * 180 / Num.pi }\n"
" toRadians { this * Num.pi / 180 }\n"
"\n"
" toChar { String.fromCodePoint(this) }\n"
" toHex { toBase(16) }\n"
" toBinary { toBase(2) }\n"
" toOctal { toBase(8) }\n"
"\n"
" gcd(other) {\n"
" var a = this.abs\n"
" var b = other.abs\n"
" while (b != 0) {\n"
" var t = b\n"
" b = a % b\n"
" a = t\n"
" }\n"
" return a\n"
" }\n"
"\n"
" lcm(other) {\n"
" if (this == 0 && other == 0) return 0\n"
" return (this * other).abs / gcd(other)\n"
" }\n"
"\n"
" digits {\n"
" if (!isInteger) Fiber.abort(\"Value must be an integer.\")\n"
" var n = this.abs\n"
" if (n == 0) {\n"
" var z = List.new()\n"
" z.add(0)\n"
" return z\n"
" }\n"
" var result = List.new()\n"
" while (n > 0) {\n"
" result.add(n % 10)\n"
" n = (n / 10).floor\n"
" }\n"
" var reversed = List.new()\n"
" var i = result.count - 1\n"
" while (i >= 0) {\n"
" reversed.add(result[i])\n"
" i = i - 1\n"
" }\n"
" return reversed\n"
" }\n"
"}\n"
"\n"
"class Sequence {\n"
" all(f) {\n"
@@ -238,11 +295,11 @@ static const char* coreModuleSource =
" return result\n"
" }\n"
"\n"
" trim() { trim_(\"\t\r\n \", true, true) }\n"
" trim() { trim_(\"\\t\\r\\n \", true, true) }\n"
" trim(chars) { trim_(chars, true, true) }\n"
" trimEnd() { trim_(\"\t\r\n \", false, true) }\n"
" trimEnd() { trim_(\"\\t\\r\\n \", false, true) }\n"
" trimEnd(chars) { trim_(chars, false, true) }\n"
" trimStart() { trim_(\"\t\r\n \", true, false) }\n"
" trimStart() { trim_(\"\\t\\r\\n \", true, false) }\n"
" trimStart(chars) { trim_(chars, true, false) }\n"
"\n"
" trim_(chars, trimStart, trimEnd) {\n"
@@ -291,6 +348,230 @@ static const char* coreModuleSource =
" }\n"
" return result\n"
" }\n"
"\n"
" lower { lower_ }\n"
" upper { upper_ }\n"
"\n"
" capitalize {\n"
" if (isEmpty) return this\n"
" return this[0].upper + this[1..-1].lower\n"
" }\n"
"\n"
" title {\n"
" if (isEmpty) return this\n"
" var result = \"\"\n"
" var capitalizeNext = true\n"
" for (c in this) {\n"
" if (c == \" \" || c == \"\\t\" || c == \"\\n\" || c == \"\\r\") {\n"
" result = result + c\n"
" capitalizeNext = true\n"
" } else if (capitalizeNext) {\n"
" result = result + c.upper\n"
" capitalizeNext = false\n"
" } else {\n"
" result = result + c.lower\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" swapCase {\n"
" var result = \"\"\n"
" for (c in this) {\n"
" var cp = c.codePoints.toList[0]\n"
" if (cp >= 65 && cp <= 90) {\n"
" result = result + c.lower\n"
" } else if (cp >= 97 && cp <= 122) {\n"
" result = result + c.upper\n"
" } else {\n"
" result = result + c\n"
" }\n"
" }\n"
" return result\n"
" }\n"
"\n"
" lastIndexOf(search) {\n"
" var last = -1\n"
" var index = 0\n"
" var size = byteCount_\n"
" var searchSize = search.byteCount_\n"
" if (searchSize > size) return -1\n"
" while (index <= size - searchSize) {\n"
" var found = indexOf(search, index)\n"
" if (found == -1) break\n"
" last = found\n"
" index = found + 1\n"
" }\n"
" return last\n"
" }\n"
"\n"
" lastIndexOf(search, start) {\n"
" var last = -1\n"
" var index = 0\n"
" var searchSize = search.byteCount_\n"
" if (searchSize > start + searchSize) return -1\n"
" while (index <= start) {\n"
" var found = indexOf(search, index)\n"
" if (found == -1 || found > start) break\n"
" last = found\n"
" index = found + 1\n"
" }\n"
" return last\n"
" }\n"
"\n"
" isLower {\n"
" if (isEmpty) return false\n"
" var hasAlpha = false\n"
" for (b in bytes) {\n"
" if (b >= 65 && b <= 90) return false\n"
" if (b >= 97 && b <= 122) hasAlpha = true\n"
" }\n"
" return hasAlpha\n"
" }\n"
"\n"
" isUpper {\n"
" if (isEmpty) return false\n"
" var hasAlpha = false\n"
" for (b in bytes) {\n"
" if (b >= 97 && b <= 122) return false\n"
" if (b >= 65 && b <= 90) hasAlpha = true\n"
" }\n"
" return hasAlpha\n"
" }\n"
"\n"
" isDigit {\n"
" if (isEmpty) return false\n"
" for (b in bytes) {\n"
" if (b < 48 || b > 57) return false\n"
" }\n"
" return true\n"
" }\n"
"\n"
" isAlpha {\n"
" if (isEmpty) return false\n"
" for (b in bytes) {\n"
" if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122))) return false\n"
" }\n"
" return true\n"
" }\n"
"\n"
" isAlphaNumeric {\n"
" if (isEmpty) return false\n"
" for (b in bytes) {\n"
" if (!((b >= 65 && b <= 90) || (b >= 97 && b <= 122) || (b >= 48 && b <= 57))) return false\n"
" }\n"
" return true\n"
" }\n"
"\n"
" isSpace {\n"
" if (isEmpty) return false\n"
" for (b in bytes) {\n"
" if (b != 32 && b != 9 && b != 10 && b != 13 && b != 12 && b != 11) return false\n"
" }\n"
" return true\n"
" }\n"
"\n"
" isAscii {\n"
" for (b in bytes) {\n"
" if (b >= 128) return false\n"
" }\n"
" return true\n"
" }\n"
"\n"
" reverse {\n"
" var list = []\n"
" for (c in this) {\n"
" list.insert(0, c)\n"
" }\n"
" return list.join(\"\")\n"
" }\n"
"\n"
" center(width) { center(width, \" \") }\n"
"\n"
" center(width, char) {\n"
" if (!(width is Num) || !width.isInteger) {\n"
" Fiber.abort(\"Width must be an integer.\")\n"
" }\n"
" if (!(char is String) || char.isEmpty) {\n"
" Fiber.abort(\"Fill character must be a non-empty string.\")\n"
" }\n"
" var len = count\n"
" if (len >= width) return this\n"
" var total = width - len\n"
" var left = (total / 2).floor\n"
" var right = total - left\n"
" return (char * (left + 1))[0...left] + this + (char * (right + 1))[0...right]\n"
" }\n"
"\n"
" lpad(width, char) {\n"
" if (!(width is Num) || !width.isInteger) {\n"
" Fiber.abort(\"Width must be an integer.\")\n"
" }\n"
" if (!(char is String) || char.isEmpty) {\n"
" Fiber.abort(\"Fill character must be a non-empty string.\")\n"
" }\n"
" var len = count\n"
" if (len >= width) return this\n"
" var pad = width - len\n"
" return (char * (pad + 1))[0...pad] + this\n"
" }\n"
"\n"
" rpad(width, char) {\n"
" if (!(width is Num) || !width.isInteger) {\n"
" Fiber.abort(\"Width must be an integer.\")\n"
" }\n"
" if (!(char is String) || char.isEmpty) {\n"
" Fiber.abort(\"Fill character must be a non-empty string.\")\n"
" }\n"
" var len = count\n"
" if (len >= width) return this\n"
" var pad = width - len\n"
" return this + (char * (pad + 1))[0...pad]\n"
" }\n"
"\n"
" zfill(width) {\n"
" if (!(width is Num) || !width.isInteger) {\n"
" Fiber.abort(\"Width must be an integer.\")\n"
" }\n"
" var len = count\n"
" if (len >= width) return this\n"
" if (startsWith(\"-\") || startsWith(\"+\")) {\n"
" return this[0] + (\"0\" * (width - len)) + this[1..-1]\n"
" }\n"
" return (\"0\" * (width - len)) + this\n"
" }\n"
"\n"
" removePrefix(prefix) {\n"
" if (!(prefix is String)) {\n"
" Fiber.abort(\"Prefix must be a string.\")\n"
" }\n"
" if (startsWith(prefix)) return this[prefix.byteCount_..-1]\n"
" return this\n"
" }\n"
"\n"
" removeSuffix(suffix) {\n"
" if (!(suffix is String)) {\n"
" Fiber.abort(\"Suffix must be a string.\")\n"
" }\n"
" if (endsWith(suffix)) {\n"
" var end = byteCount_ - suffix.byteCount_ - 1\n"
" if (end < 0) return \"\"\n"
" return this[0..end]\n"
" }\n"
" return this\n"
" }\n"
"\n"
" splitLines { replace(\"\\r\\n\", \"\\n\").replace(\"\\r\", \"\\n\").split(\"\\n\") }\n"
"\n"
" chars {\n"
" var result = []\n"
" for (c in this) {\n"
" result.add(c)\n"
" }\n"
" return result\n"
" }\n"
"\n"
" toNum { Num.fromString(this) }\n"
"}\n"
"\n"
"class StringByteSequence is Sequence {\n"
@@ -441,18 +722,18 @@ static const char* coreModuleSource =
"\n"
"class System {\n"
" static print() {\n"
" writeString_(\"\n\")\n"
" writeString_(\"\\n\")\n"
" }\n"
"\n"
" static print(obj) {\n"
" writeObject_(obj)\n"
" writeString_(\"\n\")\n"
" writeString_(\"\\n\")\n"
" return obj\n"
" }\n"
"\n"
" static printAll(sequence) {\n"
" for (object in sequence) writeObject_(object)\n"
" writeString_(\"\n\")\n"
" writeString_(\"\\n\")\n"
" }\n"
"\n"
" static write(obj) {\n"
@@ -483,4 +764,3 @@ static const char* coreModuleSource =
" }\n"
" toString { \"attributes:%(_attributes) methods:%(_methods)\" }\n"
"}\n";