pyscript-programming-language 1.13.4__py3-none-any.whl → 1.15.0.dev1__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.
- pyscript/__init__.py +2 -1
- pyscript/__init__.pyi +1 -0
- pyscript/__main__.py +87 -48
- pyscript/core/analyzer.py +31 -40
- pyscript/core/buffer.py +6 -10
- pyscript/core/cache.py +4 -3
- pyscript/core/checks.py +5 -21
- pyscript/core/constants.py +2 -2
- pyscript/core/context.py +7 -10
- pyscript/core/editor/__init__.py +27 -11
- pyscript/core/editor/bases.py +16 -16
- pyscript/core/editor/gui.py +110 -28
- pyscript/core/editor/terminal.py +17 -4
- pyscript/core/exceptions.py +8 -13
- pyscript/core/handlers.py +34 -32
- pyscript/core/highlight.py +23 -3
- pyscript/core/interpreter.py +58 -65
- pyscript/core/lexer.py +51 -52
- pyscript/core/mapping.py +11 -8
- pyscript/core/nodes.py +415 -203
- pyscript/core/parser.py +236 -246
- pyscript/core/position.py +86 -81
- pyscript/core/pysbuiltins.py +41 -81
- pyscript/core/pystypes.py +66 -13
- pyscript/core/results.py +2 -2
- pyscript/core/runner.py +5 -5
- pyscript/core/shell.py +50 -73
- pyscript/core/symtab.py +14 -15
- pyscript/core/token.py +3 -4
- pyscript/core/utils/ansi.py +97 -50
- pyscript/core/utils/debug.py +18 -5
- pyscript/core/utils/decorators.py +8 -8
- pyscript/core/utils/generic.py +19 -3
- pyscript/core/utils/module.py +6 -3
- pyscript/core/utils/similarity.py +8 -6
- pyscript/core/utils/string.py +8 -13
- pyscript/core/version.py +2 -2
- pyscript/lib/ansi.pys +1 -14
- pyscript/lib/ast/ast_dump.py +20 -32
- pyscript/lib/ast/ast_literal_eval.py +5 -12
- pyscript/lib/ast/ast_unparse.py +10 -10
- pyscript/lib/ast/ast_walk.py +20 -26
- pyscript/lib/brainfuck.pys +10 -8
- pyscript/lib/dual/__init__.pys +17 -0
- pyscript/lib/dual/py_dual.py +246 -0
- pyscript/lib/getch/__init__.pys +6 -1
- pyscript/lib/getch/py_getch.py +92 -11
- pyscript/lib/history.pys +10 -8
- pyscript/lib/inspect.pys +1 -1
- pyscript/lib/parser.pys +6 -4
- {pyscript_programming_language-1.13.4.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/METADATA +32 -29
- pyscript_programming_language-1.15.0.dev1.dist-info/RECORD +81 -0
- pyscript/site-packages/anniversary.pys +0 -4
- pyscript_programming_language-1.13.4.dist-info/RECORD +0 -80
- {pyscript_programming_language-1.13.4.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/WHEEL +0 -0
- {pyscript_programming_language-1.13.4.dist-info → pyscript_programming_language-1.15.0.dev1.dist-info}/top_level.txt +0 -0
pyscript/core/editor/__init__.py
CHANGED
|
@@ -1,15 +1,31 @@
|
|
|
1
1
|
"""
|
|
2
2
|
All Editor Application Implementations for PyScript.
|
|
3
|
+
|
|
4
|
+
Not all editors are loaded (lazy import).
|
|
3
5
|
"""
|
|
4
6
|
|
|
5
|
-
from . import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
__all__ = (
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
from ..utils.path import normpath
|
|
8
|
+
|
|
9
|
+
from importlib import import_module as _import
|
|
10
|
+
from os.path import dirname
|
|
11
|
+
from pkgutil import iter_modules
|
|
12
|
+
|
|
13
|
+
__all__ = tuple(frozenset(
|
|
14
|
+
name for _, name, _ in
|
|
15
|
+
iter_modules((dirname(normpath(__file__)),))
|
|
16
|
+
))
|
|
17
|
+
|
|
18
|
+
def __getattr__(name: str):
|
|
19
|
+
g = globals()
|
|
20
|
+
if name in g:
|
|
21
|
+
return g[name]
|
|
22
|
+
elif name in __all__:
|
|
23
|
+
module = _import(f'{__name__}.{name}')
|
|
24
|
+
g[name] = module
|
|
25
|
+
return module
|
|
26
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
27
|
+
|
|
28
|
+
def __dir__() -> list[str]:
|
|
29
|
+
return sorted(tuple(globals().keys()) + __all__)
|
|
30
|
+
|
|
31
|
+
del normpath, iter_modules, dirname
|
pyscript/core/editor/bases.py
CHANGED
|
@@ -3,24 +3,25 @@ from ..buffer import PysFileBuffer
|
|
|
3
3
|
from ..cache import pys_sys
|
|
4
4
|
from ..constants import CONFIGURATIONS_PATH
|
|
5
5
|
from ..utils.decorators import typecheck, inheritable
|
|
6
|
-
from ..utils.generic import
|
|
6
|
+
from ..utils.generic import delimuattr
|
|
7
7
|
from ..utils.string import normstr
|
|
8
8
|
|
|
9
9
|
from json import dump, load
|
|
10
|
+
from os.path import basename
|
|
10
11
|
from typing import Any
|
|
11
12
|
|
|
12
|
-
import os
|
|
13
|
-
|
|
14
13
|
class PysEditor(Pys):
|
|
15
14
|
|
|
16
15
|
@typecheck
|
|
17
16
|
def __init__(self, file: PysFileBuffer, colored: bool = True) -> None:
|
|
18
17
|
self.file = file
|
|
18
|
+
self.basename = basename(self.file.name)
|
|
19
19
|
self.colored = bool(colored)
|
|
20
|
-
self.basename = os.path.basename(self.file.name)
|
|
21
20
|
self.used = False
|
|
22
21
|
self.modified = False
|
|
23
22
|
|
|
23
|
+
self.load_configuration()
|
|
24
|
+
|
|
24
25
|
def __init_subclass__(cls, **kwargs) -> None:
|
|
25
26
|
super().__init_subclass__(**kwargs)
|
|
26
27
|
inheritable(cls)
|
|
@@ -29,20 +30,17 @@ class PysEditor(Pys):
|
|
|
29
30
|
try:
|
|
30
31
|
with open(CONFIGURATIONS_PATH, 'r', encoding=pys_sys.encoding) as file:
|
|
31
32
|
result = load(file)
|
|
32
|
-
|
|
33
|
+
if not isinstance(result, dict):
|
|
34
|
+
raise ValueError
|
|
35
|
+
for key in result:
|
|
36
|
+
if not isinstance(key, str):
|
|
33
37
|
raise ValueError
|
|
34
|
-
|
|
35
|
-
if not isinstance(key, str):
|
|
36
|
-
raise ValueError
|
|
37
|
-
self.configurations = result
|
|
38
|
+
self.configurations = result
|
|
38
39
|
except:
|
|
39
40
|
self.configurations = {}
|
|
40
41
|
|
|
41
42
|
def get_configuration(self, configuration: str, default: Any) -> Any:
|
|
42
|
-
|
|
43
|
-
return self.configurations[configuration]
|
|
44
|
-
self.configurations[configuration] = default
|
|
45
|
-
return default
|
|
43
|
+
return self.configurations.setdefault(configuration, default)
|
|
46
44
|
|
|
47
45
|
def set_configuration(self, configuration: str, value: Any) -> None:
|
|
48
46
|
self.configurations[configuration] = value
|
|
@@ -55,11 +53,12 @@ class PysEditor(Pys):
|
|
|
55
53
|
pass
|
|
56
54
|
|
|
57
55
|
def save(self, text) -> None:
|
|
56
|
+
if not self.modified:
|
|
57
|
+
return
|
|
58
|
+
|
|
58
59
|
try:
|
|
59
|
-
text = normstr(text)
|
|
60
60
|
with open(self.file.name, 'w', encoding=pys_sys.encoding) as file:
|
|
61
|
-
file.write(text)
|
|
62
|
-
setimuattr(self.file, 'text', text)
|
|
61
|
+
file.write(normstr(text))
|
|
63
62
|
except:
|
|
64
63
|
pass
|
|
65
64
|
else:
|
|
@@ -68,4 +67,5 @@ class PysEditor(Pys):
|
|
|
68
67
|
def run(self) -> None:
|
|
69
68
|
if self.used:
|
|
70
69
|
raise RuntimeError("one application object can only be used once")
|
|
70
|
+
delimuattr(self.file, 'text')
|
|
71
71
|
self.used = True
|
pyscript/core/editor/gui.py
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
from .bases import PysEditor
|
|
2
2
|
from ..buffer import PysFileBuffer
|
|
3
3
|
from ..constants import ICON_PATH
|
|
4
|
-
from ..highlight import PygmentsPyScriptStyle, PygmentsPyScriptLexer
|
|
4
|
+
from ..highlight import PYGMENTS, PygmentsPyScriptStyle, PygmentsPyScriptLexer
|
|
5
5
|
from ..utils.generic import boundary
|
|
6
6
|
from ..version import __version__
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
|
-
from tkinter import Tk, Scrollbar, Text, messagebox
|
|
10
|
-
from tkinter.font import Font
|
|
9
|
+
from tkinter import Tk, Scrollbar, Text, Canvas, messagebox
|
|
10
|
+
from tkinter.font import families, Font
|
|
11
|
+
|
|
12
|
+
MONO_FALLBACKS = (
|
|
13
|
+
'Fira Code',
|
|
14
|
+
'Cascadia Code', 'SF Mono', 'Ubuntu Mono', 'Liberation Mono',
|
|
15
|
+
'Consolas', 'Menlo', 'DejaVu Sans Mono',
|
|
16
|
+
'Courier New', 'Lucida Console', 'Monaco', 'Andale Mono',
|
|
17
|
+
'Courier', 'Terminal',
|
|
18
|
+
'monospace'
|
|
19
|
+
)
|
|
11
20
|
|
|
12
21
|
class PysGUIEditor(PysEditor, Tk):
|
|
13
22
|
|
|
@@ -15,7 +24,8 @@ try:
|
|
|
15
24
|
PysEditor.__init__(self, file, colored)
|
|
16
25
|
Tk.__init__(self)
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
def update_tab_size():
|
|
28
|
+
self.text.configure(tabs=(self.font.measure(' '),))
|
|
19
29
|
|
|
20
30
|
def on_save(event=None):
|
|
21
31
|
text = self.text.get('1.0', 'end')
|
|
@@ -26,46 +36,43 @@ try:
|
|
|
26
36
|
def on_close():
|
|
27
37
|
if self.modified:
|
|
28
38
|
answer = messagebox.askyesnocancel('Unsaved changes', 'File has been modified. Save before exit?')
|
|
29
|
-
if answer is
|
|
39
|
+
if answer is None:
|
|
40
|
+
return
|
|
41
|
+
elif answer:
|
|
30
42
|
on_save()
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
elif answer is False:
|
|
34
|
-
self.save_configuration()
|
|
35
|
-
self.destroy()
|
|
36
|
-
else:
|
|
37
|
-
self.save_configuration()
|
|
38
|
-
self.destroy()
|
|
43
|
+
self.save_configuration()
|
|
44
|
+
self.destroy()
|
|
39
45
|
|
|
40
46
|
def on_configure(event):
|
|
41
47
|
if event.widget == self:
|
|
42
48
|
zoom = self.wm_state() == 'zoomed'
|
|
43
49
|
self.set_configuration('zoom', zoom)
|
|
44
50
|
if not zoom:
|
|
45
|
-
self.set_configuration(
|
|
46
|
-
'gui-geometry',
|
|
47
|
-
f'{event.width}x{event.height}+{event.x}+{event.y}'
|
|
48
|
-
)
|
|
51
|
+
self.set_configuration('gui-geometry', f'{event.width}x{event.height}+{event.x}+{event.y}')
|
|
49
52
|
|
|
50
53
|
def on_modified(event=None):
|
|
51
|
-
|
|
52
|
-
if text.edit_modified():
|
|
54
|
+
if self.text.edit_modified():
|
|
53
55
|
self.modified = True
|
|
54
56
|
update()
|
|
55
|
-
|
|
57
|
+
refresh_line_numbers()
|
|
58
|
+
self.text.edit_modified(False)
|
|
56
59
|
|
|
57
60
|
def on_change_font(value):
|
|
58
61
|
def wrapper(event=None):
|
|
59
62
|
font = self.font
|
|
60
63
|
size = boundary(font.cget('size') + value, 1, 127)
|
|
61
64
|
font.config(size=size)
|
|
65
|
+
update_tab_size()
|
|
66
|
+
refresh_line_numbers()
|
|
62
67
|
self.set_configuration('gui-size-font', size)
|
|
63
68
|
return 'break'
|
|
64
69
|
return wrapper
|
|
65
70
|
|
|
66
71
|
def on_toggle_wrap(event=None):
|
|
67
|
-
|
|
68
|
-
|
|
72
|
+
text = self.text
|
|
73
|
+
wrap = text.cget('wrap') != 'char'
|
|
74
|
+
text.configure(wrap='char' if wrap else 'none')
|
|
75
|
+
refresh_line_numbers()
|
|
69
76
|
self.set_configuration('wrap', wrap)
|
|
70
77
|
return 'break'
|
|
71
78
|
|
|
@@ -83,13 +90,72 @@ try:
|
|
|
83
90
|
|
|
84
91
|
def on_enter(event=None):
|
|
85
92
|
text = self.text
|
|
93
|
+
if text.tag_ranges('sel'):
|
|
94
|
+
text.delete('sel.first', 'sel.last')
|
|
95
|
+
|
|
86
96
|
line = text.get('insert linestart', 'insert lineend')
|
|
87
97
|
text.insert('insert', '\n' + line[:len(line) - len(line.lstrip())])
|
|
98
|
+
text.see('insert')
|
|
88
99
|
return 'break'
|
|
89
100
|
|
|
101
|
+
def draw_line_numbers():
|
|
102
|
+
self.line_job = None
|
|
103
|
+
text = self.text
|
|
104
|
+
canvas = self.line_numbers
|
|
105
|
+
font = self.font
|
|
106
|
+
|
|
107
|
+
bold = (font.cget('family'), font.cget('size'), 'bold')
|
|
108
|
+
digits = len(text.index('end-1c').split('.')[0])
|
|
109
|
+
padding = font.measure(' ')
|
|
110
|
+
width = font.measure('0' * digits) + padding * 2
|
|
111
|
+
|
|
112
|
+
canvas.delete('all')
|
|
113
|
+
canvas.configure(width=width, bg='#1f1f1f')
|
|
114
|
+
|
|
115
|
+
current = text.index('insert').split('.')[0]
|
|
116
|
+
index = text.index('@0,0')
|
|
117
|
+
previous = None
|
|
118
|
+
|
|
119
|
+
while True:
|
|
120
|
+
info = text.dlineinfo(index)
|
|
121
|
+
if info is None:
|
|
122
|
+
break
|
|
123
|
+
|
|
124
|
+
number = index.split('.')[0]
|
|
125
|
+
if number == previous:
|
|
126
|
+
break
|
|
127
|
+
|
|
128
|
+
canvas.create_text(
|
|
129
|
+
width - padding, info[1],
|
|
130
|
+
anchor='ne',
|
|
131
|
+
text=number,
|
|
132
|
+
font=bold if number == current else font,
|
|
133
|
+
fill='#ffffff'
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
previous = number
|
|
137
|
+
index = text.index(f'{index} +1 lines')
|
|
138
|
+
|
|
139
|
+
def refresh_line_numbers(event=None):
|
|
140
|
+
if self.line_job is None:
|
|
141
|
+
self.line_job = self.after_idle(draw_line_numbers)
|
|
142
|
+
|
|
143
|
+
def on_scroll(first, last):
|
|
144
|
+
self.scrollbar.set(first, last)
|
|
145
|
+
refresh_line_numbers()
|
|
146
|
+
|
|
147
|
+
installed = frozenset(families())
|
|
148
|
+
for font_name in MONO_FALLBACKS:
|
|
149
|
+
if font_name in installed:
|
|
150
|
+
break
|
|
151
|
+
else:
|
|
152
|
+
font_name = 'monospace'
|
|
153
|
+
|
|
90
154
|
self.lexer = PygmentsPyScriptLexer()
|
|
91
|
-
self.font = Font(family=
|
|
155
|
+
self.font = Font(family=font_name, size=boundary(self.get_configuration('gui-size-font', 10), 1, 127))
|
|
156
|
+
self.line_job = None
|
|
92
157
|
self.scrollbar = Scrollbar(self)
|
|
158
|
+
self.line_numbers = Canvas(self, width=0, highlightthickness=0, bd=0)
|
|
93
159
|
self.text = Text(
|
|
94
160
|
self,
|
|
95
161
|
undo=True,
|
|
@@ -99,12 +165,16 @@ try:
|
|
|
99
165
|
bg='#000000',
|
|
100
166
|
fg='#ffffff',
|
|
101
167
|
insertbackground='white',
|
|
102
|
-
yscrollcommand=
|
|
168
|
+
yscrollcommand=on_scroll
|
|
103
169
|
)
|
|
104
170
|
|
|
171
|
+
update_tab_size()
|
|
172
|
+
|
|
105
173
|
self.scrollbar.pack_configure(side='right', fill='y')
|
|
106
174
|
self.scrollbar.configure(command=self.text.yview)
|
|
107
175
|
|
|
176
|
+
self.line_numbers.pack_configure(side='left', fill='y')
|
|
177
|
+
self.text.pack_configure(side='left', expand=True, fill='both')
|
|
108
178
|
self.text.pack_configure(side='left', expand=True, fill='both')
|
|
109
179
|
|
|
110
180
|
self.text.insert('1.0', self.file.text)
|
|
@@ -114,6 +184,9 @@ try:
|
|
|
114
184
|
self.text.bind('<Return>', on_enter)
|
|
115
185
|
self.text.bind('<Prior>', on_page_up)
|
|
116
186
|
self.text.bind('<Next>', on_page_down)
|
|
187
|
+
self.text.bind('<KeyRelease>', refresh_line_numbers)
|
|
188
|
+
self.text.bind('<ButtonRelease-1>', refresh_line_numbers)
|
|
189
|
+
self.text.bind('<Configure>', refresh_line_numbers)
|
|
117
190
|
|
|
118
191
|
self.bind('<Configure>', on_configure)
|
|
119
192
|
self.bind_all('<Control-S>', on_save)
|
|
@@ -140,18 +213,20 @@ try:
|
|
|
140
213
|
)
|
|
141
214
|
)
|
|
142
215
|
|
|
143
|
-
self.wm_minsize(
|
|
216
|
+
self.wm_minsize(250, 200)
|
|
144
217
|
self.wm_protocol('WM_DELETE_WINDOW', on_close)
|
|
145
218
|
|
|
146
|
-
if colored:
|
|
219
|
+
if PYGMENTS and colored:
|
|
220
|
+
self.update_job = None
|
|
221
|
+
|
|
147
222
|
tag_configure = self.text.tag_configure
|
|
148
223
|
for token, style in PygmentsPyScriptStyle.list_styles():
|
|
149
224
|
color = style['color']
|
|
150
225
|
if color:
|
|
151
226
|
tag_configure(str(token), foreground=f'#{color}')
|
|
152
227
|
|
|
153
|
-
def
|
|
154
|
-
self.
|
|
228
|
+
def refresh_highlight():
|
|
229
|
+
self.update_job = None
|
|
155
230
|
|
|
156
231
|
index = '1.0'
|
|
157
232
|
|
|
@@ -171,11 +246,18 @@ try:
|
|
|
171
246
|
text_tag_add(to_string(type), index, end_index)
|
|
172
247
|
index = end_index
|
|
173
248
|
|
|
249
|
+
def update():
|
|
250
|
+
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)
|
|
254
|
+
|
|
174
255
|
else:
|
|
175
256
|
def update():
|
|
176
257
|
self.title(f'PyScript {__version__} - {self.basename}{"*" if self.modified else ""}')
|
|
177
258
|
|
|
178
259
|
update()
|
|
260
|
+
refresh_line_numbers()
|
|
179
261
|
|
|
180
262
|
def run(self) -> None:
|
|
181
263
|
PysEditor.run(self)
|
pyscript/core/editor/terminal.py
CHANGED
|
@@ -22,8 +22,6 @@ try:
|
|
|
22
22
|
def __init__(self, file: PysFileBuffer, colored: bool = True) -> None:
|
|
23
23
|
PysEditor.__init__(self, file, colored)
|
|
24
24
|
|
|
25
|
-
self.load_configuration()
|
|
26
|
-
|
|
27
25
|
self.show_exit_window = False
|
|
28
26
|
|
|
29
27
|
on_edit = Condition(lambda: not self.show_exit_window)
|
|
@@ -47,6 +45,9 @@ try:
|
|
|
47
45
|
'style': style_from_pygments_cls(PygmentsPyScriptStyle)
|
|
48
46
|
})
|
|
49
47
|
|
|
48
|
+
text_other_keyword.update({
|
|
49
|
+
'scrollbar': True
|
|
50
|
+
})
|
|
50
51
|
close_window_keyword.update({
|
|
51
52
|
'content': FormattedTextControl(
|
|
52
53
|
" File has been modified. Save before exit? \n"
|
|
@@ -64,6 +65,13 @@ try:
|
|
|
64
65
|
title = file
|
|
65
66
|
return ANSI(f'\x1b[7m{title}{" " * max(0, columns - len(title))}\x1b[0m')
|
|
66
67
|
|
|
68
|
+
def create_line_prefix(line_number, wrap_count):
|
|
69
|
+
width = len(str(self.text.document.line_count))
|
|
70
|
+
number = str(line_number + 1) if wrap_count == 0 else ''
|
|
71
|
+
is_current = line_number == self.text.document.cursor_position_row
|
|
72
|
+
style = 'bg:#ffffff fg:#000000' + (' bold' if is_current else '')
|
|
73
|
+
return [(style, f' {number:>{width}} '), ('', ' ')]
|
|
74
|
+
|
|
67
75
|
else:
|
|
68
76
|
app_other_keyword.update({
|
|
69
77
|
'color_depth': ColorDepth.DEPTH_1_BIT
|
|
@@ -84,11 +92,16 @@ try:
|
|
|
84
92
|
title = file
|
|
85
93
|
return title + ' ' * max(0, columns - len(title))
|
|
86
94
|
|
|
95
|
+
def create_line_prefix(line_number, wrap_count):
|
|
96
|
+
width = len(str(self.text.document.line_count))
|
|
97
|
+
number = str(line_number + 1) if wrap_count == 0 else ''
|
|
98
|
+
return f' {number:>{width}} | '
|
|
99
|
+
|
|
87
100
|
self.text = TextArea(
|
|
88
101
|
multiline=True,
|
|
89
|
-
scrollbar=True,
|
|
90
102
|
wrap_lines=on_wrap,
|
|
91
103
|
input_processors=[TabsProcessor(tabstop=4)],
|
|
104
|
+
get_line_prefix=create_line_prefix,
|
|
92
105
|
**text_other_keyword
|
|
93
106
|
)
|
|
94
107
|
|
|
@@ -188,7 +201,7 @@ try:
|
|
|
188
201
|
)
|
|
189
202
|
|
|
190
203
|
self.text.text = self.file.text
|
|
191
|
-
self.text.buffer.on_text_changed
|
|
204
|
+
self.text.buffer.on_text_changed.add_handler(on_change)
|
|
192
205
|
|
|
193
206
|
Application.__init__(
|
|
194
207
|
self,
|
pyscript/core/exceptions.py
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from .bases import Pys
|
|
2
2
|
from .constants import NO_COLOR
|
|
3
|
-
from .utils.decorators import immutable
|
|
4
|
-
from .utils.generic import setimuattr
|
|
5
3
|
from .utils.string import indent
|
|
6
4
|
|
|
7
5
|
from typing import TYPE_CHECKING, Optional
|
|
@@ -12,7 +10,6 @@ if TYPE_CHECKING:
|
|
|
12
10
|
from .position import PysPosition
|
|
13
11
|
from .results import PysRunTimeResult
|
|
14
12
|
|
|
15
|
-
@immutable
|
|
16
13
|
class PysTraceback(Pys):
|
|
17
14
|
|
|
18
15
|
__slots__ = ('exception', 'context', 'position', 'primary', 'implicit')
|
|
@@ -26,25 +23,23 @@ class PysTraceback(Pys):
|
|
|
26
23
|
implicit: bool = False
|
|
27
24
|
) -> None:
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
self.exception = exception
|
|
27
|
+
self.context = context
|
|
28
|
+
self.position = position
|
|
29
|
+
self.primary = primary
|
|
30
|
+
self.implicit = implicit
|
|
34
31
|
|
|
35
32
|
def __repr__(self) -> str:
|
|
36
33
|
return f'<traceback of {self.exception!r}>'
|
|
37
34
|
|
|
38
35
|
def string_traceback(self) -> str:
|
|
39
|
-
# circular import problem solved
|
|
40
|
-
from .mapping import GET_ACOLOR
|
|
41
|
-
from .position import format_error_arrow
|
|
42
|
-
|
|
43
36
|
context = self.context
|
|
44
37
|
position = self.position
|
|
45
38
|
colored = not (context.flags & NO_COLOR)
|
|
46
39
|
|
|
47
40
|
if colored:
|
|
41
|
+
# circular import problem solved
|
|
42
|
+
from .mapping import GET_ACOLOR
|
|
48
43
|
reset = GET_ACOLOR('reset')
|
|
49
44
|
magenta = GET_ACOLOR('magenta')
|
|
50
45
|
bmagenta = GET_ACOLOR('bold-magenta')
|
|
@@ -63,7 +58,7 @@ class PysTraceback(Pys):
|
|
|
63
58
|
f' File {magenta}"{position.file.name}"{reset}' +
|
|
64
59
|
('' if is_positionless else f', line {magenta}{position.start_line}{reset}') +
|
|
65
60
|
('' if context_name is None else f', in {magenta}{context_name}{reset}') +
|
|
66
|
-
('' if is_positionless else f'\n{indent(format_error_arrow(
|
|
61
|
+
('' if is_positionless else f'\n{indent(position.format_error_arrow(colored), 4)}')
|
|
67
62
|
)
|
|
68
63
|
|
|
69
64
|
position = context.parent_entry_position
|
pyscript/core/handlers.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from .cache import pys_sys
|
|
2
|
-
from .constants import
|
|
2
|
+
from .constants import ENV_PYSCRIPT_WITH_GIL
|
|
3
3
|
from .context import PysContext
|
|
4
4
|
from .position import PysPosition
|
|
5
5
|
from .pystypes import PysFunction
|
|
@@ -10,36 +10,17 @@ from typing import Any
|
|
|
10
10
|
|
|
11
11
|
wrapper_function = (MethodType, classmethod, staticmethod)
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
handle_call(object.__func__, context, position)
|
|
25
|
-
|
|
26
|
-
elif ins(object, type):
|
|
27
|
-
gt = getattr
|
|
28
|
-
|
|
29
|
-
method = gt(object, '__new__', None)
|
|
30
|
-
if method is not None:
|
|
31
|
-
handle_call(method, context, position)
|
|
32
|
-
|
|
33
|
-
method = gt(object, '__init__', None)
|
|
34
|
-
if method is not None:
|
|
35
|
-
handle_call(method, context, position)
|
|
36
|
-
|
|
37
|
-
GIL = pys_sys.gil = False
|
|
38
|
-
|
|
39
|
-
else:
|
|
40
|
-
from threading import RLock
|
|
41
|
-
gil_lock = RLock()
|
|
42
|
-
|
|
13
|
+
GIL = pys_sys.gil = False
|
|
14
|
+
if is_environ(ENV_PYSCRIPT_WITH_GIL):
|
|
15
|
+
try:
|
|
16
|
+
from threading import RLock
|
|
17
|
+
gil_lock = RLock()
|
|
18
|
+
except:
|
|
19
|
+
pass
|
|
20
|
+
else:
|
|
21
|
+
GIL = pys_sys.gil = True
|
|
22
|
+
|
|
23
|
+
if GIL:
|
|
43
24
|
def handle_call(object: Any, context: PysContext, position: PysPosition) -> None:
|
|
44
25
|
with gil_lock:
|
|
45
26
|
ins = isinstance
|
|
@@ -63,4 +44,25 @@ else:
|
|
|
63
44
|
if method is not None:
|
|
64
45
|
handle_call(method, context, position)
|
|
65
46
|
|
|
66
|
-
|
|
47
|
+
else:
|
|
48
|
+
def handle_call(object: Any, context: PysContext, position: PysPosition) -> None:
|
|
49
|
+
ins = isinstance
|
|
50
|
+
|
|
51
|
+
if ins(object, PysFunction):
|
|
52
|
+
code = object.__code__
|
|
53
|
+
code.context = context
|
|
54
|
+
code.position = position
|
|
55
|
+
|
|
56
|
+
elif ins(object, wrapper_function):
|
|
57
|
+
handle_call(object.__func__, context, position)
|
|
58
|
+
|
|
59
|
+
elif ins(object, type):
|
|
60
|
+
gt = getattr
|
|
61
|
+
|
|
62
|
+
method = gt(object, '__new__', None)
|
|
63
|
+
if method is not None:
|
|
64
|
+
handle_call(method, context, position)
|
|
65
|
+
|
|
66
|
+
method = gt(object, '__init__', None)
|
|
67
|
+
if method is not None:
|
|
68
|
+
handle_call(method, context, position)
|
pyscript/core/highlight.py
CHANGED
|
@@ -9,10 +9,11 @@ from .pysbuiltins import pys_builtins
|
|
|
9
9
|
from .token import TOKENS
|
|
10
10
|
from .utils.ansi import acolor
|
|
11
11
|
from .utils.decorators import typecheck
|
|
12
|
+
from .utils.generic import dfrozen
|
|
12
13
|
from .utils.string import normstr
|
|
13
14
|
|
|
14
15
|
from html import escape as html_escape
|
|
15
|
-
from typing import Callable, Optional
|
|
16
|
+
from typing import Callable, Iterator, Optional
|
|
16
17
|
|
|
17
18
|
BUILTIN_TYPES = frozenset(
|
|
18
19
|
name
|
|
@@ -31,10 +32,11 @@ try:
|
|
|
31
32
|
from pygments.lexer import RegexLexer, include, bygroups
|
|
32
33
|
from pygments.style import Style
|
|
33
34
|
from pygments.token import (
|
|
34
|
-
Comment, Error, Escape, Generic, Keyword, Name, Number, Operator, Punctuation, String, Whitespace
|
|
35
|
+
_TokenType, Comment, Error, Escape, Generic, Keyword, Name, Number, Operator, Punctuation, String, Whitespace
|
|
35
36
|
)
|
|
36
37
|
from pygments.unistring import xid_start, xid_continue
|
|
37
38
|
|
|
39
|
+
_brackets_tokens = dfrozen({index: getattr(Punctuation, f'Brackets{index}') for index in range(3)})
|
|
38
40
|
_set_constant_keywords = frozenset(CONSTANT_KEYWORDS)
|
|
39
41
|
_keywords = '|'.join(KEYWORDS)
|
|
40
42
|
_unicode_name = f'[{xid_start}][{xid_continue}]*'
|
|
@@ -112,6 +114,10 @@ try:
|
|
|
112
114
|
Comment.Single: HIGHLIGHT_MAP['comment'],
|
|
113
115
|
Error: HIGHLIGHT_MAP['invalid'],
|
|
114
116
|
Generic.Prompt: HIGHLIGHT_MAP['keyword-other'],
|
|
117
|
+
|
|
118
|
+
Punctuation.Brackets0: HIGHLIGHT_MAP['brackets-0'],
|
|
119
|
+
Punctuation.Brackets1: HIGHLIGHT_MAP['brackets-1'],
|
|
120
|
+
Punctuation.Brackets2: HIGHLIGHT_MAP['brackets-2']
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
class PygmentsPyScriptLexer(Pys, RegexLexer):
|
|
@@ -324,6 +330,20 @@ try:
|
|
|
324
330
|
|
|
325
331
|
}
|
|
326
332
|
|
|
333
|
+
def get_tokens_unprocessed(self, *args, **kwargs) -> Iterator[tuple[int, _TokenType, str]]:
|
|
334
|
+
brackets_stack = []
|
|
335
|
+
|
|
336
|
+
for index, token, value in super().get_tokens_unprocessed(*args, **kwargs):
|
|
337
|
+
if token is Punctuation and len(value) == 1 and is_bracket(ttype := ord(value)):
|
|
338
|
+
if is_left_bracket(ttype):
|
|
339
|
+
token = _brackets_tokens[len(brackets_stack) % 3]
|
|
340
|
+
brackets_stack.append(ttype)
|
|
341
|
+
elif not (brackets_stack and BRACKETS_MAP[brackets_stack.pop()] == ttype):
|
|
342
|
+
token = Error
|
|
343
|
+
else:
|
|
344
|
+
token = _brackets_tokens[len(brackets_stack) % 3]
|
|
345
|
+
yield index, token, value
|
|
346
|
+
|
|
327
347
|
class PygmentsPyScriptShellLexer(PygmentsPyScriptLexer):
|
|
328
348
|
|
|
329
349
|
"""
|
|
@@ -521,8 +541,8 @@ def pys_highlight(
|
|
|
521
541
|
type_format = 'comment'
|
|
522
542
|
|
|
523
543
|
elif is_bracket(ttype):
|
|
524
|
-
type_format = f'brackets-{len(brackets_stack) % max_bracket_level}'
|
|
525
544
|
if is_left_bracket(ttype):
|
|
545
|
+
type_format = f'brackets-{len(brackets_stack) % max_bracket_level}'
|
|
526
546
|
brackets_stack.append(ttype)
|
|
527
547
|
elif not (brackets_stack and BRACKETS_MAP[brackets_stack.pop()] == ttype):
|
|
528
548
|
type_format = 'invalid'
|