diff --git a/Repl.vcxproj b/Repl.vcxproj index 2bd6283..d975652 100644 --- a/Repl.vcxproj +++ b/Repl.vcxproj @@ -136,6 +136,7 @@ + @@ -145,6 +146,7 @@ + diff --git a/scripts/shadow.toy b/scripts/shadow.toy deleted file mode 100644 index 2b694a6..0000000 --- a/scripts/shadow.toy +++ /dev/null @@ -1,8 +0,0 @@ -//something was odd, so I broke this down for testing -import math; - -fn shadowCastPoint(x: float, y: float, depth: int) { - return sin(tan(x/y)) * depth; -} - -print shadowCastPoint(1, 1, 10); diff --git a/scripts/test.toy b/scripts/test.toy new file mode 100644 index 0000000..4401bb4 --- /dev/null +++ b/scripts/test.toy @@ -0,0 +1,13 @@ +fn doA() { + print "doA()"; + return true; +} + +fn doB() { + print "doB()"; + return true; +} + +if (doA() || doB()) { + print "success"; +} \ No newline at end of file diff --git a/source/toy_ast_node.c b/source/toy_ast_node.c index 24669b1..1397846 100644 --- a/source/toy_ast_node.c +++ b/source/toy_ast_node.c @@ -124,6 +124,16 @@ static void freeASTNodeCustom(Toy_ASTNode* node, bool freeSelf) { //NO-OP break; + case TOY_AST_NODE_AND: + Toy_freeASTNode(node->pathAnd.left); + Toy_freeASTNode(node->pathAnd.right); + break; + + case TOY_AST_NODE_OR: + Toy_freeASTNode(node->pathOr.left); + Toy_freeASTNode(node->pathOr.right); + break; + case TOY_AST_NODE_PREFIX_INCREMENT: Toy_freeLiteral(node->prefixIncrement.identifier); break; @@ -348,6 +358,26 @@ void Toy_emitASTNodeContinue(Toy_ASTNode** nodeHandle) { *nodeHandle = tmp; } +void Toy_emitASTNodeAnd(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs) { + Toy_ASTNode* tmp = TOY_ALLOCATE(Toy_ASTNode, 1); + + tmp->type = TOY_AST_NODE_AND; + tmp->binary.left = *nodeHandle; + tmp->binary.right = rhs; + + *nodeHandle = tmp; +} + +void Toy_emitASTNodeOr(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs) { + Toy_ASTNode* tmp = TOY_ALLOCATE(Toy_ASTNode, 1); + + tmp->type = TOY_AST_NODE_OR; + tmp->binary.left = *nodeHandle; + tmp->binary.right = rhs; + + *nodeHandle = tmp; +} + void Toy_emitASTNodePrefixIncrement(Toy_ASTNode** nodeHandle, Toy_Literal identifier) { Toy_ASTNode* tmp = TOY_ALLOCATE(Toy_ASTNode, 1); diff --git a/source/toy_ast_node.h b/source/toy_ast_node.h index a9346b8..4f48c83 100644 --- a/source/toy_ast_node.h +++ b/source/toy_ast_node.h @@ -29,6 +29,8 @@ typedef enum Toy_ASTNodeType { TOY_AST_NODE_FOR, //for control flow TOY_AST_NODE_BREAK, //for control flow TOY_AST_NODE_CONTINUE, //for control flow + TOY_AST_NODE_AND, //for control flow + TOY_AST_NODE_OR, //for control flow TOY_AST_NODE_PREFIX_INCREMENT, //increment a variable TOY_AST_NODE_POSTFIX_INCREMENT, //increment a variable TOY_AST_NODE_PREFIX_DECREMENT, //decrement a variable @@ -204,6 +206,24 @@ typedef struct Toy_NodeContinue { Toy_ASTNodeType type; } Toy_NodeContinue; +//and operator +void Toy_emitASTNodeAnd(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs); //handled node becomes lhs + +typedef struct Toy_NodeAnd { + Toy_ASTNodeType type; + Toy_ASTNode* left; + Toy_ASTNode* right; +} Toy_NodeAnd; + +//or operator +void Toy_emitASTNodeOr(Toy_ASTNode** nodeHandle, Toy_ASTNode* rhs); //handled node becomes lhs + +typedef struct Toy_NodeOr { + Toy_ASTNodeType type; + Toy_ASTNode* left; + Toy_ASTNode* right; +} Toy_NodeOr; + //pre-post increment/decrement void Toy_emitASTNodePrefixIncrement(Toy_ASTNode** nodeHandle, Toy_Literal identifier); void Toy_emitASTNodePrefixDecrement(Toy_ASTNode** nodeHandle, Toy_Literal identifier); @@ -263,6 +283,8 @@ union Toy_private_node { Toy_NodeFor pathFor; Toy_NodeBreak pathBreak; Toy_NodeContinue pathContinue; + Toy_NodeAnd pathAnd; + Toy_NodeOr pathOr; Toy_NodePrefixIncrement prefixIncrement; Toy_NodePrefixDecrement prefixDecrement; Toy_NodePostfixIncrement postfixIncrement; diff --git a/source/toy_compiler.c b/source/toy_compiler.c index baa3e2e..030ddc8 100644 --- a/source/toy_compiler.c +++ b/source/toy_compiler.c @@ -318,6 +318,12 @@ bool checkNodeInTree(Toy_ASTNode* tree, Toy_ASTNode* node) { case TOY_AST_NODE_FOR: return checkNodeInTree(tree->pathFor.preClause, node) || checkNodeInTree(tree->pathFor.condition, node) || checkNodeInTree(tree->pathFor.postClause, node) || checkNodeInTree(tree->pathFor.thenPath, node); + case TOY_AST_NODE_AND: + return checkNodeInTree(tree->pathAnd.left, node) || checkNodeInTree(tree->pathAnd.right, node); + + case TOY_AST_NODE_OR: + return checkNodeInTree(tree->pathOr.left, node) || checkNodeInTree(tree->pathOr.right, node); + case TOY_AST_NODE_ERROR: case TOY_AST_NODE_LITERAL: case TOY_AST_NODE_BREAK: @@ -429,8 +435,6 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode case TOY_OP_COMPARE_GREATER: case TOY_OP_COMPARE_GREATER_EQUAL: case TOY_OP_INVERT: - case TOY_OP_AND: - case TOY_OP_OR: //place the rhs result before the outer instruction compiler->bytecode[compiler->count++] = (unsigned char)ret; //1 byte ret = TOY_OP_EOF; @@ -917,6 +921,54 @@ static Toy_Opcode Toy_writeCompilerWithJumps(Toy_Compiler* compiler, Toy_ASTNode } break; + case TOY_AST_NODE_AND: { + //process the lhs + Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, node->pathAnd.left, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + //insert the AND opcode to signal a possible jump + compiler->bytecode[compiler->count++] = TOY_OP_AND; //1 byte + int jumpToEnd = compiler->count; + compiler->count += sizeof(unsigned short); //2 bytes + + //process the rhs + override = Toy_writeCompilerWithJumps(compiler, node->pathAnd.right, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + //set the spot to jump to, to proceed + unsigned short tmpVal = compiler->count + jumpOffsets; + memcpy(compiler->bytecode + jumpToEnd, &tmpVal, sizeof(tmpVal)); + } + break; + + case TOY_AST_NODE_OR: { + //process the lhs + Toy_Opcode override = Toy_writeCompilerWithJumps(compiler, node->pathOr.left, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + //insert the AND opcode to signal a possible jump + compiler->bytecode[compiler->count++] = TOY_OP_OR; //1 byte + int jumpToEnd = compiler->count; + compiler->count += sizeof(unsigned short); //2 bytes + + //process the rhs + override = Toy_writeCompilerWithJumps(compiler, node->pathOr.right, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override != TOY_OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + //set the spot to jump to, to proceed + unsigned short tmpVal = compiler->count + jumpOffsets; + memcpy(compiler->bytecode + jumpToEnd, &tmpVal, sizeof(tmpVal)); + } + break; + case TOY_AST_NODE_FN_RETURN: { //read each returned literal onto the stack, and return the number of values to return for (int i = 0; i < node->returns.returns->fnCollection.count; i++) { diff --git a/source/toy_interpreter.c b/source/toy_interpreter.c index 1375a95..db476f7 100644 --- a/source/toy_interpreter.c +++ b/source/toy_interpreter.c @@ -1048,57 +1048,63 @@ static bool execCompareLessEqual(Toy_Interpreter* interpreter, bool invert) { } static bool execAnd(Toy_Interpreter* interpreter) { - Toy_Literal rhs = Toy_popLiteralArray(&interpreter->stack); Toy_Literal lhs = Toy_popLiteralArray(&interpreter->stack); - Toy_Literal rhsIdn = rhs; - if (TOY_IS_IDENTIFIER(rhs) && Toy_parseIdentifierToValue(interpreter, &rhs)) { - Toy_freeLiteral(rhsIdn); - } - Toy_Literal lhsIdn = lhs; if (TOY_IS_IDENTIFIER(lhs) && Toy_parseIdentifierToValue(interpreter, &lhs)) { Toy_freeLiteral(lhsIdn); } - //short-circuit - broken, see issue #73 + //short-circuit - if not true if (!TOY_IS_TRUTHY(lhs)) { Toy_pushLiteralArray(&interpreter->stack, lhs); + + int target = (int)readShort(interpreter->bytecode, &interpreter->count); + + if (target + interpreter->codeStart > interpreter->length) { + interpreter->errorOutput("[internal] AND Jump out of range\n"); + return false; + } + + //actually jump + interpreter->count = target + interpreter->codeStart; } else { - Toy_pushLiteralArray(&interpreter->stack, rhs); + readShort(interpreter->bytecode, &interpreter->count); //discard } Toy_freeLiteral(lhs); - Toy_freeLiteral(rhs); return true; } static bool execOr(Toy_Interpreter* interpreter) { - Toy_Literal rhs = Toy_popLiteralArray(&interpreter->stack); Toy_Literal lhs = Toy_popLiteralArray(&interpreter->stack); - Toy_Literal rhsIdn = rhs; - if (TOY_IS_IDENTIFIER(rhs) && Toy_parseIdentifierToValue(interpreter, &rhs)) { - Toy_freeLiteral(rhsIdn); - } - Toy_Literal lhsIdn = lhs; if (TOY_IS_IDENTIFIER(lhs) && Toy_parseIdentifierToValue(interpreter, &lhs)) { Toy_freeLiteral(lhsIdn); } - //short-circuit - broken, see issue #73 + //short-circuit - if is true if (TOY_IS_TRUTHY(lhs)) { Toy_pushLiteralArray(&interpreter->stack, lhs); + + int target = (int)readShort(interpreter->bytecode, &interpreter->count); + + if (target + interpreter->codeStart > interpreter->length) { + interpreter->errorOutput("[internal] OR Jump out of range\n"); + return false; + } + + //actually jump + interpreter->count = target + interpreter->codeStart; } else { - Toy_pushLiteralArray(&interpreter->stack, rhs); + readShort(interpreter->bytecode, &interpreter->count); //discard } Toy_freeLiteral(lhs); - Toy_freeLiteral(rhs); return true; } diff --git a/source/toy_lexer.c b/source/toy_lexer.c index 80dd566..25eb78b 100644 --- a/source/toy_lexer.c +++ b/source/toy_lexer.c @@ -237,7 +237,7 @@ static Toy_Token makeKeywordOrIdentifier(Toy_Lexer* lexer) { //scan for a keyword for (int i = 0; Toy_keywordTypes[i].keyword; i++) { - if (strlen(Toy_keywordTypes[i].keyword) == (long unsigned int)(lexer->current - lexer->start) && !strncmp(Toy_keywordTypes[i].keyword, &lexer->source[lexer->start], lexer->current - lexer->start)) { + if (strlen(Toy_keywordTypes[i].keyword) == (size_t)(lexer->current - lexer->start) && !strncmp(Toy_keywordTypes[i].keyword, &lexer->source[lexer->start], lexer->current - lexer->start)) { Toy_Token token; token.type = Toy_keywordTypes[i].type; @@ -317,10 +317,10 @@ Toy_Token Toy_private_scanLexer(Toy_Lexer* lexer) { if (advance(lexer) != '&') { return makeErrorToken(lexer, "Unexpected '&'"); } else { - return makeToken(lexer, TOY_TOKEN_AND); + return makeToken(lexer, TOY_TOKEN_AND_AND); } - case '|': return makeToken(lexer, match(lexer, '|') ? TOY_TOKEN_OR : TOY_TOKEN_PIPE); + case '|': return makeToken(lexer, match(lexer, '|') ? TOY_TOKEN_OR_OR : TOY_TOKEN_PIPE); case '?': return makeToken(lexer, TOY_TOKEN_QUESTION); case ':': return makeToken(lexer, TOY_TOKEN_COLON); diff --git a/source/toy_parser.c b/source/toy_parser.c index db9b15b..1b4f5af 100644 --- a/source/toy_parser.c +++ b/source/toy_parser.c @@ -339,6 +339,28 @@ static Toy_Opcode grouping(Toy_Parser* parser, Toy_ASTNode** nodeHandle) { } } +static Toy_Opcode circuit(Toy_Parser* parser, Toy_ASTNode** nodeHandle) { + advance(parser); + + //handle short-circuitable operators - && || + switch (parser->previous.type) { + case TOY_TOKEN_AND_AND: { + parsePrecedence(parser, nodeHandle, PREC_AND + 1); + return TOY_OP_AND; + } + + case TOY_TOKEN_OR_OR: { + parsePrecedence(parser, nodeHandle, PREC_OR + 1); + return TOY_OP_OR; + } + + default: { + error(parser, parser->previous, "Unexpected token passed to grouping precedence rule"); + return TOY_OP_EOF; + } + } +} + static Toy_Opcode binary(Toy_Parser* parser, Toy_ASTNode** nodeHandle) { advance(parser); @@ -432,16 +454,6 @@ static Toy_Opcode binary(Toy_Parser* parser, Toy_ASTNode** nodeHandle) { return TOY_OP_COMPARE_GREATER_EQUAL; } - case TOY_TOKEN_AND: { - parsePrecedence(parser, nodeHandle, PREC_AND + 1); - return TOY_OP_AND; - } - - case TOY_TOKEN_OR: { - parsePrecedence(parser, nodeHandle, PREC_OR + 1); - return TOY_OP_OR; - } - default: error(parser, parser->previous, "Unexpected token passed to binary precedence rule"); return TOY_OP_EOF; @@ -1002,8 +1014,8 @@ ParseRule parseRules[] = { //must match the token types {NULL, binary, PREC_COMPARISON},// TOKEN_GREATER, {NULL, binary, PREC_COMPARISON},// TOKEN_LESS_EQUAL, {NULL, binary, PREC_COMPARISON},// TOKEN_GREATER_EQUAL, - {NULL, binary, PREC_AND},// TOKEN_AND, - {NULL, binary, PREC_OR},// TOKEN_OR, + {NULL, circuit, PREC_AND},// TOKEN_AND, + {NULL, circuit, PREC_OR},// TOKEN_OR, //other operators {NULL, question, PREC_TERNARY}, //TOKEN_QUESTION, @@ -1285,6 +1297,16 @@ static void parsePrecedence(Toy_Parser* parser, Toy_ASTNode** nodeHandle, Preced continue; } + if (opcode == TOY_OP_AND) { + Toy_emitASTNodeAnd(nodeHandle, rhsNode); + continue; + } + + if (opcode == TOY_OP_OR) { + Toy_emitASTNodeOr(nodeHandle, rhsNode); + continue; + } + Toy_emitASTNodeBinary(nodeHandle, rhsNode, opcode); //optimise away the constants diff --git a/source/toy_token_types.h b/source/toy_token_types.h index 2de0b3f..2f52b88 100644 --- a/source/toy_token_types.h +++ b/source/toy_token_types.h @@ -74,8 +74,8 @@ typedef enum Toy_TokenType { TOY_TOKEN_GREATER, TOY_TOKEN_LESS_EQUAL, TOY_TOKEN_GREATER_EQUAL, - TOY_TOKEN_AND, - TOY_TOKEN_OR, + TOY_TOKEN_AND_AND, + TOY_TOKEN_OR_OR, //other operators TOY_TOKEN_QUESTION, diff --git a/test/scripts/short-circuit.toy b/test/scripts/short-circuit.toy new file mode 100644 index 0000000..3c65eb2 --- /dev/null +++ b/test/scripts/short-circuit.toy @@ -0,0 +1,14 @@ +//These operators should short-circuit +assert true && false == false, "true && false == false failed"; +assert false && true == false, "false && true == false failed"; + +assert true || false == true, "true || false == true failed"; +assert false || true == true, "false || true == true failed"; + + +//make sure the right value is being returned when chained +assert "a" && "b" && "c" == "c", "chained && failed"; +assert "a" || "b" || "c" == "a", "chained || failed"; + + +print "All good"; diff --git a/test/test_interpreter.c b/test/test_interpreter.c index 6f1d8fa..59d7510 100644 --- a/test/test_interpreter.c +++ b/test/test_interpreter.c @@ -138,6 +138,7 @@ int main() { "panic-within-functions.toy", "polyfill-insert.toy", "polyfill-remove.toy", + "short-circuit.toy", "short-circuiting-support.toy", "ternary-expressions.toy", "trailing-comma-bugfix.toy",