diff --git a/.github/workflows/website.yml b/.github/workflows/website.yml new file mode 100644 index 00000000..b5f86506 --- /dev/null +++ b/.github/workflows/website.yml @@ -0,0 +1,35 @@ +name: website + +on: + push: + branches: [ main ] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + ################################################### + - uses: actions/setup-node@v3.1.1 + - name: Retype build + run: | + cd docs + npm install retypeapp --global + retype build + ################################################### + - name: Setup emsdk + uses: mymindstorm/setup-emsdk@v12 + with: + version: 3.1.25 + actions-cache-folder: 'emsdk-cache' + - name: Compile + run: | + bash build_web.sh + mv web docs/.retype/static + ################################################### + - uses: crazy-max/ghaction-github-pages@v3 + with: + target_branch: gh-pages + build_dir: docs/.retype + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/CMakeLists.txt b/CMakeLists.txt index d4714e86..565d09b3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,11 +20,13 @@ endif() if(MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR- /EHsc /utf-8 /O2") else() - find_program(CLANGPP clang++) - if(CLANGPP) - message(STATUS "Using clang with libc++") - set(CMAKE_CXX_COMPILER ${CLANGPP}) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + if (NOT EMSCRIPTEN) + find_program(CLANGPP clang++) + if(CLANGPP) + message(STATUS "Using clang with libc++") + set(CMAKE_CXX_COMPILER ${CLANGPP}) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++") + endif() endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -fexceptions -O2") diff --git a/build_web.sh b/build_web.sh new file mode 100644 index 00000000..bfc5738a --- /dev/null +++ b/build_web.sh @@ -0,0 +1,7 @@ +python3 prebuild.py + +rm -rf web/lib +mkdir web/lib + +SRC=$(find src/ -name "*.cpp") +em++ $SRC -Iinclude/ -fno-rtti -fexceptions -O3 -sEXPORTED_FUNCTIONS=_pkpy_new_repl,_pkpy_repl_input,_pkpy_new_vm -sEXPORTED_RUNTIME_METHODS=ccall -o web/lib/pocketpy.js \ No newline at end of file diff --git a/include/pocketpy/common.h b/include/pocketpy/common.h index 743b9bb9..235d44f4 100644 --- a/include/pocketpy/common.h +++ b/include/pocketpy/common.h @@ -20,7 +20,7 @@ #include #include -#define PK_VERSION "1.0.8" +#define PK_VERSION "1.0.9" #include "config.h" diff --git a/include/pocketpy/frame.h b/include/pocketpy/frame.h index 5d84ce5f..95df9bcc 100644 --- a/include/pocketpy/frame.h +++ b/include/pocketpy/frame.h @@ -22,10 +22,7 @@ struct FastLocals{ FastLocals(const CodeObject* co, PyObject** a): varnames_inv(&co->varnames_inv), a(a) {} FastLocals(const FastLocals& other): varnames_inv(other.varnames_inv), a(other.a) {} - PyObject* try_get(StrName name); - bool contains(StrName name); - void erase(StrName name); - bool try_set(StrName name, PyObject* value); + PyObject** try_get_name(StrName name); NameDict_ to_namedict(); }; diff --git a/include/pocketpy/vm.h b/include/pocketpy/vm.h index d3383141..a7e0e229 100644 --- a/include/pocketpy/vm.h +++ b/include/pocketpy/vm.h @@ -351,6 +351,7 @@ public: void IndexError(const Str& msg){ _error("IndexError", msg); } void ValueError(const Str& msg){ _error("ValueError", msg); } void NameError(StrName name){ _error("NameError", fmt("name ", name.escape() + " is not defined")); } + void UnboundLocalError(StrName name){ _error("UnboundLocalError", fmt("local variable ", name.escape() + " referenced before assignment")); } void KeyError(PyObject* obj){ _error("KeyError", PK_OBJ_GET(Str, py_repr(obj))); } void BinaryOptError(const char* op) { TypeError(fmt("unsupported operand type(s) for ", op)); } diff --git a/src/ceval.cpp b/src/ceval.cpp index 5d0eda8f..f8ad4a4d 100644 --- a/src/ceval.cpp +++ b/src/ceval.cpp @@ -111,11 +111,15 @@ __NEXT_STEP:; if(_0 == PY_NULL) vm->NameError(co->varnames[byte.arg]); PUSH(_0); } DISPATCH(); - TARGET(LOAD_NAME) + TARGET(LOAD_NAME) { heap._auto_collect(); _name = StrName(byte.arg); - _0 = frame->_locals.try_get(_name); - if(_0 != nullptr) { PUSH(_0); DISPATCH(); } + PyObject** slot = frame->_locals.try_get_name(_name); + if(slot != nullptr) { + if(*slot == PY_NULL) vm->UnboundLocalError(_name); + PUSH(*slot); + DISPATCH(); + } _0 = frame->f_closure_try_get(_name); if(_0 != nullptr) { PUSH(_0); DISPATCH(); } _0 = frame->f_globals().try_get(_name); @@ -123,7 +127,7 @@ __NEXT_STEP:; _0 = vm->builtins->attr().try_get(_name); if(_0 != nullptr) { PUSH(_0); DISPATCH(); } vm->NameError(_name); - DISPATCH(); + } DISPATCH(); TARGET(LOAD_NONLOCAL) { heap._auto_collect(); _name = StrName(byte.arg); @@ -164,16 +168,17 @@ __NEXT_STEP:; TARGET(STORE_FAST) frame->_locals[byte.arg] = POPX(); DISPATCH(); - TARGET(STORE_NAME) + TARGET(STORE_NAME){ _name = StrName(byte.arg); _0 = POPX(); if(frame->_callable != nullptr){ - bool ok = frame->_locals.try_set(_name, _0); - if(!ok) vm->NameError(_name); + PyObject** slot = frame->_locals.try_get_name(_name); + if(slot == nullptr) vm->UnboundLocalError(_name); + *slot = _0; }else{ frame->f_globals().set(_name, _0); } - DISPATCH(); + } DISPATCH(); TARGET(STORE_GLOBAL) frame->f_globals().set(StrName(byte.arg), POPX()); DISPATCH(); @@ -202,8 +207,9 @@ __NEXT_STEP:; TARGET(DELETE_NAME) _name = StrName(byte.arg); if(frame->_callable != nullptr){ - if(!frame->_locals.contains(_name)) vm->NameError(_name); - frame->_locals.erase(_name); + PyObject** slot = frame->_locals.try_get_name(_name); + if(slot == nullptr) vm->UnboundLocalError(_name); + *slot = PY_NULL; }else{ if(!frame->f_globals().contains(_name)) vm->NameError(_name); frame->f_globals().erase(_name); diff --git a/src/frame.cpp b/src/frame.cpp index 053ab21e..0b4cea45 100644 --- a/src/frame.cpp +++ b/src/frame.cpp @@ -1,28 +1,10 @@ #include "pocketpy/frame.h" namespace pkpy{ - - PyObject* FastLocals::try_get(StrName name){ + PyObject** FastLocals::try_get_name(StrName name){ int index = varnames_inv->try_get(name); if(index == -1) return nullptr; - return a[index]; - } - - bool FastLocals::contains(StrName name){ - return varnames_inv->contains(name); - } - - void FastLocals::erase(StrName name){ - int index = varnames_inv->try_get(name); - if(index == -1) FATAL_ERROR(); - a[index] = nullptr; - } - - bool FastLocals::try_set(StrName name, PyObject* value){ - int index = varnames_inv->try_get(name); - if(index == -1) return false; - a[index] = value; - return true; + return &a[index]; } NameDict_ FastLocals::to_namedict(){ diff --git a/src/vm.cpp b/src/vm.cpp index 269832f6..2613f1ca 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -398,7 +398,11 @@ PyObject* VM::format(Str spec, PyObject* obj){ int width, precision; try{ if(dot >= 0){ - width = Number::stoi(spec.substr(0, dot).str()); + if(dot == 0){ + width = -1; + }else{ + width = Number::stoi(spec.substr(0, dot).str()); + } precision = Number::stoi(spec.substr(dot+1).str()); }else{ width = Number::stoi(spec.str()); @@ -424,7 +428,7 @@ PyObject* VM::format(Str spec, PyObject* obj){ }else{ ret = CAST(Str&, py_str(obj)); } - if(width > ret.length()){ + if(width != -1 && width > ret.length()){ int pad = width - ret.length(); std::string padding(pad, pad_c); if(align == '>') ret = padding.c_str() + ret; @@ -446,7 +450,7 @@ PyObject* VM::new_module(StrName name) { static std::string _opcode_argstr(VM* vm, Bytecode byte, const CodeObject* co){ std::string argStr = byte.arg == -1 ? "" : std::to_string(byte.arg); switch(byte.op){ - case OP_LOAD_CONST: + case OP_LOAD_CONST: case OP_FORMAT_STRING: if(vm != nullptr){ argStr += fmt(" (", CAST(Str, vm->py_repr(co->consts[byte.arg])), ")"); } diff --git a/tests/25_rawstring.py b/tests/25_rawstring.py index 07cd31bd..6c889892 100644 --- a/tests/25_rawstring.py +++ b/tests/25_rawstring.py @@ -42,6 +42,8 @@ assert f'{a:010}' == '0000000010' assert f'{a:010d}' == '0000000010' assert f'{a:010f}' == '010.000000' assert f'{a:010.2f}' == '0000010.00' +assert f'{a:.2f}' == '10.00' +assert f'{a:.5f}' == '10.00000' b = '123' assert f'{b:10}' == '123 ' diff --git a/tests/99_bugs.py b/tests/99_bugs.py index f058b418..f69d967d 100644 --- a/tests/99_bugs.py +++ b/tests/99_bugs.py @@ -56,4 +56,19 @@ def f(): f() # class A: a=b=1 -# class A: a, b = 1, 2 \ No newline at end of file +# class A: a, b = 1, 2 + +bmi = 0.0 + +def test(a): + if a: + bmi = 1.4 + return f'{bmi:.2f}' + +assert test(1) == '1.40' + +try: + assert test(0) == '0.00' + exit(1) +except UnboundLocalError: + pass \ No newline at end of file