Added 'min' and 'max' as standard functions

Also fixed floating point printing with sprintf
This commit is contained in:
2026-05-27 10:52:06 +10:00
parent f2b714baaa
commit 3e115095d6
13 changed files with 86 additions and 59 deletions
-1
View File
@@ -9,7 +9,6 @@
#include "toy_compiler.h" #include "toy_compiler.h"
#include "toy_vm.h" #include "toy_vm.h"
//NOTE: for testing
#include "standard_library.h" #include "standard_library.h"
#include <stdio.h> #include <stdio.h>
+72 -26
View File
@@ -1,7 +1,6 @@
#include "standard_library.h" #include "standard_library.h"
#include "toy_console_colors.h" #include "toy_console_colors.h"
#include "toy_print.h"
#include "toy_scope.h" #include "toy_scope.h"
#include "toy_stack.h" #include "toy_stack.h"
@@ -14,36 +13,85 @@ typedef struct CallbackPairs {
Toy_nativeCallback callback; Toy_nativeCallback callback;
} CallbackPairs; } CallbackPairs;
//example callbacks //example of how to write and use C bindings
static void answer(Toy_VM* vm, Toy_FunctionNative* self) { static void std_min(Toy_VM* vm, Toy_FunctionNative* self) {
(void)vm;
(void)self; (void)self;
Toy_print(TOY_CC_DEBUG "This function returns the integer '42' to the calling scope." TOY_CC_RESET);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_INTEGER(42)); //return the lesser of two values, or null on error
Toy_Value first = Toy_popStack(&vm->stack);
Toy_Value second = Toy_popStack(&vm->stack);
//check types
if ((!TOY_VALUE_IS_INTEGER(first) && !TOY_VALUE_IS_FLOAT(first)) || (!TOY_VALUE_IS_INTEGER(second) && !TOY_VALUE_IS_FLOAT(second))) {
char buffer[256];
snprintf(buffer, 256, "Invalid types '%s' and '%s' found in 'min()'", Toy_getValueTypeAsCString(Toy_unwrapValue(first).type), Toy_getValueTypeAsCString(Toy_unwrapValue(second).type));
Toy_error(buffer);
Toy_freeValue(first);
Toy_freeValue(second);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
return;
} }
static void identity(Toy_VM* vm, Toy_FunctionNative* self) { //compare ints, or coerce ints into floats if needed
//does nothing, but any arguements are left on the stack as results if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_INTEGER(second)) {
(void)vm; Toy_Value result = TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_INTEGER(first) < TOY_VALUE_AS_INTEGER(second) ? TOY_VALUE_AS_INTEGER(first) : TOY_VALUE_AS_INTEGER(second));
(void)self; Toy_pushStack(&vm->stack, result);
return;
}
else if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_FLOAT(second)) {
first = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(first) );
}
else if (TOY_VALUE_IS_FLOAT(first) && TOY_VALUE_IS_INTEGER(second)) {
second = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(second) );
} }
static void echo(Toy_VM* vm, Toy_FunctionNative* self) { //finally, do the comparison on floats
Toy_Value result = TOY_VALUE_FROM_FLOAT(TOY_VALUE_AS_FLOAT(first) < TOY_VALUE_AS_FLOAT(second) ? TOY_VALUE_AS_FLOAT(first) : TOY_VALUE_AS_FLOAT(second));
Toy_pushStack(&vm->stack, result);
//NOTE: not freeing scalar values does work, but only in narrow cases
}
static void std_max(Toy_VM* vm, Toy_FunctionNative* self) {
(void)self; (void)self;
//pops one argument, and prints it
Toy_Value value = Toy_popStack(&vm->stack);
Toy_String* string = Toy_stringifyValue(&vm->memoryBucket, value);
char* cstr = Toy_getStringRaw(string);
Toy_print(cstr); //return the lesser of two values, or null on error
Toy_Value first = Toy_popStack(&vm->stack);
Toy_Value second = Toy_popStack(&vm->stack);
free(cstr); //check types
Toy_freeString(string); if ((!TOY_VALUE_IS_INTEGER(first) && !TOY_VALUE_IS_FLOAT(first)) || (!TOY_VALUE_IS_INTEGER(second) && !TOY_VALUE_IS_FLOAT(second))) {
Toy_freeValue(value); char buffer[256];
snprintf(buffer, 256, "Invalid types '%s' and '%s' found in 'min()'", Toy_getValueTypeAsCString(Toy_unwrapValue(first).type), Toy_getValueTypeAsCString(Toy_unwrapValue(second).type));
Toy_error(buffer);
Toy_freeValue(first);
Toy_freeValue(second);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
return;
}
//compare ints, or coerce ints into floats if needed
if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_INTEGER(second)) {
Toy_Value result = TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_INTEGER(first) > TOY_VALUE_AS_INTEGER(second) ? TOY_VALUE_AS_INTEGER(first) : TOY_VALUE_AS_INTEGER(second));
Toy_pushStack(&vm->stack, result);
return;
}
else if (TOY_VALUE_IS_INTEGER(first) && TOY_VALUE_IS_FLOAT(second)) {
first = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(first) );
}
else if (TOY_VALUE_IS_FLOAT(first) && TOY_VALUE_IS_INTEGER(second)) {
second = TOY_VALUE_FROM_FLOAT( (float)TOY_VALUE_AS_INTEGER(second) );
}
//finally, do the comparison on floats
Toy_Value result = TOY_VALUE_FROM_FLOAT(TOY_VALUE_AS_FLOAT(first) > TOY_VALUE_AS_FLOAT(second) ? TOY_VALUE_AS_FLOAT(first) : TOY_VALUE_AS_FLOAT(second));
Toy_pushStack(&vm->stack, result);
//NOTE: not freeing scalar values does work, but only in narrow cases
} }
static void next(Toy_VM* vm, Toy_FunctionNative* self) { static void next(Toy_VM* vm, Toy_FunctionNative* self) {
//used by 'range' //used by 'std_range'
if (self->meta2 < self->meta1) { if (self->meta2 < self->meta1) {
Toy_Value result = TOY_VALUE_FROM_INTEGER(self->meta2); Toy_Value result = TOY_VALUE_FROM_INTEGER(self->meta2);
Toy_pushStack(&vm->stack, result); Toy_pushStack(&vm->stack, result);
@@ -54,7 +102,7 @@ static void next(Toy_VM* vm, Toy_FunctionNative* self) {
} }
} }
static void range(Toy_VM* vm, Toy_FunctionNative* self) { static void std_range(Toy_VM* vm, Toy_FunctionNative* self) {
(void)self; (void)self;
//one arg to represent the number of iterations //one arg to represent the number of iterations
@@ -79,12 +127,10 @@ static void range(Toy_VM* vm, Toy_FunctionNative* self) {
Toy_pushStack(&vm->stack, result); Toy_pushStack(&vm->stack, result);
} }
CallbackPairs callbackPairs[] = { CallbackPairs callbackPairs[] = {
{"dbg_answer", answer}, {"min", std_min},
{"dbg_identity", identity}, {"max", std_max},
{"dbg_echo", echo}, {"range", std_range},
{"range", range},
{NULL, NULL}, {NULL, NULL},
}; };
-2
View File
@@ -56,5 +56,3 @@ TOY_API void Toy_collectBucketGarbage(Toy_Bucket** bucketHandle);
#ifndef TOY_BUCKET_IDEAL #ifndef TOY_BUCKET_IDEAL
#define TOY_BUCKET_IDEAL (TOY_BUCKET_64KB - sizeof(Toy_Bucket)) #define TOY_BUCKET_IDEAL (TOY_BUCKET_64KB - sizeof(Toy_Bucket))
#endif #endif
//TODO: check for leaks when freeBucket is called, for debugging
+1 -1
View File
@@ -787,7 +787,7 @@ static unsigned int writeInstructionForThen(Toy_Bytecode** mb, Toy_AstForThen as
emitString(mb, ast.condBranch->iterable.left->varDeclare.name); emitString(mb, ast.condBranch->iterable.left->varDeclare.name);
//TODO: use a different approach //TODO: use a different approach?
//BUGFIX: shadow the iterable's name //BUGFIX: shadow the iterable's name
EMIT_BYTE(mb, code, TOY_OPCODE_READ); EMIT_BYTE(mb, code, TOY_OPCODE_READ);
EMIT_BYTE(mb, code, TOY_VALUE_NULL); EMIT_BYTE(mb, code, TOY_VALUE_NULL);
+3 -17
View File
@@ -464,29 +464,15 @@ Toy_String* Toy_stringifyValue(Toy_Bucket** bucketHandle, Toy_Value value) {
} }
case TOY_VALUE_INTEGER: { case TOY_VALUE_INTEGER: {
char buffer[16]; char buffer[128];
sprintf(buffer, "%d", value.as.integer); sprintf(buffer, "%d", value.as.integer);
return Toy_createStringLength(bucketHandle, buffer, strlen(buffer)); return Toy_createStringLength(bucketHandle, buffer, strlen(buffer));
} }
case TOY_VALUE_FLOAT: { case TOY_VALUE_FLOAT: {
//using printf char buffer[128];
char buffer[16]; sprintf(buffer, "%g", value.as.number);
sprintf(buffer, "%f", value.as.number);
//BUGFIX: printf format specificer '%f' will set the precision to 6 decimal places, which means there's trailing zeroes
unsigned int length = strlen(buffer); unsigned int length = strlen(buffer);
//find the decimal, if it exists
unsigned int decimal = 0;
while (decimal != length && buffer[decimal] != '.' && buffer[decimal] != ',') decimal++; //'.' and ',' supports more locales
//locales are hard, sorry!
if (decimal != length && buffer[decimal] == ',') buffer[decimal] = '.';
//wipe the trailing zeros
while(decimal != length && buffer[length-1] == '0') buffer[--length] = '\0';
return Toy_createStringLength(bucketHandle, buffer, length); return Toy_createStringLength(bucketHandle, buffer, length);
} }
+2 -2
View File
@@ -546,7 +546,7 @@ static void processIterate(Toy_VM* vm) {
processJump(vm); processJump(vm);
} }
} }
//TODO: support closures as a parameter //TODO: support closures in for-loops
else { else {
fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown iterable type '%s' found in for loop, exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(Toy_unwrapValue(compound).type)); fprintf(stderr, TOY_CC_ERROR "ERROR: Unknown iterable type '%s' found in for loop, exiting\n" TOY_CC_RESET, Toy_getValueTypeAsCString(Toy_unwrapValue(compound).type));
exit(-1); exit(-1);
@@ -826,7 +826,7 @@ static void processAssert(Toy_VM* vm) {
//determine the args //determine the args
if (count == 1) { if (count == 1) {
message = TOY_VALUE_FROM_STRING(Toy_toString(&vm->memoryBucket, "assertion failed")); //TODO: better default error message message = TOY_VALUE_FROM_STRING(Toy_toString(&vm->memoryBucket, "assertion failed")); //TODO: better default assert message
value = Toy_popStack(&vm->stack); value = Toy_popStack(&vm->stack);
} }
else if (count == 2) { else if (count == 2) {
-2
View File
@@ -7,5 +7,3 @@ all:
gdb: gdb:
$(MAKE) -C units -k gdb $(MAKE) -C units -k gdb
$(MAKE) -C scripts -k gdb $(MAKE) -C scripts -k gdb
#TODO: valgrind
+1 -1
View File
@@ -1 +1 @@
//TODO: empty test script //URGENT: empty test script
+1 -1
View File
@@ -1 +1 @@
//TODO: empty test script //URGENT: empty test script
+1 -1
View File
@@ -1,4 +1,4 @@
//TODO: empty test script //URGENT: empty test script
//TODO: test iteration on arrays, tables, closures //TODO: test iteration on arrays, tables, closures
+1 -1
View File
@@ -4,7 +4,7 @@
#include <string.h> #include <string.h>
int main(void) { int main(void) {
//TODO: Test not yet implemented //URGENT: Test not yet implemented
printf(TOY_CC_WARN "Test not yet implemented: %s\n" TOY_CC_RESET, __FILE__); printf(TOY_CC_WARN "Test not yet implemented: %s\n" TOY_CC_RESET, __FILE__);
return 0; return 0;
} }
+1 -1
View File
@@ -117,7 +117,7 @@ int test_functions_from_bytecodes(void) {
} }
int test_functions_from_callbacks(void) { int test_functions_from_callbacks(void) {
//TODO: Test not yet implemented //URGENT: Test not yet implemented
printf(TOY_CC_WARN "WIP test not yet implemented: %s\n" TOY_CC_RESET, __FILE__); printf(TOY_CC_WARN "WIP test not yet implemented: %s\n" TOY_CC_RESET, __FILE__);
return 0; return 0;
} }
+2 -2
View File
@@ -607,8 +607,8 @@ int main(void) {
total += res; total += res;
} }
//TODO: references //TODO: references?
//TODO: type coersions //TODO: type coersions?
//TODO: opaques? //TODO: opaques?
return total; return total;