Port the duskscript language into the engine as src/dusk/script + duskscriptcc

Brings over the duskscript VM and compiler from dusk-vm: the runtime
(core/vm) lives under src/dusk/script, gated by DUSK_SCRIPT and wired into
the engine lifecycle with print()/consolePrint() routed to the console; the
standalone duskscriptcc compiler stays a lean host tool (linux/knulli only).

Collapses int/fixed/bignum into a single float-backed SCRIPT_VALUE_NUMBER
throughout the lexer, compiler, bytecode format and VM, removing the old
int16-rollover fast paths and the type-conversion builtins they needed.

Fixes a real big-endian bug in the bytecode's 32-bit number reader (found
while adding GameCube/Wii endian support via util/endian.h) and converts
src/dusk/script and duskscriptcc to dusk's memory/string/assert utilities,
which surfaced two more latent bugs: a conservative GC stack scan reading
its own local variable's address, and an uninitialized path buffer in
scriptReadScript when the caller path has no directory component.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
YourWishesandClaude Sonnet 5 committed 2026-10-14 12:59:46 -05:00
1 parent 3948607b1e
commit c1d6a97293
323 files changed
+18389

No files matched your search

+7
View File
@@ -17,6 +17,7 @@ cmake_policy(SET CMP0079 NEW)
option(DUSK_BUILD_TESTS "Enable tests" OFF) option(DUSK_BUILD_TESTS "Enable tests" OFF)
option(DUSK_NETWORK "Enable network support" ON) option(DUSK_NETWORK "Enable network support" ON)
option(DUSK_SCRIPT "Enable scripting support" ON)
set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name") set(DUSK_GAME_NAME "Dusk" CACHE STRING "Game display name")
set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder") set(DUSK_GAME_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
@@ -110,6 +111,12 @@ if(DUSK_NETWORK)
) )
endif() endif()
if(DUSK_SCRIPT)
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
DUSK_SCRIPT
)
endif()
# Toolchains # Toolchains
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake) include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
+10
View File
@@ -9,6 +9,16 @@ if(DUSK_NETWORK)
add_subdirectory(dusknetwork) add_subdirectory(dusknetwork)
endif() endif()
if(
DUSK_SCRIPT AND
(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
)
# The duskscript runtime itself lives in src/dusk/script, added
# unconditionally of platform there since it is plain, portable C; this is
# just the duskscriptcc compiler, a host tool only built on the dev machine.
add_subdirectory(duskscriptcc)
endif()
if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli") if(DUSK_TARGET_SYSTEM STREQUAL "linux" OR DUSK_TARGET_SYSTEM STREQUAL "knulli")
add_subdirectory(dusklinux) add_subdirectory(dusklinux)
add_subdirectory(dusksdl2) add_subdirectory(dusksdl2)
+3
View File
@@ -82,6 +82,9 @@ add_subdirectory(input)
add_subdirectory(locale) add_subdirectory(locale)
add_subdirectory(perf) add_subdirectory(perf)
add_subdirectory(rpg) add_subdirectory(rpg)
if(DUSK_SCRIPT)
add_subdirectory(script)
endif()
add_subdirectory(scene) add_subdirectory(scene)
add_subdirectory(system) add_subdirectory(system)
add_subdirectory(time) add_subdirectory(time)
+9
View File
@@ -20,6 +20,9 @@
#ifdef DUSK_NETWORK #ifdef DUSK_NETWORK
#include "network/network.h" #include "network/network.h"
#endif #endif
#ifdef DUSK_SCRIPT
#include "script/script.h"
#endif
#include "system/system.h" #include "system/system.h"
#include "console/console.h" #include "console/console.h"
#include "save/save.h" #include "save/save.h"
@@ -52,6 +55,9 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
#ifdef DUSK_NETWORK #ifdef DUSK_NETWORK
errorChain(networkInit()); errorChain(networkInit());
#endif #endif
#ifdef DUSK_SCRIPT
errorChain(scriptInit());
#endif
errorChain(sceneInit()); errorChain(sceneInit());
consolePrint("Engine initialized"); consolePrint("Engine initialized");
@@ -95,6 +101,9 @@ void engineExit(void) {
errorret_t engineDispose(void) { errorret_t engineDispose(void) {
errorChain(sceneDispose()); errorChain(sceneDispose());
#ifdef DUSK_SCRIPT
errorChain(scriptDispose());
#endif
#ifdef DUSK_NETWORK #ifdef DUSK_NETWORK
errorChain(networkDispose()); errorChain(networkDispose());
#endif #endif
+25
View File
@@ -0,0 +1,25 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Includes. core/ and vm/ are their own include roots: their own headers
# include each other as "value/scriptvalue.h", "execute/scriptvmcode.h" and so
# on, with no "script/" prefix, the same as duskscriptcc's copy of core/.
target_include_directories(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
${CMAKE_CURRENT_LIST_DIR}/core
${CMAKE_CURRENT_LIST_DIR}/vm
)
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC m)
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
script.c
scriptnativeconsole.c
)
# Subdirs
add_subdirectory(core)
add_subdirectory(vm)
+89
View File
@@ -0,0 +1,89 @@
# Copyright (c) 2026 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
target_sources(${DUSK_LIBRARY_TARGET_NAME}
PUBLIC
array/scriptarraycopy.c
array/scriptarraydeep.c
array/scriptarraymodify.c
array/scriptarraynew.c
array/scriptarraysort.c
builtin/array/scriptbarraybasic.c
builtin/array/scriptbarrayfunc.c
builtin/array/scriptbarraymodify.c
builtin/array/scriptbarrayorder.c
builtin/array/scriptbarraysearch.c
builtin/array/scriptbarrayslice.c
builtin/core/scriptbconvert.c
builtin/core/scriptbinclude.c
builtin/core/scriptbprint.c
builtin/core/scriptbpromise.c
builtin/core/scriptbtype.c
builtin/math/scriptbmathbasic.c
builtin/math/scriptbmathrandom.c
builtin/math/scriptbmathround.c
builtin/math/scriptbmathtrig.c
builtin/scriptarg.c
builtin/scriptbuiltinregister.c
builtin/scriptintrinsic.c
builtin/scriptnativesig.c
builtin/string/scriptbstringcase.c
builtin/string/scriptbstringsearch.c
builtin/string/scriptbstringsplit.c
builtin/string/scriptbstringtrim.c
bytecode/scriptbytecodesize.c
bytecode/scriptvarint.c
call/scriptcall.c
coroutine/scriptcoroutineready.c
coroutine/scriptcoroutinespawn.c
coroutine/scriptframelocate.c
coroutine/scriptscheduler.c
coroutine/scriptsetposition.c
coroutine/scriptstackcheck.c
error/scriptraise.c
error/scripttrace.c
gc/scriptgccollect.c
gc/scriptgcmark.c
gc/scriptgcscan.c
heap/scriptheapalloc.c
heap/scriptheapblock.c
heap/scriptheapfree.c
heap/scriptheapinit.c
heap/scriptheapsweep.c
number/scriptnumberbinary.c
number/scriptnumberformat.c
number/scriptnumberparse.c
number/scriptnumberunary.c
ops/scriptbinaryop.c
ops/scriptindex.c
ops/scriptref.c
promise/scriptpromiseawait.c
promise/scriptpromisecombine.c
promise/scriptpromisenew.c
promise/scriptpromisesettle.c
promise/scripttimer.c
runtime/scriptruntimeconfig.c
runtime/scriptruntimecreate.c
runtime/scriptruntimeexecute.c
runtime/scriptruntimeload.c
runtime/scriptruntimerun.c
scope/scriptscopefind.c
scope/scriptscopenew.c
scope/scriptscopeset.c
scope/scriptvariable.c
string/scriptbuf.c
string/scriptstringchar.c
string/scriptstringconcat.c
string/scriptstringnew.c
string/scriptstringreject.c
string/scriptstringsearch.c
struct/scriptstruct.c
text/scripttext.c
value/scriptalloc.c
value/scriptfunction.c
value/scriptvaluecompare.c
value/scriptvalueformat.c
value/scriptvalueprimitive.c
)
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "array/scriptarraycopy.h"
#include "array/scriptarraynew.h"
#include "array/scriptarraymodify.h"
#include "value/scriptfunction.h"
scriptvalue_t scriptArrayCopy(scriptruntime_t *runtime, const scriptarray_t *source) {
scriptvalue_t result = scriptArrayNew(runtime);
uint16_t i;
for(i = 0; i < source->length; i++) scriptArrayPushRaw(runtime, result.as.array, source->items[i]);
result.as.array->category = source->category;
return result;
}
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ARRAY_SCRIPTARRAYCOPY_H
#define SCRIPT_ARRAY_SCRIPTARRAYCOPY_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
/**
* Copies the contents of one array into a new array with the same category.
*
* @param runtime The runtime.
* @param source The array to copy.
* @returns The new array value.
*/
scriptvalue_t scriptArrayCopy(scriptruntime_t *runtime, const scriptarray_t *source);
#endif
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "array/scriptarraydeep.h"
#include "array/scriptarraynew.h"
#include "array/scriptarraymodify.h"
#include "error/scriptraise.h"
#include "struct/scriptstruct.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptfunction.h"
#define SCRIPT_DEEP_MAX 16
scriptvalue_t scriptDeepCopy(scriptruntime_t *runtime, const scriptvalue_t value, const uint8_t depth) {
scriptvalue_t result;
uint16_t i;
if(value.type != SCRIPT_VALUE_ARRAY && value.type != SCRIPT_VALUE_STRUCT) return value;
if(depth >= SCRIPT_DEEP_MAX) scriptRaise(runtime, "Too deeply nested to copy, or it contains itself");
if(value.type == SCRIPT_VALUE_ARRAY) {
result = scriptArrayNew(runtime);
for(i = 0; i < value.as.array->length; i++) {
scriptArrayPushRaw(runtime, result.as.array, scriptDeepCopy(runtime, value.as.array->items[i], depth + 1));
}
result.as.array->category = value.as.array->category;
return result;
}
result = scriptStructNew(runtime);
for(i = 0; i < value.as.structure->count; i++) {
scriptStructSet(
runtime,
result.as.structure,
value.as.structure->keys[i]->data,
value.as.structure->keys[i]->length,
scriptDeepCopy(runtime, value.as.structure->values[i], depth + 1)
);
}
return result;
}
bool scriptDeepEqual(const scriptvalue_t a, const scriptvalue_t b, const uint8_t depth) {
uint16_t i;
int slot;
if(depth >= SCRIPT_DEEP_MAX) return false;
if(a.type == SCRIPT_VALUE_ARRAY && b.type == SCRIPT_VALUE_ARRAY) {
if(a.as.array->length != b.as.array->length) return false;
for(i = 0; i < a.as.array->length; i++) {
if(!scriptDeepEqual(a.as.array->items[i], b.as.array->items[i], depth + 1)) return false;
}
return true;
}
if(a.type == SCRIPT_VALUE_STRUCT && b.type == SCRIPT_VALUE_STRUCT) {
if(a.as.structure->count != b.as.structure->count) return false;
for(i = 0; i < a.as.structure->count; i++) {
slot = scriptStructFind(b.as.structure, a.as.structure->keys[i]->data, a.as.structure->keys[i]->length);
if(slot < 0) return false;
if(!scriptDeepEqual(a.as.structure->values[i], b.as.structure->values[slot], depth + 1)) return false;
}
return true;
}
return scriptValuesEqual(a, b);
}
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ARRAY_SCRIPTARRAYDEEP_H
#define SCRIPT_ARRAY_SCRIPTARRAYDEEP_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
/**
* Copies arrays and structs all the way down.
*
* @param runtime The runtime.
* @param value The value to copy.
* @param depth How deep the copy already is.
* @returns The copy.
*/
scriptvalue_t scriptDeepCopy(scriptruntime_t *runtime, const scriptvalue_t value, const uint8_t depth);
/**
* Whether two values are deeply equal: arrays and structs are compared by what
* they hold.
*
* @param a First value.
* @param b Second value.
* @param depth How deep the comparison already is.
* @returns True when they match.
*/
bool scriptDeepEqual(const scriptvalue_t a, const scriptvalue_t b, const uint8_t depth);
#endif
@@ -0,0 +1,89 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "array/scriptarraymodify.h"
#include "value/scriptalloc.h"
#include "array/scriptarraynew.h"
#include "error/scriptraise.h"
#include "value/scriptfunction.h"
#include <string.h>
void scriptArrayPush(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value) {
if(array->frozen) scriptRaise(runtime, "Can not change a constant");
scriptArrayCheck(runtime, array, value);
scriptArrayPushRaw(runtime, array, value);
}
void scriptArrayReserve(scriptruntime_t *runtime, scriptarray_t *array, const uint16_t capacity) {
scriptvalue_t *items;
if(capacity <= array->capacity) return;
if(capacity > SCRIPT_ARRAY_LENGTH_MAX) scriptRaise(runtime, "Arrays hold up to %d elements", SCRIPT_ARRAY_LENGTH_MAX);
items = scriptAllocKind(runtime, sizeof(scriptvalue_t) * capacity, SCRIPT_HEAP_KIND_ITEMS);
if(array->length) memoryCopy(items, array->items, sizeof(scriptvalue_t) * array->length);
scriptFree(runtime, array->items);
array->items = items;
array->capacity = capacity;
}
void scriptArrayPushRaw(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value) {
if(array->frozen) scriptRaise(runtime, "Can not change a constant");
if(array->length >= SCRIPT_ARRAY_LENGTH_MAX) {
scriptRaise(runtime, "Arrays hold up to %d elements", SCRIPT_ARRAY_LENGTH_MAX);
}
if(array->length >= array->capacity) {
uint16_t capacity = array->capacity ? (uint16_t)(array->capacity * 2) : 4;
scriptvalue_t *items;
if(capacity > SCRIPT_ARRAY_LENGTH_MAX) capacity = SCRIPT_ARRAY_LENGTH_MAX;
items = scriptAllocKind(runtime, sizeof(scriptvalue_t) * capacity, SCRIPT_HEAP_KIND_ITEMS);
if(array->length) memoryCopy(items, array->items, sizeof(scriptvalue_t) * array->length);
scriptFree(runtime, array->items);
array->items = items;
array->capacity = capacity;
}
array->items[array->length++] = value;
}
void scriptArrayInsert(scriptruntime_t *runtime, scriptarray_t *array, const uint16_t index, const scriptvalue_t value) {
uint16_t i;
scriptArrayCheck(runtime, array, value);
scriptArrayPushRaw(runtime, array, value);
for(i = (uint16_t)(array->length - 1); i > index; i--) {
array->items[i] = array->items[i - 1];
}
array->items[index] = value;
}
scriptvalue_t scriptArrayRemove(scriptarray_t *array, const uint16_t index) {
scriptvalue_t removed = array->items[index];
uint16_t i;
for(i = index; i + 1 < array->length; i++) array->items[i] = array->items[i + 1];
array->length--;
return removed;
}
void scriptFreeze(scriptruntime_t *runtime, const scriptvalue_t value) {
uint16_t i;
if(value.type == SCRIPT_VALUE_ARRAY) {
for(i = 0; i < value.as.array->length; i++) scriptRequireConstant(runtime, value.as.array->items[i]);
value.as.array->frozen = true;
} else if(value.type == SCRIPT_VALUE_STRUCT) {
for(i = 0; i < value.as.structure->count; i++) scriptRequireConstant(runtime, value.as.structure->values[i]);
value.as.structure->frozen = true;
}
}
void scriptRequireConstant(scriptruntime_t *runtime, const scriptvalue_t value) {
if((value.type == SCRIPT_VALUE_ARRAY && !value.as.array->frozen) || (value.type == SCRIPT_VALUE_STRUCT && !value.as.structure->frozen)) {
scriptRaise(runtime, "A constant can only hold arrays and structs that are constants");
}
}
@@ -0,0 +1,79 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ARRAY_SCRIPTARRAYMODIFY_H
#define SCRIPT_ARRAY_SCRIPTARRAYMODIFY_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
/**
* Adds a value on the end of an array, with all the checks.
*
* @param runtime The runtime.
* @param array The array.
* @param value The value to add.
*/
void scriptArrayPush(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value);
/**
* Makes room in an array for as many items as it is going to have, so adding them does not have to make the room
* again and again. Nothing is done when there is the room.
*
* @param runtime The runtime.
* @param array The array.
* @param capacity How many items.
*/
void scriptArrayReserve(scriptruntime_t *runtime, scriptarray_t *array, const uint16_t capacity);
/**
* Adds a value on the end without checking its type, for results made by the
* runtime itself. The length limit still applies.
*
* @param runtime The runtime.
* @param array The array.
* @param value The value to add.
*/
void scriptArrayPushRaw(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value);
/**
* Inserts a value in an array, the index may be the length to add on the end.
*
* @param runtime The runtime.
* @param array The array.
* @param index Where the value goes.
* @param value The value to insert.
*/
void scriptArrayInsert(scriptruntime_t *runtime, scriptarray_t *array, const uint16_t index, const scriptvalue_t value);
/**
* Removes a value from an array and returns it.
*
* @param array The array.
* @param index The index to remove, must be in bounds.
* @returns The removed value.
*/
scriptvalue_t scriptArrayRemove(scriptarray_t *array, const uint16_t index);
/**
* Makes an array or a struct a constant, which nothing changes after. What it holds that is an
* array or a struct has to be one already. Anything else is left as it is.
*
* @param runtime The runtime.
* @param value The value.
*/
void scriptFreeze(scriptruntime_t *runtime, const scriptvalue_t value);
/**
* Fails when a value is an array or a struct that is not a constant.
*
* @param runtime The runtime.
* @param value The value.
*/
void scriptRequireConstant(scriptruntime_t *runtime, const scriptvalue_t value);
#endif
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "array/scriptarraynew.h"
#include "value/scriptalloc.h"
#include "error/scriptraise.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptfunction.h"
scriptvalue_t scriptArrayNew(scriptruntime_t *runtime) {
scriptvalue_t value = { .type = SCRIPT_VALUE_ARRAY };
value.as.array = scriptAllocKind(runtime, sizeof(scriptarray_t), SCRIPT_HEAP_KIND_ARRAY);
value.as.array->category = SCRIPT_CATEGORY_UNKNOWN;
return value;
}
scriptcategory_t scriptCategoryOf(const scriptvalue_t value) {
switch(value.type) {
case SCRIPT_VALUE_NUMBER: return SCRIPT_CATEGORY_NUMBER;
case SCRIPT_VALUE_BOOL: return SCRIPT_CATEGORY_BOOL;
case SCRIPT_VALUE_STRING: return SCRIPT_CATEGORY_STRING;
case SCRIPT_VALUE_ARRAY: return SCRIPT_CATEGORY_ARRAY;
case SCRIPT_VALUE_STRUCT: return SCRIPT_CATEGORY_STRUCT;
case SCRIPT_VALUE_FUNCTION: return SCRIPT_CATEGORY_FUNCTION;
case SCRIPT_VALUE_PROMISE: return SCRIPT_CATEGORY_PROMISE;
default: return SCRIPT_CATEGORY_UNKNOWN;
}
}
void scriptArrayCheck(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value) {
scriptcategory_t category;
if(value.type == SCRIPT_VALUE_UNDEFINED) {
scriptRaise(runtime, "Arrays can not hold undefined");
}
if(value.type == SCRIPT_VALUE_NULL) {
if(array->category == SCRIPT_CATEGORY_NUMBER || array->category == SCRIPT_CATEGORY_BOOL) {
scriptRaise(runtime, "Numbers and bools can not be null, so an array of them can not hold null");
}
return;
}
category = scriptCategoryOf(value);
if(array->category == SCRIPT_CATEGORY_UNKNOWN) {
array->category = category;
} else if(array->category != category) {
scriptRaise(runtime, "Arrays can not mix types, tried to add %s to an array of another type", scriptTypeName(value));
}
}
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ARRAY_SCRIPTARRAYNEW_H
#define SCRIPT_ARRAY_SCRIPTARRAYNEW_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
#include "value/scriptarray.h"
/**
* Makes an empty array that can grow up to the array limit.
*
* @param runtime The runtime.
* @returns The array value.
*/
scriptvalue_t scriptArrayNew(scriptruntime_t *runtime);
/**
* Which kind of thing an array can hold a value as.
*
* @param value Value to look at.
* @returns The category, SCRIPT_CATEGORY_UNKNOWN for null and undefined.
*/
scriptcategory_t scriptCategoryOf(const scriptvalue_t value);
/**
* Checks a value can go in an array and sets the array's category from the
* first value placed in it. Mixed types, undefined, and null in an array of
* numbers or bools are fatal errors.
*
* @param runtime The runtime.
* @param array The array.
* @param value The value about to be added.
*/
void scriptArrayCheck(scriptruntime_t *runtime, scriptarray_t *array, const scriptvalue_t value);
#endif
@@ -0,0 +1,102 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "array/scriptarraysort.h"
#include "value/scriptalloc.h"
#include "call/scriptcall.h"
#include "string/scriptstringchar.h"
#include "value/scriptvalueprimitive.h"
#include "number/scriptnumberunary.h"
#include "error/scriptraise.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptfunction.h"
#include <string.h>
int scriptCompareValues(scriptruntime_t *runtime, const scriptvalue_t a, const scriptvalue_t b, const scriptvalue_t comparer) {
scriptvalue_t args[2];
scriptvalue_t result;
float number;
size_t i;
size_t shortest;
char x;
char y;
if(comparer.type == SCRIPT_VALUE_FUNCTION) {
args[0] = a;
args[1] = b;
result = scriptCallFunction(runtime, comparer, args, 2);
if(!scriptIsNumber(result)) scriptRaise(runtime, "The comparison function has to return a number, got %s", scriptTypeName(result));
number = result.as.number;
return number < 0 ? -1 : (number > 0 ? 1 : 0);
}
if(scriptIsNumber(a) && scriptIsNumber(b)) return scriptNumberCompare(a, b);
// Strings sort alphabetically, ignoring case, with case deciding ties
if(a.type == SCRIPT_VALUE_STRING && b.type == SCRIPT_VALUE_STRING) {
shortest = a.as.string->length < b.as.string->length ? a.as.string->length : b.as.string->length;
for(i = 0; i < shortest; i++) {
x = scriptCharLower(a.as.string->data[i]);
y = scriptCharLower(b.as.string->data[i]);
if(x != y) return (unsigned char)x < (unsigned char)y ? -1 : 1;
}
if(a.as.string->length != b.as.string->length) return a.as.string->length < b.as.string->length ? -1 : 1;
for(i = 0; i < shortest; i++) {
if(a.as.string->data[i] != b.as.string->data[i]) {
return (unsigned char)a.as.string->data[i] < (unsigned char)b.as.string->data[i] ? -1 : 1;
}
}
return 0;
}
scriptRaise(
runtime,
"Only numbers and strings can be sorted without a comparison function, got %s and %s",
scriptTypeName(a),
scriptTypeName(b)
);
return 0;
}
void scriptSortValues(
scriptruntime_t *runtime,
scriptvalue_t *items,
const uint16_t count,
const bool descending,
const scriptvalue_t comparer
) {
scriptvalue_t *temp;
uint16_t width;
uint16_t start;
if(count < 2) return;
temp = scriptAlloc(runtime, sizeof(scriptvalue_t) * count);
for(width = 1; width < count; width *= 2) {
for(start = 0; start < count; start += 2 * width) {
uint16_t middle = start + width < count ? start + width : count;
uint16_t end = start + 2 * width < count ? start + 2 * width : count;
uint16_t left = start;
uint16_t right = middle;
uint16_t out = start;
while(left < middle && right < end) {
int order = scriptCompareValues(runtime, items[left], items[right], comparer);
if(descending) order = -order;
if(order <= 0) temp[out++] = items[left++];
else temp[out++] = items[right++];
}
while(left < middle) temp[out++] = items[left++];
while(right < end) temp[out++] = items[right++];
}
memoryCopy(items, temp, sizeof(scriptvalue_t) * count);
}
scriptFree(runtime, temp);
}
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ARRAY_SCRIPTARRAYSORT_H
#define SCRIPT_ARRAY_SCRIPTARRAYSORT_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
/**
* Compares two values for sorting.
*
* @param runtime The runtime.
* @param a First value.
* @param b Second value.
* @param comparer A comparison function or undefined.
* @returns Negative, zero or positive.
*/
int scriptCompareValues(scriptruntime_t *runtime, const scriptvalue_t a, const scriptvalue_t b, const scriptvalue_t comparer);
/**
* Sorts values in place, a stable merge sort.
*
* @param runtime The runtime.
* @param items The values.
* @param count Number of values.
* @param descending Whether to sort biggest first.
* @param comparer A function(a, b) returning a number, or undefined to compare
* numbers and strings directly.
*/
void scriptSortValues(
scriptruntime_t *runtime,
scriptvalue_t *items,
const uint16_t count,
const bool descending,
const scriptvalue_t comparer
);
#endif
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/array/scriptbarraybasic.h"
#include "builtin/scriptarg.h"
#include "array/scriptarraycopy.h"
#include "array/scriptarraymodify.h"
#include "value/scriptvalueprimitive.h"
#include "array/scriptarraydeep.h"
#include "error/scriptraise.h"
#include "string/scriptstringreject.h"
#include "value/scriptvaluecompare.h"
SCRIPT_NATIVE(scriptBLen) {
scriptArgCount(runtime, argc, 1, 1);
if(args[0].type == SCRIPT_VALUE_ARRAY) return scriptNumber((float)args[0].as.array->length);
if(args[0].type == SCRIPT_VALUE_STRING) return scriptNumber((float)args[0].as.string->length);
scriptRaise(runtime, "len needs an array or a string, got %s", scriptTypeName(args[0]));
return scriptUndefined();
}
SCRIPT_NATIVE(scriptBPush) {
scriptarray_t *array;
scriptArgCount(runtime, argc, 2, 2);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
scriptArrayPush(runtime, array, args[1]);
return scriptNumber((float)array->length);
}
SCRIPT_NATIVE(scriptBPop) {
scriptarray_t *array;
scriptArgCount(runtime, argc, 1, 1);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
if(array->length == 0) return scriptUndefined();
return scriptArrayRemove(array, (uint16_t)(array->length - 1));
}
SCRIPT_NATIVE(scriptBShift) {
scriptarray_t *array;
scriptArgCount(runtime, argc, 1, 1);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
if(array->length == 0) return scriptUndefined();
return scriptArrayRemove(array, 0);
}
SCRIPT_NATIVE(scriptBUnshift) {
scriptarray_t *array;
scriptArgCount(runtime, argc, 2, 2);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
scriptArrayInsert(runtime, array, 0, args[1]);
return scriptNumber((float)array->length);
}
SCRIPT_NATIVE(scriptBEmpty) {
scriptarray_t *array;
scriptArgCount(runtime, argc, 1, 1);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
array->length = 0;
array->category = SCRIPT_CATEGORY_UNKNOWN;
return args[0];
}
SCRIPT_NATIVE(scriptBClone) {
scriptArgCount(runtime, argc, 1, 1);
if(args[0].type == SCRIPT_VALUE_STRING) return args[0];
return scriptArrayCopy(runtime, scriptArgArray(runtime, args, argc, 0));
}
SCRIPT_NATIVE(scriptBDeepClone) {
scriptArgCount(runtime, argc, 1, 1);
return scriptDeepCopy(runtime, args[0], 0);
}
SCRIPT_NATIVE(scriptBDeepEqual) {
scriptArgCount(runtime, argc, 2, 2);
return scriptBool(scriptDeepEqual(args[0], args[1], 0));
}
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYBASIC_H
#define SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYBASIC_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
// Arrays
SCRIPT_NATIVE(scriptBLen);
SCRIPT_NATIVE(scriptBPush);
SCRIPT_NATIVE(scriptBPop);
SCRIPT_NATIVE(scriptBShift);
SCRIPT_NATIVE(scriptBUnshift);
SCRIPT_NATIVE(scriptBEmpty);
SCRIPT_NATIVE(scriptBClone);
SCRIPT_NATIVE(scriptBDeepClone);
SCRIPT_NATIVE(scriptBDeepEqual);
#endif
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/array/scriptbarrayfunc.h"
#include "builtin/scriptarg.h"
#include "array/scriptarraynew.h"
#include "array/scriptarraymodify.h"
#include "call/scriptcall.h"
#include "value/scriptvalueprimitive.h"
#include "value/scriptfunction.h"
SCRIPT_NATIVE(scriptBMap) {
scriptarray_t *array;
scriptvalue_t result = scriptArrayNew(runtime);
scriptvalue_t callArgs[3];
uint16_t i;
scriptArgCount(runtime, argc, 2, 2);
array = scriptArgArray(runtime, args, argc, 0);
scriptArgFunction(runtime, args, argc, 1);
for(i = 0; i < array->length; i++) {
callArgs[0] = array->items[i];
callArgs[1] = scriptNumber((float)i);
callArgs[2] = args[0];
scriptArrayPush(runtime, result.as.array, scriptCallFunction(runtime, args[1], callArgs, 3));
}
return result;
}
SCRIPT_NATIVE(scriptBReduce) {
scriptarray_t *array;
scriptvalue_t accumulator;
scriptvalue_t callArgs[4];
uint16_t i;
scriptArgCount(runtime, argc, 3, 3);
array = scriptArgArray(runtime, args, argc, 0);
scriptArgFunction(runtime, args, argc, 1);
accumulator = args[2];
for(i = 0; i < array->length; i++) {
callArgs[0] = accumulator;
callArgs[1] = array->items[i];
callArgs[2] = scriptNumber((float)i);
callArgs[3] = args[0];
accumulator = scriptCallFunction(runtime, args[1], callArgs, 4);
}
return accumulator;
}
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYFUNC_H
#define SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYFUNC_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBMap);
SCRIPT_NATIVE(scriptBReduce);
#endif
@@ -0,0 +1,102 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/array/scriptbarraymodify.h"
#include "builtin/scriptarg.h"
#include "array/scriptarraymodify.h"
#include "array/scriptarraynew.h"
#include "value/scriptvalueprimitive.h"
#include "error/scriptraise.h"
#include "string/scriptstringreject.h"
#include "value/scriptfunction.h"
SCRIPT_NATIVE(scriptBFill) {
scriptarray_t *array;
int32_t count;
int32_t i;
scriptArgCount(runtime, argc, 3, 3);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
count = scriptArgInteger(runtime, args, argc, 2);
if(count < 0 || count > SCRIPT_ARRAY_LENGTH_MAX) scriptRaise(runtime, "fill needs a count from 0 to %d", SCRIPT_ARRAY_LENGTH_MAX);
array->length = 0;
array->category = SCRIPT_CATEGORY_UNKNOWN;
for(i = 0; i < count; i++) scriptArrayPush(runtime, array, args[1]);
return args[0];
}
SCRIPT_NATIVE(scriptBMerge) {
scriptarray_t *array;
scriptarray_t *other;
uint16_t count;
uint16_t i;
scriptArgCount(runtime, argc, 2, 2);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
other = scriptArgArray(runtime, args, argc, 1);
count = other->length;
for(i = 0; i < count; i++) scriptArrayPush(runtime, array, other->items[i]);
return scriptNumber((float)array->length);
}
SCRIPT_NATIVE(scriptBSplice) {
scriptarray_t *array;
scriptvalue_t removed;
int32_t start;
int32_t count = 1;
int32_t i;
scriptArgCount(runtime, argc, 2, 3);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
start = scriptArgInteger(runtime, args, argc, 1);
if(argc > 2) count = scriptArgInteger(runtime, args, argc, 2);
if(start < 0 || start > array->length) scriptRaise(runtime, "Index %d is out of bounds, the array holds %u elements", (int)start, (unsigned int)array->length);
if(count < 0) scriptRaise(runtime, "splice can not remove a negative number of elements");
if(count > array->length - start) count = array->length - start;
removed = scriptArrayNew(runtime);
for(i = 0; i < count; i++) scriptArrayPushRaw(runtime, removed.as.array, array->items[start + i]);
removed.as.array->category = array->category;
for(i = 0; i < count; i++) scriptArrayRemove(array, (uint16_t)start);
return removed;
}
SCRIPT_NATIVE(scriptBInsert) {
scriptarray_t *array;
int32_t index;
scriptArgCount(runtime, argc, 3, 3);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
index = scriptArgInteger(runtime, args, argc, 1);
if(index < 0 || index > array->length) scriptRaise(runtime, "Index %d is out of bounds, the array holds %u elements", (int)index, (unsigned int)array->length);
scriptArrayInsert(runtime, array, (uint16_t)index, args[2]);
return scriptNumber((float)array->length);
}
SCRIPT_NATIVE(scriptBInsertArr) {
scriptarray_t *array;
scriptarray_t *other;
int32_t index;
uint16_t count;
uint16_t i;
scriptArgCount(runtime, argc, 3, 3);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
index = scriptArgInteger(runtime, args, argc, 1);
other = scriptArgArray(runtime, args, argc, 2);
if(index < 0 || index > array->length) scriptRaise(runtime, "Index %d is out of bounds, the array holds %u elements", (int)index, (unsigned int)array->length);
count = other->length;
for(i = 0; i < count; i++) scriptArrayInsert(runtime, array, (uint16_t)(index + i), other->items[i]);
return scriptNumber((float)array->length);
}
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYMODIFY_H
#define SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYMODIFY_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBFill);
SCRIPT_NATIVE(scriptBMerge);
SCRIPT_NATIVE(scriptBSplice);
SCRIPT_NATIVE(scriptBInsert);
SCRIPT_NATIVE(scriptBInsertArr);
#endif
@@ -0,0 +1,58 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/array/scriptbarrayorder.h"
#include "builtin/scriptarg.h"
#include "builtin/math/scriptbmathrandom.h"
#include "string/scriptstringreject.h"
#include "array/scriptarraysort.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptvalueprimitive.h"
#include "value/scriptfunction.h"
SCRIPT_NATIVE(scriptBSort) {
scriptarray_t *array;
scriptvalue_t comparer = scriptUndefined();
scriptArgCount(runtime, argc, 1, 3);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
if(argc > 2) comparer = scriptArgFunction(runtime, args, argc, 2);
scriptSortValues(runtime, array->items, array->length, argc > 1 && scriptTruthy(args[1]), comparer);
return args[0];
}
SCRIPT_NATIVE(scriptBReverse) {
scriptarray_t *array;
uint16_t i;
scriptArgCount(runtime, argc, 1, 1);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
for(i = 0; i < array->length / 2; i++) {
scriptvalue_t swap = array->items[i];
array->items[i] = array->items[array->length - 1 - i];
array->items[array->length - 1 - i] = swap;
}
return args[0];
}
SCRIPT_NATIVE(scriptBShuffle) {
scriptarray_t *array;
uint16_t i;
scriptArgCount(runtime, argc, 1, 1);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
for(i = array->length; i > 1; i--) {
uint16_t pick = (uint16_t)(scriptRandomNext(runtime) % i);
scriptvalue_t swap = array->items[i - 1];
array->items[i - 1] = array->items[pick];
array->items[pick] = swap;
}
return args[0];
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYORDER_H
#define SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYORDER_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBSort);
SCRIPT_NATIVE(scriptBReverse);
SCRIPT_NATIVE(scriptBShuffle);
#endif
@@ -0,0 +1,95 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/array/scriptbarraysearch.h"
#include "value/scriptalloc.h"
#include "builtin/scriptarg.h"
#include "call/scriptcall.h"
#include "value/scriptvalueprimitive.h"
#include "error/scriptraise.h"
#include "string/scriptstringreject.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptfunction.h"
SCRIPT_NATIVE(scriptBIndexOf) {
uint16_t i;
scriptArgCount(runtime, argc, 2, 2);
if(args[0].type == SCRIPT_VALUE_STRING) {
scriptstring_t *needle = scriptArgString(runtime, args, argc, 1);
if(needle->length != 1) scriptRaise(runtime, "On a string indexof finds a single char, use strindex for a piece of text");
for(i = 0; i < args[0].as.string->length; i++) {
if(args[0].as.string->data[i] == needle->data[0]) return scriptNumber((float)i);
}
return scriptNumber(-1);
}
{
scriptarray_t *array = scriptArgArray(runtime, args, argc, 0);
for(i = 0; i < array->length; i++) {
if(scriptValuesEqual(array->items[i], args[1])) return scriptNumber((float)i);
}
}
return scriptNumber(-1);
}
SCRIPT_NATIVE(scriptBLastIndexOf) {
int32_t i;
scriptArgCount(runtime, argc, 2, 2);
if(args[0].type == SCRIPT_VALUE_STRING) {
scriptstring_t *needle = scriptArgString(runtime, args, argc, 1);
if(needle->length != 1) scriptRaise(runtime, "On a string lastindexof finds a single char");
for(i = (int32_t)args[0].as.string->length - 1; i >= 0; i--) {
if(args[0].as.string->data[i] == needle->data[0]) return scriptNumber((float)i);
}
return scriptNumber(-1);
}
{
scriptarray_t *array = scriptArgArray(runtime, args, argc, 0);
for(i = (int32_t)array->length - 1; i >= 0; i--) {
if(scriptValuesEqual(array->items[i], args[1])) return scriptNumber((float)i);
}
}
return scriptNumber(-1);
}
SCRIPT_NATIVE(scriptBFilter) {
scriptarray_t *array;
bool *keep;
uint16_t length;
uint16_t i;
uint16_t kept = 0;
scriptvalue_t callArgs[3];
scriptArgCount(runtime, argc, 1, 2);
scriptRejectString(runtime, args[0]);
array = scriptArgArrayWritable(runtime, args, argc, 0);
if(argc > 1) scriptArgFunction(runtime, args, argc, 1);
length = array->length;
keep = scriptAlloc(runtime, sizeof(bool) * (length ? length : 1));
for(i = 0; i < length; i++) {
if(argc > 1) {
callArgs[0] = array->items[i];
callArgs[1] = scriptNumber((float)i);
callArgs[2] = args[0];
keep[i] = scriptTruthy(scriptCallFunction(runtime, args[1], callArgs, 3));
} else {
keep[i] = array->items[i].type != SCRIPT_VALUE_NULL;
}
}
for(i = 0; i < length; i++) {
if(keep[i]) array->items[kept++] = array->items[i];
}
array->length = kept;
scriptFree(runtime, keep);
return args[0];
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYSEARCH_H
#define SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYSEARCH_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBIndexOf);
SCRIPT_NATIVE(scriptBLastIndexOf);
SCRIPT_NATIVE(scriptBFilter);
#endif
@@ -0,0 +1,100 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/array/scriptbarrayslice.h"
#include "builtin/scriptarg.h"
#include "array/scriptarraynew.h"
#include "array/scriptarraymodify.h"
#include "string/scriptbuf.h"
#include "error/scriptraise.h"
#include "string/scriptstringnew.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptvalueformat.h"
#include "value/scriptfunction.h"
SCRIPT_NATIVE(scriptBSubarr) {
int32_t start;
int32_t end;
int32_t length;
uint16_t i;
scriptvalue_t result;
scriptArgCount(runtime, argc, 2, 3);
if(args[0].type == SCRIPT_VALUE_STRING) {
length = args[0].as.string->length;
} else {
length = scriptArgArray(runtime, args, argc, 0)->length;
}
start = scriptArgInteger(runtime, args, argc, 1);
end = argc > 2 ? scriptArgInteger(runtime, args, argc, 2) : length;
if(start < 0 || start > length || end < start || end > length) {
scriptRaise(runtime, "The range %d to %d is out of bounds, there are %d elements", (int)start, (int)end, (int)length);
}
if(args[0].type == SCRIPT_VALUE_STRING) {
// The offset is 16 bits, the compiler for the SNES adds a 32 bit one to a pointer wrongly
const char *text = args[0].as.string->data;
uint16_t from = (uint16_t)start;
uint16_t count = (uint16_t)(end - start);
return scriptStringNew(runtime, text + from, count);
}
result = scriptArrayNew(runtime);
for(i = (uint16_t)start; i < end; i++) scriptArrayPushRaw(runtime, result.as.array, args[0].as.array->items[i]);
result.as.array->category = args[0].as.array->category;
return result;
}
SCRIPT_NATIVE(scriptBFlatten) {
scriptarray_t *array;
scriptvalue_t result = scriptArrayNew(runtime);
scriptcategory_t category = SCRIPT_CATEGORY_UNKNOWN;
uint16_t i;
uint16_t j;
scriptArgCount(runtime, argc, 1, 1);
array = scriptArgArray(runtime, args, argc, 0);
for(i = 0; i < array->length; i++) {
scriptarray_t *inner;
if(array->items[i].type != SCRIPT_VALUE_ARRAY) {
scriptRaise(runtime, "flatten needs an array of arrays, found %s", scriptTypeName(array->items[i]));
}
inner = array->items[i].as.array;
if(inner->category != SCRIPT_CATEGORY_UNKNOWN) {
if(category == SCRIPT_CATEGORY_UNKNOWN) category = inner->category;
else if(category != inner->category) scriptRaise(runtime, "flatten only works on arrays that all hold the same type");
}
for(j = 0; j < inner->length; j++) scriptArrayPushRaw(runtime, result.as.array, inner->items[j]);
}
result.as.array->category = category;
return result;
}
SCRIPT_NATIVE(scriptBJoin) {
scriptarray_t *array;
scriptstring_t *separator;
scriptbuf_t buffer;
scriptvalue_t result;
uint16_t i;
scriptArgCount(runtime, argc, 2, 2);
array = scriptArgArray(runtime, args, argc, 0);
separator = scriptArgString(runtime, args, argc, 1);
scriptBufInit(&buffer);
for(i = 0; i < array->length; i++) {
if(i) scriptBufAppend(runtime, &buffer, separator->data, separator->length);
scriptValueFormat(runtime, &buffer, array->items[i], false);
}
result = scriptStringNew(runtime, buffer.data ? buffer.data : "", buffer.length);
scriptBufFree(runtime, &buffer);
return result;
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYSLICE_H
#define SCRIPT_BUILTIN_ARRAY_SCRIPTBARRAYSLICE_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBSubarr);
SCRIPT_NATIVE(scriptBFlatten);
SCRIPT_NATIVE(scriptBJoin);
#endif
@@ -0,0 +1,120 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "builtin/core/scriptbconvert.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#include "string/scriptbuf.h"
#include "number/scriptnumberformat.h"
#include "number/scriptnumberparse.h"
#include "error/scriptraise.h"
#include "string/scriptstringnew.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptvalueformat.h"
#include "value/scriptfunction.h"
#include <math.h>
#include <string.h>
SCRIPT_NATIVE(scriptBToStr) {
scriptbuf_t buffer;
scriptvalue_t result;
int decimals = -1;
scriptArgCount(runtime, argc, 1, 2);
if(argc > 1) {
if(!scriptIsNumber(args[0])) scriptRaise(runtime, "Only numbers can have decimal places, got %s", scriptTypeName(args[0]));
decimals = (int)scriptArgInteger(runtime, args, argc, 1);
if(decimals < 0) scriptRaise(runtime, "Decimal places can not be negative");
}
if(args[0].type == SCRIPT_VALUE_STRING) return args[0];
scriptBufInit(&buffer);
if(scriptIsNumber(args[0])) scriptNumberFormat(runtime, &buffer, args[0], decimals);
else scriptValueFormat(runtime, &buffer, args[0], false);
result = scriptStringNew(runtime, buffer.data ? buffer.data : "", buffer.length);
scriptBufFree(runtime, &buffer);
return result;
}
SCRIPT_NATIVE(scriptBToBase) {
scriptvalue_t value;
int base;
int32_t number;
bool negative;
char digits[40];
uint8_t count = 0;
char text[41];
uint8_t i;
scriptArgCount(runtime, argc, 2, 2);
value = args[0];
base = (int)scriptArgInteger(runtime, args, argc, 1);
if(base < 2 || base > 36) scriptRaise(runtime, "The base has to be from 2 to 36");
if(value.type == SCRIPT_VALUE_BOOL) {
number = value.as.boolean ? 1 : 0;
} else if(value.type == SCRIPT_VALUE_NUMBER && value.as.number == floorf(value.as.number)) {
number = (int32_t)value.as.number;
} else {
scriptRaise(runtime, "tobase needs a whole number or a bool, got %s", scriptTypeName(value));
return scriptUndefined();
}
negative = number < 0;
if(negative) number = -number;
do {
int digit = (int)(number % base);
digits[count++] = (char)(digit < 10 ? '0' + digit : 'A' + digit - 10);
number /= base;
} while(number > 0);
i = 0;
if(negative) text[i++] = '-';
while(count > 0) text[i++] = digits[--count];
return scriptStringNew(runtime, text, i);
}
SCRIPT_NATIVE(scriptBToNum) {
scriptvalue_t value = scriptArgOptional(args, argc, 0);
scriptstring_t *string;
scriptparse_t parsed;
scriptvalue_t result;
int32_t integer;
scriptArgCount(runtime, argc, 1, 2);
if(scriptIsNumber(value)) return value;
string = scriptArgString(runtime, args, argc, 0);
if(argc > 1) {
parsed = scriptParseInteger(string->data, string->length, (int)scriptArgInteger(runtime, args, argc, 1), &integer);
result = scriptNumber((float)integer);
} else {
parsed = scriptParseDecimal(string->data, string->length, &result);
}
if(parsed == SCRIPT_PARSE_INVALID) scriptRaise(runtime, "Can not turn \"%s\" into a number", string->data);
if(parsed == SCRIPT_PARSE_TOOBIG) scriptRaise(runtime, "\"%s\" is too big a number", string->data);
return result;
}
SCRIPT_NATIVE(scriptBToBool) {
scriptvalue_t value;
scriptArgCount(runtime, argc, 1, 1);
value = args[0];
if(value.type == SCRIPT_VALUE_BOOL) return value;
if(scriptIsNumber(value)) return scriptBool(scriptTruthy(value));
if(value.type == SCRIPT_VALUE_STRING) {
if(value.as.string->length == 4 && memoryCompare(value.as.string->data, "true", 4) == 0) return scriptBool(true);
if(value.as.string->length == 5 && memoryCompare(value.as.string->data, "false", 5) == 0) return scriptBool(false);
}
scriptRaise(runtime, "Can not turn this %s into a bool", scriptTypeName(value));
return scriptUndefined();
}
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_CORE_SCRIPTBCONVERT_H
#define SCRIPT_BUILTIN_CORE_SCRIPTBCONVERT_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBToStr);
SCRIPT_NATIVE(scriptBToBase);
SCRIPT_NATIVE(scriptBToNum);
SCRIPT_NATIVE(scriptBToBool);
#endif
@@ -0,0 +1,37 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/core/scriptbinclude.h"
#include "builtin/scriptarg.h"
#include "call/scriptcall.h"
#include "runtime/scriptruntimeload.h"
#include "value/scriptvalueprimitive.h"
#include "value/scriptfunction.h"
// A script that is compiled with the assets of the game has the path of what it
// includes turned into the number of the asset, and an include with a number looks
// the script up by it. Anything else is a path, read when it is included.
static scriptvalue_t scriptInclude(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const bool rootAsync) {
scriptvalue_t function;
scriptArgCount(runtime, argc, 1, 1);
if(args[0].type == SCRIPT_VALUE_NUMBER) {
function = scriptLoadAsset(runtime, (uint16_t)args[0].as.number, rootAsync);
} else {
function = scriptLoadScript(runtime, scriptArgString(runtime, args, argc, 0)->data, rootAsync);
}
return scriptCallFunction(runtime, function, NULL, 0);
}
SCRIPT_NATIVE(scriptBInclude) {
return scriptInclude(runtime, args, argc, false);
}
SCRIPT_NATIVE(scriptBAInclude) {
return scriptInclude(runtime, args, argc, true);
}
@@ -0,0 +1,18 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_CORE_SCRIPTBINCLUDE_H
#define SCRIPT_BUILTIN_CORE_SCRIPTBINCLUDE_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBInclude);
SCRIPT_NATIVE(scriptBAInclude);
#endif
@@ -0,0 +1,38 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/core/scriptbprint.h"
#include "builtin/scriptarg.h"
#include "string/scriptbuf.h"
#include "error/scriptraise.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptvalueprimitive.h"
#include "value/scriptvalueformat.h"
#if SCRIPT_FEATURE_PRINT
SCRIPT_NATIVE(scriptBPrint) {
scriptbuf_t buffer;
uint8_t i;
scriptBufInit(&buffer);
for(i = 0; i < argc; i++) {
if(i) scriptBufAppend(runtime, &buffer, " ", 1);
scriptValueFormat(runtime, &buffer, args[i], false);
}
scriptBufAppend(runtime, &buffer, "\n", 1);
if(runtime->printHandler) runtime->printHandler(buffer.data, buffer.length);
scriptBufFree(runtime, &buffer);
return scriptUndefined();
}
#endif
SCRIPT_NATIVE(scriptBAssert) {
scriptArgCount(runtime, argc, 1, 1);
if(!scriptTruthy(args[0])) scriptRaise(runtime, "Assertion failed");
return scriptUndefined();
}
@@ -0,0 +1,19 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_CORE_SCRIPTBPRINT_H
#define SCRIPT_BUILTIN_CORE_SCRIPTBPRINT_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
// Core
SCRIPT_NATIVE(scriptBPrint);
SCRIPT_NATIVE(scriptBAssert);
#endif
@@ -0,0 +1,66 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/core/scriptbpromise.h"
#include "builtin/scriptarg.h"
#include "call/scriptcall.h"
#include "value/scriptfunction.h"
#include "promise/scriptpromisecombine.h"
#include "promise/scriptpromisenew.h"
#include "promise/scriptpromisesettle.h"
#include "error/scriptraise.h"
#include "promise/scripttimer.h"
#include "value/scriptvalueprimitive.h"
SCRIPT_NATIVE(scriptBResolve) {
scriptPromiseResolve(runtime, (scriptpromise_t *)self->data, scriptArgOptional(args, argc, 0));
return scriptUndefined();
}
SCRIPT_NATIVE(scriptBReject) {
scriptPromiseReject(runtime, (scriptpromise_t *)self->data, scriptArgOptional(args, argc, 0));
return scriptUndefined();
}
SCRIPT_NATIVE(scriptBPromise) {
scriptvalue_t promise;
scriptvalue_t executor;
scriptvalue_t callbacks[2];
scriptArgCount(runtime, argc, 1, 1);
executor = scriptArgFunction(runtime, args, argc, 0);
promise = scriptPromiseNew(runtime);
callbacks[0] = scriptFunctionNative(runtime, "resolve", scriptBResolve, promise.as.promise);
callbacks[1] = scriptFunctionNative(runtime, "reject", scriptBReject, promise.as.promise);
scriptCallFunction(runtime, executor, callbacks, 2);
return promise;
}
SCRIPT_NATIVE(scriptBAll) {
scriptArgCount(runtime, argc, 1, 1);
return scriptPromiseCombine(runtime, SCRIPT_COMBO_ALL, scriptArgArray(runtime, args, argc, 0));
}
SCRIPT_NATIVE(scriptBAny) {
scriptArgCount(runtime, argc, 1, 1);
return scriptPromiseCombine(runtime, SCRIPT_COMBO_ANY, scriptArgArray(runtime, args, argc, 0));
}
SCRIPT_NATIVE(scriptBFirst) {
scriptArgCount(runtime, argc, 1, 1);
return scriptPromiseCombine(runtime, SCRIPT_COMBO_FIRST, scriptArgArray(runtime, args, argc, 0));
}
SCRIPT_NATIVE(scriptBWait) {
int32_t ticks;
scriptArgCount(runtime, argc, 1, 1);
ticks = scriptArgInteger(runtime, args, argc, 0);
if(ticks < 0) scriptRaise(runtime, "Can not wait a negative number of ticks");
return scriptTimerPromise(runtime, (uint32_t)ticks);
}
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_CORE_SCRIPTBPROMISE_H
#define SCRIPT_BUILTIN_CORE_SCRIPTBPROMISE_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBResolve);
SCRIPT_NATIVE(scriptBReject);
SCRIPT_NATIVE(scriptBPromise);
SCRIPT_NATIVE(scriptBAll);
SCRIPT_NATIVE(scriptBAny);
SCRIPT_NATIVE(scriptBFirst);
SCRIPT_NATIVE(scriptBWait);
#endif
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/core/scriptbtype.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
SCRIPT_NATIVE(scriptBIsNull) {
return scriptBool(scriptArgType(args, argc, 0) == SCRIPT_VALUE_NULL);
}
SCRIPT_NATIVE(scriptBIsUndefined) {
return scriptBool(scriptArgType(args, argc, 0) == SCRIPT_VALUE_UNDEFINED);
}
SCRIPT_NATIVE(scriptBIsNullOrUndefined) {
scriptvaluetype_t type = scriptArgType(args, argc, 0);
return scriptBool(type == SCRIPT_VALUE_UNDEFINED || type == SCRIPT_VALUE_NULL);
}
SCRIPT_NATIVE(scriptBIsFunctionAsync) {
if(scriptArgType(args, argc, 0) != SCRIPT_VALUE_FUNCTION) return scriptBool(false);
return scriptBool(args[0].as.function->isAsync);
}
SCRIPT_NATIVE(scriptBIsFunction) {
return scriptBool(scriptArgType(args, argc, 0) == SCRIPT_VALUE_FUNCTION);
}
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_CORE_SCRIPTBTYPE_H
#define SCRIPT_BUILTIN_CORE_SCRIPTBTYPE_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBIsNull);
SCRIPT_NATIVE(scriptBIsUndefined);
SCRIPT_NATIVE(scriptBIsNullOrUndefined);
SCRIPT_NATIVE(scriptBIsFunction);
SCRIPT_NATIVE(scriptBIsFunctionAsync);
#endif
@@ -0,0 +1,94 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/math/scriptbmathbasic.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#include "number/scriptnumberbinary.h"
#include "number/scriptnumberunary.h"
#include "error/scriptraise.h"
#include "value/scriptfunction.h"
#include <math.h>
SCRIPT_NATIVE(scriptBAbs) {
scriptvalue_t value;
scriptArgCount(runtime, argc, 1, 1);
value = scriptArgNumber(runtime, args, argc, 0);
return scriptNumber(fabsf(value.as.number));
}
SCRIPT_NATIVE(scriptBSign) {
float number;
scriptArgCount(runtime, argc, 1, 1);
number = scriptArgNumber(runtime, args, argc, 0).as.number;
return scriptNumber(number < 0 ? -1.0f : (number > 0 ? 1.0f : 0.0f));
}
SCRIPT_NATIVE(scriptBMin) {
scriptvalue_t a;
scriptvalue_t b;
scriptArgCount(runtime, argc, 2, 2);
a = scriptArgNumber(runtime, args, argc, 0);
b = scriptArgNumber(runtime, args, argc, 1);
return scriptNumberCompare(a, b) <= 0 ? a : b;
}
SCRIPT_NATIVE(scriptBMax) {
scriptvalue_t a;
scriptvalue_t b;
scriptArgCount(runtime, argc, 2, 2);
a = scriptArgNumber(runtime, args, argc, 0);
b = scriptArgNumber(runtime, args, argc, 1);
return scriptNumberCompare(a, b) >= 0 ? a : b;
}
SCRIPT_NATIVE(scriptBClamp) {
scriptvalue_t value;
scriptvalue_t low;
scriptvalue_t high;
scriptArgCount(runtime, argc, 3, 3);
value = scriptArgNumber(runtime, args, argc, 0);
low = scriptArgNumber(runtime, args, argc, 1);
high = scriptArgNumber(runtime, args, argc, 2);
if(scriptNumberCompare(value, low) < 0) return low;
if(scriptNumberCompare(value, high) > 0) return high;
return value;
}
SCRIPT_NATIVE(scriptBPow) {
scriptvalue_t base;
scriptvalue_t result;
int32_t exponent;
int32_t i;
scriptArgCount(runtime, argc, 2, 2);
base = scriptArgNumber(runtime, args, argc, 0);
exponent = scriptArgInteger(runtime, args, argc, 1);
if(exponent < 0) {
#if SCRIPT_FEATURE_MATH
return scriptNumber(powf(base.as.number, (float)exponent));
#else
scriptRaise(runtime, "Negative powers need a maths library, this platform has none");
#endif
}
result = scriptNumber(1);
for(i = 0; i < exponent; i++) result = scriptNumberBinary(runtime, SCRIPT_OP_MUL, result, base);
return result;
}
#if SCRIPT_FEATURE_MATH
SCRIPT_NATIVE(scriptBSqrt) {
scriptvalue_t value;
scriptArgCount(runtime, argc, 1, 1);
value = scriptArgNumber(runtime, args, argc, 0);
if(value.as.number < 0) scriptRaise(runtime, "Can not take the square root of a negative number");
return scriptNumber(sqrtf(value.as.number));
}
#endif
@@ -0,0 +1,29 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_MATH_SCRIPTBMATHBASIC_H
#define SCRIPT_BUILTIN_MATH_SCRIPTBMATHBASIC_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
// Maths
SCRIPT_NATIVE(scriptBAbs);
SCRIPT_NATIVE(scriptBSign);
SCRIPT_NATIVE(scriptBMin);
SCRIPT_NATIVE(scriptBMax);
SCRIPT_NATIVE(scriptBClamp);
SCRIPT_NATIVE(scriptBPow);
SCRIPT_NATIVE(scriptBSqrt);
#endif
@@ -0,0 +1,55 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/math/scriptbmathrandom.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#include "error/scriptraise.h"
#include "value/scriptfunction.h"
uint32_t scriptRandomNext(scriptruntime_t *runtime) {
uint32_t x = runtime->randState;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
runtime->randState = x;
return x;
}
SCRIPT_NATIVE(scriptBRand) {
int32_t low;
int32_t high;
int32_t result;
scriptArgCount(runtime, argc, 2, 2);
low = scriptArgInteger(runtime, args, argc, 0);
high = scriptArgInteger(runtime, args, argc, 1);
if(high <= low) scriptRaise(runtime, "rand needs a maximum bigger than the minimum");
result = low + (int32_t)(scriptRandomNext(runtime) % (uint32_t)(high - low));
return scriptNumber((float)result);
}
SCRIPT_NATIVE(scriptBRandf) {
float low;
float high;
scriptArgCount(runtime, argc, 2, 2);
low = scriptArgNumber(runtime, args, argc, 0).as.number;
high = scriptArgNumber(runtime, args, argc, 1).as.number;
if(high <= low) scriptRaise(runtime, "randf needs a maximum bigger than the minimum");
return scriptNumber(low + ((float)scriptRandomNext(runtime) / 4294967295.0f) * (high - low));
}
SCRIPT_NATIVE(scriptBRandSeed) {
scriptArgCount(runtime, argc, 1, 1);
runtime->randState = (uint32_t)scriptArgInteger(runtime, args, argc, 0);
if(runtime->randState == 0) runtime->randState = 1;
return scriptUndefined();
}
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_MATH_SCRIPTBMATHRANDOM_H
#define SCRIPT_BUILTIN_MATH_SCRIPTBMATHRANDOM_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
/**
* Next number from the random generator.
*
* @param runtime The runtime.
* @returns A 32 bit random number.
*/
uint32_t scriptRandomNext(scriptruntime_t *runtime);
SCRIPT_NATIVE(scriptBRand);
SCRIPT_NATIVE(scriptBRandf);
SCRIPT_NATIVE(scriptBRandSeed);
#endif
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/math/scriptbmathround.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#include "value/scriptfunction.h"
#include <math.h>
SCRIPT_NATIVE(scriptBFloor) {
scriptvalue_t value;
scriptArgCount(runtime, argc, 1, 1);
value = scriptArgNumber(runtime, args, argc, 0);
return scriptNumber(floorf(value.as.number));
}
SCRIPT_NATIVE(scriptBCeil) {
scriptvalue_t value;
scriptArgCount(runtime, argc, 1, 1);
value = scriptArgNumber(runtime, args, argc, 0);
return scriptNumber(ceilf(value.as.number));
}
SCRIPT_NATIVE(scriptBRound) {
scriptvalue_t value;
scriptArgCount(runtime, argc, 1, 1);
value = scriptArgNumber(runtime, args, argc, 0);
return scriptNumber(roundf(value.as.number));
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_MATH_SCRIPTBMATHROUND_H
#define SCRIPT_BUILTIN_MATH_SCRIPTBMATHROUND_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBFloor);
SCRIPT_NATIVE(scriptBCeil);
SCRIPT_NATIVE(scriptBRound);
#endif
@@ -0,0 +1,39 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/math/scriptbmathtrig.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#if SCRIPT_FEATURE_MATH
#include <math.h>
#endif
#if SCRIPT_FEATURE_MATH
SCRIPT_NATIVE(scriptBSin) {
scriptArgCount(runtime, argc, 1, 1);
return scriptNumber(sinf(scriptArgNumber(runtime, args, argc, 0).as.number));
}
SCRIPT_NATIVE(scriptBCos) {
scriptArgCount(runtime, argc, 1, 1);
return scriptNumber(cosf(scriptArgNumber(runtime, args, argc, 0).as.number));
}
SCRIPT_NATIVE(scriptBTan) {
scriptArgCount(runtime, argc, 1, 1);
return scriptNumber(tanf(scriptArgNumber(runtime, args, argc, 0).as.number));
}
SCRIPT_NATIVE(scriptBAtan2) {
scriptArgCount(runtime, argc, 2, 2);
return scriptNumber(atan2f(
scriptArgNumber(runtime, args, argc, 0).as.number,
scriptArgNumber(runtime, args, argc, 1).as.number
));
}
#endif
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_MATH_SCRIPTBMATHTRIG_H
#define SCRIPT_BUILTIN_MATH_SCRIPTBMATHTRIG_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBSin);
SCRIPT_NATIVE(scriptBCos);
SCRIPT_NATIVE(scriptBTan);
SCRIPT_NATIVE(scriptBAtan2);
#endif
+86
View File
@@ -0,0 +1,86 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "array/scriptarraymodify.h"
#include "scripttypes.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#include "error/scriptraise.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptfunction.h"
#include <math.h>
void scriptArgCountFail(scriptruntime_t *runtime, const uint8_t argc, const uint8_t minimum, const uint8_t maximum) {
if(minimum == maximum) {
scriptRaise(runtime, "Expected %d arguments, got %d", (int)minimum, (int)argc);
}
scriptRaise(runtime, "Expected %d to %d arguments, got %d", (int)minimum, (int)maximum, (int)argc);
}
// What a native is given when it was given nothing for an argument
static const scriptvalue_t SCRIPT_ARG_UNDEFINED = { .type = SCRIPT_VALUE_UNDEFINED };
// The argument itself, or undefined, with no copy of it: copying a value is a call on the
// Super Nintendo, and what is checked here is mostly its type
static const scriptvalue_t *scriptArgPointer(const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
return index < argc ? &args[index] : &SCRIPT_ARG_UNDEFINED;
}
scriptvaluetype_t scriptArgType(const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
return scriptArgPointer(args, argc, index)->type;
}
scriptvalue_t scriptArgOptional(const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
return index < argc ? args[index] : scriptUndefined();
}
scriptarray_t *scriptArgArray(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
const scriptvalue_t *value = scriptArgPointer(args, argc, index);
if(value->type != SCRIPT_VALUE_ARRAY) {
scriptRaise(runtime, "Argument %d has to be an array, got %s", (int)index + 1, scriptTypeName(*value));
}
return value->as.array;
}
scriptarray_t *scriptArgArrayWritable(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
scriptarray_t *array = scriptArgArray(runtime, args, argc, index);
if(array->frozen) scriptRaise(runtime, "Can not change a constant");
return array;
}
scriptstring_t *scriptArgString(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
const scriptvalue_t *value = scriptArgPointer(args, argc, index);
if(value->type != SCRIPT_VALUE_STRING) {
scriptRaise(runtime, "Argument %d has to be a string, got %s", (int)index + 1, scriptTypeName(*value));
}
return value->as.string;
}
scriptvalue_t scriptArgNumber(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
const scriptvalue_t *value = scriptArgPointer(args, argc, index);
if(!scriptIsNumber(*value)) {
scriptRaise(runtime, "Argument %d has to be a number, got %s", (int)index + 1, scriptTypeName(*value));
}
return *value;
}
int32_t scriptArgInteger(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
const scriptvalue_t *value = scriptArgPointer(args, argc, index);
if(value->type == SCRIPT_VALUE_NUMBER && value->as.number == floorf(value->as.number)) {
return (int32_t)value->as.number;
}
scriptRaise(runtime, "Argument %d has to be a whole number, got %s", (int)index + 1, scriptTypeName(*value));
return 0;
}
scriptvalue_t scriptArgFunction(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index) {
const scriptvalue_t *value = scriptArgPointer(args, argc, index);
if(value->type != SCRIPT_VALUE_FUNCTION) {
scriptRaise(runtime, "Argument %d has to be a function, got %s", (int)index + 1, scriptTypeName(*value));
}
return *value;
}
+124
View File
@@ -0,0 +1,124 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_SCRIPTARG_H
#define SCRIPT_BUILTIN_SCRIPTARG_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
/**
* Says a native got the wrong number of arguments, which is a fatal error.
*
* @param runtime The runtime.
* @param argc Number of arguments given.
* @param minimum Fewest allowed.
* @param maximum Most allowed.
*/
void scriptArgCountFail(scriptruntime_t *runtime, const uint8_t argc, const uint8_t minimum, const uint8_t maximum);
/**
* Checks a native got between a minimum and maximum number of arguments. The check is made
* here, in the native, as a call is slow on the Super Nintendo and nearly always has nothing
* to say. The arguments are used twice, so they are names or numbers.
*
* @param runtime The runtime.
* @param argc Number of arguments given.
* @param minimum Fewest allowed.
* @param maximum Most allowed.
*/
#define scriptArgCount(runtime, argc, minimum, maximum) \
((argc) >= (minimum) && (argc) <= (maximum) ? (void)0 : scriptArgCountFail((runtime), (argc), (minimum), (maximum)))
/**
* The type of an argument, undefined when it was not given. A native that only wants to
* know what it was given should use this, which does not copy the value.
*
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The type.
*/
scriptvaluetype_t scriptArgType(const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
/**
* Gets an argument, undefined when it was not given.
*
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The argument or undefined.
*/
scriptvalue_t scriptArgOptional(const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
/**
* Gets an argument that has to be an array.
*
* @param runtime The runtime.
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The array.
*/
/**
* Like scriptArgArray, for a function that changes the array: a constant is not changed.
*
* @param runtime The runtime.
* @param args The arguments.
* @param argc How many there are.
* @param index The argument.
* @returns The array.
*/
scriptarray_t *scriptArgArrayWritable(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
scriptarray_t *scriptArgArray(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
/**
* Gets an argument that has to be a string.
*
* @param runtime The runtime.
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The string.
*/
scriptstring_t *scriptArgString(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
/**
* Gets an argument that has to be a number of any type.
*
* @param runtime The runtime.
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The number value.
*/
scriptvalue_t scriptArgNumber(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
/**
* Gets an argument that has to be a whole number: a number with no fraction.
*
* @param runtime The runtime.
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The number, truncated to an int32.
*/
int32_t scriptArgInteger(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
/**
* Gets an argument that has to be a function.
*
* @param runtime The runtime.
* @param args The arguments.
* @param argc Number of arguments.
* @param index Which argument.
* @returns The function value.
*/
scriptvalue_t scriptArgFunction(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, const uint8_t index);
#endif
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_SCRIPTBUILTIN_H
#define SCRIPT_BUILTIN_SCRIPTBUILTIN_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
// Every built in function has this shape, runtime and arguments in, a value out.
#define SCRIPT_NATIVE(name) scriptvalue_t name( \
scriptruntime_t *runtime, \
const scriptvalue_t *args, \
const uint8_t argc, \
scriptfunction_t *self \
)
#endif
@@ -0,0 +1,173 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "value/scriptvalueprimitive.h"
#include "builtin/scriptbuiltinregister.h"
#include "value/scriptalloc.h"
#include "builtin/core/scriptbinclude.h"
#include "builtin/math/scriptbmathbasic.h"
#include "builtin/core/scriptbpromise.h"
#include "builtin/core/scriptbprint.h"
#include "builtin/math/scriptbmathtrig.h"
#include "builtin/core/scriptbconvert.h"
#include "builtin/math/scriptbmathround.h"
#include "builtin/string/scriptbstringcase.h"
#include "builtin/array/scriptbarraybasic.h"
#include "builtin/string/scriptbstringsearch.h"
#include "builtin/array/scriptbarraymodify.h"
#include "builtin/array/scriptbarraysearch.h"
#include "builtin/array/scriptbarrayslice.h"
#include "builtin/core/scriptbtype.h"
#include "builtin/string/scriptbstringtrim.h"
#include "builtin/array/scriptbarrayfunc.h"
#include "builtin/math/scriptbmathrandom.h"
#include "builtin/array/scriptbarrayorder.h"
#include "builtin/string/scriptbstringsplit.h"
#include "value/scriptfunction.h"
#include "scope/scriptscopeset.h"
#include "assert/assert.h"
#include <string.h>
// The built in functions are function objects that are made when the program is, not
// when a runtime is made, so there is none of them to allocate, to walk when the
// collector runs or to throw away. They never change, and the collector does not know
// them for a block of the heap, so it leaves them be.
#if SCRIPT_FEATURE_PRINT
#define SCRIPT_BUILTIN_PRINT(X) X(print, scriptBPrint)
#else
#define SCRIPT_BUILTIN_PRINT(X)
#endif
#define SCRIPT_BUILTIN_LIST(X) \
SCRIPT_BUILTIN_PRINT(X) \
X(assert, scriptBAssert) \
X(isnull, scriptBIsNull) \
X(isundefined, scriptBIsUndefined) \
X(isnullorundefined, scriptBIsNullOrUndefined) \
X(isfunction, scriptBIsFunction) \
X(isfunctionasync, scriptBIsFunctionAsync) \
X(tostr, scriptBToStr) \
X(tobase, scriptBToBase) \
X(tonum, scriptBToNum) \
X(tobool, scriptBToBool) \
X(promise, scriptBPromise) \
X(all, scriptBAll) \
X(any, scriptBAny) \
X(first, scriptBFirst) \
X(wait, scriptBWait) \
X(include, scriptBInclude) \
X(ainclude, scriptBAInclude) \
X(abs, scriptBAbs) \
X(pow, scriptBPow) \
X(floor, scriptBFloor) \
X(ceil, scriptBCeil) \
X(round, scriptBRound) \
X(sign, scriptBSign) \
X(min, scriptBMin) \
X(max, scriptBMax) \
X(clamp, scriptBClamp) \
X(rand, scriptBRand) \
X(randf, scriptBRandf) \
X(randseed, scriptBRandSeed) \
X(strindex, scriptBStrIndex) \
X(contains, scriptBContains) \
X(startswith, scriptBStartsWith) \
X(endswith, scriptBEndsWith) \
X(chr, scriptBChr) \
X(stringequals, scriptBStringEquals) \
X(stringequalsinsensitive, scriptBStringEqualsInsensitive) \
X(ltrim, scriptBLtrim) \
X(rtrim, scriptBRtrim) \
X(trim, scriptBTrim) \
X(downcase, scriptBDowncase) \
X(upcase, scriptBUpcase) \
X(split, scriptBSplit) \
X(len, scriptBLen) \
X(push, scriptBPush) \
X(pop, scriptBPop) \
X(shift, scriptBShift) \
X(unshift, scriptBUnshift) \
X(fill, scriptBFill) \
X(filter, scriptBFilter) \
X(merge, scriptBMerge) \
X(splice, scriptBSplice) \
X(insert, scriptBInsert) \
X(insertarr, scriptBInsertArr) \
X(indexof, scriptBIndexOf) \
X(lastindexof, scriptBLastIndexOf) \
X(sort, scriptBSort) \
X(reverse, scriptBReverse) \
X(shuffle, scriptBShuffle) \
X(empty, scriptBEmpty) \
X(clone, scriptBClone) \
X(subarr, scriptBSubarr) \
X(deepclone, scriptBDeepClone) \
X(deepequal, scriptBDeepEqual) \
X(map, scriptBMap) \
X(reduce, scriptBReduce) \
X(flatten, scriptBFlatten) \
X(join, scriptBJoin)
// The ones that need the maths library, which not every platform has
#if SCRIPT_FEATURE_MATH
#define SCRIPT_BUILTIN_MATH_LIST(X) \
X(sin, scriptBSin) \
X(cos, scriptBCos) \
X(tan, scriptBTan) \
X(atan2, scriptBAtan2) \
X(sqrt, scriptBSqrt)
#else
#define SCRIPT_BUILTIN_MATH_LIST(X)
#endif
#define SCRIPT_BUILTIN_ALL(X) SCRIPT_BUILTIN_LIST(X) SCRIPT_BUILTIN_MATH_LIST(X)
// One function object for each of them, and one variable of the built in scope for each,
// that is copied into the scope of a runtime in one go when it is made.
#define SCRIPT_BUILTIN_FUNCTION(id, fn) { .name = #id, .native = fn },
static const scriptfunction_t SCRIPT_BUILTINS[] = {
SCRIPT_BUILTIN_ALL(SCRIPT_BUILTIN_FUNCTION)
};
#define SCRIPT_BUILTIN_INDEX(id, fn) SCRIPT_BUILTIN_INDEX_##id,
enum {
SCRIPT_BUILTIN_ALL(SCRIPT_BUILTIN_INDEX)
SCRIPT_BUILTIN_COUNT
};
#define SCRIPT_BUILTIN_VARIABLE(id, fn) { .name = #id, .value = { .type = SCRIPT_VALUE_FUNCTION, .as = { .function = (scriptfunction_t *)&SCRIPT_BUILTINS[SCRIPT_BUILTIN_INDEX_##id] } }, .declared = 1 },
static const scriptvariable_t SCRIPT_BUILTIN_VARIABLES[] = {
SCRIPT_BUILTIN_ALL(SCRIPT_BUILTIN_VARIABLE)
};
void scriptBuiltinAdd(scriptruntime_t *runtime, const char *name, scriptnative_t native) {
assertNotNull(runtime, "scriptBuiltinAdd needs a runtime, not NULL");
assertNotNull(name, "scriptBuiltinAdd needs a name, not NULL");
assertNotNull(native, "scriptBuiltinAdd needs a native function, not NULL");
scriptScopeAdd(runtime, runtime->builtinScope, name, scriptFunctionNative(runtime, name, native, NULL));
}
void scriptBuiltinAddInt(scriptruntime_t *runtime, const char *name, const int16_t value) {
assertNotNull(runtime, "scriptBuiltinAddInt needs a runtime, not NULL");
assertNotNull(name, "scriptBuiltinAddInt needs a name, not NULL");
scriptScopeAdd(runtime, runtime->builtinScope, name, scriptNumber((float)value));
}
void scriptBuiltinsRegister(scriptruntime_t *runtime) {
scriptscope_t *scope;
assertNotNull(runtime, "scriptBuiltinsRegister needs a runtime, not NULL");
scope = runtime->builtinScope;
// The scope gets all it will hold at once, and the built in functions go in as they
// are in the program, adding them one at a time takes more than the rest of starting
scriptScopeReserve(runtime, scope, (uint16_t)(SCRIPT_BUILTIN_COUNT + 1 + SCRIPT_BUILTINS_SPARE));
memoryCopy(scope->variables, SCRIPT_BUILTIN_VARIABLES, sizeof(SCRIPT_BUILTIN_VARIABLES));
scope->count = SCRIPT_BUILTIN_COUNT;
scriptScopeAdd(runtime, scope, "PI", scriptNumber(3.14159265358979323846f));
}
@@ -0,0 +1,40 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_SCRIPTBUILTINREGISTER_H
#define SCRIPT_BUILTIN_SCRIPTBUILTINREGISTER_H
#include "core/scriptcore.h"
#include "value/scriptfunction.h"
/**
* Adds one built in function to the builtin scope.
*
* @param runtime The runtime.
* @param name The function name.
* @param native The C function.
*/
void scriptBuiltinAdd(scriptruntime_t *runtime, const char *name, scriptnative_t native);
/**
* Adds a whole number to the builtin scope, which a script can read but not declare again.
* For what the game has that scripts name: the scenes, say.
*
* @param runtime The runtime.
* @param name The constant's name.
* @param value The number.
*/
void scriptBuiltinAddInt(scriptruntime_t *runtime, const char *name, const int16_t value);
/**
* Puts all the built in functions and constants in the builtin scope.
*
* @param runtime The runtime.
*/
void scriptBuiltinsRegister(scriptruntime_t *runtime);
#endif
@@ -0,0 +1,54 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "builtin/scriptintrinsic.h"
#include "builtin/core/scriptbtype.h"
#include "builtin/array/scriptbarraybasic.h"
#include "builtin/math/scriptbmathbasic.h"
#include "coroutine/scriptcoroutine.h"
#include <string.h>
typedef scriptvalue_t (*scriptintrinsicfunction_t)(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, scriptfunction_t *self);
static const struct {
const char *name;
scriptintrinsicfunction_t function;
} SCRIPT_INTRINSICS[SCRIPT_INTRINSIC_COUNT] = {
{ "isnull", scriptBIsNull },
{ "isundefined", scriptBIsUndefined },
{ "isnullorundefined", scriptBIsNullOrUndefined },
{ "isfunction", scriptBIsFunction },
{ "len", scriptBLen },
{ "abs", scriptBAbs },
{ "isfunctionasync", scriptBIsFunctionAsync }
};
const char *scriptIntrinsicName(const scriptintrinsic_t id) {
return SCRIPT_INTRINSICS[id].name;
}
scriptvalue_t scriptIntrinsicCall(scriptruntime_t *runtime, const scriptintrinsic_t id, const scriptvalue_t *value) {
scriptcoroutine_t *co = runtime->current;
const char *saved = co->nativeName;
scriptvalue_t result;
// An error is the function's, as it is in a call of it
co->nativeName = SCRIPT_INTRINSICS[id].name;
result = SCRIPT_INTRINSICS[id].function(runtime, value, 1, NULL);
co->nativeName = saved;
return result;
}
scriptintrinsic_t scriptIntrinsicFind(const char *name, const size_t length) {
uint8_t i;
for(i = 0; i < SCRIPT_INTRINSIC_COUNT; i++) {
if(strlen(SCRIPT_INTRINSICS[i].name) == length && memoryCompare(SCRIPT_INTRINSICS[i].name, name, length) == 0) return (scriptintrinsic_t)i;
}
return SCRIPT_INTRINSIC_COUNT;
}
@@ -0,0 +1,53 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
// The built in functions that take one value and say something of it, which the compiler turns into one
// instruction (INTRINSIC) in place of a call of the function, when the name is the built in function and the call has the
// one argument. The numbers are in the instruction, and in the assembly of the Super Nintendo.
typedef enum {
SCRIPT_INTRINSIC_ISNULL,
SCRIPT_INTRINSIC_ISUNDEFINED,
SCRIPT_INTRINSIC_ISNULLORUNDEFINED,
SCRIPT_INTRINSIC_ISFUNCTION,
SCRIPT_INTRINSIC_LEN,
SCRIPT_INTRINSIC_ABS,
SCRIPT_INTRINSIC_ISFUNCTIONASYNC,
SCRIPT_INTRINSIC_COUNT
} scriptintrinsic_t;
/**
* The name of an intrinsic, which is the name of the built in function.
*
* @param id The intrinsic.
* @returns The name.
*/
const char *scriptIntrinsicName(const scriptintrinsic_t id);
/**
* The built in function an intrinsic is, which does what the instruction does for what it has no quick way of doing,
* and says what is wrong with it.
*
* @param runtime The runtime.
* @param id The intrinsic.
* @param value The value it is called with.
* @returns The function.
*/
scriptvalue_t scriptIntrinsicCall(scriptruntime_t *runtime, const scriptintrinsic_t id, const scriptvalue_t *value);
/**
* Which intrinsic a name is.
*
* @param name The name.
* @param length Length of the name.
* @returns The intrinsic, SCRIPT_INTRINSIC_COUNT when the name is not one.
*/
scriptintrinsic_t scriptIntrinsicFind(const char *name, const size_t length);
@@ -0,0 +1,72 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/scriptnativesig.h"
#include "builtin/scriptarg.h"
#include "builtin/scriptbuiltinregister.h"
#include "error/scriptraise.h"
#include "value/scriptvaluecompare.h"
#include "scope/scriptscopeset.h"
#include "runtime/scriptruntimestate.h"
// What an argument is said to have to be, as the natives that check it themselves say it
static const char *scriptNativeTypesName(const uint16_t types) {
if(types == SCRIPT_TYPES_NUMBER) return "a number";
if(types == SCRIPT_TYPE_BIT(SCRIPT_VALUE_STRING)) return "a string";
if(types == SCRIPT_TYPE_BIT(SCRIPT_VALUE_ARRAY)) return "an array";
if(types == SCRIPT_TYPE_BIT(SCRIPT_VALUE_FUNCTION)) return "a function";
if(types == SCRIPT_TYPE_BIT(SCRIPT_VALUE_BOOL)) return "a boolean";
return "a different kind of value";
}
void scriptNativeCheckArgs(scriptruntime_t *runtime, const scriptnativesig_t *signature, const scriptvalue_t *args, const uint8_t mask) {
uint8_t i;
for(i = 0; i < signature->argMax; i++) {
if(!(mask & (1u << i))) continue;
if(signature->argTypes[i] & SCRIPT_TYPE_BIT(args[i].type)) continue;
scriptRaise(runtime, "Argument %d has to be %s, got %s", (int)i + 1, scriptNativeTypesName(signature->argTypes[i]), scriptTypeName(args[i]));
}
}
#if SCRIPT_FEATURE_TRUST_BYTECODE
// A native of a platform that trusts the bytecode, called by its name: nothing has looked at
// what it was given, so it is all looked at here, and what was left out is filled in
static scriptvalue_t scriptNativeCheckedCall(scriptruntime_t *runtime, const scriptvalue_t *args, const uint8_t argc, scriptfunction_t *self) {
const scriptnativeentry_t *entry = (const scriptnativeentry_t *)self->data;
const scriptnativesig_t *signature = entry->signature;
scriptvalue_t full[SCRIPT_PARAMS_MAX];
uint8_t i;
scriptArgCount(runtime, argc, signature->argMin, signature->argMax);
for(i = 0; i < signature->argMax; i++) {
if(i < argc) {
full[i] = args[i];
} else {
full[i].type = signature->defaults[i].type;
full[i].as.raw = signature->defaults[i].value;
}
}
scriptNativeCheckArgs(runtime, signature, full, (uint8_t)((1u << signature->argMax) - 1));
return entry->function(runtime, full, signature->argMax, self);
}
#endif
void scriptNativesAdd(scriptruntime_t *runtime, const scriptnativeentry_t *entries, const uint16_t count) {
uint16_t i;
runtime->nativeEntries = entries;
runtime->nativeCount = count;
for(i = 0; i < count; i++) {
#if SCRIPT_FEATURE_TRUST_BYTECODE
scriptScopeAdd(runtime, runtime->builtinScope, entries[i].name, scriptFunctionNative(runtime, entries[i].name, scriptNativeCheckedCall, (void *)&entries[i]));
#else
scriptBuiltinAdd(runtime, entries[i].name, entries[i].function);
#endif
}
}
@@ -0,0 +1,109 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_SCRIPTNATIVESIG_H
#define SCRIPT_BUILTIN_SCRIPTNATIVESIG_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
#include "value/scriptfunction.h"
#include "builtin/scriptbuiltin.h"
#include "builtin/scriptarg.h"
// What a native takes and gives back, written in natives.json and made into the tables of this
// file by tools/duskscriptnatives. The compiler uses them to call a native by its number, with
// the arguments it can check checked, and the machine checks the others.
// A set of types, a bit for each of scriptvaluetype_t
#define SCRIPT_TYPE_BIT(type) ((uint16_t)(1u << (type)))
#define SCRIPT_TYPES_NUMBER SCRIPT_TYPE_BIT(SCRIPT_VALUE_NUMBER)
// What an argument is when it is left out, which an optional argument can be. For a
// number, value holds the bits of the float, the same way a PUSH_NUMBER constant does.
typedef struct {
scriptvaluetype_t type;
int32_t value;
} scriptnativedefault_t;
typedef struct {
const char *name;
uint8_t argMin;
uint8_t argMax;
// The types each argument can be, and what is used for the optional ones that are left out
uint16_t argTypes[SCRIPT_PARAMS_MAX];
scriptnativedefault_t defaults[SCRIPT_PARAMS_MAX];
// The types it can give back, which nothing checks yet, it is what the compiler can know
uint16_t returnTypes;
} scriptnativesig_t;
// A value the game gives a script a name for, which the compiler knows the type and the value of,
// so a name that nothing in the script changes is compiled to the number
typedef struct {
const char *name;
scriptvaluetype_t type;
int32_t value;
} scriptnativeconstant_t;
typedef struct {
const scriptnativesig_t *signatures;
uint16_t count;
const scriptnativeconstant_t *constants;
uint16_t constantCount;
} scriptnativetable_t;
// What the machine finds a native by, for the instruction that calls one by its number. 16
// bytes each on the Super Nintendo, which the assembly counts on.
typedef struct scriptnativeentry_t {
scriptnative_t function;
const char *name;
const scriptnativesig_t *signature;
uint32_t reserved;
} scriptnativeentry_t;
// The table the build makes from natives.json, which the compiler is given
extern const scriptnativetable_t SCRIPT_NATIVE_TABLE;
/**
* Makes the natives of a table known to a runtime: they can be called by their number and by
* their name, which is what a script calls them by when the compiler could not use the number.
* On a platform that trusts the bytecode, the name finds a function that checks everything
* first, as the native itself does not.
*
* @param runtime The runtime.
* @param entries The natives, in the order of the table.
* @param count How many there are.
*/
void scriptNativesAdd(scriptruntime_t *runtime, const scriptnativeentry_t *entries, const uint16_t count);
/**
* Checks the arguments of a call against the signature, which says that the call is wrong
* when it is. Does the arguments that are in the mask, a bit for each.
*
* @param runtime The runtime.
* @param signature What the native takes.
* @param args The arguments.
* @param mask The arguments to check.
*/
void scriptNativeCheckArgs(scriptruntime_t *runtime, const scriptnativesig_t *signature, const scriptvalue_t *args, const uint8_t mask);
// The number of arguments of a native with a signature. When the bytecode is trusted it is the
// number the signature says, and when it is not, it is counted here.
#if SCRIPT_FEATURE_TRUST_BYTECODE
#define SCRIPT_NATIVE_ARGC(runtime, argc, minimum, maximum) ((void)0)
#else
#define SCRIPT_NATIVE_ARGC(runtime, argc, minimum, maximum) scriptArgCount((runtime), (argc), (minimum), (maximum))
#endif
// A whole number argument of a native with a signature. When the bytecode is trusted the
// compiler or the machine has seen it is one, and when it is not, it is looked at here.
#if SCRIPT_FEATURE_TRUST_BYTECODE
#define SCRIPT_NATIVE_INT(runtime, args, argc, index) ((int32_t)(args)[(index)].as.number)
#else
#define SCRIPT_NATIVE_INT(runtime, args, argc, index) scriptArgInteger((runtime), (args), (argc), (index))
#endif
#endif
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/string/scriptbstringcase.h"
#include "builtin/scriptarg.h"
#include "string/scriptstringchar.h"
#include "error/scriptraise.h"
#include "string/scriptstringnew.h"
#include "value/scriptfunction.h"
SCRIPT_NATIVE(scriptBChr) {
int32_t code;
char c;
scriptArgCount(runtime, argc, 1, 1);
code = scriptArgInteger(runtime, args, argc, 0);
if(code < 1 || code > 255) scriptRaise(runtime, "chr needs a char code from 1 to 255, got %d", (int)code);
c = (char)code;
return scriptStringNew(runtime, &c, 1);
}
SCRIPT_NATIVE(scriptBDowncase) {
scriptstring_t *text;
scriptvalue_t result;
size_t i;
scriptArgCount(runtime, argc, 1, 1);
text = scriptArgString(runtime, args, argc, 0);
result = scriptStringNew(runtime, text->data, text->length);
for(i = 0; i < text->length; i++) result.as.string->data[i] = scriptCharLower(text->data[i]);
return result;
}
SCRIPT_NATIVE(scriptBUpcase) {
scriptstring_t *text;
scriptvalue_t result;
size_t i;
scriptArgCount(runtime, argc, 1, 1);
text = scriptArgString(runtime, args, argc, 0);
result = scriptStringNew(runtime, text->data, text->length);
for(i = 0; i < text->length; i++) result.as.string->data[i] = scriptCharUpper(text->data[i]);
return result;
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGCASE_H
#define SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGCASE_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBChr);
SCRIPT_NATIVE(scriptBDowncase);
SCRIPT_NATIVE(scriptBUpcase);
#endif
@@ -0,0 +1,69 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "builtin/string/scriptbstringsearch.h"
#include "builtin/scriptarg.h"
#include "value/scriptvalueprimitive.h"
#include "string/scriptstringchar.h"
#include "string/scriptstringsearch.h"
#include <string.h>
SCRIPT_NATIVE(scriptBStrIndex) {
scriptArgCount(runtime, argc, 2, 2);
return scriptNumber((float)scriptStringSearch(scriptArgString(runtime, args, argc, 0), scriptArgString(runtime, args, argc, 1)));
}
SCRIPT_NATIVE(scriptBContains) {
scriptArgCount(runtime, argc, 2, 2);
return scriptBool(scriptStringSearch(scriptArgString(runtime, args, argc, 0), scriptArgString(runtime, args, argc, 1)) != -1);
}
SCRIPT_NATIVE(scriptBStartsWith) {
scriptstring_t *text;
scriptstring_t *piece;
scriptArgCount(runtime, argc, 2, 2);
text = scriptArgString(runtime, args, argc, 0);
piece = scriptArgString(runtime, args, argc, 1);
return scriptBool(piece->length <= text->length && memoryCompare(text->data, piece->data, piece->length) == 0);
}
SCRIPT_NATIVE(scriptBEndsWith) {
scriptstring_t *text;
scriptstring_t *piece;
scriptArgCount(runtime, argc, 2, 2);
text = scriptArgString(runtime, args, argc, 0);
piece = scriptArgString(runtime, args, argc, 1);
return scriptBool(
piece->length <= text->length &&
memoryCompare(text->data + text->length - piece->length, piece->data, piece->length) == 0
);
}
SCRIPT_NATIVE(scriptBStringEquals) {
scriptstring_t *a;
scriptstring_t *b;
scriptArgCount(runtime, argc, 2, 2);
a = scriptArgString(runtime, args, argc, 0);
b = scriptArgString(runtime, args, argc, 1);
return scriptBool(a->length == b->length && memoryCompare(a->data, b->data, a->length) == 0);
}
SCRIPT_NATIVE(scriptBStringEqualsInsensitive) {
scriptstring_t *a;
scriptstring_t *b;
size_t i;
scriptArgCount(runtime, argc, 2, 2);
a = scriptArgString(runtime, args, argc, 0);
b = scriptArgString(runtime, args, argc, 1);
if(a->length != b->length) return scriptBool(false);
for(i = 0; i < a->length; i++) {
if(scriptCharLower(a->data[i]) != scriptCharLower(b->data[i])) return scriptBool(false);
}
return scriptBool(true);
}
@@ -0,0 +1,27 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGSEARCH_H
#define SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGSEARCH_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
// Strings
SCRIPT_NATIVE(scriptBStrIndex);
SCRIPT_NATIVE(scriptBContains);
SCRIPT_NATIVE(scriptBStartsWith);
SCRIPT_NATIVE(scriptBEndsWith);
SCRIPT_NATIVE(scriptBStringEquals);
SCRIPT_NATIVE(scriptBStringEqualsInsensitive);
#endif
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "util/memory.h"
#include "scripttypes.h"
#include "builtin/string/scriptbstringsplit.h"
#include "builtin/scriptarg.h"
#include "array/scriptarraynew.h"
#include "array/scriptarraymodify.h"
#include "string/scriptstringnew.h"
#include "value/scriptfunction.h"
#include <string.h>
SCRIPT_NATIVE(scriptBSplit) {
scriptstring_t *text;
scriptstring_t *separator;
scriptvalue_t result = scriptArrayNew(runtime);
size_t start = 0;
size_t i;
scriptArgCount(runtime, argc, 2, 2);
text = scriptArgString(runtime, args, argc, 0);
separator = scriptArgString(runtime, args, argc, 1);
// An empty separator splits into single chars
if(separator->length == 0) {
for(i = 0; i < text->length; i++) scriptArrayPush(runtime, result.as.array, scriptStringNew(runtime, text->data + i, 1));
return result;
}
for(i = 0; i + separator->length <= text->length;) {
if(memoryCompare(text->data + i, separator->data, separator->length) == 0) {
scriptArrayPush(runtime, result.as.array, scriptStringNew(runtime, text->data + start, i - start));
i += separator->length;
start = i;
} else {
i++;
}
}
scriptArrayPush(runtime, result.as.array, scriptStringNew(runtime, text->data + start, text->length - start));
return result;
}
@@ -0,0 +1,16 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGSPLIT_H
#define SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGSPLIT_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBSplit);
#endif
@@ -0,0 +1,26 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "builtin/string/scriptbstringtrim.h"
#include "builtin/scriptarg.h"
#include "string/scriptstringchar.h"
SCRIPT_NATIVE(scriptBLtrim) {
scriptArgCount(runtime, argc, 1, 1);
return scriptStringTrim(runtime, scriptArgString(runtime, args, argc, 0), true, false);
}
SCRIPT_NATIVE(scriptBRtrim) {
scriptArgCount(runtime, argc, 1, 1);
return scriptStringTrim(runtime, scriptArgString(runtime, args, argc, 0), false, true);
}
SCRIPT_NATIVE(scriptBTrim) {
scriptArgCount(runtime, argc, 1, 1);
return scriptStringTrim(runtime, scriptArgString(runtime, args, argc, 0), true, true);
}
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGTRIM_H
#define SCRIPT_BUILTIN_STRING_SCRIPTBSTRINGTRIM_H
#include "core/scriptcore.h"
#include "builtin/scriptbuiltin.h"
SCRIPT_NATIVE(scriptBLtrim);
SCRIPT_NATIVE(scriptBRtrim);
SCRIPT_NATIVE(scriptBTrim);
#endif
@@ -0,0 +1,191 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BYTECODE_SCRIPTBYTECODE_H
#define SCRIPT_BYTECODE_SCRIPTBYTECODE_H
#include "core/scriptcore.h"
// A compiled script, a .dsb file, is laid out like this. Numbers are little
// endian and nothing needs aligning.
//
// "DSB" magic
// u8 version SCRIPT_BINARY_VERSION
// u16 stringCount
// u16 functionCount function 0 is the script itself
// u16 codeSize
// strings u16 length, the bytes, a zero byte
// functions u8 flags, u8 paramCount, u8 localCount, u8 stackMax,
// u16 bodyOffset, then for each parameter u16 defaultOffset
// code the instructions
//
// What is only for errors is in the map of the script, see SCRIPT_MAP_VERSION and scriptBuilderMap:
//
// "DSM" magic
// u8 version SCRIPT_MAP_VERSION
// u16 codeSize, u16 check of the code it is for, a map of other code is not used
// u16 functionCount
// u16 positionSize
// source the file the script was compiled from, u16 length, the bytes, a zero byte
// functions the name as the source is, u16 line, u16 column, u8 localCount, then the name of
// each variable, the parameters first
// positions where in the source the code is, see below
//
// The variables of a function are numbered, and they live in a block of values
// next to its stack. The parameters are the first ones. The names are only there
// for error messages. The exception is the variables at the top level of the
// script itself, and the names a function does not have a variable for. Those
// are found by name once, when the script loads, and each instruction that has
// one of them has the variable. They are globals, the variables of the script
// itself are the script's own when it is an include.
//
// Positions are only looked at when there is an error. They are a list, in the
// order of the code, of the places where the line and column of the source
// change: a varint for how far into the code it is since the last place, a
// zigzag varint for how the line changed, and a varint for the column. The code
// at an offset has the position of the last place at or before it.
//
// All the functions share one code block, the offsets point into it. The script
// is a stack machine: instructions take their operands from a stack of values
// and leave their result on it.
#define SCRIPT_BINARY_VERSION 19
// The map of a compiled script, a .map file next to the .dsb. It is for errors: the name of the file the
// script was compiled from, the names of the functions and their variables, and where in the source the
// code is. A platform that has no .map has a script that runs the same, and errors that say less.
#define SCRIPT_MAP_VERSION 1
// Flags of a function.
#define SCRIPT_BINARY_FLAG_ASYNC 1
// An offset or string index that points at nothing, a parameter with no default.
#define SCRIPT_BINARY_NONE 0xFFFF
typedef enum {
// Constants.
SCRIPT_BC_PUSH_UNDEFINED,
SCRIPT_BC_PUSH_NULL,
SCRIPT_BC_PUSH_TRUE,
SCRIPT_BC_PUSH_FALSE,
SCRIPT_BC_PUSH_INT, // i16, a whole number that fits
SCRIPT_BC_PUSH_NUMBER, // i32, the bits of a float
SCRIPT_BC_PUSH_STRING, // u16 string
// Names and literals.
SCRIPT_BC_LOAD_VAR, // u16 name, looked up in the scope
SCRIPT_BC_LOAD_LOCAL, // u8 variable
SCRIPT_BC_NEW_ARRAY, // u16 count; the array is made with room for as many items as it is written with
SCRIPT_BC_ARRAY_PUSH, // value, array -> array
SCRIPT_BC_NEW_STRUCT,
SCRIPT_BC_STRUCT_SET, // u16 key; value, struct -> struct
SCRIPT_BC_MAKE_FUNCTION, // u16 function
// Operators.
SCRIPT_BC_NOT,
SCRIPT_BC_NEGATE,
SCRIPT_BC_INVERT,
SCRIPT_BC_BINARY, // u8 token; a, b -> result
SCRIPT_BC_BINARY_INT, // u8 token, i16 constant; a -> result, a BINARY with a small number on the right
SCRIPT_BC_AND_JUMP, // u16 target; false and jump when the top is falsy
SCRIPT_BC_OR_JUMP, // u16 target; true and jump when the top is truthy
SCRIPT_BC_TO_BOOL,
// Assignment. The last byte is the token (= or a compound assignment) or the mode, and its top bit,
// SCRIPT_BC_DISCARD, says nothing is left on the stack, the value is not wanted.
SCRIPT_BC_ASSIGN_VAR, // u16 name, u8 token; value -> value
SCRIPT_BC_ASSIGN_LOCAL, // u8 variable, u8 token; value -> value
SCRIPT_BC_ASSIGN_INDEX, // u8 token; container, key, value -> value
SCRIPT_BC_ASSIGN_MEMBER, // u16 name, u8 token; container, value -> value
SCRIPT_BC_INCDEC_VAR, // u16 name, u8 mode
SCRIPT_BC_INCDEC_LOCAL, // u8 variable, u8 mode
SCRIPT_BC_INCDEC_INDEX, // u8 mode; container, key -> value
SCRIPT_BC_INCDEC_MEMBER, // u16 name, u8 mode; container -> value
// Calls, a call needs the function, then this, then the arguments.
SCRIPT_BC_CALL, // u8 argc, u16 name of the callee; function, args -> result
SCRIPT_BC_INDEX,
SCRIPT_BC_MEMBER, // u16 name
SCRIPT_BC_AWAIT,
// Statements.
SCRIPT_BC_POP,
SCRIPT_BC_DECLARE, // u16 name; value ->, in the scope, fails when it is there
SCRIPT_BC_DECLARE_LOCAL, // u8 variable; value ->
SCRIPT_BC_DECLARE_FAIL, // u16 name; value ->, a variable that is declared twice
SCRIPT_BC_JUMP, // u16 target
SCRIPT_BC_JUMP_IF_FALSE, // u16 target; value ->
SCRIPT_BC_JUMP_IF_TRUE, // u16 target; value ->
SCRIPT_BC_COMPARE_JUMP, // u8 token, u16 target; a, b ->, jumps when the comparison is false
SCRIPT_BC_COMPARE_INT_JUMP, // u8 token, i16 constant, u16 target; a ->, jumps when the comparison is false
SCRIPT_BC_COMPARE_LOCAL_INT_JUMP, // u8 variable, u8 token, i16 constant, u16 target; jumps when the comparison of the variable is false
SCRIPT_BC_JUMP_IF_EQUAL, // u16 target; subject, value -> subject, or nothing when it jumps
SCRIPT_BC_FOREACH_BEGIN,
SCRIPT_BC_FOREACH_NEXT, // u8 variable, u16 end
SCRIPT_BC_FUNC_DECL, // u16 name, u16 function
SCRIPT_BC_RETURN, // value ->
SCRIPT_BC_END, // ends a default value, value ->
// A call of a native with a signature (natives.json) by its number, after the compiler has put
// the arguments on the stack with their defaults, which it saw were what the native takes
// as far as it could, and the machine checks the others.
SCRIPT_BC_CALL_NATIVE, // u8 native, u8 argc, u8 check; placeholder, args -> result, check has a bit for each argument that is looked at
// Constants (const): mode 0 makes the array or struct on top a constant, which nothing changes after,
// and what it holds that is an array or a struct has to be a constant already. Mode 1 fails when
// the value on top is an array or a struct that is not a constant, for a const set to an expression.
SCRIPT_BC_FREEZE, // u8 mode; value -> value
// A variable set to itself plus or minus a small number as a statement, x += 1 and x = x - 2, one
// instruction in place of a load, an add and an assignment. The token is + or -.
SCRIPT_BC_ASSIGN_LOCAL_INT, // u8 variable, u8 token, i16 constant
// A variable and a small number, or two variables, that are an operator's operands: what a load, a load or a
// number and a BINARY (or a comparison and a jump) were. The result is pushed.
SCRIPT_BC_BINARY_LOCAL_INT, // u8 variable, u8 token, i16 constant; -> result
SCRIPT_BC_BINARY_LOCAL_LOCAL, // u8 variable, u8 variable, u8 token; -> result
SCRIPT_BC_COMPARE_LOCAL_LOCAL_JUMP, // u8 variable, u8 variable, u8 token, u16 target; jumps when the comparison is false
// A call of a built in function that takes one value and says something of it (isnull, len, abs, see scriptintrinsic.h):
// the value is replaced by what it says.
SCRIPT_BC_INTRINSIC, // u8 intrinsic; value -> result
// A variable set to what an operator makes of a variable and a small number, or of two variables, or to another
// variable, as a statement: what the BINARY_LOCAL_* and an assignment were, with nothing pushed and taken off.
SCRIPT_BC_SET_LOCAL_BINARY_INT, // u8 variable, u8 variable, u8 token, i16 constant
SCRIPT_BC_SET_LOCAL_BINARY_LOCAL, // u8 variable, u8 variable, u8 variable, u8 token
SCRIPT_BC_SET_LOCAL_LOCAL, // u8 variable, u8 variable
// The value on top and a variable, which is a load and a BINARY: the top is what the operator makes of it and the variable.
SCRIPT_BC_BINARY_TOP_LOCAL, // u8 variable, u8 token; value -> result
// A member or an item of a variable, which is a load and a MEMBER or an INDEX: pushed.
SCRIPT_BC_MEMBER_LOCAL, // u8 variable, u16 name; -> value
SCRIPT_BC_INDEX_LOCAL_INT, // u8 variable, i16 index; -> value
SCRIPT_BC_INDEX_LOCAL_LOCAL, // u8 variable, u8 variable; -> value
// A key of a struct that is written out, when no key before it in the struct has the same name: it is added without looking
// for one that is there.
SCRIPT_BC_STRUCT_ADD, // u16 key; value, struct -> struct
SCRIPT_BC_COUNT
} scriptbytecode_t;
// Modes of the increment and decrement instructions.
#define SCRIPT_BC_INCDEC_DECREMENT 1
#define SCRIPT_BC_INCDEC_PREFIX 2
// Set in the token or the mode of an assignment or an increment when its value is
// not wanted, which saves putting it on the stack and taking it off again.
#define SCRIPT_BC_DISCARD 0x80
// Set in the token or the mode of an assignment or an increment when its value is
// not wanted, which saves putting it on the stack and taking it off again.
#define SCRIPT_BC_DISCARD 0x80
#endif
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "core/scriptcore.h"
#include "bytecode/scriptbytecodesize.h"
const uint8_t scriptBytecodeSize[SCRIPT_BC_COUNT] = {
1 /* PUSH_UNDEFINED */,
1 /* PUSH_NULL */,
1 /* PUSH_TRUE */,
1 /* PUSH_FALSE */,
3 /* PUSH_INT */,
5 /* PUSH_NUMBER */,
3 /* PUSH_STRING */,
3 /* LOAD_VAR */,
2 /* LOAD_LOCAL */,
3 /* NEW_ARRAY */,
1 /* ARRAY_PUSH */,
1 /* NEW_STRUCT */,
3 /* STRUCT_SET */,
3 /* MAKE_FUNCTION */,
1 /* NOT */,
1 /* NEGATE */,
1 /* INVERT */,
2 /* BINARY */,
4 /* BINARY_INT */,
3 /* AND_JUMP */,
3 /* OR_JUMP */,
1 /* TO_BOOL */,
4 /* ASSIGN_VAR */,
3 /* ASSIGN_LOCAL */,
2 /* ASSIGN_INDEX */,
4 /* ASSIGN_MEMBER */,
4 /* INCDEC_VAR */,
3 /* INCDEC_LOCAL */,
2 /* INCDEC_INDEX */,
4 /* INCDEC_MEMBER */,
4 /* CALL */,
1 /* INDEX */,
3 /* MEMBER */,
1 /* AWAIT */,
1 /* POP */,
3 /* DECLARE */,
2 /* DECLARE_LOCAL */,
3 /* DECLARE_FAIL */,
3 /* JUMP */,
3 /* JUMP_IF_FALSE */,
3 /* JUMP_IF_TRUE */,
4 /* COMPARE_JUMP */,
6 /* COMPARE_INT_JUMP */,
7 /* COMPARE_LOCAL_INT_JUMP */,
3 /* JUMP_IF_EQUAL */,
1 /* FOREACH_BEGIN */,
4 /* FOREACH_NEXT */,
5 /* FUNC_DECL */,
1 /* RETURN */,
1 /* END */,
4 /* CALL_NATIVE */,
2 /* FREEZE */,
5 /* ASSIGN_LOCAL_INT */,
5 /* BINARY_LOCAL_INT */,
4 /* BINARY_LOCAL_LOCAL */,
6 /* COMPARE_LOCAL_LOCAL_JUMP */,
2 /* INTRINSIC */,
6 /* SET_LOCAL_BINARY_INT */,
5 /* SET_LOCAL_BINARY_LOCAL */,
3 /* SET_LOCAL_LOCAL */,
3 /* BINARY_TOP_LOCAL */,
4 /* MEMBER_LOCAL */,
4 /* INDEX_LOCAL_INT */,
3 /* INDEX_LOCAL_LOCAL */,
3 /* STRUCT_ADD */
};
@@ -0,0 +1,20 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BYTECODE_SCRIPTBYTECODESIZE_H
#define SCRIPT_BYTECODE_SCRIPTBYTECODESIZE_H
#include "core/scriptcore.h"
#include "bytecode/scriptbytecode.h"
/**
* How many bytes each instruction takes, its operands included, indexed by the
* instruction.
*/
extern const uint8_t scriptBytecodeSize[SCRIPT_BC_COUNT];
#endif
@@ -0,0 +1,44 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "core/scriptcore.h"
#include "bytecode/scriptvarint.h"
uint8_t scriptVarintWrite(uint8_t *out, const uint16_t value) {
uint16_t rest = value;
uint8_t length = 0;
while(rest >= 128) {
out[length++] = (uint8_t)((rest & 127) | 128);
rest = (uint16_t)(rest >> 7);
}
out[length++] = (uint8_t)rest;
return length;
}
uint8_t scriptVarintRead(const uint8_t *in, uint16_t *value) {
uint16_t result = 0;
uint8_t length = 0;
uint8_t shift = 0;
uint8_t byte;
do {
byte = in[length++];
result = (uint16_t)(result | ((uint16_t)(byte & 127) << shift));
shift += 7;
} while((byte & 128) && length < 3);
*value = result;
return length;
}
uint16_t scriptZigzag(const int16_t value) {
return value < 0 ? (uint16_t)(((uint16_t)(-(value + 1)) << 1) | 1) : (uint16_t)((uint16_t)value << 1);
}
int16_t scriptUnzigzag(const uint16_t value) {
return (value & 1) ? (int16_t)(-(int16_t)(value >> 1) - 1) : (int16_t)(value >> 1);
}
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_BYTECODE_SCRIPTVARINT_H
#define SCRIPT_BYTECODE_SCRIPTVARINT_H
#include "core/scriptcore.h"
/**
* Writes a number in as few bytes as it needs, seven bits at a time with the
* top bit saying there is more.
*
* @param out Where to write, it needs 3 bytes of room.
* @param value The number.
* @returns How many bytes were written.
*/
uint8_t scriptVarintWrite(uint8_t *out, const uint16_t value);
/**
* Reads a number written by scriptVarintWrite.
*
* @param in Where to read from.
* @param value Receives the number.
* @returns How many bytes were read.
*/
uint8_t scriptVarintRead(const uint8_t *in, uint16_t *value);
/**
* Folds a signed number into an unsigned one so small numbers of either sign are
* small: 0, -1, 1, -2 become 0, 1, 2, 3.
*
* @param value The signed number.
* @returns The folded number.
*/
uint16_t scriptZigzag(const int16_t value);
/**
* Undoes scriptZigzag.
*
* @param value The folded number.
* @returns The signed number.
*/
int16_t scriptUnzigzag(const uint16_t value);
#endif
+77
View File
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "call/scriptcall.h"
#include "promise/scriptpromisenew.h"
#include "coroutine/scriptstackcheck.h"
#include "error/scriptraise.h"
#include "value/scriptvaluecompare.h"
#include "value/scriptfunction.h"
void scriptRunScriptFunction(
scriptruntime_t *runtime,
scriptfunction_t *function,
const scriptvalue_t *args,
const uint8_t argc,
scriptvalue_t *result
) {
if(!function->executor) scriptRaise(runtime, "%s has nothing to run it", function->name);
function->executor->run(runtime, function, args, argc, result);
}
void scriptCallFunctionRef(
scriptruntime_t *runtime,
const scriptvalue_t *function,
const scriptvalue_t *args,
const uint8_t argc,
scriptvalue_t *result
) {
scriptfunction_t *fn;
scriptcoroutine_t *co = runtime->current;
scriptvalue_t returned;
scriptvalue_t promise;
const char *savedNative;
uint8_t i;
scriptStackCheck(runtime);
if(function->type != SCRIPT_VALUE_FUNCTION) scriptRaise(runtime, "%s is not a function", scriptTypeName(*function));
fn = function->as.function;
if(fn->native) {
savedNative = co->nativeName;
co->nativeName = fn->name;
returned = fn->native(runtime, args, argc, fn);
co->nativeName = savedNative;
*result = returned;
return;
}
if(fn->isAsync) {
promise = scriptPromiseNew(runtime);
promise.as.promise->hasTask = true;
promise.as.promise->taskFunction = *function;
promise.as.promise->taskArgc = argc;
for(i = 0; i < argc; i++) promise.as.promise->taskArgs[i] = args[i];
*result = promise;
return;
}
scriptRunScriptFunction(runtime, fn, args, argc, result);
}
scriptvalue_t scriptCallFunction(
scriptruntime_t *runtime,
const scriptvalue_t function,
const scriptvalue_t *args,
const uint8_t argc
) {
scriptvalue_t result;
scriptCallFunctionRef(runtime, &function, args, argc, &result);
return result;
}
+70
View File
@@ -0,0 +1,70 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_CALL_SCRIPTCALL_H
#define SCRIPT_CALL_SCRIPTCALL_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
#include "value/scriptfunction.h"
/**
* Runs the body of a script function right now, in the current coroutine, with
* whichever executor made the function.
*
* @param runtime The runtime.
* @param function The function object.
* @param args The arguments.
* @param argc Number of arguments.
* @param result Receives what the function returned.
*/
void scriptRunScriptFunction(
scriptruntime_t *runtime,
scriptfunction_t *function,
const scriptvalue_t *args,
const uint8_t argc,
scriptvalue_t *result
);
/**
* Calls a function value without copying the values around, which is what the
* virtual machine does. Everything else is as scriptCallFunction. The result may
* be where the function is.
*
* @param runtime The runtime.
* @param function The function to call, anything else is a fatal error.
* @param args The arguments.
* @param argc Number of arguments.
* @param result Receives what the function returned.
*/
void scriptCallFunctionRef(
scriptruntime_t *runtime,
const scriptvalue_t *function,
const scriptvalue_t *args,
const uint8_t argc,
scriptvalue_t *result
);
/**
* Calls a function value. Script functions count towards the call depth,
* natives do not. Async functions give back a promise that starts when it is
* awaited.
*
* @param runtime The runtime.
* @param function The function to call, anything else is a fatal error.
* @param args The arguments.
* @param argc Number of arguments.
* @returns What the function returned.
*/
scriptvalue_t scriptCallFunction(
scriptruntime_t *runtime,
const scriptvalue_t function,
const scriptvalue_t *args,
const uint8_t argc
);
#endif
+57
View File
@@ -0,0 +1,57 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_CORE_SCRIPTCORE_H
#define SCRIPT_CORE_SCRIPTCORE_H
#include <stddef.h>
#include <stdint.h>
#include <stdbool.h>
#include "core/scriptplatform.h"
// Limits, see script.md. Going over any of them is a fatal error.
#define SCRIPT_CALL_DEPTH_MAX 8
#define SCRIPT_STRUCT_KEYS_MAX 16
#define SCRIPT_STRING_LENGTH_MAX 512
#define SCRIPT_ARRAY_LENGTH_MAX 512
#define SCRIPT_PARAMS_MAX 8
// Sizes used by errors and stack traces.
#define SCRIPT_NAME_MAX 32
#define SCRIPT_FILE_MAX 96
#define SCRIPT_ERROR_MESSAGE_MAX 160
#define SCRIPT_ERROR_FRAMES_MAX 24
typedef enum {
SCRIPT_OK,
SCRIPT_ERROR_SYNTAX,
SCRIPT_ERROR_RUNTIME,
SCRIPT_ERROR_UNSUPPORTED
} scriptresult_t;
typedef struct {
char name[SCRIPT_NAME_MAX];
char file[SCRIPT_FILE_MAX];
uint16_t line;
uint16_t column;
} scriptframeinfo_t;
typedef struct {
scriptresult_t code;
char message[SCRIPT_ERROR_MESSAGE_MAX];
uint8_t frameCount;
scriptframeinfo_t frames[SCRIPT_ERROR_FRAMES_MAX];
} scripterror_t;
#endif
+162
View File
@@ -0,0 +1,162 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_CORE_SCRIPTPLATFORM_H
#define SCRIPT_CORE_SCRIPTPLATFORM_H
// What the platform the script engine is built for can do, and the sizes its
// numbers come in. Every one of the feature switches can be set from the
// compiler command line, so a feature of a small platform can be tried on a big
// one.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "util/memory.h"
#ifdef __65816__
// The Super Nintendo, built with PVSnesLib's 816-tcc. An int is 16 bits and a
// long long is 32, there is nothing bigger, no ucontext, no maths library and no
// #pragma once.
#define SCRIPT_PLATFORM_SNES 1
#ifndef SCRIPT_FEATURE_COROUTINES
#define SCRIPT_FEATURE_COROUTINES 0
#endif
#ifndef SCRIPT_FEATURE_MATH
#define SCRIPT_FEATURE_MATH 0
#endif
// There is nowhere to print to on the Super Nintendo, so print is not a function
// there, a script that calls it is calling something that is not defined. The test
// ROM puts it back, to log what the test scripts print.
#ifndef SCRIPT_FEATURE_PRINT
#define SCRIPT_FEATURE_PRINT 0
#endif
// The compiler has checked what it could of the arguments of the natives that have a
// signature (natives.json), so the machine only checks the rest and the natives take what they
// are given as what it should be. A platform that has the room does all of it as it runs.
#ifndef SCRIPT_FEATURE_TRUST_BYTECODE
#define SCRIPT_FEATURE_TRUST_BYTECODE 1
#endif
// The widest numbers there are. A bignum on this platform only has 32 bits.
typedef int32_t scriptwide_t;
typedef uint32_t scriptuwide_t;
// Pointers are 24 bits and turning one into a number loses the bank, so
// nothing here does that: places in the heap are offsets from its start, and
// the collector looks for pointers every 2 bytes.
#define SCRIPT_SCAN_STEP 2
#define SCRIPT_STATIC_ASSERT(condition, message)
// Every block of the heap has a header of 8 bytes and starts on 4. A size is 16 bits,
// and the check that a block is a block, which the collector uses to tell a stray
// number from a block, is 16 bits as well.
#define SCRIPT_HEAP_ALIGN 4
// The collector does not run before the heap is this much of full: a script that is
// small never needs it, and a run of it takes about a tenth of a second here.
#define SCRIPT_GC_MINIMUM(heapSize) ((heapSize) / 2)
typedef uint16_t scriptheapsize_t;
#define SCRIPT_HEAP_PADDING 0
#define SCRIPT_HEAP_CHECK 0x3CC3u
// A fill written in assembly, which is quicker than the library's, it is in
// src/dusksnes/script/scriptmem.asm.
void *scriptMemFill(void *destination, int value, size_t length);
#define SCRIPT_MEMSET(destination, value, length) scriptMemFill(destination, value, length)
// The smallest block of memory a runtime can be given, how many things the
// collector can have waiting at once, how many timers can be waiting, and the
// most source text that is read from a file.
#define SCRIPT_RUNTIME_MIN 16384
#define SCRIPT_GC_STACK_MAX 128
#define SCRIPT_TIMERS_MAX 4
#define SCRIPT_SOURCE_MAX 2048
// How many values the variables and stacks of the functions running at once
// can take together, in each coroutine.
#define SCRIPT_VM_VALUES_MAX 160
// Fills a variable up to 16 bytes, see scriptvariable_t.
#define SCRIPT_VARIABLE_PADDING 4
#define SCRIPT_FRAME_PADDING 12
// The global variables, a compiled script finds each one once when it loads
// and keeps a pointer to it, so they have room for all of them to start with.
#define SCRIPT_GLOBALS_MAX 48
// Room for the functions and the constants the game adds to the built in ones
#define SCRIPT_BUILTINS_SPARE 32
// The compiler keeps values in the stack, there are no registers to flush.
#define SCRIPT_UNWIND_INIT()
// How much of the stack a script may use before it is called too deep, the
// stack is 8 KB and the rest of the program needs some.
#define SCRIPT_STACK_BUDGET 4096
#else
#define SCRIPT_PLATFORM_SNES 0
#ifndef SCRIPT_FEATURE_COROUTINES
#define SCRIPT_FEATURE_COROUTINES 1
#endif
#ifndef SCRIPT_FEATURE_MATH
#define SCRIPT_FEATURE_MATH 1
#endif
#ifndef SCRIPT_FEATURE_PRINT
#define SCRIPT_FEATURE_PRINT 1
#endif
#ifndef SCRIPT_FEATURE_TRUST_BYTECODE
#define SCRIPT_FEATURE_TRUST_BYTECODE 0
#endif
typedef int64_t scriptwide_t;
typedef uint64_t scriptuwide_t;
// The collector looks for pointers where a pointer can be.
#define SCRIPT_SCAN_STEP sizeof(void *)
#define SCRIPT_STATIC_ASSERT(condition, message) _Static_assert(condition, message)
#define SCRIPT_MEMSET(destination, value, length) memorySet(destination, value, length)
#define SCRIPT_RUNTIME_MIN 262144
#define SCRIPT_GC_STACK_MAX 1024
#define SCRIPT_TIMERS_MAX 32
#define SCRIPT_SOURCE_MAX 32768
#define SCRIPT_VM_VALUES_MAX 1536
// Fills a variable up to 32 bytes, see scriptvariable_t.
#define SCRIPT_VARIABLE_PADDING 8
#define SCRIPT_FRAME_PADDING 8
#define SCRIPT_GLOBALS_MAX 128
#define SCRIPT_BUILTINS_SPARE 64
// Blocks of the heap have a header of 32 bytes and start on 16.
#define SCRIPT_HEAP_ALIGN 16
#define SCRIPT_GC_MINIMUM(heapSize) ((heapSize) / 8)
typedef uint32_t scriptheapsize_t;
#define SCRIPT_HEAP_PADDING 14
#define SCRIPT_HEAP_CHECK 0x5A5AA5A5C3C33C3CULL
// Makes the compiler put every register it is keeping a value in on the
// stack, so a search of the stack sees them.
#define SCRIPT_UNWIND_INIT() __builtin_unwind_init()
// The stack of the script when it can not have a stack of its own.
#define SCRIPT_STACK_BUDGET 65536
#endif
// A place in the heap, counted in bytes from the start of it.
typedef size_t scriptoffset_t;
// Most values a function has on its stack at once, the compiler checks it.
#define SCRIPT_VM_STACK_MAX 64
#endif
+23
View File
@@ -0,0 +1,23 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_CORE_SCRIPTSOURCE_H
#define SCRIPT_CORE_SCRIPTSOURCE_H
#include "core/scriptcore.h"
// A script's text and the name shown for it in errors.
typedef struct scriptsource_t {
char name[SCRIPT_FILE_MAX];
char path[SCRIPT_FILE_MAX];
const char *text;
size_t length;
} scriptsource_t;
struct scriptsource_t;
#endif
@@ -0,0 +1,59 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTCOROUTINE_H
#define SCRIPT_COROUTINE_SCRIPTCOROUTINE_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
#include "coroutine/scriptframe.h"
#include <setjmp.h>
#if SCRIPT_FEATURE_COROUTINES
#include <ucontext.h>
#endif
#define SCRIPT_STACK_SIZE 65536
typedef enum {
SCRIPT_COROUTINE_READY,
SCRIPT_COROUTINE_RUNNING,
SCRIPT_COROUTINE_WAITING,
SCRIPT_COROUTINE_DONE,
SCRIPT_COROUTINE_FAILED
} scriptcostate_t;
// A script that can be paused at an await. Where there is no way to switch stacks
// (SCRIPT_FEATURE_COROUTINES is 0) it runs on the stack of whoever starts it, to
// the end, and an await can only wait for what can be run right then.
typedef struct scriptcoroutine_t {
struct scriptcoroutine_t *nextAll;
struct scriptcoroutine_t *prevAll;
#if SCRIPT_FEATURE_COROUTINES
ucontext_t context;
void *stack;
#endif
jmp_buf errorJump;
scriptcostate_t state;
struct scriptcoroutine_t *nextReady;
scriptvalue_t function;
scriptvalue_t args[SCRIPT_PARAMS_MAX];
uint8_t argc;
scriptpromise_t *promise;
scriptframe_t frames[SCRIPT_CALL_DEPTH_MAX + 1];
uint8_t depth; // number of frames in use, frames[depth - 1] is the current one
const char *nativeName;
// The variables and the stack of the functions the virtual machine is running
// for this coroutine, one after the other. The block is made when the first
// function starts.
scriptvalue_t *values;
uint16_t valuesTop;
} scriptcoroutine_t;
struct scriptcoroutine_t;
#endif
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "coroutine/scriptcoroutineready.h"
void scriptReadyPush(scriptruntime_t *runtime, scriptcoroutine_t *coroutine) {
coroutine->state = SCRIPT_COROUTINE_READY;
coroutine->nextReady = NULL;
if(runtime->readyTail) runtime->readyTail->nextReady = coroutine;
else runtime->readyHead = coroutine;
runtime->readyTail = coroutine;
}
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTCOROUTINEREADY_H
#define SCRIPT_COROUTINE_SCRIPTCOROUTINEREADY_H
#include "core/scriptcore.h"
#include "coroutine/scriptcoroutine.h"
/**
* Puts a coroutine in the ready queue.
*
* @param runtime The runtime.
* @param coroutine The coroutine, it is marked ready.
*/
void scriptReadyPush(scriptruntime_t *runtime, scriptcoroutine_t *coroutine);
#endif
@@ -0,0 +1,106 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "coroutine/scriptcoroutinespawn.h"
#include "value/scriptalloc.h"
#include "promise/scriptpromisesettle.h"
#include "coroutine/scriptcoroutineready.h"
#include "call/scriptcall.h"
#include "value/scriptvalueprimitive.h"
#include "value/scriptfunction.h"
#include "scope/scriptscopeset.h"
#include "runtime/scriptruntimerun.h"
#include <setjmp.h>
#if SCRIPT_FEATURE_COROUTINES
#include <ucontext.h>
#endif
#if SCRIPT_FEATURE_COROUTINES
// makecontext can not pass a pointer on every platform, so the runtime a new
// coroutine belongs to is handed over through here just before it starts.
static scriptruntime_t *scriptEntryRuntime = NULL;
#endif
void scriptCoroutineRun(scriptruntime_t *runtime, scriptcoroutine_t *co) {
if(setjmp(co->errorJump) == 0) {
scriptvalue_t result;
scriptRunScriptFunction(runtime, co->function.as.function, co->args, co->argc, &result);
co->state = SCRIPT_COROUTINE_DONE;
// What the main script returns is kept in the global scope, where the collector sees it
if(co == runtime->root && runtime->captureResult) scriptScopeDefine(runtime, runtime->globalScope, SCRIPT_RESULT_NAME, result);
if(co->promise) scriptPromiseResolve(runtime, co->promise, result);
} else {
co->state = SCRIPT_COROUTINE_FAILED;
}
}
#if SCRIPT_FEATURE_COROUTINES
void scriptCoroutineEntry(void) {
scriptruntime_t *runtime = scriptEntryRuntime;
scriptCoroutineRun(runtime, runtime->current);
}
#endif
scriptcoroutine_t *scriptCoroutineNew(
scriptruntime_t *runtime,
const scriptvalue_t function,
const scriptvalue_t *args,
const uint8_t argc,
scriptpromise_t *promise
) {
scriptcoroutine_t *co = scriptAlloc(runtime, sizeof(scriptcoroutine_t));
uint8_t i;
co->function = function;
co->argc = argc;
for(i = 0; i < argc; i++) co->args[i] = args[i];
co->promise = promise;
#if SCRIPT_FEATURE_COROUTINES
co->stack = scriptAlloc(runtime, SCRIPT_STACK_SIZE);
getcontext(&co->context);
co->context.uc_stack.ss_sp = co->stack;
co->context.uc_stack.ss_size = SCRIPT_STACK_SIZE;
co->context.uc_link = &runtime->schedulerContext;
makecontext(&co->context, (void (*)(void))scriptCoroutineEntry, 0);
#endif
co->nextAll = runtime->coroutines;
if(runtime->coroutines) runtime->coroutines->prevAll = co;
runtime->coroutines = co;
runtime->liveCoroutines++;
return co;
}
scriptcoroutine_t *scriptCoroutineSpawn(
scriptruntime_t *runtime,
const scriptvalue_t function,
const scriptvalue_t *args,
const uint8_t argc,
scriptpromise_t *promise
) {
scriptcoroutine_t *co = scriptCoroutineNew(runtime, function, args, argc, promise);
scriptReadyPush(runtime, co);
return co;
}
void scriptCoroutineResume(scriptruntime_t *runtime, scriptcoroutine_t *coroutine) {
runtime->current = coroutine;
coroutine->state = SCRIPT_COROUTINE_RUNNING;
runtime->abortTarget = &coroutine->errorJump;
#if SCRIPT_FEATURE_COROUTINES
scriptEntryRuntime = runtime;
swapcontext(&runtime->schedulerContext, &coroutine->context);
#else
scriptCoroutineRun(runtime, coroutine);
#endif
runtime->current = NULL;
runtime->abortTarget = &runtime->schedulerJump;
}
@@ -0,0 +1,75 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTCOROUTINESPAWN_H
#define SCRIPT_COROUTINE_SCRIPTCOROUTINESPAWN_H
#include "core/scriptcore.h"
#include "coroutine/scriptcoroutine.h"
#include "value/scriptvalue.h"
/**
* Runs the function of a coroutine to the end and records how it ended, a fatal
* error ends it as failed.
*
* @param runtime The runtime.
* @param coroutine The coroutine, it is the current one.
*/
void scriptCoroutineRun(scriptruntime_t *runtime, scriptcoroutine_t *coroutine);
#if SCRIPT_FEATURE_COROUTINES
/**
* Where a new coroutine starts, it has a stack of its own to run on.
*/
void scriptCoroutineEntry(void);
#endif
/**
* Makes a coroutine, which is in the list the collector looks through and is not ready to run: whoever made it runs it.
*
* @param runtime The runtime.
* @param function The function it runs.
* @param args The arguments.
* @param argc How many there are.
* @param promise What is settled with what it returns, NULL for none.
* @returns The coroutine.
*/
scriptcoroutine_t *scriptCoroutineNew(
scriptruntime_t *runtime,
const scriptvalue_t function,
const scriptvalue_t *args,
const uint8_t argc,
scriptpromise_t *promise
);
/**
* Makes the runtime start a coroutine for a function.
*
* @param runtime The runtime.
* @param function The function value to run.
* @param args The arguments.
* @param argc Number of arguments.
* @param promise Promise settled with the result, may be NULL.
* @returns The coroutine, it is waiting in the ready queue.
*/
scriptcoroutine_t *scriptCoroutineSpawn(
scriptruntime_t *runtime,
const scriptvalue_t function,
const scriptvalue_t *args,
const uint8_t argc,
scriptpromise_t *promise
);
/**
* Runs a coroutine until it waits, finishes or fails.
*
* @param runtime The runtime.
* @param coroutine The coroutine to switch to.
*/
void scriptCoroutineResume(scriptruntime_t *runtime, scriptcoroutine_t *coroutine);
#endif
@@ -0,0 +1,32 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTFRAME_H
#define SCRIPT_COROUTINE_SCRIPTFRAME_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
#include "core/scriptsource.h"
// The virtual machine does not keep the position up to date while it runs, it
// works it out from where the frame is in the code when something wants to know,
// which is when there is an error.
//
// The name and the script of a frame are the ones of its function, they are not
// copied in for every call. The line and column are kept up to date by the
// interpreter, the machine leaves them and has the executor of the function
// work them out from locateData.
typedef struct {
const scriptfunction_t *function;
uint16_t line;
uint16_t column;
scriptvalue_t returnValue;
void *locateData;
uint8_t padding[SCRIPT_FRAME_PADDING]; // sizeof is a power of two on the SNES, the frames are indexed with a shift
} scriptframe_t;
#endif
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "coroutine/scriptframelocate.h"
void scriptFrameLocate(const scriptframe_t *frame, uint16_t *line, uint16_t *column) {
const scriptexecutor_t *executor = frame->function->executor;
*line = frame->line;
*column = frame->column;
if(executor && executor->locate && frame->locateData) executor->locate(frame->function, frame->locateData, line, column);
}
const char *scriptFrameName(const scriptframe_t *frame) {
return frame->function->name[0] ? frame->function->name : "<anonymous>";
}
const char *scriptFrameFile(const scriptframe_t *frame) {
return frame->function->source ? frame->function->source->name : "";
}
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#pragma once
#include "scripttypes.h"
/**
* Finds where a frame is in its script.
*
* @param frame The frame.
* @param line Receives the line.
* @param column Receives the column.
*/
void scriptFrameLocate(const scriptframe_t *frame, uint16_t *line, uint16_t *column);
/**
* @param frame The frame.
* @returns The name of the function of the frame, for a stack trace.
*/
const char *scriptFrameName(const scriptframe_t *frame);
/**
* @param frame The frame.
* @returns The name of the script of the frame, empty when it has none.
*/
const char *scriptFrameFile(const scriptframe_t *frame);
@@ -0,0 +1,97 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "coroutine/scriptscheduler.h"
#include "coroutine/scriptcoroutinespawn.h"
#include "value/scriptalloc.h"
#include "error/scriptraise.h"
#include "promise/scripttimer.h"
#include <setjmp.h>
scriptresult_t scriptSchedulerRun(scriptruntime_t *runtime) {
scriptcoroutine_t *co;
runtime->abortTarget = &runtime->schedulerJump;
if(setjmp(runtime->schedulerJump) != 0) {
runtime->abortTarget = NULL;
return runtime->error.code;
}
while(true) {
co = runtime->readyHead;
if(co) {
runtime->readyHead = co->nextReady;
if(!runtime->readyHead) runtime->readyTail = NULL;
scriptCoroutineResume(runtime, co);
if(co->state == SCRIPT_COROUTINE_FAILED) {
runtime->abortTarget = NULL;
return runtime->error.code;
}
if(co->state == SCRIPT_COROUTINE_DONE) scriptCoroutineReap(runtime, co);
continue;
}
if(runtime->liveCoroutines == 0) break;
if(runtime->timerCount > 0) {
scriptTimersAdvance(runtime);
continue;
}
// Everything left is waiting on something that can never happen
runtime->current = runtime->root;
scriptRaise(runtime, "The script is waiting for a promise that never settles");
}
runtime->abortTarget = NULL;
return SCRIPT_OK;
}
void scriptCoroutineReap(scriptruntime_t *runtime, scriptcoroutine_t *co) {
if(co == runtime->root) runtime->root = NULL;
if(co->prevAll) co->prevAll->nextAll = co->nextAll;
else runtime->coroutines = co->nextAll;
if(co->nextAll) co->nextAll->prevAll = co->prevAll;
#if SCRIPT_FEATURE_COROUTINES
scriptFree(runtime, co->stack);
#endif
if(co->values) scriptFree(runtime, co->values);
scriptFree(runtime, co);
runtime->liveCoroutines--;
}
#if !SCRIPT_FEATURE_COROUTINES
bool scriptSchedulerStep(scriptruntime_t *runtime) {
scriptcoroutine_t *co = runtime->readyHead;
scriptcoroutine_t *outer = runtime->current;
jmp_buf *outerAbort = runtime->abortTarget;
if(co) {
runtime->readyHead = co->nextReady;
if(!runtime->readyHead) runtime->readyTail = NULL;
scriptCoroutineResume(runtime, co);
runtime->current = outer;
runtime->abortTarget = outerAbort;
// A coroutine that failed has already said why, so the one waiting for it
// fails too without saying anything more
if(co->state == SCRIPT_COROUTINE_FAILED) longjmp(*outerAbort, 1);
if(co->state == SCRIPT_COROUTINE_DONE) scriptCoroutineReap(runtime, co);
return true;
}
if(runtime->timerCount > 0) {
scriptTimersAdvance(runtime);
return true;
}
return false;
}
#endif
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTSCHEDULER_H
#define SCRIPT_COROUTINE_SCRIPTSCHEDULER_H
#include "core/scriptcore.h"
/**
* Runs everything that is ready, and keeps going until no coroutine is left or
* something can never finish. A fatal error ends it early.
*
* @param runtime The runtime.
* @returns SCRIPT_OK, or the kind of error with the details in runtime->error.
*/
scriptresult_t scriptSchedulerRun(scriptruntime_t *runtime);
/**
* Takes a finished coroutine out of the runtime and frees it.
*
* @param runtime The runtime.
* @param co The coroutine, it has to be done.
*/
void scriptCoroutineReap(scriptruntime_t *runtime, scriptcoroutine_t *co);
#if !SCRIPT_FEATURE_COROUTINES
/**
* Runs whatever is next, a coroutine that is ready or the timer that is due
* first, from inside another coroutine. This is how an await makes progress
* when a coroutine can not be paused. A coroutine that fails fails whoever
* called this too.
*
* @param runtime The runtime.
* @returns False when there is nothing left that could run.
*/
bool scriptSchedulerStep(scriptruntime_t *runtime);
#endif
#endif
@@ -0,0 +1,17 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "coroutine/scriptsetposition.h"
void scriptSetPosition(scriptruntime_t *runtime, const uint16_t line, const uint16_t column) {
scriptcoroutine_t *co = runtime->current;
if(co && co->depth > 0) {
co->frames[co->depth - 1].line = line;
co->frames[co->depth - 1].column = column;
}
}
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTSETPOSITION_H
#define SCRIPT_COROUTINE_SCRIPTSETPOSITION_H
#include "core/scriptcore.h"
/**
* Moves the current frame's position to a node, so errors report it.
*
* @param runtime The runtime.
* @param line Line in the script.
* @param column Column in the script.
*/
void scriptSetPosition(scriptruntime_t *runtime, const uint16_t line, const uint16_t column);
#endif
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "coroutine/scriptstackcheck.h"
#include "error/scriptraise.h"
void scriptStackCheck(scriptruntime_t *runtime) {
char marker;
#if SCRIPT_FEATURE_COROUTINES
if(runtime->current && (size_t)(&marker - (char *)runtime->current->stack) < SCRIPT_STACK_RESERVE) {
scriptRaise(runtime, "The script is nested too deeply, the stack is full");
}
#else
// The stack grows down from where the script was started
if(runtime->mainStackBase && (size_t)(runtime->mainStackBase - &marker) > SCRIPT_STACK_BUDGET) {
scriptRaise(runtime, "The script is nested too deeply, the stack is full");
}
#endif
}
@@ -0,0 +1,24 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_COROUTINE_SCRIPTSTACKCHECK_H
#define SCRIPT_COROUTINE_SCRIPTSTACKCHECK_H
#include "core/scriptcore.h"
#define SCRIPT_STACK_RESERVE 8192
/**
* Raises a fatal error when the current coroutine's stack is nearly used up,
* which is what a script that nests expressions or calls far too deeply would
* otherwise do by crashing.
*
* @param runtime The runtime.
*/
void scriptStackCheck(scriptruntime_t *runtime);
#endif
+22
View File
@@ -0,0 +1,22 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_DUSKSCRIPT_H
#define SCRIPT_DUSKSCRIPT_H
// The part of dusk script every runtime shares: values, the heap, the garbage
// collector, the built in functions and running scripts. The interpreter and the
// virtual machine both build on it.
#include "core/scriptcore.h"
#include "runtime/scriptruntime.h"
#include "runtime/scriptruntimecreate.h"
#include "runtime/scriptruntimeconfig.h"
#include "runtime/scriptruntimerun.h"
#include "error/scripttrace.h"
#endif
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_DUSKSCRIPTNATIVE_H
#define SCRIPT_DUSKSCRIPTNATIVE_H
// What a native function needs, the types, the argument helpers and the value
// constructors.
#include "duskscript.h"
#include "scripttypes.h"
#include "builtin/scriptbuiltin.h"
#include "builtin/scriptarg.h"
#include "builtin/scriptbuiltinregister.h"
#include "error/scriptraise.h"
#include "value/scriptvalueprimitive.h"
#include "string/scriptstringnew.h"
#include "array/scriptarraynew.h"
#include "array/scriptarraymodify.h"
#include "struct/scriptstruct.h"
#include "promise/scriptpromisenew.h"
#include "promise/scriptpromisesettle.h"
#include "promise/scripttimer.h"
#include "runtime/scriptruntimeload.h"
#endif
+77
View File
@@ -0,0 +1,77 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "error/scriptraise.h"
#include "text/scripttext.h"
#include "coroutine/scriptframelocate.h"
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>
#include <stdlib.h>
void scriptRaise(scriptruntime_t *runtime, const char *format, ...) {
va_list args;
scripterror_t *error = &runtime->error;
scriptcoroutine_t *co = runtime->current;
int i;
SCRIPT_MEMSET(error, 0, sizeof(scripterror_t));
error->code = SCRIPT_ERROR_RUNTIME;
va_start(args, format);
scriptFormatV(error->message, sizeof(error->message), format, args);
va_end(args);
if(co) {
// A native that is running shows up as its own frame, at the line of the
// script that called it.
if(co->nativeName && co->depth > 0) {
scriptframeinfo_t *info = &error->frames[error->frameCount++];
scriptCopyText(info->name, sizeof(info->name), co->nativeName);
scriptCopyText(info->file, sizeof(info->file), scriptFrameFile(&co->frames[co->depth - 1]));
scriptFrameLocate(&co->frames[co->depth - 1], &info->line, &info->column);
}
for(i = (int)co->depth - 1; i >= 0 && error->frameCount < SCRIPT_ERROR_FRAMES_MAX; i--) {
scriptframeinfo_t *info = &error->frames[error->frameCount++];
scriptCopyText(info->name, sizeof(info->name), scriptFrameName(&co->frames[i]));
scriptCopyText(info->file, sizeof(info->file), scriptFrameFile(&co->frames[i]));
scriptFrameLocate(&co->frames[i], &info->line, &info->column);
}
}
if(!runtime->abortTarget) abort();
longjmp(*runtime->abortTarget, 1);
}
void scriptRaiseSyntax(
scriptruntime_t *runtime,
const scriptsource_t *source,
const uint16_t line,
const uint16_t column,
const char *format,
...
) {
va_list args;
scripterror_t *error = &runtime->error;
scriptframeinfo_t *info;
SCRIPT_MEMSET(error, 0, sizeof(scripterror_t));
error->code = SCRIPT_ERROR_SYNTAX;
va_start(args, format);
scriptFormatV(error->message, sizeof(error->message), format, args);
va_end(args);
info = &error->frames[error->frameCount++];
scriptCopyText(info->name, sizeof(info->name), "<parse>");
scriptCopyText(info->file, sizeof(info->file), source ? source->name : "");
info->line = line;
info->column = column;
if(!runtime->abortTarget) abort();
longjmp(*runtime->abortTarget, 1);
}
+43
View File
@@ -0,0 +1,43 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ERROR_SCRIPTRAISE_H
#define SCRIPT_ERROR_SCRIPTRAISE_H
#include "core/scriptcore.h"
#include "core/scriptsource.h"
/**
* Raises a fatal runtime error at the position the script is up to. It builds
* the stack trace and jumps out of whatever was running, it does not return.
*
* @param runtime The runtime.
* @param format Message format, see scriptFormatV.
* @param ... What the format takes.
*/
void scriptRaise(scriptruntime_t *runtime, const char *format, ...);
/**
* Raises a fatal syntax error at a position in a script, it does not return.
*
* @param runtime The runtime.
* @param source The script.
* @param line Line of the error, starting at 1.
* @param column Column of the error, starting at 1.
* @param format Message format, see scriptFormatV.
* @param ... What the format takes.
*/
void scriptRaiseSyntax(
scriptruntime_t *runtime,
const scriptsource_t *source,
const uint16_t line,
const uint16_t column,
const char *format,
...
);
#endif
+73
View File
@@ -0,0 +1,73 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "error/scripttrace.h"
#include "text/scripttext.h"
#include <string.h>
void scriptErrorFormat(const scripterror_t *error, char *buffer, const size_t size) {
size_t length = 0;
size_t remaining;
size_t lineLength;
uint8_t i;
const char *suffix;
if(size == 0) return;
buffer[0] = 0;
// Each line is formatted where it goes, and taken back when it does not fit
// whole. The text of a line can be long, so there is no room for a copy of it.
remaining = size;
// A frame has no line when the script has no map, and then there is nothing to say of where it was
if(error->frameCount > 0 && error->frames[0].line > 0) {
suffix = error->code == SCRIPT_ERROR_SYNTAX ? "" : ":";
scriptFormat(
buffer,
remaining,
"Error [%s] in %s (%u:%u)%s\n",
error->message,
error->frames[0].file,
(unsigned int)error->frames[0].line,
(unsigned int)error->frames[0].column,
suffix
);
} else {
scriptFormat(buffer, remaining, "Error [%s]\n", error->message);
}
lineLength = strlen(buffer);
if(lineLength + 1 >= remaining) {
buffer[0] = 0;
return;
}
length = lineLength;
if(error->code == SCRIPT_ERROR_SYNTAX) return;
for(i = 0; i < error->frameCount; i++) {
remaining = size - length;
if(error->frames[i].line > 0) {
scriptFormat(
buffer + length,
remaining,
" at %s (%s:%u:%u)\n",
error->frames[i].name,
error->frames[i].file,
(unsigned int)error->frames[i].line,
(unsigned int)error->frames[i].column
);
} else {
scriptFormat(buffer + length, remaining, " at %s\n", error->frames[i].name);
}
lineLength = strlen(buffer + length);
if(lineLength + 1 >= remaining) {
buffer[length] = 0;
break;
}
length += lineLength;
}
}
+28
View File
@@ -0,0 +1,28 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_ERROR_SCRIPTTRACE_H
#define SCRIPT_ERROR_SCRIPTTRACE_H
#include "core/scriptcore.h"
/**
* Writes an error as a stack trace, for example
*
* Error [Division by zero] in test.ds (14:10):
* at divide (test.ds:14:10)
* at <script> (test.ds:20:1)
*
* Syntax errors only have the first line.
*
* @param error The error to format.
* @param buffer Buffer to write the text to.
* @param size Size of the buffer in bytes.
*/
void scriptErrorFormat(const scripterror_t *error, char *buffer, const size_t size);
#endif
@@ -0,0 +1,46 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_EXECUTOR_SCRIPTEXECUTOR_H
#define SCRIPT_EXECUTOR_SCRIPTEXECUTOR_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
#include "value/scriptfunction.h"
/**
* Something that can run the body of a script function. The tree walking
* interpreter is one, the bytecode virtual machine is another, and a function
* remembers which one made it, so both kinds can call each other in one runtime.
*/
typedef struct scriptexecutor_t {
/**
* Runs the body of a script function right now, in the current coroutine.
*
* @param runtime The runtime.
* @param function The function object.
* @param args The arguments.
* @param argc Number of arguments.
* @param result Receives what the function returned. It may be where the
* function and the arguments are, they are not used after the function ends.
*/
void (*run)(
scriptruntime_t *runtime,
scriptfunction_t *function,
const scriptvalue_t *args,
const uint8_t argc,
scriptvalue_t *result
);
/**
* Finds where a frame of a function it is running is in the script, NULL when
* the line and column of the frame are always right.
*/
void (*locate)(const scriptfunction_t *function, void *data, uint16_t *line, uint16_t *column);
} scriptexecutor_t;
#endif
+13
View File
@@ -0,0 +1,13 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_GC_SCRIPTGC_H
#define SCRIPT_GC_SCRIPTGC_H
#include "core/scriptcore.h"
#endif
+90
View File
@@ -0,0 +1,90 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "gc/scriptgccollect.h"
#include "gc/scriptgcmark.h"
#include "gc/scriptgcscan.h"
#include "heap/scriptheapblock.h"
#include "heap/scriptheapsweep.h"
void scriptGcCollect(scriptruntime_t *runtime) {
scriptscope_t *scope;
scriptcoroutine_t *co;
uint16_t i;
void *block;
void *cursor;
if(runtime->gcRunning) return;
runtime->gcRunning = true;
runtime->gcCount = 0;
runtime->gcOverflow = false;
runtime->gcRuns++;
// Makes the compiler put every register it is keeping a value in on the
// stack, so the stack search below sees them
SCRIPT_UNWIND_INIT();
for(scope = runtime->scopes; scope; scope = scope->nextAll) {
for(i = 0; i < scope->count; i++) scriptGcMarkValue(runtime, scope->variables[i].value);
}
for(i = 0; i < runtime->timerCount; i++) scriptGcMarkPointer(runtime, runtime->timers[i].promise);
for(co = runtime->coroutines; co; co = co->nextAll) {
scriptGcScanRange(runtime, co, co + 1);
// The variables and stacks of the functions the virtual machine is running,
// as far as they are in use, the rest of the block holds nothing
if(co->values) {
for(i = 0; i < co->valuesTop; i++) scriptGcMarkValue(runtime, co->values[i]);
}
#if SCRIPT_FEATURE_COROUTINES
scriptGcScanRange(runtime, co->stack, (const char *)co->stack + SCRIPT_STACK_SIZE);
#endif
}
#if SCRIPT_FEATURE_COROUTINES
if(!runtime->current && runtime->mainStackBase) scriptGcScanStack(runtime, runtime->mainStackBase);
#else
// Everything runs on the one stack
if(runtime->mainStackBase) scriptGcScanStack(runtime, runtime->mainStackBase);
#endif
while(true) {
while(runtime->gcCount > 0) {
block = runtime->gcStack[--runtime->gcCount];
scriptGcScanBlock(runtime, block, scriptHeapKindOf(&runtime->heap, block));
}
// The work list filled up at some point, so find what is left by walking
if(!runtime->gcOverflow) break;
runtime->gcOverflow = false;
cursor = NULL;
while((block = scriptHeapNextPending(&runtime->heap, &cursor))) {
if(runtime->gcCount >= SCRIPT_GC_STACK_MAX) {
runtime->gcOverflow = true;
break;
}
runtime->gcStack[runtime->gcCount++] = block;
}
}
scriptHeapSweep(&runtime->heap);
scriptGcSetThreshold(runtime);
runtime->gcRunning = false;
}
void scriptGcSetThreshold(scriptruntime_t *runtime) {
// Next time around when the heap is twice what survived, within limits
runtime->gcThreshold = runtime->heap.used * 2;
if(runtime->gcThreshold < SCRIPT_GC_MINIMUM(runtime->heap.size)) runtime->gcThreshold = SCRIPT_GC_MINIMUM(runtime->heap.size);
if(runtime->gcThreshold > runtime->heap.size * 6 / 10) {
runtime->gcThreshold = runtime->heap.used + (runtime->heap.size - runtime->heap.used) / 2;
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_GC_SCRIPTGCCOLLECT_H
#define SCRIPT_GC_SCRIPTGCCOLLECT_H
#include "core/scriptcore.h"
/**
* Finds everything that is still reachable and frees the rest. Reachable means
* held by a variable, a coroutine or a promise, or sitting on one of the
* stacks the interpreter is using (those are searched conservatively, any word
* that looks like a block counts).
*
* @param runtime The runtime.
*/
void scriptGcCollect(scriptruntime_t *runtime);
/**
* Says when the next collection is, from how much of the heap is in use now.
*
* @param runtime The runtime.
*/
void scriptGcSetThreshold(scriptruntime_t *runtime);
#endif
+34
View File
@@ -0,0 +1,34 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "gc/scriptgcmark.h"
#include "heap/scriptheapblock.h"
#include "value/scriptfunction.h"
// The Super Nintendo has this in assembly, src/dusksnes/script/scriptgc.asm
#ifndef __65816__
void scriptGcMarkPointer(scriptruntime_t *runtime, const void *pointer) {
int kind = scriptHeapKindOf(&runtime->heap, pointer);
if(kind <= (int)SCRIPT_HEAP_KIND_RAW) return;
if(!scriptHeapMark(&runtime->heap, (void *)pointer)) return;
if(runtime->gcCount < SCRIPT_GC_STACK_MAX) runtime->gcStack[runtime->gcCount++] = (void *)pointer;
else runtime->gcOverflow = true;
}
#endif
void scriptGcMarkValue(scriptruntime_t *runtime, const scriptvalue_t value) {
switch(value.type) {
case SCRIPT_VALUE_STRING: scriptGcMarkPointer(runtime, value.as.string); break;
case SCRIPT_VALUE_ARRAY: scriptGcMarkPointer(runtime, value.as.array); break;
case SCRIPT_VALUE_STRUCT: scriptGcMarkPointer(runtime, value.as.structure); break;
case SCRIPT_VALUE_FUNCTION: scriptGcMarkPointer(runtime, value.as.function); break;
case SCRIPT_VALUE_PROMISE: scriptGcMarkPointer(runtime, value.as.promise); break;
default: break;
}
}
+30
View File
@@ -0,0 +1,30 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#ifndef SCRIPT_GC_SCRIPTGCMARK_H
#define SCRIPT_GC_SCRIPTGCMARK_H
#include "core/scriptcore.h"
#include "value/scriptvalue.h"
/**
* Marks the block a pointer refers to, if it refers to a live collectable one.
*
* @param runtime The runtime.
* @param pointer The address, anything may be given.
*/
void scriptGcMarkPointer(scriptruntime_t *runtime, const void *pointer);
/**
* Marks whatever a value refers to.
*
* @param runtime The runtime.
* @param value The value.
*/
void scriptGcMarkValue(scriptruntime_t *runtime, const scriptvalue_t value);
#endif
+106
View File
@@ -0,0 +1,106 @@
/**
* Copyright (c) 2026 Dominic Masters
*
* This software is released under the MIT License.
* https://opensource.org/licenses/MIT
*/
#include "scripttypes.h"
#include "gc/scriptgcscan.h"
#include <string.h>
#include "gc/scriptgcmark.h"
#include "heap/scriptheapblock.h"
#include "value/scriptfunction.h"
void scriptGcScanBlock(scriptruntime_t *runtime, void *pointer, const int kind) {
uint16_t i;
size_t slots;
scriptvalue_t *values;
switch(kind) {
case SCRIPT_HEAP_KIND_ARRAY: {
scriptarray_t *array = pointer;
// The items block is looked after here, only what is in use counts
if(scriptHeapKindOf(&runtime->heap, array->items) == (int)SCRIPT_HEAP_KIND_ITEMS) {
if(scriptHeapMark(&runtime->heap, array->items)) scriptHeapSetScanned(&runtime->heap, array->items);
for(i = 0; i < array->length; i++) scriptGcMarkValue(runtime, array->items[i]);
}
break;
}
case SCRIPT_HEAP_KIND_ITEMS:
case SCRIPT_HEAP_KIND_VALUES:
// Reached from somewhere that is not an array, so look at every slot
values = pointer;
slots = scriptHeapBlockSize(pointer) / sizeof(scriptvalue_t);
for(i = 0; i < slots; i++) {
if((unsigned int)values[i].type <= (unsigned int)SCRIPT_VALUE_PROMISE) scriptGcMarkValue(runtime, values[i]);
}
break;
case SCRIPT_HEAP_KIND_STRUCT: {
scriptstruct_t *structure = pointer;
for(i = 0; i < structure->count; i++) {
scriptGcMarkPointer(runtime, structure->keys[i]);
scriptGcMarkValue(runtime, structure->values[i]);
}
break;
}
case SCRIPT_HEAP_KIND_FUNCTION: {
scriptfunction_t *function = pointer;
scriptGcMarkPointer(runtime, function->data);
break;
}
case SCRIPT_HEAP_KIND_PROMISE: {
scriptpromise_t *promise = pointer;
scriptGcMarkValue(runtime, promise->value);
scriptGcMarkValue(runtime, promise->taskFunction);
for(i = 0; i < promise->taskArgc; i++) scriptGcMarkValue(runtime, promise->taskArgs[i]);
scriptGcMarkValue(runtime, promise->comboReason);
if(scriptHeapKindOf(&runtime->heap, promise->comboValues) == (int)SCRIPT_HEAP_KIND_VALUES) {
if(scriptHeapMark(&runtime->heap, promise->comboValues)) scriptHeapSetScanned(&runtime->heap, promise->comboValues);
for(i = 0; i < promise->comboTotal; i++) scriptGcMarkValue(runtime, promise->comboValues[i]);
}
scriptGcMarkPointer(runtime, promise->waiters);
break;
}
case SCRIPT_HEAP_KIND_WAITER: {
scriptpromisewaiter_t *waiter = pointer;
scriptGcMarkPointer(runtime, waiter->next);
scriptGcMarkPointer(runtime, waiter->combinator);
break;
}
default:
break;
}
scriptHeapSetScanned(&runtime->heap, pointer);
}
void scriptGcScanRange(scriptruntime_t *runtime, const void *low, const void *high) {
const uint8_t *cursor = low;
const uint8_t *end = high;
size_t misalign = (size_t)cursor % SCRIPT_SCAN_STEP;
const void *candidate;
// Anything on the stack that looks like a pointer is treated as one, it is
// checked against the heap before it is believed
if(misalign) cursor += SCRIPT_SCAN_STEP - misalign;
for(; cursor + sizeof(candidate) <= end; cursor += SCRIPT_SCAN_STEP) {
// candidate lives on the stack being scanned, so cursor legitimately
// equals &candidate on one iteration - a correct no-op memoryCopy's
// dest != src assert would wrongly reject, so this stays a plain memcpy
memcpy(&candidate, cursor, sizeof(candidate));
scriptGcMarkPointer(runtime, candidate);
}
}
__attribute__((noinline)) void scriptGcScanStack(scriptruntime_t *runtime, const char *base) {
volatile char marker = 0;
const char *here = (const char *)&marker;
if(here < base) scriptGcScanRange(runtime, here, base);
}
Loaded 100 of 323 files, more files were not shown because too many files have changed in this diff. Show more