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/__main__.py CHANGED
@@ -9,10 +9,11 @@ from .core.highlight import (
9
9
  PYGMENTS, HLFMT_HTML, HLFMT_ANSI, HLFMT_BBCODE, pys_highlight, PygmentsPyScriptStyle, PygmentsPyScriptLexer
10
10
  )
11
11
  from .core.runner import _namespace_to_symbol_table, pys_runner, pys_shell
12
- from .core.utils.debug import USE_NOTEBOOK
12
+ from .core.utils.debug import USE_NOTEBOOK, custom_print
13
13
  from .core.utils.generic import is_environ
14
14
  from .core.utils.module import find_module_path, remove_python_path
15
15
  from .core.utils.path import getcwd, base, normpath
16
+ from .core.utils.string import join
16
17
  from .core.version import __version__
17
18
 
18
19
  if PYGMENTS:
@@ -41,7 +42,7 @@ if sys.version_info >= (3, 13):
41
42
  from _colorize import can_colorize
42
43
  COLORIZE = can_colorize()
43
44
  except:
44
- # respect for NO_COLOR, see more here: https://no-color.org
45
+ # rule NO_COLOR, see more here: https://no-color.org
45
46
  COLORIZE = not is_environ('NO_COLOR')
46
47
  else:
47
48
  COLORIZE = True
@@ -138,7 +139,7 @@ parser.add_argument(
138
139
  parser.add_argument(
139
140
  '-t', '--terminal',
140
141
  action='store_true',
141
- help="configure terminal encoding to UTF-8 and enable ANSI escape code processing on Windows"
142
+ help=f"configure terminal encoding to {pys_sys.encoding} and enable ANSI escape code processing on Windows"
142
143
  )
143
144
 
144
145
  parser.add_argument(
@@ -215,7 +216,7 @@ if args.terminal:
215
216
 
216
217
  for fd in (sys.stdout, sys.stderr, sys.stdin):
217
218
  try:
218
- fd.reconfigure(encoding='utf-8')
219
+ fd.reconfigure(encoding=pys_sys.encoding)
219
220
  except:
220
221
  continue
221
222
 
@@ -354,10 +355,11 @@ def execute(file: PysFileBuffer) -> None:
354
355
  code, _ = result.end_process()
355
356
 
356
357
  if inspect:
357
- if sys.stdout.closed or sys.stderr.closed:
358
+ if sys.stderr.closed:
358
359
  code = 1
359
- elif sys.stdin.closed:
360
- print("Can't run interactive shell: sys.stdin closed", file=sys.stderr)
360
+ elif sys.stdout.closed or sys.stdin.closed:
361
+ io_closed = join([f'sys.{std}' for std in ('stdout', 'stdin') if getattr(sys, std).closed])
362
+ custom_print(f"Cannot run interactive shell: {io_closed} closed", file=sys.stderr)
361
363
  code = 1
362
364
  else:
363
365
  code = pys_shell(
@@ -382,14 +384,14 @@ if args.file is not None:
382
384
  elif args.highlight:
383
385
  try:
384
386
  if args.highlight in FORMATER_HIGHLIGHT_MAP:
385
- print(
387
+ custom_print(
386
388
  pys_highlight(
387
389
  source=file,
388
390
  formatter=FORMATER_HIGHLIGHT_MAP[args.highlight]
389
391
  )
390
392
  )
391
393
  else:
392
- print(
394
+ custom_print(
393
395
  highlight(
394
396
  code=file.text,
395
397
  lexer=PygmentsPyScriptLexer(),
@@ -1,10 +1,11 @@
1
1
  import os
2
+ import site
2
3
 
3
4
  PYSCRIPT_PATH = os.path.sep.join(__file__.split(os.path.sep)[:-2])
4
5
  CORE_PATH = os.path.join(PYSCRIPT_PATH, 'core')
5
6
  LIBRARIES_PATH = os.path.join(PYSCRIPT_PATH, 'lib')
6
7
  OTHER_PATH = os.path.join(PYSCRIPT_PATH, 'other')
7
- SITE_PACKAGES_PATH = os.path.join(PYSCRIPT_PATH, 'site-packages')
8
+ SITE_PACKAGES_PATH = os.path.join(site.getusersitepackages(), 'pyscript')
8
9
  ICON_PATH = os.path.join(OTHER_PATH, 'PyScript.ico')
9
10
  GLOBAL_HISTORY_PATH = os.path.join(os.path.expanduser('~'), '.pyscript_history')
10
11
  CONFIGURATIONS_PATH = os.path.join(OTHER_PATH, 'configurations.json')
@@ -151,11 +151,18 @@ try:
151
151
  else:
152
152
  font_name = 'monospace'
153
153
 
154
- self.lexer = PygmentsPyScriptLexer()
155
- self.font = Font(family=font_name, size=boundary(self.get_configuration('gui-size-font', 10), 1, 127))
156
154
  self.line_job = None
155
+ self.font = Font(
156
+ family=font_name,
157
+ size=boundary(self.get_configuration('gui-size-font', 10), 1, 127)
158
+ )
157
159
  self.scrollbar = Scrollbar(self)
158
- self.line_numbers = Canvas(self, width=0, highlightthickness=0, bd=0)
160
+ self.line_numbers = Canvas(
161
+ self,
162
+ width=0,
163
+ highlightthickness=0,
164
+ bd=0
165
+ )
159
166
  self.text = Text(
160
167
  self,
161
168
  undo=True,
@@ -217,7 +224,8 @@ try:
217
224
  self.wm_protocol('WM_DELETE_WINDOW', on_close)
218
225
 
219
226
  if PYGMENTS and colored:
220
- self.update_job = None
227
+ self.lexer = PygmentsPyScriptLexer()
228
+ self.refresh_job = None
221
229
 
222
230
  tag_configure = self.text.tag_configure
223
231
  for token, style in PygmentsPyScriptStyle.list_styles():
@@ -226,7 +234,7 @@ try:
226
234
  tag_configure(str(token), foreground=f'#{color}')
227
235
 
228
236
  def refresh_highlight():
229
- self.update_job = None
237
+ self.refresh_job = None
230
238
 
231
239
  index = '1.0'
232
240
 
@@ -248,9 +256,9 @@ try:
248
256
 
249
257
  def update():
250
258
  self.title(f'PyScript {__version__} - {self.basename}{"*" if self.modified else ""}')
251
- if self.update_job is not None:
252
- self.after_cancel(self.update_job)
253
- self.update_job = self.after(500, refresh_highlight)
259
+ if self.refresh_job is not None:
260
+ self.after_cancel(self.refresh_job)
261
+ self.refresh_job = self.after(1000, refresh_highlight)
254
262
 
255
263
  else:
256
264
  def update():
pyscript/core/handlers.py CHANGED
@@ -1,15 +1,27 @@
1
1
  from .cache import pys_sys
2
- from .constants import ENV_PYSCRIPT_WITH_GIL
2
+ from .constants import SITE_PACKAGES_PATH, ENV_PYSCRIPT_WITH_GIL
3
3
  from .context import PysContext
4
4
  from .position import PysPosition
5
5
  from .pystypes import PysFunction
6
6
  from .utils.generic import is_environ
7
7
 
8
+ from site import ENABLE_USER_SITE
8
9
  from types import MethodType
9
10
  from typing import Any
10
11
 
12
+ import os
13
+
11
14
  wrapper_function = (MethodType, classmethod, staticmethod)
12
15
 
16
+ if ENABLE_USER_SITE:
17
+ try:
18
+ if os.path.isfile(SITE_PACKAGES_PATH):
19
+ os.remove(SITE_PACKAGES_PATH)
20
+ if not os.path.exists(SITE_PACKAGES_PATH):
21
+ os.mkdir(SITE_PACKAGES_PATH)
22
+ except:
23
+ pass
24
+
13
25
  GIL = pys_sys.gil = False
14
26
  if is_environ(ENV_PYSCRIPT_WITH_GIL):
15
27
  try:
@@ -28,7 +28,6 @@ BUILTIN_FUNCTIONS = frozenset(
28
28
  )
29
29
 
30
30
  try:
31
- # if pygments module already exists
32
31
  from pygments.lexer import RegexLexer, include, bygroups
33
32
  from pygments.style import Style
34
33
  from pygments.token import (
@@ -114,7 +113,6 @@ try:
114
113
  Comment.Single: HIGHLIGHT_MAP['comment'],
115
114
  Error: HIGHLIGHT_MAP['invalid'],
116
115
  Generic.Prompt: HIGHLIGHT_MAP['keyword-other'],
117
-
118
116
  Punctuation.Brackets0: HIGHLIGHT_MAP['brackets-0'],
119
117
  Punctuation.Brackets1: HIGHLIGHT_MAP['brackets-1'],
120
118
  Punctuation.Brackets2: HIGHLIGHT_MAP['brackets-2']
@@ -308,17 +308,19 @@ def visit_BinaryOperatorNode(node: PysBinaryOperatorNode, context: PysContext) -
308
308
  result._context = context
309
309
  result._position = node.position
310
310
  with result:
311
- return_right = True
312
311
 
313
312
  if otype == T_OR:
314
313
  if left:
315
314
  return result.success(left)
315
+ return_right = True
316
316
  elif otype == T_AND:
317
317
  if not left:
318
318
  return result.success(left)
319
+ return_right = True
319
320
  elif otype == T_NULLISH:
320
321
  if left is not None:
321
322
  return result.success(left)
323
+ return_right = True
322
324
  else:
323
325
  return_right = False
324
326
 
@@ -582,11 +584,8 @@ def visit_SwitchNode(node: PysSwitchNode, context: PysContext) -> PysRunTimeResu
582
584
 
583
585
  if (fall_through or no_match_found) and (ndefault := node.default):
584
586
  register(get_visitor(ndefault.__class__)(ndefault, context))
585
- if should_return():
586
- if result.should_break:
587
- result.should_break = False
588
- else:
589
- return result
587
+ if should_return() and not result.should_break:
588
+ return result
590
589
 
591
590
  return result.success(None)
592
591
 
@@ -598,13 +597,13 @@ def visit_MatchNode(node: PysMatchNode, context: PysContext) -> PysRunTimeResult
598
597
  should_return = result.should_return
599
598
  ntarget = node.target
600
599
 
601
- compare = False
602
-
603
600
  if ntarget:
604
601
  target = register(get_visitor(ntarget.__class__)(ntarget, context))
605
602
  if should_return():
606
603
  return result
607
604
  compare = True
605
+ else:
606
+ compare = False
608
607
 
609
608
  for ncondition, nvalue in node.cases:
610
609
  condition = register(get_visitor(ncondition.__class__)(ncondition, context))
@@ -614,7 +613,7 @@ def visit_MatchNode(node: PysMatchNode, context: PysContext) -> PysRunTimeResult
614
613
  result._position = ncondition.position
615
614
  with result:
616
615
 
617
- if target == condition if compare else (True if condition else False):
616
+ if (target == condition) if compare else condition:
618
617
  value = register(get_visitor(nvalue.__class__)(nvalue, context))
619
618
  if should_return():
620
619
  return result
@@ -657,7 +656,6 @@ def visit_TryNode(node: PysTryNode, context: PysContext) -> PysRunTimeResult:
657
656
  failure(None)
658
657
 
659
658
  for (targets, tparameter), body in node.catches:
660
- handle_exception = True
661
659
  stop = False
662
660
 
663
661
  if targets:
@@ -686,6 +684,9 @@ def visit_TryNode(node: PysTryNode, context: PysContext) -> PysRunTimeResult:
686
684
  handle_exception = True
687
685
  break
688
686
 
687
+ else:
688
+ handle_exception = True
689
+
689
690
  if stop:
690
691
  break
691
692
 
@@ -902,10 +903,7 @@ def visit_ForNode(node: PysForNode, context: PysContext) -> PysRunTimeResult:
902
903
  if should_return():
903
904
  return result
904
905
 
905
- if result.should_break:
906
- result.should_break = False
907
-
908
- elif nelse := node.else_:
906
+ if not result.should_break and (nelse := node.else_):
909
907
  register(get_visitor(nelse.__class__)(nelse, context))
910
908
  if should_return():
911
909
  return result
@@ -945,10 +943,7 @@ def visit_WhileNode(node: PysWhileNode, context: PysContext) -> PysRunTimeResult
945
943
  else:
946
944
  return result
947
945
 
948
- if result.should_break:
949
- result.should_break = False
950
-
951
- elif nelse := node.else_:
946
+ if not result.should_break and (nelse := node.else_):
952
947
  register(get_visitor(nelse.__class__)(nelse, context))
953
948
  if should_return():
954
949
  return result
@@ -988,10 +983,7 @@ def visit_DoWhileNode(node: PysDoWhileNode, context: PysContext) -> PysRunTimeRe
988
983
  if should_return():
989
984
  return result
990
985
 
991
- if result.should_break:
992
- result.should_break = False
993
-
994
- elif nelse := node.else_:
986
+ if not result.should_break and (nelse := node.else_):
995
987
  register(get_visitor(nelse.__class__)(nelse, context))
996
988
  if should_return():
997
989
  return result
@@ -1033,10 +1025,7 @@ def visit_RepeatNode(node: PysRepeatNode, context: PysContext) -> PysRunTimeResu
1033
1025
  if should_return():
1034
1026
  return result
1035
1027
 
1036
- if result.should_break:
1037
- result.should_break = False
1038
-
1039
- elif nelse := node.else_:
1028
+ if not result.should_break and (nelse := node.else_):
1040
1029
  register(get_visitor(nelse.__class__)(nelse, context))
1041
1030
  if should_return():
1042
1031
  return result
pyscript/core/mapping.py CHANGED
@@ -9,16 +9,18 @@ from operator import (
9
9
  neg, inv
10
10
  )
11
11
 
12
- contains = lambda a, b : a in b
13
- not_contains = lambda a, b : a not in b
14
- and_op = lambda a, b : a and b
15
- or_op = lambda a, b : a or b
16
- typeof = lambda a : type(a).__name__
12
+ contains = lambda a, b : a in b
13
+ not_contains = lambda a, b : a not in b
14
+ and_op = lambda a, b : a and b
15
+ or_op = lambda a, b : a or b
16
+ not_isinstance = lambda a, b : not isinstance(a, b)
17
+ typeof = lambda a : type(a).__name__
17
18
 
18
19
  GET_BINARY_FUNCTION = {
19
20
  TOKENS['IS']: is_,
20
21
  TOKENS['IS_NOT']: is_not,
21
22
  TOKENS['INSTANCEOF']: isinstance,
23
+ TOKENS['NOT_INSTANCEOF']: not_isinstance,
22
24
  TOKENS['PERCENT']: mod,
23
25
  TOKENS['AMPERSAND']: and_,
24
26
  TOKENS['STAR']: mul,
@@ -150,6 +152,7 @@ SYMBOLS_TOKEN_MAP = dfrozen({
150
152
  TOKENS['IS_NOT']: 'is not',
151
153
  TOKENS['TYPEOF']: 'typeof',
152
154
  TOKENS['INSTANCEOF']: 'instanceof',
155
+ TOKENS['NOT_INSTANCEOF']: 'not instanceof',
153
156
  TOKENS['NEWLINE']: '\n',
154
157
  TOKENS['EXCLAMATION']: '!',
155
158
  TOKENS['COMMENT']: '#',
pyscript/core/nodes.py CHANGED
@@ -274,6 +274,7 @@ class PysTernaryOperatorNode(PysNode):
274
274
  invalid: PysNode,
275
275
  style: Literal['general', 'pythonic']
276
276
  ) -> 'PysTernaryOperatorNode | PysNode':
277
+
277
278
  if (l_condition := _get_literal_value(condition)) is not undefined:
278
279
  return valid if l_condition else invalid
279
280
 
@@ -398,17 +399,15 @@ class PysStatementsNode(PysNode):
398
399
  return elements[0]
399
400
 
400
401
  new = []
401
- constant_nodes = (PysConstantNode, PysDebugNode, PysEllipsisNode)
402
- interrupt_nodes = (PysReturnNode, PysThrowNode, PysContinueNode, PysBreakNode)
403
402
 
404
403
  for node in elements:
405
- if node.__class__ in constant_nodes:
404
+ if node.__class__ in _constant_nodes:
406
405
  continue
407
406
  elif node.__class__ is PysStatementsNode:
408
407
  new.extend(node.elements)
409
408
  else:
410
409
  new.append(node)
411
- if node.__class__ in interrupt_nodes:
410
+ if node.__class__ in _interrupt_nodes:
412
411
  break
413
412
 
414
413
  self = super().__new__(cls, position)
@@ -478,9 +477,9 @@ class PysIfNode(PysNode):
478
477
  if not new_cases:
479
478
  return else_ if else_ else PysStatementsNode(position, [])
480
479
 
481
- elif len(new_cases) == 1:
480
+ elif not else_ and len(new_cases) == 1:
482
481
  condition, body = new_cases[0]
483
- if body.__class__ is PysIfNode and len(body.cases) == 1 and body.else_:
482
+ if body.__class__ is PysIfNode and len(body.cases) == 1 and not body.else_:
484
483
  inner_condition, inner_body = body.cases[0]
485
484
  new_cases = [(
486
485
  PysBinaryOperatorNode(
@@ -510,12 +509,16 @@ class PysSwitchNode(PysNode):
510
509
  target: PysNode,
511
510
  cases: list[tuple[PysNode, PysNode]],
512
511
  default: PysNode | None
513
- ) -> 'PysSwitchNode':
512
+ ) -> 'PysSwitchNode | PysNode':
513
+ default = _optional_part(default)
514
+
515
+ if not (cases or default):
516
+ return target
514
517
 
515
518
  self = super().__new__(cls, position)
516
519
  setimuattr(self, 'target', target)
517
520
  setimuattr(self, 'cases', tuple(cases))
518
- setimuattr(self, 'default', _optional_part(default))
521
+ setimuattr(self, 'default', default)
519
522
  return self
520
523
 
521
524
  def __repr__(self) -> str:
@@ -532,12 +535,31 @@ class PysMatchNode(PysNode):
532
535
  target: PysNode | None,
533
536
  cases: list[tuple[PysNode, PysNode]],
534
537
  default: PysNode | None
535
- ) -> 'PysMatchNode':
538
+ ) -> 'PysMatchNode | PysNode':
539
+
540
+ if target and (l_target := _get_literal_value(target)) is not undefined:
541
+ new_cases = []
542
+
543
+ for condition, value in cases:
544
+ if (l_condition := _get_literal_value(condition)) is undefined:
545
+ new_cases.append((condition, value))
546
+ elif l_target == l_condition:
547
+ default = value
548
+ break
549
+
550
+ cases = new_cases
551
+ default = _optional_part(default, expression=True)
552
+
553
+ if not cases:
554
+ return default if default else PysConstantNode(position, None)
555
+
556
+ else:
557
+ default = _optional_part(default, expression=True)
536
558
 
537
559
  self = super().__new__(cls, position)
538
560
  setimuattr(self, 'target', target)
539
561
  setimuattr(self, 'cases', tuple(cases))
540
- setimuattr(self, 'default', _optional_part(default))
562
+ setimuattr(self, 'default', default)
541
563
  return self
542
564
 
543
565
  def __repr__(self) -> str:
@@ -602,8 +624,7 @@ class PysForNode(PysNode):
602
624
  def __new__(
603
625
  cls,
604
626
  position: PysPosition,
605
- header: tuple[PysNode | None, PysNode | None, PysNode | None] |
606
- tuple[PysNode, PysNode],
627
+ header: tuple[PysNode | None, PysNode | None, PysNode | None] | tuple[PysNode, PysNode],
607
628
  body: PysNode,
608
629
  else_: PysNode | None
609
630
  ) -> 'PysForNode':
@@ -628,12 +649,16 @@ class PysWhileNode(PysNode):
628
649
  condition: PysNode,
629
650
  body: PysNode,
630
651
  else_: PysNode | None
631
- ) -> 'PysWhileNode':
652
+ ) -> 'PysWhileNode | PysNode':
653
+ else_ = _optional_part(else_)
654
+
655
+ if (l_condition := _get_literal_value(condition)) is not undefined and not l_condition:
656
+ return else_ if else_ else PysStatementsNode(position, [])
632
657
 
633
658
  self = super().__new__(cls, position)
634
659
  setimuattr(self, 'condition', condition)
635
660
  setimuattr(self, 'body', body)
636
- setimuattr(self, 'else_', _optional_part(else_))
661
+ setimuattr(self, 'else_', else_)
637
662
  return self
638
663
 
639
664
  def __repr__(self) -> str:
@@ -747,8 +772,17 @@ class PysGlobalNode(PysNode):
747
772
 
748
773
  @typecheck
749
774
  def __new__(cls, position: PysPosition, identifiers: list[PysToken]) -> 'PysGlobalNode':
775
+ registered = set()
776
+ new = []
777
+
778
+ for identifier in identifiers:
779
+ if identifier.value in registered:
780
+ continue
781
+ registered.add(identifier.value)
782
+ new.append(identifier)
783
+
750
784
  self = super().__new__(cls, position)
751
- setimuattr(self, 'identifiers', tuple(set(identifiers)))
785
+ setimuattr(self, 'identifiers', tuple(new))
752
786
  return self
753
787
 
754
788
  def __repr__(self) -> str:
@@ -761,7 +795,7 @@ class PysReturnNode(PysNode):
761
795
  @typecheck
762
796
  def __new__(cls, position: PysPosition, value: PysNode | None) -> 'PysReturnNode':
763
797
  self = super().__new__(cls, position)
764
- setimuattr(self, 'value', _optional_part(value))
798
+ setimuattr(self, 'value', _optional_part(value, expression=True))
765
799
  return self
766
800
 
767
801
  def __repr__(self) -> str:
@@ -846,6 +880,9 @@ class PysBreakNode(PysNode):
846
880
  def __repr__(self) -> Literal['Break()']:
847
881
  return 'Break()'
848
882
 
883
+ _constant_nodes = frozenset([PysConstantNode, PysDebugNode, PysEllipsisNode])
884
+ _interrupt_nodes = frozenset([PysReturnNode, PysThrowNode, PysContinueNode, PysBreakNode])
885
+
849
886
  def _get_literal_value(item: Any) -> Any | PysUndefined:
850
887
 
851
888
  if isinstance(item, (PysConstantNode, PysToken)):
@@ -877,9 +914,9 @@ def _get_literal_value(item: Any) -> Any | PysUndefined:
877
914
 
878
915
  return undefined
879
916
 
880
- def _optional_part(part: PysNode | None) -> PysNode | None:
917
+ def _optional_part(part: PysNode | None, *, expression: bool = False) -> PysNode | None:
881
918
  return None if (
919
+ (not expression and part.__class__ in _constant_nodes) or
882
920
  _get_literal_value(part) is None or
883
- (part.__class__ is PysStatementsNode and not part.elements) or
884
- (part.__class__ in (PysDebugNode, PysEllipsisNode))
921
+ (part.__class__ is PysStatementsNode and not part.elements)
885
922
  ) else part