diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 00000000..5fb52e91 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,21 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: pocketpy +message: >- + If you use this software, please cite it using the + metadata from this file. +type: software +authors: + - name: blueloveTH + website: 'https://pocketpy.dev' +repository-code: 'https://github.com/pocketpy/pocketpy' +url: 'https://pocketpy.dev' +abstract: >- + A portable python 3.x interpreter in modern C for game scripting. +keywords: + - python + - c + - interpreter +license: MIT \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index d92008e1..ac483d90 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,9 +34,10 @@ else() add_definitions(-DNDEBUG) endif() - # disable -Wshorten-64-to-32 for apple if(APPLE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-shorten-64-to-32") + else() + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-pointer-to-int-cast -Wno-int-to-pointer-cast") endif() if(PK_ENABLE_DETERMINISM) diff --git a/README.md b/README.md index f433a131..b0e7d9da 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Featured|HelloGitHub + +Ask DeepWiki

pocketpy is a portable Python 3.x interpreter, written in C11. diff --git a/docs/modules/math.md b/docs/modules/math.md index 421edbef..f84571db 100644 --- a/docs/modules/math.md +++ b/docs/modules/math.md @@ -123,11 +123,14 @@ Convert angle `x` from radians to degrees. Convert angle `x` from degrees to radians. - ### `math.modf(x)` Return the fractional and integer parts of `x`. Both results carry the sign of `x` and are floats. +### `math.copysign(x, y)` + +Return a float with the magnitude (absolute value) of `x` but the sign of `y`. + ### `math.factorial(x)` -Return `x` factorial as an integer. \ No newline at end of file +Return `x` factorial as an integer. diff --git a/include/pocketpy/interpreter/vm.h b/include/pocketpy/interpreter/vm.h index 8cde83d4..b231cfc4 100644 --- a/include/pocketpy/interpreter/vm.h +++ b/include/pocketpy/interpreter/vm.h @@ -125,6 +125,8 @@ bool pk_stack_binaryop(VM* self, py_Name op, py_Name rop); void pk_print_stack(VM* self, py_Frame* frame, Bytecode byte); +bool pk_format_object(VM* self, py_Ref val, c11_sv spec); + // type registration void pk_object__register(); void pk_number__register(); diff --git a/python/builtins.py b/python/builtins.py index 84ab2a23..d19dd307 100644 --- a/python/builtins.py +++ b/python/builtins.py @@ -81,119 +81,6 @@ def sorted(iterable, key=None, reverse=False): a.sort(key=key, reverse=reverse) return a -##### str ##### -def __format_string(self: str, *args, **kwargs) -> str: - def tokenizeString(s: str): - tokens = [] - L, R = 0,0 - - mode = None - curArg = 0 - # lookingForKword = False - - while(R 0.5) { + *floordiv += 1.0; + } + } + else { + /* div is zero - get the same sign as the true quotient */ + *floordiv = copysign(0.0, vx / wx); /* zero w/ sign of vx/wx */ + } +} + static bool int__floordiv__(int argc, py_Ref argv) { PY_CHECK_ARGC(2); py_i64 lhs = py_toint(&argv[0]); @@ -175,13 +213,45 @@ static bool int__mod__(int argc, py_Ref argv) { return true; } +static bool float__floordiv__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + py_f64 lhs = py_tofloat(&argv[0]); + py_f64 rhs; + if(try_castfloat(&argv[1], &rhs)) { + if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), q); + return true; + } + py_newnotimplemented(py_retval()); + return true; +} + +static bool float__rfloordiv__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + py_f64 rhs = py_tofloat(&argv[0]); + py_f64 lhs; + if(try_castfloat(&argv[1], &lhs)) { + if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), q); + return true; + } + py_newnotimplemented(py_retval()); + return true; +} + static bool float__mod__(int argc, py_Ref argv) { PY_CHECK_ARGC(2); py_f64 lhs = py_tofloat(&argv[0]); py_f64 rhs; if(try_castfloat(&argv[1], &rhs)) { if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); - py_newfloat(py_retval(), fmod(lhs, rhs)); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), r); return true; } py_newnotimplemented(py_retval()); @@ -194,13 +264,31 @@ static bool float__rmod__(int argc, py_Ref argv) { py_f64 lhs; if(try_castfloat(&argv[1], &lhs)) { if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); - py_newfloat(py_retval(), fmod(lhs, rhs)); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_newfloat(py_retval(), r); return true; } py_newnotimplemented(py_retval()); return true; } +static bool float__divmod__(int argc, py_Ref argv) { + PY_CHECK_ARGC(2); + py_f64 lhs = py_tofloat(&argv[0]); + py_f64 rhs; + if(try_castfloat(&argv[1], &rhs)) { + if(rhs == 0.0) return ZeroDivisionError("float modulo by zero"); + double q, r; + cpy11__float_div_mod(lhs, rhs, &q, &r); + py_Ref p = py_newtuple(py_retval(), 2); + py_newfloat(&p[0], q); + py_newfloat(&p[1], r); + return true; + } + return TypeError("divmod() expects int or float as divisor"); +} + static bool int__divmod__(int argc, py_Ref argv) { PY_CHECK_ARGC(2); PY_CHECK_ARG_TYPE(1, tp_int); @@ -535,8 +623,11 @@ void pk_number__register() { py_bindmagic(tp_int, __divmod__, int__divmod__); // fmod + py_bindmagic(tp_float, __floordiv__, float__floordiv__); + py_bindmagic(tp_float, __rfloordiv__, float__rfloordiv__); py_bindmagic(tp_float, __mod__, float__mod__); py_bindmagic(tp_float, __rmod__, float__rmod__); + py_bindmagic(tp_float, __divmod__, float__divmod__); // int.__invert__ & int. py_bindmagic(tp_int, __invert__, int__invert__); diff --git a/src/bindings/py_str.c b/src/bindings/py_str.c index d142a521..d651a370 100644 --- a/src/bindings/py_str.c +++ b/src/bindings/py_str.c @@ -1,9 +1,11 @@ #include "pocketpy/common/str.h" +#include "pocketpy/objects/base.h" #include "pocketpy/pocketpy.h" #include "pocketpy/objects/object.h" #include "pocketpy/interpreter/vm.h" #include "pocketpy/common/sstream.h" +#include c11_string* pk_tostr(py_Ref self) { assert(self->type == tp_str); @@ -394,6 +396,99 @@ static bool str_encode(int argc, py_Ref argv) { return true; } +static bool str_format(int argc, py_Ref argv) { + c11_sv self = py_tosv(argv); + py_Ref args = argv + 1; + int64_t auto_field_index = -1; + bool manual_field_used = false; + const char* p_begin = self.data; + const char* p_end = self.data + self.size; + const char* p = p_begin; + c11_sbuf buf; + c11_sbuf__ctor(&buf); + while(p < p_end) { + if(*p == '{') { + if((p + 1) < p_end && p[1] == '{') { + // '{{' -> '{' + c11_sbuf__write_char(&buf, '{'); + p += 2; + } else { + if((p + 1) >= p_end) { + return ValueError("single '{' encountered in format string"); + } + p++; + // parse field + c11_sv field = {p, 0}; + while(p < p_end && *p != '}' && *p != ':') { + p++; + } + if(p < p_end) field.size = p - field.data; + // parse spec + c11_sv spec = {p, 0}; + if(*p == ':') { + while(p < p_end && *p != '}') { + p++; + } + if(p < p_end) spec.size = p - spec.data; + } + if(p < p_end) { + c11__rtassert(*p == '}'); + } else { + return ValueError("expected '}' before end of string"); + } + // parse auto field + int64_t arg_index; + if(field.size > 0) { // {0} + if(auto_field_index >= 0) { + return ValueError( + "cannot switch from automatic field numbering to manual field specification"); + } + IntParsingResult res = c11__parse_uint(field, &arg_index, 10); + if(res != IntParsing_SUCCESS) { + return ValueError("only integer field name is supported"); + } + manual_field_used = true; + } else { // {} + if(manual_field_used) { + return ValueError( + "cannot switch from manual field specification to automatic field numbering"); + } + auto_field_index++; + arg_index = auto_field_index; + } + // do format + if(arg_index < 0 || arg_index >= (argc - 1)) { + return IndexError("replacement index %i out of range for positional args tuple", + arg_index); + } + bool ok = pk_format_object(pk_current_vm, &args[arg_index], spec); + if(!ok) { + c11_sbuf__dtor(&buf); + return false; + } + // append to buf + c11__rtassert(py_isstr(py_retval())); + c11_sv formatted = py_tosv(py_retval()); + c11_sbuf__write_sv(&buf, formatted); + p++; // skip '}' + } + } else if(*p == '}') { + if((p + 1) < p_end && p[1] == '}') { + // '}}' -> '}' + c11_sbuf__write_char(&buf, '}'); + p += 2; + } else { + return ValueError("single '}' encountered in format string"); + } + } else { + c11_sbuf__write_char(&buf, *p); + p++; + } + } + c11_sbuf__py_submit(&buf, py_retval()); + return true; +} + py_Type pk_str__register() { py_Type type = pk_newtype("str", tp_object, NULL, NULL, false, true); // no need to dtor because the memory is controlled by the object @@ -434,6 +529,7 @@ py_Type pk_str__register() { py_bindmethod(tp_str, "find", str_find); py_bindmethod(tp_str, "index", str_index); py_bindmethod(tp_str, "encode", str_encode); + py_bindmethod(tp_str, "format", str_format); return type; } diff --git a/src/common/_generated.c b/src/common/_generated.c index 75316f7b..316daab7 100644 --- a/src/common/_generated.c +++ b/src/common/_generated.c @@ -2,7 +2,7 @@ #include "pocketpy/common/_generated.h" #include const char kPythonLibs_bisect[] = "\"\"\"Bisection algorithms.\"\"\"\n\ndef insort_right(a, x, lo=0, hi=None):\n \"\"\"Insert item x in list a, and keep it sorted assuming a is sorted.\n\n If x is already in a, insert it to the right of the rightmost x.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n lo = bisect_right(a, x, lo, hi)\n a.insert(lo, x)\n\ndef bisect_right(a, x, lo=0, hi=None):\n \"\"\"Return the index where to insert item x in list a, assuming a is sorted.\n\n The return value i is such that all e in a[:i] have e <= x, and all e in\n a[i:] have e > x. So if x already appears in the list, a.insert(x) will\n insert just after the rightmost x already there.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n if lo < 0:\n raise ValueError('lo must be non-negative')\n if hi is None:\n hi = len(a)\n while lo < hi:\n mid = (lo+hi)//2\n if x < a[mid]: hi = mid\n else: lo = mid+1\n return lo\n\ndef insort_left(a, x, lo=0, hi=None):\n \"\"\"Insert item x in list a, and keep it sorted assuming a is sorted.\n\n If x is already in a, insert it to the left of the leftmost x.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n lo = bisect_left(a, x, lo, hi)\n a.insert(lo, x)\n\n\ndef bisect_left(a, x, lo=0, hi=None):\n \"\"\"Return the index where to insert item x in list a, assuming a is sorted.\n\n The return value i is such that all e in a[:i] have e < x, and all e in\n a[i:] have e >= x. So if x already appears in the list, a.insert(x) will\n insert just before the leftmost x already there.\n\n Optional args lo (default 0) and hi (default len(a)) bound the\n slice of a to be searched.\n \"\"\"\n\n if lo < 0:\n raise ValueError('lo must be non-negative')\n if hi is None:\n hi = len(a)\n while lo < hi:\n mid = (lo+hi)//2\n if a[mid] < x: lo = mid+1\n else: hi = mid\n return lo\n\n# Create aliases\nbisect = bisect_right\ninsort = insort_right\n"; -const char kPythonLibs_builtins[] = "def all(iterable):\n for i in iterable:\n if not i:\n return False\n return True\n\ndef any(iterable):\n for i in iterable:\n if i:\n return True\n return False\n\ndef enumerate(iterable, start=0):\n n = start\n for elem in iterable:\n yield n, elem\n n += 1\n\ndef __minmax_reduce(op, args):\n if len(args) == 2: # min(1, 2)\n return args[0] if op(args[0], args[1]) else args[1]\n if len(args) == 0: # min()\n raise TypeError('expected 1 arguments, got 0')\n if len(args) == 1: # min([1, 2, 3, 4]) -> min(1, 2, 3, 4)\n args = args[0]\n args = iter(args)\n try:\n res = next(args)\n except StopIteration:\n raise ValueError('args is an empty sequence')\n while True:\n try:\n i = next(args)\n except StopIteration:\n break\n if op(i, res):\n res = i\n return res\n\ndef min(*args, key=None):\n key = key or (lambda x: x)\n return __minmax_reduce(lambda x,y: key(x)key(y), args)\n\ndef sum(iterable):\n res = 0\n for i in iterable:\n res += i\n return res\n\ndef map(f, iterable):\n for i in iterable:\n yield f(i)\n\ndef filter(f, iterable):\n for i in iterable:\n if f(i):\n yield i\n\ndef zip(a, b):\n a = iter(a)\n b = iter(b)\n while True:\n try:\n ai = next(a)\n bi = next(b)\n except StopIteration:\n break\n yield ai, bi\n\ndef reversed(iterable):\n a = list(iterable)\n a.reverse()\n return a\n\ndef sorted(iterable, key=None, reverse=False):\n a = list(iterable)\n a.sort(key=key, reverse=reverse)\n return a\n\n##### str #####\ndef __format_string(self: str, *args, **kwargs) -> str:\n def tokenizeString(s: str):\n tokens = []\n L, R = 0,0\n \n mode = None\n curArg = 0\n # lookingForKword = False\n \n while(R list[str]:\n tp_module = type(__import__('math'))\n if isinstance(obj, tp_module):\n return [k for k, _ in obj.__dict__.items()]\n names = set()\n if not isinstance(obj, type):\n obj_d = obj.__dict__\n if obj_d is not None:\n names.update([k for k, _ in obj_d.items()])\n cls = type(obj)\n else:\n cls = obj\n while cls is not None:\n names.update([k for k, _ in cls.__dict__.items()])\n cls = cls.__base__\n return sorted(list(names))\n\nclass set:\n def __init__(self, iterable=None):\n iterable = iterable or []\n self._a = {}\n self.update(iterable)\n\n def add(self, elem):\n self._a[elem] = None\n \n def discard(self, elem):\n self._a.pop(elem, None)\n\n def remove(self, elem):\n del self._a[elem]\n \n def clear(self):\n self._a.clear()\n\n def update(self, other):\n for elem in other:\n self.add(elem)\n\n def __len__(self):\n return len(self._a)\n \n def copy(self):\n return set(self._a.keys())\n \n def __and__(self, other):\n return {elem for elem in self if elem in other}\n\n def __sub__(self, other):\n return {elem for elem in self if elem not in other}\n \n def __or__(self, other):\n ret = self.copy()\n ret.update(other)\n return ret\n\n def __xor__(self, other): \n _0 = self - other\n _1 = other - self\n return _0 | _1\n\n def union(self, other):\n return self | other\n\n def intersection(self, other):\n return self & other\n\n def difference(self, other):\n return self - other\n\n def symmetric_difference(self, other): \n return self ^ other\n \n def __eq__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) == 0\n \n def __ne__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) != 0\n\n def isdisjoint(self, other):\n return len(self & other) == 0\n \n def issubset(self, other):\n return len(self - other) == 0\n \n def issuperset(self, other):\n return len(other - self) == 0\n\n def __contains__(self, elem):\n return elem in self._a\n \n def __repr__(self):\n if len(self) == 0:\n return 'set()'\n return '{'+ ', '.join([repr(i) for i in self._a.keys()]) + '}'\n \n def __iter__(self):\n return iter(self._a.keys())"; +const char kPythonLibs_builtins[] = "def all(iterable):\n for i in iterable:\n if not i:\n return False\n return True\n\ndef any(iterable):\n for i in iterable:\n if i:\n return True\n return False\n\ndef enumerate(iterable, start=0):\n n = start\n for elem in iterable:\n yield n, elem\n n += 1\n\ndef __minmax_reduce(op, args):\n if len(args) == 2: # min(1, 2)\n return args[0] if op(args[0], args[1]) else args[1]\n if len(args) == 0: # min()\n raise TypeError('expected 1 arguments, got 0')\n if len(args) == 1: # min([1, 2, 3, 4]) -> min(1, 2, 3, 4)\n args = args[0]\n args = iter(args)\n try:\n res = next(args)\n except StopIteration:\n raise ValueError('args is an empty sequence')\n while True:\n try:\n i = next(args)\n except StopIteration:\n break\n if op(i, res):\n res = i\n return res\n\ndef min(*args, key=None):\n key = key or (lambda x: x)\n return __minmax_reduce(lambda x,y: key(x)key(y), args)\n\ndef sum(iterable):\n res = 0\n for i in iterable:\n res += i\n return res\n\ndef map(f, iterable):\n for i in iterable:\n yield f(i)\n\ndef filter(f, iterable):\n for i in iterable:\n if f(i):\n yield i\n\ndef zip(a, b):\n a = iter(a)\n b = iter(b)\n while True:\n try:\n ai = next(a)\n bi = next(b)\n except StopIteration:\n break\n yield ai, bi\n\ndef reversed(iterable):\n a = list(iterable)\n a.reverse()\n return a\n\ndef sorted(iterable, key=None, reverse=False):\n a = list(iterable)\n a.sort(key=key, reverse=reverse)\n return a\n\n\ndef help(obj):\n if hasattr(obj, '__func__'):\n obj = obj.__func__\n # print(obj.__signature__)\n if obj.__doc__:\n print(obj.__doc__)\n\ndef complex(real, imag=0):\n import cmath\n return cmath.complex(real, imag) # type: ignore\n\ndef dir(obj) -> list[str]:\n tp_module = type(__import__('math'))\n if isinstance(obj, tp_module):\n return [k for k, _ in obj.__dict__.items()]\n names = set()\n if not isinstance(obj, type):\n obj_d = obj.__dict__\n if obj_d is not None:\n names.update([k for k, _ in obj_d.items()])\n cls = type(obj)\n else:\n cls = obj\n while cls is not None:\n names.update([k for k, _ in cls.__dict__.items()])\n cls = cls.__base__\n return sorted(list(names))\n\nclass set:\n def __init__(self, iterable=None):\n iterable = iterable or []\n self._a = {}\n self.update(iterable)\n\n def add(self, elem):\n self._a[elem] = None\n \n def discard(self, elem):\n self._a.pop(elem, None)\n\n def remove(self, elem):\n del self._a[elem]\n \n def clear(self):\n self._a.clear()\n\n def update(self, other):\n for elem in other:\n self.add(elem)\n\n def __len__(self):\n return len(self._a)\n \n def copy(self):\n return set(self._a.keys())\n \n def __and__(self, other):\n return {elem for elem in self if elem in other}\n\n def __sub__(self, other):\n return {elem for elem in self if elem not in other}\n \n def __or__(self, other):\n ret = self.copy()\n ret.update(other)\n return ret\n\n def __xor__(self, other): \n _0 = self - other\n _1 = other - self\n return _0 | _1\n\n def union(self, other):\n return self | other\n\n def intersection(self, other):\n return self & other\n\n def difference(self, other):\n return self - other\n\n def symmetric_difference(self, other): \n return self ^ other\n \n def __eq__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) == 0\n \n def __ne__(self, other):\n if not isinstance(other, set):\n return NotImplemented\n return len(self ^ other) != 0\n\n def isdisjoint(self, other):\n return len(self & other) == 0\n \n def issubset(self, other):\n return len(self - other) == 0\n \n def issuperset(self, other):\n return len(other - self) == 0\n\n def __contains__(self, elem):\n return elem in self._a\n \n def __repr__(self):\n if len(self) == 0:\n return 'set()'\n return '{'+ ', '.join([repr(i) for i in self._a.keys()]) + '}'\n \n def __iter__(self):\n return iter(self._a.keys())"; const char kPythonLibs_cmath[] = "import math\n\nclass complex:\n def __init__(self, real, imag=0):\n self._real = float(real)\n self._imag = float(imag)\n\n @property\n def real(self):\n return self._real\n \n @property\n def imag(self):\n return self._imag\n\n def conjugate(self):\n return complex(self.real, -self.imag)\n \n def __repr__(self):\n s = ['(', str(self.real)]\n s.append('-' if self.imag < 0 else '+')\n s.append(str(abs(self.imag)))\n s.append('j)')\n return ''.join(s)\n \n def __eq__(self, other):\n if type(other) is complex:\n return self.real == other.real and self.imag == other.imag\n if type(other) in (int, float):\n return self.real == other and self.imag == 0\n return NotImplemented\n \n def __ne__(self, other):\n res = self == other\n if res is NotImplemented:\n return res\n return not res\n \n def __add__(self, other):\n if type(other) is complex:\n return complex(self.real + other.real, self.imag + other.imag)\n if type(other) in (int, float):\n return complex(self.real + other, self.imag)\n return NotImplemented\n \n def __radd__(self, other):\n return self.__add__(other)\n \n def __sub__(self, other):\n if type(other) is complex:\n return complex(self.real - other.real, self.imag - other.imag)\n if type(other) in (int, float):\n return complex(self.real - other, self.imag)\n return NotImplemented\n \n def __rsub__(self, other):\n if type(other) is complex:\n return complex(other.real - self.real, other.imag - self.imag)\n if type(other) in (int, float):\n return complex(other - self.real, -self.imag)\n return NotImplemented\n \n def __mul__(self, other):\n if type(other) is complex:\n return complex(self.real * other.real - self.imag * other.imag,\n self.real * other.imag + self.imag * other.real)\n if type(other) in (int, float):\n return complex(self.real * other, self.imag * other)\n return NotImplemented\n \n def __rmul__(self, other):\n return self.__mul__(other)\n \n def __truediv__(self, other):\n if type(other) is complex:\n denominator = other.real ** 2 + other.imag ** 2\n real_part = (self.real * other.real + self.imag * other.imag) / denominator\n imag_part = (self.imag * other.real - self.real * other.imag) / denominator\n return complex(real_part, imag_part)\n if type(other) in (int, float):\n return complex(self.real / other, self.imag / other)\n return NotImplemented\n \n def __pow__(self, other: int | float):\n if type(other) in (int, float):\n return complex(self.__abs__() ** other * math.cos(other * phase(self)),\n self.__abs__() ** other * math.sin(other * phase(self)))\n return NotImplemented\n \n def __abs__(self) -> float:\n return math.sqrt(self.real ** 2 + self.imag ** 2)\n\n def __neg__(self):\n return complex(-self.real, -self.imag)\n \n def __hash__(self):\n return hash((self.real, self.imag))\n\n\n# Conversions to and from polar coordinates\n\ndef phase(z: complex):\n return math.atan2(z.imag, z.real)\n\ndef polar(z: complex):\n return z.__abs__(), phase(z)\n\ndef rect(r: float, phi: float):\n return r * math.cos(phi) + r * math.sin(phi) * 1j\n\n# Power and logarithmic functions\n\ndef exp(z: complex):\n return math.exp(z.real) * rect(1, z.imag)\n\ndef log(z: complex, base=2.718281828459045):\n return math.log(z.__abs__(), base) + phase(z) * 1j\n\ndef log10(z: complex):\n return log(z, 10)\n\ndef sqrt(z: complex):\n return z ** 0.5\n\n# Trigonometric functions\n\ndef acos(z: complex):\n return -1j * log(z + sqrt(z * z - 1))\n\ndef asin(z: complex):\n return -1j * log(1j * z + sqrt(1 - z * z))\n\ndef atan(z: complex):\n return 1j / 2 * log((1 - 1j * z) / (1 + 1j * z))\n\ndef cos(z: complex):\n return (exp(z) + exp(-z)) / 2\n\ndef sin(z: complex):\n return (exp(z) - exp(-z)) / (2 * 1j)\n\ndef tan(z: complex):\n return sin(z) / cos(z)\n\n# Hyperbolic functions\n\ndef acosh(z: complex):\n return log(z + sqrt(z * z - 1))\n\ndef asinh(z: complex):\n return log(z + sqrt(z * z + 1))\n\ndef atanh(z: complex):\n return 1 / 2 * log((1 + z) / (1 - z))\n\ndef cosh(z: complex):\n return (exp(z) + exp(-z)) / 2\n\ndef sinh(z: complex):\n return (exp(z) - exp(-z)) / 2\n\ndef tanh(z: complex):\n return sinh(z) / cosh(z)\n\n# Classification functions\n\ndef isfinite(z: complex):\n return math.isfinite(z.real) and math.isfinite(z.imag)\n\ndef isinf(z: complex):\n return math.isinf(z.real) or math.isinf(z.imag)\n\ndef isnan(z: complex):\n return math.isnan(z.real) or math.isnan(z.imag)\n\ndef isclose(a: complex, b: complex):\n return math.isclose(a.real, b.real) and math.isclose(a.imag, b.imag)\n\n# Constants\n\npi = math.pi\ne = math.e\ntau = 2 * pi\ninf = math.inf\ninfj = complex(0, inf)\nnan = math.nan\nnanj = complex(0, nan)\n"; const char kPythonLibs_collections[] = "from typing import TypeVar, Iterable\n\ndef Counter[T](iterable: Iterable[T]):\n a: dict[T, int] = {}\n for x in iterable:\n if x in a:\n a[x] += 1\n else:\n a[x] = 1\n return a\n\n\nclass defaultdict(dict):\n def __init__(self, default_factory, *args):\n super().__init__(*args)\n self.default_factory = default_factory\n\n def __missing__(self, key):\n self[key] = self.default_factory()\n return self[key]\n\n def __repr__(self) -> str:\n return f\"defaultdict({self.default_factory}, {super().__repr__()})\"\n\n def copy(self):\n return defaultdict(self.default_factory, self)\n\n\nclass deque[T]:\n _head: int\n _tail: int\n _maxlen: int | None\n _capacity: int\n _data: list[T]\n\n def __init__(self, iterable: Iterable[T] = None, maxlen: int | None = None):\n if maxlen is not None:\n assert maxlen > 0\n\n self._head = 0\n self._tail = 0\n self._maxlen = maxlen\n self._capacity = 8 if maxlen is None else maxlen + 1\n self._data = [None] * self._capacity # type: ignore\n\n if iterable is not None:\n self.extend(iterable)\n\n @property\n def maxlen(self) -> int | None:\n return self._maxlen\n\n def __resize_2x(self):\n backup = list(self)\n self._capacity *= 2\n self._head = 0\n self._tail = len(backup)\n self._data.clear()\n self._data.extend(backup)\n self._data.extend([None] * (self._capacity - len(backup)))\n\n def append(self, x: T):\n if (self._tail + 1) % self._capacity == self._head:\n if self._maxlen is None:\n self.__resize_2x()\n else:\n self.popleft()\n self._data[self._tail] = x\n self._tail = (self._tail + 1) % self._capacity\n\n def appendleft(self, x: T):\n if (self._tail + 1) % self._capacity == self._head:\n if self._maxlen is None:\n self.__resize_2x()\n else:\n self.pop()\n self._head = (self._head - 1) % self._capacity\n self._data[self._head] = x\n\n def copy(self):\n return deque(self, maxlen=self.maxlen)\n \n def count(self, x: T) -> int:\n n = 0\n for item in self:\n if item == x:\n n += 1\n return n\n \n def extend(self, iterable: Iterable[T]):\n for x in iterable:\n self.append(x)\n\n def extendleft(self, iterable: Iterable[T]):\n for x in iterable:\n self.appendleft(x)\n \n def pop(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n self._tail = (self._tail - 1) % self._capacity\n x = self._data[self._tail]\n self._data[self._tail] = None\n return x\n \n def popleft(self) -> T:\n if self._head == self._tail:\n raise IndexError(\"pop from an empty deque\")\n x = self._data[self._head]\n self._data[self._head] = None\n self._head = (self._head + 1) % self._capacity\n return x\n \n def clear(self):\n i = self._head\n while i != self._tail:\n self._data[i] = None # type: ignore\n i = (i + 1) % self._capacity\n self._head = 0\n self._tail = 0\n\n def rotate(self, n: int = 1):\n if len(self) == 0:\n return\n if n > 0:\n n = n % len(self)\n for _ in range(n):\n self.appendleft(self.pop())\n elif n < 0:\n n = -n % len(self)\n for _ in range(n):\n self.append(self.popleft())\n\n def __len__(self) -> int:\n return (self._tail - self._head) % self._capacity\n\n def __contains__(self, x: object) -> bool:\n for item in self:\n if item == x:\n return True\n return False\n \n def __iter__(self):\n i = self._head\n while i != self._tail:\n yield self._data[i]\n i = (i + 1) % self._capacity\n\n def __eq__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return NotImplemented\n if len(self) != len(other):\n return False\n for x, y in zip(self, other):\n if x != y:\n return False\n return True\n \n def __ne__(self, other: object) -> bool:\n if not isinstance(other, deque):\n return NotImplemented\n return not self == other\n \n def __repr__(self) -> str:\n if self.maxlen is None:\n return f\"deque({list(self)!r})\"\n return f\"deque({list(self)!r}, maxlen={self.maxlen})\"\n\n"; const char kPythonLibs_dataclasses[] = "def _get_annotations(cls: type):\n inherits = []\n while cls is not object:\n inherits.append(cls)\n cls = cls.__base__\n inherits.reverse()\n res = {}\n for cls in inherits:\n res.update(cls.__annotations__)\n return res.keys()\n\ndef _wrapped__init__(self, *args, **kwargs):\n cls = type(self)\n cls_d = cls.__dict__\n fields = _get_annotations(cls)\n i = 0 # index into args\n for field in fields:\n if field in kwargs:\n setattr(self, field, kwargs.pop(field))\n else:\n if i < len(args):\n setattr(self, field, args[i])\n i += 1\n elif field in cls_d: # has default value\n setattr(self, field, cls_d[field])\n else:\n raise TypeError(f\"{cls.__name__} missing required argument {field!r}\")\n if len(args) > i:\n raise TypeError(f\"{cls.__name__} takes {len(fields)} positional arguments but {len(args)} were given\")\n if len(kwargs) > 0:\n raise TypeError(f\"{cls.__name__} got an unexpected keyword argument {next(iter(kwargs))!r}\")\n\ndef _wrapped__repr__(self):\n fields = _get_annotations(type(self))\n obj_d = self.__dict__\n args: list = [f\"{field}={obj_d[field]!r}\" for field in fields]\n return f\"{type(self).__name__}({', '.join(args)})\"\n\ndef _wrapped__eq__(self, other):\n if type(self) is not type(other):\n return False\n fields = _get_annotations(type(self))\n for field in fields:\n if getattr(self, field) != getattr(other, field):\n return False\n return True\n\ndef _wrapped__ne__(self, other):\n return not self.__eq__(other)\n\ndef dataclass(cls: type):\n assert type(cls) is type\n cls_d = cls.__dict__\n if '__init__' not in cls_d:\n cls.__init__ = _wrapped__init__\n if '__repr__' not in cls_d:\n cls.__repr__ = _wrapped__repr__\n if '__eq__' not in cls_d:\n cls.__eq__ = _wrapped__eq__\n if '__ne__' not in cls_d:\n cls.__ne__ = _wrapped__ne__\n fields = _get_annotations(cls)\n has_default = False\n for field in fields:\n if field in cls_d:\n has_default = True\n else:\n if has_default:\n raise TypeError(f\"non-default argument {field!r} follows default argument\")\n return cls\n\ndef asdict(obj) -> dict:\n fields = _get_annotations(type(obj))\n obj_d = obj.__dict__\n return {field: obj_d[field] for field in fields}"; @@ -11,7 +11,7 @@ const char kPythonLibs_functools[] = "class cache:\n def __init__(self, f):\n const char kPythonLibs_heapq[] = "# Heap queue algorithm (a.k.a. priority queue)\ndef heappush(heap, item):\n \"\"\"Push item onto heap, maintaining the heap invariant.\"\"\"\n heap.append(item)\n _siftdown(heap, 0, len(heap)-1)\n\ndef heappop(heap):\n \"\"\"Pop the smallest item off the heap, maintaining the heap invariant.\"\"\"\n lastelt = heap.pop() # raises appropriate IndexError if heap is empty\n if heap:\n returnitem = heap[0]\n heap[0] = lastelt\n _siftup(heap, 0)\n return returnitem\n return lastelt\n\ndef heapreplace(heap, item):\n \"\"\"Pop and return the current smallest value, and add the new item.\n\n This is more efficient than heappop() followed by heappush(), and can be\n more appropriate when using a fixed-size heap. Note that the value\n returned may be larger than item! That constrains reasonable uses of\n this routine unless written as part of a conditional replacement:\n\n if item > heap[0]:\n item = heapreplace(heap, item)\n \"\"\"\n returnitem = heap[0] # raises appropriate IndexError if heap is empty\n heap[0] = item\n _siftup(heap, 0)\n return returnitem\n\ndef heappushpop(heap, item):\n \"\"\"Fast version of a heappush followed by a heappop.\"\"\"\n if heap and heap[0] < item:\n item, heap[0] = heap[0], item\n _siftup(heap, 0)\n return item\n\ndef heapify(x):\n \"\"\"Transform list into a heap, in-place, in O(len(x)) time.\"\"\"\n n = len(x)\n # Transform bottom-up. The largest index there's any point to looking at\n # is the largest with a child index in-range, so must have 2*i + 1 < n,\n # or i < (n-1)/2. If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so\n # j-1 is the largest, which is n//2 - 1. If n is odd = 2*j+1, this is\n # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1.\n for i in reversed(range(n//2)):\n _siftup(x, i)\n\n# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos\n# is the index of a leaf with a possibly out-of-order value. Restore the\n# heap invariant.\ndef _siftdown(heap, startpos, pos):\n newitem = heap[pos]\n # Follow the path to the root, moving parents down until finding a place\n # newitem fits.\n while pos > startpos:\n parentpos = (pos - 1) >> 1\n parent = heap[parentpos]\n if newitem < parent:\n heap[pos] = parent\n pos = parentpos\n continue\n break\n heap[pos] = newitem\n\ndef _siftup(heap, pos):\n endpos = len(heap)\n startpos = pos\n newitem = heap[pos]\n # Bubble up the smaller child until hitting a leaf.\n childpos = 2*pos + 1 # leftmost child position\n while childpos < endpos:\n # Set childpos to index of smaller child.\n rightpos = childpos + 1\n if rightpos < endpos and not heap[childpos] < heap[rightpos]:\n childpos = rightpos\n # Move the smaller child up.\n heap[pos] = heap[childpos]\n pos = childpos\n childpos = 2*pos + 1\n # The leaf at pos is empty now. Put newitem there, and bubble it up\n # to its final resting place (by sifting its parents down).\n heap[pos] = newitem\n _siftdown(heap, startpos, pos)"; const char kPythonLibs_linalg[] = "from vmath import *"; const char kPythonLibs_operator[] = "# https://docs.python.org/3/library/operator.html#mapping-operators-to-functions\n\ndef le(a, b): return a <= b\ndef lt(a, b): return a < b\ndef ge(a, b): return a >= b\ndef gt(a, b): return a > b\ndef eq(a, b): return a == b\ndef ne(a, b): return a != b\n\ndef and_(a, b): return a & b\ndef or_(a, b): return a | b\ndef xor(a, b): return a ^ b\ndef invert(a): return ~a\ndef lshift(a, b): return a << b\ndef rshift(a, b): return a >> b\n\ndef is_(a, b): return a is b\ndef is_not(a, b): return a is not b\ndef not_(a): return not a\ndef truth(a): return bool(a)\ndef contains(a, b): return b in a\n\ndef add(a, b): return a + b\ndef sub(a, b): return a - b\ndef mul(a, b): return a * b\ndef truediv(a, b): return a / b\ndef floordiv(a, b): return a // b\ndef mod(a, b): return a % b\ndef pow(a, b): return a ** b\ndef neg(a): return -a\ndef matmul(a, b): return a @ b\n\ndef getitem(a, b): return a[b]\ndef setitem(a, b, c): a[b] = c\ndef delitem(a, b): del a[b]\n\ndef iadd(a, b): a += b; return a\ndef isub(a, b): a -= b; return a\ndef imul(a, b): a *= b; return a\ndef itruediv(a, b): a /= b; return a\ndef ifloordiv(a, b): a //= b; return a\ndef imod(a, b): a %= b; return a\n# def ipow(a, b): a **= b; return a\n# def imatmul(a, b): a @= b; return a\ndef iand(a, b): a &= b; return a\ndef ior(a, b): a |= b; return a\ndef ixor(a, b): a ^= b; return a\ndef ilshift(a, b): a <<= b; return a\ndef irshift(a, b): a >>= b; return a\n"; -const char kPythonLibs_typing[] = "class _Placeholder:\n def __init__(self, *args, **kwargs):\n pass\n def __getitem__(self, *args):\n return self\n def __call__(self, *args, **kwargs):\n return self\n def __and__(self, other):\n return self\n def __or__(self, other):\n return self\n def __xor__(self, other):\n return self\n\n\n_PLACEHOLDER = _Placeholder()\n\nSequence = _PLACEHOLDER\nList = _PLACEHOLDER\nDict = _PLACEHOLDER\nTuple = _PLACEHOLDER\nSet = _PLACEHOLDER\nAny = _PLACEHOLDER\nUnion = _PLACEHOLDER\nOptional = _PLACEHOLDER\nCallable = _PLACEHOLDER\nType = _PLACEHOLDER\nTypeAlias = _PLACEHOLDER\nNewType = _PLACEHOLDER\n\nLiteral = _PLACEHOLDER\nLiteralString = _PLACEHOLDER\n\nIterable = _PLACEHOLDER\nGenerator = _PLACEHOLDER\nIterator = _PLACEHOLDER\n\nHashable = _PLACEHOLDER\n\nTypeVar = _PLACEHOLDER\nSelf = _PLACEHOLDER\n\nProtocol = object\nGeneric = object\nNever = object\n\nTYPE_CHECKING = False\n\n# decorators\noverload = lambda x: x\nfinal = lambda x: x\n\n# exhaustiveness checking\nassert_never = lambda x: x\n"; +const char kPythonLibs_typing[] = "class _Placeholder:\n def __init__(self, *args, **kwargs):\n pass\n def __getitem__(self, *args):\n return self\n def __call__(self, *args, **kwargs):\n return self\n def __and__(self, other):\n return self\n def __or__(self, other):\n return self\n def __xor__(self, other):\n return self\n\n\n_PLACEHOLDER = _Placeholder()\n\nSequence = _PLACEHOLDER\nList = _PLACEHOLDER\nDict = _PLACEHOLDER\nTuple = _PLACEHOLDER\nSet = _PLACEHOLDER\nAny = _PLACEHOLDER\nUnion = _PLACEHOLDER\nOptional = _PLACEHOLDER\nCallable = _PLACEHOLDER\nType = _PLACEHOLDER\nTypeAlias = _PLACEHOLDER\nNewType = _PLACEHOLDER\n\nLiteral = _PLACEHOLDER\nLiteralString = _PLACEHOLDER\n\nIterable = _PLACEHOLDER\nGenerator = _PLACEHOLDER\nIterator = _PLACEHOLDER\n\nHashable = _PLACEHOLDER\n\nTypeVar = _PLACEHOLDER\nSelf = _PLACEHOLDER\n\nProtocol = object\nGeneric = object\nNever = object\n\nTYPE_CHECKING = False\n\n# decorators\noverload = lambda x: x\nfinal = lambda x: x\n\n# exhaustiveness checking\nassert_never = lambda x: x\n\nTypedDict = dict\nNotRequired = _PLACEHOLDER\n"; const char* load_kPythonLib(const char* name) { if (strchr(name, '.') != NULL) return NULL; diff --git a/src/interpreter/ceval.c b/src/interpreter/ceval.c index 492ca795..ad5368c1 100644 --- a/src/interpreter/ceval.c +++ b/src/interpreter/ceval.c @@ -11,8 +11,6 @@ #include #include -static bool stack_format_object(VM* self, c11_sv spec); - #define DISPATCH() \ do { \ frame->ip++; \ @@ -118,8 +116,7 @@ __NEXT_STEP: #if PK_ENABLE_WATCHDOG if(self->watchdog_info.max_reset_time > 0) { - clock_t now = clock(); - if(now > self->watchdog_info.max_reset_time) { + if(!py_debugger_isattached() && clock() > self->watchdog_info.max_reset_time) { self->watchdog_info.max_reset_time = 0; TimeoutError("watchdog timeout"); goto __ERROR; @@ -1191,8 +1188,9 @@ __NEXT_STEP: ////////////////// case OP_FORMAT_STRING: { py_Ref spec = c11__at(py_TValue, &frame->co->consts, byte.arg); - bool ok = stack_format_object(self, py_tosv(spec)); + bool ok = pk_format_object(self, TOP(), py_tosv(spec)); if(!ok) goto __ERROR; + py_assign(TOP(), py_retval()); DISPATCH(); } default: c11__unreachable(); @@ -1298,10 +1296,9 @@ bool pk_stack_binaryop(VM* self, py_Name op, py_Name rop) { rhs_t); } -static bool stack_format_object(VM* self, c11_sv spec) { +bool pk_format_object(VM* self, py_Ref val, c11_sv spec) { // format TOS via `spec` inplace - // spec: '!r:.2f', '.2f' - py_StackRef val = TOP(); + // spec: '!r:.2f', ':.2f', '.2f' if(spec.size == 0) return py_str(val); if(spec.data[0] == '!') { @@ -1442,7 +1439,7 @@ static bool stack_format_object(VM* self, c11_sv spec) { c11_string__delete(body); // inplace update - c11_sbuf__py_submit(&buf, val); + c11_sbuf__py_submit(&buf, py_retval()); return true; } diff --git a/src/modules/math.c b/src/modules/math.c index 4ef3f81e..8c6fb55b 100644 --- a/src/modules/math.c +++ b/src/modules/math.c @@ -133,6 +133,7 @@ static bool math_radians(int argc, py_Ref argv) { } TWO_ARG_FUNC(fmod, fmod) +TWO_ARG_FUNC(copysign, copysign) static bool math_modf(int argc, py_Ref argv) { PY_CHECK_ARGC(1); @@ -200,6 +201,7 @@ void pk__add_module_math() { py_bindfunc(mod, "fmod", math_fmod); py_bindfunc(mod, "modf", math_modf); + py_bindfunc(mod, "copysign", math_copysign); py_bindfunc(mod, "factorial", math_factorial); } diff --git a/src/modules/time.c b/src/modules/time.c index 118901ba..bed35ec6 100644 --- a/src/modules/time.c +++ b/src/modules/time.c @@ -5,24 +5,22 @@ #define NANOS_PER_SEC 1000000000 #ifndef __circle__ - int64_t time_ns() { - struct timespec tms; - #ifdef CLOCK_REALTIME - clock_gettime(CLOCK_REALTIME, &tms); - #else - /* The C11 way */ - timespec_get(&tms, TIME_UTC); - #endif - /* seconds, multiplied with 1 billion */ - int64_t nanos = tms.tv_sec * (int64_t)NANOS_PER_SEC; - /* Add full nanoseconds */ - nanos += tms.tv_nsec; - return nanos; - } +int64_t time_ns() { + struct timespec tms; +#ifdef CLOCK_REALTIME + clock_gettime(CLOCK_REALTIME, &tms); #else - int64_t time_ns() { - return 0; - } + /* The C11 way */ + timespec_get(&tms, TIME_UTC); +#endif + /* seconds, multiplied with 1 billion */ + int64_t nanos = tms.tv_sec * (int64_t)NANOS_PER_SEC; + /* Add full nanoseconds */ + nanos += tms.tv_nsec; + return nanos; +} +#else +int64_t time_ns() { return 0; } #endif static bool time_time(int argc, py_Ref argv) { @@ -39,15 +37,21 @@ static bool time_time_ns(int argc, py_Ref argv) { return true; } +static bool time_perf_counter(int argc, py_Ref argv) { + PY_CHECK_ARGC(0); + py_newfloat(py_retval(), (double)clock() / CLOCKS_PER_SEC); + return true; +} + static bool time_sleep(int argc, py_Ref argv) { PY_CHECK_ARGC(1); py_f64 secs; if(!py_castfloat(argv, &secs)) return false; - int64_t start = time_ns(); - const int64_t end = start + secs * 1000000000; + clock_t start = clock(); + const clock_t end = start + (clock_t)(secs * CLOCKS_PER_SEC); while(true) { - int64_t now = time_ns(); + clock_t now = clock(); if(now >= end) break; } py_newnone(py_retval()); @@ -101,6 +105,7 @@ void pk__add_module_time() { py_bindfunc(mod, "time", time_time); py_bindfunc(mod, "time_ns", time_time_ns); + py_bindfunc(mod, "perf_counter", time_perf_counter); py_bindfunc(mod, "sleep", time_sleep); py_bindfunc(mod, "localtime", time_localtime); } diff --git a/src/public/GlobalSetup.c b/src/public/GlobalSetup.c index 60486556..ea09569a 100644 --- a/src/public/GlobalSetup.c +++ b/src/public/GlobalSetup.c @@ -29,8 +29,8 @@ void py_initialize() { bool is_little_endian = *(char*)&x == 1; if(!is_little_endian) c11__abort("is_little_endian != true"); - static_assert(sizeof(py_TValue) == 24, "sizeof(py_TValue) != 24"); - static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4"); + _Static_assert(sizeof(py_TValue) == 24, "sizeof(py_TValue) != 24"); + _Static_assert(offsetof(py_TValue, extra) == 4, "offsetof(py_TValue, extra) != 4"); pk_current_vm = pk_all_vm[0] = &pk_default_vm; diff --git a/src/public/PyDict.c b/src/public/PyDict.c index 0cc797e2..1e65e13b 100644 --- a/src/public/PyDict.c +++ b/src/public/PyDict.c @@ -651,10 +651,18 @@ bool dict_items__next__(int argc, py_Ref argv) { return StopIteration(); } +bool dict_items__len__(int argc, py_Ref argv) { + PY_CHECK_ARGC(1); + DictIterator* iter = py_touserdata(py_arg(0)); + py_newint(py_retval(), iter->dict->length); + return true; +} + py_Type pk_dict_items__register() { py_Type type = pk_newtype("dict_iterator", tp_object, NULL, NULL, false, true); py_bindmagic(type, __iter__, pk_wrapper__self); py_bindmagic(type, __next__, dict_items__next__); + py_bindmagic(type, __len__, dict_items__len__); return type; } diff --git a/tests/02_float.py b/tests/02_float.py index e2b4821c..4652133a 100644 --- a/tests/02_float.py +++ b/tests/02_float.py @@ -113,4 +113,17 @@ assert abs(0.0) == 0.0 assert eq(10 % 4, 2) assert eq(10.5 % 4, 2.5) assert eq(10 % 4.5, 1.0) -assert eq(10.5 % 4.5, 1.5) \ No newline at end of file +assert eq(10.5 % 4.5, 1.5) + +assert eq(10.5 // 4, 2.0) +assert eq(10.5 // 4.5, 2.0) +_0, _1 = divmod(10.5, 4) +assert eq(_0, 2.0) +assert eq(_1, 2.5) + +assert eq(3.4 % -2, -0.6) +assert eq(-2 % 3.4, 1.4) +assert eq(-3.4 % -2, -1.4) +assert eq(-6 // 3.4, -2.0) +assert eq(-6 % 3.4, 0.8) + diff --git a/tests/04_str.py b/tests/04_str.py index 4a72746b..bfbf0cb3 100644 --- a/tests/04_str.py +++ b/tests/04_str.py @@ -209,8 +209,8 @@ 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 "{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" diff --git a/tests/25_rfstring.py b/tests/25_rfstring.py index 4aaf4ebf..5b687bd6 100644 --- a/tests/25_rfstring.py +++ b/tests/25_rfstring.py @@ -43,9 +43,18 @@ assert f'{a:<10}' == '10 ' assert f'{a:<10.2f}' == '10.00 ' assert f'{a:>10.2f}' == ' 10.00' +assert '{}'.format(a) == '10' +assert '{:>10}'.format(a) == ' 10' +assert '{:<10}'.format(a) == '10 ' +assert '{:<10.2f}'.format(a) == '10.00 ' +assert '{:>10.2f}'.format(a) == ' 10.00' + assert f'{a:^10}' == ' 10 ' assert f'{a:^10.2f}' == ' 10.00 ' +assert '{:^10}'.format(a) == ' 10 ' +assert '{:^10.2f}'.format(a) == ' 10.00 ' + assert f'{a:3d}' == ' 10' assert f'{a:10d}' == ' 10' assert f'{a:1d}' == '10'