pyscript-programming-language 1.15.0.dev1__py3-none-any.whl → 1.15.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. pyscript/__main__.py +11 -9
  2. pyscript/core/constants.py +2 -1
  3. pyscript/core/editor/gui.py +16 -8
  4. pyscript/core/handlers.py +13 -1
  5. pyscript/core/highlight.py +0 -2
  6. pyscript/core/interpreter.py +15 -26
  7. pyscript/core/mapping.py +8 -5
  8. pyscript/core/nodes.py +56 -19
  9. pyscript/core/parser.py +92 -61
  10. pyscript/core/pysbuiltins.py +38 -33
  11. pyscript/core/pystypes.py +9 -4
  12. pyscript/core/runner.py +4 -3
  13. pyscript/core/shell.py +15 -17
  14. pyscript/core/token.py +1 -0
  15. pyscript/core/utils/debug.py +2 -2
  16. pyscript/core/utils/decorators.py +7 -5
  17. pyscript/core/utils/generic.py +3 -2
  18. pyscript/core/utils/module.py +3 -0
  19. pyscript/core/utils/string.py +7 -3
  20. pyscript/core/version.py +13 -2
  21. pyscript/lib/brainfuck.pys +17 -23
  22. pyscript/lib/dual/__init__.pys +5 -3
  23. pyscript/lib/dual/py_dual.py +103 -64
  24. pyscript/lib/explorer.pys +53 -75
  25. pyscript/lib/getch/__init__.pys +2 -1
  26. pyscript/lib/getch/py_getch.py +7 -4
  27. pyscript/lib/history.pys +15 -5
  28. pyscript/lib/site.pys +19 -19
  29. pyscript/other/message.txt +72 -0
  30. {pyscript_programming_language-1.15.0.dev1.dist-info → pyscript_programming_language-1.15.2.dist-info}/METADATA +28 -24
  31. {pyscript_programming_language-1.15.0.dev1.dist-info → pyscript_programming_language-1.15.2.dist-info}/RECORD +34 -33
  32. {pyscript_programming_language-1.15.0.dev1.dist-info → pyscript_programming_language-1.15.2.dist-info}/WHEEL +1 -1
  33. /pyscript/{site-packages → lib}/this.pys +0 -0
  34. {pyscript_programming_language-1.15.0.dev1.dist-info → pyscript_programming_language-1.15.2.dist-info}/top_level.txt +0 -0
pyscript/core/parser.py CHANGED
@@ -401,7 +401,22 @@ class PysParser(Pys):
401
401
  )
402
402
 
403
403
  def instanceof(self) -> PysParserResult:
404
- return self.binary_operator(self.bitwise, (TOKENS['KEYWORD'], 'instanceof'), instanceof=True)
404
+ result = PysParserResult()
405
+
406
+ node = result.try_register(self.binary_operator(
407
+ self.bitwise,
408
+ (TOKENS['KEYWORD'], 'instanceof'),
409
+ (TOKENS['KEYWORD'], 'not'),
410
+ instanceof=True
411
+ ))
412
+
413
+ if result.error:
414
+ return result
415
+ elif node:
416
+ return result.success(node)
417
+
418
+ self.reverse(result.to_reverse_count)
419
+ return self.bitwise()
405
420
 
406
421
  def bitwise(self) -> PysParserResult:
407
422
  return self.binary_operator(
@@ -1182,66 +1197,71 @@ class PysParser(Pys):
1182
1197
 
1183
1198
  self.skip(result)
1184
1199
 
1185
- if self.current_token.type != TOKENS['LEFT_CURLY']:
1186
- return result.failure(self.new_error("expected '{'"))
1200
+ cases = []
1201
+ default = None
1187
1202
 
1188
- left_bracket_token = self.current_token
1203
+ if self.current_token.type == TOKENS['SEMICOLON']:
1204
+ result.register_advancement()
1205
+ self.advance()
1189
1206
 
1190
- result.register_advancement()
1191
- self.advance()
1192
- self.skip(result)
1207
+ elif self.current_token.type == TOKENS['LEFT_CURLY']:
1208
+ left_bracket_token = self.current_token
1193
1209
 
1194
- cases = []
1195
- default = None
1210
+ result.register_advancement()
1211
+ self.advance()
1212
+ self.skip(result)
1196
1213
 
1197
- while True:
1214
+ while True:
1198
1215
 
1199
- if self.current_token.match(TOKENS['KEYWORD'], 'case'):
1200
- result.register_advancement()
1201
- self.advance()
1202
- self.skip(result)
1216
+ if self.current_token.match(TOKENS['KEYWORD'], 'case'):
1217
+ result.register_advancement()
1218
+ self.advance()
1219
+ self.skip(result)
1203
1220
 
1204
- case = result.register(self.walrus(), True)
1205
- if result.error:
1206
- return result
1221
+ case = result.register(self.walrus(), True)
1222
+ if result.error:
1223
+ return result
1207
1224
 
1208
- self.skip(result)
1225
+ self.skip(result)
1209
1226
 
1210
- if self.current_token.type != TOKENS['COLON']:
1211
- return result.failure(self.new_error("expected ':'"))
1227
+ if self.current_token.type != TOKENS['COLON']:
1228
+ return result.failure(self.new_error("expected ':'"))
1212
1229
 
1213
- result.register_advancement()
1214
- self.advance()
1230
+ result.register_advancement()
1231
+ self.advance()
1215
1232
 
1216
- body = result.register(self.statements())
1217
- if result.error:
1218
- return result
1233
+ body = result.register(self.statements())
1234
+ if result.error:
1235
+ return result
1219
1236
 
1220
- cases.append((case, body))
1237
+ cases.append((case, body))
1221
1238
 
1222
- elif self.current_token.match(TOKENS['KEYWORD'], 'default', 'else'):
1223
- result.register_advancement()
1224
- self.advance()
1225
- self.skip(result)
1239
+ elif self.current_token.match(TOKENS['KEYWORD'], 'default', 'else'):
1240
+ result.register_advancement()
1241
+ self.advance()
1242
+ self.skip(result)
1226
1243
 
1227
- if self.current_token.type != TOKENS['COLON']:
1228
- return result.failure(self.new_error("expected ':'"))
1244
+ if self.current_token.type != TOKENS['COLON']:
1245
+ return result.failure(self.new_error("expected ':'"))
1229
1246
 
1230
- result.register_advancement()
1231
- self.advance()
1247
+ result.register_advancement()
1248
+ self.advance()
1232
1249
 
1233
- default = result.register(self.statements())
1234
- if result.error:
1235
- return result
1250
+ default = result.register(self.statements())
1251
+ if result.error:
1252
+ return result
1236
1253
 
1237
- break
1254
+ break
1238
1255
 
1239
- else:
1240
- break
1256
+ else:
1257
+ break
1241
1258
 
1242
- self.close_bracket(result, left_bracket_token)
1243
- if result.error:
1244
- return result
1259
+ self.close_bracket(result, left_bracket_token)
1260
+ if result.error:
1261
+ return result
1262
+
1263
+ else:
1264
+ return result.failure(self.new_error("expected '{'"))
1245
1265
 
1246
1266
  return result.success(
1247
1267
  PysSwitchNode(
@@ -2325,15 +2345,12 @@ class PysParser(Pys):
2325
2345
  if result.error:
2326
2346
  return result
2327
2347
 
2328
- if body.__class__ == PysStatementsNode:
2329
- setimuattr(
2330
- body, 'position',
2331
- PysPosition(
2332
- self.current_token.position.file,
2333
- left_bracket_token.position.start,
2334
- end
2335
- )
2336
- )
2348
+ if body.__class__ is PysStatementsNode:
2349
+ setimuattr(body, 'position', PysPosition(
2350
+ self.current_token.position.file,
2351
+ left_bracket_token.position.start,
2352
+ end
2353
+ ))
2337
2354
 
2338
2355
  return result.success(body)
2339
2356
 
@@ -2350,10 +2367,7 @@ class PysParser(Pys):
2350
2367
 
2351
2368
  body = result.register(self.statement())
2352
2369
  if result.error:
2353
- return result.failure(
2354
- self.new_error("expected statement, expression, '{', or ';'"),
2355
- fatal=False
2356
- )
2370
+ return result.failure(self.new_error("expected statement, expression, '{', or ';'"), fatal=False)
2357
2371
 
2358
2372
  return result.success(body)
2359
2373
 
@@ -2393,7 +2407,7 @@ class PysParser(Pys):
2393
2407
  self.skip_expression(result)
2394
2408
 
2395
2409
  if not self.current_token.match(TOKENS['KEYWORD'], 'in'):
2396
- return result.failure(self.new_error("expected 'in'"))
2410
+ return result.failure(self.new_error("expected 'in' or 'instanceof'"))
2397
2411
 
2398
2412
  operations[-1] = PysToken(
2399
2413
  TOKENS['EXCLAMATION_GREATER_THAN'],
@@ -2483,11 +2497,28 @@ class PysParser(Pys):
2483
2497
  TOKENS['DOUBLE_AMPERSAND' if token.value == 'and' else 'DOUBLE_PIPE'],
2484
2498
  token.position
2485
2499
  )
2500
+
2486
2501
  elif instanceof:
2487
- operand = PysToken(
2488
- TOKENS['INSTANCEOF'],
2489
- token.position
2490
- )
2502
+
2503
+ if token.match(TOKENS['KEYWORD'], 'not'):
2504
+ result.register_advancement()
2505
+ self.advance()
2506
+ self.skip_expression(result)
2507
+
2508
+ if not self.current_token.match(TOKENS['KEYWORD'], 'instanceof'):
2509
+ return result.failure(self.new_error("expected 'instanceof'"), fatal=False)
2510
+
2511
+ operand = PysToken(
2512
+ TOKENS['NOT_INSTANCEOF'],
2513
+ token.position
2514
+ )
2515
+
2516
+ else:
2517
+ operand = PysToken(
2518
+ TOKENS['INSTANCEOF'],
2519
+ token.position
2520
+ )
2521
+
2491
2522
  else:
2492
2523
  operand = token
2493
2524
 
@@ -37,6 +37,7 @@ static_wrapper_function = (staticmethod,)
37
37
  wrapper_function = (MethodType, PysPythonFunction, classmethod)
38
38
  python_function = (BuiltinFunctionType, BuiltinMethodType, FunctionType)
39
39
 
40
+ pynewobject = object.__new__
40
41
  pyvars = builtins.vars
41
42
  pydir = builtins.dir
42
43
 
@@ -55,34 +56,36 @@ def _magic_method(
55
56
  return NotImplemented
56
57
 
57
58
  def _unpack_comprehension_function(context: PysContext, position: PysPosition, function: Callable) -> Callable:
58
- check = function
59
- final = function
60
- offset = 0
59
+ check = final = function
60
+ length = offset = 0
61
61
 
62
- if isinstance(function, wrapper_function):
62
+ b_isinstance = isinstance
63
+
64
+ if b_isinstance(function, wrapper_function):
63
65
  check = function.__func__
64
- offset += 2 if isinstance(function, PysPythonFunction) else 1
65
- elif isinstance(function, static_wrapper_function):
66
+ offset += 2 if b_isinstance(function, PysPythonFunction) else 1
67
+ elif b_isinstance(function, static_wrapper_function):
66
68
  check = function.__func__
69
+ elif b_isinstance(function, type):
70
+ offset += 1
71
+ check = function.__new__
72
+ if check is pynewobject:
73
+ check = function.__init__
67
74
 
68
- if isinstance(check, PysFunction):
75
+ if b_isinstance(check, PysFunction):
69
76
  length = max(check.__code__.parameters_length - offset, 0)
70
- if length == 0:
71
- def final(item):
72
- return function()
73
- elif length > 1:
74
- def final(item):
75
- return function(*item)
76
-
77
- elif isinstance(check, python_function):
77
+ elif b_isinstance(check, python_function):
78
78
  parameters = signature(check).parameters
79
79
  length = max(len(parameters) - offset, 0)
80
- if length == 0:
81
- def final(item):
82
- return function()
83
- elif length > 1 or any(p.kind == p.VAR_POSITIONAL for p in parameters.values()):
84
- def final(item):
85
- return function(*item)
80
+ if length == 1 and any(p.kind == p.VAR_POSITIONAL for p in parameters.values()):
81
+ length = 2
82
+
83
+ if length == 0:
84
+ def final(item):
85
+ return function()
86
+ elif length > 1:
87
+ def final(item):
88
+ return function(*item)
86
89
 
87
90
  handle_call(function, context, position)
88
91
  return final
@@ -116,8 +119,7 @@ def require(context, position, name):
116
119
  name: A name or path of the module to be imported.
117
120
  """
118
121
 
119
- name, *other_components = normstr(name).split('>')
120
-
122
+ name, *components = normstr(name).split('>')
121
123
  filename = context.file.name
122
124
  path, module_path = find_module_path(filename, name)
123
125
 
@@ -189,9 +191,8 @@ def require(context, position, name):
189
191
  finally:
190
192
  loading_modules.discard(module_path)
191
193
 
192
- for component in other_components:
194
+ for component in components:
193
195
  module = getattr(module, component)
194
-
195
196
  return module
196
197
 
197
198
  @PysBuiltinFunction
@@ -386,7 +387,7 @@ def dir(context, position, *args):
386
387
  def exec(context, position, source, globals=None):
387
388
 
388
389
  """
389
- exec(source: str | bytes, globals: Optional[dict]) -> None
390
+ exec(source: str | bytes | bytearray | Iterable | IOBase, globals: Optional[dict] = None) -> None
390
391
 
391
392
  Executes PyScript code statements from the given source.
392
393
 
@@ -416,7 +417,7 @@ def exec(context, position, source, globals=None):
416
417
  def eval(context, position, source, globals=None):
417
418
 
418
419
  """
419
- eval(source: str | bytes, globals: Optional[dict]) -> None
420
+ eval(source: str | bytes | bytearray | Iterable | IOBase, globals: Optional[dict] = None) -> None
420
421
 
421
422
  Executes a PyScript code expression from the given source.
422
423
 
@@ -460,7 +461,8 @@ def ce(context, position, a, b, *, rel_tol=1e-9, abs_tol=0):
460
461
  abs_tol: maximum difference for being considered "close", regardless of the magnitude of the input values.
461
462
  """
462
463
 
463
- if isinstance(a, real_complex_number) and isinstance(b, real_complex_number):
464
+ b_isinstance = isinstance
465
+ if b_isinstance(a, real_complex_number) and b_isinstance(b, real_complex_number):
464
466
  return isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)
465
467
 
466
468
  result = _magic_method(context, position, a, '__ce__', b, rel_tol=rel_tol, abs_tol=abs_tol)
@@ -494,7 +496,8 @@ def nce(context, position, a, b, *, rel_tol=1e-9, abs_tol=0):
494
496
  abs_tol: maximum difference for being considered "close", regardless of the magnitude of the input values.
495
497
  """
496
498
 
497
- if isinstance(a, real_complex_number) and isinstance(b, real_complex_number):
499
+ b_isinstance = isinstance
500
+ if b_isinstance(a, real_complex_number) and b_isinstance(b, real_complex_number):
498
501
  return not isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol)
499
502
 
500
503
  result = _magic_method(context, position, a, '__nce__', b, rel_tol=rel_tol, abs_tol=abs_tol)
@@ -525,9 +528,10 @@ def increment(context, position, object):
525
528
  __increment__ method, which if unsuccessful will throw a TypeError.
526
529
  """
527
530
 
528
- if isinstance(object, real_number):
531
+ b_isinstance = isinstance
532
+ if b_isinstance(object, real_number):
529
533
  return object + 1
530
- elif isinstance(object, sequence):
534
+ elif b_isinstance(object, sequence):
531
535
  return tuple(_pyincrement(context, position, obj) for obj in object)
532
536
 
533
537
  result = _magic_method(context, position, object, '__increment__')
@@ -549,9 +553,10 @@ def decrement(context, position, object):
549
553
  call the __decrement__ method, which if unsuccessful will throw a TypeError.
550
554
  """
551
555
 
552
- if isinstance(object, real_number):
556
+ b_isinstance = isinstance
557
+ if b_isinstance(object, real_number):
553
558
  return object - 1
554
- elif isinstance(object, sequence):
559
+ elif b_isinstance(object, sequence):
555
560
  return tuple(_pydecrement(context, position, obj) for obj in object)
556
561
 
557
562
  result = _magic_method(context, position, object, '__decrement__')
pyscript/core/pystypes.py CHANGED
@@ -11,7 +11,6 @@ from .utils.generic import setimuattr, dinit, drepr, dor, dror, dior, dsetitem,
11
11
  from .utils.similarity import diff_get_closest
12
12
  from .utils.string import join
13
13
 
14
- from builtins import help as pyhelp
15
14
  from itertools import islice
16
15
  from types import MethodType
17
16
  from typing import Any, Callable, Optional
@@ -203,7 +202,7 @@ class PysFunction(PysObject):
203
202
  PysTraceback(
204
203
  TypeError(
205
204
  f"{self.__qualname__}() missing {total_missing} required positional argument"
206
- f"{'' if total_missing == 1 else 's'}: {join(missing_arguments, conjunction='and')}"
205
+ f"{'' if total_missing == 1 else 's'}: {join(missing_arguments)}"
207
206
  ),
208
207
  context,
209
208
  position
@@ -311,10 +310,16 @@ class PysPrinter(PysObject):
311
310
 
312
311
  class PysHelper(PysPrinter):
313
312
 
314
- __slots__ = ()
313
+ __slots__ = ('_pyhelp',)
315
314
 
316
315
  def __init__(self) -> None:
317
316
  super().__init__('help')
317
+ try:
318
+ from builtins import help
319
+ except:
320
+ def help(*args, **kwargs):
321
+ raise NotImplementedError('python builtins.help not found')
322
+ setimuattr(self, '_pyhelp', help)
318
323
 
319
324
  def __repr__(self) -> str:
320
325
  return f'Type {self.name}() for interactive help, or {self.name}(object) for help about object.'
@@ -328,4 +333,4 @@ class PysHelper(PysPrinter):
328
333
  "To get the list of builtin functions, types, exceptions, and other objects, type 'help(\"builtins\")'."
329
334
  )
330
335
  else:
331
- pyhelp(*args, **kwargs)
336
+ self._pyhelp(*args, **kwargs)
pyscript/core/runner.py CHANGED
@@ -153,13 +153,14 @@ def pys_runner(
153
153
  pys_sys.flags = context.flags
154
154
 
155
155
  try:
156
- del file, flags, symbol_table, context_parent, context_parent_entry_position, \
157
- lexer, tokens, error, parser, analyzer
156
+ del (
157
+ file, flags, symbol_table, context_parent, context_parent_entry_position, lexer, tokens, error, parser,
158
+ analyzer
159
+ )
158
160
  except:
159
161
  pass
160
162
 
161
163
  visitor_result = get_visitor(node.__class__)(node, context)
162
-
163
164
  if visitor_result.error:
164
165
  return result.failure(visitor_result.error)
165
166
 
pyscript/core/shell.py CHANGED
@@ -233,10 +233,8 @@ try:
233
233
  @singleton
234
234
  class PysHistory(Pys, History):
235
235
 
236
- def __new_singleton__(cls) -> 'PysHistory':
237
- if USE_FILE_HISTORY:
238
- return super().__new__(cls)
239
- raise NotImplementedError('not using file history')
236
+ def __new_singleton__(cls) -> 'PysHistory | InMemoryHistory':
237
+ return super().__new__(cls) if USE_FILE_HISTORY else InMemoryHistory()
240
238
 
241
239
  def _write_history_safely(self, mode: str, function: str, *args) -> None:
242
240
  try:
@@ -254,16 +252,16 @@ try:
254
252
  self._write_history_safely('w', 'write', '')
255
253
  return []
256
254
 
257
- try:
258
- strings = []
259
- lines = []
255
+ strings = []
256
+ lines = []
260
257
 
261
- def add_to_string():
262
- if not lines:
263
- return
264
- strings.insert(0, '\n'.join(lines))
265
- lines.clear()
258
+ def add_to_string():
259
+ if not lines:
260
+ return
261
+ strings.insert(0, '\n'.join(lines))
262
+ lines.clear()
266
263
 
264
+ try:
267
265
  with open(HISTORY_PATH, 'r', encoding=pys_sys.encoding) as file:
268
266
  for line in file:
269
267
  line = line[:-1]
@@ -273,13 +271,15 @@ try:
273
271
  lines.append(line)
274
272
  add_to_string()
275
273
 
276
- if HISTORY_LIMIT > 0 and len(strings) > HISTORY_LIMIT:
274
+ if 0 < HISTORY_LIMIT < len(strings):
277
275
  self._manage_history_capacity(strings)
278
276
 
279
- return strings
280
277
  except:
281
278
  return []
282
279
 
280
+ else:
281
+ return strings
282
+
283
283
  # this function is called within `append_string()` which adds a new string or history entry to `_loaded_strings`
284
284
  # before invoking this function
285
285
  def store_string(self, string: str) -> None:
@@ -290,8 +290,6 @@ try:
290
290
  else:
291
291
  self._manage_history_capacity(self._loaded_strings)
292
292
 
293
- history = (PysHistory if USE_FILE_HISTORY else InMemoryHistory)()
294
-
295
293
  class PysPromptToolkitLineShell(PysLineShell, PromptSession):
296
294
 
297
295
  def __init__(self, ps1: str = '>>> ', ps2: str = '... ', colored: bool = True) -> None:
@@ -381,7 +379,7 @@ try:
381
379
  input_processors=[TabsProcessor(tabstop=4)],
382
380
  key_bindings=key_bindings,
383
381
  prompt_continuation=lambda width, line_number, is_soft_wrap: self._ps2,
384
- history=history,
382
+ history=PysHistory(),
385
383
  **other_keyword
386
384
  )
387
385
 
pyscript/core/token.py CHANGED
@@ -25,6 +25,7 @@ TOKENS = dfrozen({
25
25
  'TYPEOF': 0x08,
26
26
  'INSTANCEOF': 0x09,
27
27
  'NEWLINE': ord('\n'),
28
+ 'NOT_INSTANCEOF': 0x0b,
28
29
  'EXCLAMATION': ord('!'),
29
30
  'COMMENT': ord('#'),
30
31
  'PERCENT': ord('%'),
@@ -40,13 +40,13 @@ def import_readline() -> Literal[False]:
40
40
  USE_NOTEBOOK = False
41
41
 
42
42
  try:
43
- from IPython import get_ipython
43
+ from IPython import get_ipython # type: ignore
44
44
  if type(get_ipython()).__name__ in (
45
45
  'ZMQInteractiveShell', # Jupyter Notebook / Lab
46
46
  'Shell', # Google Colab
47
47
  'TerminalInteractiveShell' # IPython Terminal
48
48
  ):
49
- from IPython.display import clear_output
49
+ from IPython.display import clear_output # type: ignore
50
50
  def clear_shell() -> None:
51
51
  clear_output()
52
52
  USE_NOTEBOOK = True
@@ -13,9 +13,10 @@ class _PysUtilities(Pys):
13
13
  # circular import problem solved
14
14
  from ..cache import pys_sys
15
15
  singletons = pys_sys.singletons
16
- if type(singletons.get(cls, None)) is not cls:
17
- singletons[cls] = cls.__new_singleton__(cls, *args, **kwargs)
18
- return singletons[cls]
16
+ object = singletons.get(cls, None)
17
+ if type(object) is not cls:
18
+ singletons[cls] = object = cls.__new_singleton__(cls, *args, **kwargs)
19
+ return object
19
20
 
20
21
  @staticmethod
21
22
  def readonly_attribute(*args, **kwargs):
@@ -34,10 +35,11 @@ def typecheck(func, *args, **kwargs):
34
35
  if not is_environ(ENV_PYSCRIPT_NO_TYPECHECK):
35
36
  try:
36
37
  from beartype import beartype as typecheck
37
- TYPECHECK_STACK += 1
38
- BEARTYPE = True
39
38
  except:
40
39
  pass
40
+ else:
41
+ TYPECHECK_STACK += 1
42
+ BEARTYPE = True
41
43
 
42
44
  def immutable(cls):
43
45
  cls.__setattr__ = _PysUtilities.readonly_attribute
@@ -1,5 +1,5 @@
1
1
  from inspect import currentframe
2
- from types import FrameType, MappingProxyType, UnionType
2
+ from types import FrameType, UnionType
3
3
  from typing import Any, Optional, Sequence
4
4
 
5
5
  import os
@@ -21,10 +21,11 @@ dclear = dict.clear
21
21
  dget = dict.get
22
22
  dkeys = dict.keys
23
23
  ditems = dict.items
24
- dfrozen = MappingProxyType
25
24
 
26
25
  if sys.version_info >= (3, 15):
27
26
  dfrozen = frozendict
27
+ else:
28
+ from types import MappingProxyType as dfrozen
28
29
 
29
30
  def get_frame(deep: int = 0) -> FrameType | None:
30
31
  deep += 1
@@ -22,6 +22,9 @@ def get_module_candidate(path: str, entry: str = '__init__') -> str | None:
22
22
  return candidate
23
23
 
24
24
  def find_module_path(filename: str | None, name: str, entry: str = '__init__') -> tuple[str | None, str | None]:
25
+ if name in ('_pyscript', 'builtins', 'sys'):
26
+ return None, None
27
+
25
28
  # circular import problem solved
26
29
  from ..cache import pys_sys
27
30
 
@@ -38,11 +38,15 @@ def normstr(obj) -> str:
38
38
 
39
39
  def join(sequence: Sequence[str], conjunction: str = 'and') -> str:
40
40
  length = len(sequence)
41
- if length == 1:
41
+ if length == 0:
42
+ return ''
43
+ elif length == 1:
42
44
  return sequence[0]
43
45
  elif length == 2:
44
- return f'{sequence[0]} {conjunction} {sequence[1]}'
45
- return f'{", ".join(sequence[:-1])}, {conjunction} {sequence[-1]}'
46
+ a, b = sequence
47
+ return f'{a} {conjunction} {b}'
48
+ *others, end = sequence
49
+ return f'{", ".join(others)}, {conjunction} {end}'
46
50
 
47
51
  def indent(string: str, length: int, indent: str = ' ') -> str:
48
52
  prefix = indent * length
pyscript/core/version.py CHANGED
@@ -4,9 +4,11 @@ from .mapping import TAG_VERSION_MAP
4
4
  from .utils.decorators import immutable, inheritable, singleton
5
5
 
6
6
  from re import match as match_regex
7
+ from types import NotImplementedType
8
+ from typing import Any
7
9
 
8
- __version__ = '1.15.0.dev1'
9
- __date__ = '19 October 2026, 11:45 UTC+7'
10
+ __version__ = '1.15.2'
11
+ __date__ = '08 November 2026, 12:30 UTC+7'
10
12
  __author__ = ('AzzamMuhyala',)
11
13
 
12
14
  version = pys_sys.version = f'{__version__} ({__date__})'
@@ -72,6 +74,15 @@ class PysVersionInfo(Pys, tuple):
72
74
  def release(self) -> tuple[int, int, int]:
73
75
  return self[0:3]
74
76
 
77
+ def __add__(self, other: Any) -> NotImplementedType:
78
+ return NotImplemented
79
+
80
+ def __mul__(self, other: Any) -> NotImplementedType:
81
+ return NotImplemented
82
+
83
+ def __rmul__(self, other: Any) -> NotImplementedType:
84
+ return NotImplemented
85
+
75
86
  def __lt__(self, other: tuple) -> bool:
76
87
  return self.release < other
77
88