easycoder 251103.3__py2.py3-none-any.whl → 251104.2__py2.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.
Potentially problematic release.
This version of easycoder might be problematic. Click here for more details.
- easycoder/__init__.py +1 -1
- easycoder/ec_classes.py +7 -2
- easycoder/ec_compiler.py +30 -7
- easycoder/ec_core.py +19 -19
- easycoder/ec_debug.py +464 -0
- easycoder/ec_handler.py +1 -1
- easycoder/ec_program.py +17 -8
- easycoder/ec_pyside.py +9 -12
- {easycoder-251103.3.dist-info → easycoder-251104.2.dist-info}/METADATA +1 -1
- easycoder-251104.2.dist-info/RECORD +20 -0
- easycoder-251103.3.dist-info/RECORD +0 -19
- {easycoder-251103.3.dist-info → easycoder-251104.2.dist-info}/WHEEL +0 -0
- {easycoder-251103.3.dist-info → easycoder-251104.2.dist-info}/entry_points.txt +0 -0
- {easycoder-251103.3.dist-info → easycoder-251104.2.dist-info}/licenses/LICENSE +0 -0
easycoder/__init__.py
CHANGED
easycoder/ec_classes.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import sys
|
|
2
2
|
|
|
3
|
-
class FatalError:
|
|
3
|
+
class FatalError(BaseException):
|
|
4
4
|
def __init__(self, compiler, message):
|
|
5
5
|
compiler.showWarnings()
|
|
6
6
|
lino = compiler.tokens[compiler.index].lino
|
|
@@ -58,4 +58,9 @@ class Token:
|
|
|
58
58
|
self.token = token
|
|
59
59
|
|
|
60
60
|
class Object():
|
|
61
|
-
|
|
61
|
+
"""Dynamic object that allows arbitrary attribute assignment"""
|
|
62
|
+
def __setattr__(self, name: str, value) -> None:
|
|
63
|
+
self.__dict__[name] = value
|
|
64
|
+
|
|
65
|
+
def __getattr__(self, name: str):
|
|
66
|
+
return self.__dict__.get(name)
|
easycoder/ec_compiler.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .ec_classes import
|
|
1
|
+
from .ec_classes import FatalError
|
|
2
2
|
from .ec_value import Value
|
|
3
3
|
from .ec_condition import Condition
|
|
4
4
|
|
|
@@ -18,9 +18,11 @@ class Compiler:
|
|
|
18
18
|
self.debugCompile = False
|
|
19
19
|
self.valueTypes = {}
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
# Get the current code size. Used during compilation
|
|
22
|
+
def getCodeSize(self):
|
|
22
23
|
return len(self.program.code)
|
|
23
24
|
|
|
25
|
+
# Get the current index (the program counter)
|
|
24
26
|
def getIndex(self):
|
|
25
27
|
return self.index
|
|
26
28
|
|
|
@@ -39,6 +41,7 @@ class Compiler:
|
|
|
39
41
|
self.index += 1
|
|
40
42
|
return self.getToken()
|
|
41
43
|
|
|
44
|
+
# Peek ahead to see the next token without advancing the index
|
|
42
45
|
def peek(self):
|
|
43
46
|
try:
|
|
44
47
|
return self.tokens[self.index + 1].token
|
|
@@ -68,22 +71,24 @@ class Compiler:
|
|
|
68
71
|
self.index += 1
|
|
69
72
|
return self.condition.compileCondition()
|
|
70
73
|
|
|
74
|
+
# Test if the current token has a specified value
|
|
71
75
|
def tokenIs(self, value):
|
|
72
76
|
return self.getToken() == value
|
|
73
77
|
|
|
78
|
+
# Test if the next token has the specified value
|
|
74
79
|
def nextIs(self, value):
|
|
75
80
|
return self.nextToken() == value
|
|
76
81
|
|
|
82
|
+
# Get the command at a given pc in the code list
|
|
77
83
|
def getCommandAt(self, pc):
|
|
78
84
|
return self.program.code[pc]
|
|
79
85
|
|
|
80
86
|
# Add a command to the code list
|
|
81
87
|
def addCommand(self, command):
|
|
82
|
-
|
|
83
|
-
if hasattr(self.program, 'usingGraphics'):
|
|
84
|
-
pass
|
|
88
|
+
command['bp'] = False
|
|
85
89
|
self.code.append(command)
|
|
86
90
|
|
|
91
|
+
# Test if the current token is a symbol
|
|
87
92
|
def isSymbol(self):
|
|
88
93
|
token = self.getToken()
|
|
89
94
|
try:
|
|
@@ -92,10 +97,12 @@ class Compiler:
|
|
|
92
97
|
return False
|
|
93
98
|
return True
|
|
94
99
|
|
|
100
|
+
# Test if the next token is a symbol
|
|
95
101
|
def nextIsSymbol(self):
|
|
96
102
|
self.next()
|
|
97
103
|
return self.isSymbol()
|
|
98
104
|
|
|
105
|
+
# Skip the next token if it matches the value given
|
|
99
106
|
def skip(self, token):
|
|
100
107
|
next = self.peek()
|
|
101
108
|
if type(token) == list:
|
|
@@ -105,21 +112,26 @@ class Compiler:
|
|
|
105
112
|
return
|
|
106
113
|
elif next == token: self.nextToken()
|
|
107
114
|
|
|
115
|
+
# Rewind to a given position in the code list
|
|
108
116
|
def rewindTo(self, index):
|
|
109
117
|
self.index = index
|
|
110
118
|
|
|
119
|
+
# Get source line number containing the current token
|
|
111
120
|
def getLino(self):
|
|
112
121
|
if self.index >= len(self.tokens):
|
|
113
122
|
return 0
|
|
114
123
|
return self.tokens[self.index].lino
|
|
115
124
|
|
|
125
|
+
# Issue a warning
|
|
116
126
|
def warning(self, message):
|
|
117
127
|
self.warnings.append(f'Warning at line {self.getLino() + 1} of {self.program.name}: {message}')
|
|
118
128
|
|
|
129
|
+
# Print all warnings
|
|
119
130
|
def showWarnings(self):
|
|
120
131
|
for warning in self.warnings:
|
|
121
132
|
print(warning)
|
|
122
133
|
|
|
134
|
+
# Get the symbol record for the current token (assumes it is a symbol name)
|
|
123
135
|
def getSymbolRecord(self):
|
|
124
136
|
token = self.getToken()
|
|
125
137
|
if not token in self.symbols:
|
|
@@ -131,18 +143,23 @@ class Compiler:
|
|
|
131
143
|
symbolRecord['used'] = True
|
|
132
144
|
return symbolRecord
|
|
133
145
|
|
|
146
|
+
# Add a value type
|
|
134
147
|
def addValueType(self):
|
|
135
148
|
self.valueTypes[self.getToken()] = True
|
|
136
149
|
|
|
150
|
+
# Test if a given value is in the value types list
|
|
137
151
|
def hasValue(self, type):
|
|
138
152
|
return type in self.valueTypes
|
|
139
153
|
|
|
154
|
+
# Compile a program label (a symbol ending with ':')
|
|
140
155
|
def compileLabel(self, command):
|
|
141
156
|
return self.compileSymbol(command, self.getToken())
|
|
142
157
|
|
|
158
|
+
# Compile a variable
|
|
143
159
|
def compileVariable(self, command, extra=None):
|
|
144
160
|
return self.compileSymbol(command, self.nextToken(), extra)
|
|
145
161
|
|
|
162
|
+
# Compile a symbol
|
|
146
163
|
def compileSymbol(self, command, name, extra=None):
|
|
147
164
|
try:
|
|
148
165
|
v = self.symbols[name]
|
|
@@ -151,7 +168,7 @@ class Compiler:
|
|
|
151
168
|
if v:
|
|
152
169
|
FatalError(self, f'Duplicate symbol name "{name}"')
|
|
153
170
|
return False
|
|
154
|
-
self.symbols[name] = self.
|
|
171
|
+
self.symbols[name] = self.getCodeSize()
|
|
155
172
|
command['program'] = self.program
|
|
156
173
|
command['type'] = 'symbol'
|
|
157
174
|
command['name'] = name
|
|
@@ -174,6 +191,10 @@ class Compiler:
|
|
|
174
191
|
# print(f'Compile {token}')
|
|
175
192
|
if not token:
|
|
176
193
|
return False
|
|
194
|
+
if len(self.code) == 0:
|
|
195
|
+
if self.program.parent == None and self.program.usingGraphics:
|
|
196
|
+
cmd = {'domain': 'graphics', 'keyword': 'init', 'debug': False}
|
|
197
|
+
self.code.append(cmd)
|
|
177
198
|
mark = self.getIndex()
|
|
178
199
|
for domain in self.program.getDomains():
|
|
179
200
|
handler = domain.keywordHandler(token)
|
|
@@ -198,7 +219,7 @@ class Compiler:
|
|
|
198
219
|
keyword = self.getToken()
|
|
199
220
|
if not keyword:
|
|
200
221
|
return False
|
|
201
|
-
|
|
222
|
+
# print(f'Compile keyword "{keyword}"')
|
|
202
223
|
if keyword.endswith(':'):
|
|
203
224
|
command = {}
|
|
204
225
|
command['domain'] = None
|
|
@@ -224,8 +245,10 @@ class Compiler:
|
|
|
224
245
|
else:
|
|
225
246
|
return False
|
|
226
247
|
|
|
248
|
+
# Compile fom the current location, stopping on any of a list of tokens
|
|
227
249
|
def compileFromHere(self, stopOn):
|
|
228
250
|
return self.compileFrom(self.getIndex(), stopOn)
|
|
229
251
|
|
|
252
|
+
# Compile from the start of the script
|
|
230
253
|
def compileFromStart(self):
|
|
231
254
|
return self.compileFrom(0, [])
|
easycoder/ec_core.py
CHANGED
|
@@ -31,13 +31,13 @@ class Core(Handler):
|
|
|
31
31
|
cmd['keyword'] = 'gotoPC'
|
|
32
32
|
cmd['goto'] = 0
|
|
33
33
|
cmd['debug'] = False
|
|
34
|
-
skip = self.
|
|
34
|
+
skip = self.getCodeSize()
|
|
35
35
|
self.add(cmd)
|
|
36
36
|
# Process the 'or'
|
|
37
|
-
self.getCommandAt(orHere)['or'] = self.
|
|
37
|
+
self.getCommandAt(orHere)['or'] = self.getCodeSize()
|
|
38
38
|
self.compileOne()
|
|
39
39
|
# Fixup the skip
|
|
40
|
-
self.getCommandAt(skip)['goto'] = self.
|
|
40
|
+
self.getCommandAt(skip)['goto'] = self.getCodeSize()
|
|
41
41
|
|
|
42
42
|
#############################################################################
|
|
43
43
|
# Keyword handlers
|
|
@@ -468,7 +468,7 @@ class Core(Handler):
|
|
|
468
468
|
if url != None:
|
|
469
469
|
command['url'] = url
|
|
470
470
|
command['or'] = None
|
|
471
|
-
get = self.
|
|
471
|
+
get = self.getCodeSize()
|
|
472
472
|
if self.peek() == 'timeout':
|
|
473
473
|
self.nextToken()
|
|
474
474
|
command['timeout'] = self.nextValue()
|
|
@@ -556,7 +556,7 @@ class Core(Handler):
|
|
|
556
556
|
command['condition'] = self.nextCondition()
|
|
557
557
|
self.add(command)
|
|
558
558
|
self.nextToken()
|
|
559
|
-
pcElse = self.
|
|
559
|
+
pcElse = self.getCodeSize()
|
|
560
560
|
cmd = {}
|
|
561
561
|
cmd['lino'] = command['lino']
|
|
562
562
|
cmd['domain'] = 'core'
|
|
@@ -569,7 +569,7 @@ class Core(Handler):
|
|
|
569
569
|
if self.peek() == 'else':
|
|
570
570
|
self.nextToken()
|
|
571
571
|
# Add a 'goto' to skip the 'else'
|
|
572
|
-
pcNext = self.
|
|
572
|
+
pcNext = self.getCodeSize()
|
|
573
573
|
cmd = {}
|
|
574
574
|
cmd['lino'] = command['lino']
|
|
575
575
|
cmd['domain'] = 'core'
|
|
@@ -578,15 +578,15 @@ class Core(Handler):
|
|
|
578
578
|
cmd['debug'] = False
|
|
579
579
|
self.add(cmd)
|
|
580
580
|
# Fixup the link to the 'else' branch
|
|
581
|
-
self.getCommandAt(pcElse)['goto'] = self.
|
|
581
|
+
self.getCommandAt(pcElse)['goto'] = self.getCodeSize()
|
|
582
582
|
# Process the 'else' branch
|
|
583
583
|
self.nextToken()
|
|
584
584
|
self.compileOne()
|
|
585
585
|
# Fixup the pcNext 'goto'
|
|
586
|
-
self.getCommandAt(pcNext)['goto'] = self.
|
|
586
|
+
self.getCommandAt(pcNext)['goto'] = self.getCodeSize()
|
|
587
587
|
else:
|
|
588
588
|
# We're already at the next command
|
|
589
|
-
self.getCommandAt(pcElse)['goto'] = self.
|
|
589
|
+
self.getCommandAt(pcElse)['goto'] = self.getCodeSize()
|
|
590
590
|
return True
|
|
591
591
|
|
|
592
592
|
def r_if(self, command):
|
|
@@ -605,7 +605,7 @@ class Core(Handler):
|
|
|
605
605
|
name = self.nextToken()
|
|
606
606
|
item = [keyword, name]
|
|
607
607
|
imports.append(item)
|
|
608
|
-
self.symbols[name] = self.
|
|
608
|
+
self.symbols[name] = self.getCodeSize()
|
|
609
609
|
variable = {}
|
|
610
610
|
variable['domain'] = None
|
|
611
611
|
variable['name'] = name
|
|
@@ -747,7 +747,7 @@ class Core(Handler):
|
|
|
747
747
|
else:
|
|
748
748
|
command['file'] = self.getValue()
|
|
749
749
|
command['or'] = None
|
|
750
|
-
load = self.
|
|
750
|
+
load = self.getCodeSize()
|
|
751
751
|
self.processOr(command, load)
|
|
752
752
|
return True
|
|
753
753
|
else:
|
|
@@ -916,7 +916,7 @@ class Core(Handler):
|
|
|
916
916
|
cmd['debug'] = False
|
|
917
917
|
self.add(cmd)
|
|
918
918
|
# Fixup the link
|
|
919
|
-
command['goto'] = self.
|
|
919
|
+
command['goto'] = self.getCodeSize()
|
|
920
920
|
return True
|
|
921
921
|
return False
|
|
922
922
|
|
|
@@ -1007,7 +1007,7 @@ class Core(Handler):
|
|
|
1007
1007
|
else:
|
|
1008
1008
|
command['result'] = None
|
|
1009
1009
|
command['or'] = None
|
|
1010
|
-
post = self.
|
|
1010
|
+
post = self.getCodeSize()
|
|
1011
1011
|
self.processOr(command, post)
|
|
1012
1012
|
return True
|
|
1013
1013
|
|
|
@@ -1106,7 +1106,7 @@ class Core(Handler):
|
|
|
1106
1106
|
FatalError(self.compiler, f'Symbol {symbolRecord["name"]} is not a value holder')
|
|
1107
1107
|
else:
|
|
1108
1108
|
command['or'] = None
|
|
1109
|
-
self.processOr(command, self.
|
|
1109
|
+
self.processOr(command, self.getCodeSize())
|
|
1110
1110
|
return True
|
|
1111
1111
|
else:
|
|
1112
1112
|
FatalError(self.compiler, f'Symbol {self.getToken()} is not a variable')
|
|
@@ -1274,7 +1274,7 @@ class Core(Handler):
|
|
|
1274
1274
|
else:
|
|
1275
1275
|
command['file'] = self.getValue()
|
|
1276
1276
|
command['or'] = None
|
|
1277
|
-
save = self.
|
|
1277
|
+
save = self.getCodeSize()
|
|
1278
1278
|
self.processOr(command, save)
|
|
1279
1279
|
return True
|
|
1280
1280
|
|
|
@@ -1772,7 +1772,7 @@ class Core(Handler):
|
|
|
1772
1772
|
else:
|
|
1773
1773
|
token = self.nextToken()
|
|
1774
1774
|
if token in ['graphics', 'debugger']:
|
|
1775
|
-
if not
|
|
1775
|
+
if not self.program.usingGraphics:
|
|
1776
1776
|
print('Loading graphics module')
|
|
1777
1777
|
from .ec_pyside import Graphics
|
|
1778
1778
|
self.program.graphics = Graphics
|
|
@@ -1823,10 +1823,10 @@ class Core(Handler):
|
|
|
1823
1823
|
return None
|
|
1824
1824
|
# token = self.getToken()
|
|
1825
1825
|
command['condition'] = code
|
|
1826
|
-
test = self.
|
|
1826
|
+
test = self.getCodeSize()
|
|
1827
1827
|
self.add(command)
|
|
1828
1828
|
# Set up a goto for when the test fails
|
|
1829
|
-
fail = self.
|
|
1829
|
+
fail = self.getCodeSize()
|
|
1830
1830
|
cmd = {}
|
|
1831
1831
|
cmd['lino'] = command['lino']
|
|
1832
1832
|
cmd['domain'] = 'core'
|
|
@@ -1847,7 +1847,7 @@ class Core(Handler):
|
|
|
1847
1847
|
cmd['debug'] = False
|
|
1848
1848
|
self.add(cmd)
|
|
1849
1849
|
# Fixup the 'goto' on completion
|
|
1850
|
-
self.getCommandAt(fail)['goto'] = self.
|
|
1850
|
+
self.getCommandAt(fail)['goto'] = self.getCodeSize()
|
|
1851
1851
|
return True
|
|
1852
1852
|
|
|
1853
1853
|
def r_while(self, command):
|
easycoder/ec_debug.py
ADDED
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import sys, os, json, html
|
|
2
|
+
from PySide6.QtWidgets import (
|
|
3
|
+
QMainWindow,
|
|
4
|
+
QWidget,
|
|
5
|
+
QFrame,
|
|
6
|
+
QHBoxLayout,
|
|
7
|
+
QVBoxLayout,
|
|
8
|
+
QLabel,
|
|
9
|
+
QSplitter,
|
|
10
|
+
QFileDialog,
|
|
11
|
+
QMessageBox,
|
|
12
|
+
QScrollArea,
|
|
13
|
+
QSizePolicy,
|
|
14
|
+
QToolBar
|
|
15
|
+
)
|
|
16
|
+
from PySide6.QtGui import QAction, QKeySequence, QTextCursor
|
|
17
|
+
from PySide6.QtCore import Qt, QTimer, QProcess
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
class Object():
|
|
21
|
+
"""Dynamic object that allows arbitrary attribute assignment"""
|
|
22
|
+
def __setattr__(self, name: str, value: Any) -> None:
|
|
23
|
+
self.__dict__[name] = value
|
|
24
|
+
|
|
25
|
+
def __getattr__(self, name: str) -> Any:
|
|
26
|
+
return self.__dict__.get(name)
|
|
27
|
+
|
|
28
|
+
class Debugger(QMainWindow):
|
|
29
|
+
|
|
30
|
+
###########################################################################
|
|
31
|
+
# The left-hand column of the main window
|
|
32
|
+
class MainLeftColumn(QWidget):
|
|
33
|
+
def __init__(self, parent=None):
|
|
34
|
+
super().__init__(parent)
|
|
35
|
+
layout = QVBoxLayout(self)
|
|
36
|
+
layout.addWidget(QLabel("Left column"))
|
|
37
|
+
layout.addStretch()
|
|
38
|
+
|
|
39
|
+
###########################################################################
|
|
40
|
+
# The right-hand column of the main window
|
|
41
|
+
class MainRightColumn(QWidget):
|
|
42
|
+
scroll: QScrollArea
|
|
43
|
+
layout: QHBoxLayout # type: ignore[assignment]
|
|
44
|
+
blob: QLabel
|
|
45
|
+
|
|
46
|
+
def __init__(self, parent=None):
|
|
47
|
+
super().__init__(parent)
|
|
48
|
+
|
|
49
|
+
# Create a scroll area - its content widget holds the lines
|
|
50
|
+
self.scroll = QScrollArea(self)
|
|
51
|
+
self.scroll.setWidgetResizable(True)
|
|
52
|
+
|
|
53
|
+
# Ensure this widget and the scroll area expand to fill available space
|
|
54
|
+
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
55
|
+
self.scroll.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
|
56
|
+
|
|
57
|
+
self.content = QWidget()
|
|
58
|
+
# let the content expand horizontally but have flexible height
|
|
59
|
+
self.content.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
|
60
|
+
|
|
61
|
+
self.inner_layout = QVBoxLayout(self.content)
|
|
62
|
+
# spacing and small top/bottom margins to separate lines
|
|
63
|
+
self.inner_layout.setSpacing(0)
|
|
64
|
+
self.inner_layout.setContentsMargins(0, 0, 0, 0)
|
|
65
|
+
|
|
66
|
+
self.scroll.setWidget(self.content)
|
|
67
|
+
|
|
68
|
+
# outer layout for this widget contains only the scroll area
|
|
69
|
+
main_layout = QVBoxLayout(self)
|
|
70
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
71
|
+
main_layout.addWidget(self.scroll)
|
|
72
|
+
# ensure the scroll area gets the stretch so it fills the parent
|
|
73
|
+
main_layout.setStretch(0, 1)
|
|
74
|
+
|
|
75
|
+
#######################################################################
|
|
76
|
+
# Add a line to the right-hand column
|
|
77
|
+
def addLine(self, spec):
|
|
78
|
+
class Label(QLabel):
|
|
79
|
+
def __init__(self, text, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft, on_click=spec.onClick):
|
|
80
|
+
super().__init__()
|
|
81
|
+
self.setText(text)
|
|
82
|
+
# remove QLabel's internal margins/padding to reduce top/bottom space
|
|
83
|
+
self.setMargin(0)
|
|
84
|
+
self.setContentsMargins(0, 0, 0, 0)
|
|
85
|
+
self.setStyleSheet("padding:0px; margin:0px; font-family: mono")
|
|
86
|
+
fm = self.fontMetrics()
|
|
87
|
+
# set a compact fixed height based on font metrics
|
|
88
|
+
self.setFixedHeight(fm.height())
|
|
89
|
+
# optional fixed width (used for the lino column)
|
|
90
|
+
if fixed_width is not None:
|
|
91
|
+
self.setFixedWidth(fixed_width)
|
|
92
|
+
# align horizontally (keep vertically centered)
|
|
93
|
+
self.setAlignment(align | Qt.AlignmentFlag.AlignVCenter)
|
|
94
|
+
# optional click callback
|
|
95
|
+
self._on_click = on_click
|
|
96
|
+
|
|
97
|
+
def mousePressEvent(self, event):
|
|
98
|
+
if self._on_click:
|
|
99
|
+
try:
|
|
100
|
+
self._on_click()
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
super().mousePressEvent(event)
|
|
104
|
+
|
|
105
|
+
spec.label = self
|
|
106
|
+
panel = QWidget()
|
|
107
|
+
# ensure the panel itself has no margins
|
|
108
|
+
try:
|
|
109
|
+
panel.setContentsMargins(0, 0, 0, 0)
|
|
110
|
+
except Exception:
|
|
111
|
+
pass
|
|
112
|
+
# tidy layout: remove spacing/margins so lines sit flush
|
|
113
|
+
layout = QHBoxLayout(panel)
|
|
114
|
+
layout.setSpacing(0)
|
|
115
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
116
|
+
self.layout: QHBoxLayout = layout # type: ignore
|
|
117
|
+
# make panel take minimal vertical space
|
|
118
|
+
panel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
119
|
+
# compute width to fit a 4-digit line number using this widget's font
|
|
120
|
+
fm_main = self.fontMetrics()
|
|
121
|
+
width_4 = fm_main.horizontalAdvance('0000') + 8
|
|
122
|
+
|
|
123
|
+
# create the red blob (always present). We'll toggle its opacity
|
|
124
|
+
# by changing the stylesheet (rgba alpha 255/0). Do NOT store it
|
|
125
|
+
# on the MainRightColumn instance — keep it per-line.
|
|
126
|
+
blob = QLabel()
|
|
127
|
+
blob_size = 10
|
|
128
|
+
blob.setFixedSize(blob_size, blob_size)
|
|
129
|
+
|
|
130
|
+
def set_blob_visible(widget, visible):
|
|
131
|
+
alpha = 255 if visible else 0
|
|
132
|
+
widget.setStyleSheet(f"background-color: rgba(255,0,0,{alpha}); border-radius: {blob_size//2}px; margin:0px; padding:0px;")
|
|
133
|
+
widget._blob_visible = visible
|
|
134
|
+
# force repaint
|
|
135
|
+
widget.update()
|
|
136
|
+
|
|
137
|
+
# attach methods to this blob so callers can toggle it via spec.label
|
|
138
|
+
blob.showBlob = lambda: set_blob_visible(blob, True) # type: ignore[attr-defined]
|
|
139
|
+
blob.hideBlob = lambda: set_blob_visible(blob, False) # type: ignore[attr-defined]
|
|
140
|
+
|
|
141
|
+
# initialize according to spec flag
|
|
142
|
+
if spec.bp:
|
|
143
|
+
blob.showBlob() # type: ignore[attr-defined]
|
|
144
|
+
else:
|
|
145
|
+
blob.hideBlob() # type: ignore[attr-defined]
|
|
146
|
+
|
|
147
|
+
# expose the blob to the outside via spec['label'] so onClick can call showBlob/hideBlob
|
|
148
|
+
spec.label = blob
|
|
149
|
+
|
|
150
|
+
# create the line-number label; clicking it reports back to the caller
|
|
151
|
+
lino_label = Label(str(spec.lino+1), fixed_width=width_4, align=Qt.AlignmentFlag.AlignRight,
|
|
152
|
+
on_click=lambda: spec.onClick(spec.lino))
|
|
153
|
+
lino_label.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
|
154
|
+
# create the text label for the line itself
|
|
155
|
+
text_label = Label(spec.line, fixed_width=None, align=Qt.AlignmentFlag.AlignLeft)
|
|
156
|
+
text_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
157
|
+
layout.addWidget(lino_label)
|
|
158
|
+
layout.addSpacing(10)
|
|
159
|
+
layout.addWidget(blob, 0, Qt.AlignmentFlag.AlignVCenter)
|
|
160
|
+
layout.addSpacing(3)
|
|
161
|
+
layout.addWidget(text_label)
|
|
162
|
+
self.inner_layout.addWidget(panel)
|
|
163
|
+
return panel
|
|
164
|
+
|
|
165
|
+
def showBlob(self):
|
|
166
|
+
self.blob.setStyleSheet("background-color: red; border-radius: 5px; margin:0px; padding:0px;")
|
|
167
|
+
|
|
168
|
+
def hideBlob(self):
|
|
169
|
+
self.blob.setStyleSheet("background-color: none; border-radius: 5px; margin:0px; padding:0px;")
|
|
170
|
+
|
|
171
|
+
def addStretch(self):
|
|
172
|
+
self.layout.addStretch()
|
|
173
|
+
|
|
174
|
+
###########################################################################
|
|
175
|
+
# Main debugger class initializer
|
|
176
|
+
def __init__(self, program, width=800, height=600, ratio=0.2):
|
|
177
|
+
super().__init__()
|
|
178
|
+
self.program = program
|
|
179
|
+
self.setWindowTitle("EasyCoder Debugger")
|
|
180
|
+
self.setMinimumSize(width, height)
|
|
181
|
+
self.stopped = True
|
|
182
|
+
|
|
183
|
+
# try to load saved geometry from ~/.ecdebug.conf
|
|
184
|
+
cfg_path = os.path.join(os.path.expanduser("~"), ".ecdebug.conf")
|
|
185
|
+
initial_width = width
|
|
186
|
+
# default console height (pixels) if not stored in cfg
|
|
187
|
+
console_height = 150
|
|
188
|
+
try:
|
|
189
|
+
if os.path.exists(cfg_path):
|
|
190
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
191
|
+
cfg = json.load(f)
|
|
192
|
+
x = int(cfg.get("x", 0))
|
|
193
|
+
y = int(cfg.get("y", 0))
|
|
194
|
+
w = int(cfg.get("width", width))
|
|
195
|
+
h = int(cfg.get("height", height))
|
|
196
|
+
ratio =float(cfg.get("ratio", ratio))
|
|
197
|
+
# load console height if present
|
|
198
|
+
console_height = int(cfg.get("console_height", console_height))
|
|
199
|
+
# Apply loaded geometry
|
|
200
|
+
self.setGeometry(x, y, w, h)
|
|
201
|
+
initial_width = w
|
|
202
|
+
except Exception:
|
|
203
|
+
# ignore errors and continue with defaults
|
|
204
|
+
initial_width = width
|
|
205
|
+
|
|
206
|
+
# process handle for running scripts
|
|
207
|
+
self._proc = None
|
|
208
|
+
# in-process Program instance and writer
|
|
209
|
+
self._program = None
|
|
210
|
+
self._writer = None
|
|
211
|
+
self._orig_stdout = None
|
|
212
|
+
self._orig_stderr = None
|
|
213
|
+
self._flush_timer = None
|
|
214
|
+
|
|
215
|
+
# Keep a ratio so proportions are preserved when window is resized
|
|
216
|
+
self.ratio = ratio
|
|
217
|
+
|
|
218
|
+
# Central horizontal splitter (left/right)
|
|
219
|
+
self.hsplitter = QSplitter(Qt.Orientation.Horizontal, self)
|
|
220
|
+
self.hsplitter.setHandleWidth(8)
|
|
221
|
+
self.hsplitter.splitterMoved.connect(self.on_splitter_moved)
|
|
222
|
+
|
|
223
|
+
# Left pane
|
|
224
|
+
left = QFrame()
|
|
225
|
+
left.setFrameShape(QFrame.Shape.StyledPanel)
|
|
226
|
+
left_layout = QVBoxLayout(left)
|
|
227
|
+
left_layout.setContentsMargins(8, 8, 8, 8)
|
|
228
|
+
self.leftColumn = self.MainLeftColumn()
|
|
229
|
+
left_layout.addWidget(self.leftColumn)
|
|
230
|
+
left_layout.addStretch()
|
|
231
|
+
|
|
232
|
+
# Right pane
|
|
233
|
+
right = QFrame()
|
|
234
|
+
right.setFrameShape(QFrame.Shape.StyledPanel)
|
|
235
|
+
right_layout = QVBoxLayout(right)
|
|
236
|
+
right_layout.setContentsMargins(8, 8, 8, 8)
|
|
237
|
+
self.rightColumn = self.MainRightColumn()
|
|
238
|
+
# Give the rightColumn a stretch factor so its scroll area fills the vertical space
|
|
239
|
+
right_layout.addWidget(self.rightColumn, 1)
|
|
240
|
+
|
|
241
|
+
# Add panes to horizontal splitter
|
|
242
|
+
self.hsplitter.addWidget(left)
|
|
243
|
+
self.hsplitter.addWidget(right)
|
|
244
|
+
|
|
245
|
+
# Initial sizes (proportional) for horizontal splitter
|
|
246
|
+
total = initial_width
|
|
247
|
+
self.hsplitter.setSizes([int(self.ratio * total), int((1 - self.ratio) * total)])
|
|
248
|
+
|
|
249
|
+
# Create a vertical splitter so we can add a resizable console panel at the bottom
|
|
250
|
+
self.vsplitter = QSplitter(Qt.Orientation.Vertical, self)
|
|
251
|
+
self.vsplitter.setHandleWidth(6)
|
|
252
|
+
# top: the existing horizontal splitter
|
|
253
|
+
self.vsplitter.addWidget(self.hsplitter)
|
|
254
|
+
|
|
255
|
+
# bottom: console panel
|
|
256
|
+
console_frame = QFrame()
|
|
257
|
+
console_frame.setFrameShape(QFrame.Shape.StyledPanel)
|
|
258
|
+
console_layout = QVBoxLayout(console_frame)
|
|
259
|
+
console_layout.setContentsMargins(4, 4, 4, 4)
|
|
260
|
+
# simple read-only text console for script output and messages
|
|
261
|
+
from PySide6.QtWidgets import QTextEdit
|
|
262
|
+
self.console = QTextEdit()
|
|
263
|
+
self.console.setReadOnly(True)
|
|
264
|
+
console_layout.addWidget(self.console)
|
|
265
|
+
self.vsplitter.addWidget(console_frame)
|
|
266
|
+
|
|
267
|
+
# Set initial vertical sizes: prefer saved console_height if available
|
|
268
|
+
try:
|
|
269
|
+
total_h = int(h) if 'h' in locals() else max(300, self.height())
|
|
270
|
+
ch = max(50, min(total_h - 50, console_height))
|
|
271
|
+
self.vsplitter.setSizes([int(total_h - ch), int(ch)])
|
|
272
|
+
except Exception:
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
# Use the vertical splitter as the central widget
|
|
276
|
+
self.setCentralWidget(self.vsplitter)
|
|
277
|
+
self.parse(program.script.lines)
|
|
278
|
+
self.show()
|
|
279
|
+
|
|
280
|
+
def on_splitter_moved(self, pos, index):
|
|
281
|
+
# Update stored ratio when user drags the splitter
|
|
282
|
+
left_width = self.hsplitter.widget(0).width()
|
|
283
|
+
total = max(1, sum(w.width() for w in (self.hsplitter.widget(0), self.hsplitter.widget(1))))
|
|
284
|
+
self.ratio = left_width / total
|
|
285
|
+
|
|
286
|
+
def resizeEvent(self, event):
|
|
287
|
+
# Preserve the proportional widths when the window is resized
|
|
288
|
+
total_width = max(1, self.width())
|
|
289
|
+
left_w = max(0, int(self.ratio * total_width))
|
|
290
|
+
right_w = max(0, total_width - left_w)
|
|
291
|
+
self.hsplitter.setSizes([left_w, right_w])
|
|
292
|
+
super().resizeEvent(event)
|
|
293
|
+
|
|
294
|
+
###########################################################################
|
|
295
|
+
# Parse a script into the right-hand column
|
|
296
|
+
def parse(self, script):
|
|
297
|
+
self.scriptLines = []
|
|
298
|
+
# Clear existing lines from the right column layout
|
|
299
|
+
layout = self.rightColumn.inner_layout
|
|
300
|
+
while layout.count():
|
|
301
|
+
item = layout.takeAt(0)
|
|
302
|
+
widget = item.widget()
|
|
303
|
+
if widget:
|
|
304
|
+
widget.deleteLater()
|
|
305
|
+
|
|
306
|
+
# Parse and add new lines
|
|
307
|
+
lino = 0
|
|
308
|
+
for line in script:
|
|
309
|
+
if len(line) > 0:
|
|
310
|
+
line = line.replace("\t", " ")
|
|
311
|
+
line = self.coloriseLine(line, lino)
|
|
312
|
+
else:
|
|
313
|
+
# still need to call coloriseLine to keep token list in sync
|
|
314
|
+
self.coloriseLine(line, lino)
|
|
315
|
+
lineSpec = Object()
|
|
316
|
+
lineSpec.lino = lino
|
|
317
|
+
lineSpec.line = line
|
|
318
|
+
lineSpec.bp = False
|
|
319
|
+
lineSpec.onClick = self.onClickLino
|
|
320
|
+
lino += 1
|
|
321
|
+
self.scriptLines.append(lineSpec)
|
|
322
|
+
lineSpec.panel = self.rightColumn.addLine(lineSpec)
|
|
323
|
+
self.rightColumn.addStretch()
|
|
324
|
+
|
|
325
|
+
###########################################################################
|
|
326
|
+
# Colorise a line of script for HTML display
|
|
327
|
+
def coloriseLine(self, line, lino=None):
|
|
328
|
+
output = ''
|
|
329
|
+
|
|
330
|
+
# Preserve leading spaces (render as except the first)
|
|
331
|
+
if len(line) > 0 and line[0] == ' ':
|
|
332
|
+
output += '<span>'
|
|
333
|
+
n = 0
|
|
334
|
+
while n < len(line) and line[n] == ' ': n += 1
|
|
335
|
+
output += ' ' * (n - 1)
|
|
336
|
+
output += '</span>'
|
|
337
|
+
|
|
338
|
+
# Find the first unquoted ! (not inside backticks)
|
|
339
|
+
comment_start = None
|
|
340
|
+
in_backtick = False
|
|
341
|
+
for idx, c in enumerate(line):
|
|
342
|
+
if c == '`':
|
|
343
|
+
in_backtick = not in_backtick
|
|
344
|
+
elif c == '!' and not in_backtick:
|
|
345
|
+
comment_start = idx
|
|
346
|
+
break
|
|
347
|
+
|
|
348
|
+
if comment_start is not None:
|
|
349
|
+
code_part = line[:comment_start]
|
|
350
|
+
comment_part = line[comment_start:]
|
|
351
|
+
else:
|
|
352
|
+
code_part = line
|
|
353
|
+
comment_part = None
|
|
354
|
+
|
|
355
|
+
# Tokenize code_part as before (respecting backticks)
|
|
356
|
+
tokens = []
|
|
357
|
+
i = 0
|
|
358
|
+
L = len(code_part)
|
|
359
|
+
while i < L:
|
|
360
|
+
if code_part[i].isspace():
|
|
361
|
+
i += 1
|
|
362
|
+
continue
|
|
363
|
+
if code_part[i] == '`':
|
|
364
|
+
j = code_part.find('`', i + 1)
|
|
365
|
+
if j == -1:
|
|
366
|
+
tokens.append(code_part[i:])
|
|
367
|
+
break
|
|
368
|
+
else:
|
|
369
|
+
tokens.append(code_part[i:j+1])
|
|
370
|
+
i = j + 1
|
|
371
|
+
else:
|
|
372
|
+
j = i
|
|
373
|
+
while j < L and not code_part[j].isspace():
|
|
374
|
+
j += 1
|
|
375
|
+
tokens.append(code_part[i:j])
|
|
376
|
+
i = j
|
|
377
|
+
|
|
378
|
+
# Colour code tokens and generate a list of elements
|
|
379
|
+
for token in tokens:
|
|
380
|
+
if token == '':
|
|
381
|
+
continue
|
|
382
|
+
elif token[0].isupper():
|
|
383
|
+
esc = html.escape(token)
|
|
384
|
+
element = f' <span style="color: purple; font-weight: bold;">{esc}</span>'
|
|
385
|
+
elif token[0].isdigit():
|
|
386
|
+
esc = html.escape(token)
|
|
387
|
+
element = f' <span style="color: green;">{esc}</span>'
|
|
388
|
+
elif token[0] == '`':
|
|
389
|
+
esc = html.escape(token)
|
|
390
|
+
element = f' <span style="color: peru;">{esc}</span>'
|
|
391
|
+
else:
|
|
392
|
+
esc = html.escape(token)
|
|
393
|
+
element = f' <span>{esc}</span>'
|
|
394
|
+
output += element
|
|
395
|
+
# Colour comment if present
|
|
396
|
+
if comment_part is not None:
|
|
397
|
+
esc = html.escape(comment_part)
|
|
398
|
+
output += f'<span style="color: green;"> {esc}</span>'
|
|
399
|
+
|
|
400
|
+
return output
|
|
401
|
+
|
|
402
|
+
###########################################################################
|
|
403
|
+
# Here when the user clicks a line number
|
|
404
|
+
def onClickLino(self, lino):
|
|
405
|
+
lineSpec = self.scriptLines[lino]
|
|
406
|
+
lineSpec.bp = not lineSpec.bp
|
|
407
|
+
if lineSpec.bp: lineSpec.label.showBlob()
|
|
408
|
+
else: lineSpec.label.hideBlob()
|
|
409
|
+
# Set a breakpoint on this command
|
|
410
|
+
command = self.program.code[self.program.pc]
|
|
411
|
+
command['bp'] = True
|
|
412
|
+
self.program.code[self.program.pc] = command
|
|
413
|
+
|
|
414
|
+
###########################################################################
|
|
415
|
+
# Scroll to a given line number
|
|
416
|
+
def scrollTo(self, lino):
|
|
417
|
+
# Ensure the line number is valid
|
|
418
|
+
if lino < 0 or lino >= len(self.scriptLines):
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
# Get the panel widget for this line
|
|
422
|
+
lineSpec = self.scriptLines[lino]
|
|
423
|
+
panel = lineSpec.panel
|
|
424
|
+
|
|
425
|
+
if not panel:
|
|
426
|
+
return
|
|
427
|
+
|
|
428
|
+
# Get the scroll area from the right column
|
|
429
|
+
scroll_area = self.rightColumn.scroll
|
|
430
|
+
|
|
431
|
+
# Get the vertical position of the panel relative to the content widget
|
|
432
|
+
panel_y = panel.y()
|
|
433
|
+
panel_height = panel.height()
|
|
434
|
+
|
|
435
|
+
# Get the viewport height (visible area)
|
|
436
|
+
viewport_height = scroll_area.viewport().height()
|
|
437
|
+
|
|
438
|
+
# Calculate the target scroll position to center the panel
|
|
439
|
+
# We want the panel's center to align with the viewport's center
|
|
440
|
+
target_scroll = panel_y + (panel_height // 2) - (viewport_height // 2)
|
|
441
|
+
|
|
442
|
+
# Clamp to valid scroll range
|
|
443
|
+
scrollbar = scroll_area.verticalScrollBar()
|
|
444
|
+
target_scroll = max(scrollbar.minimum(), min(target_scroll, scrollbar.maximum()))
|
|
445
|
+
|
|
446
|
+
# Smoothly scroll to the target position
|
|
447
|
+
scrollbar.setValue(target_scroll)
|
|
448
|
+
|
|
449
|
+
# Bring the window to the front
|
|
450
|
+
self.raise_()
|
|
451
|
+
self.activateWindow()
|
|
452
|
+
|
|
453
|
+
###########################################################################
|
|
454
|
+
# Here when each instruction is about to run
|
|
455
|
+
def step(self):
|
|
456
|
+
if self.stopped:
|
|
457
|
+
lino=self.program.code[self.program.pc]['lino']
|
|
458
|
+
print(lino)
|
|
459
|
+
self.scrollTo(lino)
|
|
460
|
+
return False
|
|
461
|
+
else:
|
|
462
|
+
if self.program.code[self.program.pc]['bp']:
|
|
463
|
+
pass
|
|
464
|
+
return True
|
easycoder/ec_handler.py
CHANGED
|
@@ -22,7 +22,7 @@ class Handler:
|
|
|
22
22
|
self.compileVariable = compiler.compileVariable
|
|
23
23
|
self.rewindTo = compiler.rewindTo
|
|
24
24
|
self.warning = compiler.warning
|
|
25
|
-
self.
|
|
25
|
+
self.getCodeSize = compiler.getCodeSize
|
|
26
26
|
self.add = compiler.addCommand
|
|
27
27
|
self.getCommandAt = compiler.getCommandAt
|
|
28
28
|
self.compileOne = compiler.compileOne
|
easycoder/ec_program.py
CHANGED
|
@@ -47,6 +47,9 @@ class Program:
|
|
|
47
47
|
self.useClass(Core)
|
|
48
48
|
self.externalControl = False
|
|
49
49
|
self.ticker = 0
|
|
50
|
+
self.usingGraphics = False
|
|
51
|
+
self.debugging = False
|
|
52
|
+
self.debugger = None
|
|
50
53
|
self.running = True
|
|
51
54
|
|
|
52
55
|
# This is called at 10msec intervals by the GUI code
|
|
@@ -147,8 +150,9 @@ class Program:
|
|
|
147
150
|
name = value['name']
|
|
148
151
|
symbolRecord = self.getSymbolRecord(name)
|
|
149
152
|
# if symbolRecord['hasValue']:
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
if symbolRecord:
|
|
154
|
+
handler = self.domainIndex[symbolRecord['domain']].valueHandler('symbol')
|
|
155
|
+
result = handler(symbolRecord)
|
|
152
156
|
# else:
|
|
153
157
|
# # Call the given domain to handle a value
|
|
154
158
|
# # domain = self.domainIndex[value['domain']]
|
|
@@ -181,7 +185,10 @@ class Program:
|
|
|
181
185
|
return None
|
|
182
186
|
|
|
183
187
|
def getValue(self, value):
|
|
184
|
-
|
|
188
|
+
result = self.evaluate(value)
|
|
189
|
+
if result:
|
|
190
|
+
return result.get('content') # type: ignore[union-attr]
|
|
191
|
+
return None
|
|
185
192
|
|
|
186
193
|
def getRuntimeValue(self, value):
|
|
187
194
|
if value is None:
|
|
@@ -287,9 +294,9 @@ class Program:
|
|
|
287
294
|
return
|
|
288
295
|
|
|
289
296
|
def releaseParent(self):
|
|
290
|
-
if self.parent.waiting and self.parent.program.running:
|
|
291
|
-
self.parent.waiting = False
|
|
292
|
-
self.parent.program.run(self.parent.pc)
|
|
297
|
+
if self.parent and self.parent.waiting and self.parent.program.running: # type: ignore[union-attr]
|
|
298
|
+
self.parent.waiting = False # type: ignore[union-attr]
|
|
299
|
+
self.parent.program.run(self.parent.pc) # type: ignore[union-attr]
|
|
293
300
|
|
|
294
301
|
# Flush the queue
|
|
295
302
|
def flush(self, pc):
|
|
@@ -306,6 +313,8 @@ class Program:
|
|
|
306
313
|
lino = command['lino'] + 1
|
|
307
314
|
line = self.script.lines[command['lino']].strip()
|
|
308
315
|
print(f'{self.name}: Line {lino}: {domainName}:{keyword}: {line}')
|
|
316
|
+
if self.debugger != None:
|
|
317
|
+
self.debugger.step()
|
|
309
318
|
domain = self.domainIndex[domainName]
|
|
310
319
|
handler = domain.runHandler(keyword)
|
|
311
320
|
if handler:
|
|
@@ -378,9 +387,9 @@ class Program:
|
|
|
378
387
|
if type(v2) == int:
|
|
379
388
|
if type(v1) != int:
|
|
380
389
|
v2 = f'{v2}'
|
|
381
|
-
if v1 > v2:
|
|
390
|
+
if v1 > v2: # type: ignore[operator]
|
|
382
391
|
return 1
|
|
383
|
-
if v1 < v2:
|
|
392
|
+
if v1 < v2: # type: ignore[operator]
|
|
384
393
|
return -1
|
|
385
394
|
return 0
|
|
386
395
|
|
easycoder/ec_pyside.py
CHANGED
|
@@ -3,6 +3,7 @@ from functools import partial
|
|
|
3
3
|
from .ec_handler import Handler
|
|
4
4
|
from .ec_classes import RuntimeError, Object
|
|
5
5
|
from .ec_border import Border
|
|
6
|
+
from .ec_debug import Debugger
|
|
6
7
|
from PySide6.QtCore import Qt, QTimer, Signal, QRect
|
|
7
8
|
from PySide6.QtGui import QPixmap, QPainter
|
|
8
9
|
from PySide6.QtWidgets import (
|
|
@@ -835,12 +836,7 @@ class Graphics(Handler):
|
|
|
835
836
|
return self.nextPC()
|
|
836
837
|
|
|
837
838
|
# Initialize the graphics environment
|
|
838
|
-
def k_init(self, command):
|
|
839
|
-
# return True
|
|
840
|
-
if self.nextIs('graphics'):
|
|
841
|
-
self.add(command)
|
|
842
|
-
return True
|
|
843
|
-
return False
|
|
839
|
+
# Unused: def k_init(self, command):
|
|
844
840
|
|
|
845
841
|
def r_init(self, command):
|
|
846
842
|
self.app = QApplication(sys.argv)
|
|
@@ -862,6 +858,7 @@ class Graphics(Handler):
|
|
|
862
858
|
timer.timeout.connect(flush)
|
|
863
859
|
timer.start(10)
|
|
864
860
|
QTimer.singleShot(500, init)
|
|
861
|
+
if self.program.debugging: self.program.debugger = Debugger(self.program)
|
|
865
862
|
self.app.lastWindowClosed.connect(on_last_window_closed)
|
|
866
863
|
self.app.exec()
|
|
867
864
|
|
|
@@ -912,11 +909,11 @@ class Graphics(Handler):
|
|
|
912
909
|
# on tick
|
|
913
910
|
def k_on(self, command):
|
|
914
911
|
def setupOn():
|
|
915
|
-
command['goto'] = self.
|
|
912
|
+
command['goto'] = self.getCodeSize() + 2
|
|
916
913
|
self.add(command)
|
|
917
914
|
self.nextToken()
|
|
918
915
|
# Step over the click handler
|
|
919
|
-
pcNext = self.
|
|
916
|
+
pcNext = self.getCodeSize()
|
|
920
917
|
cmd = {}
|
|
921
918
|
cmd['domain'] = 'core'
|
|
922
919
|
cmd['lino'] = command['lino']
|
|
@@ -933,7 +930,7 @@ class Graphics(Handler):
|
|
|
933
930
|
cmd['debug'] = False
|
|
934
931
|
self.add(cmd)
|
|
935
932
|
# Fixup the goto
|
|
936
|
-
self.getCommandAt(pcNext)['goto'] = self.
|
|
933
|
+
self.getCommandAt(pcNext)['goto'] = self.getCodeSize()
|
|
937
934
|
|
|
938
935
|
token = self.nextToken()
|
|
939
936
|
command['type'] = token
|
|
@@ -953,11 +950,11 @@ class Graphics(Handler):
|
|
|
953
950
|
return True
|
|
954
951
|
elif token == 'tick':
|
|
955
952
|
command['tick'] = True
|
|
956
|
-
command['runOnTick'] = self.
|
|
953
|
+
command['runOnTick'] = self.getCodeSize() + 2
|
|
957
954
|
self.add(command)
|
|
958
955
|
self.nextToken()
|
|
959
956
|
# Step over the on tick action
|
|
960
|
-
pcNext = self.
|
|
957
|
+
pcNext = self.getCodeSize()
|
|
961
958
|
cmd = {}
|
|
962
959
|
cmd['domain'] = 'core'
|
|
963
960
|
cmd['lino'] = command['lino']
|
|
@@ -974,7 +971,7 @@ class Graphics(Handler):
|
|
|
974
971
|
cmd['debug'] = False
|
|
975
972
|
self.add(cmd)
|
|
976
973
|
# Fixup the goto
|
|
977
|
-
self.getCommandAt(pcNext)['goto'] = self.
|
|
974
|
+
self.getCommandAt(pcNext)['goto'] = self.getCodeSize()
|
|
978
975
|
return True
|
|
979
976
|
return False
|
|
980
977
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easycoder
|
|
3
|
-
Version:
|
|
3
|
+
Version: 251104.2
|
|
4
4
|
Summary: Rapid scripting in English
|
|
5
5
|
Keywords: compiler,scripting,prototyping,programming,coding,python,low code,hypertalk,computer language,learn to code
|
|
6
6
|
Author-email: Graham Trott <gtanyware@gmail.com>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
easycoder/__init__.py,sha256=MRZ5qqYf7XUeRfrculBoOmXvlayyAdVqU7wI5n47Bqs,339
|
|
2
|
+
easycoder/close.png,sha256=3B9ueRNtEu9E4QNmZhdyC4VL6uqKvGmdfeFxIV9aO_Y,9847
|
|
3
|
+
easycoder/ec_border.py,sha256=KpOy0Jq8jI_6DYGo4jaFvoBP_jTIoAYWrmuHhl-FXA4,2355
|
|
4
|
+
easycoder/ec_classes.py,sha256=EWpB3Wta_jvKZ8SNIWua_ElIbw1FzKMyM3_IiXBn-lg,1995
|
|
5
|
+
easycoder/ec_compiler.py,sha256=Q6a9nMmZogJzHu8OB4VeMzmBBarVEl3-RkqH2gW4LiU,6599
|
|
6
|
+
easycoder/ec_condition.py,sha256=uamZrlW3Ej3R4bPDuduGB2f00M80Z1D0qV8muDx4Qfw,784
|
|
7
|
+
easycoder/ec_core.py,sha256=F8MQ1dJfzQoYxTrCyJ7aUrmyxjQEW4dmWd9uq71rR0w,100876
|
|
8
|
+
easycoder/ec_debug.py,sha256=JrF8GDzymB2YCOuULtlot4jO1yiVEbt_hQqjZPDcPEI,18785
|
|
9
|
+
easycoder/ec_handler.py,sha256=doGCMXBCQxvSaD3omKMlXoR_LvQODxV7dZyoWafecyg,2336
|
|
10
|
+
easycoder/ec_keyboard.py,sha256=H8DhPT8-IvAIGgRxCs4qSaF_AQLaS6lxxtDteejbktM,19010
|
|
11
|
+
easycoder/ec_program.py,sha256=biRWYKUs7ywFEgTte0oerTyPxgZlP4odfv_xJAN7ao8,10696
|
|
12
|
+
easycoder/ec_pyside.py,sha256=WDzJn64fPKMJCwTBXjngCRkJyS4fA72FmwfUoqul-Hw,58400
|
|
13
|
+
easycoder/ec_timestamp.py,sha256=myQnnF-mT31_1dpQKv2VEAu4BCcbypvMdzq7_DUi1xc,277
|
|
14
|
+
easycoder/ec_value.py,sha256=zgDJTJhIg3yOvmnnKIfccIizmIhGbtvL_ghLTL1T5fg,2516
|
|
15
|
+
easycoder/tick.png,sha256=OedASXJJTYvnza4J6Kv5m5lz6DrBfy667zX_WGgtbmM,9127
|
|
16
|
+
easycoder-251104.2.dist-info/entry_points.txt,sha256=JXAZbenl0TnsIft2FcGJbJ-4qoztVu2FuT8PFmWFexM,44
|
|
17
|
+
easycoder-251104.2.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
18
|
+
easycoder-251104.2.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
|
19
|
+
easycoder-251104.2.dist-info/METADATA,sha256=SFh8m9keUUTbvuC0q4R3n7lCuBA_whA4Q2Pt8s1EMWc,6897
|
|
20
|
+
easycoder-251104.2.dist-info/RECORD,,
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
easycoder/__init__.py,sha256=wKNst-IocgEEuxeW5PXV9sMNZLt2GSUBeD6LNfBcYrQ,339
|
|
2
|
-
easycoder/close.png,sha256=3B9ueRNtEu9E4QNmZhdyC4VL6uqKvGmdfeFxIV9aO_Y,9847
|
|
3
|
-
easycoder/ec_border.py,sha256=KpOy0Jq8jI_6DYGo4jaFvoBP_jTIoAYWrmuHhl-FXA4,2355
|
|
4
|
-
easycoder/ec_classes.py,sha256=YGUiKnVN6T5scoeBmmGDQAtE8xJgaTHi0Exh9A7H2Y4,1750
|
|
5
|
-
easycoder/ec_compiler.py,sha256=1zQ0ApmeHLqPAyP3tKGg1OR0kTEx2S-u6rAxP6EzHS0,5517
|
|
6
|
-
easycoder/ec_condition.py,sha256=uamZrlW3Ej3R4bPDuduGB2f00M80Z1D0qV8muDx4Qfw,784
|
|
7
|
-
easycoder/ec_core.py,sha256=c51wdwTwppZwGLOhN22SBCdAmnTFQw0eirMJxkOb8vI,100780
|
|
8
|
-
easycoder/ec_handler.py,sha256=zEZ5cPruEVZp3SIQ6ZjdZN5jDyW2gFvHcNmFaet4Sd4,2324
|
|
9
|
-
easycoder/ec_keyboard.py,sha256=H8DhPT8-IvAIGgRxCs4qSaF_AQLaS6lxxtDteejbktM,19010
|
|
10
|
-
easycoder/ec_program.py,sha256=aPuZOYWFqGd1jsLDl5M5YmLu5LfFAewF9EZh6zHIbyM,10308
|
|
11
|
-
easycoder/ec_pyside.py,sha256=Pkf0lgDTgC7WOjjleiGeBj4e9y9de4DDNlZFsajkVjU,58374
|
|
12
|
-
easycoder/ec_timestamp.py,sha256=myQnnF-mT31_1dpQKv2VEAu4BCcbypvMdzq7_DUi1xc,277
|
|
13
|
-
easycoder/ec_value.py,sha256=zgDJTJhIg3yOvmnnKIfccIizmIhGbtvL_ghLTL1T5fg,2516
|
|
14
|
-
easycoder/tick.png,sha256=OedASXJJTYvnza4J6Kv5m5lz6DrBfy667zX_WGgtbmM,9127
|
|
15
|
-
easycoder-251103.3.dist-info/entry_points.txt,sha256=JXAZbenl0TnsIft2FcGJbJ-4qoztVu2FuT8PFmWFexM,44
|
|
16
|
-
easycoder-251103.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
17
|
-
easycoder-251103.3.dist-info/WHEEL,sha256=Dyt6SBfaasWElUrURkknVFAZDHSTwxg3PaTza7RSbkY,100
|
|
18
|
-
easycoder-251103.3.dist-info/METADATA,sha256=BIHn7arUH62vZ-y2lCwKbYIFuCU4Yl_SVZgUVvFKAs8,6897
|
|
19
|
-
easycoder-251103.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|