diff --git a/docs/TODO.txt b/docs/TODO.txt index a65fe9a..409ceb3 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -21,12 +21,13 @@ DONE: function returns can have specified types DONE: closures are explicitly supported DONE: functions are first-class citizens DONE: functions take a set number of parameters +DONE: functions last argument can be a rest parameter TODO: functions return a set number of values -TODO: functions last argument can be a rest parameter -TODO: assert needs to kill the whole script +TODO: assert needs to kill the whole script, not just functions +TODO: ternary operator TODO: Nullish types TODO: A way to check the type of a variable (typeOf keyword) TODO: a = b = c = 1; ? @@ -34,3 +35,5 @@ TODO: are compounds shallow or deep copies? TODO: Assertion-based test scripts TODO: standard library TODO: external runner library +TODO: document how it all works +TODO: third output stream, for parser/compiler/interpreter errors diff --git a/scripts/error-test.toy b/scripts/error-test.toy new file mode 100644 index 0000000..e69de29 diff --git a/source/interpreter.c b/source/interpreter.c index c39c16a..0166725 100644 --- a/source/interpreter.c +++ b/source/interpreter.c @@ -782,9 +782,15 @@ static bool execFnCall(Interpreter* interpreter) { LiteralArray* paramArray = AS_ARRAY(inner.literalCache.literals[ readShort(inner.bytecode, &inner.count) ]); LiteralArray* returnArray = AS_ARRAY(inner.literalCache.literals[ readShort(inner.bytecode, &inner.count) ]); + //get the rest param, if it exists + Literal restParam = TO_NULL_LITERAL; + if (paramArray->count >= 2 && AS_TYPE(paramArray->literals[ paramArray->count -1 ]).typeOf == LITERAL_FUNCTION_ARG_REST) { + restParam = paramArray->literals[ paramArray->count -2 ]; + } + //check the param total is correct - if (paramArray->count != arguments.count * 2) { - printf(ERROR "ERROR: Incorrect number of arguments passed to function \"\n"); + if ((IS_NULL(restParam) && paramArray->count != arguments.count * 2) || (!IS_NULL(restParam) && paramArray->count -2 > arguments.count * 2)) { + printf(ERROR "ERROR: Incorrect number of arguments passed to function \""); printLiteral(identifier); printf("\"\n" RESET); @@ -795,16 +801,55 @@ static bool execFnCall(Interpreter* interpreter) { return false; } - for (int i = 0; i < paramArray->count; i += 2) { //contents is the indexes of identifier & type + //contents is the indexes of identifier & type + for (int i = 0; i < paramArray->count - (IS_NULL(restParam) ? 0 : 2); i += 2) { //don't count the rest parameter, if present //declare and define each entry in the scope if (!declareScopeVariable(inner.scope, paramArray->literals[i], paramArray->literals[i + 1])) { printf(ERROR "[internal] Could not re-declare parameter\n" RESET); + //free, and skip out + freeLiteralArray(&arguments); freeInterpreter(&inner); return false; } if (!setScopeVariable(inner.scope, paramArray->literals[i], popLiteralArray(&arguments), false)) { printf(ERROR "[internal] Could not define parameter (bad type?)\n" RESET); + //free, and skip out + freeLiteralArray(&arguments); + freeInterpreter(&inner); + return false; + } + } + + //if using rest, pack the optional extra arguments into the rest parameter (array) + if (!IS_NULL(restParam)) { + LiteralArray rest; + initLiteralArray(&rest); + + while (arguments.count > 0) { + pushLiteralArray(&rest, popLiteralArray(&arguments)); + } + + Literal restType = TO_TYPE_LITERAL(LITERAL_ARRAY, true); + TYPE_PUSH_SUBTYPE(&restType, TO_TYPE_LITERAL(LITERAL_ANY, false)); + + //declare & define the rest parameter + if (!declareScopeVariable(inner.scope, restParam, restType)) { + printf(ERROR "[internal] Could not declare rest parameter\n" RESET); + //free, and skip out + freeLiteral(restType); + freeLiteralArray(&rest); + freeLiteralArray(&arguments); + freeInterpreter(&inner); + return false; + } + + if (!setScopeVariable(inner.scope, restParam, TO_ARRAY_LITERAL(&rest), false)) { + printf(ERROR "[internal] Could not define rest parameter\n" RESET); + //free, and skip out + freeLiteral(restType); + freeLiteralArray(&rest); + freeLiteralArray(&arguments); freeInterpreter(&inner); return false; } @@ -1083,7 +1128,7 @@ static void execInterpreter(Interpreter* interpreter) { static void readInterpreterSections(Interpreter* interpreter) { //data section - const short literalCount = readShort(interpreter->bytecode, &interpreter->count); + const unsigned short literalCount = readShort(interpreter->bytecode, &interpreter->count); if (command.verbose) { printf(NOTICE "Reading %d literals\n" RESET, literalCount); diff --git a/source/literal.c b/source/literal.c index fa9a777..425fc84 100644 --- a/source/literal.c +++ b/source/literal.c @@ -305,6 +305,10 @@ void printLiteralCustom(Literal literal, void (printFn)(const char*)) { case LITERAL_ANY: printFn("(any)"); break; + + default: + //should never be seen + fprintf(stderr, ERROR "[internal] Unrecognized literal type in print: %d\n" RESET, literal.type); } } @@ -489,7 +493,12 @@ bool literalsAreEqual(Literal lhs, Literal rhs) { return true; case LITERAL_FUNCTION_INTERMEDIATE: - fprintf(stderr, ERROR "[internal] Can't compare functions\n" RESET); + fprintf(stderr, ERROR "[internal] Can't compare intermediate functions\n" RESET); + return false; + + default: + //should never be seen + fprintf(stderr, ERROR "[internal] Unrecognized literal type in equality: %d\n" RESET, lhs.type); return false; } diff --git a/source/literal.h b/source/literal.h index 973f247..d7ff370 100644 --- a/source/literal.h +++ b/source/literal.h @@ -19,6 +19,7 @@ typedef enum { LITERAL_TYPE, LITERAL_TYPE_INTERMEDIATE, //used to process types in the compiler only LITERAL_FUNCTION_INTERMEDIATE, //used to process functions in the compiler only + LITERAL_FUNCTION_ARG_REST, //used to process function rest parameters // LITERAL_FUNCTION_NATIVE, //for handling native functions LITERAL_ANY, //used by the type system only } LiteralType; diff --git a/source/parser.c b/source/parser.c index ed9b6b4..bc3a431 100644 --- a/source/parser.c +++ b/source/parser.c @@ -1419,8 +1419,7 @@ static void fnDecl(Parser* parser, Node** nodeHandle) { Literal argIdentifier = _toIdentifierLiteral(cpy, strlen(cpy)); //BUGFIX: use this instead of the macro //set the type (array of any types) - Literal argTypeLiteral = TO_TYPE_LITERAL(LITERAL_ARRAY, false); - TYPE_PUSH_SUBTYPE(&argTypeLiteral, TO_TYPE_LITERAL(LITERAL_ANY, false)); + Literal argTypeLiteral = TO_TYPE_LITERAL(LITERAL_FUNCTION_ARG_REST, false); //emit the node to the argument list (grow the node if needed) if (argumentNode->fnCollection.capacity < argumentNode->fnCollection.count + 1) { diff --git a/test/functions.toy b/test/functions.toy index 8a1ee05..00778e5 100644 --- a/test/functions.toy +++ b/test/functions.toy @@ -30,7 +30,7 @@ var tally = make(); assert tally() == 1 && tally() == 2, "Closures failed"; -//expressions as arguments +//test expressions as arguments fn argFn() { return 42; } @@ -42,4 +42,14 @@ fn outerFn(val) { outerFn(argFn()); +//test extra parameters +fn extra(one, two, ...rest) { + assert rest == ["three", "four", "five", "six", "seven"], "rest parameters failed"; +} + +extra("one", "two", "three", "four", "five", "six", "seven"); + + + + print "All good"; \ No newline at end of file