easycoder 250118.1__py2.py3-none-any.whl → 251103.4__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.
easycoder/ec_core.py CHANGED
@@ -1,8 +1,9 @@
1
- import json, math, hashlib, threading, os, subprocess, sys, requests, time, numbers, base64
1
+ import json, math, hashlib, threading, os, subprocess, time
2
+ import numbers, base64, binascii, random, requests, paramiko
3
+ from copy import deepcopy
2
4
  from psutil import Process
3
- from datetime import datetime, timezone
4
- from random import randrange
5
- from .ec_classes import FatalError, RuntimeWarning, RuntimeError, AssertionError, Condition
5
+ from datetime import datetime
6
+ from .ec_classes import FatalError, RuntimeWarning, RuntimeError, AssertionError, NoValueError, NoValueRuntimeError, Object
6
7
  from .ec_handler import Handler
7
8
  from .ec_timestamp import getTimestamp
8
9
 
@@ -14,6 +15,29 @@ class Core(Handler):
14
15
 
15
16
  def getName(self):
16
17
  return 'core'
18
+
19
+ def noSymbolWarning(self):
20
+ self.warning(f'Symbol "{self.getToken()}" not found')
21
+
22
+ def processOr(self, command, orHere):
23
+ self.add(command)
24
+ if self.peek() == 'or':
25
+ self.nextToken()
26
+ self.nextToken()
27
+ # Add a 'goto' to skip the 'or'
28
+ cmd = {}
29
+ cmd['lino'] = command['lino']
30
+ cmd['domain'] = 'core'
31
+ cmd['keyword'] = 'gotoPC'
32
+ cmd['goto'] = 0
33
+ cmd['debug'] = False
34
+ skip = self.getPC()
35
+ self.add(cmd)
36
+ # Process the 'or'
37
+ self.getCommandAt(orHere)['or'] = self.getPC()
38
+ self.compileOne()
39
+ # Fixup the skip
40
+ self.getCommandAt(skip)['goto'] = self.getPC()
17
41
 
18
42
  #############################################################################
19
43
  # Keyword handlers
@@ -26,7 +50,7 @@ class Core(Handler):
26
50
  if self.nextToken() == 'to':
27
51
  if self.nextIsSymbol():
28
52
  symbolRecord = self.getSymbolRecord()
29
- if symbolRecord['valueHolder']:
53
+ if symbolRecord['hasValue']:
30
54
  if self.peek() == 'giving':
31
55
  # This variable must be treated as a second value
32
56
  command['value2'] = self.getValue()
@@ -57,7 +81,7 @@ class Core(Handler):
57
81
  except:
58
82
  value2 = None
59
83
  target = self.getVariable(command['target'])
60
- if not target['valueHolder']:
84
+ if not target['hasValue']:
61
85
  self.variableDoesNotHoldAValueError(target['name'])
62
86
  targetValue = self.getSymbolValue(target)
63
87
  if targetValue == None:
@@ -87,11 +111,11 @@ class Core(Handler):
87
111
  if self.nextIs('to'):
88
112
  if self.nextIsSymbol():
89
113
  symbolRecord = self.getSymbolRecord()
90
- if symbolRecord['valueHolder']:
114
+ if symbolRecord['hasValue']:
91
115
  command['target'] = symbolRecord['name']
92
116
  self.add(command)
93
117
  return True
94
- self.warning(f'Core.append: Variable "{symbolRecord["name"]}" does not hold a value')
118
+ self.warning(f'Core.append: Variable {symbolRecord["name"]} does not hold a value')
95
119
  return False
96
120
 
97
121
  def r_append(self, command):
@@ -106,14 +130,6 @@ class Core(Handler):
106
130
  self.putSymbolValue(target, val)
107
131
  return self.nextPC()
108
132
 
109
- # Define an array
110
- def k_array(self, command):
111
- return self.compileVariable(command)
112
-
113
- def r_array(self, command):
114
- return self.nextPC()
115
-
116
- # Assertion
117
133
  #assert {condition} [with {message}]
118
134
  def k_assert(self, command):
119
135
  command['test'] = self.nextCondition()
@@ -122,7 +138,7 @@ class Core(Handler):
122
138
  command['with'] = self.nextValue()
123
139
  else:
124
140
  command['with'] = None
125
- self.addCommand(command)
141
+ self.add(command)
126
142
  return True
127
143
 
128
144
  def r_assert(self, command):
@@ -139,29 +155,30 @@ class Core(Handler):
139
155
  cmd['keyword'] = 'end'
140
156
  cmd['debug'] = True
141
157
  cmd['lino'] = command['lino']
142
- self.addCommand(cmd)
158
+ self.add(cmd)
143
159
  return self.nextPC()
144
160
  else:
145
161
  return self.compileFromHere(['end'])
146
162
 
147
- # Clear (set False)
148
163
  # clear {variable}
149
164
  def k_clear(self, command):
150
165
  if self.nextIsSymbol():
151
166
  target = self.getSymbolRecord()
152
- if target['valueHolder']:
153
- command['target'] = target['name']
167
+ command['target'] = target['name']
168
+ if target['hasValue'] or target['keyword'] == 'ssh':
154
169
  self.add(command)
155
170
  return True
156
171
  return False
157
172
 
158
173
  def r_clear(self, command):
159
174
  target = self.getVariable(command['target'])
160
- val = {}
161
- val['type'] = 'boolean'
162
- val['content'] = False
163
- self.putSymbolValue(target, val)
164
- # self.add(command)
175
+ if target['keyword'] == 'ssh':
176
+ target['ssh'] = None
177
+ else:
178
+ val = {}
179
+ val['type'] = 'boolean'
180
+ val['content'] = False
181
+ self.putSymbolValue(target, val)
165
182
  return self.nextPC()
166
183
 
167
184
  # Close a file
@@ -200,7 +217,11 @@ class Core(Handler):
200
217
  # Debug the script
201
218
  def k_debug(self, command):
202
219
  token = self.peek()
203
- if token in ['step', 'stop', 'program', 'custom']:
220
+ if token == 'compile':
221
+ self.compiler.debugCompile = True
222
+ self.nextToken()
223
+ return True
224
+ elif token in ['step', 'stop', 'program', 'custom']:
204
225
  command['mode'] = token
205
226
  self.nextToken()
206
227
  elif token == 'stack':
@@ -220,7 +241,9 @@ class Core(Handler):
220
241
  return True
221
242
 
222
243
  def r_debug(self, command):
223
- if command['mode'] == 'step':
244
+ if command['mode'] == 'compile':
245
+ self.program.debugStep = True
246
+ elif command['mode'] == 'step':
224
247
  self.program.debugStep = True
225
248
  elif command['mode'] == 'stop':
226
249
  self.program.debugStep = False
@@ -245,46 +268,48 @@ class Core(Handler):
245
268
  def k_decrement(self, command):
246
269
  if self.nextIsSymbol():
247
270
  symbolRecord = self.getSymbolRecord()
248
- if symbolRecord['valueHolder']:
271
+ if symbolRecord['hasValue']:
249
272
  command['target'] = self.getToken()
250
273
  self.add(command)
251
274
  return True
252
- self.warning(f'Core.decrement: Variable "{symbolRecord["name"]}" does not hold a value')
275
+ self.warning(f'Core.decrement: Variable {symbolRecord["name"]} does not hold a value')
253
276
  return False
254
277
 
255
278
  def r_decrement(self, command):
256
279
  return self.incdec(command, '-')
257
280
 
258
281
  # Delete a file or a property
259
- # delete {filename}
282
+ # delete file {filename}
260
283
  # delete property {value} of {variable}
284
+ # delete element {name} of {variable}
261
285
  def k_delete(self, command):
262
286
  token = self.nextToken( )
287
+ command['type'] = token
263
288
  if token == 'file':
264
- command['type'] = 'file'
265
289
  command['filename'] = self.nextValue()
266
290
  self.add(command)
267
291
  return True
268
- elif token == 'property':
269
- command['key'] = self.nextValue();
270
- if self.nextIs('of'):
271
- command['type'] = 'property'
272
- command['var'] = self.nextToken()
273
- self.add(command)
274
- return True
275
- else:
276
- self.warning(f'Core.delete: "of" expected; got {self.getToken()}')
292
+ elif token in ['property', 'element']:
293
+ command['key'] = self.nextValue()
294
+ self.skip('of')
295
+ if self.nextIsSymbol():
296
+ record = self.getSymbolRecord()
297
+ if record['hasValue']:
298
+ command['var'] = record['name']
299
+ self.add(command)
300
+ return True
301
+ NoValueError(self.compiler, record)
302
+ self.warning(f'Core.delete: variable expected; got {self.getToken()}')
277
303
  else:
278
- self.warning(f'Core.delete: "file" or "property" expected; got {token}')
304
+ self.warning(f'Core.delete: "file", "property" or "element" expected; got {token}')
279
305
  return False
280
306
 
281
307
  def r_delete(self, command):
282
308
  type = command['type']
283
309
  if type == 'file':
284
310
  filename = self.getRuntimeValue(command['filename'])
285
- if os.path.isfile(filename):
286
- print('Deleting',filename)
287
- os.remove(filename)
311
+ if filename != None:
312
+ if os.path.isfile(filename): os.remove(filename)
288
313
  elif type == 'property':
289
314
  key = self.getRuntimeValue(command['key'])
290
315
  symbolRecord = self.getVariable(command['var'])
@@ -293,6 +318,15 @@ class Core(Handler):
293
318
  content.pop(key, None)
294
319
  value['content'] = content
295
320
  self.putSymbolValue(symbolRecord, value)
321
+ elif type == 'element':
322
+ key = self.getRuntimeValue(command['key'])
323
+ symbolRecord = self.getVariable(command['var'])
324
+ value = self.getSymbolValue(symbolRecord)
325
+ content = value['content']
326
+ if key >= 0 and key < len(content): del(content[key])
327
+ else: RuntimeError(self.program, f'Index {key} out of range')
328
+ value['content'] = content
329
+ self.putSymbolValue(symbolRecord, value)
296
330
  return self.nextPC()
297
331
 
298
332
  # Arithmetic division
@@ -308,7 +342,7 @@ class Core(Handler):
308
342
  command['target'] = self.getToken()
309
343
  self.add(command)
310
344
  return True
311
- FatalError(self.program.compiler, 'Symbol expected')
345
+ FatalError(self.compiler, 'Symbol expected')
312
346
  else:
313
347
  # First value must be a variable
314
348
  if command['value1']['type'] == 'symbol':
@@ -325,7 +359,7 @@ class Core(Handler):
325
359
  except:
326
360
  value2 = None
327
361
  target = self.getVariable(command['target'])
328
- if not target['valueHolder']:
362
+ if not target['hasValue']:
329
363
  self.variableDoesNotHoldAValueError(target['name'])
330
364
  return None
331
365
  value = self.getSymbolValue(target)
@@ -345,6 +379,29 @@ class Core(Handler):
345
379
  self.putSymbolValue(target, value)
346
380
  return self.nextPC()
347
381
 
382
+ # download [binary] {url} to {path}
383
+ def k_download(self, command):
384
+ if self.nextIs('binary'):
385
+ command['binary'] = True
386
+ self.nextToken()
387
+ else: command['binary'] = False
388
+ command['url'] = self.getValue()
389
+ self.skip('to')
390
+ command['path'] = self.nextValue()
391
+ self.add(command)
392
+ return True
393
+
394
+ def r_download(self, command):
395
+ binary = command['binary']
396
+ url = self.getRuntimeValue(command['url'])
397
+ path = self.getRuntimeValue(command['path'])
398
+ mode = 'wb' if binary else 'w'
399
+ response = requests.get(url, stream=True)
400
+ with open(path, mode) as f:
401
+ for chunk in response.iter_content(chunk_size=8192):
402
+ if chunk: f.write(chunk)
403
+ return self.nextPC()
404
+
348
405
  # Dummy command for testing
349
406
  def k_dummy(self, command):
350
407
  self.add(command)
@@ -367,11 +424,13 @@ class Core(Handler):
367
424
  return True
368
425
 
369
426
  def r_exit(self, command):
427
+ if self.program.parent == None and self.program.graphics != None:
428
+ self.program.graphics.force_exit(None)
370
429
  return -1
371
430
 
372
431
  # Declare a file variable
373
432
  def k_file(self, command):
374
- return self.compileVariable(command, False)
433
+ return self.compileVariable(command)
375
434
 
376
435
  def r_file(self, command):
377
436
  return self.nextPC()
@@ -395,46 +454,32 @@ class Core(Handler):
395
454
  self.run(label)
396
455
  return next
397
456
 
398
- # Issue a REST GET request
399
457
  # get {variable) from {url} [or {command}]
400
458
  def k_get(self, command):
401
459
  if self.nextIsSymbol():
402
460
  symbolRecord = self.getSymbolRecord()
403
- if symbolRecord['valueHolder']:
461
+ if symbolRecord['hasValue']:
404
462
  command['target'] = self.getToken()
405
463
  else:
406
- FatalError(self.compiler, f'Variable "{symbolRecord["name"]}" does not hold a value')
464
+ NoValueError(self.compiler, symbolRecord)
407
465
  if self.nextIs('from'):
408
- command['url'] = self.nextValue()
409
- command['or'] = None
410
- get = self.getPC()
411
- if self.peek() == 'timeout':
412
- self.nextToken()
413
- command['timeout'] = self.nextValue()
414
- else:
415
- timeout = {}
416
- timeout['type'] = 'int'
417
- timeout['content'] = 5
418
- command['timeout'] = timeout
419
- self.addCommand(command)
420
- if self.peek() == 'or':
421
- self.nextToken()
422
- self.nextToken()
423
- # Add a 'goto' to skip the 'or'
424
- cmd = {}
425
- cmd['lino'] = command['lino']
426
- cmd['domain'] = 'core'
427
- cmd['keyword'] = 'gotoPC'
428
- cmd['goto'] = 0
429
- cmd['debug'] = False
430
- skip = self.getPC()
431
- self.addCommand(cmd)
432
- # Process the 'or'
433
- self.getCommandAt(get)['or'] = self.getPC()
434
- self.compileOne()
435
- # Fixup the skip
436
- self.getCommandAt(skip)['goto'] = self.getPC()
437
- return True
466
+ if self.nextIs('url'):
467
+ url = self.nextValue()
468
+ if url != None:
469
+ command['url'] = url
470
+ command['or'] = None
471
+ get = self.getPC()
472
+ if self.peek() == 'timeout':
473
+ self.nextToken()
474
+ command['timeout'] = self.nextValue()
475
+ else:
476
+ timeout = {}
477
+ timeout['type'] = 'int'
478
+ timeout['content'] = 5
479
+ command['timeout'] = timeout
480
+ self.processOr(command, get)
481
+ return True
482
+ return False
438
483
 
439
484
  def r_get(self, command):
440
485
  global errorCode, errorReason
@@ -499,17 +544,17 @@ class Core(Handler):
499
544
 
500
545
  def r_gosub(self, command):
501
546
  label = command['gosub'] + ':'
502
- address = self.symbols[label]
503
- if address != None:
547
+ if label in self.symbols:
548
+ address = self.symbols[label]
504
549
  self.stack.append(self.nextPC())
505
550
  return address
506
- RuntimeError(self.program, f'There is no label "{label + ":"}"')
551
+ RuntimeError(self.program, f'There is no label "{label}"')
507
552
  return None
508
553
 
509
554
  # if <condition> <action> [else <action>]
510
555
  def k_if(self, command):
511
556
  command['condition'] = self.nextCondition()
512
- self.addCommand(command)
557
+ self.add(command)
513
558
  self.nextToken()
514
559
  pcElse = self.getPC()
515
560
  cmd = {}
@@ -518,7 +563,7 @@ class Core(Handler):
518
563
  cmd['keyword'] = 'gotoPC'
519
564
  cmd['goto'] = 0
520
565
  cmd['debug'] = False
521
- self.addCommand(cmd)
566
+ self.add(cmd)
522
567
  # Get the 'then' code
523
568
  self.compileOne()
524
569
  if self.peek() == 'else':
@@ -531,7 +576,7 @@ class Core(Handler):
531
576
  cmd['keyword'] = 'gotoPC'
532
577
  cmd['goto'] = 0
533
578
  cmd['debug'] = False
534
- self.addCommand(cmd)
579
+ self.add(cmd)
535
580
  # Fixup the link to the 'else' branch
536
581
  self.getCommandAt(pcElse)['goto'] = self.getPC()
537
582
  # Process the 'else' branch
@@ -552,37 +597,29 @@ class Core(Handler):
552
597
  self.program.pc += 1
553
598
  return self.program.pc
554
599
 
600
+ # Import one or more variables
555
601
  def k_import(self, command):
556
- if self.peek() == 'plugin':
557
- # Import a plugin
602
+ imports = []
603
+ while True:
604
+ keyword = self.nextToken()
605
+ name = self.nextToken()
606
+ item = [keyword, name]
607
+ imports.append(item)
608
+ self.symbols[name] = self.getPC()
609
+ variable = {}
610
+ variable['domain'] = None
611
+ variable['name'] = name
612
+ variable['keyword'] = keyword
613
+ variable['import'] = None
614
+ variable['used'] = False
615
+ variable['hasValue'] = True if keyword == 'variable' else False
616
+ self.add(variable)
617
+ if self.peek() != 'and':
618
+ break
558
619
  self.nextToken()
559
- clazz = self.nextToken()
560
- if self.nextIs('from'):
561
- source = self.nextToken()
562
- self.program.importPlugin(f'{source}:{clazz}')
563
- return True
564
- return False
565
- else:
566
- # Import one or more variables
567
- imports = []
568
- while True:
569
- keyword = self.nextToken()
570
- name = self.nextToken()
571
- item = [keyword, name]
572
- imports.append(item)
573
- self.symbols[name] = self.getPC()
574
- variable = {}
575
- variable['domain'] = None
576
- variable['name'] = name
577
- variable['keyword'] = keyword
578
- variable['import'] = None
579
- self.addCommand(variable)
580
- if self.peek() != 'and':
581
- break
582
- self.nextToken()
583
- command['imports'] = json.dumps(imports)
584
- self.add(command)
585
- return True
620
+ command['imports'] = json.dumps(imports)
621
+ self.add(command)
622
+ return True
586
623
 
587
624
  def r_import(self, command):
588
625
  exports = self.program.exports
@@ -606,11 +643,11 @@ class Core(Handler):
606
643
  def k_increment(self, command):
607
644
  if self.nextIsSymbol():
608
645
  symbolRecord = self.getSymbolRecord()
609
- if symbolRecord['valueHolder']:
646
+ if symbolRecord['hasValue']:
610
647
  command['target'] = self.getToken()
611
648
  self.add(command)
612
649
  return True
613
- self.warning(f'Core.increment: Variable "{symbolRecord["name"]}" does not hold a value')
650
+ self.warning(f'Core.increment: Variable {symbolRecord["name"]} does not hold a value')
614
651
  return False
615
652
 
616
653
  def r_increment(self, command):
@@ -686,7 +723,7 @@ class Core(Handler):
686
723
  return self.nextPC()
687
724
 
688
725
  # 1 Load a plugin. This is done at compile time.
689
- # 2 Load text from a file
726
+ # 2 Load text from a file or ssh
690
727
  def k_load(self, command):
691
728
  self.nextToken()
692
729
  if self.tokenIs('plugin'):
@@ -696,19 +733,60 @@ class Core(Handler):
696
733
  self.program.importPlugin(f'{source}:{clazz}')
697
734
  return True
698
735
  elif self.isSymbol():
699
- command['target'] = self.getToken()
700
- if self.nextIs('from'):
701
- command['file'] = self.nextValue()
702
- self.add(command)
703
- return True
736
+ symbolRecord = self.getSymbolRecord()
737
+ if symbolRecord['hasValue']:
738
+ command['target'] = symbolRecord['name']
739
+ if self.nextIs('from'):
740
+ if self.nextIsSymbol():
741
+ record = self.getSymbolRecord()
742
+ if record['keyword'] == 'ssh':
743
+ command['ssh'] = record['name']
744
+ command['path'] = self.nextValue()
745
+ else:
746
+ command['file'] = self.getValue()
747
+ else:
748
+ command['file'] = self.getValue()
749
+ command['or'] = None
750
+ load = self.getPC()
751
+ self.processOr(command, load)
752
+ return True
753
+ else:
754
+ FatalError(self.compiler, f'I don\'t understand \'{self.getToken()}\'')
704
755
  return False
705
756
 
706
757
  def r_load(self, command):
758
+ errorReason = None
707
759
  target = self.getVariable(command['target'])
708
- file = self.getRuntimeValue(command['file'])
709
- f = open(file, 'r')
710
- content = f.read()
711
- f.close()
760
+ if 'ssh' in command:
761
+ ssh = self.getVariable(command['ssh'])
762
+ path = self.getRuntimeValue(command['path'])
763
+ sftp = ssh['sftp']
764
+ try:
765
+ with sftp.open(path, 'r') as remote_file: content = remote_file.read().decode()
766
+ except:
767
+ errorReason = f'Unable to read from {path}'
768
+ if command['or'] != None:
769
+ print(f'Exception "{errorReason}": Running the "or" clause')
770
+ return command['or']
771
+ else:
772
+ RuntimeError(self.program, f'Error: {errorReason}')
773
+ else:
774
+ filename = self.getRuntimeValue(command['file'])
775
+ try:
776
+ with open(filename) as f: content = f.read()
777
+ try:
778
+ if filename.endswith('.json'): content = json.loads(content)
779
+ except:
780
+ errorReason = 'Bad or null JSON string'
781
+ except:
782
+ errorReason = f'Unable to read from {filename}'
783
+
784
+ if errorReason:
785
+ if command['or'] != None:
786
+ print(f'Exception "{errorReason}": Running the "or" clause')
787
+ return command['or']
788
+ else:
789
+ RuntimeError(self.program, f'Error: {errorReason}')
712
790
  value = {}
713
791
  value['type'] = 'text'
714
792
  value['content'] = content
@@ -729,6 +807,12 @@ class Core(Handler):
729
807
  target['locked'] = True
730
808
  return self.nextPC()
731
809
 
810
+ # Log a message
811
+ def k_log(self, command):
812
+ command['log'] = True
813
+ command['keyword'] = 'print'
814
+ return self.k_print(command)
815
+
732
816
  # Declare a module variable
733
817
  def k_module(self, command):
734
818
  return self.compileVariable(command)
@@ -749,14 +833,14 @@ class Core(Handler):
749
833
  command['target'] = self.getToken()
750
834
  self.add(command)
751
835
  return True
752
- FatalError(self.program.compiler, 'Symbol expected')
836
+ FatalError(self.compiler, 'Symbol expected')
753
837
  else:
754
838
  # First value must be a variable
755
839
  if command['value1']['type'] == 'symbol':
756
840
  command['target'] = command['value1']['name']
757
841
  self.add(command)
758
842
  return True
759
- FatalError(self.program.compiler, 'First value must be a variable')
843
+ FatalError(self.compiler, 'First value must be a variable')
760
844
  return False
761
845
 
762
846
  def r_multiply(self, command):
@@ -766,7 +850,7 @@ class Core(Handler):
766
850
  except:
767
851
  value2 = None
768
852
  target = self.getVariable(command['target'])
769
- if not target['valueHolder']:
853
+ if not target['hasValue']:
770
854
  self.variableDoesNotHoldAValueError(target['name'])
771
855
  return None
772
856
  value = self.getSymbolValue(target)
@@ -791,17 +875,17 @@ class Core(Handler):
791
875
  def k_negate(self, command):
792
876
  if self.nextIsSymbol():
793
877
  symbolRecord = self.getSymbolRecord()
794
- if symbolRecord['valueHolder']:
878
+ if symbolRecord['hasValue']:
795
879
  command['target'] = self.getToken()
796
880
  self.add(command)
797
881
  return True
798
- self.warning(f'Core.negate: Variable "{symbolRecord["name"]}" does not hold a value')
882
+ self.warning(f'Core.negate: Variable {symbolRecord["name"]} does not hold a value')
799
883
  return False
800
884
 
801
885
  def r_negate(self, command):
802
886
  symbolRecord = self.getVariable(command['target'])
803
- if not symbolRecord['valueHolder']:
804
- RuntimeError(self.program, f'{symbolRecord["name"]} does not hold a value')
887
+ if not symbolRecord['hasValue']:
888
+ NoValueRuntimeError(self.program, symbolRecord)
805
889
  return None
806
890
  value = self.getSymbolValue(symbolRecord)
807
891
  if value == None:
@@ -810,13 +894,6 @@ class Core(Handler):
810
894
  self.putSymbolValue(symbolRecord, value)
811
895
  return self.nextPC()
812
896
 
813
- # Define an object variable
814
- def k_object(self, command):
815
- return self.compileVariable(command)
816
-
817
- def r_object(self, command):
818
- return self.nextPC()
819
-
820
897
  # on message {action}
821
898
  def k_on(self, command):
822
899
  if self.nextIs('message'):
@@ -829,7 +906,7 @@ class Core(Handler):
829
906
  cmd['keyword'] = 'gotoPC'
830
907
  cmd['goto'] = 0
831
908
  cmd['debug'] = False
832
- self.addCommand(cmd)
909
+ self.add(cmd)
833
910
  # Add the action and a 'stop'
834
911
  self.compileOne()
835
912
  cmd = {}
@@ -837,7 +914,7 @@ class Core(Handler):
837
914
  cmd['lino'] = command['lino']
838
915
  cmd['keyword'] = 'stop'
839
916
  cmd['debug'] = False
840
- self.addCommand(cmd)
917
+ self.add(cmd)
841
918
  # Fixup the link
842
919
  command['goto'] = self.getPC()
843
920
  return True
@@ -865,7 +942,7 @@ class Core(Handler):
865
942
  elif token == 'writing':
866
943
  mode = 'w'
867
944
  else:
868
- FatalError(self.program.compiler, 'Unknown file open mode {self.getToken()}')
945
+ FatalError(self.compiler, 'Unknown file open mode {self.getToken()}')
869
946
  return False
870
947
  command['mode'] = mode
871
948
  else:
@@ -898,15 +975,15 @@ class Core(Handler):
898
975
  command['from'] = self.getToken()
899
976
  self.add(command)
900
977
  return True
901
- return False;
978
+ return False
902
979
 
903
980
  def r_pop(self, command):
904
981
  symbolRecord = self.getVariable(command['target'])
905
- if not symbolRecord['valueHolder']:
906
- RuntimeError(self.program, f'{symbolRecord["name"]} does not hold a value')
982
+ if not symbolRecord['hasValue']:
983
+ NoValueRuntimeError(self.program, symbolRecord)
907
984
  stackRecord = self.getVariable(command['from'])
908
985
  stack = self.getSymbolValue(stackRecord)
909
- v = stack.pop();
986
+ v = stack.pop()
910
987
  self.putSymbolValue(stackRecord, stack)
911
988
  value = {}
912
989
  value['type'] = 'int' if type(v) == int else 'text'
@@ -931,24 +1008,7 @@ class Core(Handler):
931
1008
  command['result'] = None
932
1009
  command['or'] = None
933
1010
  post = self.getPC()
934
- self.addCommand(command)
935
- if self.peek() == 'or':
936
- self.nextToken()
937
- self.nextToken()
938
- # Add a 'goto' to skip the 'or'
939
- cmd = {}
940
- cmd['lino'] = command['lino']
941
- cmd['domain'] = 'core'
942
- cmd['keyword'] = 'gotoPC'
943
- cmd['goto'] = 0
944
- cmd['debug'] = False
945
- skip = self.getPC()
946
- self.addCommand(cmd)
947
- # Process the 'or'
948
- self.getCommandAt(post)['or'] = self.getPC()
949
- self.compileOne()
950
- # Fixup the skip
951
- self.getCommandAt(skip)['goto'] = self.getPC()
1011
+ self.processOr(command, post)
952
1012
  return True
953
1013
 
954
1014
  def r_post(self, command):
@@ -988,18 +1048,20 @@ class Core(Handler):
988
1048
  command['value'] = value
989
1049
  self.add(command)
990
1050
  return True
991
- FatalError(self.program.compiler, 'I can\'t print this value')
1051
+ FatalError(self.compiler, 'I can\'t print this value')
992
1052
  return False
993
1053
 
994
1054
  def r_print(self, command):
995
1055
  value = self.getRuntimeValue(command['value'])
996
1056
  program = command['program']
997
1057
  code = program.code[program.pc]
998
- lino = code['lino'] + 1
999
- if value == None:
1000
- print(f'{lino}-> <empty>')
1058
+ lino = str(code['lino'] + 1)
1059
+ # while len(lino) < 5: lino = f' {lino}'
1060
+ if value == None: value = '<empty>'
1061
+ if 'log' in command:
1062
+ print(f'{datetime.now().time()}:{self.program.name}:{lino}->{value}')
1001
1063
  else:
1002
- print(f'{lino}-> {value}')
1064
+ print(value)
1003
1065
  return self.nextPC()
1004
1066
 
1005
1067
  # Push a value onto a stack
@@ -1018,7 +1080,7 @@ class Core(Handler):
1018
1080
  return False
1019
1081
 
1020
1082
  def r_push(self, command):
1021
- value = self.getRuntimeValue(command['value'])
1083
+ value = deepcopy(self.getRuntimeValue(command['value']))
1022
1084
  stackRecord = self.getVariable(command['to'])
1023
1085
  if stackRecord['keyword'] != 'stack':
1024
1086
  RuntimeError(self.program, f'{stackRecord["name"]} is not a stack')
@@ -1031,30 +1093,35 @@ class Core(Handler):
1031
1093
  self.putSymbolValue(stackRecord, stack)
1032
1094
  return self.nextPC()
1033
1095
 
1034
- # Put a value into a variable
1035
1096
  # put {value} into {variable}
1036
1097
  def k_put(self, command):
1037
- command['value'] = self.nextValue()
1038
- if self.nextIs('into'):
1039
- if self.nextIsSymbol():
1040
- symbolRecord = self.getSymbolRecord()
1041
- command['target'] = symbolRecord['name']
1042
- if 'valueholder' in symbolRecord and symbolRecord['valueHolder'] == False:
1043
- FatalError(self.program.compiler, f'Symbol {symbolRecord["name"]} is not a value holder')
1098
+ value = self.nextValue()
1099
+ if value != None:
1100
+ command['value'] = value
1101
+ if self.nextIs('into'):
1102
+ if self.nextIsSymbol():
1103
+ symbolRecord = self.getSymbolRecord()
1104
+ command['target'] = symbolRecord['name']
1105
+ if 'hasValue' in symbolRecord and symbolRecord['hasValue'] == False:
1106
+ FatalError(self.compiler, f'Symbol {symbolRecord["name"]} is not a value holder')
1107
+ else:
1108
+ command['or'] = None
1109
+ self.processOr(command, self.getPC())
1110
+ return True
1044
1111
  else:
1045
- self.add(command)
1046
- return True
1047
- else:
1048
- FatalError(self.program.compiler, f'Symbol {self.getToken()} is not a variable')
1112
+ FatalError(self.compiler, f'Symbol {self.getToken()} is not a variable')
1049
1113
  return False
1050
1114
 
1051
1115
  def r_put(self, command):
1052
1116
  value = self.evaluate(command['value'])
1053
1117
  if value == None:
1054
- return -1
1118
+ if command['or'] != None:
1119
+ return command['or']
1120
+ else:
1121
+ RuntimeError(self.program, f'Error: could not compute value')
1055
1122
  symbolRecord = self.getVariable(command['target'])
1056
- if not symbolRecord['valueHolder']:
1057
- RuntimeError(self.program, f'{symbolRecord["name"]} does not hold a value')
1123
+ if not symbolRecord['hasValue']:
1124
+ NoValueRuntimeError(self.program, symbolRecord)
1058
1125
  return -1
1059
1126
  self.putSymbolValue(symbolRecord, value)
1060
1127
  return self.nextPC()
@@ -1069,7 +1136,7 @@ class Core(Handler):
1069
1136
  command['line'] = False
1070
1137
  if self.nextIsSymbol():
1071
1138
  symbolRecord = self.getSymbolRecord()
1072
- if symbolRecord['valueHolder']:
1139
+ if symbolRecord['hasValue']:
1073
1140
  if self.peek() == 'from':
1074
1141
  self.nextToken()
1075
1142
  if self.nextIsSymbol():
@@ -1079,9 +1146,9 @@ class Core(Handler):
1079
1146
  command['file'] = fileRecord['name']
1080
1147
  self.add(command)
1081
1148
  return True
1082
- FatalError(self.program.compiler, f'Symbol "{symbolRecord["name"]}" is not a value holder')
1149
+ FatalError(self.compiler, f'Symbol "{symbolRecord["name"]}" is not a value holder')
1083
1150
  return False
1084
- FatalError(self.program.compiler, f'Symbol "{self.getToken()}" has not been declared')
1151
+ FatalError(self.compiler, f'Symbol "{self.getToken()}" has not been declared')
1085
1152
  return False
1086
1153
 
1087
1154
  def r_read(self, command):
@@ -1098,6 +1165,16 @@ class Core(Handler):
1098
1165
  self.putSymbolValue(symbolRecord, value)
1099
1166
  return self.nextPC()
1100
1167
 
1168
+ # Release the parent script
1169
+ def k_release(self, command):
1170
+ if self.nextIs('parent'):
1171
+ self.add(command)
1172
+ return True
1173
+
1174
+ def r_release(self, command):
1175
+ self.program.releaseParent()
1176
+ return self.nextPC()
1177
+
1101
1178
  # Replace a substring
1102
1179
  #replace {value} with {value} in {variable}
1103
1180
  def k_replace(self, command):
@@ -1128,16 +1205,6 @@ class Core(Handler):
1128
1205
  self.putSymbolValue(templateRecord, value)
1129
1206
  return self.nextPC()
1130
1207
 
1131
- # Release the parent script
1132
- def k_release(self, command):
1133
- if self.nextIs('parent'):
1134
- self.add(command)
1135
- return True
1136
-
1137
- def r_release(self, command):
1138
- self.program.releaseParent()
1139
- return self.nextPC()
1140
-
1141
1208
  # Return from subroutine
1142
1209
  def k_return(self, command):
1143
1210
  self.add(command)
@@ -1147,6 +1214,7 @@ class Core(Handler):
1147
1214
  return self.stack.pop()
1148
1215
 
1149
1216
  # Compile and run a script
1217
+ # run {path} [as {module}] [with {variable} [and {variable}...]]
1150
1218
  def k_run(self, command):
1151
1219
  try:
1152
1220
  command['path'] = self.nextValue()
@@ -1157,20 +1225,24 @@ class Core(Handler):
1157
1225
  if self.nextIsSymbol():
1158
1226
  record = self.getSymbolRecord()
1159
1227
  if record['keyword'] == 'module':
1160
- command['module'] = record['name']
1161
- exports = []
1162
- if self.nextIs('with'):
1163
- while True:
1164
- name = self.nextToken()
1165
- record = self.getSymbolRecord()
1166
- exports.append(name)
1167
- if self.peek() != 'and':
1168
- break
1169
- self.nextToken()
1170
- command['exports'] = json.dumps(exports)
1171
- self.add(command)
1172
- return True
1173
- return False
1228
+ name = record['name']
1229
+ command['module'] = name
1230
+ else: FatalError(self.compiler, f'Symbol \'name\' is not a module')
1231
+ else: FatalError(self.compiler, 'Module name expected after \'as\'')
1232
+ else: FatalError(self.compiler, '\'as {module name}\' expected')
1233
+ exports = []
1234
+ if self.peek() == 'with':
1235
+ self.nextToken()
1236
+ while True:
1237
+ name = self.nextToken()
1238
+ record = self.getSymbolRecord()
1239
+ exports.append(name)
1240
+ if self.peek() != 'and':
1241
+ break
1242
+ self.nextToken()
1243
+ command['exports'] = json.dumps(exports)
1244
+ self.add(command)
1245
+ return True
1174
1246
 
1175
1247
  def r_run(self, command):
1176
1248
  module = self.getVariable(command['module'])
@@ -1187,28 +1259,63 @@ class Core(Handler):
1187
1259
  p(path).start(parent, module, exports)
1188
1260
  return 0
1189
1261
 
1190
- # Provide a name for the script
1191
- def k_script(self, command):
1192
- self.program.name = self.nextToken()
1193
- return True
1194
-
1195
1262
  # Save a value to a file
1196
1263
  def k_save(self, command):
1197
1264
  command['content'] = self.nextValue()
1198
- if self.nextIs('to'):
1199
- command['file'] = self.nextValue()
1200
- self.add(command)
1201
- return True
1202
- return False
1265
+ self.skip('to')
1266
+ if self.nextIsSymbol():
1267
+ record = self.getSymbolRecord()
1268
+ if record['keyword'] == 'ssh':
1269
+ command['ssh'] = record['name']
1270
+ command['path'] = self.nextValue()
1271
+ self.add(command)
1272
+ else:
1273
+ command['file'] = self.getValue()
1274
+ else:
1275
+ command['file'] = self.getValue()
1276
+ command['or'] = None
1277
+ save = self.getPC()
1278
+ self.processOr(command, save)
1279
+ return True
1203
1280
 
1204
1281
  def r_save(self, command):
1282
+ errorReason = None
1205
1283
  content = self.getRuntimeValue(command['content'])
1206
- file = self.getRuntimeValue(command['file'])
1207
- f = open(file, 'w')
1208
- f.write(content)
1209
- f.close()
1284
+ if 'ssh' in command:
1285
+ ssh = self.getVariable(command['ssh'])
1286
+ path = self.getRuntimeValue(command['path'])
1287
+ sftp = ssh['sftp']
1288
+ if path.endswith('.json'): content = json.dumps(content)
1289
+ try:
1290
+ with sftp.open(path, 'w') as remote_file: remote_file.write(content)
1291
+ except:
1292
+ errorReason = 'Unable to write to {path}'
1293
+ if command['or'] != None:
1294
+ print(f'Exception "{errorReason}": Running the "or" clause')
1295
+ return command['or']
1296
+ else:
1297
+ RuntimeError(self.program, f'Error: {errorReason}')
1298
+ else:
1299
+ filename = self.getRuntimeValue(command['file'])
1300
+ if filename.endswith('.json'): content = json.dumps(content)
1301
+ try:
1302
+ with open(filename, 'w') as f: f.write(content)
1303
+ except:
1304
+ errorReason = f'Unable to write to {filename}'
1305
+
1306
+ if errorReason:
1307
+ if command['or'] != None:
1308
+ print(f'Exception "{errorReason}": Running the "or" clause')
1309
+ return command['or']
1310
+ else:
1311
+ RuntimeError(self.program, f'Error: {errorReason}')
1210
1312
  return self.nextPC()
1211
1313
 
1314
+ # Provide a name for the script
1315
+ def k_script(self, command):
1316
+ self.program.name = self.nextToken()
1317
+ return True
1318
+
1212
1319
  # Send a message to a module
1213
1320
  def k_send(self, command):
1214
1321
  command['message'] = self.nextValue()
@@ -1229,16 +1336,40 @@ class Core(Handler):
1229
1336
 
1230
1337
  # Set a value
1231
1338
  # set {variable}
1339
+ # set {ssh} host {host} user {user} password {password}
1232
1340
  # set the elements of {variable} to {value}
1233
1341
  # set element/property of {variable} to {value}
1234
1342
  def k_set(self, command):
1235
1343
  if self.nextIsSymbol():
1236
- target = self.getSymbolRecord()
1237
- if target['valueHolder']:
1344
+ record = self.getSymbolRecord()
1345
+ command['target'] = record['name']
1346
+ if record['hasValue']:
1238
1347
  command['type'] = 'set'
1239
- command['target'] = target['name']
1240
1348
  self.add(command)
1241
1349
  return True
1350
+ elif record['keyword'] == 'ssh':
1351
+ host = None
1352
+ user = None
1353
+ password = None
1354
+ while True:
1355
+ token = self.peek()
1356
+ if token == 'host':
1357
+ self.nextToken()
1358
+ host = self.nextValue()
1359
+ elif token == 'user':
1360
+ self.nextToken()
1361
+ user = self.nextValue()
1362
+ elif token == 'password':
1363
+ self.nextToken()
1364
+ password = self.nextValue()
1365
+ else: break
1366
+ command['host'] = host
1367
+ command['user'] = user
1368
+ command['password'] = password
1369
+ command['type'] = 'ssh'
1370
+ self.add(command)
1371
+ return True
1372
+
1242
1373
  return False
1243
1374
 
1244
1375
  token = self.getToken()
@@ -1270,7 +1401,10 @@ class Core(Handler):
1270
1401
  if self.nextIsSymbol():
1271
1402
  command['target'] = self.getSymbolRecord()['name']
1272
1403
  if self.nextIs('to'):
1273
- command['value'] = self.nextValue()
1404
+ value = self.nextValue()
1405
+ if value == None:
1406
+ FatalError(self.compiler, 'Unable to get a value')
1407
+ command['value'] = value
1274
1408
  self.add(command)
1275
1409
  return True
1276
1410
 
@@ -1283,6 +1417,12 @@ class Core(Handler):
1283
1417
  command['value'] = self.nextValue()
1284
1418
  self.add(command)
1285
1419
  return True
1420
+
1421
+ elif token == 'path':
1422
+ command['path'] = self.nextValue()
1423
+ self.add(command)
1424
+ return True
1425
+
1286
1426
  return False
1287
1427
 
1288
1428
  def r_set(self, command):
@@ -1312,6 +1452,7 @@ class Core(Handler):
1312
1452
  newValue[index] = value
1313
1453
  symbolRecord['elements'] = elements
1314
1454
  symbolRecord['value'] = newValue
1455
+ symbolRecord['index'] = 0
1315
1456
  return self.nextPC()
1316
1457
 
1317
1458
  elif cmdType == 'element':
@@ -1333,6 +1474,11 @@ class Core(Handler):
1333
1474
  self.encoding = self.getRuntimeValue(command['encoding'])
1334
1475
  return self.nextPC()
1335
1476
 
1477
+ elif cmdType == 'path':
1478
+ path = self.getRuntimeValue(command['path'])
1479
+ os.chdir(path)
1480
+ return self.nextPC()
1481
+
1336
1482
  elif cmdType == 'property':
1337
1483
  value = self.getRuntimeValue(command['value'])
1338
1484
  name = self.getRuntimeValue(command['name'])
@@ -1352,13 +1498,55 @@ class Core(Handler):
1352
1498
  val['content'] = content
1353
1499
  self.putSymbolValue(targetVariable, val)
1354
1500
  return self.nextPC()
1501
+
1502
+ elif cmdType == 'ssh':
1503
+ target = self.getVariable(command['target'])
1504
+ host = self.getRuntimeValue(command['host'])
1505
+ user = self.getRuntimeValue(command['user'])
1506
+ password = self.getRuntimeValue(command['password'])
1507
+ ssh = paramiko.SSHClient()
1508
+ target['ssh'] = ssh
1509
+ ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
1510
+ try:
1511
+ ssh.connect(host, username=user, password=password, timeout=10)
1512
+ target['sftp'] = ssh.open_sftp()
1513
+ except:
1514
+ target['error'] = f'Unable to connect to {host} (timeout)'
1515
+ return self.nextPC()
1516
+
1517
+ # Shuffle a list
1518
+ def k_shuffle(self, command):
1519
+ if self.nextIsSymbol():
1520
+ symbolRecord = self.getSymbolRecord()
1521
+ if symbolRecord['hasValue']:
1522
+ command['target'] = self.getToken()
1523
+ self.add(command)
1524
+ return True
1525
+ self.warning(f'Core.negate: Variable {symbolRecord["name"]} does not hold a value')
1526
+ return False
1527
+
1528
+ def r_shuffle(self, command):
1529
+ symbolRecord = self.getVariable(command['target'])
1530
+ if not symbolRecord['hasValue']:
1531
+ NoValueRuntimeError(self.program, symbolRecord)
1532
+ return None
1533
+ value = self.getSymbolValue(symbolRecord)
1534
+ if value == None:
1535
+ RuntimeError(self.program, f'{symbolRecord["name"]} has not been initialised')
1536
+ content = value['content']
1537
+ if isinstance(content, list):
1538
+ random.shuffle(content)
1539
+ value['content'] = content
1540
+ self.putSymbolValue(symbolRecord, value)
1541
+ return self.nextPC()
1542
+ RuntimeError(self.program, f'{symbolRecord["name"]} is not a list')
1355
1543
 
1356
1544
  # Split a string into a variable with several elements
1357
1545
  # split {variable} on {value}
1358
1546
  def k_split(self, command):
1359
1547
  if self.nextIsSymbol():
1360
1548
  symbolRecord = self.getSymbolRecord()
1361
- if symbolRecord['valueHolder']:
1549
+ if symbolRecord['hasValue']:
1362
1550
  command['target'] = symbolRecord['name']
1363
1551
  value = {}
1364
1552
  value['type'] = 'text'
@@ -1374,6 +1562,7 @@ class Core(Handler):
1374
1562
  command['on'] = self.nextValue()
1375
1563
  self.add(command)
1376
1564
  return True
1565
+ else: self.noSymbolWarning()
1377
1566
  return False
1378
1567
 
1379
1568
  def r_split(self, command):
@@ -1382,6 +1571,7 @@ class Core(Handler):
1382
1571
  content = value['content'].split(self.getRuntimeValue(command['on']))
1383
1572
  elements = len(content)
1384
1573
  target['elements'] = elements
1574
+ target['index'] = 0
1385
1575
  target['value'] = [None] * elements
1386
1576
 
1387
1577
  for index, item in enumerate(content):
@@ -1393,6 +1583,12 @@ class Core(Handler):
1393
1583
 
1394
1584
  return self.nextPC()
1395
1585
 
1586
+ def k_ssh(self, command):
1587
+ return self.compileVariable(command)
1588
+
1589
+ def r_ssh(self, command):
1590
+ return self.nextPC()
1591
+
1396
1592
  # Declare a stack variable
1397
1593
  def k_stack(self, command):
1398
1594
  return self.compileVariable(command)
@@ -1422,7 +1618,7 @@ class Core(Handler):
1422
1618
  command['background'] = background
1423
1619
  self.add(command)
1424
1620
  return True
1425
- FatalError(self.program.compiler, 'I can\'t give this command')
1621
+ FatalError(self.compiler, 'I can\'t give this command')
1426
1622
  return False
1427
1623
 
1428
1624
  def r_system(self, command):
@@ -1442,7 +1638,7 @@ class Core(Handler):
1442
1638
  if self.nextToken() == 'from':
1443
1639
  if self.nextIsSymbol():
1444
1640
  symbolRecord = self.getSymbolRecord()
1445
- if symbolRecord['valueHolder']:
1641
+ if symbolRecord['hasValue']:
1446
1642
  if self.peek() == 'giving':
1447
1643
  # This variable must be treated as a second value
1448
1644
  command['value2'] = self.getValue()
@@ -1465,7 +1661,7 @@ class Core(Handler):
1465
1661
  self.add(command)
1466
1662
  return True
1467
1663
  else:
1468
- FatalError(self.program.compiler, f'\'{self.getToken()}\' is not a symbol')
1664
+ FatalError(self.compiler, f'\'{self.getToken()}\' is not a symbol')
1469
1665
  else:
1470
1666
  self.warning(f'Core.take: Expected "giving"')
1471
1667
  return False
@@ -1477,7 +1673,7 @@ class Core(Handler):
1477
1673
  except:
1478
1674
  value2 = None
1479
1675
  target = self.getVariable(command['target'])
1480
- if not target['valueHolder']:
1676
+ if not target['hasValue']:
1481
1677
  self.variableDoesNotHoldAValueError(target['name'])
1482
1678
  return None
1483
1679
  value = self.getSymbolValue(target)
@@ -1499,7 +1695,7 @@ class Core(Handler):
1499
1695
  def k_toggle(self, command):
1500
1696
  if self.nextIsSymbol():
1501
1697
  target = self.getSymbolRecord()
1502
- if target['valueHolder']:
1698
+ if target['hasValue']:
1503
1699
  command['target'] = target['name']
1504
1700
  self.add(command)
1505
1701
  return True
@@ -1515,6 +1711,24 @@ class Core(Handler):
1515
1711
  self.add(command)
1516
1712
  return self.nextPC()
1517
1713
 
1714
+ # Trim whitespace from a variable
1715
+ def k_trim(self, command):
1716
+ if self.nextIsSymbol():
1717
+ record = self.getSymbolRecord()
1718
+ if record['hasValue']:
1719
+ command['name'] = record['name']
1720
+ self.add(command)
1721
+ return True
1722
+ return False
1723
+
1724
+ def r_trim(self, command):
1725
+ record = self.getVariable(command['name'])
1726
+ value = record['value'][record['index']]
1727
+ if value['type'] == 'text':
1728
+ content = value['content']
1729
+ value['content'] = content.strip()
1730
+ return self.nextPC()
1731
+
1518
1732
  # Truncate a file
1519
1733
  def k_truncate(self, command):
1520
1734
  if self.nextIsSymbol():
@@ -1544,9 +1758,34 @@ class Core(Handler):
1544
1758
  target['locked'] = False
1545
1759
  return self.nextPC()
1546
1760
 
1761
+ # Use a plugin module
1762
+ def k_use(self, command):
1763
+ if self.peek() == 'plugin':
1764
+ # Import a plugin
1765
+ self.nextToken()
1766
+ clazz = self.nextToken()
1767
+ if self.nextIs('from'):
1768
+ source = self.nextToken()
1769
+ self.program.importPlugin(f'{source}:{clazz}')
1770
+ return True
1771
+ return False
1772
+ else:
1773
+ token = self.nextToken()
1774
+ if token in ['graphics', 'debugger']:
1775
+ if not hasattr(self.program, 'usingGraphics'):
1776
+ print('Loading graphics module')
1777
+ from .ec_pyside import Graphics
1778
+ self.program.graphics = Graphics
1779
+ self.program.useClass(Graphics)
1780
+ self.program.usingGraphics = True
1781
+ if token == 'debugger': self.program.debugging = True
1782
+ return True
1783
+ return False
1784
+
1547
1785
  # Declare a general-purpose variable
1548
1786
  def k_variable(self, command):
1549
- return self.compileVariable(command, True)
1787
+ self.compiler.addValueType()
1788
+ return self.compileVariable(command)
1550
1789
 
1551
1790
  def r_variable(self, command):
1552
1791
  return self.nextPC()
@@ -1585,7 +1824,7 @@ class Core(Handler):
1585
1824
  # token = self.getToken()
1586
1825
  command['condition'] = code
1587
1826
  test = self.getPC()
1588
- self.addCommand(command)
1827
+ self.add(command)
1589
1828
  # Set up a goto for when the test fails
1590
1829
  fail = self.getPC()
1591
1830
  cmd = {}
@@ -1594,7 +1833,7 @@ class Core(Handler):
1594
1833
  cmd['keyword'] = 'gotoPC'
1595
1834
  cmd['goto'] = 0
1596
1835
  cmd['debug'] = False
1597
- self.addCommand(cmd)
1836
+ self.add(cmd)
1598
1837
  # Do the body of the while
1599
1838
  self.nextToken()
1600
1839
  if self.compileOne() == False:
@@ -1606,7 +1845,7 @@ class Core(Handler):
1606
1845
  cmd['keyword'] = 'gotoPC'
1607
1846
  cmd['goto'] = test
1608
1847
  cmd['debug'] = False
1609
- self.addCommand(cmd)
1848
+ self.add(cmd)
1610
1849
  # Fixup the 'goto' on completion
1611
1850
  self.getCommandAt(fail)['goto'] = self.getPC()
1612
1851
  return True
@@ -1652,9 +1891,8 @@ class Core(Handler):
1652
1891
 
1653
1892
  def incdec(self, command, mode):
1654
1893
  symbolRecord = self.getVariable(command['target'])
1655
- if not symbolRecord['valueHolder']:
1656
- RuntimeError(self.program, f'{symbolRecord["name"]} does not hold a value')
1657
- return None
1894
+ if not symbolRecord['hasValue']:
1895
+ NoValueRuntimeError(self.program, symbolRecord)
1658
1896
  value = self.getSymbolValue(symbolRecord)
1659
1897
  if value == None:
1660
1898
  RuntimeError(self.program, f'{symbolRecord["name"]} has not been initialised')
@@ -1675,13 +1913,15 @@ class Core(Handler):
1675
1913
  value['name'] = token
1676
1914
  symbolRecord = self.getSymbolRecord()
1677
1915
  keyword = symbolRecord['keyword']
1916
+
1678
1917
  if keyword == 'module':
1679
1918
  value['type'] = 'module'
1680
1919
  return value
1681
1920
 
1682
- if keyword == 'variable':
1921
+ if keyword in ['ssh', 'variable']:
1683
1922
  value['type'] = 'symbol'
1684
1923
  return value
1924
+
1685
1925
  return None
1686
1926
 
1687
1927
  value['type'] = token
@@ -1701,7 +1941,7 @@ class Core(Handler):
1701
1941
  if token in ['now', 'today', 'newline', 'tab', 'empty']:
1702
1942
  return value
1703
1943
 
1704
- if token in ['stringify', 'json', 'lowercase', 'uppercase', 'hash', 'random', 'float', 'integer', 'encode', 'decode']:
1944
+ if token in ['stringify', 'prettify', 'json', 'lowercase', 'uppercase', 'hash', 'random', 'float', 'integer', 'encode', 'decode']:
1705
1945
  value['content'] = self.nextValue()
1706
1946
  return value
1707
1947
 
@@ -1720,10 +1960,10 @@ class Core(Handler):
1720
1960
  if self.nextToken() == 'of':
1721
1961
  if self.nextIsSymbol():
1722
1962
  symbolRecord = self.getSymbolRecord()
1723
- if symbolRecord['valueHolder']:
1963
+ if symbolRecord['hasValue']:
1724
1964
  value['target'] = symbolRecord['name']
1725
1965
  return value
1726
- self.warning(f'Core.compileValue: Token \'{self.getToken()}\' does not hold a value')
1966
+ self.warning(f'Core.compileValue: Token {symbolRecord["name"]} does not hold a value')
1727
1967
  return None
1728
1968
 
1729
1969
  if token == 'property':
@@ -1731,13 +1971,10 @@ class Core(Handler):
1731
1971
  if self.nextToken() == 'of':
1732
1972
  if self.nextIsSymbol():
1733
1973
  symbolRecord = self.getSymbolRecord()
1734
- if symbolRecord['valueHolder']:
1974
+ if symbolRecord['hasValue']:
1735
1975
  value['target'] = symbolRecord['name']
1736
1976
  return value
1737
- else:
1738
- value['value'] = self.getValue()
1739
- return value
1740
- self.warning(f'Core.compileValue: Token \'{self.getToken()}\' does not hold a value')
1977
+ NoValueError(self.compiler, symbolRecord)
1741
1978
  return None
1742
1979
 
1743
1980
  if token == 'arg':
@@ -1780,8 +2017,9 @@ class Core(Handler):
1780
2017
  if token == 'count':
1781
2018
  if self.nextIs('of'):
1782
2019
  if self.nextIsSymbol():
1783
- value['name'] = self.getToken()
1784
- return value
2020
+ if self.getSymbolRecord()['hasValue']:
2021
+ value['name'] = self.getToken()
2022
+ return value
1785
2023
  return None
1786
2024
 
1787
2025
  if token == 'index':
@@ -1808,10 +2046,12 @@ class Core(Handler):
1808
2046
  return None
1809
2047
 
1810
2048
  if token == 'value':
1811
- value['type'] = 'valueOf'
1812
2049
  if self.nextIs('of'):
1813
- value['content'] = self.nextValue()
1814
- return value
2050
+ v = self.nextValue()
2051
+ if v !=None:
2052
+ value['type'] = 'valueOf'
2053
+ value['content'] = v
2054
+ return value
1815
2055
  return None
1816
2056
 
1817
2057
  if token == 'length':
@@ -1880,14 +2120,24 @@ class Core(Handler):
1880
2120
  return value
1881
2121
 
1882
2122
  if token == 'error':
1883
- if self.peek() == 'code':
2123
+ token = self.peek()
2124
+ if token == 'code':
1884
2125
  self.nextToken()
1885
2126
  value['item'] = 'errorCode'
1886
2127
  return value
1887
- if self.peek() == 'reason':
2128
+ elif token == 'reason':
1888
2129
  self.nextToken()
1889
2130
  value['item'] = 'errorReason'
1890
2131
  return value
2132
+ elif token in ['in', 'of']:
2133
+ self.nextToken()
2134
+ if self.nextIsSymbol():
2135
+ record = self.getSymbolRecord()
2136
+ if record['keyword'] == 'ssh':
2137
+ value['item'] = 'sshError'
2138
+ value['name'] = record['name']
2139
+ return value
2140
+ return None
1891
2141
 
1892
2142
  if token == 'type':
1893
2143
  if self.nextIs('of'):
@@ -1906,6 +2156,9 @@ class Core(Handler):
1906
2156
  value['command'] = self.nextValue()
1907
2157
  return value
1908
2158
 
2159
+ if token == 'ticker':
2160
+ return value
2161
+
1909
2162
  return None
1910
2163
 
1911
2164
  #############################################################################
@@ -1984,6 +2237,10 @@ class Core(Handler):
1984
2237
  base64_bytes = content.encode('ascii')
1985
2238
  message_bytes = base64.b64decode(base64_bytes)
1986
2239
  value['content'] = message_bytes.decode('ascii')
2240
+ elif self.encoding == 'hex':
2241
+ hex_bytes = content.encode('utf-8')
2242
+ message_bytes = binascii.unhexlify(hex_bytes)
2243
+ value['content'] = message_bytes.decode('utf-8')
1987
2244
  else:
1988
2245
  value = v
1989
2246
  return value
@@ -2027,6 +2284,10 @@ class Core(Handler):
2027
2284
  data_bytes = content.encode('ascii')
2028
2285
  base64_bytes = base64.b64encode(data_bytes)
2029
2286
  value['content'] = base64_bytes.decode('ascii')
2287
+ elif self.encoding == 'hex':
2288
+ data_bytes = content.encode('utf-8')
2289
+ hex_bytes = binascii.hexlify(data_bytes)
2290
+ value['content'] = hex_bytes.decode('utf-8')
2030
2291
  else:
2031
2292
  value = v
2032
2293
  return value
@@ -2040,6 +2301,10 @@ class Core(Handler):
2040
2301
  elif v['item'] == 'errorReason':
2041
2302
  value['type'] = 'text'
2042
2303
  value['content'] = errorReason
2304
+ elif v['item'] == 'sshError':
2305
+ record = self.getVariable(v['name'])
2306
+ value['type'] = 'text'
2307
+ value['content'] = record['error'] if 'error' in record else ''
2043
2308
  return value
2044
2309
 
2045
2310
  def v_files(self, v):
@@ -2119,7 +2384,7 @@ class Core(Handler):
2119
2384
  try:
2120
2385
  value['content'] = json.loads(item)
2121
2386
  except:
2122
- RuntimeError(self.program, 'Cannot encode value')
2387
+ value = None
2123
2388
  return value
2124
2389
 
2125
2390
  def v_keys(self, v):
@@ -2191,7 +2456,7 @@ class Core(Handler):
2191
2456
  def v_now(self, v):
2192
2457
  value = {}
2193
2458
  value['type'] = 'int'
2194
- value['content'] = getTimestamp(time.time())
2459
+ value['content'] = int(time.time())
2195
2460
  return value
2196
2461
 
2197
2462
  def v_position(self, v):
@@ -2203,6 +2468,13 @@ class Core(Handler):
2203
2468
  value['content'] = haystack.rfind(needle) if last else haystack.find(needle)
2204
2469
  return value
2205
2470
 
2471
+ def v_prettify(self, v):
2472
+ item = self.getRuntimeValue(v['content'])
2473
+ value = {}
2474
+ value['type'] = 'text'
2475
+ value['content'] = json.dumps(item, indent=4)
2476
+ return value
2477
+
2206
2478
  def v_property(self, v):
2207
2479
  propertyValue = self.getRuntimeValue(v['name'])
2208
2480
  if 'target' in v:
@@ -2228,7 +2500,7 @@ class Core(Handler):
2228
2500
  limit = self.getRuntimeValue(v['content'])
2229
2501
  value = {}
2230
2502
  value['type'] = 'int'
2231
- value['content'] = randrange(0, limit)
2503
+ value['content'] = random.randrange(0, limit)
2232
2504
  return value
2233
2505
 
2234
2506
  def v_right(self, v):
@@ -2255,9 +2527,17 @@ class Core(Handler):
2255
2527
  return value
2256
2528
 
2257
2529
  # This is used by the expression evaluator to get the value of a symbol
2258
- def v_symbol(self, symbolRecord):
2259
- if symbolRecord['keyword'] == 'variable':
2530
+ def v_symbol(self, value):
2531
+ name = value['name']
2532
+ symbolRecord = self.program.getSymbolRecord(name)
2533
+ keyword = symbolRecord['keyword']
2534
+ if keyword == 'variable':
2260
2535
  return self.getSymbolValue(symbolRecord)
2536
+ elif keyword == 'ssh':
2537
+ v = {}
2538
+ v['type'] = 'boolean'
2539
+ v['content'] = True if 'ssh' in symbolRecord and symbolRecord['ssh'] != None else False
2540
+ return v
2261
2541
  else:
2262
2542
  return None
2263
2543
 
@@ -2283,6 +2563,12 @@ class Core(Handler):
2283
2563
  value['content'] = round(math.tan(angle * 0.01745329) * radius)
2284
2564
  return value
2285
2565
 
2566
+ def v_ticker(self, v):
2567
+ value = {}
2568
+ value['type'] = 'int'
2569
+ value['content'] = self.program.ticker
2570
+ return value
2571
+
2286
2572
  def v_timestamp(self, v):
2287
2573
  value = {}
2288
2574
  value['type'] = 'int'
@@ -2339,7 +2625,7 @@ class Core(Handler):
2339
2625
  v = self.getRuntimeValue(v['content'])
2340
2626
  value = {}
2341
2627
  value['type'] = 'int'
2342
- value['content'] = int(v)
2628
+ value['content'] = int(v) if v != '' else 0
2343
2629
  return value
2344
2630
 
2345
2631
  def v_weekday(self, v):
@@ -2351,19 +2637,46 @@ class Core(Handler):
2351
2637
  #############################################################################
2352
2638
  # Compile a condition
2353
2639
  def compileCondition(self):
2354
- condition = Condition()
2355
- if self.getToken() == 'not':
2640
+ condition = Object()
2641
+ condition.negate = False
2642
+
2643
+ token = self.getToken()
2644
+
2645
+ if token == 'not':
2356
2646
  condition.type = 'not'
2357
2647
  condition.value = self.nextValue()
2358
2648
  return condition
2359
2649
 
2360
- if self.getToken() == 'file':
2650
+ elif token == 'error':
2651
+ self.nextToken()
2652
+ self.skip('in')
2653
+ if self.nextIsSymbol():
2654
+ record = self.getSymbolRecord()
2655
+ if record['keyword'] == 'ssh':
2656
+ condition.type = 'sshError'
2657
+ condition.target = record['name']
2658
+ return condition
2659
+ return None
2660
+
2661
+ elif token == 'file':
2361
2662
  path = self.nextValue()
2362
- if self.peek() == 'exists':
2363
- condition.type = 'exists'
2364
- condition.path = path
2365
- self.nextToken()
2663
+ condition.path = path
2664
+ condition.type = 'exists'
2665
+ self.skip('on')
2666
+ if self.nextIsSymbol():
2667
+ record = self.getSymbolRecord()
2668
+ if record['keyword'] == 'ssh':
2669
+ condition.type = 'sshExists'
2670
+ condition.target = record['name']
2671
+ token = self.nextToken()
2672
+ else: token = self.getToken()
2673
+ if token == 'exists':
2366
2674
  return condition
2675
+ elif token == 'does':
2676
+ if self.nextIs('not'):
2677
+ if self.nextIs('exist'):
2678
+ condition.negate = not condition.negate
2679
+ return condition
2367
2680
  return None
2368
2681
 
2369
2682
  value = self.getValue()
@@ -2383,6 +2696,25 @@ class Core(Handler):
2383
2696
  return condition
2384
2697
  return None
2385
2698
 
2699
+ if token == 'does':
2700
+ self.nextToken()
2701
+ if self.nextIs('not'):
2702
+ token = self.nextToken()
2703
+ if token == 'have':
2704
+ if self.nextToken() == 'property':
2705
+ prop = self.nextValue()
2706
+ condition.type = 'hasProperty'
2707
+ condition.property = prop
2708
+ condition.negate = not condition.negate
2709
+ return condition
2710
+ elif token == 'include':
2711
+ value = self.nextValue()
2712
+ condition.type = 'includes'
2713
+ condition.value2 = value
2714
+ condition.negate = not condition.negate
2715
+ return condition
2716
+ return None
2717
+
2386
2718
  if token in ['starts', 'ends']:
2387
2719
  self.nextToken()
2388
2720
  if self.nextToken() == 'with':
@@ -2409,7 +2741,7 @@ class Core(Handler):
2409
2741
  condition.type = 'is'
2410
2742
  condition.value2 = self.getValue()
2411
2743
  return condition
2412
-
2744
+
2413
2745
  if condition.value1:
2414
2746
  # It's a boolean if
2415
2747
  condition.type = 'boolean'
@@ -2461,7 +2793,8 @@ class Core(Handler):
2461
2793
 
2462
2794
  def c_exists(self, condition):
2463
2795
  path = self.getRuntimeValue(condition.path)
2464
- return os.path.exists(path)
2796
+ comparison = os.path.exists(path)
2797
+ return not comparison if condition.negate else comparison
2465
2798
 
2466
2799
  def c_greater(self, condition):
2467
2800
  comparison = self.program.compare(condition.value1, condition.value2)
@@ -2475,12 +2808,13 @@ class Core(Handler):
2475
2808
  hasProp = True
2476
2809
  except:
2477
2810
  hasProp = False
2478
- return hasProp
2811
+ return not hasProp if condition.negate else hasProp
2479
2812
 
2480
2813
  def c_includes(self, condition):
2481
2814
  value1 = self.getRuntimeValue(condition.value1)
2482
2815
  value2 = self.getRuntimeValue(condition.value2)
2483
- return value2 in value1
2816
+ includes = value2 in value1
2817
+ return not includes if condition.negate else includes
2484
2818
 
2485
2819
  def c_is(self, condition):
2486
2820
  comparison = self.program.compare(condition.value1, condition.value2)
@@ -2503,7 +2837,7 @@ class Core(Handler):
2503
2837
  return not comparison if condition.negate else comparison
2504
2838
 
2505
2839
  def c_not(self, condition):
2506
- return not self.getRuntimeValue(condition.value1)
2840
+ return not self.getRuntimeValue(condition.value)
2507
2841
 
2508
2842
  def c_object(self, condition):
2509
2843
  comparison = type(self.getRuntimeValue(condition.value1)) is dict
@@ -2511,6 +2845,24 @@ class Core(Handler):
2511
2845
 
2512
2846
  def c_odd(self, condition):
2513
2847
  return self.getRuntimeValue(condition.value1) % 2 == 1
2848
+
2849
+ def c_sshError(self, condition):
2850
+ target = self.getVariable(condition.target)
2851
+ errormsg = target['error'] if 'error' in target else None
2852
+ condition.errormsg = errormsg
2853
+ test = errormsg != None
2854
+ return not test if condition.negate else test
2855
+
2856
+ def c_sshExists(self, condition):
2857
+ path = self.getRuntimeValue(condition.path)
2858
+ ssh = self.getVariable(condition.target)
2859
+ sftp = ssh['sftp']
2860
+ try:
2861
+ with sftp.open(path, 'r') as remote_file: remote_file.read().decode()
2862
+ comparison = True
2863
+ except:
2864
+ comparison = False
2865
+ return not comparison if condition.negate else comparison
2514
2866
 
2515
2867
  def c_starts(self, condition):
2516
2868
  value1 = self.getRuntimeValue(condition.value1)
@@ -2520,4 +2872,3 @@ class Core(Handler):
2520
2872
  def c_string(self, condition):
2521
2873
  comparison = type(self.getRuntimeValue(condition.value1)) is str
2522
2874
  return not comparison if condition.negate else comparison
2523
- # xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx