From 45246b316a30db0bbcf5f34d97ed36ef6df2ea3d Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Sun, 9 May 2021 00:26:40 +0300 Subject: [PATCH 01/13] Start implemeting Number class --- python_chakra/__init__.py | 7 ++- python_chakra/dll_wrapper.py | 15 +++-- python_chakra/index.py | 104 +++++++++++++++++++++++++++++++---- readme.md | 2 + 4 files changed, 111 insertions(+), 17 deletions(-) diff --git a/python_chakra/__init__.py b/python_chakra/__init__.py index d30e80c..2586de4 100644 --- a/python_chakra/__init__.py +++ b/python_chakra/__init__.py @@ -1,4 +1,9 @@ -from .index import * # noqa: F401 +import sys + +assert sys.version_info >= (3, 7), \ + "Only versions of C Python interpreter greater than or equal to 3.7 are supported" + +from .index import * # noqa: F401, E402 __title__ = "python_chakra" __author__ = "MadProbe" diff --git a/python_chakra/dll_wrapper.py b/python_chakra/dll_wrapper.py index 8e4ed43..80c3886 100644 --- a/python_chakra/dll_wrapper.py +++ b/python_chakra/dll_wrapper.py @@ -344,16 +344,23 @@ def js_eval(code): return call(js_eval_function, str_to_js_string(code)) -def to_number(value: Union[JSValueRef, int]) -> JSValueRef: +def to_number(value: Union[JSValueRef, float, int]) -> JSValueRef: number = JSValueRef() - if type(value) is int: - c = chakra_core.JsIntToNumber(value, byref(number)) - else: + if type(value) is JSValueRef: c = chakra_core.JsConvertValueToNumber(value, byref(number)) + else: + c = chakra_core.JsDoubleToNumber(c_longdouble(value), byref(number)) assert c == 0, descriptive_message(c, "to_number") return number +def to_double(value: Union[JSValueRef, float, int]) -> float: + number = c_longdouble() + c = chakra_core.JsNumbertToDouble(to_number(value), byref(number)) + assert c == 0, descriptive_message(c, "to_double") + return number.value + + def to_int(value: JSValueRef) -> int: value = to_number(value) number = c_int(0) diff --git a/python_chakra/index.py b/python_chakra/index.py index 852e7d7..bb59292 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -1,20 +1,12 @@ +from __future__ import annotations from os import getcwd -from typing import Any, Union +from typing import Any, Optional, Union from .dll_wrapper import * from .modules import (JavaScriptModule, ModuleRuntime, default_loader, default_path_resolver) from .utils import ValueSkeleton -# Forwards -class Object: - pass - - -class BigInt: - pass - - class Boolean(ValueSkeleton): __slots__ = "_as_parameter_", @@ -56,8 +48,95 @@ def is_object(self): class Number(ValueSkeleton): - def __init__(self) -> None: - pass + __slots__ = "_as_parameter_", "value" + _as_parameter_: JSValueRef + value: float + + def __init__(self, value: NumberLike) -> None: + if type(value) is Number: + # Borrow properties from Number + # for a small optimization (speed + memory) + self._as_parameter_ = value._as_parameter_ + self.value = value.value + else: + self._as_parameter_ = to_number(value) + self.value = to_double(value) + + def __update(self): + self._as_parameter_ = to_number(self.value) + return self + + def is_number(self): + return True + + def to_float(self) -> float: + return self.value + + def __iadd__(self, other: NumberLike): + self.value += self.__to_float(other) + return self.__update() + + def __isub__(self, other: NumberLike): + self.value -= self.__to_float(other) + return self.__update() + + def __idiv__(self, other: NumberLike): + self.value /= self.__to_float(other) + return self.__update() + + def __ifloordiv__(self, other: NumberLike): + self.value //= self.__to_float(other) + return self.__update() + + def __imul__(self, other: NumberLike): + self.value *= self.__to_float(other) + return self.__update() + + def __ipow__(self, other: NumberLike, modulo: Optional[NumberLike] = None): + self.value **= self.__to_float(other) + return self.__update() + + def __add__(self, other: NumberLike): + return Number(self).__iadd__(other) + + def __sub__(self, other: NumberLike): + return Number(self).__isub__(other) + + def __truediv__(self, other: NumberLike): + return Number(self).__idiv__(other) + + def __floordiv__(self, other: NumberLike): + return Number(self).__ifloordiv__(other) + + def __mul__(self, other: NumberLike): + return Number(self).__imul__(other) + + def __pow__(self, other: NumberLike, modulo: Optional[NumberLike] = None): + return Number(self).__ipow__(other, modulo) + + def __repr__(self) -> str: + return f"Number(value={self.value})" + + def __eq__(self, other: NumberLike) -> bool: + return self.value == self.__to_float(other) + + def __ne__(self, other: NumberLike) -> bool: + return self.value != self.__to_float(other) + + def __abs__(self): + return Number.from_(self) + + def __to_float(self, other) -> float: + if type(other) is float: + return other + elif type(other) is JSValueRef: + return to_double(other) + else: + return float(other) + + @staticmethod + def from_(value: Union[int, float, JSValueRef]): + return Number(value) class String(ValueSkeleton): @@ -179,6 +258,7 @@ def is_null(self): _refs = [] +NumberLike = Union[Number, JSValueRef, int, float] class JSRuntime: diff --git a/readme.md b/readme.md index bd4a2dd..255d691 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,7 @@ # Intro +Creating a PR is highly appreciated! + This module is a multifunctional and easy-to-use wrapper around [ChakraCore](https://github.com/chakra-core/ChakraCore) JavaScript engine (WIP) This wrapper includes: From 430b4d58190b610aa41179b11ffd82d16c16c3a0 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Sat, 15 May 2021 19:56:13 +0300 Subject: [PATCH 02/13] add **, **=, %, %=, /=, +<>, -<>, abs(<>), bool(<>) --- python_chakra/index.py | 54 ++++++++++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/python_chakra/index.py b/python_chakra/index.py index bb59292..aff9e0e 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -62,7 +62,7 @@ def __init__(self, value: NumberLike) -> None: self._as_parameter_ = to_number(value) self.value = to_double(value) - def __update(self): + def __update(self) -> Number: self._as_parameter_ = to_number(self.value) return self @@ -72,46 +72,55 @@ def is_number(self): def to_float(self) -> float: return self.value - def __iadd__(self, other: NumberLike): + def __iadd__(self, other: NumberLike) -> Number: self.value += self.__to_float(other) return self.__update() - def __isub__(self, other: NumberLike): + def __isub__(self, other: NumberLike) -> Number: self.value -= self.__to_float(other) return self.__update() - def __idiv__(self, other: NumberLike): + def __itruediv__(self, other: NumberLike) -> Number: self.value /= self.__to_float(other) return self.__update() - def __ifloordiv__(self, other: NumberLike): + def __ifloordiv__(self, other: NumberLike) -> Number: self.value //= self.__to_float(other) return self.__update() - def __imul__(self, other: NumberLike): + def __imul__(self, other: NumberLike) -> Number: self.value *= self.__to_float(other) return self.__update() - def __ipow__(self, other: NumberLike, modulo: Optional[NumberLike] = None): - self.value **= self.__to_float(other) + def __imod__(self, other: NumberLike) -> Number: + self.value %= self.__to_float(other) return self.__update() - def __add__(self, other: NumberLike): + def __ipow__(self, other: NumberLike, + modulo: Optional[NumberLike] = None) -> Number: + if modulo is not None: + self.value = self.value ** self.__to_float(other) % self.__to_float(modulo) + else: + self.value **= self.__to_float(other) + return self.__update() + + def __add__(self, other: NumberLike) -> Number: return Number(self).__iadd__(other) - def __sub__(self, other: NumberLike): + def __sub__(self, other: NumberLike) -> Number: return Number(self).__isub__(other) - def __truediv__(self, other: NumberLike): - return Number(self).__idiv__(other) + def __truediv__(self, other: NumberLike) -> Number: + return Number(self).__itruediv__(other) - def __floordiv__(self, other: NumberLike): + def __floordiv__(self, other: NumberLike) -> Number: return Number(self).__ifloordiv__(other) - def __mul__(self, other: NumberLike): - return Number(self).__imul__(other) + def __mod__(self, other: NumberLike) -> Number: + return Number(self).__imod__(other) - def __pow__(self, other: NumberLike, modulo: Optional[NumberLike] = None): + def __pow__(self, other: NumberLike, + modulo: Optional[NumberLike] = None) -> Number: return Number(self).__ipow__(other, modulo) def __repr__(self) -> str: @@ -123,8 +132,17 @@ def __eq__(self, other: NumberLike) -> bool: def __ne__(self, other: NumberLike) -> bool: return self.value != self.__to_float(other) - def __abs__(self): - return Number.from_(self) + def __abs__(self) -> Number: + return Number(abs(self.value)) + + def __neg__(self) -> Number: + return Number(-self.value) + + def __pos__(self) -> Number: + return self + + def __bool__(self) -> bool: + return self.value != 0.0 def __to_float(self, other) -> float: if type(other) is float: From d08e24b1f87545595abdba2060e7466a41eddb7a Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Sat, 15 May 2021 19:58:28 +0300 Subject: [PATCH 03/13] fix linter errors --- python_chakra/index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python_chakra/index.py b/python_chakra/index.py index aff9e0e..2c42532 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -119,7 +119,7 @@ def __floordiv__(self, other: NumberLike) -> Number: def __mod__(self, other: NumberLike) -> Number: return Number(self).__imod__(other) - def __pow__(self, other: NumberLike, + def __pow__(self, other: NumberLike, modulo: Optional[NumberLike] = None) -> Number: return Number(self).__ipow__(other, modulo) @@ -140,7 +140,7 @@ def __neg__(self) -> Number: def __pos__(self) -> Number: return self - + def __bool__(self) -> bool: return self.value != 0.0 From 8729319bb8088db74db0694be7db38fad56256cb Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Mon, 17 May 2021 15:36:11 +0300 Subject: [PATCH 04/13] add __float__, __int__, __index__ --- python_chakra/index.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/python_chakra/index.py b/python_chakra/index.py index 2c42532..46e7dd1 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -144,6 +144,15 @@ def __pos__(self) -> Number: def __bool__(self) -> bool: return self.value != 0.0 + def __float__(self) -> float: + return float(self.value) + + def __int__(self) -> int: + return int(self.value) + + def __index__(self) -> int: + return int(self.value) + def __to_float(self, other) -> float: if type(other) is float: return other @@ -279,6 +288,10 @@ def is_null(self): NumberLike = Union[Number, JSValueRef, int, float] +def _to_float(): + pass + + class JSRuntime: __slots__ = "_as_parameter_", "__flags", "__runtime", \ "__context", "__module_runtime", "__promise_queue" From 3254ea97cce1dc7ecd21d06980078f75839fc5d9 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Mon, 17 May 2021 16:00:09 +0300 Subject: [PATCH 05/13] add __iter__, __len__ --- python_chakra/index.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/python_chakra/index.py b/python_chakra/index.py index 46e7dd1..c5efbee 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -153,6 +153,16 @@ def __int__(self) -> int: def __index__(self) -> int: return int(self.value) + def __len__(self) -> int: + return max(min(len(str(self.value)), 0), maxsize) + + def __iter__(self) -> Iterable[Union[int, None]]: + for digit_or_dot in str(self.value): + if digit_or_dot == ".": + yield None + else: + yield int(digit_or_dot) + def __to_float(self, other) -> float: if type(other) is float: return other @@ -162,7 +172,7 @@ def __to_float(self, other) -> float: return float(other) @staticmethod - def from_(value: Union[int, float, JSValueRef]): + def from_(value: NumberLike) -> Number: return Number(value) From bd8bebbb0ae116af697216ea68566a4c0f042b82 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Mon, 17 May 2021 16:01:28 +0300 Subject: [PATCH 06/13] add __ceil__, __floor__, __trunc__, __round__ --- python_chakra/index.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/python_chakra/index.py b/python_chakra/index.py index c5efbee..32484ba 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -1,6 +1,8 @@ from __future__ import annotations from os import getcwd -from typing import Any, Optional, Union +from sys import maxsize +from math import floor, ceil, trunc +from typing import Any, Optional, Tuple, Union from .dll_wrapper import * from .modules import (JavaScriptModule, ModuleRuntime, default_loader, default_path_resolver) @@ -66,6 +68,9 @@ def __update(self) -> Number: self._as_parameter_ = to_number(self.value) return self + def as_integer_ratio(self) -> Tuple[int, int]: + return self.value.as_integer_ratio() + def is_number(self): return True @@ -171,6 +176,18 @@ def __to_float(self, other) -> float: else: return float(other) + def __ceil__(self) -> int: + return ceil(self.value) + + def __floor__(self) -> int: + return floor(self.value) + + def __trunc__(self) -> int: + return trunc(self.value) + + def __round__(self, ndigits: Optional[int] = None) -> int: + return round(self.value, ndigits) + @staticmethod def from_(value: NumberLike) -> Number: return Number(value) From d18d585066d9901421ca8baf685e6f4e57bf6ea3 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Mon, 17 May 2021 16:04:12 +0300 Subject: [PATCH 07/13] remove from_ static method --- python_chakra/index.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/python_chakra/index.py b/python_chakra/index.py index 32484ba..5aaf72c 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -188,10 +188,6 @@ def __trunc__(self) -> int: def __round__(self, ndigits: Optional[int] = None) -> int: return round(self.value, ndigits) - @staticmethod - def from_(value: NumberLike) -> Number: - return Number(value) - class String(ValueSkeleton): def __init__(self) -> None: From f49a2e66ae03358c06f466ad6613330a52ce396d Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Mon, 17 May 2021 18:38:32 +0300 Subject: [PATCH 08/13] refactor to move __to_float to global scope --- python_chakra/index.py | 43 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/python_chakra/index.py b/python_chakra/index.py index 5aaf72c..9c3d10e 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -78,35 +78,37 @@ def to_float(self) -> float: return self.value def __iadd__(self, other: NumberLike) -> Number: - self.value += self.__to_float(other) + self.value += _to_float(other) return self.__update() def __isub__(self, other: NumberLike) -> Number: - self.value -= self.__to_float(other) + self.value -= _to_float(other) return self.__update() def __itruediv__(self, other: NumberLike) -> Number: - self.value /= self.__to_float(other) + self.value /= _to_float(other) return self.__update() def __ifloordiv__(self, other: NumberLike) -> Number: - self.value //= self.__to_float(other) + self.value //= _to_float(other) return self.__update() def __imul__(self, other: NumberLike) -> Number: - self.value *= self.__to_float(other) + self.value *= _to_float(other) return self.__update() def __imod__(self, other: NumberLike) -> Number: - self.value %= self.__to_float(other) + self.value %= _to_float(other) return self.__update() def __ipow__(self, other: NumberLike, modulo: Optional[NumberLike] = None) -> Number: if modulo is not None: - self.value = self.value ** self.__to_float(other) % self.__to_float(modulo) + other = _to_float(other) + modulo = _to_float(modulo) + self.value = self.value ** other % modulo else: - self.value **= self.__to_float(other) + self.value **= _to_float(other) return self.__update() def __add__(self, other: NumberLike) -> Number: @@ -132,10 +134,10 @@ def __repr__(self) -> str: return f"Number(value={self.value})" def __eq__(self, other: NumberLike) -> bool: - return self.value == self.__to_float(other) + return self.value == _to_float(other) def __ne__(self, other: NumberLike) -> bool: - return self.value != self.__to_float(other) + return self.value != _to_float(other) def __abs__(self) -> Number: return Number(abs(self.value)) @@ -168,14 +170,6 @@ def __iter__(self) -> Iterable[Union[int, None]]: else: yield int(digit_or_dot) - def __to_float(self, other) -> float: - if type(other) is float: - return other - elif type(other) is JSValueRef: - return to_double(other) - else: - return float(other) - def __ceil__(self) -> int: return ceil(self.value) @@ -311,8 +305,15 @@ def is_null(self): NumberLike = Union[Number, JSValueRef, int, float] -def _to_float(): - pass +def _to_float(other: NumberLike) -> float: + if type(other) is float: + return other + elif type(other) is Number: + return other.value + elif type(other) is JSValueRef: + return to_double(other) + else: + return float(other) class JSRuntime: @@ -417,7 +418,7 @@ def __exit__(self, *_): for ref in _refs: js_release(ref) except: # noqa: E722 - print("Failed to dispose ref") + print("Failed to dispose object references") try: set_current_context(0) dispose_runtime(self) From d670f2344448f3499d2e7623898174af482a29a4 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Tue, 18 May 2021 23:31:40 +0300 Subject: [PATCH 09/13] a bit of style changes --- python_chakra/__init__.py | 5 +++-- python_chakra/dll_wrapper.py | 24 ++++++++++++------------ python_chakra/index.py | 4 ++-- python_chakra/modules.py | 17 ++++++++--------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/python_chakra/__init__.py b/python_chakra/__init__.py index 2586de4..29f7156 100644 --- a/python_chakra/__init__.py +++ b/python_chakra/__init__.py @@ -1,7 +1,8 @@ import sys -assert sys.version_info >= (3, 7), \ - "Only versions of C Python interpreter greater than or equal to 3.7 are supported" +assert sys.version_info >= (3, 7), "Only Python >= 3.7 is supported!" + +del sys from .index import * # noqa: F401, E402 diff --git a/python_chakra/dll_wrapper.py b/python_chakra/dll_wrapper.py index 80c3886..a73f2fb 100644 --- a/python_chakra/dll_wrapper.py +++ b/python_chakra/dll_wrapper.py @@ -39,8 +39,8 @@ def run(self, task): arguments, 1, byref(result)) # print("Done promise continuation callback") except Exception as ex: - print("An error happed when executed promise continuation callback:", - ex, sep="\n") + print("An error happed when executed", + "promise continuation callback:", ex, sep="\n") finally: js_release(c_void_p(task)) @@ -119,16 +119,16 @@ def wrapper(function): POINTER(POINTER(JSValueRef)), c_ushort, c_void_p) @wraps(function) - def dummy(callee, new_call, arguments, arguments_count, __user_data__): + def dummy(callee, new_call, args, arg_count, __user_data__): if new_call and not constructor: throw(create_type_error(f"{name} is not constructor")) else: try: - if arguments_count == 0: + if arg_count == 0: this = None else: - this = arguments[0] - return function(*c_array_to_iterator(arguments, arguments_count, 1), + this = args[0] + return function(*c_array_to_iterator(args, arg_count, 1), this=this, callee=callee, new_call=bool(new_call)) @@ -395,14 +395,13 @@ def inspect(value: JSValueRef, indent="\t") -> str: def set_promise_callback(callback): __callback_refs.append(callback) - c = chakra_core.JsSetPromiseContinuationCallback(cast(callback, c_void_p), - 0) + c = chakra_core.JsSetPromiseContinuationCallback(callback, 0) assert c == 0, descriptive_message(c, "set_promise_callback") def set_rejections_callback(callback): __callback_refs.append(callback) - c = chakra_core.JsSetHostPromiseRejectionTracker(cast(callback, c_void_p), 0) + c = chakra_core.JsSetHostPromiseRejectionTracker(callback, 0) assert c == 0, descriptive_message(c, "set_rejections_callback") @@ -435,7 +434,7 @@ def set_fetch_importing_module_from_script_callback(callback, module=0): def set_url(record, url): url = cast(url, c_void_p) - __callback_refs.append(url) # not callback, but why not to keep the reference? + __callback_refs.append(url) # not callback, but why not to keep it? c = chakra_core.JsSetModuleHostInfo(record, 6, url) assert c == 0, descriptive_message(c, "set_url") @@ -485,7 +484,7 @@ def parse_module_source(record: c_void_p, context_count: int, script: c_char_p, script_len: int, - flags: int): + flags: int = 0): ex = c_void_p() # print(len(script)) # print(script_len) @@ -595,7 +594,8 @@ def js_value_to_string(value: JSValueRef) -> str: """ Converts JavaScript value to python string """ - # Convert script result to String in JavaScript; redundant if script returns a String + # Convert script result to String in JavaScript; + # redundant if script returns a String result_js_string: JSValueRef = c_void_p() chakra_core.JsConvertValueToString(value, byref(result_js_string)) diff --git a/python_chakra/index.py b/python_chakra/index.py index 9c3d10e..c495166 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -32,7 +32,7 @@ def __init__(self, *, value: JSValueRef = None, raise TypeError() set_property(js_globalThis, attach_to_global_as, self) - def set_property(self, name: Union[str, int], value: JSValueRef) -> Object: + def set_property(self, name: Union[str, int], value: JSValueRef): set_property(self, name, value) return self @@ -42,7 +42,7 @@ def get_property(self, name: Union[str, int]) -> JSValueRef: def __getitem__(self, name: Union[str, int]) -> JSValueRef: return self.get_property(name) - def __setitem__(self, name: Union[str, int], value: JSValueRef) -> Object: + def __setitem__(self, name: Union[str, int], value: JSValueRef): return self.set_property(name, value) def is_object(self): diff --git a/python_chakra/modules.py b/python_chakra/modules.py index 56a15ab..3efeaf3 100644 --- a/python_chakra/modules.py +++ b/python_chakra/modules.py @@ -18,8 +18,11 @@ class ModuleFIFOQueue: pass -PathResolverFunctionType = Callable[[Callable[[None, str, str], URL], str, str], URL] -LoaderFunctionType = Callable[[Callable[[None, str], URL], str], URL] +_DefaultPathResolverFunction = Callable[[None, str, str], URL] +PathResolverFunctionType = Callable[[_DefaultPathResolverFunction, + str, str], URL] +_DefaultLoaderFunctionType = Callable[[None, str], URL] +LoaderFunctionType = Callable[[_DefaultLoaderFunctionType, str], URL] ModuleOrNone = Union[JavaScriptModule, None] @@ -54,11 +57,7 @@ def __init__(self, promise_queue: PromiseFIFOQueue, def parse(self): script = str_to_array(self.code + "\0", encoding="UTF-16") # TODO: Properly handle syntax errors of the module code - parse_module_source(self, - self.cookie, - script, - len(script), - 0) + parse_module_source(self, self.cookie, script, len(script)) if self.root: self.__module_queue.exec() @@ -73,10 +72,10 @@ def dispose(self): self.spec = None -def default_path_resolver(_, base, spec) -> URL: +def default_path_resolver(_, base: str, spec: str) -> URL: if is_valid_url(spec): return parse_url(spec) - elif spec.startswith("/") or spec.startswith("./") or spec.startswith("../"): + elif spec.startswith(("/", "./", "../")): return parse_url(spec, base=base) raise SyntaxError("Cannot resolve path %s" % spec) From c19e15ff0ec071722fd98a50f0a52c8107fbb1ea Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Thu, 20 May 2021 23:49:12 +0300 Subject: [PATCH 10/13] test numbers and add divmod(<>) --- examples/main.py | 5 +++++ examples/test.js | 5 +++++ python_chakra/dll_wrapper.py | 13 ++++++++----- python_chakra/index.py | 11 ++++++++--- setup.py | 13 ++++++------- 5 files changed, 32 insertions(+), 15 deletions(-) diff --git a/examples/main.py b/examples/main.py index 842ab95..0faa6eb 100644 --- a/examples/main.py +++ b/examples/main.py @@ -25,11 +25,16 @@ def error(*args, **_): @javascript_method() def write_(*args, **_): print(*map(js_value_to_string, args), end=None) + + @javascript_method() + def count(a: JSValueRef = None, b: JSValueRef = None, **_): + return (Number(a) + Number(b))._as_parameter_.value global_this["writeln"] = create_function(log, "log", attach_to_global_as="print", attach_to=console) create_function(warn, "warn", attach_to=console) create_function(error, "error", attach_to=console) create_function(write_, "write", attach_to_global_as=True) + create_function(count, "count", attach_to_global_as=True) runtime.exec_module("./examples/test.js") diff --git a/examples/test.js b/examples/test.js index 4d48f6b..a951384 100644 --- a/examples/test.js +++ b/examples/test.js @@ -24,5 +24,10 @@ await test() await import("./test3.js").then(() => console.log(".then: After dynamic import")); console.log("After await dynamic import"); await test() +try { + console.log("Count:", count(1, 4)) +} catch (error) { + console.error(error); +} // ; diff --git a/python_chakra/dll_wrapper.py b/python_chakra/dll_wrapper.py index a73f2fb..58355c8 100644 --- a/python_chakra/dll_wrapper.py +++ b/python_chakra/dll_wrapper.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re from ctypes import * from enum import IntEnum @@ -5,7 +7,7 @@ from traceback import format_exception from typing import Iterable, List, Literal, Union -from .utils import chakra_core, FIFOQueue +from .utils import FIFOQueue, chakra_core class ErrorCodesEnum(IntEnum): @@ -49,6 +51,7 @@ def run(self, task): StrictModeType = Union[bool, Literal[0, 1]] JSValueRef = c_void_p JSRef = c_void_p +_NumberLike = Union[POINTER(JSValueRef), JSValueRef, float, int] c_func_type = chakra_core._FuncPtr c_true = 1 c_false = 0 @@ -344,9 +347,9 @@ def js_eval(code): return call(js_eval_function, str_to_js_string(code)) -def to_number(value: Union[JSValueRef, float, int]) -> JSValueRef: +def to_number(value: _NumberLike) -> JSValueRef: number = JSValueRef() - if type(value) is JSValueRef: + if type(value) is POINTER(JSValueRef) or type(value) is JSValueRef: c = chakra_core.JsConvertValueToNumber(value, byref(number)) else: c = chakra_core.JsDoubleToNumber(c_longdouble(value), byref(number)) @@ -354,9 +357,9 @@ def to_number(value: Union[JSValueRef, float, int]) -> JSValueRef: return number -def to_double(value: Union[JSValueRef, float, int]) -> float: +def to_double(value: _NumberLike) -> float: number = c_longdouble() - c = chakra_core.JsNumbertToDouble(to_number(value), byref(number)) + c = chakra_core.JsNumberToDouble(to_number(value), byref(number)) assert c == 0, descriptive_message(c, "to_double") return number.value diff --git a/python_chakra/index.py b/python_chakra/index.py index c495166..7856f35 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -1,11 +1,13 @@ from __future__ import annotations + +from math import ceil, floor, trunc from os import getcwd from sys import maxsize -from math import floor, ceil, trunc from typing import Any, Optional, Tuple, Union + from .dll_wrapper import * -from .modules import (JavaScriptModule, ModuleRuntime, - default_loader, default_path_resolver) +from .modules import (JavaScriptModule, ModuleRuntime, default_loader, + default_path_resolver) from .utils import ValueSkeleton @@ -123,6 +125,9 @@ def __truediv__(self, other: NumberLike) -> Number: def __floordiv__(self, other: NumberLike) -> Number: return Number(self).__ifloordiv__(other) + def __divmod__(self, other: NumberLike) -> Tuple[Number, Number]: + return (self // other, self % other) + def __mod__(self, other: NumberLike) -> Number: return Number(self).__imod__(other) diff --git a/setup.py b/setup.py index 89da9ce..1a98bb4 100644 --- a/setup.py +++ b/setup.py @@ -1,17 +1,15 @@ """ Parts of this file were taken from the discord.py project -(https://github.com/Rapptz/discord.py) which have been permitted for use under the -MIT license. +(https://github.com/Rapptz/discord.py) +which have been permitted for use under the MIT license. """ from setuptools import setup import re -requirements = [] with open('requirements.txt') as f: requirements = f.read().splitlines() -version = '' with open('python_chakra/__init__.py') as f: version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) @@ -19,14 +17,15 @@ if not version: raise RuntimeError('version is not set') -readme = '' with open('readme.md') as f: readme = f.read() +repo = "https://github.com/MadProbe/PythonChakra" + setup(name='python-chakra', author='MadProbe', - url='https://github.com/MadProbe/PythonChakra', - project_urls={"Issue tracker": "https://github.com/MadProbe/PythonChakra/issues"}, + url=repo, + project_urls={"Issue tracker": f"{repo}/issues"}, version=version, packages=['python_chakra', 'python_chakra.utils'], license='MIT', From a0db732fda8c6b3dedde632099a256b7a94acef0 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Fri, 21 May 2021 23:43:05 +0300 Subject: [PATCH 11/13] fix a small bug --- python_chakra/dll_wrapper.py | 3 +-- python_chakra/modules.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/python_chakra/dll_wrapper.py b/python_chakra/dll_wrapper.py index 58355c8..a44bee5 100644 --- a/python_chakra/dll_wrapper.py +++ b/python_chakra/dll_wrapper.py @@ -486,7 +486,6 @@ def is_constructor(value: JSValueRef) -> bool: def parse_module_source(record: c_void_p, context_count: int, script: c_char_p, - script_len: int, flags: int = 0): ex = c_void_p() # print(len(script)) @@ -494,7 +493,7 @@ def parse_module_source(record: c_void_p, c = chakra_core.JsParseModuleSource(record, context_count, script, - len(script) - 2, + len(script), flags, byref(ex)) # print(js_value_to_string(ex)) diff --git a/python_chakra/modules.py b/python_chakra/modules.py index 3efeaf3..b6c7002 100644 --- a/python_chakra/modules.py +++ b/python_chakra/modules.py @@ -55,9 +55,9 @@ def __init__(self, promise_queue: PromiseFIFOQueue, set_url(module, self.spec) def parse(self): - script = str_to_array(self.code + "\0", encoding="UTF-16") - # TODO: Properly handle syntax errors of the module code - parse_module_source(self, self.cookie, script, len(script)) + script = str_to_array(self.code, encoding="UTF-16") + # TODO: Properly handle syntax errors + parse_module_source(self, self.cookie, script) if self.root: self.__module_queue.exec() From 0cb128d02cee647df4414e1ea5cf5a81591156f0 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Sat, 22 May 2021 00:00:14 +0300 Subject: [PATCH 12/13] add >, <, >=, <= ops --- python_chakra/index.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/python_chakra/index.py b/python_chakra/index.py index 7856f35..a4355b0 100644 --- a/python_chakra/index.py +++ b/python_chakra/index.py @@ -144,6 +144,18 @@ def __eq__(self, other: NumberLike) -> bool: def __ne__(self, other: NumberLike) -> bool: return self.value != _to_float(other) + def __gt__(self, other: NumberLike) -> bool: + return self.value > _to_float(other) + + def __ge__(self, other: NumberLike) -> bool: + return self.value >= _to_float(other) + + def __lt__(self, other: NumberLike) -> bool: + return self.value < _to_float(other) + + def __le__(self, other: NumberLike) -> bool: + return self.value <= _to_float(other) + def __abs__(self) -> Number: return Number(abs(self.value)) From e0050ed19a845497a2d3c9d7d7ebfc5063bbf619 Mon Sep 17 00:00:00 2001 From: MadProbe <49519179+MadProbe@users.noreply.github.com> Date: Sat, 22 May 2021 00:30:42 +0300 Subject: [PATCH 13/13] less boilerplate code --- examples/main.py | 2 +- python_chakra/dll_wrapper.py | 13 +++++++++---- python_chakra/modules.py | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/examples/main.py b/examples/main.py index 0faa6eb..fdd29ad 100644 --- a/examples/main.py +++ b/examples/main.py @@ -28,7 +28,7 @@ def write_(*args, **_): @javascript_method() def count(a: JSValueRef = None, b: JSValueRef = None, **_): - return (Number(a) + Number(b))._as_parameter_.value + return Number(a) + Number(b) global_this["writeln"] = create_function(log, "log", attach_to_global_as="print", attach_to=console) diff --git a/python_chakra/dll_wrapper.py b/python_chakra/dll_wrapper.py index a44bee5..ed06d53 100644 --- a/python_chakra/dll_wrapper.py +++ b/python_chakra/dll_wrapper.py @@ -131,10 +131,15 @@ def dummy(callee, new_call, args, arg_count, __user_data__): this = None else: this = args[0] - return function(*c_array_to_iterator(args, arg_count, 1), - this=this, - callee=callee, - new_call=bool(new_call)) + r = function(*c_array_to_iterator(args, arg_count, 1), + this=this, + callee=callee, + new_call=bool(new_call)) + while r is not None and hasattr(r, "_as_parameter_"): + r = r._as_parameter_ + if type(r) is JSValueRef: + r = r.value + return r except Exception as ex: message = format_exception(type(ex), ex, ex.__traceback__) throw(create_error('\n'.join(message))) diff --git a/python_chakra/modules.py b/python_chakra/modules.py index b6c7002..ecb7c4e 100644 --- a/python_chakra/modules.py +++ b/python_chakra/modules.py @@ -93,7 +93,7 @@ def default_loader(_, url: URL): if name == "posix": href = "/" + href with open(href, 'r') as file: - return ''.join(file.readlines()) + return file.read() else: raise TypeError(f"Path scheme \"{scheme}\" is not supported")