easycoder 251104.2__py2.py3-none-any.whl → 260108.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.

Potentially problematic release.


This version of easycoder might be problematic. Click here for more details.

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