blueloveTH 1 năm trước cách đây
mục cha
commit
bc991249b9

+ 2 - 0
.gitignore

@@ -34,3 +34,5 @@ docs/references.md
 
 .xmake
 .vs
+
+tests/00_tmp.py

+ 1 - 1
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,

+ 0 - 2
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

+ 84 - 170
src/compiler/compiler.c

@@ -7,6 +7,7 @@
 #include "pocketpy/common/sstream.h"
 #include "pocketpy/common/config.h"
 #include "pocketpy/common/memorypool.h"
+#include <assert.h>
 #include <ctype.h>
 #include <stdbool.h>
 
@@ -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, "<f-string>", 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 | <expr>]*, @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      },

+ 121 - 26
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",

+ 31 - 22
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 <stdbool.h>
 
 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;
 }

+ 4 - 0
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);

+ 6 - 2
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         '