mirror of
https://github.com/krgamestudios/Toy.git
synced 2026-04-15 14:54:07 +10:00
Strings, due to their potentially large size, are stored outside of a routine's code section, in the data section. To access the correct string, you must read the jump index, then the real address from the jump table - and extra layer of indirection will result in more flexible data down the road, I hope. Other changes include: * Added string concat operator .. * Added TOY_STRING_MAX_LENGTH * Strings can't be created or concatenated longer than the max length * The parser will display a warning if the bucket is too small for a string at max length, but it will continue * Added TOY_BUCKET_IDEAL to correspend with max string length * The bucket now allocates an address that is 4-byte aligned * Fixed missing entries in the parser rule table * Corrected some failing TOY_BITNESS tests
348 lines
7.5 KiB
C
348 lines
7.5 KiB
C
#include "toy_vm.h"
|
|
#include "toy_console_colors.h"
|
|
|
|
#include "toy_lexer.h"
|
|
#include "toy_parser.h"
|
|
#include "toy_bytecode.h"
|
|
#include "toy_print.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
//utils
|
|
Toy_Bytecode makeBytecodeFromSource(Toy_Bucket** bucketHandle, const char* source) {
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
return bc;
|
|
}
|
|
|
|
//tests
|
|
int test_setup_and_teardown(Toy_Bucket** bucketHandle) {
|
|
//basic init & quit
|
|
{
|
|
//generate bytecode for testing
|
|
const char* source = "(1 + 2) * (3 + 4);";
|
|
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
//run the setup
|
|
Toy_VM vm;
|
|
Toy_bindVM(&vm, bc.ptr);
|
|
|
|
//check the header size
|
|
int headerSize = 3 + strlen(TOY_VERSION_BUILD) + 1;
|
|
if (headerSize % 4 != 0) {
|
|
headerSize += 4 - (headerSize % 4); //ceil
|
|
}
|
|
|
|
//check the routine was loaded correctly
|
|
if (
|
|
vm.routine - vm.bc != headerSize ||
|
|
vm.routineSize != 72 ||
|
|
vm.paramSize != 0 ||
|
|
vm.jumpsSize != 0 ||
|
|
vm.dataSize != 0 ||
|
|
vm.subsSize != 0
|
|
)
|
|
{
|
|
fprintf(stderr, TOY_CC_ERROR "ERROR: failed to setup and teadown Toy_VM, source: %s\n" TOY_CC_RESET, source);
|
|
|
|
//cleanup and return
|
|
Toy_freeVM(&vm);
|
|
return -1;
|
|
}
|
|
|
|
//don't run it this time, simply teadown
|
|
Toy_freeVM(&vm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int test_simple_execution(Toy_Bucket** bucketHandle) {
|
|
//test execution
|
|
{
|
|
//generate bytecode for testing
|
|
const char* source = "(1 + 2) * (3 + 4);";
|
|
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
//run the setup
|
|
Toy_VM vm;
|
|
Toy_bindVM(&vm, bc.ptr);
|
|
|
|
//run
|
|
Toy_runVM(&vm);
|
|
|
|
//check the final state of the stack
|
|
if (vm.stack == NULL ||
|
|
vm.stack->count != 1 ||
|
|
TOY_VALUE_IS_INTEGER( Toy_peekStack(&vm.stack) ) != true ||
|
|
TOY_VALUE_AS_INTEGER( Toy_peekStack(&vm.stack) ) != 21
|
|
)
|
|
{
|
|
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected result in 'Toy_VM', source: %s\n" TOY_CC_RESET, source);
|
|
|
|
//cleanup and return
|
|
Toy_freeVM(&vm);
|
|
return -1;
|
|
}
|
|
|
|
//teadown
|
|
Toy_freeVM(&vm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int test_opcode_not_equal(Toy_Bucket** bucketHandle) {
|
|
//testing a specific opcode; '!=' is compressed into a single word, so lets check it works
|
|
{
|
|
//generate bytecode for testing
|
|
const char* source = "3 != 5;";
|
|
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
//run the setup
|
|
Toy_VM vm;
|
|
Toy_bindVM(&vm, bc.ptr);
|
|
|
|
//run
|
|
Toy_runVM(&vm);
|
|
|
|
//check the final state of the stack
|
|
if (vm.stack == NULL ||
|
|
vm.stack->count != 1 ||
|
|
TOY_VALUE_IS_BOOLEAN( Toy_peekStack(&vm.stack) ) != true ||
|
|
TOY_VALUE_AS_BOOLEAN( Toy_peekStack(&vm.stack) ) != true
|
|
)
|
|
{
|
|
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected result in 'Toy_VM', source: %s\n" TOY_CC_RESET, source);
|
|
|
|
//cleanup and return
|
|
Toy_freeVM(&vm);
|
|
return -1;
|
|
}
|
|
|
|
//teadown
|
|
Toy_freeVM(&vm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char* callbackUtilReceived = NULL;
|
|
static void callbackUtil(const char* msg) {
|
|
if (msg != NULL) {
|
|
free(callbackUtilReceived);
|
|
callbackUtilReceived = (char*)malloc(strlen(msg) + 1);
|
|
strcpy(callbackUtilReceived, msg);
|
|
}
|
|
}
|
|
|
|
int test_keywords(Toy_Bucket** bucketHandle) {
|
|
//test print
|
|
{
|
|
//setup
|
|
Toy_setPrintCallback(callbackUtil);
|
|
const char* source = "print 42;";
|
|
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
Toy_VM vm;
|
|
Toy_bindVM(&vm, bc.ptr);
|
|
|
|
//run
|
|
Toy_runVM(&vm);
|
|
|
|
//check the final state of the stack
|
|
if (callbackUtilReceived == NULL ||
|
|
strcmp(callbackUtilReceived, "42") != 0)
|
|
{
|
|
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected value '%s' passed to print keyword, source: %s\n" TOY_CC_RESET, callbackUtilReceived != NULL ? callbackUtilReceived : "NULL", source);
|
|
|
|
//cleanup and return
|
|
Toy_resetPrintCallback();
|
|
free(callbackUtilReceived);
|
|
Toy_freeVM(&vm);
|
|
return -1;
|
|
}
|
|
|
|
//teadown
|
|
Toy_resetPrintCallback();
|
|
free(callbackUtilReceived);
|
|
callbackUtilReceived = NULL;
|
|
Toy_freeVM(&vm);
|
|
}
|
|
|
|
//test print with a string
|
|
{
|
|
//setup
|
|
Toy_setPrintCallback(callbackUtil);
|
|
const char* source = "print \"Hello world!\";";
|
|
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
Toy_VM vm;
|
|
Toy_bindVM(&vm, bc.ptr);
|
|
|
|
//run
|
|
Toy_runVM(&vm);
|
|
|
|
//check the final state of the stack
|
|
if (callbackUtilReceived == NULL ||
|
|
strcmp(callbackUtilReceived, "Hello world!") != 0)
|
|
{
|
|
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected value '%s' passed to print keyword, source: %s\n" TOY_CC_RESET, callbackUtilReceived != NULL ? callbackUtilReceived : "NULL", source);
|
|
|
|
//cleanup and return
|
|
Toy_resetPrintCallback();
|
|
free(callbackUtilReceived);
|
|
Toy_freeVM(&vm);
|
|
return -1;
|
|
}
|
|
|
|
//teadown
|
|
Toy_resetPrintCallback();
|
|
free(callbackUtilReceived);
|
|
callbackUtilReceived = NULL;
|
|
Toy_freeVM(&vm);
|
|
}
|
|
|
|
//test print with a string concat
|
|
{
|
|
//setup
|
|
Toy_setPrintCallback(callbackUtil);
|
|
const char* source = "print \"Hello\" .. \"world!\";";
|
|
|
|
Toy_Lexer lexer;
|
|
Toy_bindLexer(&lexer, source);
|
|
|
|
Toy_Parser parser;
|
|
Toy_bindParser(&parser, &lexer);
|
|
|
|
Toy_Ast* ast = Toy_scanParser(bucketHandle, &parser);
|
|
Toy_Bytecode bc = Toy_compileBytecode(ast);
|
|
|
|
Toy_VM vm;
|
|
Toy_bindVM(&vm, bc.ptr);
|
|
|
|
//run
|
|
Toy_runVM(&vm);
|
|
|
|
//check the final state of the stack
|
|
if (callbackUtilReceived == NULL ||
|
|
strcmp(callbackUtilReceived, "Helloworld!") != 0)
|
|
{
|
|
fprintf(stderr, TOY_CC_ERROR "ERROR: Unexpected value '%s' passed to print keyword, source: %s\n" TOY_CC_RESET, callbackUtilReceived != NULL ? callbackUtilReceived : "NULL", source);
|
|
|
|
//cleanup and return
|
|
Toy_resetPrintCallback();
|
|
free(callbackUtilReceived);
|
|
Toy_freeVM(&vm);
|
|
return -1;
|
|
}
|
|
|
|
//teadown
|
|
Toy_resetPrintCallback();
|
|
free(callbackUtilReceived);
|
|
callbackUtilReceived = NULL;
|
|
Toy_freeVM(&vm);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int main() {
|
|
//run each test set, returning the total errors given
|
|
int total = 0, res = 0;
|
|
|
|
{
|
|
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
|
res = test_setup_and_teardown(&bucket);
|
|
Toy_freeBucket(&bucket);
|
|
if (res == 0) {
|
|
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
|
|
}
|
|
total += res;
|
|
}
|
|
|
|
{
|
|
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
|
res = test_simple_execution(&bucket);
|
|
Toy_freeBucket(&bucket);
|
|
if (res == 0) {
|
|
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
|
|
}
|
|
total += res;
|
|
}
|
|
|
|
{
|
|
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
|
res = test_opcode_not_equal(&bucket);
|
|
Toy_freeBucket(&bucket);
|
|
if (res == 0) {
|
|
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
|
|
}
|
|
total += res;
|
|
}
|
|
|
|
{
|
|
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
|
|
res = test_keywords(&bucket);
|
|
Toy_freeBucket(&bucket);
|
|
if (res == 0) {
|
|
printf(TOY_CC_NOTICE "All good\n" TOY_CC_RESET);
|
|
}
|
|
total += res;
|
|
}
|
|
|
|
return total;
|
|
}
|