Просмотр исходного кода

Merge branch 'main' into updated-c-binding-test

Kolten Pearson 2 лет назад
Родитель
Сommit
4fce05b611
14 измененных файлов с 196 добавлено и 24 удалено
  1. 1 1
      .gitignore
  2. 1 1
      docs/features/differences.md
  3. 49 3
      python/builtins.py
  4. 36 0
      python/collections.py
  5. 13 0
      src/ceval.h
  6. 2 2
      src/common.h
  7. 10 8
      src/compiler.h
  8. 46 1
      src/expr.h
  9. 6 3
      src/lexer.h
  10. 3 1
      src/main.cpp
  11. 2 0
      src/opcodes.h
  12. 6 0
      src/pocketpy.h
  13. 5 4
      src/vm.h
  14. 16 0
      tests/31_cmp.py

+ 1 - 1
.gitignore

@@ -21,9 +21,9 @@ plugins/macos/pocketpy/pocketpy.*
 src/_generated.h
 profile.sh
 test
-tmp.rar
 src/httplib.h
 pocketpy.exe
 main.obj
 pocketpy.exp
 pocketpy.lib
+APPS

+ 1 - 1
docs/features/differences.md

@@ -42,4 +42,4 @@ The easiest way to test a feature is to [try it on your browser](https://pocketp
 6. `__ne__` is not required. Define `__eq__` is enough.
 7. Raw string cannot have boundary quotes in it, even escaped. See [#55](https://github.com/blueloveTH/pocketpy/issues/55).
 8. In a starred unpacked assignment, e.g. `a, b, *c = x`, the starred variable can only be presented in the last position. `a, *b, c = x` is not supported.
-9. `a < b < c` does not work as you expected. Use `a < b and b < c` instead.
+9. `a < b < c` does not work as you expected. Use `a < b and b < c` instead. **(available in main branch now)**

+ 49 - 3
python/builtins.py

@@ -104,7 +104,8 @@ def sorted(iterable, reverse=False, key=None):
     return a
 
 ##### str #####
-def __f(self, sep):
+def __f(self, sep=None):
+    sep = sep or ' '
     if sep == "":
         return list(self)
     res = []
@@ -130,6 +131,22 @@ def __f(self, *args):
     return self
 str.format = __f
 
+def __f(self, chars=None):
+    chars = chars or ' \t\n\r'
+    i = 0
+    while i < len(self) and self[i] in chars:
+        ++i
+    return self[i:]
+str.lstrip = __f
+
+def __f(self, chars=None):
+    chars = chars or ' \t\n\r'
+    j = len(self) - 1
+    while j >= 0 and self[j] in chars:
+        --j
+    return self[:j+1]
+str.rstrip = __f
+
 def __f(self, chars=None):
     chars = chars or ' \t\n\r'
     i = 0
@@ -169,8 +186,37 @@ def __f(self, reverse=False, key=None):
         self.reverse()
 list.sort = __f
 
-def staticmethod(f):
-    return f    # no effect
+def __f(self, other):
+    for i, j in zip(self, other):
+        if i != j:
+            return i < j
+    return len(self) < len(other)
+tuple.__lt__ = __f
+list.__lt__ = __f
+
+def __f(self, other):
+    for i, j in zip(self, other):
+        if i != j:
+            return i > j
+    return len(self) > len(other)
+tuple.__gt__ = __f
+list.__gt__ = __f
+
+def __f(self, other):
+    for i, j in zip(self, other):
+        if i != j:
+            return i <= j
+    return len(self) <= len(other)
+tuple.__le__ = __f
+list.__le__ = __f
+
+def __f(self, other):
+    for i, j in zip(self, other):
+        if i != j:
+            return i >= j
+    return len(self) >= len(other)
+tuple.__ge__ = __f
+list.__ge__ = __f
 
 type.__repr__ = lambda self: "<class '" + self.__name__ + "'>"
 

+ 36 - 0
python/collections.py

@@ -81,3 +81,39 @@ def Counter(iterable):
         else:
             a[x] = 1
     return a
+
+class defaultdict:
+    def __init__(self, default_factory) -> None:
+        self.default_factory = default_factory
+        self._a = {}
+
+    def __getitem__(self, key):
+        if key not in self._a:
+            self._a[key] = self.default_factory()
+        return self._a[key]
+        
+    def __setitem__(self, key, value):
+        self._a[key] = value
+
+    def __repr__(self) -> str:
+        return f"defaultdict({self.default_factory}, {self._a})"
+    
+    def __eq__(self, __o: object) -> bool:
+        if not isinstance(__o, defaultdict):
+            return False
+        if self.default_factory != __o.default_factory:
+            return False
+        return self._a == __o._a
+    
+    def __len__(self):
+        return len(self._a)
+
+    def keys(self):
+        return self._a.keys()
+    
+    def values(self):
+        return self._a.values()
+    
+    def items(self):
+        return self._a.items()
+

+ 13 - 0
src/ceval.h

@@ -69,6 +69,12 @@ __NEXT_STEP:;
     TARGET(POP_TOP) POP(); DISPATCH();
     TARGET(DUP_TOP) PUSH(TOP()); DISPATCH();
     TARGET(ROT_TWO) std::swap(TOP(), SECOND()); DISPATCH();
+    TARGET(ROT_THREE)
+        _0 = TOP();
+        TOP() = SECOND();
+        SECOND() = THIRD();
+        THIRD() = _0;
+        DISPATCH();
     TARGET(PRINT_EXPR)
         if(TOP() != None) _stdout(this, CAST(Str&, py_repr(TOP())) + "\n");
         POP();
@@ -408,6 +414,13 @@ __NEXT_STEP:;
         if(py_bool(TOP()) == false) frame->jump_abs(byte.arg);
         else POP();
         DISPATCH();
+    TARGET(SHORTCUT_IF_FALSE_OR_POP)
+        if(py_bool(TOP()) == false){        // [b, False]
+            STACK_SHRINK(2);                // []
+            PUSH(vm->False);                // [False]
+            frame->jump_abs(byte.arg);
+        } else POP();                       // [b]
+        DISPATCH();
     TARGET(LOOP_CONTINUE)
         frame->jump_abs(co_blocks[byte.block].start);
         DISPATCH();

+ 2 - 2
src/common.h

@@ -178,9 +178,9 @@ inline PyObject* const PY_OP_CALL = (PyObject*)0b100011;
 inline PyObject* const PY_OP_YIELD = (PyObject*)0b110011;
 
 #ifdef _WIN32
-    char kPlatformSep = '\\';
+    inline const char kPlatformSep = '\\';
 #else
-    char kPlatformSep = '/';
+    inline const char kPlatformSep = '/';
 #endif
 
 } // namespace pkpy

+ 10 - 8
src/compiler.h

@@ -93,12 +93,12 @@ class Compiler {
         rules[TK("**")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_EXPONENT };
         rules[TK(">")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK("<")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
-        rules[TK("==")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_EQUALITY };
-        rules[TK("!=")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_EQUALITY };
+        rules[TK("==")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
+        rules[TK("!=")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK(">=")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK("<=")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
-        rules[TK("in")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_TEST };
-        rules[TK("is")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_TEST };
+        rules[TK("in")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
+        rules[TK("is")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK("<<")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_BITWISE_SHIFT };
         rules[TK(">>")] =       { nullptr,               METHOD(exprBinaryOp),       PREC_BITWISE_SHIFT };
         rules[TK("&")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_BITWISE_AND };
@@ -107,8 +107,8 @@ class Compiler {
         rules[TK("@")] =        { nullptr,               METHOD(exprBinaryOp),       PREC_FACTOR };
         rules[TK("if")] =       { nullptr,               METHOD(exprTernary),        PREC_TERNARY };
         rules[TK(",")] =        { nullptr,               METHOD(exprTuple),          PREC_TUPLE };
-        rules[TK("not in")] =   { nullptr,               METHOD(exprBinaryOp),       PREC_TEST };
-        rules[TK("is not")] =   { nullptr,               METHOD(exprBinaryOp),       PREC_TEST };
+        rules[TK("not in")] =   { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
+        rules[TK("is not")] =   { nullptr,               METHOD(exprBinaryOp),       PREC_COMPARISION };
         rules[TK("and") ] =     { nullptr,               METHOD(exprAnd),            PREC_LOGICAL_AND };
         rules[TK("or")] =       { nullptr,               METHOD(exprOr),             PREC_LOGICAL_OR };
         rules[TK("not")] =      { METHOD(exprNot),       nullptr,                    PREC_LOGICAL_NOT };
@@ -856,8 +856,10 @@ __SUBSCR_END:
         consume(TK("@id"));
         int namei = StrName(prev().str()).index;
         int super_namei = -1;
-        if(match(TK("(")) && match(TK("@id"))){
-            super_namei = StrName(prev().str()).index;
+        if(match(TK("("))){
+            if(match(TK("@id"))){
+                super_namei = StrName(prev().str()).index;
+            }
             consume(TK(")"));
         }
         if(super_namei == -1) ctx()->emit(OP_LOAD_NONE, BC_NOARG, prev().line);

+ 46 - 1
src/expr.h

@@ -23,6 +23,7 @@ struct Expr{
     virtual bool is_literal() const { return false; }
     virtual bool is_json_object() const { return false; }
     virtual bool is_attrib() const { return false; }
+    virtual bool is_compare() const { return false; }
 
     // for OP_DELETE_XXX
     [[nodiscard]] virtual bool emit_del(CodeEmitContext* ctx) { return false; }
@@ -531,6 +532,7 @@ struct FStringExpr: Expr{
     }
 
     void _load_simple_expr(CodeEmitContext* ctx, Str expr){
+        // TODO: pre compile this into a function
         int dot = expr.index(".");
         if(dot < 0){
             ctx->emit(OP_LOAD_NAME, StrName(expr.sv()).index, line);
@@ -682,8 +684,48 @@ struct BinaryExpr: Expr{
     Expr_ rhs;
     std::string str() const override { return TK_STR(op); }
 
+    bool is_compare() const override {
+        switch(op){
+            case TK("<"): case TK("<="): case TK("=="):
+            case TK("!="): case TK(">"): case TK(">="): return true;
+            default: return false;
+        }
+    }
+
+    void _emit_compare(CodeEmitContext* ctx, std::vector<int>& jmps){
+        if(lhs->is_compare()){
+            static_cast<BinaryExpr*>(lhs.get())->_emit_compare(ctx, jmps);
+        }else{
+            lhs->emit(ctx); // [a]
+        }
+        rhs->emit(ctx); // [a, b]
+        ctx->emit(OP_DUP_TOP, BC_NOARG, line);      // [a, b, b]
+        ctx->emit(OP_ROT_THREE, BC_NOARG, line);    // [b, a, b]
+        switch(op){
+            case TK("<"):   ctx->emit(OP_COMPARE_LT, BC_NOARG, line);  break;
+            case TK("<="):  ctx->emit(OP_COMPARE_LE, BC_NOARG, line);  break;
+            case TK("=="):  ctx->emit(OP_COMPARE_EQ, BC_NOARG, line);  break;
+            case TK("!="):  ctx->emit(OP_COMPARE_NE, BC_NOARG, line);  break;
+            case TK(">"):   ctx->emit(OP_COMPARE_GT, BC_NOARG, line);  break;
+            case TK(">="):  ctx->emit(OP_COMPARE_GE, BC_NOARG, line);  break;
+            default: UNREACHABLE();
+        }
+        // [b, RES]
+        int index = ctx->emit(OP_SHORTCUT_IF_FALSE_OR_POP, BC_NOARG, line);
+        jmps.push_back(index);
+    }
+
     void emit(CodeEmitContext* ctx) override {
-        lhs->emit(ctx);
+        std::vector<int> jmps;
+        if(is_compare() && lhs->is_compare()){
+            // (a < b) < c
+            static_cast<BinaryExpr*>(lhs.get())->_emit_compare(ctx, jmps);
+            // [b, RES]
+        }else{
+            // (1 + 2) < c
+            lhs->emit(ctx);
+        }
+
         rhs->emit(ctx);
         switch (op) {
             case TK("+"):   ctx->emit(OP_BINARY_ADD, BC_NOARG, line);  break;
@@ -700,6 +742,7 @@ struct BinaryExpr: Expr{
             case TK("!="):  ctx->emit(OP_COMPARE_NE, BC_NOARG, line);  break;
             case TK(">"):   ctx->emit(OP_COMPARE_GT, BC_NOARG, line);  break;
             case TK(">="):  ctx->emit(OP_COMPARE_GE, BC_NOARG, line);  break;
+
             case TK("in"):      ctx->emit(OP_CONTAINS_OP, 0, line);   break;
             case TK("not in"):  ctx->emit(OP_CONTAINS_OP, 1, line);   break;
             case TK("is"):      ctx->emit(OP_IS_OP, 0, line);         break;
@@ -714,6 +757,8 @@ struct BinaryExpr: Expr{
             case TK("@"):   ctx->emit(OP_BINARY_MATMUL, BC_NOARG, line);  break;
             default: FATAL_ERROR();
         }
+
+        for(int i: jmps) ctx->patch_jump(i);
     }
 };
 

+ 6 - 3
src/lexer.h

@@ -79,9 +79,12 @@ enum Precedence {
   PREC_LOGICAL_OR,    // or
   PREC_LOGICAL_AND,   // and
   PREC_LOGICAL_NOT,   // not
-  PREC_EQUALITY,      // == !=
-  PREC_TEST,          // in / is / is not / not in
-  PREC_COMPARISION,   // < > <= >=
+  /* https://docs.python.org/3/reference/expressions.html#comparisons
+   * Unlike C, all comparison operations in Python have the same priority,
+   * which is lower than that of any arithmetic, shifting or bitwise operation.
+   * Also unlike C, expressions like a < b < c have the interpretation that is conventional in mathematics.
+   */
+  PREC_COMPARISION,   // < > <= >= != ==, in / is / is not / not in
   PREC_BITWISE_OR,    // |
   PREC_BITWISE_XOR,   // ^
   PREC_BITWISE_AND,   // &

+ 3 - 1
src/main.cpp

@@ -7,9 +7,11 @@
 
 int main(int argc, char** argv){
     pkpy::VM* vm = pkpy_new_vm();
-    vm->bind_builtin_func<0>("input", [](pkpy::VM* vm, pkpy::ArgsView args){
+    pkpy::PyObject* input_f = vm->bind_builtin_func<0>("input", [](pkpy::VM* vm, pkpy::ArgsView args){
+        // pkpy::getline() has bugs for PIPE input on Windows
         return VAR(pkpy::getline());
     });
+    vm->_modules["sys"]->attr("stdin")->attr().set("readline", input_f);
     if(argc == 1){
         pkpy::REPL* repl = pkpy_new_repl(vm);
         bool need_more_lines = false;

+ 2 - 0
src/opcodes.h

@@ -6,6 +6,7 @@ OPCODE(NO_OP)
 OPCODE(POP_TOP)
 OPCODE(DUP_TOP)
 OPCODE(ROT_TWO)
+OPCODE(ROT_THREE)
 OPCODE(PRINT_EXPR)
 /**************************/
 OPCODE(LOAD_CONST)
@@ -75,6 +76,7 @@ OPCODE(JUMP_ABSOLUTE)
 OPCODE(POP_JUMP_IF_FALSE)
 OPCODE(JUMP_IF_TRUE_OR_POP)
 OPCODE(JUMP_IF_FALSE_OR_POP)
+OPCODE(SHORTCUT_IF_FALSE_OR_POP)
 OPCODE(LOOP_CONTINUE)
 OPCODE(LOOP_BREAK)
 OPCODE(GOTO)

+ 6 - 0
src/pocketpy.h

@@ -104,6 +104,10 @@ inline void init_builtins(VM* _vm) {
         return VAR_T(VoidP, obj);
     });
 
+    _vm->bind_builtin_func<1>("staticmethod", [](VM* vm, ArgsView args) {
+        return args[0];
+    });
+
     _vm->bind_builtin_func<1>("__import__", [](VM* vm, ArgsView args) {
         return vm->py_import(CAST(Str&, args[0]));
     });
@@ -1110,8 +1114,10 @@ inline void add_module_sys(VM* vm){
 
     PyObject* stdout_ = vm->heap.gcnew<DummyInstance>(vm->tp_object, {});
     PyObject* stderr_ = vm->heap.gcnew<DummyInstance>(vm->tp_object, {});
+    PyObject* stdin_ = vm->heap.gcnew<DummyInstance>(vm->tp_object, {});
     vm->setattr(mod, "stdout", stdout_);
     vm->setattr(mod, "stderr", stderr_);
+    vm->setattr(mod, "stdin", stdin_);
 
     vm->bind_func<1>(stdout_, "write", [](VM* vm, ArgsView args) {
         vm->_stdout(vm, CAST(Str&, args[0]));

+ 5 - 4
src/vm.h

@@ -1005,7 +1005,7 @@ inline Str VM::disassemble(CodeObject_ co){
 
     std::vector<int> jumpTargets;
     for(auto byte : co->codes){
-        if(byte.op == OP_JUMP_ABSOLUTE || byte.op == OP_POP_JUMP_IF_FALSE){
+        if(byte.op == OP_JUMP_ABSOLUTE || byte.op == OP_POP_JUMP_IF_FALSE || byte.op == OP_POP_JUMP_IF_FALSE || byte.op == OP_SHORTCUT_IF_FALSE_OR_POP){
             jumpTargets.push_back(byte.arg);
         }
     }
@@ -1027,11 +1027,12 @@ inline Str VM::disassemble(CodeObject_ co){
             pointer = "   ";
         }
         ss << pad(line, 8) << pointer << pad(std::to_string(i), 3);
-        ss << " " << pad(OP_NAMES[byte.op], 20) << " ";
+        ss << " " << pad(OP_NAMES[byte.op], 25) << " ";
         // ss << pad(byte.arg == -1 ? "" : std::to_string(byte.arg), 5);
         std::string argStr = _opcode_argstr(this, byte, co.get());
-        ss << pad(argStr, 40);      // may overflow
-        ss << co->blocks[byte.block].type;
+        ss << argStr;
+        // ss << pad(argStr, 40);      // may overflow
+        // ss << co->blocks[byte.block].type;
         if(i != co->codes.size() - 1) ss << '\n';
     }
 

+ 16 - 0
tests/31_cmp.py

@@ -0,0 +1,16 @@
+assert 1<2
+assert 1+1==2
+assert 2+1>=2
+
+assert 1<2<3
+assert 1<2<3<4
+assert 1<2<3<4<5
+
+assert 1<1+1<3
+assert 1<1+1<3<4
+assert 1<1+1<3<2+2<5
+
+a = [1,2,3]
+assert a[0] < a[1] < a[2]
+assert a[0]+1 == a[1] < a[2]
+assert a[0]+1 == a[1] < a[2]+1 < 5