From ae1dc5841e2f25ff3030d9244954c148caa71751 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Sat, 14 Jan 2023 10:24:15 +0000 Subject: [PATCH] Added ternary operator, resolved #46 --- scripts/small.toy | 21 ++++++++++---- source/ast_node.c | 17 ++++++++++++ source/ast_node.h | 12 ++++++++ source/compiler.c | 41 ++++++++++++++++++++++++++++ source/lexer.c | 1 + source/opcodes.h | 3 ++ source/parser.c | 27 ++++++++++++++++-- source/token_types.h | 1 + source/toy_common.h | 2 +- test/scripts/ternary-expressions.toy | 21 ++++++++++++++ test/test_interpreter.c | 3 +- 11 files changed, 140 insertions(+), 9 deletions(-) create mode 100644 test/scripts/ternary-expressions.toy diff --git a/scripts/small.toy b/scripts/small.toy index 722d8d8..9b6cede 100644 --- a/scripts/small.toy +++ b/scripts/small.toy @@ -1,10 +1,21 @@ - +//test basic truth ternaries { - var t = astype [int]; - var arr: t = [1, 2, 3.14]; + assert true ? true : false, "Basic true ternary failed"; + assert false ? false : true, "Basic false ternary failed"; } + +//test nesting { - var t = astype [string:int]; - var dict: t = ["one": 1, "two": 2, "pi": 3.14]; + fn least(a, b, c) { + return a < b ? a : b < c ? b : c; + } + + assert least(1, 2, 3) == 1, "Least 1, 2, 3 failed"; + assert least(10, 5, 7) == 5, "Least 10, 5, 7 failed"; + assert least(9, 7, 5) == 5, "Least 9, 7, 5 failed"; } + + +print "All good"; + diff --git a/source/ast_node.c b/source/ast_node.c index 8ba4125..8785663 100644 --- a/source/ast_node.c +++ b/source/ast_node.c @@ -29,6 +29,12 @@ void freeASTNodeCustom(ASTNode* node, bool freeSelf) { freeASTNode(node->binary.right); break; + case AST_NODE_TERNARY: + freeASTNode(node->ternary.condition); + freeASTNode(node->ternary.thenPath); + freeASTNode(node->ternary.elsePath); + break; + case AST_NODE_GROUPING: freeASTNode(node->grouping.child); break; @@ -169,6 +175,17 @@ void emitASTNodeBinary(ASTNode** nodeHandle, ASTNode* rhs, Opcode opcode) { *nodeHandle = tmp; } +void emitASTNodeTernary(ASTNode** nodeHandle, ASTNode* condition, ASTNode* thenPath, ASTNode* elsePath) { + ASTNode* tmp = ALLOCATE(ASTNode, 1); + + tmp->type = AST_NODE_TERNARY; + tmp->ternary.condition = condition; + tmp->ternary.thenPath = thenPath; + tmp->ternary.elsePath = elsePath; + + *nodeHandle = tmp; +} + void emitASTNodeGrouping(ASTNode** nodeHandle) { ASTNode* tmp = ALLOCATE(ASTNode, 1); diff --git a/source/ast_node.h b/source/ast_node.h index 473b6f6..84e4170 100644 --- a/source/ast_node.h +++ b/source/ast_node.h @@ -13,6 +13,7 @@ typedef enum ASTNodeType { AST_NODE_LITERAL, //a simple value AST_NODE_UNARY, //one child + opcode AST_NODE_BINARY, //two children, left and right + opcode + AST_NODE_TERNARY, //three children, condition, then path & else path AST_NODE_GROUPING, //one child AST_NODE_BLOCK, //contains a sub-node array AST_NODE_COMPOUND, //contains a sub-node array @@ -62,6 +63,16 @@ typedef struct NodeBinary { ASTNode* right; } NodeBinary; +//ternary operator +void emitASTNodeTernary(ASTNode** nodeHandle, ASTNode* condition, ASTNode* thenPath, ASTNode* elsePath); + +typedef struct NodeTernary { + ASTNodeType type; + ASTNode* condition; + ASTNode* thenPath; + ASTNode* elsePath; +} NodeTernary; + //grouping of other AST nodes void emitASTNodeGrouping(ASTNode** nodeHandle); @@ -232,6 +243,7 @@ union _node { NodeLiteral atomic; NodeUnary unary; NodeBinary binary; + NodeTernary ternary; NodeGrouping grouping; NodeBlock block; NodeCompound compound; diff --git a/source/compiler.c b/source/compiler.c index 9d59efa..4bd3fac 100644 --- a/source/compiler.c +++ b/source/compiler.c @@ -331,6 +331,47 @@ static Opcode writeCompilerWithJumps(Compiler* compiler, ASTNode* node, void* br } break; + case AST_NODE_TERNARY: { + // TODO + + //process the condition + Opcode override = writeCompilerWithJumps(compiler, node->ternary.condition, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override != OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + //cache the point to insert the jump distance at + compiler->bytecode[compiler->count++] = OP_IF_FALSE_JUMP; //1 byte + int jumpToElse = compiler->count; + compiler->count += sizeof(unsigned short); //2 bytes + + //write the then path + override = writeCompilerWithJumps(compiler, node->pathIf.thenPath, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override != OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + int jumpToEnd = 0; + + //insert jump to end + compiler->bytecode[compiler->count++] = OP_JUMP; //1 byte + jumpToEnd = compiler->count; + compiler->count += sizeof(unsigned short); //2 bytes + + //update the jumpToElse to point here + AS_USHORT(compiler->bytecode[jumpToElse]) = compiler->count + jumpOffsets; //2 bytes + + //write the else path + Opcode override2 = writeCompilerWithJumps(compiler, node->pathIf.elsePath, breakAddressesPtr, continueAddressesPtr, jumpOffsets, rootNode); + if (override2 != OP_EOF) {//compensate for indexing & dot notation being screwy + compiler->bytecode[compiler->count++] = (unsigned char)override; //1 byte + } + + //update the jumpToEnd to point here + AS_USHORT(compiler->bytecode[jumpToEnd]) = compiler->count + jumpOffsets; //2 bytes + } + break; + case AST_NODE_GROUPING: { compiler->bytecode[compiler->count++] = (unsigned char)OP_GROUPING_BEGIN; //1 byte Opcode override = writeCompilerWithJumps(compiler, node->grouping.child, breakAddressesPtr, continueAddressesPtr, jumpOffsets, node->grouping.child); diff --git a/source/lexer.c b/source/lexer.c index 4895347..15a769e 100644 --- a/source/lexer.c +++ b/source/lexer.c @@ -292,6 +292,7 @@ Token scanLexer(Lexer* lexer) { case '|': return makeToken(lexer, match(lexer, '|') ? TOKEN_OR : TOKEN_PIPE); + case '?': return makeToken(lexer, TOKEN_QUESTION); case ':': return makeToken(lexer, TOKEN_COLON); case ';': return makeToken(lexer, TOKEN_SEMICOLON); case ',': return makeToken(lexer, TOKEN_COMMA); diff --git a/source/opcodes.h b/source/opcodes.h index 9f7d8fb..f0d4f4e 100644 --- a/source/opcodes.h +++ b/source/opcodes.h @@ -76,6 +76,9 @@ typedef enum Opcode { //pop the stack at the end of a complex statement OP_POP_STACK, + //ternary shorthand + OP_TERNARY, + //meta OP_FN_END, //different from SECTION_END OP_SECTION_END = 255, diff --git a/source/parser.c b/source/parser.c index 9ee28bf..7e1068e 100644 --- a/source/parser.c +++ b/source/parser.c @@ -778,6 +778,21 @@ static Opcode indexAccess(Parser* parser, ASTNode** nodeHandle) { //TODO: fix in return OP_INDEX; } +static Opcode question(Parser* parser, ASTNode** nodeHandle) { + advance(parser); //for the question mark + + ASTNode* thenPath = NULL; + ASTNode* elsePath = NULL; + + parsePrecedence(parser, &thenPath, PREC_TERNARY); + consume(parser, TOKEN_COLON, "Expected ':' in ternary expression"); + parsePrecedence(parser, &elsePath, PREC_TERNARY); + + emitASTNodeTernary(nodeHandle, NULL, thenPath, elsePath); + + return OP_TERNARY; +} + static Opcode dot(Parser* parser, ASTNode** nodeHandle) { advance(parser); //for the dot @@ -824,8 +839,8 @@ ParseRule parseRules[] = { //must match the token types {NULL, NULL, PREC_NONE},// TOKEN_OF, {NULL, NULL, PREC_NONE},// TOKEN_PRINT, {NULL, NULL, PREC_NONE},// TOKEN_RETURN, - {atomic, NULL, PREC_NONE},// TOKEN_TYPE, - {asType, NULL, PREC_PRIMARY},// TOKEN_ASTYPE, + {atomic, NULL, PREC_PRIMARY},// TOKEN_TYPE, + {asType, NULL, PREC_CALL},// TOKEN_ASTYPE, {typeOf, NULL, PREC_CALL},// TOKEN_TYPEOF, {NULL, NULL, PREC_NONE},// TOKEN_VAR, {NULL, NULL, PREC_NONE},// TOKEN_WHILE, @@ -871,6 +886,7 @@ ParseRule parseRules[] = { //must match the token types {NULL, binary, PREC_OR},// TOKEN_OR, //other operators + {NULL, question, PREC_TERNARY}, //TOKEN_QUESTION, {NULL, NULL, PREC_NONE},// TOKEN_COLON, {NULL, NULL, PREC_NONE},// TOKEN_SEMICOLON, {NULL, NULL, PREC_NONE},// TOKEN_COMMA, @@ -1120,6 +1136,13 @@ static void parsePrecedence(Parser* parser, ASTNode** nodeHandle, PrecedenceRule dottify(parser, &rhsNode); } + //BUGFIX: ternary shorthand + if (opcode == OP_TERNARY) { + rhsNode->ternary.condition = *nodeHandle; + *nodeHandle = rhsNode; + continue; + } + emitASTNodeBinary(nodeHandle, rhsNode, opcode); //optimise away the constants diff --git a/source/token_types.h b/source/token_types.h index 7816dd7..896a315 100644 --- a/source/token_types.h +++ b/source/token_types.h @@ -78,6 +78,7 @@ typedef enum TokenType { TOKEN_OR, //other operators + TOKEN_QUESTION, TOKEN_COLON, TOKEN_SEMICOLON, TOKEN_COMMA, diff --git a/source/toy_common.h b/source/toy_common.h index 4a2b535..3feaba0 100644 --- a/source/toy_common.h +++ b/source/toy_common.h @@ -6,7 +6,7 @@ #define TOY_VERSION_MAJOR 0 #define TOY_VERSION_MINOR 7 -#define TOY_VERSION_PATCH 0 +#define TOY_VERSION_PATCH 1 #define TOY_VERSION_BUILD __DATE__ " " __TIME__ //platform-specific specifications diff --git a/test/scripts/ternary-expressions.toy b/test/scripts/ternary-expressions.toy new file mode 100644 index 0000000..9b6cede --- /dev/null +++ b/test/scripts/ternary-expressions.toy @@ -0,0 +1,21 @@ +//test basic truth ternaries +{ + assert true ? true : false, "Basic true ternary failed"; + assert false ? false : true, "Basic false ternary failed"; +} + + +//test nesting +{ + fn least(a, b, c) { + return a < b ? a : b < c ? b : c; + } + + assert least(1, 2, 3) == 1, "Least 1, 2, 3 failed"; + assert least(10, 5, 7) == 5, "Least 10, 5, 7 failed"; + assert least(9, 7, 5) == 5, "Least 9, 7, 5 failed"; +} + + +print "All good"; + diff --git a/test/test_interpreter.c b/test/test_interpreter.c index 0f3a115..31d181c 100644 --- a/test/test_interpreter.c +++ b/test/test_interpreter.c @@ -196,7 +196,8 @@ int main() { "long-dictionary.toy", "long-literals.toy", "native-functions.toy", - "panic-within-functions.toy", + "panic-within-functions.toy", + "ternary-expressions.toy", "types.toy", NULL };