From 547229e15017a645d51ed25bf0ee2f79b2e29686 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Fri, 10 Apr 2026 15:28:56 +1000 Subject: [PATCH] Script tests re-added, all tests can run under gdb Also fixed a minor bug with printing, and removed the ability to configure the parser. Added and updated QUICKSTART.md as a quick way to get people started. There's some broken scripts under 'scripts/' that require functions to work properly. --- QUICKSTART.md | 146 ++++++++++++++++ repl/main.c | 11 +- scripts/benchpress.toy | 17 ++ scripts/fib.toy | 16 ++ scripts/fizzbuzz.toy | 24 +++ scripts/funky.toy | 20 +++ scripts/leapyear.toy | 8 + source/toy_console_colors.h | 2 +- source/toy_parser.c | 20 +-- source/toy_parser.h | 6 - source/toy_vm.c | 4 +- tests/makefile | 6 +- tests/scripts/gdb_init | 2 + tests/scripts/makefile | 52 ++++++ tests/scripts/test_arrays.toy | 26 +++ tests/scripts/test_control_flow.toy | 9 + tests/scripts/test_keyword_assert.toy | 13 ++ tests/scripts/test_keyword_if_then_else.toy | 65 +++++++ .../test_keyword_if_then_else_no_braces.toy | 46 +++++ tests/scripts/test_keyword_print.toy | 21 +++ .../test_keyword_while_break_continue.toy | 132 +++++++++++++++ tests/scripts/test_scopes.toy | 17 ++ tests/scripts/test_tables.toy | 33 ++++ tests/scripts/test_truthiness.toy | 80 +++++++++ tests/scripts/test_variables.toy | 160 ++++++++++++++++++ tests/units/makefile | 28 +-- 26 files changed, 916 insertions(+), 48 deletions(-) create mode 100644 QUICKSTART.md create mode 100644 scripts/benchpress.toy create mode 100644 scripts/fib.toy create mode 100644 scripts/fizzbuzz.toy create mode 100644 scripts/funky.toy create mode 100644 scripts/leapyear.toy create mode 100644 tests/scripts/gdb_init create mode 100644 tests/scripts/makefile create mode 100644 tests/scripts/test_arrays.toy create mode 100644 tests/scripts/test_control_flow.toy create mode 100644 tests/scripts/test_keyword_assert.toy create mode 100644 tests/scripts/test_keyword_if_then_else.toy create mode 100644 tests/scripts/test_keyword_if_then_else_no_braces.toy create mode 100644 tests/scripts/test_keyword_print.toy create mode 100644 tests/scripts/test_keyword_while_break_continue.toy create mode 100644 tests/scripts/test_scopes.toy create mode 100644 tests/scripts/test_tables.toy create mode 100644 tests/scripts/test_truthiness.toy create mode 100644 tests/scripts/test_variables.toy diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..2710a73 --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,146 @@ +# Toy v2 Quick-Start Guide + +To help you start using Toy as fast as possible, here are the most useful elements of the language. Not everything available is listed, but this should let you start coding right away. + +## Keyword 'print' + +The `print` keyword takes one value as a parameter, which is sent to stdout by default, or can be redirected elsewhere using C. + +``` +print "Hello World!"; +``` + +## Keyword 'assert' + +The `assert` keyword takes two values as parameters, separated by a comma. If the first value is falsy or `null`, the optional second parameter is sent to stderr by default, or can be redirected elsewhere using C. If no second parameter is provided, a generic error message is used instead. + +``` +//nothing happens +assert 1 < 2; + +//this assert will fail, and output the second parameter +assert null, "Hello world!"; +``` + +## Variables and Types + +Variables can be declared with the `var` keyword, and can be given an optional type from the list below. If no type is specified, `any` is used by default. + +``` +var answer = 42; + +var question: string = "How many roads must a man walk down?"; +``` + +To make a variable immutable, use the `const` keyword after the type declaration. In this case, it must be assigend a value. + +``` +var quote: string const = "War. War never changes."; +``` + +The types available in Toy are: + +| type | name | description | +| --- | --- | --- | +| `bool` | boolean | Either `true` or `false`. | +| `int` | integer | Any whole number (32-bits). | +| `float` | float | A decimal number (32-bits), using floating-point arithmetic. | +| `string` | string | A series of characters used for text processing. | +| `array` | array | A series of values stored sequentially in memory. | +| `table` | table | A series key-value pairs stored in such a way that allows for fast lookups. Booleans, functions, opaques and `null` can't be used as keys. | +| `function` | function | A chunk of reusable code that takes zero or more parameters, and returns zero or more results. Functions are declared with the `fn` keyword. | +| `opaque` | opaque | This value is unusable in Toy, but allows you to pass data between C functions. | +| `any` | any | The default type when nothing is specified. Theis can hold any value. | + +## Control Flow + +Choosing an option, or repeating a chunk of code multiple times, is essential for any general purpose language. + +Choosing between two options can be done with the `if-then-else` else statement. If the condition is truthy, the 'then-branch' will be executed. Otherwise, the optional 'else-branch' is executed instead. + +``` +var answer = 42; + +if (answer < 56) { + print "Cod dang it!"; +} +else { + print "Something's fishy here..."; +} +``` + +``` +var challenge = "hard"; + +if (challenge == "hard") { + print "I choose to build a scripting language, not because it's easy, but because it's hard!"; +} + +//the else-branch is optional +``` + +To repeat a certain action, use the `while-then` loop, which repeats the body as long as the condition is true at the beginning of each loop. + +``` +var loops = 0; + +while (loops++ < 8) { + print "These episodes are endless."; +} +``` + +To break out of a loop, you can use the `break` keyword. Alternatively, to restart the loop early, use the `continue` keyword. + +``` +var loops = 0; + +while (true) { + if (++loops < 15532) { + continue; + } + + break; //poor yuki ;_; +} +``` + +*Note: The `for` loop is coming, eventually, but isn't vital right now.* + +## Arrays and Tables + +Arrays are defined with a pair of brackets, and can contain a list of comma-separated values. + +``` +//'array' is a reserved keyword, so it can't be used as a name +var a = [1,2,3]; + +//instead, it's used as a type +var b: array = [4,5,6]; + +//define an empty array like this +var c: array = []; + +//arrays are zero-indexed +print a[0]; //'1' +``` + +Tables are also defined with brackets, and contain a comma-separated list of key-value pairs defined by colons: + +``` +//most types can be used as keys +var t = ["alpha": 1, "beta": 2, "gamma": 3]; + +//the 'table' keyword can define the type, and an empty table still has a colon +var u: table = [:]; + +//printing a table does NOT guarantee internal order, but the elements can be accessed with the keys. +print t["beta"]; +``` + +## Functions + +Watch this space. + +## External Libraries and Extending Toy + +Watch this space. + diff --git a/repl/main.c b/repl/main.c index 25e0f7b..0ef84ed 100644 --- a/repl/main.c +++ b/repl/main.c @@ -116,21 +116,21 @@ static void printCallback(const char* msg) { } static void errorAndExitCallback(const char* msg) { - fprintf(stderr, TOY_CC_ERROR "Error: %s\n" TOY_CC_RESET, msg); + fprintf(stderr, TOY_CC_ERROR "Error: %s" TOY_CC_RESET "\n", msg); exit(-1); } static void errorAndContinueCallback(const char* msg) { - fprintf(stderr, TOY_CC_ERROR "Error: %s\n" TOY_CC_RESET, msg); + fprintf(stderr, TOY_CC_ERROR "Error: %s" TOY_CC_RESET "\n", msg); } static void assertFailureAndExitCallback(const char* msg) { - fprintf(stderr, TOY_CC_ASSERT "Assert Failure: %s\n" TOY_CC_RESET, msg); + fprintf(stderr, TOY_CC_ASSERT "Assert Failure: %s" TOY_CC_RESET "\n", msg); exit(-1); } static void assertFailureAndContinueCallback(const char* msg) { - fprintf(stderr, TOY_CC_ASSERT "Assert Failure: %s\n" TOY_CC_RESET, msg); + fprintf(stderr, TOY_CC_ASSERT "Assert Failure: %s" TOY_CC_RESET "\n", msg); } static void noOpCallback(const char* msg) { @@ -325,7 +325,6 @@ int repl(const char* filepath) { Toy_bindLexer(&lexer, inputBuffer); Toy_Parser parser; Toy_bindParser(&parser, &lexer); - Toy_configureParser(&parser, false); Toy_Ast* ast = Toy_scanParser(&bucket, &parser); //Ast is in the bucket, so it doesn't need to be freed //parsing error, retry @@ -478,8 +477,6 @@ int main(int argc, const char* argv[]) { Toy_Parser parser; Toy_bindParser(&parser, &lexer); - Toy_configureParser(&parser, cmd.removeAssert); - Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); Toy_Ast* ast = Toy_scanParser(&bucket, &parser); unsigned char* bytecode = Toy_compileToBytecode(ast); diff --git a/scripts/benchpress.toy b/scripts/benchpress.toy new file mode 100644 index 0000000..4c3eecb --- /dev/null +++ b/scripts/benchpress.toy @@ -0,0 +1,17 @@ +//calculate the nth fibonacci number, and print it + +var counter: int = 0; + +var first: int = 1; +var second: int = 0; + +//BUG: This causes a stack overflow +while (counter < 100_000) { + var third: int = first + second; + first = second; + second = third; + + print third; + + ++counter; +} \ No newline at end of file diff --git a/scripts/fib.toy b/scripts/fib.toy new file mode 100644 index 0000000..902dc6a --- /dev/null +++ b/scripts/fib.toy @@ -0,0 +1,16 @@ +//BUG: Not yet functional + +//example of the fibonacci sequence +fn fib(n: int) { + if (n < 2) return n; + return fib(n-1) + fib(n-2); +} + +//TODO: type coercion syntax hasn't been decided on yet, but it will be needed +for (var i = 1; i <= 10; i++) { + print i .. ":" .. fib(i); +} + +//Note to my future self: yes, the base case in 'fib()' is 'n < 2', stop second guessing yourself! +//Note to my past self: don't tell me what to do! +//Note to both of you: keep it down you young whipper snappers! \ No newline at end of file diff --git a/scripts/fizzbuzz.toy b/scripts/fizzbuzz.toy new file mode 100644 index 0000000..3441d17 --- /dev/null +++ b/scripts/fizzbuzz.toy @@ -0,0 +1,24 @@ +//standard example, using 'while' instead of 'for', because it's not ready yet + +var counter: int = 0; + +while (++counter <= 100) { + var result: string = ""; + + if (counter % 3 == 0) { + result = result .. "fizz"; + } + + if (counter % 5 == 0) { + result = result .. "buzz"; + } + + //finally + if (result != "") { + print result; + } + else { + print counter; + } +} + diff --git a/scripts/funky.toy b/scripts/funky.toy new file mode 100644 index 0000000..532933d --- /dev/null +++ b/scripts/funky.toy @@ -0,0 +1,20 @@ +//BUG: functions aren't working yet +fn makeCounter() { + var counter: int = 0; + + fn increment() { + return ++counter; + } + + return increment; +} + +var tally = makeCounter(); + +while (true) { + var result = tally(); + + if (result >= 10_000_000) { + break; + } +} diff --git a/scripts/leapyear.toy b/scripts/leapyear.toy new file mode 100644 index 0000000..f9410ab --- /dev/null +++ b/scripts/leapyear.toy @@ -0,0 +1,8 @@ +//BUG: Not yet functional + +//find the leap years +fn isLeapYear(n: int) { + if (n % 400 == 0) return true; + if (n % 100 == 0) return false; + return n % 4 == 0; +} diff --git a/source/toy_console_colors.h b/source/toy_console_colors.h index 419cbc6..9267663 100644 --- a/source/toy_console_colors.h +++ b/source/toy_console_colors.h @@ -43,7 +43,7 @@ reference: https://stackoverflow.com/questions/4842424/list-of-ansi-color-escape #define TOY_CC_WARN "\033[" TOY_CC_FONT_YELLOW ";" TOY_CC_BACK_DEFAULT "m" #define TOY_CC_ERROR "\033[" TOY_CC_FONT_RED ";" TOY_CC_BACK_DEFAULT "m" #define TOY_CC_ASSERT "\033[" TOY_CC_FONT_BLACK ";" TOY_CC_BACK_MAGENTA "m" -#define TOY_CC_RESET "\033[" "0" "m" +#define TOY_CC_RESET "\033[" TOY_CC_FONT_DEFAULT ";" TOY_CC_BACK_DEFAULT "m" //for unsupported platforms, these become no-ops #else diff --git a/source/toy_parser.c b/source/toy_parser.c index 469b87b..8615009 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -818,18 +818,12 @@ static void makeAssertStmt(Toy_Bucket** bucketHandle, Toy_Parser* parser, Toy_As Toy_Ast* ast = NULL; //assert's emit function is a bit different makeExpr(bucketHandle, parser, &ast); - //if assert is disabled, don't emit the assert - if (parser->removeAssert) { - Toy_private_emitAstPass(bucketHandle, rootHandle); + //NOTE: if it's an aggregate node, then it's got a second arg + if (ast->type == TOY_AST_AGGREGATE) { + Toy_private_emitAstAssert(bucketHandle, rootHandle, ast->aggregate.left, ast->aggregate.right); } else { - //NOTE: if it's an aggregate node, then it's got a second arg - if (ast->type == TOY_AST_AGGREGATE) { - Toy_private_emitAstAssert(bucketHandle, rootHandle, ast->aggregate.left, ast->aggregate.right); - } - else { - Toy_private_emitAstAssert(bucketHandle, rootHandle, ast, NULL); - } + Toy_private_emitAstAssert(bucketHandle, rootHandle, ast, NULL); } consume(parser, TOY_TOKEN_OPERATOR_SEMICOLON, "Expected ';' at the end of assert statement"); @@ -1159,10 +1153,4 @@ void Toy_resetParser(Toy_Parser* parser) { parser->error = false; parser->panic = false; - - parser->removeAssert = false; -} - -void Toy_configureParser(Toy_Parser* parser, bool removeAssert) { - parser->removeAssert = removeAssert; } diff --git a/source/toy_parser.h b/source/toy_parser.h index 52e32fc..6a356a0 100644 --- a/source/toy_parser.h +++ b/source/toy_parser.h @@ -14,14 +14,8 @@ typedef struct Toy_Parser { bool error; bool panic; //currently processing an error - - //configs - bool removeAssert; } Toy_Parser; TOY_API void Toy_bindParser(Toy_Parser* parser, Toy_Lexer* lexer); TOY_API Toy_Ast* Toy_scanParser(Toy_Bucket** bucketHandle, Toy_Parser* parser); TOY_API void Toy_resetParser(Toy_Parser* parser); - -//configure certain options -TOY_API void Toy_configureParser(Toy_Parser* parser, bool removeAssert); diff --git a/source/toy_vm.c b/source/toy_vm.c index 8a71a7c..fa54cad 100644 --- a/source/toy_vm.c +++ b/source/toy_vm.c @@ -838,11 +838,11 @@ static void processIndex(Toy_VM* vm) { //extract cstring, based on type if (str->info.type == TOY_STRING_LEAF) { const char* cstr = str->leaf.data; - result = Toy_toStringLength(&vm->memoryBucket, cstr + i, l); + result = Toy_createStringLength(&vm->memoryBucket, cstr + i, l); } else if (str->info.type == TOY_STRING_NODE) { char* cstr = Toy_getStringRaw(str); - result = Toy_toStringLength(&vm->memoryBucket, cstr + i, l); + result = Toy_createStringLength(&vm->memoryBucket, cstr + i, l); free(cstr); } else { diff --git a/tests/makefile b/tests/makefile index bc34806..7563927 100644 --- a/tests/makefile +++ b/tests/makefile @@ -1,9 +1,11 @@ #bridge file -export CFLAGS+=-DTOY_CC_ENABLED - all: $(MAKE) -C units -k + $(MAKE) -C scripts -k gdb: $(MAKE) -C units -k gdb + $(MAKE) -C scripts -k gdb + +#TODO: valgrind \ No newline at end of file diff --git a/tests/scripts/gdb_init b/tests/scripts/gdb_init new file mode 100644 index 0000000..6c33498 --- /dev/null +++ b/tests/scripts/gdb_init @@ -0,0 +1,2 @@ +set breakpoint pending on + diff --git a/tests/scripts/makefile b/tests/scripts/makefile new file mode 100644 index 0000000..f344aa8 --- /dev/null +++ b/tests/scripts/makefile @@ -0,0 +1,52 @@ +#compiler settings +CC=gcc +CFLAGS+=-std=c17 -g -Wall -Werror -Wextra -Wpedantic -Wformat=2 -Wno-newline-eof +LIBS+=-lm +LDFLAGS+= + +ifeq ($(shell uname),Linux) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(shell uname),NetBSD) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(OS),Windows_NT) +LDFLAGS=-Wl,--gc-sections +else ifeq ($(shell uname),Darwin) +LDFLAGS=-Wl,-dead_strip +else + @echo "LDFLAGS set failed - what platform is this?" +endif + +#directories +TEST_ROOTDIR=../.. +TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) +TEST_REPLDIR=$(TEST_ROOTDIR)/$(TOY_REPLDIR) +TEST_SCRIPTDIR=. + +#file names +TEST_SCRIPTFILES=$(wildcard $(TEST_SCRIPTDIR)/test_*.toy) + +#build the source and repl, copy to the local dir, and run +all: source repl copy run + +#compile the source and repl first +source: + $(MAKE) -C $(TEST_SOURCEDIR) + +repl: source + $(MAKE) -C $(TEST_REPLDIR) + +copy: + cp -r $(TEST_ROOTDIR)/$(TOY_OUTDIR) . + +run: $(TEST_SCRIPTFILES:.toy=.toy-run) + +%.toy-run: %.toy + find -name repl* -type f -exec {} -f ../$< --verbose \; + +#using gdb +gdb: source repl copy run-gdb + +run-gdb: $(TEST_SCRIPTFILES:.toy=.toy-run-gdb) + +%.toy-run-gdb: %.toy + gdb $(TEST_OUTDIR)/$(TEST_REPLNAME) -ix gdb_init -ex=run --batch --return-child-result --args "out/repl.out" "-f" "../$<" "--verbose" \ No newline at end of file diff --git a/tests/scripts/test_arrays.toy b/tests/scripts/test_arrays.toy new file mode 100644 index 0000000..1e92e4c --- /dev/null +++ b/tests/scripts/test_arrays.toy @@ -0,0 +1,26 @@ +//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; \ No newline at end of file diff --git a/tests/scripts/test_control_flow.toy b/tests/scripts/test_control_flow.toy new file mode 100644 index 0000000..2b98bd7 --- /dev/null +++ b/tests/scripts/test_control_flow.toy @@ -0,0 +1,9 @@ +//these are allowed +/* EMPTY */; +if (true) { } +if (true) pass; + + +//these are not allowed +// if (true) /* EMPTY */; + diff --git a/tests/scripts/test_keyword_assert.toy b/tests/scripts/test_keyword_assert.toy new file mode 100644 index 0000000..154d2bd --- /dev/null +++ b/tests/scripts/test_keyword_assert.toy @@ -0,0 +1,13 @@ +//NOTE: these tests are all passing - failing tests can be found under the 'mustfails' directory + +//basic assert statement +assert true; + +//assert on a string (tests for it's truthiness) +assert "Hello world"; + +//assert on a condition +assert 1 < 2; + +//assert with an optional message +assert true, "Assertion message"; diff --git a/tests/scripts/test_keyword_if_then_else.toy b/tests/scripts/test_keyword_if_then_else.toy new file mode 100644 index 0000000..9fe8d5e --- /dev/null +++ b/tests/scripts/test_keyword_if_then_else.toy @@ -0,0 +1,65 @@ +//literals +if (true) { + print "Success 1"; +} +else { + print "Failure 1"; +} + +//false literals +if (false) { + print "Failure 2"; +} +else { + print "Success 2"; +} + +//conditionals +if (1 < 2) { + print "Success 3"; +} +if (1 > 2) { + print "Failure 3"; +} + + +//variables +var a = 42; + +if (a) { + print "Success 4"; +} +else { + print "Failure 4"; +} + + +if (a == 42) { + print "Success 5"; +} +else { + print "Failure 5"; +} + +//concatenated strings +if ("foo" .. "bar" == "foobar") { + print "Success 6"; +} +else { + print "Failure 6"; +} + + +if ("foobar" == "foo" .. "bar") { + print "Success 7"; +} +else { + print "Failure 7"; +} + +if ("fizz" .. "le" == "fi" .. "zzle") { + print "Success 8"; +} +else { + print "Failure 8"; +} diff --git a/tests/scripts/test_keyword_if_then_else_no_braces.toy b/tests/scripts/test_keyword_if_then_else_no_braces.toy new file mode 100644 index 0000000..9aeeb42 --- /dev/null +++ b/tests/scripts/test_keyword_if_then_else_no_braces.toy @@ -0,0 +1,46 @@ +//literals +if (true) + print "Success 1"; +else + print "Failure 1"; + +//false literals +if (false) + print "Failure 2"; +else + print "Success 2"; + +//conditionals +if (1 < 2) + print "Success 3"; +if (1 > 2) + print "Failure 3"; + +//variables +var a = 42; + +if (a) + print "Success 4"; +else + print "Failure 4"; + +if (a == 42) + print "Success 5"; +else + print "Failure 5"; + +//concatenated strings +if ("foo" .. "bar" == "foobar") + print "Success 6"; +else + print "Failure 6"; + +if ("foobar" == "foo" .. "bar") + print "Success 7"; +else + print "Failure 7"; + +if ("fizz" .. "le" == "fi" .. "zzle") + print "Success 8"; +else + print "Failure 8"; diff --git a/tests/scripts/test_keyword_print.toy b/tests/scripts/test_keyword_print.toy new file mode 100644 index 0000000..fe63044 --- /dev/null +++ b/tests/scripts/test_keyword_print.toy @@ -0,0 +1,21 @@ +//basic print statement +print 42; + +//print compount expressions +print 3 * 5; + +//print a string +print "Hello world!"; + +//print a concatenated string +print "Hello" .. "world!"; + +//print with escaped characters +print "\tHello\nworld!"; + +//print from a substring string +print "Hello world"[0,5]; + +//print from a substring, after a concat +print ("hello" .. "world")[2,6]; + diff --git a/tests/scripts/test_keyword_while_break_continue.toy b/tests/scripts/test_keyword_while_break_continue.toy new file mode 100644 index 0000000..b07a39a --- /dev/null +++ b/tests/scripts/test_keyword_while_break_continue.toy @@ -0,0 +1,132 @@ +//make sure it works with multiple repititions + +//------------------------- + +//test break +while (true) { + break; + assert false, "break failed"; +} + +//test continue +var flag1: bool = true; +while (flag1) { + flag1 = false; + continue; + assert false, "continue failed"; +} + +//------------------------- + +//test break +while (true) { + break; + assert false, "break failed"; +} + +//test continue +var flag2: bool = true; +while (flag2) { + flag2 = false; + continue; + assert false, "continue failed"; +} + +//------------------------- + +//test break +while (true) { + break; + assert false, "break failed"; +} + +//test continue +var flag3: bool = true; +while (flag3) { + flag3 = false; + continue; + assert false, "continue failed"; +} + +//------------------------- + +{ + //test break + while (true) { + break; + assert false, "break failed"; + } + + //test continue + var flag4: bool = true; + while (flag4) { + flag4 = false; + continue; + assert false, "continue failed"; + } +} + +//------------------------- + +{ + //test break + while (true) { + { + break; + } + assert false, "break failed"; + } + + //test continue + var flag5: bool = true; + while (flag5) { + flag5 = false; + { + continue; + } + assert false, "continue failed"; + } +} + +//------------------------- + +{ + //iteration + var iteration = 0; + while(iteration < 10) { + print iteration; + iteration += 1; + } +} + +{ + //if and while work together + var count = 1; + while (count <= 10) { + if (count % 2 == 0) { + print "even"; + } + else { + print "odd"; + } + count += 1; + } +} + +//------------------------- + +{ + //make sure break and continue point to the correct locations + var loops = 0; + + while (true) { + if (++loops < 15532) { + continue; + } + + break; + } + + assert loops == 15532, "Yuki loop failed (break + continue)"; +} + diff --git a/tests/scripts/test_scopes.toy b/tests/scripts/test_scopes.toy new file mode 100644 index 0000000..985267b --- /dev/null +++ b/tests/scripts/test_scopes.toy @@ -0,0 +1,17 @@ +//shadowing +var answer = 42; +print answer; //42 +{ + var answer = 7; + print answer; //7 +} +print answer; //42 + +//rebinding +var question = 42; +print question; //42 +{ + var question = question; + print question; //42 +} +print question; //42 diff --git a/tests/scripts/test_tables.toy b/tests/scripts/test_tables.toy new file mode 100644 index 0000000..08ad0e8 --- /dev/null +++ b/tests/scripts/test_tables.toy @@ -0,0 +1,33 @@ +//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"; \ No newline at end of file diff --git a/tests/scripts/test_truthiness.toy b/tests/scripts/test_truthiness.toy new file mode 100644 index 0000000..efc4576 --- /dev/null +++ b/tests/scripts/test_truthiness.toy @@ -0,0 +1,80 @@ +//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"; + } +} \ No newline at end of file diff --git a/tests/scripts/test_variables.toy b/tests/scripts/test_variables.toy new file mode 100644 index 0000000..5d0d01a --- /dev/null +++ b/tests/scripts/test_variables.toy @@ -0,0 +1,160 @@ +//declare a variable with an initial value +var answer = 42; + +//declare a variable without an initial value +var empty; + +//assign a previously existing variable +answer = 6 * 9; + +//access a variable +answer = answer + 1; + +//compound assignments +answer += 5; +answer -= 5; +answer *= 9; +answer /= 2; +answer %= 10; + +//equality checks +print 1 == 1; //true +print 1 != 1; //false + +//comparison checks +print 1 < 2; //true + +print "foo" > "bar"; //true + +print 1 < 2; //true +print 1 > 2; //false + +print 2 <= 2; //true +print 2 >= 2; //true + +print 1 <= 2; //true +print 1 >= 2; //false + +//logical checks +print true && true; //true +print true && false; //false +print false && true; //false +print false && false; //false + +print true || true; //true +print true || false; //true +print false || true; //true +print false || false; //false + +print !true; //false +print !false; //true + +//logical AND short-circuits and chained assignments +{ + var a = 1; + var b = 2; + var c = a + 1 && b + 2; + + assert a == 1, "short circuit 1.1"; + assert b == 2, "short circuit 1.2"; + assert c == 4, "short circuit 1.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = (a + 1) && b + 2; + + assert a == 4, "short circuit 2.1"; + assert b == 2, "short circuit 2.2"; + assert c == 4, "short circuit 2.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = a + 1 && b + 2; + + assert a == 4, "short circuit 3.1"; + assert b == 2, "short circuit 3.2"; + assert c == 4, "short circuit 3.3"; +} + +//logical OR short-circuits and chained assignments +{ + var a = 1; + var b = 2; + var c = a + 1 || b + 2; + + assert a == 1, "short circuit 4.1"; + assert b == 2, "short circuit 4.2"; + assert c == 2, "short circuit 4.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = (a + 1) || b + 2; + + assert a == 2, "short circuit 5.1"; + assert b == 2, "short circuit 5.2"; + assert c == 2, "short circuit 5.3"; +} + +{ + var a = 1; + var b = 2; + var c = a = a + 1 || b + 2; + + assert a == 2, "short circuit 6.1"; + assert b == 2, "short circuit 6.2"; + assert c == 2, "short circuit 6.3"; +} + +//types +{ + var a: int; + var b: int = 42; + + a = 69; + b = 8891; + + print a; + print b; +} + +//constants +{ + var c: int const = 42; + print c; +} + +//indexing +{ + var s = "Hello" .. "world!"; + print s[3, 3]; +} + +//increment & decrement (prefix) +{ + var a = 42; + assert a == 42, "prefix increment & decrement 1.1"; + assert ++a == 43, "prefix increment & decrement 1.2"; + assert a == 43, "prefix increment & decrement 1.3"; + assert --a == 42, "prefix increment & decrement 1.4"; + assert a == 42, "prefix increment & decrement 1.5"; +} + +//increment & decrement (postfix) +{ + var a = 42; + assert a == 42, "postfix increment & decrement 1.1"; + assert a++ == 42, "postfix increment & decrement 1.2"; + assert a == 43, "postfix increment & decrement 1.3"; + assert a-- == 43, "postfix increment & decrement 1.4"; + assert a == 42, "postfix increment & decrement 1.5"; + + print a; +} + +//TODO: type casting diff --git a/tests/units/makefile b/tests/units/makefile index 4715f35..f1de733 100644 --- a/tests/units/makefile +++ b/tests/units/makefile @@ -19,30 +19,30 @@ endif #directories TEST_ROOTDIR=../.. TEST_SOURCEDIR=$(TEST_ROOTDIR)/$(TOY_SOURCEDIR) -TEST_CASESDIR=. +TEST_UNITSDIR=. TEST_OUTDIR=out TEST_OBJDIR=obj #file names TEST_SOURCEFILES=$(wildcard $(TEST_SOURCEDIR)/*.c) -TEST_CASESFILES=$(wildcard $(TEST_CASESDIR)/test_*.c) +TEST_UNITSFILES=$(wildcard $(TEST_UNITSDIR)/test_*.c) -#build the object files, compile the test cases, and run -all: build-source build-cases build-link build-run +#build the object files, compile the tess, and run +all: build-source build-units build-link build-run #targets for each step .PHONY: build-source build-source: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_SOURCEFILES:.c=.o))) -.PHONY: build-cases -build-cases: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_CASESFILES:.c=.o))) +.PHONY: build-units +build-units: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_UNITSFILES:.c=.o))) .PHONY: build-link -build-link: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) +build-link: $(TEST_OUTDIR) $(TEST_OBJDIR) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.exe))) .PHONY: build-run -build-run: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.run))) +build-run: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.run))) #util targets $(TEST_OUTDIR): @@ -55,8 +55,8 @@ $(TEST_OBJDIR): $(TEST_OBJDIR)/%.o: $(TEST_SOURCEDIR)/%.c $(CC) -c -o $@ $< $(addprefix -I,$(TEST_SOURCEDIR)) $(CFLAGS) -fdata-sections -ffunction-sections -$(TEST_OBJDIR)/%.o: $(TEST_CASESDIR)/%.c - $(CC) -c -o $@ $< $(addprefix -I,$(TEST_SOURCEDIR) $(TEST_CASESDIR)) $(CFLAGS) -fdata-sections -ffunction-sections +$(TEST_OBJDIR)/%.o: $(TEST_UNITSDIR)/%.c + $(CC) -c -o $@ $< $(addprefix -I,$(TEST_SOURCEDIR) $(TEST_UNITSDIR)) $(CFLAGS) -fdata-sections -ffunction-sections $(TEST_OUTDIR)/%.exe: $(TEST_OBJDIR)/%.o @$(CC) -o $@ $< $(addprefix $(TEST_OBJDIR)/,$(notdir $(TEST_SOURCEFILES:.c=.o))) $(CFLAGS) $(LIBS) $(LDFLAGS) @@ -66,20 +66,20 @@ $(TEST_OUTDIR)/%.run: $(TEST_OUTDIR)/%.exe $< #debugging targets -gdb: build-source build-cases build-link build-run-gdb +gdb: build-source build-units build-link build-run-gdb .PHONY: build-run-gdb -build-run-gdb: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.run-gdb))) +build-run-gdb: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.run-gdb))) .PRECIOUS: $(TEST_OUTDIR)/%.run-gdb $(TEST_OUTDIR)/%.run-gdb: $(TEST_OUTDIR)/%.exe gdb $< -ix gdb_init -ex=run --batch --return-child-result --args "$<" #valgrind targets -valgrind: build-source build-cases build-link build-run-valgrind +valgrind: build-source build-units build-link build-run-valgrind .PHONY: build-run-valgrind -build-run-valgrind: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_CASESFILES:%.c=%.run-valgrind))) +build-run-valgrind: $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.exe))) $(addprefix $(TEST_OUTDIR)/,$(notdir $(TEST_UNITSFILES:%.c=%.run-valgrind))) .PRECIOUS: $(TEST_OUTDIR)/%.run-valgrind $(TEST_OUTDIR)/%.run-valgrind: $(TEST_OUTDIR)/%.exe