Forráskód Böngészése

Merge branch 'blueloveTH:main' into datetime-file

M Sai Kiran 2 éve
szülő
commit
1ad67650ac
13 módosított fájl, 208 hozzáadás és 67 törlés
  1. 11 1
      .github/workflows/main.yml
  2. 23 22
      README.md
  3. 20 20
      README_zh.md
  4. 1 1
      build.sh
  5. 1 1
      docs/bindings.md
  6. 1 1
      docs/index.md
  7. 1 0
      docs/quick-start/installation.md
  8. 109 8
      python/builtins.py
  9. 0 6
      src/ceval.cpp
  10. 13 5
      src/vm.cpp
  11. 13 0
      tests/04_str.py
  12. 9 0
      tests/80_json.py
  13. 6 2
      tests/80_traceback.py

+ 11 - 1
.github/workflows/main.yml

@@ -1,5 +1,15 @@
 name: build
-on: [push, pull_request]
+on:
+   push:
+     paths-ignore:
+       - 'docs/**'
+       - 'web/**'
+       - '**.md'
+   pull_request:
+     paths-ignore:
+       - 'docs/**'
+       - 'web/**'
+       - '**.md'
 jobs:
   build_win:
     runs-on: windows-latest

+ 23 - 22
README.md

@@ -38,7 +38,7 @@ These platforms are officially tested.
 + Android 64-bit / 32-bit
 + iOS 64-bit
 + Emscripten 32-bit
-+ Raspberry Pi 64-bit
++ Raspberry Pi OS 64-bit
 
 ## Quick Start
 
@@ -56,11 +56,12 @@ To compile it with your project, these flags must be set:
 
 + `--std=c++17` flag must be set
 + Exception must be enabled
++ For MSVC, `/utf-8` flag must be set
 
 For development build on Linux, use this snippet.
 ```bash
 # prerequisites
-sudo apt-get install libc++-dev libc++abi-dev clang++
+sudo apt-get install libc++-dev libc++abi-dev clang
 # build the repo
 bash build.sh
 # unittest
@@ -114,26 +115,26 @@ for a quick overview of the supported features.
 
 | Name            | Example                         | Supported |
 | --------------- | ------------------------------- | --------- |
-| If Else         | `if..else..elif`                | YES       |
-| Loop            | `for/while/break/continue`      | YES       |
-| Function        | `def f(x,*args,y=1):`           | YES       |
-| Subclass        | `class A(B):`                   | YES       |
-| List            | `[1, 2, 'a']`                   | YES       |
-| ListComp        | `[i for i in range(5)]`         | YES       |
-| Slice           | `a[1:2], a[:2], a[1:]`          | YES       |
-| Tuple           | `(1, 2, 'a')`                   | YES       |
-| Dict            | `{'a': 1, 'b': 2}`              | YES       |
-| F-String        | `f'value is {x}'`               | YES       |
-| Unpacking       | `a, b = 1, 2`                   | YES       |
-| Star Unpacking  | `a, *b = [1, 2, 3]`             | YES       |
-| Exception       | `raise/try..catch`              | YES       |
-| Dynamic Code    | `eval()/exec()`                 | YES       |
-| Reflection      | `hasattr()/getattr()/setattr()` | YES       |
-| Import          | `import/from..import`           | YES       |
-| Context Block   | `with <expr> as <id>:`          | YES       |
-| Type Annotation | `def  f(a:int, b:float=1)`      | YES       |
-| Generator       | `yield i`                       | YES       |
-| Decorator       | `@cache`                        | YES       |
+| If Else         | `if..else..elif`                |        |
+| Loop            | `for/while/break/continue`      |        |
+| Function        | `def f(x,*args,y=1):`           |        |
+| Subclass        | `class A(B):`                   |        |
+| List            | `[1, 2, 'a']`                   |        |
+| ListComp        | `[i for i in range(5)]`         |        |
+| Slice           | `a[1:2], a[:2], a[1:]`          |        |
+| Tuple           | `(1, 2, 'a')`                   |        |
+| Dict            | `{'a': 1, 'b': 2}`              |        |
+| F-String        | `f'value is {x}'`               |        |
+| Unpacking       | `a, b = 1, 2`                   |        |
+| Star Unpacking  | `a, *b = [1, 2, 3]`             |        |
+| Exception       | `raise/try..catch`              |        |
+| Dynamic Code    | `eval()/exec()`                 |        |
+| Reflection      | `hasattr()/getattr()/setattr()` |        |
+| Import          | `import/from..import`           |        |
+| Context Block   | `with <expr> as <id>:`          |        |
+| Type Annotation | `def  f(a:int, b:float=1)`      |        |
+| Generator       | `yield i`                       |        |
+| Decorator       | `@cache`                        |        |
 
 ## Contribution
 

+ 20 - 20
README_zh.md

@@ -27,7 +27,7 @@ pkpy 支持任何拥有 C++17 编译器的平台。
 + Android 64-bit / 32-bit
 + iOS 64-bit
 + Emscripten 32-bit
-+ Raspberry Pi 64-bit
++ Raspberry Pi OS 64-bit
 
 ## 快速上手
 
@@ -76,25 +76,25 @@ int main(){
 
 | 特性         | 示例                            | 支持 |
 | ------------ | ------------------------------- | ---- |
-| 分支         | `if..else..elif`                | YES  |
-| 循环         | `for/while/break/continue`      | YES  |
-| 函数         | `def f(x,*args,y=1):`           | YES  |
-| 类与继承     | `class A(B):`                   | YES  |
-| 列表         | `[1, 2, 'a']`                   | YES  |
-| 列表生成式   | `[i for i in range(5)]`         | YES  |
-| 切片         | `a[1:2], a[:2], a[1:]`          | YES  |
-| 元组         | `(1, 2, 'a')`                   | YES  |
-| 字典         | `{'a': 1, 'b': 2}`              | YES  |
-| 格式化字符串 | `f'value is {x}'`               | YES  |
-| 序列解包     | `a, b = 1, 2`                   | YES  |
-| 异常         | `raise/try..catch`              | YES  |
-| 动态分发     | `eval()/exec()`                 | YES  |
-| 反射         | `hasattr()/getattr()/setattr()` | YES  |
-| 导入模块     | `import/from..import`           | YES  |
-| 上下文管理器 | `with <expr> as <id>:`          | YES  |
-| 类型标注 | `def  f(a:int, b:float=1)`      | YES       |
-| 生成器       | `yield i`                       | YES       |
-| 装饰器 | `@cache` | YES |
+| 分支         | `if..else..elif`                |  |
+| 循环         | `for/while/break/continue`      |  |
+| 函数         | `def f(x,*args,y=1):`           |  |
+| 类与继承     | `class A(B):`                   |  |
+| 列表         | `[1, 2, 'a']`                   |  |
+| 列表生成式   | `[i for i in range(5)]`         |  |
+| 切片         | `a[1:2], a[:2], a[1:]`          |  |
+| 元组         | `(1, 2, 'a')`                   |  |
+| 字典         | `{'a': 1, 'b': 2}`              |  |
+| 格式化字符串 | `f'value is {x}'`               |  |
+| 序列解包     | `a, b = 1, 2`                   |  |
+| 异常         | `raise/try..catch`              |  |
+| 动态分发     | `eval()/exec()`                 |  |
+| 反射         | `hasattr()/getattr()/setattr()` |  |
+| 导入模块     | `import/from..import`           |  |
+| 上下文管理器 | `with <expr> as <id>:`          |  |
+| 类型标注     | `def  f(a:int, b:float=1)`      | ✅ |
+| 生成器       | `yield i`                       |  |
+| 装饰器       | `@cache`                        | ✅ |
 
 ## 参考
 

+ 1 - 1
build.sh

@@ -10,7 +10,7 @@ fi
 # Check if clang++ is installed
 if ! type -P clang++ >/dev/null 2>&1; then
     echo "clang++ is required and not installed. Kindly install it."
-    echo "Run: sudo apt-get install libc++-dev libc++abi-dev clang++"
+    echo "Run: sudo apt-get install libc++-dev libc++abi-dev clang"
     exit 1
 fi
 

+ 1 - 1
docs/bindings.md

@@ -160,4 +160,4 @@ For example, `vm->bind__add__` is preferred over `vm->bind_method<1>(type, "__ad
 
 ### Further reading
 
-See [linalg.h](https://github.com/blueloveTH/pocketpy/blob/main/src/linalg.h) for a complete example used by `linalg` module.
+See [linalg.h](https://github.com/blueloveTH/pocketpy/blob/main/src/random.cpp) for a complete example used by `random` module.

+ 1 - 1
docs/index.md

@@ -39,7 +39,7 @@ These platforms are officially tested.
 + Android 64-bit / 32-bit
 + iOS 64-bit
 + Emscripten 32-bit
-+ Raspberry Pi 64-bit
++ Raspberry Pi OS 64-bit
 
 ## Sponsor me
 

+ 1 - 0
docs/quick-start/installation.md

@@ -43,6 +43,7 @@ To compile it with your project, these flags must be set:
 
 + `--std=c++17` flag must be set
 + Exception must be enabled
++ For MSVC, `/utf-8` flag must be set
 
 For emscripten, you must enable exceptions to make pocketpy work properly.
 See https://emscripten.org/docs/porting/exceptions.html.

+ 109 - 8
python/builtins.py

@@ -96,14 +96,115 @@ def sorted(iterable, reverse=False, key=None):
     return a
 
 ##### str #####
-def __f(self, *args):
-    if '{}' in self:
-        for i in range(len(args)):
-            self = self.replace('{}', str(args[i]), 1)
-    else:
-        for i in range(len(args)):
-            self = self.replace('{'+str(i)+'}', str(args[i]))
-    return self
+def __f(self: str, *args, **kwargs) -> str:
+    def tokenizeString(s: str):
+        tokens = []
+        L, R = 0,0
+        
+        mode = None
+        curArg = 0
+        # lookingForKword = False
+        
+        while(R<len(s)):
+            curChar = s[R]
+            nextChar = s[R+1] if R+1<len(s) else ''
+            
+            # Invalid case 1: stray '}' encountered, example: "ABCD EFGH {name} IJKL}", "Hello {vv}}", "HELLO {0} WORLD}"
+            if curChar == '}' and nextChar != '}':
+                raise ValueError("Single '}' encountered in format string")        
+            
+            # Valid Case 1: Escaping case, we escape "{{ or "}}" to be "{" or "}", example: "{{}}", "{{My Name is {0}}}"
+            if (curChar == '{' and nextChar == '{') or (curChar == '}' and nextChar == '}'):
+                
+                if (L<R): # Valid Case 1.1: make sure we are not adding empty string
+                    tokens.append(s[L:R]) # add the string before the escape
+                
+                
+                tokens.append(curChar) # Valid Case 1.2: add the escape char
+                L = R+2 # move the left pointer to the next char
+                R = R+2 # move the right pointer to the next char
+                continue
+            
+            # Valid Case 2: Regular command line arg case: example:  "ABCD EFGH {} IJKL", "{}", "HELLO {} WORLD"
+            elif curChar == '{' and nextChar == '}':
+                if mode is not None and mode != 'auto':
+                    # Invalid case 2: mixing automatic and manual field specifications -- example: "ABCD EFGH {name} IJKL {}", "Hello {vv} {}", "HELLO {0} WORLD {}" 
+                    raise ValueError("Cannot switch from manual field numbering to automatic field specification")
+                
+                mode = 'auto'
+                if(L<R): # Valid Case 2.1: make sure we are not adding empty string
+                    tokens.append(s[L:R]) # add the string before the special marker for the arg
+                
+                tokens.append("{"+str(curArg)+"}") # Valid Case 2.2: add the special marker for the arg
+                curArg+=1 # increment the arg position, this will be used for referencing the arg later
+                
+                L = R+2 # move the left pointer to the next char
+                R = R+2 # move the right pointer to the next char
+                continue
+            
+            # Valid Case 3: Key-word arg case: example: "ABCD EFGH {name} IJKL", "Hello {vv}", "HELLO {name} WORLD"
+            elif (curChar == '{'):
+                
+                if mode is not None and mode != 'manual':
+                    # # Invalid case 2: mixing automatic and manual field specifications -- example: "ABCD EFGH {} IJKL {name}", "Hello {} {1}", "HELLO {} WORLD {name}"
+                    raise ValueError("Cannot switch from automatic field specification to manual field numbering")
+                
+                mode = 'manual'
+                
+                if(L<R): # Valid case 3.1: make sure we are not adding empty string
+                    tokens.append(s[L:R]) # add the string before the special marker for the arg
+                
+                # We look for the end of the keyword          
+                kwL = R # Keyword left pointer
+                kwR = R+1 # Keyword right pointer
+                while(kwR<len(s) and s[kwR]!='}'):
+                    if s[kwR] == '{': # Invalid case 3: stray '{' encountered, example: "ABCD EFGH {n{ame} IJKL {", "Hello {vv{}}", "HELLO {0} WOR{LD}"
+                        raise ValueError("Unexpected '{' in field name")
+                    kwR += 1
+                
+                # Valid case 3.2: We have successfully found the end of the keyword
+                if kwR<len(s) and s[kwR] == '}':
+                    tokens.append(s[kwL:kwR+1]) # add the special marker for the arg
+                    L = kwR+1
+                    R = kwR+1
+                    
+                # Invalid case 4: We didn't find the end of the keyword, throw error
+                else:
+                    raise ValueError("Expected '}' before end of string")
+                continue
+            
+            R = R+1
+        
+        
+        # Valid case 4: We have reached the end of the string, add the remaining string to the tokens 
+        if L<R:
+            tokens.append(s[L:R])
+                
+        # print(tokens)
+        return tokens
+
+    tokens = tokenizeString(self)
+    argMap = {}
+    for i, a in enumerate(args):
+        argMap[str(i)] = a
+    final_tokens = []
+    for t in tokens:
+        if t[0] == '{' and t[-1] == '}':
+            key = t[1:-1]
+            argMapVal = argMap.get(key, None)
+            kwargsVal = kwargs.get(key, None)
+                                    
+            if argMapVal is None and kwargsVal is None:
+                raise ValueError("No arg found for token: "+t)
+            elif argMapVal is not None:
+                final_tokens.append(str(argMapVal))
+            else:
+                final_tokens.append(str(kwargsVal))
+        else:
+            final_tokens.append(t)
+    
+    return ''.join(final_tokens)
+
 str.format = __f
 
 def __f(self, chars=None):

+ 0 - 6
src/ceval.cpp

@@ -760,12 +760,6 @@ __NEXT_STEP:;
             PK_UNUSED(e);
             PyObject* obj = POPX();
             Exception& _e = CAST(Exception&, obj);
-            int actual_ip = frame->_ip;
-            if(_e._ip_on_error >= 0 && _e._code_on_error == (void*)frame->co) actual_ip = _e._ip_on_error;
-            int current_line = frame->co->lines[actual_ip];         // current line
-            auto current_f_name = frame->co->name.sv();             // current function name
-            if(frame->_callable == nullptr) current_f_name = "";    // not in a function
-            _e.st_push(frame->co->src->snapshot(current_line, nullptr, current_f_name));
             _pop_frame();
             if(callstack.empty()){
 #if PK_DEBUG_FULL_EXCEPTION

+ 13 - 5
src/vm.cpp

@@ -1082,13 +1082,21 @@ void VM::_error(Exception e){
 }
 
 void VM::_raise(bool re_raise){
-    Frame* top = top_frame().get();
+    Frame* frame = top_frame().get();
+    Exception& e = PK_OBJ_GET(Exception, s_data.top());
     if(!re_raise){
-        Exception& e = PK_OBJ_GET(Exception, s_data.top());
-        e._ip_on_error = top->_ip;
-        e._code_on_error = (void*)top->co;
+        e._ip_on_error = frame->_ip;
+        e._code_on_error = (void*)frame->co;
     }
-    bool ok = top->jump_to_exception_handler();
+    bool ok = frame->jump_to_exception_handler();
+
+    int actual_ip = frame->_ip;
+    if(e._ip_on_error >= 0 && e._code_on_error == (void*)frame->co) actual_ip = e._ip_on_error;
+    int current_line = frame->co->lines[actual_ip];         // current line
+    auto current_f_name = frame->co->name.sv();             // current function name
+    if(frame->_callable == nullptr) current_f_name = "";    // not in a function
+    e.st_push(frame->co->src->snapshot(current_line, nullptr, current_f_name));
+
     if(ok) throw HandledException();
     else throw UnhandledException();
 }

+ 13 - 0
tests/04_str.py

@@ -97,6 +97,19 @@ assert "{0} {1} {2}".format("I", "love", "Python") == "I love Python"
 assert "{2} {1} {0}".format("I", "love", "Python") == "Python love I"
 assert "{0}{1}{0}".format("abra", "cad") == "abracadabra"
 
+assert "{k}={v}".format(k="key", v="value") == "key=value"
+assert "{k}={k}".format(k="key") == "key=key"
+assert "{0}={1}".format('{0}', '{1}') == "{0}={1}"
+assert "{{{0}}}".format(1) == "{1}"
+assert "{0}{1}{1}".format(1, 2, 3) == "122"
+try:
+    "{0}={1}}".format(1, 2)
+    exit(1)
+except ValueError:
+    pass
+assert "{{{}xxx{}x}}".format(1, 2) == "{1xxx2x}"
+assert "{{abc}}".format() == "{abc}"
+
 # 3rd slice
 a = "Hello, World!"
 assert a[::-1] == "!dlroW ,olleH"

+ 9 - 0
tests/80_json.py

@@ -16,6 +16,15 @@ a = {
 
 import json
 
+assert json.loads("1") == 1
+assert json.loads('"1"') == "1"
+assert json.loads("0.0") == 0.0
+assert json.loads("[1, 2]") == [1, 2]
+assert json.loads("null") == None
+assert json.loads("true") == True
+assert json.loads("false") == False
+assert json.loads("{}") == {}
+
 _j = json.dumps(a)
 _a = json.loads(_j)
 

+ 6 - 2
tests/80_traceback.py

@@ -6,5 +6,9 @@ try:
 except KeyError:
     s = traceback.format_exc()
 
-assert s == r'''Traceback (most recent call last):
-KeyError: 6'''
+ok = s == '''Traceback (most recent call last):
+  File "80_traceback.py", line 5
+    b = a[6]
+KeyError: 6'''
+
+assert ok, s