diff --git a/.gitignore b/.gitignore index ce195c89..3d69b671 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ docs/references.md .xmake .vs + +tests/00_tmp.py diff --git a/include/pocketpy/compiler/lexer.h b/include/pocketpy/compiler/lexer.h index e357c125..a54f3140 100644 --- a/include/pocketpy/compiler/lexer.h +++ b/include/pocketpy/compiler/lexer.h @@ -10,7 +10,7 @@ extern const char* TokenSymbols[]; typedef enum TokenIndex{ TK_EOF, TK_EOL, TK_SOF, - TK_ID, TK_NUM, TK_STR, TK_FSTR, TK_BYTES, TK_IMAG, + TK_ID, TK_NUM, TK_STR, TK_FSTR_BEGIN, TK_FSTR_CPNT, TK_FSTR_SPEC, TK_FSTR_END, TK_BYTES, TK_IMAG, TK_INDENT, TK_DEDENT, /***************/ TK_IS_NOT, TK_NOT_IN, TK_YIELD_FROM, diff --git a/include/pocketpy/xmacros/opcodes.h b/include/pocketpy/xmacros/opcodes.h index bab48a10..be3fe906 100644 --- a/include/pocketpy/xmacros/opcodes.h +++ b/include/pocketpy/xmacros/opcodes.h @@ -67,7 +67,6 @@ OPCODE(LOOP_BREAK) OPCODE(JUMP_ABSOLUTE_TOP) OPCODE(GOTO) /**************************/ -OPCODE(REPR) OPCODE(CALL) OPCODE(CALL_VARGS) OPCODE(RETURN_VALUE) @@ -108,7 +107,6 @@ OPCODE(PUSH_EXCEPTION) OPCODE(BEGIN_EXC_HANDLING) OPCODE(END_EXC_HANDLING) /**************************/ -OPCODE(FSTRING_EVAL) OPCODE(FORMAT_STRING) /**************************/ #endif diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 1dd95054..42f512e5 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -7,6 +7,7 @@ #include "pocketpy/common/sstream.h" #include "pocketpy/common/config.h" #include "pocketpy/common/memorypool.h" +#include #include #include @@ -227,6 +228,30 @@ UnaryExpr* UnaryExpr__new(int line, Expr* child, Opcode opcode) { return self; } +typedef struct FStringSpecExpr { + EXPR_COMMON_HEADER + Expr* child; + c11_sv spec; +} FStringSpecExpr; + +void FStringSpecExpr__emit_(Expr* self_, Ctx* ctx) { + FStringSpecExpr* self = (FStringSpecExpr*)self_; + vtemit_(self->child, ctx); + int index = Ctx__add_const_string(ctx, self->spec); + Ctx__emit_(ctx, OP_FORMAT_STRING, index, self->line); +} + +FStringSpecExpr* FStringSpecExpr__new(int line, Expr* child, c11_sv spec) { + const static ExprVt Vt = {.emit_ = FStringSpecExpr__emit_, .dtor = UnaryExpr__dtor}; + static_assert_expr_size(FStringSpecExpr); + FStringSpecExpr* self = PoolExpr_alloc(); + self->vt = &Vt; + self->line = line; + self->child = child; + self->spec = spec; + return self; +} + typedef struct RawStringExpr { EXPR_COMMON_HEADER c11_sv value; @@ -236,8 +261,7 @@ typedef struct RawStringExpr { void RawStringExpr__emit_(Expr* self_, Ctx* ctx) { RawStringExpr* self = (RawStringExpr*)self_; int index = Ctx__add_const_string(ctx, self->value); - Ctx__emit_(ctx, OP_LOAD_CONST, index, self->line); - Ctx__emit_(ctx, self->opcode, BC_NOARG, self->line); + Ctx__emit_(ctx, self->opcode, index, self->line); } RawStringExpr* RawStringExpr__new(int line, c11_sv value, Opcode opcode) { @@ -505,6 +529,11 @@ static SequenceExpr* SequenceExpr__new(int line, const ExprVt* vt, int count, Op return self; } +SequenceExpr* FStringExpr__new(int line, int count) { + const static ExprVt ListExprVt = {.dtor = SequenceExpr__dtor, .emit_ = SequenceExpr__emit_}; + return SequenceExpr__new(line, &ListExprVt, count, OP_BUILD_STRING); +} + SequenceExpr* ListExpr__new(int line, int count) { const static ExprVt ListExprVt = {.dtor = SequenceExpr__dtor, .emit_ = SequenceExpr__emit_}; return SequenceExpr__new(line, &ListExprVt, count, OP_BUILD_LIST); @@ -611,162 +640,6 @@ LambdaExpr* LambdaExpr__new(int line, int index) { return self; } -typedef struct FStringExpr { - EXPR_COMMON_HEADER - c11_sv src; -} FStringExpr; - -static bool is_fmt_valid_char(char c) { - switch(c) { - // clang-format off - case '-': case '=': case '*': case '#': case '@': case '!': case '~': - case '<': case '>': case '^': - case '.': case 'f': case 'd': case 's': - case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - return true; - default: return false; - // clang-format on - } -} - -static void _load_expr(Ctx* ctx, c11_sv expr, int line) { - bool repr = false; - const char* expr_end = expr.data + expr.size; - if(expr.size >= 2 && expr_end[-2] == '!') { - switch(expr_end[-1]) { - case 'r': - repr = true; - expr.size -= 2; // expr[:-2] - break; - case 's': - repr = false; - expr.size -= 2; // expr[:-2] - break; - default: break; // nothing happens - } - } - - c11_string* source = c11_string__new2(expr.data, expr.size); - bool ok = py_compile(source->data, "", EVAL_MODE, true); - if(!ok) { - py_printexc(); - c11__abort("f-string: invalid expression"); - } - int index = Ctx__add_const(ctx, py_retval()); - c11_string__delete(source); - Ctx__emit_(ctx, OP_FSTRING_EVAL, index, line); - - if(repr) Ctx__emit_(ctx, OP_REPR, BC_NOARG, line); -} - -static void FStringExpr__emit_(Expr* self_, Ctx* ctx) { - FStringExpr* self = (FStringExpr*)self_; - int i = 0; // left index - int j = 0; // right index - int count = 0; // how many string parts - bool flag = false; // true if we are in a expression - - const char* src = self->src.data; - while(j < self->src.size) { - if(flag) { - if(src[j] == '}') { - // add expression - c11_sv expr = {src + i, j - i}; // src[i:j] - // BUG: ':' is not a format specifier in f"{stack[2:]}" - int conon = c11_sv__index(expr, ':'); - if(conon >= 0) { - c11_sv spec = {expr.data + (conon + 1), - expr.size - (conon + 1)}; // expr[conon+1:] - // filter some invalid spec - bool ok = true; - for(int k = 0; k < spec.size; k++) { - char c = spec.data[k]; - if(!is_fmt_valid_char(c)) { - ok = false; - break; - } - } - if(ok) { - expr.size = conon; // expr[:conon] - _load_expr(ctx, expr, self->line); - Ctx__emit_(ctx, - OP_FORMAT_STRING, - Ctx__add_const_string(ctx, spec), - self->line); - } else { - // ':' is not a spec indicator - _load_expr(ctx, expr, self->line); - } - } else { - _load_expr(ctx, expr, self->line); - } - flag = false; - count++; - } - } else { - if(src[j] == '{') { - // look at next char - if(j + 1 < self->src.size && src[j + 1] == '{') { - // {{ -> { - j++; - Ctx__emit_(ctx, - OP_LOAD_CONST, - Ctx__add_const_string(ctx, (c11_sv){"{", 1}), - self->line); - count++; - } else { - // { -> } - flag = true; - i = j + 1; - } - } else if(src[j] == '}') { - // look at next char - if(j + 1 < self->src.size && src[j + 1] == '}') { - // }} -> } - j++; - Ctx__emit_(ctx, - OP_LOAD_CONST, - Ctx__add_const_string(ctx, (c11_sv){"}", 1}), - self->line); - count++; - } else { - // } -> error - // throw std::runtime_error("f-string: unexpected }"); - // just ignore - } - } else { - // literal - i = j; - while(j < self->src.size && src[j] != '{' && src[j] != '}') - j++; - c11_sv literal = {src + i, j - i}; // src[i:j] - Ctx__emit_(ctx, OP_LOAD_CONST, Ctx__add_const_string(ctx, literal), self->line); - count++; - continue; // skip j++ - } - } - j++; - } - - if(flag) { - // literal - c11_sv literal = {src + i, self->src.size - i}; // src[i:] - Ctx__emit_(ctx, OP_LOAD_CONST, Ctx__add_const_string(ctx, literal), self->line); - count++; - } - Ctx__emit_(ctx, OP_BUILD_STRING, count, self->line); -} - -FStringExpr* FStringExpr__new(int line, c11_sv src) { - const static ExprVt Vt = {.emit_ = FStringExpr__emit_}; - static_assert_expr_size(FStringExpr); - FStringExpr* self = PoolExpr_alloc(); - self->vt = &Vt; - self->line = line; - self->src = src; - return self; -} - // AndExpr, OrExpr typedef struct LogicBinaryExpr { EXPR_COMMON_HEADER @@ -1669,9 +1542,39 @@ static Error* exprBytes(Compiler* self) { } static Error* exprFString(Compiler* self) { - c11_sv sv = c11_string__sv(prev()->value._str); - Ctx__s_push(ctx(), (Expr*)FStringExpr__new(prev()->line, sv)); - return NULL; + // @fstr-begin, [@fstr-cpnt | ]*, @fstr-end + int count = 0; + int line = prev()->line; + while(true) { + if(match(TK_FSTR_END)) { + SequenceExpr* e = FStringExpr__new(line, count); + for(int i = count - 1; i >= 0; i--) { + Expr* item = Ctx__s_popx(ctx()); + c11__setitem(Expr*, &e->items, i, item); + } + Ctx__s_push(ctx(), (Expr*)e); + return NULL; + } else if(match(TK_FSTR_CPNT)) { + // OP_LOAD_CONST + LiteralExpr* e = LiteralExpr__new(prev()->line, &prev()->value); + Ctx__s_push(ctx(), (Expr*)e); + count++; + } else { + // {a!r:.2f} + Error* err = EXPR(self); + if(err) return err; + count++; + + if(match(TK_FSTR_SPEC)) { + c11_sv spec = Token__sv(prev()); + // ':.2f}' -> ':.2f' + spec.size--; + Expr* child = Ctx__s_popx(ctx()); + FStringSpecExpr* e = FStringSpecExpr__new(prev()->line, child, spec); + Ctx__s_push(ctx(), (Expr*)e); + } + } + } } static Error* exprImag(Compiler* self) { @@ -2766,14 +2669,25 @@ Error* pk_compile(SourceData_ src, CodeObject* out) { Error* err = Lexer__process(src, &tokens); if(err) return err; - // Token* data = (Token*)tokens.data; - // printf("%s\n", src->filename->data); - // for(int i = 0; i < tokens.count; i++) { - // Token* t = data + i; - // c11_string* tmp = c11_string__new2(t->start, t->length); - // printf("[%d] %s: %s\n", t->line, TokenSymbols[t->type], tmp->data); - // c11_string__delete(tmp); - // } +#if 0 + Token* data = (Token*)tokens.data; + printf("%s\n", src->filename->data); + for(int i = 0; i < tokens.count; i++) { + Token* t = data + i; + c11_string* tmp = c11_string__new2(t->start, t->length); + if(t->value.index == TokenValue_STR) { + const char* value_str = t->value._str->data; + printf("[%d] %s: %s (value._str=%s)\n", + t->line, + TokenSymbols[t->type], + tmp->data, + value_str); + } else { + printf("[%d] %s: %s\n", t->line, TokenSymbols[t->type], tmp->data); + } + c11_string__delete(tmp); + } +#endif Compiler compiler; Compiler__ctor(&compiler, src, tokens); @@ -2829,7 +2743,7 @@ const static PrattRule rules[TK__COUNT__] = { [TK_ID] = { exprName, }, [TK_NUM] = { exprLiteral, }, [TK_STR] = { exprLiteral, }, - [TK_FSTR] = { exprFString, }, + [TK_FSTR_BEGIN] = { exprFString, }, [TK_IMAG] = { exprImag, }, [TK_BYTES] = { exprBytes, }, [TK_LBRACE] = { exprMap }, diff --git a/src/compiler/lexer.c b/src/compiler/lexer.c index 437a9e7d..cccbad40 100644 --- a/src/compiler/lexer.c +++ b/src/compiler/lexer.c @@ -36,6 +36,8 @@ double TokenDeserializer__read_float(TokenDeserializer* self, char c); const static TokenValue EmptyTokenValue; +static Error* lex_one_token(Lexer* self, bool* eof, bool is_fstring); + static void Lexer__ctor(Lexer* self, SourceData_ src) { PK_INCREF(src); self->src = src; @@ -269,20 +271,22 @@ static Error* eat_name(Lexer* self) { enum StringType { NORMAL_STRING, RAW_STRING, F_STRING, NORMAL_BYTES }; -static Error* eat_string(Lexer* self, char quote, enum StringType type) { - bool raw = type == RAW_STRING; +static Error* _eat_string(Lexer* self, c11_sbuf* buff, char quote, enum StringType type) { + bool is_raw = type == RAW_STRING; + bool is_fstring = type == F_STRING; + + if(is_fstring) { add_token(self, TK_FSTR_BEGIN); } // previous char is quote bool quote3 = match_n_chars(self, 2, quote); - c11_sbuf buff; - c11_sbuf__ctor(&buff); while(true) { char c = eatchar_include_newline(self); if(c == quote) { if(quote3 && !match_n_chars(self, 2, quote)) { - c11_sbuf__write_char(&buff, c); + c11_sbuf__write_char(buff, c); continue; } + // end of string break; } if(c == '\0') { return SyntaxError(self, "EOL while scanning string literal"); } @@ -290,39 +294,88 @@ static Error* eat_string(Lexer* self, char quote, enum StringType type) { if(!quote3) return SyntaxError(self, "EOL while scanning string literal"); else { - c11_sbuf__write_char(&buff, c); + c11_sbuf__write_char(buff, c); continue; } } - if(!raw && c == '\\') { + if(!is_raw && c == '\\') { switch(eatchar_include_newline(self)) { - case '"': c11_sbuf__write_char(&buff, '"'); break; - case '\'': c11_sbuf__write_char(&buff, '\''); break; - case '\\': c11_sbuf__write_char(&buff, '\\'); break; - case 'n': c11_sbuf__write_char(&buff, '\n'); break; - case 'r': c11_sbuf__write_char(&buff, '\r'); break; - case 't': c11_sbuf__write_char(&buff, '\t'); break; - case 'b': c11_sbuf__write_char(&buff, '\b'); break; + case '"': c11_sbuf__write_char(buff, '"'); break; + case '\'': c11_sbuf__write_char(buff, '\''); break; + case '\\': c11_sbuf__write_char(buff, '\\'); break; + case 'n': c11_sbuf__write_char(buff, '\n'); break; + case 'r': c11_sbuf__write_char(buff, '\r'); break; + case 't': c11_sbuf__write_char(buff, '\t'); break; + case 'b': c11_sbuf__write_char(buff, '\b'); break; case 'x': { char hex[3] = {eatchar(self), eatchar(self), '\0'}; int code; if(sscanf(hex, "%x", &code) != 1) { return SyntaxError(self, "invalid hex char"); } - c11_sbuf__write_char(&buff, (char)code); + c11_sbuf__write_char(buff, (char)code); } break; default: return SyntaxError(self, "invalid escape char"); } } else { - c11_sbuf__write_char(&buff, c); + if(is_fstring) { + if(c == '{') { + if(matchchar(self, '{')) { + // '{{' -> '{' + c11_sbuf__write_char(buff, '{'); + } else { + // submit previous string + c11_string* res = c11_sbuf__submit(buff); + if(res->size > 0) { + TokenValue value = {TokenValue_STR, ._str = res}; + add_token_with_value(self, TK_FSTR_CPNT, value); + } else { + c11_string__delete(res); + } + c11_sbuf__ctor(buff); // re-init buffer + + // submit {expr} tokens + bool eof = false; + int token_count = self->nexts.count; + while(!eof) { + Error* err = lex_one_token(self, &eof, true); + if(err) return err; + } + if(self->nexts.count == token_count) { + // f'{}' is not allowed + return SyntaxError(self, "f-string: empty expression not allowed"); + } + } + } else if(c == '}') { + if(matchchar(self, '}')) { + // '}}' -> '}' + c11_sbuf__write_char(buff, '}'); + } else { + return SyntaxError(self, "f-string: single '}' is not allowed"); + } + }else{ + c11_sbuf__write_char(buff, c); + } + } else { + c11_sbuf__write_char(buff, c); + } } } - c11_string* res = c11_sbuf__submit(&buff); + c11_string* res = c11_sbuf__submit(buff); TokenValue value = {TokenValue_STR, ._str = res}; - if(type == F_STRING) { - add_token_with_value(self, TK_FSTR, value); - } else if(type == NORMAL_BYTES) { + + if(is_fstring) { + if(res->size > 0) { + add_token_with_value(self, TK_FSTR_CPNT, value); + } else { + c11_string__delete(res); + } + add_token(self, TK_FSTR_END); + return NULL; + } + + if(type == NORMAL_BYTES) { add_token_with_value(self, TK_BYTES, value); } else { add_token_with_value(self, TK_STR, value); @@ -330,6 +383,14 @@ static Error* eat_string(Lexer* self, char quote, enum StringType type) { return NULL; } +static Error* eat_string(Lexer* self, char quote, enum StringType type) { + c11_sbuf buff; + c11_sbuf__ctor(&buff); + Error* err = _eat_string(self, &buff, quote, type); + c11_sbuf__dtor(&buff); + return err; +} + static Error* eat_number(Lexer* self) { const char* i = self->token_start; while(is_possible_number_char(*i)) @@ -376,7 +437,22 @@ static Error* eat_number(Lexer* self) { return SyntaxError(self, "invalid number literal"); } -static Error* lex_one_token(Lexer* self, bool* eof) { +static Error* eat_fstring_spec(Lexer* self, bool* eof) { + while(true) { + char c = eatchar_include_newline(self); + if(c == '\n' || c == '\0') { + return SyntaxError(self, "EOL while scanning f-string format spec"); + } + if(c == '}') { + add_token(self, TK_FSTR_SPEC); + *eof = true; + break; + } + } + return NULL; +} + +static Error* lex_one_token(Lexer* self, bool* eof, bool is_fstring) { *eof = false; while(*self->curr_char) { self->token_start = self->curr_char; @@ -391,9 +467,20 @@ static Error* lex_one_token(Lexer* self, bool* eof) { case '#': skip_line_comment(self); break; case '~': add_token(self, TK_INVERT); return NULL; case '{': add_token(self, TK_LBRACE); return NULL; - case '}': add_token(self, TK_RBRACE); return NULL; + case '}': { + if(is_fstring) { + *eof = true; + return NULL; + } + add_token(self, TK_RBRACE); + return NULL; + } case ',': add_token(self, TK_COMMA); return NULL; - case ':': add_token(self, TK_COLON); return NULL; + case ':': { + if(is_fstring && self->brackets_level == 0) { return eat_fstring_spec(self, eof); } + add_token(self, TK_COLON); + return NULL; + } case ';': add_token(self, TK_SEMICOLON); return NULL; case '(': add_token(self, TK_LPAREN); return NULL; case ')': add_token(self, TK_RPAREN); return NULL; @@ -461,12 +548,15 @@ static Error* lex_one_token(Lexer* self, bool* eof) { return NULL; } case '!': + if(is_fstring && self->brackets_level == 0) { + if(matchchar(self, 'r')) { return eat_fstring_spec(self, eof); } + } if(matchchar(self, '=')) { add_token(self, TK_NE); + return NULL; } else { return SyntaxError(self, "expected '=' after '!'"); } - break; case '*': if(matchchar(self, '*')) { add_token(self, TK_POW); // '**' @@ -507,6 +597,8 @@ static Error* lex_one_token(Lexer* self, bool* eof) { } } + if(is_fstring) return SyntaxError(self, "unterminated f-string expression"); + self->token_start = self->curr_char; while(self->indents.count > 1) { c11_vector__pop(&self->indents); @@ -530,7 +622,7 @@ Error* Lexer__process(SourceData_ src, TokenArray* out_tokens) { bool eof = false; while(!eof) { - void* err = lex_one_token(&lexer, &eof); + void* err = lex_one_token(&lexer, &eof, false); if(err) { Lexer__dtor(&lexer); return err; @@ -558,7 +650,10 @@ const char* TokenSymbols[] = { "@id", "@num", "@str", - "@fstr", + "@fstr-begin", // TK_FSTR_BEGIN + "@fstr-cpnt", // TK_FSTR_CPNT + "@fstr-spec", // TK_FSTR_SPEC + "@fstr-end", // TK_FSTR_END "@bytes", "@imag", "@indent", diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index db956de0..6c3f3c1c 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -1,4 +1,5 @@ #include "pocketpy/common/config.h" +#include "pocketpy/common/str.h" #include "pocketpy/common/utils.h" #include "pocketpy/interpreter/frame.h" #include "pocketpy/interpreter/vm.h" @@ -10,7 +11,7 @@ #include static bool stack_unpack_sequence(VM* self, uint16_t arg); -static bool format_object(py_Ref obj, c11_sv spec); +static bool stack_format_object(VM* self, c11_sv spec); #define DISPATCH() \ do { \ @@ -469,8 +470,9 @@ FrameResult VM__run_top_frame(VM* self) { } case OP_BUILD_BYTES: { int size; - const char* data = py_tostrn(TOP(), &size); - unsigned char* p = py_newbytes(TOP(), size); + py_Ref string = c11__at(py_TValue, &frame->co->consts, byte.arg); + const char* data = py_tostrn(string, &size); + unsigned char* p = py_newbytes(SP()++, size); memcpy(p, data, size); DISPATCH(); } @@ -660,11 +662,6 @@ FrameResult VM__run_top_frame(VM* self) { DISPATCH_JUMP_ABSOLUTE(target); } /*****************************************/ - case OP_REPR: { - if(!py_repr(TOP())) goto __ERROR; - py_assign(TOP(), py_retval()); - DISPATCH(); - } case OP_CALL: { ManagedHeap__collect_if_needed(&self->heap); vectorcall_opcall(byte.arg & 0xFF, byte.arg >> 8); @@ -1022,21 +1019,10 @@ FrameResult VM__run_top_frame(VM* self) { DISPATCH(); } ////////////////// - case OP_FSTRING_EVAL: { - py_TValue* code = c11__at(py_TValue, &frame->co->consts, byte.arg); - assert(py_istype(code, tp_code)); - py_newglobals(SP()++); - py_newlocals(SP()++); - PUSH(code); - if(!pk_exec(py_touserdata(code), frame->module)) goto __ERROR; - PUSH(py_retval()); - DISPATCH(); - } case OP_FORMAT_STRING: { py_Ref spec = c11__at(py_TValue, &frame->co->consts, byte.arg); - bool ok = format_object(TOP(), py_tosv(spec)); + bool ok = stack_format_object(self, py_tosv(spec)); if(!ok) goto __ERROR; - py_assign(TOP(), py_retval()); DISPATCH(); } default: c11__unreachedable(); @@ -1132,9 +1118,31 @@ static bool stack_unpack_sequence(VM* self, uint16_t arg) { return true; } -static bool format_object(py_Ref val, c11_sv spec) { +static bool stack_format_object(VM* self, c11_sv spec) { + // format TOS via `spec` inplace + // spec: '!r:.2f', '.2f' + py_StackRef val = TOP(); if(spec.size == 0) return py_str(val); + if(spec.data[0] == '!'){ + if(c11_sv__startswith(spec, (c11_sv){"!r", 2})){ + spec.data += 2; + spec.size -= 2; + if(!py_repr(val)) return false; + py_assign(val, py_retval()); + if(spec.size == 0) return true; + }else{ + return ValueError("invalid conversion specifier (only !r is supported)"); + } + } + + assert(spec.size > 0); + + if(spec.data[0] == ':'){ + spec.data++; + spec.size--; + } + char type; switch(spec.data[spec.size - 1]) { case 'f': @@ -1253,6 +1261,7 @@ static bool format_object(py_Ref val, c11_sv spec) { } c11_string__delete(body); - c11_sbuf__py_submit(&buf, py_retval()); + // inplace update + c11_sbuf__py_submit(&buf, val); return true; } \ No newline at end of file diff --git a/src/public/py_str.c b/src/public/py_str.c index 231ab353..97430dab 100644 --- a/src/public/py_str.c +++ b/src/public/py_str.c @@ -646,6 +646,10 @@ py_Type pk_bytes__register() { } bool py_str(py_Ref val) { + if(val->type == tp_str) { + py_assign(py_retval(), val); + return true; + } py_Ref tmp = py_tpfindmagic(val->type, __str__); if(!tmp) return py_repr(val); return py_call(tmp, 1, val); diff --git a/tests/25_rfstring.py b/tests/25_rfstring.py index 12c7e032..f95b67cf 100644 --- a/tests/25_rfstring.py +++ b/tests/25_rfstring.py @@ -20,6 +20,9 @@ assert s == 'asdasd\nasds1321321321测试\\测试' t = 4 assert f'123{t}56789' == '123456789' +assert f'{{' == '{' +assert f'}}' == '}' + b = 123 s = f'''->->{s}<-<- {b} @@ -117,10 +120,11 @@ class A: return 'A' a = A() +assert f'{a!r}' == 'A()' assert f'{a!r:10}' == 'A() ' -assert f'{a!s:10}' == 'A ' +assert f'{a:10}' == 'A ' assert f'{a:10}' == 'A ' assert f'{A()!r:10}' == 'A() ' -assert f'{A()!s:10}' == 'A ' +assert f'{A():10}' == 'A ' assert f'{A():10}' == 'A ' \ No newline at end of file