easycoder 251105.1__py2.py3-none-any.whl → 260111.1__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.
Files changed (51) hide show
  1. easycoder/__init__.py +6 -3
  2. easycoder/debugger/__init__.py +5 -0
  3. easycoder/debugger/ec_dbg_value_display copy.py +195 -0
  4. easycoder/debugger/ec_dbg_value_display.py +24 -0
  5. easycoder/debugger/ec_dbg_watch_list copy.py +219 -0
  6. easycoder/debugger/ec_dbg_watchlist.py +293 -0
  7. easycoder/debugger/ec_debug.py +1025 -0
  8. easycoder/ec_classes.py +487 -11
  9. easycoder/ec_compiler.py +81 -44
  10. easycoder/ec_condition.py +1 -1
  11. easycoder/ec_core.py +1042 -1081
  12. easycoder/ec_gclasses.py +236 -0
  13. easycoder/ec_graphics.py +1683 -0
  14. easycoder/ec_handler.py +18 -14
  15. easycoder/ec_mqtt.py +248 -0
  16. easycoder/ec_program.py +297 -168
  17. easycoder/ec_psutil.py +48 -0
  18. easycoder/ec_value.py +65 -47
  19. easycoder/pre/README.md +3 -0
  20. easycoder/pre/__init__.py +17 -0
  21. easycoder/pre/debugger/__init__.py +5 -0
  22. easycoder/pre/debugger/ec_dbg_value_display copy.py +195 -0
  23. easycoder/pre/debugger/ec_dbg_value_display.py +24 -0
  24. easycoder/pre/debugger/ec_dbg_watch_list copy.py +219 -0
  25. easycoder/pre/debugger/ec_dbg_watchlist.py +293 -0
  26. easycoder/{ec_debug.py → pre/debugger/ec_debug.py} +418 -185
  27. easycoder/pre/ec_border.py +67 -0
  28. easycoder/pre/ec_classes.py +470 -0
  29. easycoder/pre/ec_compiler.py +291 -0
  30. easycoder/pre/ec_condition.py +27 -0
  31. easycoder/pre/ec_core.py +2772 -0
  32. easycoder/pre/ec_gclasses.py +230 -0
  33. easycoder/{ec_pyside.py → pre/ec_graphics.py} +583 -433
  34. easycoder/pre/ec_handler.py +79 -0
  35. easycoder/pre/ec_keyboard.py +439 -0
  36. easycoder/pre/ec_program.py +557 -0
  37. easycoder/pre/ec_psutil.py +48 -0
  38. easycoder/pre/ec_timestamp.py +11 -0
  39. easycoder/pre/ec_value.py +124 -0
  40. easycoder/pre/icons/close.png +0 -0
  41. easycoder/pre/icons/exit.png +0 -0
  42. easycoder/pre/icons/run.png +0 -0
  43. easycoder/pre/icons/step.png +0 -0
  44. easycoder/pre/icons/stop.png +0 -0
  45. easycoder/pre/icons/tick.png +0 -0
  46. {easycoder-251105.1.dist-info → easycoder-260111.1.dist-info}/METADATA +11 -1
  47. easycoder-260111.1.dist-info/RECORD +59 -0
  48. easycoder-251105.1.dist-info/RECORD +0 -24
  49. {easycoder-251105.1.dist-info → easycoder-260111.1.dist-info}/WHEEL +0 -0
  50. {easycoder-251105.1.dist-info → easycoder-260111.1.dist-info}/entry_points.txt +0 -0
  51. {easycoder-251105.1.dist-info → easycoder-260111.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,2772 @@
1
+ import json, math, hashlib, threading, os, subprocess, time
2
+ import base64, binascii, random, requests, paramiko
3
+ from copy import deepcopy
4
+ from datetime import datetime
5
+ from .ec_classes import (
6
+ FatalError,
7
+ RuntimeWarning,
8
+ RuntimeError,
9
+ RuntimeAssertionError,
10
+ NoValueError,
11
+ NoValueRuntimeError,
12
+ ECObject,
13
+ ECVariable,
14
+ ECDictionary,
15
+ ECList,
16
+ ECFile,
17
+ ECStack,
18
+ ECSSH,
19
+ ECValue
20
+ )
21
+
22
+ from .ec_handler import Handler
23
+
24
+ class Core(Handler):
25
+
26
+ def __init__(self, compiler):
27
+ super().__init__(compiler)
28
+ self.encoding = 'utf-8'
29
+
30
+ def getName(self):
31
+ return 'core'
32
+
33
+ def noSymbolWarning(self):
34
+ self.warning(f'Symbol "{self.getToken()}" not found')
35
+
36
+ def processOr(self, command, orHere):
37
+ self.add(command)
38
+ if self.peek() == 'or':
39
+ self.nextToken()
40
+ self.nextToken()
41
+ # Add a 'goto' to skip the 'or'
42
+ cmd = {}
43
+ cmd['lino'] = command['lino']
44
+ cmd['domain'] = 'core'
45
+ cmd['keyword'] = 'gotoPC'
46
+ cmd['goto'] = 0
47
+ cmd['debug'] = False
48
+ skip = self.getCodeSize()
49
+ self.add(cmd)
50
+ # Process the 'or'
51
+ self.getCommandAt(orHere)['or'] = self.getCodeSize()
52
+ self.compileOne()
53
+ # Fixup the skip
54
+ self.getCommandAt(skip)['goto'] = self.getCodeSize()
55
+
56
+ #############################################################################
57
+ # Keyword handlers
58
+
59
+ # Arithmetic add
60
+ # add {value} to {variable}
61
+ # add {value1} to {value2} giving {variable}
62
+ def k_add(self, command):
63
+ # Get the (first) value
64
+ command['value1'] = self.nextValue()
65
+ if command['value1'] == None: return False
66
+ self.skip('to')
67
+ if self.nextIsSymbol():
68
+ record = self.getSymbolRecord()
69
+ if not isinstance(self.getObject(record), ECVariable): return False
70
+ # If 'giving' comes next, this variable is the second value
71
+ if self.peek() == 'giving':
72
+ v2 = ECValue(type='symbol', content=record['name'])
73
+ command['value2'] = v2
74
+ self.nextToken()
75
+ # Now get the target variable
76
+ if self.nextIsSymbol():
77
+ record = self.getSymbolRecord()
78
+ self.checkObjectType(record, ECVariable)
79
+ command['target'] = record['name']
80
+ self.add(command)
81
+ return True
82
+ else:
83
+ # Here the variable is the target
84
+ command['target'] = record['name']
85
+ if self.getObject(record).isMutable():
86
+ self.add(command)
87
+ return True
88
+ else:
89
+ # Here we have 2 values so 'giving' must come next
90
+ command['value2'] = self.getValue()
91
+ if self.nextToken() == 'giving':
92
+ if self.nextIsSymbol():
93
+ record = self.getSymbolRecord()
94
+ self.checkObjectType(record, ECVariable)
95
+ command['target'] = record['name']
96
+ self.add(command)
97
+ return True
98
+ # raise FatalError(self.compiler, 'Cannot add values: target variable expected')
99
+ return False
100
+
101
+ def r_add(self, command):
102
+ value1 = self.textify(command['value1'])
103
+ value2 = self.textify(command['value2']) if 'value2' in command else None
104
+ target = self.getVariable(command['target'])
105
+ # Check that the target variable is mutable. If not, it's not an arithmetic add
106
+ # If value2 exists, we are adding two values and storing the result in target
107
+ if value2 != None:
108
+ # add X to Y giving Z
109
+ targetValue = ECValue(type=int, content=int(value1) + int(value2))
110
+ else:
111
+ # add X to Y
112
+ targetValue = self.getSymbolValue(target)
113
+ targetValue.setContent(int(targetValue.getContent()) + int(value1))
114
+ self.putSymbolValue(target, targetValue)
115
+ return self.nextPC()
116
+
117
+ # Append a value to an array
118
+ # append {value} to {array}
119
+ def k_append(self, command):
120
+ command['value'] = self.nextValue()
121
+ if self.nextIs('to'):
122
+ if self.nextIsSymbol():
123
+ record = self.getSymbolRecord()
124
+ self.program.checkObjectType(self.getObject(record), ECList)
125
+ command['target'] = record['name']
126
+ self.add(command)
127
+ return True
128
+ return False
129
+
130
+ def r_append(self, command):
131
+ value = self.textify(command['value'])
132
+ target = self.getVariable(command['target'])
133
+ content = target['object'].getContent()
134
+ items = [] if content == None else content
135
+ if not type(items) == list:
136
+ RuntimeError(self.program, f'{command["target"]} is not a JSON list')
137
+ items.append(value)
138
+ self.putSymbolValue(target, items)
139
+ return self.nextPC()
140
+
141
+ #assert {condition} [with {message}]
142
+ def k_assert(self, command):
143
+ command['test'] = self.nextCondition()
144
+ if self.peek() == 'with':
145
+ self.nextToken()
146
+ command['with'] = self.nextValue()
147
+ else:
148
+ command['with'] = None
149
+ self.add(command)
150
+ return True
151
+
152
+ def r_assert(self, command):
153
+ test = self.program.condition.testCondition(command['test'])
154
+ if test:
155
+ return self.nextPC()
156
+ RuntimeAssertionError(self.program, self.textify(command['with']))
157
+
158
+ # Begin a block
159
+ def k_begin(self, command):
160
+ if self.nextToken() == 'end':
161
+ cmd = {}
162
+ cmd['domain'] = 'core'
163
+ cmd['keyword'] = 'end'
164
+ cmd['debug'] = True
165
+ cmd['lino'] = command['lino']
166
+ self.add(cmd)
167
+ return self.nextPC()
168
+ else:
169
+ return self.compileFromHere(['end'])
170
+
171
+ # clear {variable}
172
+ def k_clear(self, command):
173
+ token = self.nextToken()
174
+ if token == 'breakpoint':
175
+ command['breakpoint'] = True
176
+ self.add(command)
177
+ return True
178
+ elif self.isSymbol():
179
+ record = self.getSymbolRecord()
180
+ command['target'] = record['name']
181
+ object = self.getObject(record)
182
+ if isinstance(object, ECSSH):
183
+ self.add(command)
184
+ return True
185
+ if isinstance(object, ECVariable):
186
+ self.add(command)
187
+ return True
188
+ return False
189
+
190
+ def r_clear(self, command):
191
+ if 'breakpoint' in command:
192
+ self.program.breakpoint = False
193
+ else:
194
+ target = self.getVariable(command['target'])
195
+ if target['keyword'] == 'ssh':
196
+ target['ssh'] = None
197
+ else:
198
+ self.putSymbolValue(target, ECValue(type=bool, content=False))
199
+ return self.nextPC()
200
+
201
+ # Close a file
202
+ # close {file}
203
+ def k_close(self, command):
204
+ if self.nextIsSymbol():
205
+ fileRecord = self.getSymbolRecord()
206
+ if fileRecord['keyword'] == 'file':
207
+ command['file'] = fileRecord['name']
208
+ self.add(command)
209
+ return True
210
+ return False
211
+
212
+ def r_close(self, command):
213
+ fileRecord = self.getVariable(command['file'])
214
+ fileRecord['file'].close()
215
+ return self.nextPC()
216
+
217
+ # Create directory
218
+ # create directory {name}
219
+ def k_create(self, command):
220
+ if self.nextIs('directory'):
221
+ command['item'] = 'directory'
222
+ command['path'] = self.nextValue()
223
+ self.add(command)
224
+ return True
225
+ return False
226
+
227
+ def r_create(self, command):
228
+ if command['item'] == 'directory':
229
+ path = self.textify(command['path'])
230
+ if not os.path.exists(path):
231
+ os.makedirs(path)
232
+ return self.nextPC()
233
+
234
+ # Debug the script
235
+ def k_debug(self, command):
236
+ token = self.peek()
237
+ if token == 'compile':
238
+ self.compiler.debugCompile = True
239
+ self.nextToken()
240
+ return True
241
+ elif token in ['step', 'stop', 'skip', 'breakpoint', 'program', 'custom']:
242
+ command['mode'] = token
243
+ self.nextToken()
244
+ elif token == 'stack':
245
+ command['mode'] = self.nextToken()
246
+ if (self.nextIsSymbol()):
247
+ command['stack'] = self.getToken()
248
+ if self.peek() == 'as':
249
+ self.nextToken()
250
+ command['as'] = self.nextValue()
251
+ else:
252
+ command['as'] = 'Stack'
253
+ else:
254
+ return False
255
+ else:
256
+ command['mode'] = None
257
+ self.add(command)
258
+ return True
259
+
260
+ def r_debug(self, command):
261
+ if command['mode'] == 'compile':
262
+ self.program.debugStep = True
263
+ elif command['mode'] == 'step':
264
+ self.program.debugStep = True
265
+ elif command['mode'] == 'stop':
266
+ self.program.debugStep = False
267
+ elif command['mode'] == 'skip':
268
+ self.program.debugSkip = True
269
+ elif command['mode'] == 'breakpoint':
270
+ self.program.breakpoint = True
271
+ elif command['mode'] == 'program':
272
+ for item in self.code:
273
+ print(json.dumps(item, indent = 2))
274
+ elif command['mode'] == 'stack':
275
+ stackRecord = self.getVariable(command['stack'])
276
+ value = self.getSymbolValue(stackRecord)
277
+ print(f'{self.textify(command["as"])}:',json.dumps(self.getSymbolValue(stackRecord), indent = 2))
278
+ elif command['mode'] == 'custom':
279
+ # Custom debugging code goes in here
280
+ record = self.getVariable('Script')
281
+ print('(Debug) Script:',record)
282
+ value = self.textify(record)
283
+ print('(Debug) Value:',value)
284
+ pass
285
+ return self.nextPC()
286
+
287
+ # Decrement a variable
288
+ # decrement {variable}
289
+ def k_decrement(self, command):
290
+ if self.nextIsSymbol():
291
+ record = self.getSymbolRecord()
292
+ self.checkObjectType(self.getObject(record), ECVariable)
293
+ command['target'] = record['name']
294
+ self.add(command)
295
+ return True
296
+ return False
297
+
298
+ def r_decrement(self, command):
299
+ return self.incdec(command, '-')
300
+
301
+ # Delete a file or a property
302
+ # delete file {filename}
303
+ # delete entry/property/element {name/number} of {variable}
304
+ def k_delete(self, command):
305
+ token = self.nextToken( )
306
+ command['type'] = token
307
+ if token == 'file':
308
+ command['filename'] = self.nextValue()
309
+ self.add(command)
310
+ return True
311
+ elif token in ('entry', 'property', 'element'):
312
+ command['key'] = self.nextValue()
313
+ self.skip('of')
314
+ if self.nextIsSymbol():
315
+ record = self.getSymbolRecord()
316
+ command['variable'] = record['name']
317
+ if token == 'entry':
318
+ self.checkObjectType(self.getObject(record), ECDictionary)
319
+ self.add(command)
320
+ return True
321
+ self.warning(f'Core.delete: variable expected; got {self.getToken()}')
322
+ else:
323
+ self.warning(f'Core.delete: "file", " entry", "property" or "element" expected; got {token}')
324
+ return False
325
+
326
+ def r_delete(self, command):
327
+ type = command['type']
328
+ if type == 'file':
329
+ filename = self.textify(command['filename'])
330
+ if filename != None:
331
+ if os.path.isfile(filename): os.remove(filename)
332
+ elif type == 'entry':
333
+ key = self.textify(command['key'])
334
+ record = self.getVariable(command['variable'])
335
+ self.getObject(record).deleteEntry(key)
336
+ elif type == 'property':
337
+ raise NotImplementedError('Core.delete property not implemented yet')
338
+ key = self.textify(command['key'])
339
+ record = self.getVariable(command['var'])
340
+ value = self.getSymbolValue(record)
341
+ content = value.getContent()
342
+ content.pop(key, None)
343
+ value.setContent(content)
344
+ self.putSymbolValue(record, value)
345
+ elif type == 'element':
346
+ key = self.textify(command['key'])
347
+ record = self.getVariable(command['variable'])
348
+ value = self.getSymbolValue(record)
349
+ content = value.getContent()
350
+ if isinstance(key, int):
351
+ if key >= 0 and key < len(content): del(content[key])
352
+ elif isinstance(key, str):
353
+ if key in content: content.remove(key)
354
+ else: RuntimeError(self.program, f'Index {key} out of range')
355
+ value.setContent(content)
356
+ self.putSymbolValue(record, value)
357
+ return self.nextPC()
358
+
359
+ # Declare a dictionary variable
360
+ def k_dictionary(self, command):
361
+ self.compiler.addValueType()
362
+ return self.compileVariable(command, 'ECDictionary')
363
+
364
+ def r_dictionary(self, command):
365
+ return self.nextPC()
366
+
367
+
368
+ # Arithmetic divide
369
+ # divide {variable} by {value}
370
+ # divide {value1} by {value2} giving {variable}
371
+ def k_divide(self, command):
372
+ # Get the (first) item. If it's a symbol, it may be the target variable
373
+ if self.nextIsSymbol():
374
+ record = self.getSymbolRecord()
375
+ self.checkObjectType(record, ECVariable)
376
+ # Hold onto the variable and its value
377
+ variable1 = record['name']
378
+ value1 = self.getValue()
379
+ else:
380
+ # Here we have a value
381
+ value1 = self.getValue()
382
+ variable1 = None
383
+ self.skip('by')
384
+ command['value2'] = self.nextValue()
385
+ # if 'giving' comes next, the target is the next value
386
+ if self.peek() == 'giving':
387
+ self.nextToken()
388
+ if self.nextIsSymbol():
389
+ record = self.getSymbolRecord()
390
+ self.checkObjectType(record, ECVariable)
391
+ command['target'] = record['name']
392
+ command['value1'] = value1
393
+ self.add(command)
394
+ return True
395
+ else:
396
+ # Here the first variable is the target
397
+ if variable1 != None:
398
+ command['target'] = variable1
399
+ self.add(command)
400
+ return True
401
+ return False
402
+
403
+ def r_divide(self, command):
404
+ value1 = self.textify(command['value1']) if 'value1' in command else None
405
+ value2 = self.textify(command['value2'])
406
+ target = self.getVariable(command['target'])
407
+ # Check that the target variable can hold a value
408
+ self.checkObjectType(target, ECVariable)
409
+ # If value1 exists, we are adding two values and storing the result in target
410
+ if value1 != None:
411
+ # divide X by Y giving Z
412
+ targetValue = ECValue(type=int, content=int(value1) // int(value2))
413
+ else:
414
+ # divide X by Y
415
+ targetValue = self.getSymbolValue(target)
416
+ targetValue.setContent(int(targetValue.getContent()) // int(value2))
417
+ self.putSymbolValue(target, targetValue)
418
+ return self.nextPC()
419
+
420
+ # download [binary] {url} to {path}
421
+ def k_download(self, command):
422
+ if self.nextIs('binary'):
423
+ command['binary'] = True
424
+ self.nextToken()
425
+ else: command['binary'] = False
426
+ command['url'] = self.getValue()
427
+ self.skip('to')
428
+ command['path'] = self.nextValue()
429
+ self.add(command)
430
+ return True
431
+
432
+ def r_download(self, command):
433
+ binary = command['binary']
434
+ url = self.textify(command['url'])
435
+ path = self.textify(command['path'])
436
+ mode = 'wb' if binary else 'w'
437
+ response = requests.get(url, stream=True)
438
+ with open(path, mode) as f:
439
+ for chunk in response.iter_content(chunk_size=8192):
440
+ if chunk: f.write(chunk)
441
+ return self.nextPC()
442
+
443
+ # Match a begin
444
+ def k_end(self, command):
445
+ self.add(command)
446
+ return True
447
+
448
+ def r_end(self, command):
449
+ return self.nextPC()
450
+
451
+ # Exit the script
452
+ def k_exit(self, command):
453
+ self.add(command)
454
+ return True
455
+
456
+ def r_exit(self, command):
457
+ if self.program.parent == None and self.program.graphics != None:
458
+ self.program.graphics.force_exit(None)
459
+ return -1
460
+
461
+ # Declare a file variable
462
+ def k_file(self, command):
463
+ self.compiler.addValueType()
464
+ return self.compileVariable(command, 'ECFile')
465
+
466
+ def r_file(self, command):
467
+ return self.nextPC()
468
+
469
+ # Fork to a label
470
+ # fork [to] {label}
471
+ def k_fork(self, command):
472
+ self.skip('to') # Optional 'to' (core-reserved keyword, plugin-safe)
473
+ command['fork'] = self.nextToken()
474
+ self.add(command)
475
+ return True
476
+
477
+ def r_fork(self, command):
478
+ next = self.nextPC()
479
+ label = command['fork']
480
+ try:
481
+ label = self.symbols[label + ':']
482
+ except:
483
+ RuntimeError(self.program, f'There is no label "{label + ":"}"')
484
+ return None
485
+ self.run(label)
486
+ return next
487
+
488
+ # get {variable) from {url} [or {command}]
489
+ def k_get(self, command):
490
+ if self.nextIsSymbol():
491
+ record = self.getSymbolRecord()
492
+ if isinstance(self.getObject(record), ECObject):
493
+ command['target'] = self.getToken()
494
+ else:
495
+ NoValueError(self.compiler, record)
496
+ if self.nextIs('from'):
497
+ if self.nextIs('url'):
498
+ url = self.nextValue()
499
+ if url != None:
500
+ command['url'] = url
501
+ command['or'] = None
502
+ get = self.getCodeSize()
503
+ if self.peek() == 'timeout':
504
+ self.nextToken()
505
+ command['timeout'] = self.nextValue()
506
+ else:
507
+ timeout = ECValue(type = int, content = 5)
508
+ command['timeout'] = timeout
509
+ self.processOr(command, get)
510
+ return True
511
+ return False
512
+
513
+ def r_get(self, command):
514
+ global errorCode, errorReason
515
+ retval = ECValue(type=str)
516
+ url = self.textify(command['url'])
517
+ target = self.getVariable(command['target'])
518
+ response = {}
519
+ try:
520
+ timeout = self.textify(command['timeout'])
521
+ response = requests.get(url, auth = ('user', 'pass'), timeout=timeout)
522
+ if response.status_code >= 400:
523
+ errorCode = response.status_code
524
+ errorReason = response.reason
525
+ if command['or'] != None:
526
+ return command['or']
527
+ else:
528
+ RuntimeError(self.program, f'Error code {errorCode}: {errorReason}')
529
+ except Exception as e:
530
+ errorReason = str(e)
531
+ if command['or'] != None:
532
+ return command['or']
533
+ else:
534
+ RuntimeError(self.program, f'Error: {errorReason}')
535
+ retval.setContent(response.text) # type: ignore
536
+ self.program.putSymbolValue(target, retval)
537
+ return self.nextPC()
538
+
539
+ # Go to a label
540
+ # go [to] {label}
541
+ def k_go(self, command):
542
+ self.skip('to') # Optional 'to' (core-reserved keyword, plugin-safe)
543
+ return self.k_goto(command)
544
+
545
+ def k_goto(self, command):
546
+ command['keyword'] = 'goto'
547
+ command['goto'] = self.nextToken()
548
+ self.add(command)
549
+ return True
550
+
551
+ def r_goto(self, command):
552
+ label = f'{command["goto"]}:'
553
+ try:
554
+ if self.symbols[label]:
555
+ return self.symbols[label]
556
+ except:
557
+ pass
558
+ RuntimeError(self.program, f'There is no label "{label}"')
559
+ return None
560
+
561
+ def r_gotoPC(self, command):
562
+ return command['goto']
563
+
564
+ # Call a subroutine
565
+ # gosub [to] {label}
566
+ def k_gosub(self, command):
567
+ self.skip('to') # Optional 'to' (core-reserved keyword, plugin-safe)
568
+ command['gosub'] = self.nextToken()
569
+ self.add(command)
570
+ return True
571
+
572
+ def r_gosub(self, command):
573
+ label = command['gosub'] + ':'
574
+ if label in self.symbols:
575
+ address = self.symbols[label]
576
+ self.stack.append(self.nextPC())
577
+ return address
578
+ RuntimeError(self.program, f'There is no label "{label}"')
579
+ return None
580
+
581
+ # if <condition> <action> [else <action>]
582
+ def k_if(self, command):
583
+ command['condition'] = self.nextCondition()
584
+ self.add(command)
585
+ self.nextToken()
586
+ pcElse = self.getCodeSize()
587
+ cmd = {}
588
+ cmd['lino'] = command['lino']
589
+ cmd['domain'] = 'core'
590
+ cmd['keyword'] = 'gotoPC'
591
+ cmd['goto'] = 0
592
+ cmd['debug'] = False
593
+ self.add(cmd)
594
+ # Get the 'then' code
595
+ self.compileOne()
596
+ if self.peek() == 'else':
597
+ self.nextToken()
598
+ # Add a 'goto' to skip the 'else'
599
+ pcNext = self.getCodeSize()
600
+ cmd = {}
601
+ cmd['lino'] = command['lino']
602
+ cmd['domain'] = 'core'
603
+ cmd['keyword'] = 'gotoPC'
604
+ cmd['goto'] = 0
605
+ cmd['debug'] = False
606
+ self.add(cmd)
607
+ # Fixup the link to the 'else' branch
608
+ self.getCommandAt(pcElse)['goto'] = self.getCodeSize()
609
+ # Process the 'else' branch
610
+ self.nextToken()
611
+ self.compileOne()
612
+ # Fixup the pcNext 'goto'
613
+ self.getCommandAt(pcNext)['goto'] = self.getCodeSize()
614
+ else:
615
+ # We're already at the next command
616
+ self.getCommandAt(pcElse)['goto'] = self.getCodeSize()
617
+ return True
618
+
619
+ def r_if(self, command):
620
+ test = self.program.condition.testCondition(command['condition'])
621
+ if test:
622
+ self.program.pc += 2
623
+ else:
624
+ self.program.pc += 1
625
+ return self.program.pc
626
+
627
+ # Import one or more variables
628
+ def k_import(self, command):
629
+ self.add(command)
630
+ imports = []
631
+ while True:
632
+ vartype = self.nextToken()
633
+ for domain in self.program.getDomains():
634
+ handler = domain.keywordHandler(vartype)
635
+ if handler != None:
636
+ variable = {}
637
+ if not handler(variable):
638
+ raise RuntimeError(self.program, f'Failed to handle variable type "{vartype}"')
639
+ imports.append(variable)
640
+ if self.peek() != 'and':
641
+ break
642
+ self.nextToken()
643
+ command['imports'] = imports
644
+ return True
645
+
646
+ def r_import(self, command):
647
+ exports = self.program.exports
648
+ imports = command['imports']
649
+ if len(imports) < len(exports):
650
+ RuntimeError(self.program, 'Too few imports')
651
+ elif len(imports) > len(exports):
652
+ RuntimeError(self.program, 'Too many imports')
653
+ for n in range(0, len(imports)):
654
+ exportRecord = exports[n]
655
+ importRecord = imports[n]
656
+ if importRecord['classname'] != exportRecord['classname']:
657
+ raise RuntimeError(self.program, f'Import {n} does not match export (wrong type)')
658
+ name = importRecord['name']
659
+ importRecord.clear()
660
+ importRecord['name'] = name
661
+ importRecord['domain'] = exportRecord['domain']
662
+ importRecord['keyword'] = exportRecord['keyword']
663
+ importRecord['import'] = exportRecord
664
+ return self.nextPC()
665
+
666
+ # Increment a variable
667
+ def k_increment(self, command):
668
+ if self.nextIsSymbol():
669
+ record = self.getSymbolRecord()
670
+ self.checkObjectType(self.getObject(record), ECVariable)
671
+ command['target'] = record['name']
672
+ self.add(command)
673
+ return True
674
+ return False
675
+
676
+ def r_increment(self, command):
677
+ return self.incdec(command, '+')
678
+
679
+ # Index to a specified element in a variable
680
+ # index {variable} to {value}
681
+ def k_index(self, command):
682
+ # get the variable
683
+ if self.nextIsSymbol():
684
+ command['target'] = self.getToken()
685
+ if self.nextToken() == 'to':
686
+ # get the value
687
+ command['value'] = self.nextValue()
688
+ self.add(command)
689
+ return True
690
+ return False
691
+
692
+ def r_index(self, command):
693
+ value = self.textify(command['value'])
694
+ record = self.getVariable(command['target'])
695
+ self.getObject(record).setIndex(value)
696
+ return self.nextPC()
697
+
698
+ # Input a value from the terminal
699
+ # input {variable} [with {prompt}]
700
+ def k_input(self, command):
701
+ # get the variable
702
+ if self.nextIsSymbol():
703
+ command['target'] = self.getToken()
704
+ value = ECValue(type=str, content=': ')
705
+ command['prompt'] = value
706
+ if self.peek() == 'with':
707
+ self.nextToken()
708
+ command['prompt'] = self.nextValue()
709
+ self.add(command)
710
+ return True
711
+ return False
712
+
713
+ def r_input(self, command):
714
+ record = self.getVariable(command['target'])
715
+ prompt = command['prompt'].getValue()
716
+ value = ECValue(type=str, content=prompt+input(prompt))
717
+ self.putSymbolValue(record, value)
718
+ return self.nextPC()
719
+
720
+ # Declare a list variable
721
+ def k_list(self, command):
722
+ self.compiler.addValueType()
723
+ return self.compileVariable(command, 'ECList')
724
+
725
+ def r_list(self, command):
726
+ return self.nextPC()
727
+
728
+ # 1 Load a plugin. This is done at compile time.
729
+ # 2 Load text from a file or ssh
730
+ def k_load(self, command):
731
+ self.nextToken()
732
+ if self.tokenIs('plugin'):
733
+ clazz = self.nextToken()
734
+ if self.nextIs('from'):
735
+ source = self.nextToken()
736
+ self.program.importPlugin(f'{source}:{clazz}')
737
+ return True
738
+ elif self.isSymbol():
739
+ record = self.getSymbolRecord()
740
+ if isinstance(self.getObject(record), (ECVariable, ECDictionary, ECList)):
741
+ command['target'] = record['name']
742
+ if self.nextIs('from'):
743
+ if self.nextIsSymbol():
744
+ record = self.getSymbolRecord()
745
+ if record['keyword'] == 'ssh':
746
+ command['ssh'] = record['name']
747
+ command['path'] = self.nextValue()
748
+ else:
749
+ command['file'] = self.getValue()
750
+ else:
751
+ command['file'] = self.getValue()
752
+ command['or'] = None
753
+ load = self.getCodeSize()
754
+ self.processOr(command, load)
755
+ return True
756
+ else:
757
+ FatalError(self.compiler, f'I don\'t understand \'{self.getToken()}\'')
758
+ return False
759
+
760
+ def r_load(self, command):
761
+ errorReason = None
762
+ target = self.getVariable(command['target'])
763
+ if 'ssh' in command:
764
+ ssh = self.getVariable(command['ssh'])
765
+ path = self.textify(command['path'])
766
+ sftp = ssh['sftp']
767
+ try:
768
+ with sftp.open(path, 'r') as remote_file: content = remote_file.read().decode()
769
+ except:
770
+ errorReason = f'Unable to read from {path}'
771
+ if command['or'] != None:
772
+ print(f'Exception "{errorReason}": Running the "or" clause')
773
+ return command['or']
774
+ else:
775
+ RuntimeError(self.program, f'Error: {errorReason}')
776
+ else:
777
+ filename = self.textify(command['file'])
778
+ try:
779
+ with open(filename) as f: content = f.read()
780
+ except:
781
+ errorReason = f'Unable to read from {filename}'
782
+
783
+ if errorReason:
784
+ if command['or'] != None:
785
+ print(f'Exception "{errorReason}": Running the "or" clause')
786
+ return command['or']
787
+ else:
788
+ RuntimeError(self.program, f'Error: {errorReason}')
789
+ value = ECValue(type=str, content=content)
790
+ self.putSymbolValue(target, value)
791
+ return self.nextPC()
792
+
793
+ # Lock a variable
794
+ def k_lock(self, command):
795
+ if self.nextIsSymbol():
796
+ record = self.getSymbolRecord()
797
+ command['target'] = record['name']
798
+ self.add(command)
799
+ return True
800
+ return False
801
+
802
+ def r_lock(self, command):
803
+ target = self.getVariable(command['target'])
804
+ target['locked'] = True
805
+ return self.nextPC()
806
+
807
+ # Log a message
808
+ def k_log(self, command):
809
+ command['log'] = True
810
+ command['keyword'] = 'print'
811
+ return self.k_print(command)
812
+
813
+ # Declare a module variable
814
+ def k_module(self, command):
815
+ self.compiler.addValueType()
816
+ return self.compileVariable(command, 'ECObject')
817
+
818
+ def r_module(self, command):
819
+ return self.nextPC()
820
+
821
+ # Arithmetic multiply
822
+ # multiply {variable} by {value}
823
+ # multiply {value1} by {value2} giving {variable}
824
+ def k_multiply(self, command):
825
+ # Get the (first) item. If it's a symbol, it may be the target variable
826
+ if self.nextIsSymbol():
827
+ record = self.getSymbolRecord()
828
+ self.checkObjectType(record, ECVariable)
829
+ # Hold onto the variable and its value
830
+ variable1 = record['name']
831
+ value1 = self.getValue()
832
+ else:
833
+ # Here we have a value
834
+ value1 = self.getValue()
835
+ variable1 = None
836
+ self.skip('by')
837
+ command['value2'] = self.nextValue()
838
+ # if 'giving' comes next, the target is the next value
839
+ if self.peek() == 'giving':
840
+ self.nextToken()
841
+ if self.nextIsSymbol():
842
+ record = self.getSymbolRecord()
843
+ self.checkObjectType(record, ECVariable)
844
+ command['target'] = record['name']
845
+ command['value1'] = value1
846
+ self.add(command)
847
+ return True
848
+ else:
849
+ # Here the first variable is the target
850
+ if variable1 != None:
851
+ command['target'] = variable1
852
+ self.add(command)
853
+ return True
854
+ return False
855
+
856
+ def r_multiply(self, command):
857
+ value1 = self.textify(command['value1']) if 'value1' in command else None
858
+ value2 = self.textify(command['value2'])
859
+ target = self.getVariable(command['target'])
860
+ # Check that the target variable can hold a value
861
+ self.checkObjectType(target, ECVariable)
862
+ # If value1 exists, we are adding two values and storing the result in target
863
+ if value1 != None:
864
+ # multiply X by Y giving Z
865
+ targetValue = ECValue(type=int, content=int(value1) * int(value2))
866
+ else:
867
+ # multiply X by Y
868
+ targetValue = self.getSymbolValue(target)
869
+ targetValue.setContent(int(targetValue.getContent()) * int(value2))
870
+ self.putSymbolValue(target, targetValue)
871
+ return self.nextPC()
872
+
873
+ # Negate a variable
874
+ def k_negate(self, command):
875
+ if self.nextIsSymbol():
876
+ record = self.getSymbolRecord()
877
+ if record['hasValue']:
878
+ command['target'] = self.getToken()
879
+ self.add(command)
880
+ return True
881
+ self.warning(f'Core.negate: Variable {record["name"]} does not hold a value')
882
+ return False
883
+
884
+ def r_negate(self, command):
885
+ record = self.getVariable(command['target'])
886
+ if not record['hasValue']:
887
+ NoValueRuntimeError(self.program, record)
888
+ return None
889
+ value = self.getSymbolValue(record)
890
+ if value == None:
891
+ RuntimeError(self.program, f'{record["name"]} has not been initialised')
892
+ value.setContent(value.getContent() * -1)
893
+ self.putSymbolValue(record, value)
894
+ return self.nextPC()
895
+
896
+ # on message {action}
897
+ def k_on(self, command):
898
+ if self.nextIs('message'):
899
+ self.nextToken()
900
+ command['goto'] = 0
901
+ self.add(command)
902
+ cmd = {}
903
+ cmd['domain'] = 'core'
904
+ cmd['lino'] = command['lino']
905
+ cmd['keyword'] = 'gotoPC'
906
+ cmd['goto'] = 0
907
+ cmd['debug'] = False
908
+ self.add(cmd)
909
+ # Add the action and a 'stop'
910
+ self.compileOne()
911
+ cmd = {}
912
+ cmd['domain'] = 'core'
913
+ cmd['lino'] = command['lino']
914
+ cmd['keyword'] = 'stop'
915
+ cmd['debug'] = False
916
+ self.add(cmd)
917
+ # Fixup the link
918
+ command['goto'] = self.getCodeSize()
919
+ return True
920
+ return False
921
+
922
+ def r_on(self, command):
923
+ self.program.onMessage(self.nextPC()+1)
924
+ return command['goto']
925
+
926
+ # Open a file
927
+ # open {file} for reading/writing/appending
928
+ def k_open(self, command):
929
+ if self.nextIsSymbol():
930
+ record = self.getSymbolRecord()
931
+ command['target'] = record['name']
932
+ command['path'] = self.nextValue()
933
+ if record['keyword'] == 'file':
934
+ if self.peek() == 'for':
935
+ self.nextToken()
936
+ token = self.nextToken()
937
+ if token == 'appending':
938
+ mode = 'a'
939
+ elif token == 'reading':
940
+ mode = 'r'
941
+ elif token == 'writing':
942
+ mode = 'w'
943
+ else:
944
+ FatalError(self.compiler, 'Unknown file open mode {self.getToken()}')
945
+ return False
946
+ command['mode'] = mode
947
+ else:
948
+ command['mode'] = 'r'
949
+ self.add(command)
950
+ return True
951
+ else:
952
+ FatalError(self.compiler, f'Variable "{self.getToken()}" is not a file')
953
+ else:
954
+ self.warning(f'Core.open: Variable "{self.getToken()}" not declared')
955
+ return False
956
+
957
+ def r_open(self, command):
958
+ record = self.getVariable(command['target'])
959
+ path = self.textify(command['path'])
960
+ if command['mode'] == 'r' and os.path.exists(path) or command['mode'] != 'r':
961
+ record['file'] = open(path, command['mode'])
962
+ return self.nextPC()
963
+ RuntimeError(self.program, f"File {path} does not exist")
964
+
965
+ # Dummy command to hit a debugger breakpoint
966
+ def k_pass(self, command):
967
+ self.add(command)
968
+ return True
969
+
970
+ def r_pass(self, command):
971
+ return self.nextPC()
972
+
973
+ # Pop a value from a stack
974
+ # pop {variable} from {stack}
975
+ def k_pop(self, command):
976
+ if (self.nextIsSymbol()):
977
+ record = self.getSymbolRecord()
978
+ self.checkObjectType(record, ECObject)
979
+ command['target'] = record['name']
980
+ if self.peek() == 'from':
981
+ self.nextToken()
982
+ if self.nextIsSymbol():
983
+ record = self.getSymbolRecord()
984
+ self.checkObjectType(record, ECStack)
985
+ command['from'] = record['name']
986
+ self.add(command)
987
+ return True
988
+ return False
989
+
990
+ def r_pop(self, command):
991
+ record = self.getVariable(command['target'])
992
+ stackRecord = self.getVariable(command['from'])
993
+ value = stackRecord['object'].pop()
994
+ self.putSymbolValue(record, value)
995
+ return self.nextPC()
996
+
997
+ # Perform an HTTP POST
998
+ # post {value} to {url} [giving {variable}] [or {command}]
999
+ def k_post(self, command):
1000
+ if self.nextIs('to'):
1001
+ command['value'] = self.getConstant('')
1002
+ command['url'] = self.getValue()
1003
+ else:
1004
+ command['value'] = self.getValue()
1005
+ if self.nextIs('to'):
1006
+ command['url'] = self.nextValue()
1007
+ if self.peek() == 'giving':
1008
+ self.nextToken()
1009
+ command['result'] = self.nextToken()
1010
+ else:
1011
+ command['result'] = None
1012
+ command['or'] = None
1013
+ post = self.getCodeSize()
1014
+ self.processOr(command, post)
1015
+ return True
1016
+
1017
+ def r_post(self, command):
1018
+ global errorCode, errorReason
1019
+ retval = ECValue(type=str, content = '')
1020
+ value = self.textify(command['value'])
1021
+ url = self.textify(command['url'])
1022
+ try:
1023
+ response = requests.post(url, value, timeout=5)
1024
+ retval.setContent(response.text) # type: ignore
1025
+ if response.status_code >= 400:
1026
+ errorCode = response.status_code
1027
+ errorReason = response.reason
1028
+ if command['or'] != None:
1029
+ print(f'Error {errorCode} {errorReason}: Running the "or" clause')
1030
+ return command['or']
1031
+ else:
1032
+ RuntimeError(self.program, f'Error code {errorCode}: {errorReason}')
1033
+ except Exception as e:
1034
+ errorReason = str(e)
1035
+ if command['or'] != None:
1036
+ print(f'Exception "{errorReason}": Running the "or" clause')
1037
+ return command['or']
1038
+ else:
1039
+ RuntimeError(self.program, f'Error: {errorReason}')
1040
+ if command['result'] != None:
1041
+ result = self.getVariable(command['result'])
1042
+ self.program.putSymbolValue(result, retval)
1043
+ return self.nextPC()
1044
+
1045
+ # Print a value
1046
+ def k_print(self, command):
1047
+ value = self.nextValue()
1048
+ if value != None:
1049
+ command['value'] = value
1050
+ self.add(command)
1051
+ return True
1052
+ FatalError(self.compiler, 'I can\'t print this value')
1053
+ return False
1054
+
1055
+ def r_print(self, command):
1056
+ value = self.textify(command['value'])
1057
+ program = command['program']
1058
+ code = program.code[program.pc]
1059
+ lino = str(code['lino'] + 1)
1060
+ # while len(lino) < 5: lino = f' {lino}'
1061
+ if value == None: value = '<empty>'
1062
+ if 'log' in command:
1063
+ print(f'{datetime.now().time()}:{self.program.name}:{lino}->{value}')
1064
+ else:
1065
+ print(value)
1066
+ return self.nextPC()
1067
+
1068
+ # Push a value onto a stack
1069
+ # push {value} to/onto {stack}
1070
+ def k_push(self, command):
1071
+ value = self.nextValue()
1072
+ command['value'] = value
1073
+ peekValue = self.peek()
1074
+ if peekValue in ['onto', 'to']:
1075
+ self.nextToken()
1076
+ if self.nextIsSymbol():
1077
+ record = self.getSymbolRecord()
1078
+ command['to'] = record['name']
1079
+ self.add(command)
1080
+ return True
1081
+ return False
1082
+
1083
+ def r_push(self, command):
1084
+ value = deepcopy(self.evaluate(command['value']))
1085
+ stackRecord = self.getVariable(command['to'])
1086
+ stackRecord['object'].push(value)
1087
+ return self.nextPC()
1088
+
1089
+ # put {value} into {variable/dictionary/list}
1090
+ def k_put(self, command):
1091
+ value = self.nextValue()
1092
+ if value != None:
1093
+ command['value'] = value
1094
+ valueType = value.getType()
1095
+ if self.nextIs('into'):
1096
+ if self.nextIsSymbol():
1097
+ record = self.getSymbolRecord()
1098
+ command['target'] = record['name']
1099
+ object = self.getObject(record)
1100
+ self.checkObjectType(object, (ECVariable, ECDictionary, ECList))
1101
+ if (isinstance(object, ECVariable) and not valueType in ('dict', 'list', 'json') or
1102
+ isinstance(object, (ECDictionary, ECList))):
1103
+ command['or'] = None
1104
+ self.processOr(command, self.getCodeSize())
1105
+ return True
1106
+ else:
1107
+ FatalError(self.compiler, f'Symbol {self.getToken()} is not a variable')
1108
+ return False
1109
+
1110
+ def r_put(self, command):
1111
+ value = self.evaluate(command['value'])
1112
+ record = self.getVariable(command['target'])
1113
+ self.putSymbolValue(record, value)
1114
+ return self.nextPC()
1115
+
1116
+ # Read from a file
1117
+ # read {variable} from {file}
1118
+ def k_read(self, command):
1119
+ if self.peek() == 'line':
1120
+ self.nextToken()
1121
+ command['line'] = True
1122
+ else:
1123
+ command['line'] = False
1124
+ if self.nextIsSymbol():
1125
+ record = self.getSymbolRecord()
1126
+ self.checkObjectType(self.getObject(record), ECVariable)
1127
+ if self.peek() == 'from':
1128
+ self.nextToken()
1129
+ if self.nextIsSymbol():
1130
+ fileRecord = self.getSymbolRecord()
1131
+ self.checkObjectType(fileRecord['object'], ECFile)
1132
+ command['target'] = record['name']
1133
+ command['file'] = fileRecord['name']
1134
+ self.add(command)
1135
+ return True
1136
+ return False
1137
+ FatalError(self.compiler, f'Symbol "{self.getToken()}" has not been declared')
1138
+ return False
1139
+
1140
+ def r_read(self, command):
1141
+ record = self.getVariable(command['target'])
1142
+ fileRecord = self.getVariable(command['file'])
1143
+ line = command['line']
1144
+ file = fileRecord['file']
1145
+ if file.mode == 'r':
1146
+ content = file.readline().split('\n')[0] if line else file.read()
1147
+ value = ECValue(type=str, content=content)
1148
+ self.putSymbolValue(record, value)
1149
+ return self.nextPC()
1150
+
1151
+ # Release the parent script
1152
+ def k_release(self, command):
1153
+ if self.nextIs('parent'):
1154
+ self.add(command)
1155
+ return True
1156
+
1157
+ def r_release(self, command):
1158
+ self.program.releaseParent()
1159
+ return self.nextPC()
1160
+
1161
+ # Replace a substring
1162
+ #replace {value} with {value} in {variable}
1163
+ def k_replace(self, command):
1164
+ original = self.nextValue()
1165
+ if self.peek() == 'with':
1166
+ self.nextToken()
1167
+ replacement = self.nextValue()
1168
+ if self.nextIs('in'):
1169
+ if self.nextIsSymbol():
1170
+ templateRecord = self.getSymbolRecord()
1171
+ command['original'] = original
1172
+ command['replacement'] = replacement
1173
+ command['target'] = templateRecord['name']
1174
+ self.add(command)
1175
+ return True
1176
+ return False
1177
+
1178
+ def r_replace(self, command):
1179
+ templateRecord = self.getVariable(command['target'])
1180
+ content = self.getSymbolValue(templateRecord).getContent()
1181
+ original = self.textify(command['original'])
1182
+ replacement = self.textify(command['replacement'])
1183
+ content = content.replace(original, str(replacement))
1184
+ value = ECValue(type=str, content=content)
1185
+ self.putSymbolValue(templateRecord, value)
1186
+ return self.nextPC()
1187
+
1188
+ # Reset a variable
1189
+ def k_reset(self, command):
1190
+ if self.nextIsSymbol():
1191
+ record = self.getSymbolRecord()
1192
+ command['target'] = record['name']
1193
+ self.add(command)
1194
+ return True
1195
+ return False
1196
+
1197
+ def r_reset(self, command):
1198
+ record = self.getVariable(command['target'])
1199
+ self.getObject(record).reset()
1200
+ return self.nextPC()
1201
+
1202
+ # Return from subroutine
1203
+ def k_return(self, command):
1204
+ self.add(command)
1205
+ return True
1206
+
1207
+ def r_return(self, command):
1208
+ self.program.debugSkip = False
1209
+ return self.stack.pop()
1210
+
1211
+ # Compile and run a script
1212
+ # run {path} [as {module}] [with {variable} [and {variable}...]]
1213
+ def k_run(self, command):
1214
+ try:
1215
+ command['path'] = self.nextValue()
1216
+ except Exception as e:
1217
+ self.warning(f'Core.run: Path expected')
1218
+ return False
1219
+ if self.nextIs('as'):
1220
+ if self.nextIsSymbol():
1221
+ record = self.getSymbolRecord()
1222
+ if record['keyword'] == 'module':
1223
+ name = record['name']
1224
+ command['module'] = name
1225
+ else: FatalError(self.compiler, f'Symbol \'name\' is not a module')
1226
+ else: FatalError(self.compiler, 'Module name expected after \'as\'')
1227
+ else: FatalError(self.compiler, '\'as {module name}\' expected')
1228
+ exports = []
1229
+ if self.peek() == 'with':
1230
+ self.nextToken()
1231
+ while True:
1232
+ name = self.nextToken()
1233
+ record = self.getSymbolRecord()
1234
+ exports.append(name)
1235
+ if self.peek() != 'and':
1236
+ break
1237
+ self.nextToken()
1238
+ command['exports'] = json.dumps(exports)
1239
+ self.add(command)
1240
+ return True
1241
+
1242
+ def r_run(self, command):
1243
+ module = self.getVariable(command['module'])
1244
+ path = self.textify(command['path'])
1245
+ exports = json.loads(command['exports'])
1246
+ for n in range(0, len(exports)):
1247
+ exports[n] = self.getVariable(exports[n])
1248
+ module['path'] = path
1249
+ parent = ECValue()
1250
+ parent.program = self.program # type: ignore
1251
+ parent.pc = self.nextPC() # type: ignore
1252
+ parent.waiting = True # type: ignore
1253
+ p = self.program.__class__
1254
+ p(path).start(parent, module, exports)
1255
+ return 0
1256
+
1257
+ # Save a value to a file
1258
+ def k_save(self, command):
1259
+ command['content'] = self.nextValue()
1260
+ self.skip('to')
1261
+ if self.nextIsSymbol():
1262
+ record = self.getSymbolRecord()
1263
+ if record['keyword'] == 'ssh':
1264
+ command['ssh'] = record['name']
1265
+ command['path'] = self.nextValue()
1266
+ else:
1267
+ command['file'] = self.getValue()
1268
+ else:
1269
+ command['file'] = self.getValue()
1270
+ command['or'] = None
1271
+ save = self.getCodeSize()
1272
+ self.processOr(command, save)
1273
+ return True
1274
+
1275
+ def r_save(self, command):
1276
+ errorReason = None
1277
+ content = self.textify(command['content'])
1278
+ if 'ssh' in command:
1279
+ ssh = self.getVariable(command['ssh'])
1280
+ path = self.textify(command['path'])
1281
+ sftp = ssh['sftp']
1282
+ if path.endswith('.json'): content = json.dumps(content)
1283
+ try:
1284
+ with sftp.open(path, 'w') as remote_file: remote_file.write(content)
1285
+ except:
1286
+ errorReason = 'Unable to write to {path}'
1287
+ if command['or'] != None:
1288
+ print(f'Exception "{errorReason}": Running the "or" clause')
1289
+ return command['or']
1290
+ else:
1291
+ RuntimeError(self.program, f'Error: {errorReason}')
1292
+ else:
1293
+ filename = self.textify(command['file'])
1294
+ try:
1295
+ if content == None:
1296
+ content = ''
1297
+ elif isinstance(content, dict) or isinstance(content, list):
1298
+ content = json.dumps(content)
1299
+ elif not isinstance(content, str):
1300
+ content = self.textify(content)
1301
+ with open(filename, 'w') as f: f.write(content)
1302
+ except Exception as e:
1303
+ errorReason = f'Unable to write to {filename}: {str(e)}'
1304
+
1305
+ if errorReason:
1306
+ if command['or'] != None:
1307
+ print(f'Exception "{errorReason}": Running the "or" clause')
1308
+ return command['or']
1309
+ else:
1310
+ RuntimeError(self.program, f'Error: {errorReason}')
1311
+ return self.nextPC()
1312
+
1313
+ # Provide a name for the script
1314
+ def k_script(self, command):
1315
+ self.program.name = self.nextToken()
1316
+ return True
1317
+
1318
+ # Send a message to a module
1319
+ def k_send(self, command):
1320
+ command['message'] = self.nextValue()
1321
+ if self.nextIs('to'):
1322
+ if self.nextIsSymbol():
1323
+ record = self.getSymbolRecord()
1324
+ if record['keyword'] == 'module':
1325
+ command['module'] = record['name']
1326
+ self.add(command)
1327
+ return True
1328
+ return False
1329
+
1330
+ def r_send(self, command):
1331
+ message = self.textify(command['message'])
1332
+ module = self.getVariable(command['module'])
1333
+ module['child'].handleMessage(message)
1334
+ return self.nextPC()
1335
+
1336
+ # Set a value
1337
+ # set {variable}
1338
+ # set {variable} to {value}
1339
+ # set {ssh} host {host} user {user} password {password}
1340
+ # set the elements of {variable} to {value}
1341
+ # set element/entry/property of {variable} to {value}
1342
+ # set breakpoint
1343
+ def k_set(self, command):
1344
+ if self.nextIsSymbol():
1345
+ record = self.getSymbolRecord()
1346
+ command['target'] = record['name']
1347
+ if record['keyword'] == 'ssh':
1348
+ host = None
1349
+ user = None
1350
+ password = None
1351
+ while True:
1352
+ token = self.peek()
1353
+ if token == 'host':
1354
+ self.nextToken()
1355
+ host = self.nextValue()
1356
+ elif token == 'user':
1357
+ self.nextToken()
1358
+ user = self.nextValue()
1359
+ elif token == 'password':
1360
+ self.nextToken()
1361
+ password = self.nextValue()
1362
+ else: break
1363
+ command['host'] = host
1364
+ command['user'] = user
1365
+ command['password'] = password
1366
+ command['type'] = 'ssh'
1367
+ self.add(command)
1368
+ return True
1369
+ elif isinstance(self.getObject(record), ECVariable):
1370
+ self.skip('to')
1371
+ mark = self.compiler.getIndex()
1372
+ value = self.nextValue()
1373
+ if value != None:
1374
+ command['type'] = 'setValue'
1375
+ command['value'] = value
1376
+ else:
1377
+ self.rewindTo(mark)
1378
+ command['type'] = 'set'
1379
+ self.add(command)
1380
+ return True
1381
+ return False
1382
+
1383
+ token = self.getToken()
1384
+ if token == 'the':
1385
+ token = self.nextToken()
1386
+ command['type'] = token
1387
+
1388
+ if token == 'elements':
1389
+ self.nextToken()
1390
+ if self.peek() == 'of':
1391
+ self.nextToken()
1392
+ if self.nextIsSymbol():
1393
+ command['name'] = self.getToken()
1394
+ if self.peek() == 'to':
1395
+ self.nextToken()
1396
+ command['elements'] = self.nextValue()
1397
+ self.add(command)
1398
+ return True
1399
+
1400
+ elif token == 'encoding':
1401
+ if self.nextIs('to'):
1402
+ command['encoding'] = self.nextValue()
1403
+ self.add(command)
1404
+ return True
1405
+
1406
+ elif token in ('entry', 'property'):
1407
+ command['key'] = self.nextValue()
1408
+ if self.nextIs('of'):
1409
+ if self.nextIsSymbol():
1410
+ record = self.getSymbolRecord()
1411
+ if token == 'entry':
1412
+ self.checkObjectType(self.getObject(record), ECDictionary)
1413
+ command['target'] = record['name']
1414
+ if self.nextIs('to'):
1415
+ value = self.nextValue()
1416
+ if value == None:
1417
+ FatalError(self.compiler, 'Unable to get a value')
1418
+ command['value'] = value
1419
+ self.add(command)
1420
+ return True
1421
+
1422
+ elif token == 'element':
1423
+ command['index'] = self.nextValue()
1424
+ if self.nextIs('of'):
1425
+ if self.nextIsSymbol():
1426
+ command['target'] = self.getSymbolRecord()['name']
1427
+ if self.nextIs('to'):
1428
+ command['value'] = self.nextValue()
1429
+ self.add(command)
1430
+ return True
1431
+
1432
+ elif token == 'path':
1433
+ command['path'] = self.nextValue()
1434
+ self.add(command)
1435
+ return True
1436
+
1437
+ elif token == 'breakpoint':
1438
+ command['breakpoint'] = True
1439
+ self.add(command)
1440
+ return True
1441
+
1442
+ return False
1443
+
1444
+ def r_set(self, command):
1445
+ cmdType = command['type']
1446
+ if cmdType == 'set':
1447
+ target = self.getVariable(command['target'])
1448
+ self.putSymbolValue(target, ECValue(type=bool, content=True))
1449
+ return self.nextPC()
1450
+
1451
+ elif cmdType == 'setValue':
1452
+ value = self.evaluate(command['value'])
1453
+ target = self.getVariable(command['target'])
1454
+ self.putSymbolValue(target, value)
1455
+ return self.nextPC()
1456
+
1457
+ elif cmdType == 'elements':
1458
+ record = self.getVariable(command['name'])
1459
+ elements = self.textify(command['elements'])
1460
+ object = self.getObject(record)
1461
+ self.checkObjectType(object, ECObject)
1462
+ object.setElements(elements)
1463
+ return self.nextPC()
1464
+
1465
+ elif cmdType == 'element':
1466
+ value = self.textify(command['value'])
1467
+ index = self.textify(command['index'])
1468
+ target = self.getVariable(command['target'])
1469
+ val = self.getSymbolValue(target)
1470
+ content = val.getContent()
1471
+ if content == '':
1472
+ content = []
1473
+ # else:
1474
+ # content = json.loads(content)
1475
+ content[index] = value
1476
+ val.setContent(content)
1477
+ self.putSymbolValue(target, val)
1478
+ return self.nextPC()
1479
+
1480
+ elif cmdType == 'encoding':
1481
+ self.encoding = self.textify(command['encoding'])
1482
+ return self.nextPC()
1483
+
1484
+ elif cmdType == 'path':
1485
+ path = self.textify(command['path'])
1486
+ os.chdir(path)
1487
+ return self.nextPC()
1488
+
1489
+ elif cmdType == 'entry':
1490
+ key = self.textify(command['key'])
1491
+ value = self.textify(command['value'])
1492
+ record = self.getVariable(command['target'])
1493
+ self.checkObjectType(self.getObject(record), ECDictionary)
1494
+ variable = self.getObject(record)
1495
+ variable.setEntry(key, value)
1496
+ return self.nextPC()
1497
+
1498
+ elif cmdType == 'property':
1499
+ key = self.textify(command['key'])
1500
+ value = self.evaluate(command['value'])
1501
+ record = self.getVariable(command['target'])
1502
+ variable = self.getObject(record)
1503
+ variable.setProperty(key, value)
1504
+ content = variable.getContent()
1505
+ if content == None: content = {}
1506
+ elif not isinstance(content, dict):
1507
+ raise RuntimeError(self.program, f'{record["name"]} is not a dictionary')
1508
+ if isinstance(value, dict): content[key] = value
1509
+ else: content[key] = self.textify(value)
1510
+ variable.setContent(ECValue(type='dict', content=content))
1511
+ return self.nextPC()
1512
+
1513
+ elif cmdType == 'ssh':
1514
+ target = self.getVariable(command['target'])
1515
+ host = self.textify(command['host'])
1516
+ user = self.textify(command['user'])
1517
+ password = self.textify(command['password'])
1518
+ ssh = paramiko.SSHClient()
1519
+ target['ssh'] = ssh
1520
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
1521
+ try:
1522
+ ssh.connect(host, username=user, password=password, timeout=10)
1523
+ target['sftp'] = ssh.open_sftp()
1524
+ except:
1525
+ target['error'] = f'Unable to connect to {host} (timeout)'
1526
+ return self.nextPC()
1527
+
1528
+ elif cmdType == 'breakpoint':
1529
+ self.program.breakpoint = True
1530
+ return self.nextPC()
1531
+
1532
+ # Shuffle a JSON list
1533
+ def k_shuffle(self, command):
1534
+ if self.nextIsSymbol():
1535
+ record = self.getSymbolRecord()
1536
+ if record['hasValue']:
1537
+ command['target'] = self.getToken()
1538
+ self.add(command)
1539
+ return True
1540
+ self.warning(f'Core.negate: Variable {record["name"]} does not hold a value')
1541
+ return False
1542
+
1543
+ def r_shuffle(self, command):
1544
+ record = self.getVariable(command['target'])
1545
+ if not record['hasValue']:
1546
+ NoValueRuntimeError(self.program, record)
1547
+ return None
1548
+ value = self.getSymbolValue(record)
1549
+ if value == None:
1550
+ RuntimeError(self.program, f'{record["name"]} has not been initialised')
1551
+ content = value.getContent()
1552
+ if isinstance(content, list):
1553
+ random.shuffle(content)
1554
+ value.setContent(content)
1555
+ self.putSymbolValue(record, value)
1556
+ return self.nextPC()
1557
+ RuntimeError(self.program, f'{record["name"]} is not a list')
1558
+
1559
+ # Split a string into a variable with several elements
1560
+ # split {variable} on {value}
1561
+ def k_split(self, command):
1562
+ if self.nextIsSymbol():
1563
+ record = self.getSymbolRecord()
1564
+ if isinstance(record['object'], ECObject):
1565
+ command['target'] = record['name']
1566
+ value = ECValue(type=str, content='\n')
1567
+ command['on'] = value
1568
+ if self.peek() == 'on':
1569
+ self.nextToken()
1570
+ if self.peek() == 'tab':
1571
+ value.setContent('\t')
1572
+ self.nextToken()
1573
+ else:
1574
+ command['on'] = self.nextValue()
1575
+ self.add(command)
1576
+ return True
1577
+ else: self.noSymbolWarning()
1578
+ return False
1579
+
1580
+ def r_split(self, command):
1581
+ target = self.getVariable(command['target'])
1582
+ value = self.getSymbolValue(target)
1583
+ content = value.getContent().split(self.textify(command['on']))
1584
+ elements = len(content)
1585
+ object = target['object']
1586
+ object.setElements(elements)
1587
+
1588
+ for n in range(0, elements):
1589
+ val = ECValue(type=str, content=content[n])
1590
+ object.setIndex(n)
1591
+ object.setValue(val)
1592
+ object.setIndex(0)
1593
+
1594
+ return self.nextPC()
1595
+
1596
+ # Declare an SSH connection variable
1597
+ def k_ssh(self, command):
1598
+ self.compiler.addValueType()
1599
+ return self.compileVariable(command, 'ECSSH')
1600
+
1601
+ def r_ssh(self, command):
1602
+ return self.nextPC()
1603
+
1604
+ # Declare a stack variable
1605
+ def k_stack(self, command):
1606
+ self.compiler.addValueType()
1607
+ return self.compileVariable(command, 'ECStack')
1608
+
1609
+ def r_stack(self, command):
1610
+ return self.nextPC()
1611
+
1612
+ # Stop the current execution thread
1613
+ def k_stop(self, command):
1614
+ self.add(command)
1615
+ return True
1616
+
1617
+ def r_stop(self, command):
1618
+ return 0
1619
+
1620
+ # Issue a system call
1621
+ # system {command}
1622
+ def k_system(self, command):
1623
+ background = False
1624
+ token = self.nextToken()
1625
+ if token == 'background':
1626
+ self.nextToken()
1627
+ background = True
1628
+ value = self.getValue()
1629
+ if value != None:
1630
+ command['value'] = value
1631
+ command['background'] = background
1632
+ self.add(command)
1633
+ return True
1634
+ FatalError(self.compiler, 'I can\'t give this command')
1635
+ return False
1636
+
1637
+ def r_system(self, command):
1638
+ value = self.textify(command['value'])
1639
+ if value != None:
1640
+ if command['background']:
1641
+ subprocess.Popen(["sh",value,"&"])
1642
+ else:
1643
+ os.system(value)
1644
+ return self.nextPC()
1645
+
1646
+ # Arithmetic subtraction
1647
+ # take {value} from {variable}
1648
+ # take {value1} from {value2} giving {variable}
1649
+ def k_take(self, command):
1650
+ # Get the (first) value
1651
+ command['value1'] = self.nextValue()
1652
+ self.skip('from')
1653
+ if self.nextIsSymbol():
1654
+ record = self.getSymbolRecord()
1655
+ self.checkObjectType(record, ECObject)
1656
+ # If 'giving' comes next, this variable is the second value
1657
+ if self.peek() == 'giving':
1658
+ v2 = ECValue(type='symbol')
1659
+ v2.setContent(record['name'])
1660
+ command['value2'] = v2
1661
+ self.nextToken()
1662
+ # Now get the target variable
1663
+ if self.nextIsSymbol():
1664
+ record = self.getSymbolRecord()
1665
+ self.checkObjectType(record, ECVariable)
1666
+ command['target'] = record['name']
1667
+ self.add(command)
1668
+ return True
1669
+ else:
1670
+ # Here the variable is the target
1671
+ command['target'] = record['name']
1672
+ self.add(command)
1673
+ return True
1674
+ else:
1675
+ # Here we have 2 values so 'giving' must come next
1676
+ command['value2'] = self.getValue()
1677
+ if self.nextToken() == 'giving':
1678
+ if self.nextIsSymbol():
1679
+ record = self.getSymbolRecord()
1680
+ self.checkObjectType(record, ECVariable)
1681
+ command['target'] = record['name']
1682
+ self.add(command)
1683
+ return True
1684
+ raise FatalError(self.compiler, 'Cannot subtract values: target variable expected')
1685
+ return False
1686
+
1687
+ def r_take(self, command):
1688
+ value1 = self.textify(command['value1'])
1689
+ value2 = self.textify(command['value2']) if 'value2' in command else None
1690
+ target = self.getVariable(command['target'])
1691
+ # Check that the target variable can hold a value
1692
+ self.checkObjectType(target, ECVariable)
1693
+ # If value2 exists, we are adding two values and storing the result in target
1694
+ if value2 != None:
1695
+ # take X from Y giving Z
1696
+ targetValue = ECValue(type=int, content=int(value2) - int(value1))
1697
+ else:
1698
+ # take X from Y
1699
+ targetValue = self.getSymbolValue(target)
1700
+ targetValue.setContent(int(targetValue.getContent()) - int(value1))
1701
+ self.putSymbolValue(target, targetValue)
1702
+ return self.nextPC()
1703
+
1704
+ # Toggle a boolean value
1705
+ def k_toggle(self, command):
1706
+ if self.nextIsSymbol():
1707
+ target = self.getSymbolRecord()
1708
+ self.checkObjectType(target, ECVariable)
1709
+ command['target'] = target['name']
1710
+ self.add(command)
1711
+ return True
1712
+ return False
1713
+
1714
+ def r_toggle(self, command):
1715
+ target = self.getVariable(command['target'])
1716
+ value = self.getSymbolValue(target)
1717
+ val = ECValue(type=bool, content=not value.getContent())
1718
+ self.putSymbolValue(target, val)
1719
+ self.add(command)
1720
+ return self.nextPC()
1721
+
1722
+ # Trim whitespace from a variable
1723
+ def k_trim(self, command):
1724
+ if self.nextIsSymbol():
1725
+ record = self.getSymbolRecord()
1726
+ if record['hasValue']:
1727
+ command['name'] = record['name']
1728
+ self.add(command)
1729
+ return True
1730
+ return False
1731
+
1732
+ def r_trim(self, command):
1733
+ record = self.getVariable(command['name'])
1734
+ value = record['value'][record['index']]
1735
+ if value.getType() == str:
1736
+ content = value.getContent()
1737
+ value.setContent(content.strip())
1738
+ return self.nextPC()
1739
+
1740
+ # Truncate a file
1741
+ def k_truncate(self, command):
1742
+ if self.nextIsSymbol():
1743
+ fileRecord = self.getSymbolRecord()
1744
+ if fileRecord['keyword'] == 'file':
1745
+ command['file'] = fileRecord['name']
1746
+ self.add(command)
1747
+ return True
1748
+ return False
1749
+
1750
+ def r_truncate(self, command):
1751
+ fileRecord = self.getVariable(command['file'])
1752
+ fileRecord['file'].truncate()
1753
+ return self.nextPC()
1754
+
1755
+ # Unlock a variable
1756
+ def k_unlock(self, command):
1757
+ if self.nextIsSymbol():
1758
+ record = self.getSymbolRecord()
1759
+ command['target'] = record['name']
1760
+ self.add(command)
1761
+ return True
1762
+ return False
1763
+
1764
+ def r_unlock(self, command):
1765
+ target = self.getVariable(command['target'])
1766
+ target['locked'] = False
1767
+ return self.nextPC()
1768
+
1769
+ # use plugin {class} from {source}
1770
+ # use graphics
1771
+ # use psutil.
1772
+ def k_use(self, command):
1773
+ if self.peek() == 'plugin':
1774
+ # Import a plugin
1775
+ self.nextToken()
1776
+ clazz = self.nextToken()
1777
+ if self.nextIs('from'):
1778
+ source = self.nextToken()
1779
+ self.program.importPlugin(f'{source}:{clazz}')
1780
+ return True
1781
+ return False
1782
+ else:
1783
+ token = self.nextToken()
1784
+ if token == 'graphics':
1785
+ return self.program.useGraphics()
1786
+ elif token == 'psutil':
1787
+ return self.program.usePSUtil()
1788
+ return False
1789
+
1790
+ # Declare a general-purpose variable
1791
+ def k_variable(self, command):
1792
+ self.compiler.addValueType()
1793
+ return self.compileVariable(command, 'ECVariable')
1794
+
1795
+ def r_variable(self, command):
1796
+ return self.nextPC()
1797
+
1798
+ # Pause for a specified time
1799
+ def k_wait(self, command):
1800
+ command['value'] = self.nextValue()
1801
+ multipliers = {}
1802
+ multipliers['milli'] = 1
1803
+ multipliers['millis'] = 1
1804
+ multipliers['tick'] = 10
1805
+ multipliers['ticks'] = 10
1806
+ multipliers['second'] = 1000
1807
+ multipliers['seconds'] = 1000
1808
+ multipliers['minute'] = 60000
1809
+ multipliers['minutes'] = 60000
1810
+ command['multiplier'] = multipliers['second']
1811
+ token = self.peek()
1812
+ if token in multipliers:
1813
+ self.nextToken()
1814
+ command['multiplier'] = multipliers[token]
1815
+ self.add(command)
1816
+ return True
1817
+
1818
+ def r_wait(self, command):
1819
+ value = self.textify(command['value']) * command['multiplier']
1820
+ next = self.nextPC()
1821
+ threading.Timer(value/1000.0, lambda: (self.run(next))).start()
1822
+ return 0
1823
+
1824
+ # while <condition> <action>
1825
+ def k_while(self, command):
1826
+ code = self.nextCondition()
1827
+ if code == None:
1828
+ return None
1829
+ # token = self.getToken()
1830
+ command['condition'] = code
1831
+ test = self.getCodeSize()
1832
+ self.add(command)
1833
+ # Set up a goto for when the test fails
1834
+ fail = self.getCodeSize()
1835
+ cmd = {}
1836
+ cmd['lino'] = command['lino']
1837
+ cmd['domain'] = 'core'
1838
+ cmd['keyword'] = 'gotoPC'
1839
+ cmd['goto'] = 0
1840
+ cmd['debug'] = False
1841
+ self.add(cmd)
1842
+ # Do the body of the while
1843
+ self.nextToken()
1844
+ if self.compileOne() == False:
1845
+ return False
1846
+ # Repeat the test
1847
+ cmd = {}
1848
+ cmd['lino'] = command['lino']
1849
+ cmd['domain'] = 'core'
1850
+ cmd['keyword'] = 'gotoPC'
1851
+ cmd['goto'] = test
1852
+ cmd['debug'] = False
1853
+ self.add(cmd)
1854
+ # Fixup the 'goto' on completion
1855
+ self.getCommandAt(fail)['goto'] = self.getCodeSize()
1856
+ return True
1857
+
1858
+ def r_while(self, command):
1859
+ test = self.program.condition.testCondition(command['condition'])
1860
+ if test:
1861
+ self.program.pc += 2
1862
+ else:
1863
+ self.program.pc += 1
1864
+ return self.program.pc
1865
+
1866
+ # Write to a file
1867
+ def k_write(self, command):
1868
+ if self.peek() == 'line':
1869
+ self.nextToken()
1870
+ command['line'] = True
1871
+ else:
1872
+ command['line'] = False
1873
+ command['value'] = self.nextValue()
1874
+ if self.peek() == 'to':
1875
+ self.nextToken()
1876
+ if self.nextIsSymbol():
1877
+ fileRecord = self.getSymbolRecord()
1878
+ if fileRecord['keyword'] == 'file':
1879
+ command['file'] = fileRecord['name']
1880
+ self.add(command)
1881
+ return True
1882
+ return False
1883
+
1884
+ def r_write(self, command):
1885
+ value = self.textify(command['value'])
1886
+ fileRecord = self.getVariable(command['file'])
1887
+ file = fileRecord['file']
1888
+ if file.mode in ['w', 'w+', 'a', 'a+']:
1889
+ file.write(f'{value}')
1890
+ if command['line']:
1891
+ file.write('\n')
1892
+ return self.nextPC()
1893
+
1894
+ #############################################################################
1895
+ # Support functions
1896
+
1897
+ def incdec(self, command, mode):
1898
+ record = self.getVariable(command['target'])
1899
+ self.checkObjectType(record['object'], ECVariable)
1900
+ value = self.getSymbolValue(record)
1901
+ content = value.getContent()
1902
+ if not isinstance(content, int):
1903
+ RuntimeError(self.program, f'Variable {record["name"]} does not hold an integer')
1904
+ if mode == '+': value.setContent(content + 1)
1905
+ else: value.setContent(content - 1)
1906
+ self.putSymbolValue(record, value)
1907
+ return self.nextPC()
1908
+
1909
+ #############################################################################
1910
+ # Compile a value in this domain
1911
+ def compileValue(self):
1912
+ value = ECValue()
1913
+ token = self.getToken()
1914
+ if self.isSymbol():
1915
+ value.setValue(type='symbol', content=token)
1916
+ return value
1917
+
1918
+ value.setType(token)
1919
+
1920
+ if token == 'arg':
1921
+ self.nextToken()
1922
+ value.index = self.getValue()
1923
+ return value
1924
+
1925
+ if token in ['cos', 'sin', 'tan']:
1926
+ value.angle = self.nextValue()
1927
+ if self.nextToken() == 'radius':
1928
+ value.radius = self.nextValue()
1929
+ return value
1930
+ return None
1931
+
1932
+ if token in ['now', 'today', 'newline', 'tab', 'empty']:
1933
+ return value
1934
+
1935
+ if token in ['stringify', 'prettify', 'json', 'lowercase', 'uppercase', 'hash', 'random', float, 'integer', 'encode', 'decode']:
1936
+ value.setContent(self.nextValue())
1937
+ return value
1938
+
1939
+ if (token in ['datime', 'datetime']):
1940
+ value.setType('datime')
1941
+ value.timestamp = self.nextValue()
1942
+ if self.peek() == 'format':
1943
+ self.nextToken()
1944
+ value.format = self.nextValue()
1945
+ else:
1946
+ value.format = None
1947
+ return value
1948
+
1949
+ if token == 'element':
1950
+ value.index = self.nextValue()
1951
+ if self.nextToken() == 'of':
1952
+ if self.nextIsSymbol():
1953
+ record = self.getSymbolRecord()
1954
+ self.checkObjectType(record['object'], ECList)
1955
+ value.target = ECValue(type='symbol', content=record['name'])
1956
+ return value
1957
+ return None
1958
+
1959
+ if token == 'entry':
1960
+ value.key = self.nextValue() # type: ignore
1961
+ if self.nextToken() in ('in', 'of'):
1962
+ if self.nextIsSymbol():
1963
+ record = self.getSymbolRecord()
1964
+ object = record['object']
1965
+ self.checkObjectType(object, ECDictionary)
1966
+ value.target = object.name
1967
+ return value
1968
+ return None
1969
+
1970
+ if token == 'arg':
1971
+ value.setContent(self.nextValue())
1972
+ if self.getToken() == 'of':
1973
+ if self.nextIsSymbol():
1974
+ record = self.getSymbolRecord()
1975
+ if record['keyword'] == 'variable':
1976
+ value.target = record['name'] # type: ignore
1977
+ return value
1978
+ return None
1979
+
1980
+ if token == 'trim':
1981
+ self.nextToken()
1982
+ value.setContent(self.getValue())
1983
+ return value
1984
+
1985
+ if self.getToken() == 'the':
1986
+ self.nextToken()
1987
+
1988
+ token = self.getToken()
1989
+ value.setType(token)
1990
+
1991
+ if token == 'args':
1992
+ return value
1993
+
1994
+ if token == 'elements':
1995
+ if self.nextIs('of'):
1996
+ if self.nextIsSymbol():
1997
+ value.name = self.getToken() # type: ignore
1998
+ return value
1999
+ return None
2000
+
2001
+ if token == 'keys':
2002
+ if self.nextIs('of'):
2003
+ if self.nextIsSymbol():
2004
+ value.name = self.getToken() # type: ignore
2005
+ return value
2006
+ return None
2007
+
2008
+ if token == 'count':
2009
+ if self.nextIs('of'):
2010
+ if self.nextIsSymbol():
2011
+ record = self.getSymbolRecord()
2012
+ object = record['object']
2013
+ if isinstance(object, (ECVariable, ECList)):
2014
+ value.setContent(record['name'])
2015
+ return value
2016
+ return None
2017
+
2018
+ if token == 'index':
2019
+ if self.nextIs('of'):
2020
+ if self.nextIsSymbol():
2021
+ value.variable = self.getSymbolRecord()['name'] # type: ignore
2022
+ if self.peek() == 'in':
2023
+ value.value = None # type: ignore
2024
+ value.setType('indexOf')
2025
+ self.nextToken()
2026
+ if self.nextIsSymbol():
2027
+ value.target = self.getSymbolRecord()['name'] # type: ignore
2028
+ return value
2029
+ else:
2030
+ value.name = self.getToken() # type: ignore
2031
+ return value
2032
+ else:
2033
+ value.value = self.getValue() # type: ignore
2034
+ if self.nextIs('in'):
2035
+ value.variable = None # type: ignore
2036
+ value.setType('indexOf')
2037
+ if self.nextIsSymbol():
2038
+ value.target = self.getSymbolRecord()['name'] # type: ignore
2039
+ return value
2040
+ return None
2041
+
2042
+ if token == 'value':
2043
+ if self.nextIs('of'):
2044
+ v = self.nextValue()
2045
+ if v !=None:
2046
+ value.setValue(type='valueOf', content=v)
2047
+ return value
2048
+ return None
2049
+
2050
+ if token == 'length':
2051
+ value.setType('lengthOf')
2052
+ if self.nextIs('of'):
2053
+ value.setContent(self.nextValue())
2054
+ return value
2055
+ return None
2056
+
2057
+ if token in ['left', 'right']:
2058
+ value.count = self.nextValue() # type: ignore
2059
+ if self.nextToken() == 'of':
2060
+ value.setContent(self.nextValue())
2061
+ return value
2062
+ return None
2063
+
2064
+ # from {n} of {value}
2065
+ # from {n} to {m} of {value}
2066
+ if token == 'from':
2067
+ value.start = self.nextValue() # type: ignore
2068
+ if self.peek() == 'to':
2069
+ self.nextToken()
2070
+ value.to = self.nextValue() # type: ignore
2071
+ else:
2072
+ value.to = None # type: ignore
2073
+ if self.nextToken() == 'of':
2074
+ value.setContent(self.nextValue())
2075
+ return value
2076
+
2077
+ # position of [the] [last] {needle} in {haystack}
2078
+ if token == 'position':
2079
+ self.skip('of')
2080
+ self.skip('the')
2081
+ if self.peek() == 'last':
2082
+ value.last = True # type: ignore
2083
+ self.nextToken()
2084
+ value.needle = self.nextValue() # type: ignore
2085
+ self.skip('in')
2086
+ value.haystack = self.nextValue() # type: ignore
2087
+ return value
2088
+
2089
+ if token == 'message':
2090
+ return value
2091
+
2092
+ if token == 'timestamp':
2093
+ value.format = None # type: ignore
2094
+ if self.peek() == 'of':
2095
+ self.nextToken()
2096
+ value.timestamp = self.nextValue() # type: ignore
2097
+ if self.peek() == 'format':
2098
+ self.nextToken()
2099
+ value.format = self.nextValue() # type: ignore
2100
+ return value
2101
+
2102
+ if token == 'files':
2103
+ token = self.nextToken()
2104
+ if token in ['in', 'of']:
2105
+ value.target = self.nextValue() # type: ignore
2106
+ return value
2107
+ return None
2108
+
2109
+ if token == 'weekday':
2110
+ value.setType('weekday')
2111
+ return value
2112
+
2113
+ if token == 'error':
2114
+ token = self.peek()
2115
+ if token == 'code':
2116
+ self.nextToken()
2117
+ value.item = 'errorCode' # type: ignore
2118
+ return value
2119
+ elif token == 'reason':
2120
+ self.nextToken()
2121
+ value.item = 'errorReason' # type: ignore
2122
+ return value
2123
+ elif token in ['in', 'of']:
2124
+ self.nextToken()
2125
+ if self.nextIsSymbol():
2126
+ record = self.getSymbolRecord()
2127
+ if isinstance(record['object'], ECSSH):
2128
+ value.item = 'sshError' # type: ignore
2129
+ value.name = record['name'] # type: ignore
2130
+ return value
2131
+ return None
2132
+
2133
+ if token == 'type':
2134
+ if self.nextIs('of'):
2135
+ value.value = self.nextValue() # type: ignore
2136
+ return value
2137
+ return None
2138
+
2139
+ if token == 'modification':
2140
+ if self.nextIs('time'):
2141
+ if self.nextIs('of'):
2142
+ value.fileName = self.nextValue() # type: ignore
2143
+ return value
2144
+ return None
2145
+
2146
+ if token == 'system':
2147
+ value.setContent(self.nextValue())
2148
+ return value
2149
+
2150
+ if token == 'ticker':
2151
+ return value
2152
+
2153
+ return None
2154
+
2155
+ #############################################################################
2156
+ # Modify a value or leave it unchanged.
2157
+ def modifyValue(self, value):
2158
+ if self.peek() == 'modulo':
2159
+ self.nextToken()
2160
+ mv = ECValue(type='modulo', content=value)
2161
+ mv.modval = self.nextValue() # type: ignore
2162
+ return mv
2163
+
2164
+ return value
2165
+
2166
+ #############################################################################
2167
+ # Value handlers
2168
+
2169
+ def v_args(self, v):
2170
+ return ECValue(type=str, content=json.dumps(self.program.argv))
2171
+
2172
+ def v_arg(self, v):
2173
+ index = self.textify(v['index'])
2174
+ if index >= len(self.program.argv):
2175
+ RuntimeError(self.program, 'Index exceeds # of args')
2176
+ return ECValue(type=str, content=self.program.argv[index])
2177
+
2178
+ def v_boolean(self, v):
2179
+ value = ECValue(type=bool, content=v.getContent())
2180
+
2181
+ def v_cos(self, v):
2182
+ angle = self.textify(v['angle'])
2183
+ radius = self.textify(v['radius'])
2184
+ return ECValue(type=int, content=round(math.cos(angle * 0.01745329) * radius))
2185
+
2186
+ def v_count(self, v):
2187
+ content = self.textify(self.getVariable(v.getContent()))
2188
+ if content == None: raise RuntimeError(self.program, 'Count: No value provided')
2189
+ return ECValue(type=int, content=len(content))
2190
+
2191
+ def v_datime(self, v):
2192
+ ts = self.textify(v.timestamp)
2193
+ fmt = v.format
2194
+ if fmt == None:
2195
+ fmt = '%b %d %Y %H:%M:%S'
2196
+ else:
2197
+ fmt = self.textify(fmt)
2198
+ return ECValue(type=str, content=datetime.fromtimestamp(ts/1000).strftime(fmt))
2199
+
2200
+ def v_decode(self, v):
2201
+ content = self.textify(v.getContent())
2202
+ value = ECValue(type=str)
2203
+ if self.encoding == 'utf-8':
2204
+ value.setContent(content.decode('utf-8'))
2205
+ elif self.encoding == 'base64':
2206
+ base64_bytes = content.encode('ascii')
2207
+ message_bytes = base64.b64decode(base64_bytes)
2208
+ value.setContent(message_bytes.decode('ascii'))
2209
+ elif self.encoding == 'hex':
2210
+ hex_bytes = content.encode('utf-8')
2211
+ message_bytes = binascii.unhexlify(hex_bytes)
2212
+ value.setContent(message_bytes.decode('utf-8'))
2213
+ else:
2214
+ value = v
2215
+ return value
2216
+
2217
+ def v_element(self, v):
2218
+ index = self.textify(v.index)
2219
+ targetName = v.target
2220
+ target = self.getVariable(targetName.getContent())
2221
+ variable = self.getObject(target)
2222
+ self.checkObjectType(variable, ECList)
2223
+ content = variable.getContent()
2224
+ if not type(content) == list:
2225
+ RuntimeError(self.program, f'{targetName} is not a list')
2226
+ if index >= len(content):
2227
+ RuntimeError(self.program, f'Index out of range in {targetName}')
2228
+ targetValue = content[index]
2229
+ if isinstance(targetValue, ECValue):
2230
+ targetValue = self.textify(targetValue)
2231
+ return targetValue
2232
+
2233
+ def v_elements(self, v):
2234
+ var = self.getVariable(v.name)
2235
+ object = var['object']
2236
+ self.checkObjectType(object, ECVariable)
2237
+ return ECValue(type=int, content=object.getElements())
2238
+
2239
+ def v_empty(self, v):
2240
+ return ECValue(type=str, content='' )
2241
+
2242
+ def v_encode(self, v):
2243
+ content = self.textify(v.getContent())
2244
+ value = ECValue(type=str)
2245
+ if self.encoding == 'utf-8':
2246
+ value.setContent(content.encode('utf-8'))
2247
+ elif self.encoding == 'base64':
2248
+ data_bytes = content.encode('ascii')
2249
+ base64_bytes = base64.b64encode(data_bytes)
2250
+ value.setContent(base64_bytes.decode('ascii'))
2251
+ elif self.encoding == 'hex':
2252
+ data_bytes = content.encode('utf-8')
2253
+ hex_bytes = binascii.hexlify(data_bytes)
2254
+ value.setContent(hex_bytes.decode('utf-8'))
2255
+ else:
2256
+ value = v
2257
+ return value
2258
+
2259
+ def v_entry(self, v):
2260
+ record = self.getVariable(v.target)
2261
+ dictionary = self.getObject(record)
2262
+ return dictionary.getEntry(self.textify(v.key))
2263
+
2264
+ def v_error(self, v):
2265
+ global errorCode, errorReason
2266
+ value = ECValue()
2267
+ item = v.item
2268
+ if item == 'errorCode':
2269
+ value.setValue(type=int, content=errorCode)
2270
+ elif item == 'errorReason':
2271
+ value.setValue(type=str, content=errorReason)
2272
+ elif item == 'sshError':
2273
+ record = self.getVariable(v.name)
2274
+ value.setValue(type=str, content=record['error'] if 'error' in record else '')
2275
+ return value
2276
+
2277
+ def v_files(self, v):
2278
+ path = self.textify(v.target)
2279
+ return ECValue(type=str, content=json.dumps(os.listdir(path)))
2280
+
2281
+ def v_float(self, v):
2282
+ val = self.textify(v.getContent())
2283
+ value = ECValue(type=float)
2284
+ try:
2285
+ value.setContent(float(val))
2286
+ except:
2287
+ RuntimeWarning(self.program, f'Value cannot be parsed as floating-point')
2288
+ value.setContent(0.0)
2289
+ return value
2290
+
2291
+ def v_from(self, v):
2292
+ content = self.textify(v.getContent())
2293
+ start = self.textify(v.start)
2294
+ to = self.textify(v.to)
2295
+ if start is not None and type(start) != int:
2296
+ RuntimeError(self.program, 'Invalid "from" value')
2297
+ if to is not None and type(to) != int:
2298
+ RuntimeError(self.program, 'Invalid "to" value')
2299
+ return ECValue(type=str, content=content[start:] if to == None else content[start:to])
2300
+
2301
+ def v_hash(self, v):
2302
+ hashval = self.textify(v.getContent())
2303
+ return ECValue(type=str, content=hashlib.sha256(hashval.encode('utf-8')).hexdigest())
2304
+
2305
+ def v_index(self, v):
2306
+ record = self.getVariable(v.name)
2307
+ object = self.getObject(record)
2308
+ return ECValue(type=int, content=object.getIndex())
2309
+
2310
+ def v_indexOf(self, v):
2311
+ value = v.value
2312
+ if value == None:
2313
+ var = self.getObject(self.getVariable(v.variable))
2314
+ value = var.getContent()
2315
+ else:
2316
+ value = self.textify(value)
2317
+ target = self.getObject(self.getVariable(v.target))
2318
+ if hasattr(target, 'getIndexOf'):
2319
+ index = target.getIndexOf(value)
2320
+ else:
2321
+ data = target.getContent()
2322
+ try: index = data.index(value)
2323
+ except: index = -1
2324
+ return ECValue(type=int, content=index)
2325
+
2326
+ def v_integer(self, v):
2327
+ val = self.textify(v.getValue())
2328
+ return ECValue(type=int, content=int(val))
2329
+
2330
+ def v_json(self, v):
2331
+ item = self.textify(v.getContent())
2332
+ value = ECValue()
2333
+ try:
2334
+ v = json.loads(item)
2335
+ if type(v) == list: value.setType('list')
2336
+ elif type(v) == dict: value.setType('dict')
2337
+ else: value.setType(str)
2338
+ value.setContent(v)
2339
+ except:
2340
+ value = None
2341
+ return value
2342
+
2343
+ def v_keys(self, v):
2344
+ dictionary = self.getObject(self.getVariable(v.name))
2345
+ return ECValue(type='list', content=list(dictionary.keys())) # type: ignore
2346
+
2347
+ def v_left(self, v):
2348
+ content = self.textify(v.getContent())
2349
+ count = self.textify(v.count)
2350
+ return ECValue(type=str, content=content[0:count])
2351
+
2352
+ def v_lengthOf(self, v):
2353
+ content = self.textify(v.getContent())
2354
+ if type(content) == str:
2355
+ return ECValue(type=int, content=len(content))
2356
+ RuntimeError(self.program, 'Value is not a string')
2357
+
2358
+ def v_lowercase(self, v):
2359
+ content = self.textify(v.getValue())
2360
+ return ECValue(type=str, content=content.lower())
2361
+
2362
+ def v_message(self, v):
2363
+ return ECValue(type=str, content=self.program.message)
2364
+
2365
+ def v_modification(self, v):
2366
+ fileName = self.textify(v['fileName'])
2367
+ ts = int(os.stat(fileName).st_mtime)
2368
+ return ECValue(type=int, content=ts)
2369
+
2370
+ def v_modulo(self, v):
2371
+ val = self.textify(v.getContent())
2372
+ modval = self.textify(v.modval)
2373
+ return ECValue(type=int, content=val % modval)
2374
+
2375
+ def v_newline(self, v):
2376
+ return ECValue(type=str, content='\n')
2377
+
2378
+ def v_now(self, v):
2379
+ return ECValue(type=int, content=int(time.time()))
2380
+
2381
+ def v_position(self, v):
2382
+ needle = self.textify(v.needle)
2383
+ haystack = self.textify(v.haystack)
2384
+ last = v.last
2385
+ return ECValue(type=int, content=haystack.rfind(needle) if last else haystack.find(needle))
2386
+
2387
+ def v_prettify(self, v):
2388
+ item = self.textify(v.getContent())
2389
+ if isinstance(item, str): item = json.loads(item)
2390
+ return ECValue(type=str, content=json.dumps(item, indent=4))
2391
+
2392
+ def v_property(self, v):
2393
+ propertyName = v.name
2394
+ propertyValue = self.textify(propertyName)
2395
+ targetName = v.target
2396
+ target = self.getVariable(targetName.getContent())
2397
+ variable = self.getObject(target)
2398
+ return variable.getProperty(propertyValue)
2399
+
2400
+ def v_random(self, v):
2401
+ limit = self.textify(v.getValue())
2402
+ return ECValue(type=int, content=random.randrange(0, limit))
2403
+
2404
+ def v_right(self, v):
2405
+ content = self.textify(v.getContent())
2406
+ count = self.textify(v.count)
2407
+ return ECValue(type=str, content=content[-count:])
2408
+
2409
+ def v_sin(self, v):
2410
+ angle = self.textify(v.angle)
2411
+ radius = self.textify(v.radius)
2412
+ return ECValue(type=int, content=round(math.sin(angle * 0.01745329) * radius))
2413
+
2414
+ def v_stringify(self, v):
2415
+ item = self.textify(v.getContent())
2416
+ item = json.loads(item)
2417
+ return ECValue(type=str, content=json.dumps(item))
2418
+
2419
+ # This is used by the expression evaluator to get the value of a symbol
2420
+ def v_symbol(self, v):
2421
+ name = v.name
2422
+ record = self.program.getSymbolRecord(name)
2423
+ keyword = record['keyword']
2424
+ if keyword == 'object':
2425
+ return record['object'].getValue()
2426
+ elif keyword == 'variable':
2427
+ return self.getSymbolValue(record)
2428
+ elif keyword == 'ssh':
2429
+ return ECValue(type=bool, content=True if 'ssh' in record and record['ssh'] != None else False)
2430
+ else:
2431
+ return None
2432
+
2433
+ def v_system(self, v):
2434
+ command = self.textify(v.getContent())
2435
+ result = os.popen(command).read()
2436
+ return ECValue(type=str, content=result)
2437
+
2438
+ def v_tab(self, v):
2439
+ return ECValue(type=str, content='\t')
2440
+
2441
+ def v_tan(self, v):
2442
+ angle = self.textify(v['angle'])
2443
+ radius = self.textify(v['radius'])
2444
+ return ECValue(type=int, content=round(math.tan(angle * 0.01745329) * radius))
2445
+
2446
+ def v_ticker(self, v):
2447
+ return ECValue(type=int, content=self.program.ticker)
2448
+
2449
+ def v_timestamp(self, v):
2450
+ value = ECValue(type=int)
2451
+ fmt = v.format
2452
+ if fmt == None:
2453
+ value.setContent(int(time.time()))
2454
+ else:
2455
+ fmt = self.textify(fmt)
2456
+ dt = self.textify(v.timestamp)
2457
+ spec = datetime.strptime(dt, fmt)
2458
+ t = datetime.now().replace(hour=spec.hour, minute=spec.minute, second=spec.second, microsecond=0)
2459
+ value.setContent(int(t.timestamp()))
2460
+ return value
2461
+
2462
+ def v_today(self, v):
2463
+ return ECValue(type=int, content=int(datetime.combine(datetime.now().date(),datetime.min.time()).timestamp()) * 1000)
2464
+
2465
+ def v_trim(self, v):
2466
+ content = v.getContent()
2467
+ content = self.textify(content)
2468
+ return ECValue(type=str, content=content.strip())
2469
+
2470
+ def v_type(self, v):
2471
+ value = ECValue(type=str)
2472
+ val = self.textify(v['value'])
2473
+ if val is None:
2474
+ value.setContent('none')
2475
+ elif type(val) is str:
2476
+ value.setContent(str)
2477
+ elif type(val) is int:
2478
+ value.setContent('numeric')
2479
+ elif type(val) is bool:
2480
+ value.setContent(bool)
2481
+ elif type(val) is list:
2482
+ value.setContent('list')
2483
+ elif type(val) is dict:
2484
+ value.setContent('dict')
2485
+ return value
2486
+
2487
+ def v_uppercase(self, v):
2488
+ content = self.textify(v.getContent())
2489
+ return ECValue(type=str, content=content.upper())
2490
+
2491
+ def v_valueOf(self, v):
2492
+ v = self.textify(v.getContent())
2493
+ return ECValue(type=int, content=int(v) if v != '' else 0)
2494
+
2495
+ def v_variable(self, v):
2496
+ name = v.getContent()
2497
+ record = self.program.getSymbolRecord(name)
2498
+ variable = record['object']
2499
+ self.checkObjectType(variable, ECVariable)
2500
+ value = variable.getValue()
2501
+ return value
2502
+
2503
+ def v_weekday(self, v):
2504
+ return ECValue(type=int, content=datetime.today().weekday())
2505
+
2506
+ #############################################################################
2507
+ # Compile a condition
2508
+ def compileCondition(self):
2509
+ condition = ECValue()
2510
+ condition.negate = False # type: ignore
2511
+
2512
+ token = self.getToken()
2513
+
2514
+ if token == 'not':
2515
+ condition.type = 'not' # type: ignore
2516
+ condition.value = self.nextValue() # type: ignore
2517
+ return condition
2518
+
2519
+ elif token == 'error':
2520
+ self.nextToken()
2521
+ self.skip('in')
2522
+ if self.nextIsSymbol():
2523
+ record = self.getSymbolRecord()
2524
+ if record['keyword'] == 'ssh':
2525
+ condition.type = 'sshError' # type: ignore
2526
+ condition.target = record['name'] # type: ignore
2527
+ return condition
2528
+ return None
2529
+
2530
+ elif token == 'file':
2531
+ path = self.nextValue()
2532
+ condition.path = path # type: ignore
2533
+ condition.type = 'exists' # type: ignore
2534
+ self.skip('on')
2535
+ if self.nextIsSymbol():
2536
+ record = self.getSymbolRecord()
2537
+ if record['keyword'] == 'ssh':
2538
+ condition.type = 'sshExists' # type: ignore
2539
+ condition.target = record['name'] # type: ignore
2540
+ token = self.nextToken()
2541
+ else: token = self.getToken()
2542
+ if token == 'exists':
2543
+ return condition
2544
+ elif token == 'does':
2545
+ if self.nextIs('not'):
2546
+ if self.nextIs('exist'):
2547
+ condition.negate = not condition.negate # type: ignore
2548
+ return condition
2549
+ return None
2550
+
2551
+ value = self.getValue()
2552
+ if value == None:
2553
+ return None
2554
+
2555
+ condition.value1 = value # type: ignore
2556
+ token = self.peek()
2557
+ condition.type = token # type: ignore
2558
+
2559
+ if token == 'has':
2560
+ self.nextToken()
2561
+ token = self.nextToken()
2562
+ if token in ('entry', 'property:'):
2563
+ value = self.nextValue()
2564
+ if token == 'entry':
2565
+ condition.type = 'hasEntry' # type: ignore
2566
+ condition.entry = value # type: ignore
2567
+ elif token == 'property:':
2568
+ condition.type = 'hasProperty' # type: ignore
2569
+ condition.property = value # type: ignore
2570
+ return condition
2571
+ return None
2572
+
2573
+ if token == 'does':
2574
+ self.nextToken()
2575
+ if self.nextIs('not'):
2576
+ token = self.nextToken()
2577
+ if token == 'have':
2578
+ token = self.nextToken()
2579
+ if token in ('entry', 'property:'):
2580
+ value = self.nextValue()
2581
+ if token == 'entry':
2582
+ condition.type = 'hasEntry' # type: ignore
2583
+ condition.entry = value # type: ignore
2584
+ elif token == 'property':
2585
+ condition.type = 'hasProperty' # type: ignore
2586
+ condition.property = value # type: ignore
2587
+ condition.negate = not condition.negate # type: ignore
2588
+ return condition
2589
+ elif token == 'include':
2590
+ value = self.nextValue()
2591
+ condition.type = 'includes' # type: ignore
2592
+ condition.value2 = value # type: ignore
2593
+ condition.negate = not condition.negate # type: ignore
2594
+ return condition
2595
+ return None
2596
+
2597
+ if token in ['starts', 'ends']:
2598
+ self.nextToken()
2599
+ if self.nextToken() == 'with':
2600
+ condition.value2 = self.nextValue() # type: ignore
2601
+ return condition
2602
+
2603
+ if token == 'includes':
2604
+ condition.value2 = self.nextValue() # type: ignore
2605
+ return condition
2606
+
2607
+ if token == 'is':
2608
+ token = self.nextToken()
2609
+ if self.peek() == 'not':
2610
+ self.nextToken()
2611
+ condition.negate = True # type: ignore
2612
+ token = self.nextToken()
2613
+ condition.type = token # type: ignore
2614
+ if token in ['numeric', 'string', bool, 'none', 'list', 'object', 'even', 'odd', 'empty']:
2615
+ return condition
2616
+ if token in ['greater', 'less']:
2617
+ if self.nextToken() == 'than':
2618
+ condition.value2 = self.nextValue() # type: ignore
2619
+ return condition
2620
+ condition.type = 'is' # type: ignore
2621
+ condition.value2 = self.getValue() # type: ignore
2622
+ return condition
2623
+
2624
+ if condition.value1: # type: ignore
2625
+ # It's a boolean if
2626
+ condition.type = bool # type: ignore
2627
+ return condition
2628
+
2629
+ self.warning(f'Core.compileCondition: I can\'t get a conditional:')
2630
+ return None
2631
+
2632
+ def isNegate(self):
2633
+ token = self.getToken()
2634
+ if token == 'not':
2635
+ self.nextToken()
2636
+ return True
2637
+ return False
2638
+
2639
+ #############################################################################
2640
+ # Condition handlers
2641
+
2642
+ def c_boolean(self, condition):
2643
+ value = self.textify(condition.value1)
2644
+ if type(value) == bool:
2645
+ return not value if condition.negate else value
2646
+ elif type(value) == int:
2647
+ return True if condition.negate else False
2648
+ elif type(value) == str:
2649
+ if value.lower() == 'true':
2650
+ return False if condition.negate else True
2651
+ elif value.lower() == 'false':
2652
+ return True if condition.negate else False
2653
+ else:
2654
+ return True if condition.negate else False
2655
+ return False
2656
+
2657
+ def c_empty(self, condition):
2658
+ if condition.value1.getType() == 'symbol':
2659
+ record = self.getVariable(condition.value1.content)
2660
+ variable = self.getObject(record)
2661
+ if isinstance(variable, (ECList, ECDictionary)):
2662
+ comparison = variable.isEmpty()
2663
+ return not comparison if condition.negate else comparison
2664
+ value = self.textify(condition.value1)
2665
+ if value == None:
2666
+ comparison = True
2667
+ elif isinstance(value, str):
2668
+ comparison = len(value) == 0
2669
+ else:
2670
+ domainName = condition.value1.domain
2671
+ domain = self.program.domainIndex[domainName] # type: ignore
2672
+ handler = domain.valueHandler('empty') # type: ignore
2673
+ if handler: comparison = self.textify(handler(condition.value1))
2674
+ return not comparison if condition.negate else comparison
2675
+
2676
+ def c_ends(self, condition):
2677
+ value1 = self.textify(condition.value1)
2678
+ value2 = self.textify(condition.value2)
2679
+ return value1.endswith(value2)
2680
+
2681
+ def c_even(self, condition):
2682
+ return self.textify(condition.value1) % 2 == 0
2683
+
2684
+ def c_exists(self, condition):
2685
+ path = self.textify(condition.path)
2686
+ comparison = os.path.exists(path)
2687
+ return not comparison if condition.negate else comparison
2688
+
2689
+ def c_greater(self, condition):
2690
+ comparison = self.program.compare(condition.value1, condition.value2)
2691
+ if comparison == None:
2692
+ raise RuntimeError(self.program, f'Cannot compare {self.textify(condition.value1)} and {self.textify(condition.value2)}')
2693
+ return comparison <= 0 if condition.negate else comparison > 0
2694
+
2695
+ def c_hasEntry(self, condition):
2696
+ dictionary = self.getObject(self.getVariable(condition.value1.content))
2697
+ entry = self.textify(condition.entry)
2698
+ hasEntry = dictionary.hasEntry(entry) # type: ignore
2699
+ return not hasEntry if condition.negate else hasEntry
2700
+
2701
+ def c_hasProperty(self, condition):
2702
+ record = self.getVariable(condition.value1)
2703
+ variable = self.getObject(record)
2704
+ prop = self.textify(condition.property)
2705
+ hasProp = variable.hasProperty(prop)
2706
+ return not hasProp if condition.negate else hasProp
2707
+
2708
+ def c_includes(self, condition):
2709
+ value1 = self.textify(condition.value1)
2710
+ value2 = self.textify(condition.value2)
2711
+ includes = value2 in value1
2712
+ return not includes if condition.negate else includes
2713
+
2714
+ def c_is(self, condition):
2715
+ comparison = self.program.compare(condition.value1, condition.value2)
2716
+ if comparison == None: comparison = 1
2717
+ return comparison != 0 if condition.negate else comparison == 0
2718
+
2719
+ def c_less(self, condition):
2720
+ comparison = self.program.compare(condition.value1, condition.value2)
2721
+ if comparison == None:
2722
+ raise RuntimeError(self.program, f'Cannot compare {self.textify(condition.value1)} and {self.textify(condition.value2)}')
2723
+ return comparison >= 0 if condition.negate else comparison < 0
2724
+
2725
+ def c_list(self, condition):
2726
+ comparison = type(self.textify(condition.value1)) is list
2727
+ return not comparison if condition.negate else comparison
2728
+
2729
+ def c_numeric(self, condition):
2730
+ comparison = type(self.textify(condition.value1)) is int
2731
+ return not comparison if condition.negate else comparison
2732
+
2733
+ def c_none(self, condition):
2734
+ comparison = self.textify(condition.value1) is None
2735
+ return not comparison if condition.negate else comparison
2736
+
2737
+ def c_not(self, condition):
2738
+ return not self.textify(condition.value)
2739
+
2740
+ def c_object(self, condition):
2741
+ comparison = type(self.textify(condition.value1)) is dict
2742
+ return not comparison if condition.negate else comparison
2743
+
2744
+ def c_odd(self, condition):
2745
+ return self.textify(condition.value1) % 2 == 1
2746
+
2747
+ def c_sshError(self, condition):
2748
+ target = self.getVariable(condition.target)
2749
+ errormsg = target['error'] if 'error' in target else None
2750
+ condition.errormsg = errormsg
2751
+ test = errormsg != None
2752
+ return not test if condition.negate else test
2753
+
2754
+ def c_sshExists(self, condition):
2755
+ path = self.textify(condition.path)
2756
+ ssh = self.getVariable(condition.target)
2757
+ sftp = ssh['sftp']
2758
+ try:
2759
+ with sftp.open(path, 'r') as remote_file: remote_file.read().decode()
2760
+ comparison = True
2761
+ except:
2762
+ comparison = False
2763
+ return not comparison if condition.negate else comparison
2764
+
2765
+ def c_starts(self, condition):
2766
+ value1 = self.textify(condition.value1)
2767
+ value2 = self.textify(condition.value2)
2768
+ return value1.startswith(value2)
2769
+
2770
+ def c_string(self, condition):
2771
+ comparison = type(self.textify(condition.value1)) is str
2772
+ return not comparison if condition.negate else comparison