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:
1 parent
3948607b1e
commit
c1d6a97293
323 files changed
+18389
No files matched your search
@@ -17,6 +17,7 @@ cmake_policy(SET CMP0079 NEW)
|
||||
|
||||
option(DUSK_BUILD_TESTS "Enable tests" OFF)
|
||||
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_AUTHOR "YourWishes" CACHE STRING "Game author / coder")
|
||||
@@ -110,6 +111,12 @@ if(DUSK_NETWORK)
|
||||
)
|
||||
endif()
|
||||
|
||||
if(DUSK_SCRIPT)
|
||||
target_compile_definitions(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
DUSK_SCRIPT
|
||||
)
|
||||
endif()
|
||||
|
||||
# Toolchains
|
||||
include(cmake/targets/${DUSK_TARGET_SYSTEM}.cmake)
|
||||
|
||||
|
||||
@@ -9,6 +9,16 @@ if(DUSK_NETWORK)
|
||||
add_subdirectory(dusknetwork)
|
||||
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")
|
||||
add_subdirectory(dusklinux)
|
||||
add_subdirectory(dusksdl2)
|
||||
|
||||
@@ -82,6 +82,9 @@ add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(perf)
|
||||
add_subdirectory(rpg)
|
||||
if(DUSK_SCRIPT)
|
||||
add_subdirectory(script)
|
||||
endif()
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
|
||||
@@ -20,6 +20,9 @@
|
||||
#ifdef DUSK_NETWORK
|
||||
#include "network/network.h"
|
||||
#endif
|
||||
#ifdef DUSK_SCRIPT
|
||||
#include "script/script.h"
|
||||
#endif
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
#include "save/save.h"
|
||||
@@ -52,6 +55,9 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
#ifdef DUSK_NETWORK
|
||||
errorChain(networkInit());
|
||||
#endif
|
||||
#ifdef DUSK_SCRIPT
|
||||
errorChain(scriptInit());
|
||||
#endif
|
||||
errorChain(sceneInit());
|
||||
|
||||
consolePrint("Engine initialized");
|
||||
@@ -95,6 +101,9 @@ void engineExit(void) {
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
errorChain(sceneDispose());
|
||||
#ifdef DUSK_SCRIPT
|
||||
errorChain(scriptDispose());
|
||||
#endif
|
||||
#ifdef DUSK_NETWORK
|
||||
errorChain(networkDispose());
|
||||
#endif
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 *)▮
|
||||
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
Reference in new issue
Block a user