easycoder 260108.1__py2.py3-none-any.whl → 260118.3__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 +17 -14
- easycoder/ec_compiler.py +1 -0
- easycoder/ec_core.py +101 -51
- easycoder/ec_gclasses.py +36 -9
- easycoder/ec_graphics.py +65 -12
- easycoder/ec_handler.py +1 -1
- easycoder/ec_mqtt.py +229 -43
- easycoder/ec_program.py +25 -17
- easycoder/ec_value.py +4 -3
- easycoder/pre/ec_core.py +8 -9
- easycoder/pre/ec_program.py +1 -1
- easycoder/pre/ec_value.py +1 -1
- {easycoder-260108.1.dist-info → easycoder-260118.3.dist-info}/METADATA +2 -1
- {easycoder-260108.1.dist-info → easycoder-260118.3.dist-info}/RECORD +18 -18
- {easycoder-260108.1.dist-info → easycoder-260118.3.dist-info}/WHEEL +0 -0
- {easycoder-260108.1.dist-info → easycoder-260118.3.dist-info}/entry_points.txt +0 -0
- {easycoder-260108.1.dist-info → easycoder-260118.3.dist-info}/licenses/LICENSE +0 -0
easycoder/ec_handler.py
CHANGED
easycoder/ec_mqtt.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
from easycoder import Handler, ECObject, ECValue,
|
|
1
|
+
from easycoder import Handler, ECObject, ECValue, RuntimeError
|
|
2
2
|
import paho.mqtt.client as mqtt
|
|
3
3
|
import time
|
|
4
|
-
|
|
4
|
+
import threading
|
|
5
|
+
import json
|
|
6
|
+
|
|
5
7
|
#############################################################################
|
|
6
8
|
# MQTT client class
|
|
7
9
|
class MQTTClient():
|
|
@@ -14,7 +16,14 @@ class MQTTClient():
|
|
|
14
16
|
self.broker = broker
|
|
15
17
|
self.port = port
|
|
16
18
|
self.topics = topics
|
|
19
|
+
self.onConnectPC = None
|
|
17
20
|
self.onMessagePC = None
|
|
21
|
+
self.timeout = False
|
|
22
|
+
self.messages = {}
|
|
23
|
+
self.chunked_messages = {} # Store incoming chunked messages {topic: {part_num: data}}
|
|
24
|
+
self.confirmation_lock = threading.Lock()
|
|
25
|
+
self.chunk_size = 1024 # Default chunk size
|
|
26
|
+
self.last_send_time = None # Time taken for last message transmission (seconds)
|
|
18
27
|
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=self.clientID) # type: ignore
|
|
19
28
|
|
|
20
29
|
# Setup callbacks
|
|
@@ -25,28 +34,132 @@ class MQTTClient():
|
|
|
25
34
|
print(f"Client {self.clientID} connected")
|
|
26
35
|
for item in self.topics:
|
|
27
36
|
topic = self.program.getObject(self.program.getVariable(item))
|
|
28
|
-
self.client.subscribe(topic.getName(), qos=topic.
|
|
29
|
-
print(f"Subscribed to topic: {topic.getName()} with QoS {topic.
|
|
37
|
+
self.client.subscribe(topic.getName(), qos=topic.getQoS())
|
|
38
|
+
print(f"Subscribed to topic: {topic.getName()} with QoS {topic.getQoS()}")
|
|
39
|
+
|
|
40
|
+
if self.onConnectPC is not None:
|
|
41
|
+
self.program.run(self.onConnectPC)
|
|
42
|
+
self.program.flushCB()
|
|
30
43
|
|
|
31
44
|
def on_message(self, client, userdata, msg):
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
payload = msg.payload.decode('utf-8')
|
|
46
|
+
topic = msg.topic
|
|
47
|
+
|
|
48
|
+
# Check if this is a chunked message (format: "!part!<n> <total><data>")
|
|
49
|
+
if payload.startswith('!part!'):
|
|
50
|
+
# Extract: "!part!<n> <total><data>"
|
|
51
|
+
header_end = payload.find(' ', 6) # Find space after part number
|
|
52
|
+
if header_end > 6:
|
|
53
|
+
try:
|
|
54
|
+
part_num = int(payload[6:header_end]) # Extract part number
|
|
55
|
+
# Find next space to get total_chunks
|
|
56
|
+
total_end = payload.find(' ', header_end + 1)
|
|
57
|
+
if total_end > header_end:
|
|
58
|
+
total_chunks = int(payload[header_end + 1:total_end])
|
|
59
|
+
data = payload[total_end + 1:] # Rest is data
|
|
60
|
+
|
|
61
|
+
# Initialize chunked message storage if this is part 0
|
|
62
|
+
if part_num == 0:
|
|
63
|
+
self.chunked_messages[topic] = {}
|
|
64
|
+
|
|
65
|
+
# Store this chunk with its part number
|
|
66
|
+
if topic in self.chunked_messages:
|
|
67
|
+
self.chunked_messages[topic][part_num] = data
|
|
68
|
+
print(f"Received chunk {part_num}/{total_chunks - 1} on topic {topic}")
|
|
69
|
+
except (ValueError, IndexError):
|
|
70
|
+
pass
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
elif payload.startswith('!last!'):
|
|
74
|
+
# Final chunk: "!last!<total><data>"
|
|
75
|
+
try:
|
|
76
|
+
# Find where the total ends and data begins (first space after !last!)
|
|
77
|
+
space_pos = payload.find(' ', 6)
|
|
78
|
+
if space_pos > 6:
|
|
79
|
+
total_chunks = int(payload[6:space_pos])
|
|
80
|
+
data = payload[space_pos + 1:] # Rest is data
|
|
81
|
+
|
|
82
|
+
# Initialize topic storage if not present (single chunk case)
|
|
83
|
+
if topic not in self.chunked_messages:
|
|
84
|
+
self.chunked_messages[topic] = {}
|
|
85
|
+
|
|
86
|
+
# Store the last chunk
|
|
87
|
+
self.chunked_messages[topic][total_chunks - 1] = data
|
|
88
|
+
|
|
89
|
+
# Verify all chunks are present
|
|
90
|
+
expected_parts = set(range(total_chunks))
|
|
91
|
+
received_parts = set(self.chunked_messages[topic].keys())
|
|
92
|
+
|
|
93
|
+
if expected_parts == received_parts:
|
|
94
|
+
# All chunks received - assemble complete message
|
|
95
|
+
message_parts = [self.chunked_messages[topic][i] for i in sorted(self.chunked_messages[topic].keys())]
|
|
96
|
+
complete_message = ''.join(message_parts)
|
|
97
|
+
del self.chunked_messages[topic]
|
|
98
|
+
|
|
99
|
+
# Confirmation is now handled at the EasyCoder level
|
|
100
|
+
# print(f"All chunks received for topic {topic} ({len(complete_message)} bytes total).")
|
|
101
|
+
self.message = {"topic": topic, "payload": complete_message}
|
|
102
|
+
|
|
103
|
+
if self.onMessagePC is not None:
|
|
104
|
+
# print(f'Run from PC {self.onMessagePC}')
|
|
105
|
+
self.program.run(self.onMessagePC)
|
|
106
|
+
self.program.flushCB()
|
|
107
|
+
else:
|
|
108
|
+
missing = expected_parts - received_parts
|
|
109
|
+
print(f"Warning: Missing chunks {missing} for topic {topic}")
|
|
110
|
+
except (ValueError, IndexError):
|
|
111
|
+
pass
|
|
112
|
+
return
|
|
37
113
|
|
|
38
114
|
def getMessageTopic(self):
|
|
39
|
-
return self.message.topic
|
|
115
|
+
return self.message.topic # type: ignore
|
|
40
116
|
|
|
41
|
-
def
|
|
42
|
-
return self.message
|
|
117
|
+
def getReceivedMessage(self):
|
|
118
|
+
return self.message
|
|
43
119
|
|
|
44
120
|
def onMessage(self, pc):
|
|
45
121
|
self.onMessagePC = pc
|
|
122
|
+
|
|
123
|
+
def sendMessage(self, topic, message, qos, chunk_size=0):
|
|
124
|
+
"""Send a message, optionally chunked if chunk_size > 0
|
|
125
|
+
|
|
126
|
+
Stores transmission time in self.last_send_time (seconds)
|
|
127
|
+
"""
|
|
128
|
+
send_start = time.time()
|
|
129
|
+
|
|
130
|
+
# Send message in chunks using selected strategy
|
|
131
|
+
message_len = len(message)
|
|
132
|
+
num_chunks = (message_len + chunk_size - 1) // chunk_size
|
|
133
|
+
|
|
134
|
+
# print(f'Sending message ({message_len} bytes) in {num_chunks} chunks of size {chunk_size} to topic {topic} with QoS {qos}')
|
|
135
|
+
|
|
136
|
+
self._send_rapid_fire(topic, message, qos, chunk_size, num_chunks)
|
|
137
|
+
|
|
138
|
+
self.last_send_time = time.time() - send_start
|
|
139
|
+
print(f'Message transmission complete in {self.last_send_time:.3f} seconds')
|
|
46
140
|
|
|
47
|
-
def
|
|
48
|
-
|
|
141
|
+
def _send_rapid_fire(self, topic, message, qos, chunk_size, num_chunks):
|
|
142
|
+
"""Send all chunks as rapidly as possible, wait for single final confirmation"""
|
|
143
|
+
# print(f"Sending all {num_chunks} chunks as fast as possible...")
|
|
144
|
+
|
|
145
|
+
# Send all chunks rapidly
|
|
146
|
+
for i in range(num_chunks):
|
|
147
|
+
start = i * chunk_size
|
|
148
|
+
end = min(start + chunk_size, len(message))
|
|
149
|
+
chunk_data = message[start:end]
|
|
150
|
+
|
|
151
|
+
# Prepare chunk with header: "!part!<n> <total><data>" or "!last!<total><data>"
|
|
152
|
+
if i == num_chunks - 1:
|
|
153
|
+
chunk_msg = f"!last!{num_chunks} {chunk_data}"
|
|
154
|
+
else:
|
|
155
|
+
chunk_msg = f"!part!{i} {num_chunks} {chunk_data}"
|
|
156
|
+
|
|
157
|
+
# Send without waiting
|
|
158
|
+
self.client.publish(topic, chunk_msg, qos=qos)
|
|
159
|
+
# print(f"Sent chunk {i}/{num_chunks - 1} to topic {topic} with QoS {qos}: {chunk_msg}")
|
|
160
|
+
# No waiting here; confirmations are handled in EasyCoder using sender identity
|
|
49
161
|
|
|
162
|
+
# Start the MQTT client loop
|
|
50
163
|
def run(self):
|
|
51
164
|
self.client.connect(self.broker, int(self.port), 60)
|
|
52
165
|
self.client.loop_start()
|
|
@@ -57,19 +170,31 @@ class ECTopic(ECObject):
|
|
|
57
170
|
def __init__(self):
|
|
58
171
|
super().__init__()
|
|
59
172
|
|
|
60
|
-
def create(self, name, qos=1):
|
|
61
|
-
super().__init__()
|
|
62
|
-
self.name = name
|
|
63
|
-
self.qos = qos
|
|
64
|
-
|
|
65
173
|
def getName(self):
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
174
|
+
v = self.getValue()
|
|
175
|
+
if v is None:
|
|
176
|
+
return ""
|
|
177
|
+
if v is None:
|
|
178
|
+
return ""
|
|
179
|
+
return v['name']
|
|
180
|
+
|
|
181
|
+
def getQoS(self):
|
|
182
|
+
v = self.getValue()
|
|
183
|
+
if v is None:
|
|
184
|
+
return 0
|
|
185
|
+
if v is None:
|
|
186
|
+
return 0
|
|
187
|
+
return int(v['qos'])
|
|
188
|
+
|
|
189
|
+
def textify(self):
|
|
190
|
+
v = self.getValue()
|
|
191
|
+
if v is None:
|
|
192
|
+
return ""
|
|
193
|
+
return f'{{"name": "{v["name"]}", "qos": {v["qos"]}}}'
|
|
70
194
|
|
|
71
195
|
###############################################################################
|
|
72
|
-
|
|
196
|
+
###############################################################################
|
|
197
|
+
# The MQTT compiler and runtime handlers
|
|
73
198
|
class MQTT(Handler):
|
|
74
199
|
|
|
75
200
|
def __init__(self, compiler):
|
|
@@ -89,7 +214,8 @@ class MQTT(Handler):
|
|
|
89
214
|
self.checkObjectType(record, ECTopic)
|
|
90
215
|
command['topic'] = record['name']
|
|
91
216
|
self.skip('name')
|
|
92
|
-
|
|
217
|
+
name =self.nextValue()
|
|
218
|
+
command['name'] = name
|
|
93
219
|
self.skip('qos')
|
|
94
220
|
command['qos'] = self.nextValue()
|
|
95
221
|
self.add(command)
|
|
@@ -99,7 +225,10 @@ class MQTT(Handler):
|
|
|
99
225
|
def r_init(self, command):
|
|
100
226
|
record = self.getVariable(command['topic'])
|
|
101
227
|
topic = ECTopic()
|
|
102
|
-
|
|
228
|
+
value = {}
|
|
229
|
+
value['name'] = self.textify(command['name'])
|
|
230
|
+
value['qos'] = int(self.textify(command['qos']))
|
|
231
|
+
topic.setValue(value)
|
|
103
232
|
record['object'] = topic
|
|
104
233
|
return self.nextPC()
|
|
105
234
|
|
|
@@ -149,7 +278,9 @@ class MQTT(Handler):
|
|
|
149
278
|
token = self.peek()
|
|
150
279
|
if token == 'mqtt':
|
|
151
280
|
self.nextToken()
|
|
152
|
-
|
|
281
|
+
event = self.nextToken()
|
|
282
|
+
if event in ['connect', 'message']:
|
|
283
|
+
command['event'] = event
|
|
153
284
|
self.nextToken()
|
|
154
285
|
command['goto'] = 0
|
|
155
286
|
self.add(command)
|
|
@@ -174,23 +305,33 @@ class MQTT(Handler):
|
|
|
174
305
|
return False
|
|
175
306
|
|
|
176
307
|
def r_on(self, command):
|
|
177
|
-
|
|
308
|
+
event = command['event']
|
|
309
|
+
if event == 'connect':
|
|
310
|
+
self.program.mqttClient.onConnectPC = self.nextPC()+1
|
|
311
|
+
elif event == 'message':
|
|
312
|
+
self.program.mqttClient.onMessagePC = self.nextPC()+1
|
|
178
313
|
return command['goto']
|
|
179
314
|
|
|
180
315
|
# send {message} to {topic}
|
|
181
316
|
def k_send(self, command):
|
|
182
317
|
if self.nextIs('mqtt'):
|
|
183
318
|
command['message'] = self.nextValue()
|
|
184
|
-
self.skip('from')
|
|
185
|
-
if self.nextIsSymbol():
|
|
186
|
-
record = self.getSymbolRecord()
|
|
187
|
-
self.checkObjectType(record, MQTTClient)
|
|
188
|
-
command['from'] = record['name']
|
|
189
319
|
self.skip('to')
|
|
190
320
|
if self.nextIsSymbol():
|
|
191
321
|
record = self.getSymbolRecord()
|
|
192
322
|
self.checkObjectType(record, MQTTClient)
|
|
193
323
|
command['to'] = record['name']
|
|
324
|
+
token = self.peek()
|
|
325
|
+
if token == 'with':
|
|
326
|
+
self.nextToken()
|
|
327
|
+
while True:
|
|
328
|
+
token = self.nextToken()
|
|
329
|
+
if token == 'qos':
|
|
330
|
+
command['qos'] = self.nextValue()
|
|
331
|
+
if self.peek() == 'and':
|
|
332
|
+
self.nextToken()
|
|
333
|
+
else:
|
|
334
|
+
break
|
|
194
335
|
self.add(command)
|
|
195
336
|
return True
|
|
196
337
|
return False
|
|
@@ -200,7 +341,38 @@ class MQTT(Handler):
|
|
|
200
341
|
raise RuntimeError(self.program, 'No MQTT client defined')
|
|
201
342
|
topic = self.getObject(self.getVariable(command['to']))
|
|
202
343
|
message = self.textify(command['message'])
|
|
203
|
-
|
|
344
|
+
|
|
345
|
+
# Validate that outgoing message is valid JSON with required 'sender' and 'action' fields
|
|
346
|
+
try:
|
|
347
|
+
payload_dict = json.loads(message)
|
|
348
|
+
if not isinstance(payload_dict, dict):
|
|
349
|
+
raise RuntimeError(self.program, f'MQTT message must be a JSON object, got {type(payload_dict).__name__}')
|
|
350
|
+
|
|
351
|
+
# sender can be a string (topic name) or a dict with 'name' and 'qos' keys
|
|
352
|
+
if 'sender' not in payload_dict:
|
|
353
|
+
raise RuntimeError(self.program, 'MQTT message must contain "sender" field')
|
|
354
|
+
sender_val = payload_dict.get('sender')
|
|
355
|
+
if isinstance(sender_val, str):
|
|
356
|
+
pass # sender is a string (topic name), valid
|
|
357
|
+
elif isinstance(sender_val, dict):
|
|
358
|
+
if 'name' not in sender_val or not isinstance(sender_val.get('name'), str):
|
|
359
|
+
raise RuntimeError(self.program, 'MQTT message "sender" dict must contain "name" field as string')
|
|
360
|
+
else:
|
|
361
|
+
raise RuntimeError(self.program, f'MQTT message "sender" must be a string or dict, got {type(sender_val).__name__}')
|
|
362
|
+
|
|
363
|
+
# action must be a string
|
|
364
|
+
if 'action' not in payload_dict or not isinstance(payload_dict.get('action'), str):
|
|
365
|
+
raise RuntimeError(self.program, 'MQTT message must contain "action" field as string')
|
|
366
|
+
except json.JSONDecodeError as e:
|
|
367
|
+
raise RuntimeError(self.program, f'MQTT message must be valid JSON: {e}')
|
|
368
|
+
|
|
369
|
+
if 'qos' in command:
|
|
370
|
+
qos = int(self.textify(command['qos']))
|
|
371
|
+
else:
|
|
372
|
+
qos = topic.qos if hasattr(topic, 'qos') else 1
|
|
373
|
+
self.program.mqttClient.sendMessage(topic.getName(), message, qos, chunk_size=1024)
|
|
374
|
+
if self.program.mqttClient.timeout:
|
|
375
|
+
return 0
|
|
204
376
|
return self.nextPC()
|
|
205
377
|
|
|
206
378
|
# Declare a topic variable
|
|
@@ -214,15 +386,22 @@ class MQTT(Handler):
|
|
|
214
386
|
#############################################################################
|
|
215
387
|
# Compile a value in this domain
|
|
216
388
|
def compileValue(self):
|
|
217
|
-
token = self.
|
|
218
|
-
if token == '
|
|
219
|
-
value = ECValue(domain=self.getName())
|
|
389
|
+
token = self.getToken()
|
|
390
|
+
if token == 'the':
|
|
220
391
|
token = self.nextToken()
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
392
|
+
if self.isSymbol():
|
|
393
|
+
record = self.getSymbolRecord()
|
|
394
|
+
object = self.getObject(record)
|
|
395
|
+
if isinstance(object, ECTopic):
|
|
396
|
+
return ECValue(domain=self.getName(), type='topic', content=record['name'])
|
|
397
|
+
else: return None
|
|
224
398
|
else:
|
|
225
|
-
|
|
399
|
+
if token == 'mqtt':
|
|
400
|
+
token = self.nextToken()
|
|
401
|
+
if token == 'message':
|
|
402
|
+
return ECValue(domain=self.getName(), type='mqtt', content=token)
|
|
403
|
+
# else:
|
|
404
|
+
# return self.getValue()
|
|
226
405
|
return None
|
|
227
406
|
|
|
228
407
|
#############################################################################
|
|
@@ -234,10 +413,17 @@ class MQTT(Handler):
|
|
|
234
413
|
# Value handlers
|
|
235
414
|
|
|
236
415
|
def v_message(self, v):
|
|
237
|
-
return self.program.mqttClient.
|
|
416
|
+
return self.program.mqttClient.message
|
|
417
|
+
|
|
418
|
+
def v_mqtt(self, v):
|
|
419
|
+
content = v.getContent()
|
|
420
|
+
if content == 'message':
|
|
421
|
+
return self.program.mqttClient.message
|
|
422
|
+
return None
|
|
238
423
|
|
|
239
424
|
def v_topic(self, v):
|
|
240
|
-
|
|
425
|
+
topic = self.getObject(self.getVariable(self.textify(v.getContent())))
|
|
426
|
+
return f'{{"name": "{topic.getName()}", "qos": {topic.getQoS()}}}'
|
|
241
427
|
|
|
242
428
|
#############################################################################
|
|
243
429
|
# Compile a condition
|
easycoder/ec_program.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import time, sys, json
|
|
1
|
+
import time, sys, json, traceback
|
|
2
2
|
from copy import deepcopy
|
|
3
3
|
from collections import deque
|
|
4
|
+
|
|
4
5
|
from .ec_classes import (
|
|
5
6
|
Script,
|
|
6
7
|
Token,
|
|
@@ -60,6 +61,7 @@ class Program:
|
|
|
60
61
|
self.value = self.compiler.value
|
|
61
62
|
self.condition = self.compiler.condition
|
|
62
63
|
self.graphics = None
|
|
64
|
+
self.mqtt = None
|
|
63
65
|
self.psutil = None
|
|
64
66
|
self.useClass(Core)
|
|
65
67
|
self.ticker = 0
|
|
@@ -93,7 +95,7 @@ class Program:
|
|
|
93
95
|
f'{round((finishCompile - startCompile) * 1000)} ms')
|
|
94
96
|
for name in self.symbols.keys():
|
|
95
97
|
record = self.code[self.symbols[name]]
|
|
96
|
-
if name[-1] != ':' and not 'used'
|
|
98
|
+
if name[-1] != ':' and not record['used']:
|
|
97
99
|
print(f'Variable "{name}" not used')
|
|
98
100
|
else:
|
|
99
101
|
print(f'Run {self.name}')
|
|
@@ -119,12 +121,12 @@ class Program:
|
|
|
119
121
|
self.useClass(Graphics)
|
|
120
122
|
return True
|
|
121
123
|
|
|
122
|
-
# Use the
|
|
124
|
+
# Use the MQTT module
|
|
123
125
|
def useMQTT(self):
|
|
124
|
-
if self.
|
|
125
|
-
print('Loading
|
|
126
|
+
if self.mqtt == None:
|
|
127
|
+
print('Loading MQTT module')
|
|
126
128
|
from .ec_mqtt import MQTT
|
|
127
|
-
self.
|
|
129
|
+
self.mqtt = MQTT
|
|
128
130
|
self.useClass(MQTT)
|
|
129
131
|
return True
|
|
130
132
|
|
|
@@ -252,7 +254,7 @@ class Program:
|
|
|
252
254
|
# Test if an item is a string or a number
|
|
253
255
|
def getItemType(self, value):
|
|
254
256
|
return int if isinstance(value, int) else str
|
|
255
|
-
|
|
257
|
+
|
|
256
258
|
# Get the value of an item that may be an ECValue or a raw value. Return as an ECValue
|
|
257
259
|
def getValueOf(self, item):
|
|
258
260
|
value = ECValue()
|
|
@@ -270,6 +272,15 @@ class Program:
|
|
|
270
272
|
elif varType == 'dict': value.setValue(type=dict, content=item)
|
|
271
273
|
else: value.setValue(type=None, content=None)
|
|
272
274
|
return value
|
|
275
|
+
|
|
276
|
+
# Get the value of an item from its domain handler
|
|
277
|
+
def textifyInDomain(self, value):
|
|
278
|
+
domainName = value.getDomain() # type: ignore
|
|
279
|
+
if domainName == None: domainName = 'core'
|
|
280
|
+
domain = self.domainIndex[domainName]
|
|
281
|
+
handler = domain.valueHandler(value.getType()) # type: ignore
|
|
282
|
+
result = handler(value) if handler else None
|
|
283
|
+
return result
|
|
273
284
|
|
|
274
285
|
# Runtime function to evaluate an ECObject or ECValue. Returns another ECValue
|
|
275
286
|
# This function may be called recursively by value handlers.
|
|
@@ -298,7 +309,7 @@ class Program:
|
|
|
298
309
|
|
|
299
310
|
elif valType == 'symbol': # type: ignore
|
|
300
311
|
# If it's a symbol, get its value
|
|
301
|
-
record = self.getVariable(value.
|
|
312
|
+
record = self.getVariable(value.getName()) # type: ignore
|
|
302
313
|
if not 'object' in record: return None # type: ignore
|
|
303
314
|
variable = self.getObject(record) # type: ignore
|
|
304
315
|
result = variable.getValue() # type: ignore
|
|
@@ -325,12 +336,7 @@ class Program:
|
|
|
325
336
|
result.setValue(type=str, content=content)
|
|
326
337
|
|
|
327
338
|
else:
|
|
328
|
-
|
|
329
|
-
domainName = value.getDomain() # type: ignore
|
|
330
|
-
if domainName == None: domainName = 'core'
|
|
331
|
-
domain = self.domainIndex[domainName]
|
|
332
|
-
handler = domain.valueHandler(value.getType()) # type: ignore
|
|
333
|
-
if handler: result = handler(value)
|
|
339
|
+
result = self.textifyInDomain(value)
|
|
334
340
|
|
|
335
341
|
return result
|
|
336
342
|
|
|
@@ -343,7 +349,7 @@ class Program:
|
|
|
343
349
|
if isinstance(value, dict):
|
|
344
350
|
value = value['object']
|
|
345
351
|
if isinstance(value, ECObject):
|
|
346
|
-
value = value.
|
|
352
|
+
value = value.textify() # type: ignore
|
|
347
353
|
if isinstance(value, ECValue): # type: ignore
|
|
348
354
|
v = self.evaluate(value) # type: ignore
|
|
349
355
|
else:
|
|
@@ -353,6 +359,8 @@ class Program:
|
|
|
353
359
|
if v.getType() == 'object':
|
|
354
360
|
return value.getContent() # type: ignore
|
|
355
361
|
return v.getContent()
|
|
362
|
+
elif isinstance(v, ECObject):
|
|
363
|
+
return v.textify() # type: ignore
|
|
356
364
|
if isinstance(v, (dict, list)):
|
|
357
365
|
return json.dumps(v)
|
|
358
366
|
return v
|
|
@@ -372,7 +380,6 @@ class Program:
|
|
|
372
380
|
value = object.getValue() # type: ignore
|
|
373
381
|
if value is None:
|
|
374
382
|
raise NoValueRuntimeError(self, f'Symbol "{record["name"]}" has no value')
|
|
375
|
-
# copy = deepcopy(value)
|
|
376
383
|
copy = ECValue(domain=value.getDomain(),type=value.getType(),content=deepcopy(value.getContent()))
|
|
377
384
|
return copy
|
|
378
385
|
|
|
@@ -487,7 +494,8 @@ class Program:
|
|
|
487
494
|
pass # Place a breakpoint here for a debugger to catch
|
|
488
495
|
self.pc = handler(command)
|
|
489
496
|
except Exception as e:
|
|
490
|
-
|
|
497
|
+
tb = traceback.format_exc()
|
|
498
|
+
raise RuntimeError(self, f'Error during execution of {domainName}:{keyword}: {str(e)}\n\nTraceback:\n{tb}')
|
|
491
499
|
# Deal with 'exit'
|
|
492
500
|
if self.pc == -1:
|
|
493
501
|
queue = deque()
|
easycoder/ec_value.py
CHANGED
|
@@ -22,11 +22,11 @@ class Value:
|
|
|
22
22
|
|
|
23
23
|
value = ECValue()
|
|
24
24
|
|
|
25
|
-
if token == 'true':
|
|
25
|
+
if token.lower() == 'true':
|
|
26
26
|
value.setValue(bool, True)
|
|
27
27
|
return value
|
|
28
28
|
|
|
29
|
-
if token == 'false':
|
|
29
|
+
if token.lower() == 'false':
|
|
30
30
|
value.setValue(bool, False)
|
|
31
31
|
return value
|
|
32
32
|
|
|
@@ -96,8 +96,9 @@ class Value:
|
|
|
96
96
|
self.compiler.warning(f'ec_value.compileValue: Cannot get the value of "{token}"')
|
|
97
97
|
return None
|
|
98
98
|
if item.getType() == 'symbol':
|
|
99
|
-
object = self.compiler.getSymbolRecord(item.
|
|
99
|
+
object = self.compiler.getSymbolRecord(item.getName())['object']
|
|
100
100
|
if not object.hasRuntimeValue(): return None
|
|
101
|
+
item.setContent(object.name)
|
|
101
102
|
|
|
102
103
|
if self.peek() == 'cat':
|
|
103
104
|
self.nextToken() # consume 'cat'
|
easycoder/pre/ec_core.py
CHANGED
|
@@ -69,7 +69,7 @@ class Core(Handler):
|
|
|
69
69
|
if not isinstance(self.getObject(record), ECVariable): return False
|
|
70
70
|
# If 'giving' comes next, this variable is the second value
|
|
71
71
|
if self.peek() == 'giving':
|
|
72
|
-
v2 = ECValue(type='symbol',
|
|
72
|
+
v2 = ECValue(type='symbol', name=record['name'])
|
|
73
73
|
command['value2'] = v2
|
|
74
74
|
self.nextToken()
|
|
75
75
|
# Now get the target variable
|
|
@@ -1655,8 +1655,7 @@ class Core(Handler):
|
|
|
1655
1655
|
self.checkObjectType(record, ECObject)
|
|
1656
1656
|
# If 'giving' comes next, this variable is the second value
|
|
1657
1657
|
if self.peek() == 'giving':
|
|
1658
|
-
v2 = ECValue(type='symbol')
|
|
1659
|
-
v2.setContent(record['name'])
|
|
1658
|
+
v2 = ECValue(type='symbol', name=record['name'])
|
|
1660
1659
|
command['value2'] = v2
|
|
1661
1660
|
self.nextToken()
|
|
1662
1661
|
# Now get the target variable
|
|
@@ -1912,7 +1911,7 @@ class Core(Handler):
|
|
|
1912
1911
|
value = ECValue()
|
|
1913
1912
|
token = self.getToken()
|
|
1914
1913
|
if self.isSymbol():
|
|
1915
|
-
value.setValue(type='symbol',
|
|
1914
|
+
value.setValue(type='symbol', name=token)
|
|
1916
1915
|
return value
|
|
1917
1916
|
|
|
1918
1917
|
value.setType(token)
|
|
@@ -1946,13 +1945,13 @@ class Core(Handler):
|
|
|
1946
1945
|
value.format = None
|
|
1947
1946
|
return value
|
|
1948
1947
|
|
|
1949
|
-
if token == '
|
|
1948
|
+
if token == 'item':
|
|
1950
1949
|
value.index = self.nextValue()
|
|
1951
1950
|
if self.nextToken() == 'of':
|
|
1952
1951
|
if self.nextIsSymbol():
|
|
1953
1952
|
record = self.getSymbolRecord()
|
|
1954
1953
|
self.checkObjectType(record['object'], ECList)
|
|
1955
|
-
value.target = ECValue(type='symbol',
|
|
1954
|
+
value.target = ECValue(type='symbol', name=record['name'])
|
|
1956
1955
|
return value
|
|
1957
1956
|
return None
|
|
1958
1957
|
|
|
@@ -2214,10 +2213,10 @@ class Core(Handler):
|
|
|
2214
2213
|
value = v
|
|
2215
2214
|
return value
|
|
2216
2215
|
|
|
2217
|
-
def
|
|
2216
|
+
def v_item(self, v):
|
|
2218
2217
|
index = self.textify(v.index)
|
|
2219
2218
|
targetName = v.target
|
|
2220
|
-
target = self.getVariable(targetName.
|
|
2219
|
+
target = self.getVariable(targetName.getName())
|
|
2221
2220
|
variable = self.getObject(target)
|
|
2222
2221
|
self.checkObjectType(variable, ECList)
|
|
2223
2222
|
content = variable.getContent()
|
|
@@ -2656,7 +2655,7 @@ class Core(Handler):
|
|
|
2656
2655
|
|
|
2657
2656
|
def c_empty(self, condition):
|
|
2658
2657
|
if condition.value1.getType() == 'symbol':
|
|
2659
|
-
record = self.getVariable(condition.value1.
|
|
2658
|
+
record = self.getVariable(condition.value1.name)
|
|
2660
2659
|
variable = self.getObject(record)
|
|
2661
2660
|
if isinstance(variable, (ECList, ECDictionary)):
|
|
2662
2661
|
comparison = variable.isEmpty()
|
easycoder/pre/ec_program.py
CHANGED
|
@@ -289,7 +289,7 @@ class Program:
|
|
|
289
289
|
|
|
290
290
|
elif valType == 'symbol': # type: ignore
|
|
291
291
|
# If it's a symbol, get its value
|
|
292
|
-
record = self.getVariable(value.
|
|
292
|
+
record = self.getVariable(value.getName()) # type: ignore
|
|
293
293
|
if not 'object' in record: return None # type: ignore
|
|
294
294
|
variable = self.getObject(record) # type: ignore
|
|
295
295
|
result = variable.getValue() # type: ignore
|
easycoder/pre/ec_value.py
CHANGED
|
@@ -96,7 +96,7 @@ class Value:
|
|
|
96
96
|
self.compiler.warning(f'ec_value.compileValue: Cannot get the value of "{token}"')
|
|
97
97
|
return None
|
|
98
98
|
if item.getType() == 'symbol':
|
|
99
|
-
object = self.compiler.getSymbolRecord(item.
|
|
99
|
+
object = self.compiler.getSymbolRecord(item.getName())['object']
|
|
100
100
|
if not object.hasRuntimeValue(): return None
|
|
101
101
|
|
|
102
102
|
if self.peek() == 'cat':
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: easycoder
|
|
3
|
-
Version:
|
|
3
|
+
Version: 260118.3
|
|
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>
|
|
@@ -12,6 +12,7 @@ Requires-Dist: requests
|
|
|
12
12
|
Requires-Dist: psutil
|
|
13
13
|
Requires-Dist: paramiko
|
|
14
14
|
Requires-Dist: pyside6
|
|
15
|
+
Requires-Dist: paho-mqtt
|
|
15
16
|
Project-URL: Home, https://github.com/easycoder/easycoder-py
|
|
16
17
|
|
|
17
18
|
# Introduction
|