Compare commits

...

12 Commits

Author SHA1 Message Date
Kayne Ruse a00739f580 Moved 'debugStackPrint' to 'stack_inspector.c' 2026-05-30 20:32:22 +10:00
Kayne Ruse d194bff5fe Revised tests 2026-05-30 10:53:21 +10:00
Kayne Ruse 75cb1dfa86 Config tweak 2026-05-30 08:23:25 +10:00
Kayne Ruse 27d2a21710 Updated vscode settings 2026-05-28 23:03:40 +10:00
Kayne Ruse abba7e0534 Updated cheatsheet.md 2026-05-27 12:35:43 +10:00
Kayne Ruse b312c0d8a3 Added floor, ceil and sqrt to the standard library 2026-05-27 12:24:20 +10:00
Kayne Ruse 3e115095d6 Added 'min' and 'max' as standard functions
Also fixed floating point printing with sprintf
2026-05-27 10:53:01 +10:00
Kayne Ruse f2b714baaa Fixed an extra return opcode in empty functions 2026-05-27 09:52:21 +10:00
Kayne Ruse 92e4a41662 Renamed 'scripts' directory to 'examples'
Also allowed assignment within conditionals
2026-05-26 23:42:33 +10:00
Kayne Ruse bbb1e38649 Fixed parameter strings in data section
Fucking finally.
2026-05-26 18:51:24 +10:00
Kayne Ruse 190477add8 Fixed negative variables handled incorrectly 2026-05-26 18:01:49 +10:00
Kayne Ruse 69175e801a Fixed a hash collision causing null variables to be overwritten
Andrew, don't you dare run my code through a clanker again or I'll hunt
your Canuck ass down and beat you with a hockey stick.
2026-05-26 17:52:07 +10:00
67 changed files with 620 additions and 422 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"recommendations": [ "recommendations": [
"gruntfuggly.todo-tree" "FanaticPythoner.better-todo-tree"
] ]
} }
+7 -7
View File
@@ -1,16 +1,16 @@
{ {
"todo-tree.filtering.includeGlobs": [ "better-todo-tree.filtering.includeGlobs": [
"**/repl/**", "**/repl/**",
"**/scripts/**", "**/examples/**",
"**/source/**", "**/source/**",
"**/tests/**", "**/tests/**"
"**/tools/**",
], ],
"todo-tree.filtering.excludeGlobs": [ "better-todo-tree.filtering.excludeGlobs": [
"**/obj/**", "**/obj/**",
"**/out/**", "**/out/**",
"**/docs/**"
], ],
"todo-tree.general.tags": [ "better-todo-tree.general.tags": [
"URGENT", "URGENT",
"BUG", "BUG",
"TODO", "TODO",
@@ -19,7 +19,7 @@
"WONTFIX", "WONTFIX",
"NOTE" "NOTE"
], ],
"todo-tree.highlights.customHighlight": { "better-todo-tree.highlights.customHighlight": {
"URGENT": { "URGENT": {
"icon": "alert", "icon": "alert",
"type": "text", "type": "text",
+3 -4
View File
@@ -33,10 +33,9 @@ fn makeCounter() {
var tally = makeCounter(); var tally = makeCounter();
while (true) { var result = 0;
var result = tally(); while (result = tally()) {
print result;
print result; //prints 1 to 10
if (result >= 10) { if (result >= 10) {
break; break;
+3 -4
View File
@@ -29,10 +29,9 @@ fn makeCounter() {
var tally = makeCounter(); var tally = makeCounter();
while (true) { var result = 0;
var result = tally(); while (result = tally()) {
print result;
print result; //prints 1 to 10
if (result >= 10) { if (result >= 10) {
break; break;
+15
View File
@@ -142,3 +142,18 @@ TOY_API unsigned int Toy_runVM(Toy_VM* vm);
TOY_API void Toy_freeVM(Toy_VM* vm); TOY_API void Toy_freeVM(Toy_VM* vm);
TOY_API Toy_Value Toy_getReturnValueFromVM(Toy_VM* parentVM, Toy_VM* subVM); TOY_API Toy_Value Toy_getReturnValueFromVM(Toy_VM* parentVM, Toy_VM* subVM);
``` ```
## Standard Library
The tools in `repl/` includes a standard library, which can be added to the root VM by calling `initStandardLibrary` just before `Toy_runVM`. It provides the following general purpose functions:
* `min(x, y)`
* `max(x, y)`
* `floor(x)`
* `ceil(x)`
* `sqrt(x)`
* `range(x)`
It also offers an example of how to write an API.
*Note: Range returns a closure intended for use in the `for` keyword, but the that keyword isn't done yet.*
@@ -1,5 +1,3 @@
//tentatively functional
//fibonacci sequence //fibonacci sequence
fn fib(n) { fn fib(n) {
if (n < 2) return n; if (n < 2) return n;
+2
View File
@@ -0,0 +1,2 @@
//the semicolon is super important
print "Hello world!";
@@ -1,11 +1,10 @@
//find the leap years //is 'n' a leap year
fn isLeapYear(n: Int) { fn isLeapYear(n: Int) {
if (n % 400 == 0) return true; if (n % 400 == 0) return true;
if (n % 100 == 0) return false; if (n % 100 == 0) return false;
return n % 4 == 0; return n % 4 == 0;
} }
//check for string reuse
{ {
print isLeapYear(1999); print isLeapYear(1999);
} }
+10
View File
@@ -0,0 +1,10 @@
var randi: Int = 69420;
fn rand() {
//a quick and dirty random number generator
return randi = randi * 1664525 + 1013904223;
}
print rand();
print rand();
print rand();
+3 -3
View File
@@ -8,11 +8,11 @@ fn makeCounter() {
return increment; return increment;
} }
//'tally' becomes a closure
var tally = makeCounter(); var tally = makeCounter();
while (true) { var result = 0;
var result = tally(); while (result = tally()) {
print result; print result;
if (result >= 10) { if (result >= 10) {
+1 -1
View File
@@ -13,7 +13,7 @@ int inspect_bucket(Toy_Bucket** bucketHandle) {
unsigned char* ptr = iter->data; unsigned char* ptr = iter->data;
while ((ptr - iter->data < iter->count) && *((int*)ptr) != 0) { //for each partition while ((ptr >= iter->data) && (ptr < iter->data + iter->count) && *((int*)ptr) != 0) { //for each partition
if ( ( *((int*)ptr) & 1) == 0) { //is this partition still in use? if ( ( *((int*)ptr) & 1) == 0) { //is this partition still in use?
occupied++; occupied++;
+4
View File
@@ -233,6 +233,10 @@ int inspect_instruction(unsigned char* bytecode, unsigned int pc, unsigned int j
printf(MARKER "MODULO %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : ""); printf(MARKER "MODULO %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] == TOY_OPCODE_ASSIGN ? "and ASSIGN" : "");
return 4; return 4;
case TOY_OPCODE_INVERT:
printf(MARKER "INVERT\n", MARKER_VALUE(pc, unsigned char));
return 4;
case TOY_OPCODE_COMPARE_EQUAL: case TOY_OPCODE_COMPARE_EQUAL:
printf(MARKER "COMPARE %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] != TOY_OPCODE_NEGATE ? "==" : "!="); printf(MARKER "COMPARE %s\n", MARKER_VALUE(pc, unsigned char), bytecode[pc + 1] != TOY_OPCODE_NEGATE ? "==" : "!=");
return 4; return 4;
+7 -33
View File
@@ -1,6 +1,7 @@
#include "bucket_inspector.h"
#include "ast_inspector.h" #include "ast_inspector.h"
#include "bytecode_inspector.h" #include "bytecode_inspector.h"
#include "bucket_inspector.h" #include "stack_inspector.h"
#include "toy_console_colors.h" #include "toy_console_colors.h"
@@ -9,7 +10,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>
@@ -26,7 +26,7 @@ unsigned char* readFile(char* path, int* size) {
//determine the file's length //determine the file's length
fseek(file, 0L, SEEK_END); fseek(file, 0L, SEEK_END);
*size = ftell(file); *size = (int)ftell(file);
rewind(file); rewind(file);
//make some space //make some space
@@ -44,7 +44,7 @@ unsigned char* readFile(char* path, int* size) {
return NULL; return NULL;
} }
buffer[(*size)++] = '\0'; buffer[(*size)] = '\0';
//clean up and return //clean up and return
fclose(file); fclose(file);
@@ -247,32 +247,6 @@ CmdLine parseCmdLine(int argc, const char* argv[]) {
} }
//debugging //debugging
static void debugStackPrint(Toy_Stack* stack) {
//DEBUG: if there's anything on the stack, print it
if (stack->count > 0) {
Toy_Bucket* stringBucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
printf("\n" TOY_CC_NOTICE "Stack Dump" TOY_CC_RESET "\n" TOY_CC_NOTICE "%-20s%-20s" TOY_CC_RESET "\n", "type", "value");
for (unsigned int i = 0; i < stack->count; i++) {
Toy_Value v = ((Toy_Value*)(stack + 1))[i]; //'stack + 1' is a naughty trick
//print type
printf("%-20s", Toy_getValueTypeAsCString(v.type));
//print value
Toy_String* string = Toy_stringifyValue(&stringBucket, Toy_unwrapValue(v));
char* buffer = Toy_getStringRaw(string);
printf("%-20s", buffer);
free(buffer);
Toy_freeString(string);
printf("\n");
}
Toy_freeBucket(&stringBucket);
}
}
static void debugScopePrint(Toy_Scope* scope, int depth) { static void debugScopePrint(Toy_Scope* scope, int depth) {
//DEBUG: if there's anything in the scope, print it //DEBUG: if there's anything in the scope, print it
if (scope->count > 0) { if (scope->count > 0) {
@@ -334,7 +308,7 @@ int repl(const char* filepath, bool verbose) {
inputBuffer[--length] = '\0'; inputBuffer[--length] = '\0';
} }
if (length == 0 || !inputBuffer[ strspn(inputBuffer, " \r\n\t") ]) { if (length == 0 || inputBuffer[ strspn(inputBuffer, " \r\n\t") ] == '\0') {
printf("%s> ", prompt); //shows the terminal prompt and restart printf("%s> ", prompt); //shows the terminal prompt and restart
continue; continue;
} }
@@ -392,7 +366,7 @@ int repl(const char* filepath, bool verbose) {
//print the debug info //print the debug info
if (verbose) { if (verbose) {
debugStackPrint(vm.stack); inspect_stack(vm.stack);
debugScopePrint(vm.scope, 0); debugScopePrint(vm.scope, 0);
depthBeforeGC = inspect_bucket(&vm.memoryBucket); depthBeforeGC = inspect_bucket(&vm.memoryBucket);
@@ -516,7 +490,7 @@ int main(int argc, const char* argv[]) {
//print the debug info //print the debug info
if (cmd.verbose) { if (cmd.verbose) {
debugStackPrint(vm.stack); inspect_stack(vm.stack);
debugScopePrint(vm.scope, 0); debugScopePrint(vm.scope, 0);
} }
+39
View File
@@ -0,0 +1,39 @@
#include "stack_inspector.h"
#include "toy_console_colors.h"
#include "toy_string.h"
#include <stdio.h>
#include <stdlib.h>
void inspect_stack(Toy_Stack* stack) {
printf(TOY_CC_NOTICE "Stack State: %u / %u\n" TOY_CC_RESET, stack->count, stack->capacity);
if (stack->count == 0) {
printf(TOY_CC_NOTICE "\n-- Empty Stack --\n\n" TOY_CC_RESET);
return;
}
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL);
printf(TOY_CC_NOTICE "-- Beginning Stack Dump --\n\n%-10s%-20s%-20s\n\n" TOY_CC_RESET, "Index", "Type", "Value");
for (unsigned int i = 0; i < stack->count; i++) {
Toy_Value v = ((Toy_Value*)(stack + 1))[i]; //'stack + 1' is a naughty trick
//print type
printf("%-10u%-20s", i, Toy_getValueTypeAsCString(v.type));
//print value
Toy_String* string = Toy_stringifyValue(&bucket, Toy_unwrapValue(v));
char* buffer = Toy_getStringRaw(string); //lazy, but it works
printf("%-20s", buffer);
free(buffer);
Toy_freeString(string);
printf("\n");
}
printf(TOY_CC_NOTICE "\n-- End Stack Dump --\n\n" TOY_CC_RESET);
Toy_freeBucket(&bucket);
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "toy_stack.h"
void inspect_stack(Toy_Stack* stack);
+161 -24
View File
@@ -1,49 +1,185 @@
#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"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <math.h>
typedef struct CallbackPairs { typedef struct CallbackPairs {
const char* name; const char* name;
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;
}
//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 identity(Toy_VM* vm, Toy_FunctionNative* self) { static void std_max(Toy_VM* vm, Toy_FunctionNative* self) {
//does nothing, but any arguements are left on the stack as results
(void)vm;
(void)self; (void)self;
//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 'max()'", 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 echo(Toy_VM* vm, Toy_FunctionNative* self) { static void std_floor(Toy_VM* vm, Toy_FunctionNative* self) {
(void)self; (void)self;
//pops one argument, and prints it
Toy_Value value = Toy_popStack(&vm->stack); Toy_Value value = Toy_popStack(&vm->stack);
Toy_String* string = Toy_stringifyValue(&vm->memoryBucket, value);
char* cstr = Toy_getStringRaw(string);
Toy_print(cstr); if (!TOY_VALUE_IS_INTEGER(value) && !TOY_VALUE_IS_FLOAT(value)) {
char buffer[256];
snprintf(buffer, 256, "Invalid type '%s' found in 'floor()'", Toy_getValueTypeAsCString(Toy_unwrapValue(value).type));
Toy_error(buffer);
free(cstr); Toy_freeValue(value);
Toy_freeString(string); Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
Toy_freeValue(value); return;
}
//this only runs on floats, but converts them to integers
if (TOY_VALUE_IS_FLOAT(value)) {
double d = (double)TOY_VALUE_AS_FLOAT(value);
d = floor(d);
value = TOY_VALUE_FROM_INTEGER((int)d);
}
Toy_pushStack(&vm->stack, value);
}
static void std_ceil(Toy_VM* vm, Toy_FunctionNative* self) {
(void)self;
Toy_Value value = Toy_popStack(&vm->stack);
if (!TOY_VALUE_IS_INTEGER(value) && !TOY_VALUE_IS_FLOAT(value)) {
char buffer[256];
snprintf(buffer, 256, "Invalid type '%s' found in 'ceil()'", Toy_getValueTypeAsCString(Toy_unwrapValue(value).type));
Toy_error(buffer);
Toy_freeValue(value);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
return;
}
//this only runs on floats, but converts them to integers
if (TOY_VALUE_IS_FLOAT(value)) {
double d = (double)TOY_VALUE_AS_FLOAT(value);
d = ceil(d);
value = TOY_VALUE_FROM_INTEGER((int)d);
}
Toy_pushStack(&vm->stack, value);
}
static void std_sqrt(Toy_VM* vm, Toy_FunctionNative* self) {
(void)self;
Toy_Value value = Toy_popStack(&vm->stack);
if (!TOY_VALUE_IS_INTEGER(value) && !TOY_VALUE_IS_FLOAT(value)) {
char buffer[256];
snprintf(buffer, 256, "Invalid type '%s' found in 'sqrt()'", Toy_getValueTypeAsCString(Toy_unwrapValue(value).type));
Toy_error(buffer);
Toy_freeValue(value);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
return;
}
double d = 0;
if (TOY_VALUE_IS_INTEGER(value)) d = (double)TOY_VALUE_AS_INTEGER(value);
else if (TOY_VALUE_IS_FLOAT(value)) d = (double)TOY_VALUE_AS_FLOAT(value);
//only works on non-negative values
if (d < 0) {
char buffer[256];
snprintf(buffer, 256, "Can't get the sqrt of a negative number (found '%g')", d);
Toy_error(buffer);
Toy_freeValue(value);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
return;
}
//do the thing and push the result
d = sqrt(d);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_FLOAT((float)d));
} }
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 +190,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 +215,13 @@ 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}, {"floor", std_floor},
{"range", range}, {"ceil", std_ceil},
{"sqrt", std_sqrt},
{"range", std_range},
{NULL, NULL}, {NULL, NULL},
}; };
-16
View File
@@ -1,16 +0,0 @@
//calculate the nth fibonacci number, and print it
var counter: Int = 0;
var first: Int = 1;
var second: Int = 0;
while (counter < 100_000) {
var third: Int = first + second;
first = second;
second = third;
print third;
++counter;
}
-11
View File
@@ -1,11 +0,0 @@
fn output(arg) {
print arg;
}
var array = ["alpha", "bravo", "charlie"];
array.forEach(echo);
array.forEach(output);
-35
View File
@@ -1,35 +0,0 @@
//WARN: This is just a scratch pad, don't use it
//TODO: table.hasValue or table.getKeyFromValue?
//for (var i in array) print i;
//for (var i in table) print i;
//for (var i in range(10)) print i;
//for (range(10)) print "ha";
//example of a `range`-like function
fn range(limit: Int) {
var counter: Int = 0;
fn next() {
if (counter >= limit) {
return null;
}
else return counter++;
}
return next;
}
var next = range(10);
fn log(x) {
if (x == null) return;
print x;
}
while (true) {
log(next());
}
-21
View File
@@ -1,21 +0,0 @@
var table = [
"Alpha": 1,
"Bravo": 2,
"Charlie": 3,
"Delta": 4,
"Echo": 5,
"Foxtrot": 6,
"Golf": 7,
"Hotel": 8,
"India": 9,
"Juliett": 10,
"Kilo": 11,
"Lima": 12,
"Mike": 13,
];
for (var i in table) {
print i;
}
-12
View File
@@ -1,12 +0,0 @@
var randi: Int = 69420;
fn rand() {
return randi = randi * 1664525 + 1013904223;
}
var a = rand();
+4 -3
View File
@@ -86,9 +86,10 @@ typedef enum Toy_AstFlag {
TOY_AST_FLAG_PREFIX_DECREMENT = 42, TOY_AST_FLAG_PREFIX_DECREMENT = 42,
TOY_AST_FLAG_POSTFIX_INCREMENT = 43, TOY_AST_FLAG_POSTFIX_INCREMENT = 43,
TOY_AST_FLAG_POSTFIX_DECREMENT = 44, TOY_AST_FLAG_POSTFIX_DECREMENT = 44,
TOY_AST_FLAG_INVERT = 45,
TOY_AST_FLAG_INVOKATION = 45, TOY_AST_FLAG_INVOKATION = 46,
TOY_AST_FLAG_ATTRIBUTE = 46, TOY_AST_FLAG_ATTRIBUTE = 47,
// TOY_AST_FLAG_TERNARY, // TOY_AST_FLAG_TERNARY,
} Toy_AstFlag; } Toy_AstFlag;
@@ -96,7 +97,7 @@ typedef enum Toy_AstFlag {
//the root AST type //the root AST type
typedef union Toy_Ast Toy_Ast; typedef union Toy_Ast Toy_Ast;
typedef struct Toy_AstBlock { typedef struct Toy_AstBlock { //TODO: optimize away single-line blocks in the AST
Toy_AstType type; Toy_AstType type;
bool innerScope; bool innerScope;
Toy_Ast* child; //begin encoding the line Toy_Ast* child; //begin encoding the line
+3 -1
View File
@@ -37,6 +37,8 @@ Toy_Value Toy_private_handleStringAttributes(Toy_VM* vm, Toy_Value compound, Toy
free(buffer); free(buffer);
return TOY_VALUE_FROM_STRING(str); return TOY_VALUE_FROM_STRING(str);
} }
//TODO: as capitalized
//TODO: split?
else { else {
char buffer[256]; char buffer[256];
snprintf(buffer, 256, "Unknown attribute '%s' of type '%s'", TOY_VALUE_AS_STRING(attribute)->leaf.data, Toy_getValueTypeAsCString(compound.type)); snprintf(buffer, 256, "Unknown attribute '%s' of type '%s'", TOY_VALUE_AS_STRING(attribute)->leaf.data, Toy_getValueTypeAsCString(compound.type));
@@ -172,7 +174,7 @@ static void attr_tableRemove(Toy_VM* vm, Toy_FunctionNative* self) {
Toy_Value Toy_private_handleTableAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) { Toy_Value Toy_private_handleTableAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute) {
if (MATCH_VALUE_AND_CSTRING(attribute, "length")) { if (MATCH_VALUE_AND_CSTRING(attribute, "length")) {
return TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_ARRAY(compound)->count); return TOY_VALUE_FROM_INTEGER(TOY_VALUE_AS_TABLE(compound)->count);
} }
else if (MATCH_VALUE_AND_CSTRING(attribute, "insert")) { else if (MATCH_VALUE_AND_CSTRING(attribute, "insert")) {
Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_tableInsert); Toy_Function* fn = Toy_createFunctionFromCallback(&vm->memoryBucket, attr_tableInsert);
-2
View File
@@ -11,13 +11,11 @@
// [x] array.length // [x] array.length
// [x] array.pushBack(x) // [x] array.pushBack(x)
// [x] array.popBack() // [x] array.popBack()
// [x] array.forEach(fn) // fn(x) -> void
// [ ] array.sort(fn) // fn(a,b) -> int // [ ] array.sort(fn) // fn(a,b) -> int
// [x] table.length // [x] table.length
// [x] table.insert(x, y) // [x] table.insert(x, y)
// [x] table.hasKey(x) // [x] table.hasKey(x)
// [x] table.remove(x) // [x] table.remove(x)
// [ ] table.forEach(fn) // fn(x,y) -> void
Toy_Value Toy_private_handleStringAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute); Toy_Value Toy_private_handleStringAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
Toy_Value Toy_private_handleArrayAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute); Toy_Value Toy_private_handleArrayAttributes(Toy_VM* vm, Toy_Value compound, Toy_Value attribute);
-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
+32 -8
View File
@@ -78,6 +78,7 @@ static void emitByte(unsigned char** handle, unsigned int* capacity, unsigned in
((unsigned char*)(*handle))[(*count)++] = byte; ((unsigned char*)(*handle))[(*count)++] = byte;
} }
//BUG: There might be issues here when compiled on big-endian platforms
static void emitInt(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned int bytes) { static void emitInt(unsigned char** handle, unsigned int* capacity, unsigned int* count, unsigned int bytes) {
char* ptr = (char*)&bytes; char* ptr = (char*)&bytes;
emitByte(handle, capacity, count, *(ptr++)); emitByte(handle, capacity, count, *(ptr++));
@@ -168,8 +169,8 @@ static unsigned int emitString(Toy_Bytecode** mb, Toy_String* str) {
} }
//mark the position within the jump index, reusing an existing entry if it exists //mark the position within the jump index, reusing an existing entry if it exists
for (unsigned int i = 0; i < (*mb)->jumpsCount; i++) { for (unsigned int i = 0; i < (*mb)->jumpsCount; i += 4) {
if ((*mb)->jumps[i] == dataAddr) { if (*(unsigned int*)((*mb)->jumps + i) == dataAddr) {
//reuse, and finish //reuse, and finish
EMIT_INT(mb, code, i); EMIT_INT(mb, code, i);
return 1; return 1;
@@ -200,13 +201,13 @@ static unsigned int emitParameters(Toy_Bytecode* mb, Toy_Ast* ast) {
} }
//the address within the data section //the address within the data section
char buffer[128]; char buffer[256];
snprintf(buffer, 128, "%.*s", ast->varDeclare.name->info.length, ast->varDeclare.name->leaf.data); snprintf(buffer, 256, "%.*s", ast->varDeclare.name->info.length, ast->varDeclare.name->leaf.data);
unsigned int dataAddr = emitCStringToData(&(mb->data), &(mb->dataCapacity), &(mb->dataCount), buffer); unsigned int dataAddr = emitCStringToData(&(mb->data), &(mb->dataCapacity), &(mb->dataCount), buffer);
//check the param index for that entry i.e. don't reuse parameter names //check the param index for that entry i.e. don't reuse parameter names
for (unsigned int i = 0; i < mb->paramCount; i++) { for (unsigned int i = 0; i < mb->paramCount; i+=4) {
if (mb->param[i] == dataAddr) { if (*(unsigned int*)(mb->param + i) == dataAddr) {
//not allowed //not allowed
fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Function parameters must have unique names\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_ERROR "COMPILER ERROR: Function parameters must have unique names\n" TOY_CC_RESET);
mb->panic = true; mb->panic = true;
@@ -376,6 +377,17 @@ static unsigned int writeInstructionUnary(Toy_Bytecode** mb, Toy_AstUnary ast) {
result = 1; result = 1;
} }
else if (ast.flag == TOY_AST_FLAG_INVERT) {
result = writeBytecodeFromAst(mb, ast.child);
EMIT_BYTE(mb, code, TOY_OPCODE_INVERT);
//4-byte alignment
EMIT_BYTE(mb, code, 0);
EMIT_BYTE(mb, code, 0);
EMIT_BYTE(mb, code, 0);
}
else { else {
fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST unary flag found\n" TOY_CC_RESET); fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST unary flag found\n" TOY_CC_RESET);
exit(-1); exit(-1);
@@ -620,6 +632,12 @@ static unsigned int writeInstructionIfThenElse(Toy_Bytecode** mb, Toy_AstIfThenE
//cond-branch //cond-branch
writeBytecodeFromAst(mb, ast.condBranch); writeBytecodeFromAst(mb, ast.condBranch);
//leave the assigned value on the stack when inside a condition
if (checkForChainedAssign(ast.condBranch)) {
Toy_AstVarAccess access = { .type = TOY_AST_VAR_ACCESS, .child = ast.condBranch->varAssign.target };
writeInstructionAccess(mb, access);
}
//emit the jump word (opcode, type, condition, padding) //emit the jump word (opcode, type, condition, padding)
EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); EMIT_BYTE(mb, code, TOY_OPCODE_JUMP);
EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE);
@@ -665,6 +683,12 @@ static unsigned int writeInstructionWhileThen(Toy_Bytecode** mb, Toy_AstWhileThe
//cond-branch //cond-branch
writeBytecodeFromAst(mb, ast.condBranch); writeBytecodeFromAst(mb, ast.condBranch);
//leave the assigned value on the stack when inside a condition
if (checkForChainedAssign(ast.condBranch)) {
Toy_AstVarAccess access = { .type = TOY_AST_VAR_ACCESS, .child = ast.condBranch->varAssign.target };
writeInstructionAccess(mb, access);
}
//emit the jump word (opcode, type, condition, padding) //emit the jump word (opcode, type, condition, padding)
EMIT_BYTE(mb, code, TOY_OPCODE_JUMP); EMIT_BYTE(mb, code, TOY_OPCODE_JUMP);
EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE); EMIT_BYTE(mb, code, TOY_OP_PARAM_JUMP_RELATIVE);
@@ -763,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);
@@ -1440,7 +1464,7 @@ static void writeBytecodeBody(Toy_Bytecode* mb, Toy_Ast* ast) {
writeBytecodeFromAst(&mb, ast); writeBytecodeFromAst(&mb, ast);
//append an extra return if needed //append an extra return if needed
if (mb->codeCount <= 4 || mb->code[mb->codeCount - 4] != TOY_OPCODE_RETURN) { //if empty or no return statement if (mb->codeCount < 4 || mb->code[mb->codeCount - 4] != TOY_OPCODE_RETURN) { //if empty or no return statement
EMIT_BYTE(&mb, code, TOY_OPCODE_RETURN); //end terminator EMIT_BYTE(&mb, code, TOY_OPCODE_RETURN); //end terminator
EMIT_BYTE(&mb, code, 0); //4-byte alignment EMIT_BYTE(&mb, code, 0); //4-byte alignment
EMIT_BYTE(&mb, code, 0); EMIT_BYTE(&mb, code, 0);
+1
View File
@@ -22,6 +22,7 @@ typedef enum Toy_OpcodeType {
TOY_OPCODE_MULTIPLY, TOY_OPCODE_MULTIPLY,
TOY_OPCODE_DIVIDE, TOY_OPCODE_DIVIDE,
TOY_OPCODE_MODULO, TOY_OPCODE_MODULO,
TOY_OPCODE_INVERT, //negative numbers
//comparison instructions //comparison instructions
TOY_OPCODE_COMPARE_EQUAL, TOY_OPCODE_COMPARE_EQUAL,
+4 -4
View File
@@ -198,7 +198,7 @@ static ParsingTuple parsingRulesetTable[] = {
//structural operators //structural operators
{PREC_CALL,group,invoke},// TOY_TOKEN_OPERATOR_PAREN_LEFT, {PREC_CALL,group,invoke},// TOY_TOKEN_OPERATOR_PAREN_LEFT,
{PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_PAREN_RIGHT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_PAREN_RIGHT,
{PREC_GROUP,compound,aggregate},// TOY_TOKEN_OPERATOR_BRACKET_LEFT, {PREC_CALL,compound,aggregate},// TOY_TOKEN_OPERATOR_BRACKET_LEFT,
{PREC_NONE,compound,aggregate},// TOY_TOKEN_OPERATOR_BRACKET_RIGHT, {PREC_NONE,compound,aggregate},// TOY_TOKEN_OPERATOR_BRACKET_RIGHT,
{PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACE_LEFT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACE_LEFT,
{PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACE_RIGHT, {PREC_NONE,NULL,NULL},// TOY_TOKEN_OPERATOR_BRACE_RIGHT,
@@ -324,7 +324,7 @@ static Toy_AstFlag literal(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_As
case TOY_TOKEN_LITERAL_INTEGER: { case TOY_TOKEN_LITERAL_INTEGER: {
//filter the '_' character //filter the '_' character
char buffer[parser->previous.length]; char buffer[parser->previous.length + 1];
unsigned int i = 0, o = 0; unsigned int i = 0, o = 0;
do { do {
@@ -341,7 +341,7 @@ static Toy_AstFlag literal(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_As
case TOY_TOKEN_LITERAL_FLOAT: { case TOY_TOKEN_LITERAL_FLOAT: {
//filter the '_' character //filter the '_' character
char buffer[parser->previous.length]; char buffer[parser->previous.length + 1];
unsigned int i = 0, o = 0; unsigned int i = 0, o = 0;
do { do {
@@ -417,7 +417,7 @@ static Toy_AstFlag unary(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_Ast*
} }
else { else {
//actually emit the negation node //actually emit the negation node
Toy_private_emitAstUnary(bucketHandle, rootHandle, TOY_AST_FLAG_NEGATE); Toy_private_emitAstUnary(bucketHandle, rootHandle, TOY_AST_FLAG_INVERT);
} }
} }
+4
View File
@@ -1,5 +1,6 @@
#include "toy_print.h" #include "toy_print.h"
#include <assert.h>
#include <stdio.h> #include <stdio.h>
static Toy_callbackType printCallback = puts; static Toy_callbackType printCallback = puts;
@@ -19,14 +20,17 @@ void Toy_assertFailure(const char* msg) {
} }
void Toy_setPrintCallback(Toy_callbackType cb) { void Toy_setPrintCallback(Toy_callbackType cb) {
assert(cb);
printCallback = cb; printCallback = cb;
} }
void Toy_setErrorCallback(Toy_callbackType cb) { void Toy_setErrorCallback(Toy_callbackType cb) {
assert(cb);
errorCallback = cb; errorCallback = cb;
} }
void Toy_setAssertFailureCallback(Toy_callbackType cb) { void Toy_setAssertFailureCallback(Toy_callbackType cb) {
assert(cb);
assertCallback = cb; assertCallback = cb;
} }
+1 -1
View File
@@ -48,7 +48,7 @@ static void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, T
} }
//if this spot is free, insert and return //if this spot is free, insert and return
if (TOY_VALUE_IS_NULL(scope->data[probe].value)) { if (scope->data[probe].key == NULL) { //fuck my life
scope->data[probe] = entry; scope->data[probe] = entry;
scope->count++; scope->count++;
scope->maxPsl = entry.psl > scope->maxPsl ? entry.psl : scope->maxPsl; scope->maxPsl = entry.psl > scope->maxPsl ? entry.psl : scope->maxPsl;
+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);
} }
+26 -2
View File
@@ -73,6 +73,8 @@ static void processRead(Toy_VM* vm) {
//grab the jump as an integer //grab the jump as an integer
unsigned int jump = *((int*)(vm->code + vm->jumpsAddr + READ_INT(vm))); unsigned int jump = *((int*)(vm->code + vm->jumpsAddr + READ_INT(vm)));
//BUG: the jump parameter could cause an out of bounds read if it's malformed
//jumps are relative to the data address //jumps are relative to the data address
char* cstring = (char*)(vm->code + vm->dataAddr + jump); char* cstring = (char*)(vm->code + vm->dataAddr + jump);
@@ -544,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);
@@ -552,6 +554,25 @@ static void processIterate(Toy_VM* vm) {
} }
static void processArithmetic(Toy_VM* vm, Toy_OpcodeType opcode) { static void processArithmetic(Toy_VM* vm, Toy_OpcodeType opcode) {
//BUGFIX: handle negative variables
if (opcode == TOY_OPCODE_INVERT) {
Toy_Value value = Toy_popStack(&vm->stack);
if (TOY_VALUE_IS_INTEGER(value)) {
int intermediate = TOY_VALUE_AS_INTEGER(value);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_INTEGER(-intermediate));
}
else if (TOY_VALUE_IS_FLOAT(value)) {
float intermediate = TOY_VALUE_AS_FLOAT(value);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_FLOAT(-intermediate));
}
else {
Toy_error("Can't negate a non-number");
Toy_freeValue(value);
Toy_pushStack(&vm->stack, TOY_VALUE_FROM_NULL());
}
return;
}
Toy_Value right = Toy_popStack(&vm->stack); Toy_Value right = Toy_popStack(&vm->stack);
Toy_Value left = Toy_popStack(&vm->stack); Toy_Value left = Toy_popStack(&vm->stack);
@@ -805,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) {
@@ -874,6 +895,8 @@ static void processConcat(Toy_VM* vm) {
static void processIndex(Toy_VM* vm) { static void processIndex(Toy_VM* vm) {
unsigned char count = READ_BYTE(vm); //value[index, length] ; 1[2, 3] unsigned char count = READ_BYTE(vm); //value[index, length] ; 1[2, 3]
//TODO: slicing from the end of a string/array, value[0:-1]
Toy_Value value = TOY_VALUE_FROM_NULL(); Toy_Value value = TOY_VALUE_FROM_NULL();
Toy_Value index = TOY_VALUE_FROM_NULL(); Toy_Value index = TOY_VALUE_FROM_NULL();
Toy_Value length = TOY_VALUE_FROM_NULL(); Toy_Value length = TOY_VALUE_FROM_NULL();
@@ -1094,6 +1117,7 @@ static unsigned int process(Toy_VM* vm) {
case TOY_OPCODE_MULTIPLY: case TOY_OPCODE_MULTIPLY:
case TOY_OPCODE_DIVIDE: case TOY_OPCODE_DIVIDE:
case TOY_OPCODE_MODULO: case TOY_OPCODE_MODULO:
case TOY_OPCODE_INVERT:
processArithmetic(vm, opcode); processArithmetic(vm, opcode);
break; break;
-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
@@ -0,0 +1 @@
//WARN: not yet implemented
@@ -157,4 +157,9 @@ print !false; //true
print a; print a;
} }
//TODO: type casting {
//Yet Another Cosmic Joke
var a = null;
var i = 42;
print a;
}
+90
View File
@@ -0,0 +1,90 @@
//NOTE: Not tested: null (neither true or false), Opaque, Any
{
//booleans
var value: Bool = true;
if (value) print "boolean";
else assert false, "boolean";
}
{
var value: Bool = false;
if (value) assert false, "boolean";
else print "boolean";
}
//integers
{
var value: Int = 42;
if (value) print "integer";
else assert false, "integer";
}
{
var value: Int = 0;
if (value) assert false, "integer";
else print "integer";
}
//floats
{
var value: Float = 42.8891;
if (value) print "float";
else assert false, "float";
}
{
var value: Float = 0;
if (value) assert false, "float";
else print "float";
}
//strings
{
var value: String = "foobar";
if (value) print "string";
else assert false, "string";
}
{
var value: String = ""; //empty string is truthy
if (value) print "string";
else assert false, "string";
}
//arrays
{
var value: Array = [1,2,3];
if (value) print "array";
else assert false, "array";
}
{
var value: Array = []; //empty array is truthy
if (value) print "array";
else assert false, "array";
}
//table
{
var value: Table = ["alpha":"bravo"];
if (value) print "table";
else assert false, "table";
}
{
var value: Table = [:]; //empty table is truthy
if (value) print "table";
else assert false, "table";
}
//function
{
fn identity(x) {
return x;
}
if (identity) print "function";
else assert false, "function";
}
//TODO: test library as API, would be cleaner than using asserts
@@ -1,4 +1,4 @@
//typenames are now capitalized, so ensure they don't overlap with variable names //NOTE: typenames are now capitalized, to ensure they don't overlap with variable names
var bool: Bool = true; var bool: Bool = true;
print bool; print bool;
@@ -1,4 +1,4 @@
//TODO: empty test script //WARN: empty test script
//TODO: test iteration on arrays, tables, closures //TODO: test iteration on arrays, tables, closures
@@ -17,5 +17,5 @@ print "\tHello\nworld!";
print "Hello world"[0,5]; print "Hello world"[0,5];
//print from a substring, after a concat //print from a substring, after a concat
print ("hello" .. "world")[2,6]; print ("hello" .. "world")[3,3]; //should print "low"
@@ -0,0 +1,12 @@
fn empty() {
//
}
fn almostEmpty() {
return;
}
fn notEmpty() {
return 42;
}
+2 -2
View File
@@ -20,10 +20,10 @@ endif
TEST_ROOTDIR=../.. TEST_ROOTDIR=../..
TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR)
TEST_REPLDIR=$(TEST_ROOTDIR)/$(TOY_REPLDIR) TEST_REPLDIR=$(TEST_ROOTDIR)/$(TOY_REPLDIR)
TEST_SCRIPTDIR=. TEST_SCRIPTDIRS=basics values keywords algorithms
#file names #file names
TEST_SCRIPTFILES=$(wildcard $(TEST_SCRIPTDIR)/test_*.toy) TEST_SCRIPTFILES=$(foreach dir,$(TEST_SCRIPTDIRS), $(wildcard $(dir)/test_*.toy))
#build the source and repl, copy to the local dir, and run #build the source and repl, copy to the local dir, and run
all: source repl copy run all: source repl copy run
-26
View File
@@ -1,26 +0,0 @@
//1-D array
var arr = [1, 2, 3];
arr[1] = 6;
assert arr == [1, 6, 3], "1-D array failed";
//we need to go deeper
var barr = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
barr[1][1] = 99;
assert barr == [[1, 2, 3],[4,99,6],[7,8,9]], "2-D array failed";
//test trailing commas
var a = [1, 2, 3, ];
print a;
//test empty arrays
var b = [];
print b;
-1
View File
@@ -1 +0,0 @@
//TODO: empty test script
-22
View File
@@ -1,22 +0,0 @@
//closures
fn makeCounter() {
var counter: Int = 0;
fn increment() {
return ++counter;
}
return increment;
}
var tally = makeCounter();
while (true) {
var result = tally();
print result;
if (result >= 10) {
break;
}
}
@@ -1,10 +0,0 @@
//check functions are first class citizens
fn hello() {
print "Hello world";
}
fn ident(x) {
return x;
}
assert ident(hello) == hello, "First class function check failed";
@@ -1,11 +0,0 @@
fn a(x) {
assert x == 42;
}
fn b() {
return 42;
}
a(b());
-1
View File
@@ -1 +0,0 @@
//TODO: empty test script
-8
View File
@@ -1,8 +0,0 @@
fn empty() { //BUG: there's an extra return in the bytecode
return;
}
fn full() {
return 42;
}
-33
View File
@@ -1,33 +0,0 @@
//1-D table
var a = ["alpha": 1, "beta": 2, "gamma": 3];
a["beta"] = 6;
print a;
assert a == ["alpha": 1, "beta": 6, "gamma": 3], "1-D tables failed";
//nested
var b = [
"outer": ["inner": true],
"alpha": 1,
"beta": 2,
"gamma": 3
];
print b;
assert b == ["alpha": 1, "beta": 2, "gamma": 3, "outer": ["inner": true]], "nested tables failed";
//test empty tables
var empty = [:];
print empty;
assert empty == [:], "empty tables failed";
//test trailing commas
var trailing = [
"alpha":1,
"beta":2,
"gamma":3,
];
print trailing;
assert trailing == ["alpha": 1, "beta": 2, "gamma": 3], "trailing tables failed";
-80
View File
@@ -1,80 +0,0 @@
//booleans
{
var value: Bool = true;
if (value) {
print "boolean";
}
else {
assert false, "boolean";
}
}
{
var value: Bool = false;
if (value) {
assert false, "boolean";
}
else {
print "boolean";
}
}
//integers
{
var value: Int = 42;
if (value) {
print "integer";
}
else {
assert false, "integer";
}
}
{
var value: Int = 0;
if (value) {
assert false, "integer";
}
else {
print "integer";
}
}
//floats
{
var value: Float = 42.8891;
if (value) {
print "float";
}
else {
assert false, "float";
}
}
{
var value: Float = 0;
if (value) {
assert false, "float";
}
else {
print "float";
}
}
//everything else
{
var value: String = "foobar";
if (value) {
print "string";
}
else {
assert false, "string";
}
}
+46
View File
@@ -0,0 +1,46 @@
//1-D array
{
var array: Array = [1, 2, 3];
array[1] = 6;
assert array == [1, 6, 3], "1-D array failed";
}
//we need to go deeper
{
var array = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
];
array[1][1] = 99;
assert array == [[1, 2, 3],[4,99,6],[7,8,9]], "2-D array failed";
}
//attributes
{
var array = [0,1,2,3,4,5,6,7,8,9];
assert array.length == 10, "Array length attribute failed";
array.pushBack(10);
assert array.length == 11, "Array pushBack attribute failed";
array.popBack();
array.popBack();
assert array.popBack() == 8, "Array popBack attribute failed";
}
//sorting algorithm
{
//WARN: Array sorting algorithm not yet implemented
}
//syntax
{
var empty = [];
assert empty.length == 0, "Empty array failed";
var trailing = [1, 2, 3, ];
assert trailing == [1,2,3], "Trailing comma array failed";
}
+55
View File
@@ -0,0 +1,55 @@
//define and invoke a function
{
fn greeting() {
return "Hello world";
}
assert greeting() == "Hello world", "Function definition failed";
}
//functions within other function argument lists
{
fn outer(arg: String) {
return arg == "Hello world";
}
fn inner() {
return "Hello world";
}
assert outer(inner()), "Functions within argument lists failed";
}
//first class functions
{
fn hello() {
print "Hello world";
}
fn identity(x) {
return x;
}
assert identity(hello) == hello, "First class functions failed";
}
//closures
{
//closures
fn makeCounter() {
var counter: Int = 0;
fn increment() {
return ++counter;
}
return increment;
}
var tally: Function = makeCounter();
tally();
tally();
tally();
assert tally() == 4, "closures failed";
}
+27
View File
@@ -0,0 +1,27 @@
//declare, define and manipulate strings
{
var string: String = "Hello World";
assert string[5] == " ", "String indexing failed";
var greeting: String = string[0:5];
assert greeting == "Hello", "String slicing failed";
var parting: String = "Goodbye " .. string[6:5];
print parting;
assert parting == "Goodbye World", "String concat from an index failed";
//WARN: Unbounded slice not possible without indexing from the end of a string
//var simple: String = parting[5:-1];
//assert simple == "bye World", "String unbounded slice failed";
}
//var greeting: String = "Hello World";
//var parting: String = "Goodbye " .. greeting[6:5];
//String attributes
{
var string: String = "Hello World";
assert string.length == 11, "string.length failed";
assert string.asUpper == "HELLO WORLD", "string.asUpper failed";
assert string.asLower == "hello world", "string.asLower failed";
}
+33
View File
@@ -0,0 +1,33 @@
//1-D table
{
var table: Table = ["alpha": 1, "beta": 2, "gamma": 3];
table["beta"] = 6;
assert table == ["alpha": 1, "beta": 6, "gamma": 3], "1-D tables failed";
}
//nested
{
var table: Table = [
"outer": ["inner": true],
"alpha": 1,
"beta": 2,
"gamma": 3
];
assert table == ["alpha": 1, "beta": 2, "gamma": 3, "outer": ["inner": true]], "nested tables failed";
}
//syntax
{
var empty = [:];
assert empty.length == 0, "Empty table failed";
var trailing = [
"alpha":1,
"beta":2,
"gamma":3,
];
assert trailing == ["alpha": 1, "beta": 2, "gamma": 3], "Trailing comma table failed";
}
+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 //WARN: 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 //WARN: 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 -3
View File
@@ -607,9 +607,8 @@ int main(void) {
total += res; total += res;
} }
//TODO: references //WARN: Testing references not implemented
//TODO: type coersions //WARN: Testing opaques not implemented
//TODO: opaques?
return total; return total;
} }