diff --git a/src/compiler.c b/src/compiler.c index 6088a70..346e949 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -2724,43 +2724,46 @@ static void string(int exprType) { krk_rewindScanner(beforeExpression); /* To get us back to where we were with a string token */ parser = parserBefore; c = inner.start; - KrkToken which = syntheticToken("str"); - int hasEq = 0; + + int formatType = 0; + while (*c == ' ') c++; if (*c == '=') { c++; while (*c == ' ') c++; emitConstant(OBJECT_VAL(krk_copyString(start,c-start))); - emitByte(OP_SWAP); - hasEq = 1; + formatType |= FORMAT_OP_EQ; } + if (*c == '!') { c++; /* Conversion specifiers, must only be one */ if (*c == 'r') { - which = syntheticToken("repr"); + formatType |= FORMAT_OP_REPR; } else if (*c == 's') { - which = syntheticToken("str"); + formatType |= FORMAT_OP_STR; } else { error("Unsupported conversion flag '%c' for f-string expression.", *c); goto _cleanupError; } c++; } - size_t ind = identifierConstant(&which); - EMIT_OPERAND_OP(OP_GET_GLOBAL, ind); - emitByte(OP_SWAP); - emitBytes(OP_CALL, 1); + if (*c == ':') { /* TODO format specs */ - error("Format spec not supported in f-string (GH-10)"); - goto _cleanupError; + const char * formatStart = c+1; + c++; + while (c < end && *c != '}') c++; + emitConstant(OBJECT_VAL(krk_copyString(formatStart,c-formatStart))); + formatType |= FORMAT_OP_FORMAT; } + + EMIT_OPERAND_OP(OP_FORMAT_VALUE, formatType); + if (*c != '}') { error("Expected closing '}' after expression in f-string"); goto _cleanupError; } - if (hasEq) emitByte(OP_ADD); if (atLeastOne) emitByte(OP_ADD); atLeastOne = 1; c++; diff --git a/src/kuroko/chunk.h b/src/kuroko/chunk.h index c88dce6..95ae8a1 100644 --- a/src/kuroko/chunk.h +++ b/src/kuroko/chunk.h @@ -126,6 +126,7 @@ typedef enum { OP_CALL_METHOD, OP_CLOSE_MANY, OP_POP_MANY, + OP_FORMAT_VALUE, /* Two opcode instructions */ OP_JUMP_IF_FALSE_OR_POP, @@ -177,6 +178,7 @@ typedef enum { OP_CALL_METHOD_LONG, OP_CLOSE_MANY_LONG, OP_POP_MANY_LONG, + OP_FORMAT_VALUE_LONG, /* should be unused */ } KrkOpCode; /** diff --git a/src/opcodes.h b/src/opcodes.h index 8f0a32a..416d055 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -97,6 +97,7 @@ OPERAND(OP_SLICE, (void)0) OPERAND(OP_CALL_METHOD, (void)0) OPERAND(OP_CLOSE_MANY, (void)0) OPERAND(OP_POP_MANY, (void)0) +OPERAND(OP_FORMAT_VALUE, (void)0) JUMP(OP_JUMP_IF_FALSE_OR_POP,+) JUMP(OP_JUMP_IF_TRUE_OR_POP,+) JUMP(OP_JUMP,+) diff --git a/src/private.h b/src/private.h index d078b8d..c7f3bce 100644 --- a/src/private.h +++ b/src/private.h @@ -51,3 +51,10 @@ typedef enum { METHOD__MAX, } KrkSpecialMethods; + +#define FORMAT_OP_EQ (1 << 0) +#define FORMAT_OP_REPR (1 << 1) +#define FORMAT_OP_STR (1 << 2) +#define FORMAT_OP_FORMAT (1 << 3) + + diff --git a/src/vm.c b/src/vm.c index b9db161..c255a5e 100644 --- a/src/vm.c +++ b/src/vm.c @@ -2537,6 +2537,74 @@ static inline void makeCollection(NativeFn func, size_t count) { } } +static inline int doFormatString(int options) { + if (options & FORMAT_OP_FORMAT) { + krk_swap(1); + if (options & FORMAT_OP_EQ) { + krk_swap(2); + } + } else if (options & FORMAT_OP_EQ) { + krk_swap(1); + } + + /* Was this a repr or str call? (it can't be both) */ + if (options & FORMAT_OP_STR) { + KrkClass * type = krk_getType(krk_peek(0)); + if (likely(type->_tostr != NULL)) { + krk_push(krk_callDirect(type->_tostr, 1)); + if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) return 1; + } else { + krk_runtimeError(vm.exceptions->typeError, + "Can not convert %s to str", krk_typeName(krk_peek(0))); + return 1; + } + } else if (options & FORMAT_OP_REPR) { + KrkClass * type = krk_getType(krk_peek(0)); + if (likely(type->_reprer != NULL)) { + krk_push(krk_callDirect(type->_reprer, 1)); + if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) return 1; + } else { + krk_runtimeError(vm.exceptions->typeError, + "Can not repr %s", krk_typeName(krk_peek(0))); + return 1; + } + } + + if (!(options & FORMAT_OP_FORMAT)) { + /* Push empty string */ + krk_push(OBJECT_VAL(S(""))); + } else { + /* Swap args so value is first */ + krk_swap(1); + } + + /* Get the type of the value */ + KrkClass * type = krk_getType(krk_peek(1)); + + if (likely(type->_format != NULL)) { + krk_push(krk_callDirect(type->_format, 2)); + if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) return 1; + } else { + krk_runtimeError(vm.exceptions->attributeError, "'%s' object has no attribute '%s'", krk_typeName(krk_peek(1)), "__format__"); + return 1; + } + + if (!IS_STRING(krk_peek(0))) { + krk_runtimeError(vm.exceptions->typeError, "format result is not str"); + return 1; + } + + if (options & FORMAT_OP_EQ) { + krk_push(krk_operator_add(krk_peek(1), krk_peek(0))); + krk_swap(2); + krk_pop(); + krk_pop(); + } + + return 0; +} + + /** * VM main loop. */ @@ -3429,6 +3497,14 @@ _finishReturn: (void)0; krk_currentThread.stackTop[-count] = values->values.values[0]; break; } + + case OP_FORMAT_VALUE_LONG: + THREE_BYTE_OPERAND; + case OP_FORMAT_VALUE: { + ONE_BYTE_OPERAND; + if (doFormatString(OPERAND)) goto _finishException; + break; + } } if (unlikely(krk_currentThread.flags & KRK_THREAD_HAS_EXCEPTION)) { _finishException: diff --git a/test/testFormatSpecs.krk b/test/testFormatSpecs.krk new file mode 100644 index 0000000..05ef36d --- /dev/null +++ b/test/testFormatSpecs.krk @@ -0,0 +1,2 @@ +print(f'{4225325:#016_x}') +print(f'{4225325325325325325632632532432432:#016_x}') diff --git a/test/testFormatSpecs.krk.expect b/test/testFormatSpecs.krk.expect new file mode 100644 index 0000000..9c42e92 --- /dev/null +++ b/test/testFormatSpecs.krk.expect @@ -0,0 +1,2 @@ +0x0000_0040_792d +0xd053_1a76_d6a2_ecdd_ed78_bd72_f630