easycoder 251104.1__py2.py3-none-any.whl → 251215.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
easycoder/ec_program.py CHANGED
@@ -1,7 +1,15 @@
1
- import time, json, sys
1
+ import time, sys
2
2
  from copy import deepcopy
3
3
  from collections import deque
4
- from .ec_classes import Script, Token, FatalError, RuntimeError, Object
4
+ from .ec_classes import (
5
+ Script,
6
+ Token,
7
+ FatalError,
8
+ RuntimeError,
9
+ NoValueRuntimeError,
10
+ ECObject,
11
+ ECValue
12
+ )
5
13
  from .ec_compiler import Compiler
6
14
  from .ec_core import Core
7
15
  import importlib
@@ -16,20 +24,25 @@ def flush():
16
24
 
17
25
  class Program:
18
26
 
19
- def __init__(self, argv):
27
+ def __init__(self, arg):
20
28
  global queue
21
29
  print(f'EasyCoder version {version("easycoder")}')
22
- if len(argv) == 0:
30
+ if len(arg) == 0:
23
31
  print('No script supplied')
24
32
  exit()
25
- if argv in ['-v', '--version']: return
26
- scriptName = argv
33
+ if arg in ['-v', '--version']: return
34
+ if arg[0:6] == 'debug ':
35
+ print('Debug mode requested')
36
+ self.scriptName = arg[6:]
37
+ self.debugging = True
38
+ else:
39
+ self.scriptName = arg
40
+ self.debugging = False
27
41
 
28
- f = open(scriptName, 'r')
42
+ f = open(self.scriptName, 'r')
29
43
  source = f.read()
30
44
  f.close()
31
45
  queue = deque()
32
- self.argv = argv
33
46
  self.domains = []
34
47
  self.domainIndex = {}
35
48
  self.name = '<anon>'
@@ -41,22 +54,31 @@ class Program:
41
54
  self.stack = []
42
55
  self.script = Script(source)
43
56
  self.compiler = Compiler(self)
57
+ self.object = ECObject()
44
58
  self.value = self.compiler.value
45
59
  self.condition = self.compiler.condition
46
60
  self.graphics = None
61
+ self.psutil = None
47
62
  self.useClass(Core)
48
- self.externalControl = False
49
63
  self.ticker = 0
50
- self.running = True
64
+ self.graphicsRunning = False
65
+ self.debugger = None
66
+ self.running = False
67
+ self.parent = None
68
+ self.message = None
69
+ self.onMessagePC = 0
70
+ self.breakpoint = False
51
71
 
52
72
  # This is called at 10msec intervals by the GUI code
53
73
  def flushCB(self):
54
74
  self.ticker += 1
75
+ # if self.ticker % 1000 == 0: print(f'GUI Tick {self.ticker}')
55
76
  flush()
56
77
 
57
78
  def start(self, parent=None, module = None, exports=[]):
58
79
  self.parent = parent
59
80
  self.exports = exports
81
+ if self.debugging: self.useGraphics()
60
82
  if module != None:
61
83
  module['child'] = self
62
84
  startCompile = time.time()
@@ -69,7 +91,7 @@ class Program:
69
91
  f'{round((finishCompile - startCompile) * 1000)} ms')
70
92
  for name in self.symbols.keys():
71
93
  record = self.code[self.symbols[name]]
72
- if name[-1] != ':' and not record['used']:
94
+ if name[-1] != ':' and not 'used' in record:
73
95
  print(f'Variable "{name}" not used')
74
96
  else:
75
97
  print(f'Run {self.name}')
@@ -78,13 +100,35 @@ class Program:
78
100
  self.compiler.showWarnings()
79
101
 
80
102
  # If this is the main script and there's no graphics, run a main loop
81
- if parent == None and self.externalControl == False:
82
- while True:
103
+ if parent == None:
104
+ while not self.graphicsRunning:
83
105
  if self.running == True:
84
106
  flush()
85
107
  time.sleep(0.01)
86
108
  else:
87
109
  break
110
+
111
+ # Use the graphics module
112
+ def useGraphics(self):
113
+ if self.graphics == None:
114
+ print('Loading graphics module')
115
+ from .ec_graphics import Graphics
116
+ self.graphics = Graphics
117
+ self.useClass(Graphics)
118
+ return True
119
+
120
+ # Use the psutil module
121
+ def usePSUtil(self):
122
+ if self.psutil == None:
123
+ print('Loading psutil module')
124
+ from .ec_psutil import PSUtil
125
+ self.psutil = PSUtil
126
+ self.useClass(PSUtil)
127
+ return True
128
+
129
+ # Indicate that graphics are running
130
+ def startGraphics(self):
131
+ self.graphicsRunning = True
88
132
 
89
133
  # Import a plugin
90
134
  def importPlugin(self, source):
@@ -109,118 +153,203 @@ class Program:
109
153
  self.domains.append(handler)
110
154
  self.domainIndex[handler.getName()] = handler
111
155
 
156
+ # This is the runtime callback for event handlers
157
+ def callback(self, item, record, goto):
158
+ object = self.getObject(record)
159
+ values = object.getValues() # type: ignore
160
+ for i, v in enumerate(values):
161
+ if isinstance(v, ECValue): v = v.getContent()
162
+ if v == item:
163
+ object.setIndex(i) # type: ignore
164
+ self.run(goto)
165
+ return
166
+
112
167
  # Get the domain list
113
168
  def getDomains(self):
114
169
  return self.domains
170
+
171
+ def isSymbol(self, name):
172
+ return name in self.symbols
115
173
 
116
- def getSymbolRecord(self, name):
174
+ # Get the symbol record for a given name
175
+ def getVariable(self, name):
176
+ if isinstance(name, dict): name = name['name']
117
177
  try:
118
178
  target = self.code[self.symbols[name]]
119
- if target['import'] != None:
179
+ if 'import' in target:
120
180
  target = target['import']
121
181
  return target
122
182
  except:
123
183
  RuntimeError(self, f'Unknown symbol \'{name}\'')
184
+
185
+ # Get the object represented by a symbol record
186
+ def getObject(self, record):
187
+ if isinstance(record, dict) and 'object' in record:
188
+ return record['object']
189
+ return record
190
+
191
+ # Check if an object is an instance of a given class
192
+ # This can either be variable record (a dict) or an instance of ECObject
193
+ def isObjectType(self, object, classes):
194
+ if isinstance(object, dict) and 'object' in object and isinstance(object['object'], ECObject):
195
+ object = object['object']
196
+ return isinstance(object, classes)
197
+
198
+ # Check if the object is an instance of one of a set of classes. Compile and runtime
199
+ def checkObjectType(self, object, classes):
200
+ if isinstance(object, dict): return
201
+ if not isinstance(object, classes):
202
+ if self.running:
203
+ raise RuntimeError(self, f"Objects of type {type(object)} are not instances of {classes}")
204
+ else:
205
+ raise FatalError(self.compiler, f"Objects of type {type(object)} are not instances of {classes}")
206
+
207
+ # Get the inner (non-EC) object from a name, record or object
208
+ def getInnerObject(self, object):
209
+ if isinstance(object, dict): object = object['object']
210
+ elif isinstance(object, str):
211
+ record = self.getVariable(object) # type: ignore
212
+ object = self.getObject(record) # type: ignore
213
+ value = object.getValue() # type: ignore
214
+ if isinstance(value, ECValue) and value.getType() == 'object':
215
+ return value.getContent()
216
+ else: return value
124
217
 
125
- def doValue(self, value):
126
- if value == None:
127
- RuntimeError(self, f'Undefined value (variable not initialized?)')
128
-
218
+ def constant(self, content, numeric):
129
219
  result = {}
130
- valType = value['type']
131
- if valType in ['boolean', 'int', 'text', 'object']:
132
- result = value
220
+ result['type'] = 'int' if numeric else 'str'
221
+ result['content'] = content
222
+ return result
223
+
224
+ # Test if an item is a string or a number
225
+ def getItemType(self, value):
226
+ return 'int' if isinstance(value, int) else 'str'
227
+
228
+ # Get the value of an item that may be an ECValue or a raw value. Return as an ECValue
229
+ def getValueOf(self, item):
230
+ value = ECValue()
231
+ if isinstance(item, ECValue):
232
+ if item.getType() == 'object':
233
+ return item.getContent()
234
+ else: value = item
235
+ else:
236
+ varType = type(item).__name__
237
+ if varType in ['int', 'str', 'bool', 'float', 'list', 'dict']:
238
+ if varType == 'int': value.setValue(type='int', content=item)
239
+ elif varType == 'str': value.setValue(type='str', content=item)
240
+ elif varType == 'bool': value.setValue(type='boolean', content=item)
241
+ elif varType == 'float': value.setValue(type='str', content=str(item))
242
+ elif varType == 'list': value.setValue(type='list', content=item)
243
+ elif varType == 'dict': value.setValue(type='dict', content=item)
244
+ else: value.setValue(None)
245
+ return value
246
+
247
+ # Runtime function to evaluate an ECObject or ECValue. Returns another ECValue
248
+ # This function may be called recursively by value handlers.
249
+ def evaluate(self, item):
250
+ if isinstance(item, ECObject):
251
+ value = item.getValue()
252
+ if value == None:
253
+ raise RuntimeError(self, f'Symbol {item.getName()} not initialized')
254
+ else: value = item
255
+ try:
256
+ valType = value.getType() # type: ignore
257
+ except:
258
+ RuntimeError(self, 'Value does not hold a valid ECValue')
259
+ result = ECValue(type=valType)
260
+
261
+ if valType in ('str', 'int', 'boolean', 'list', 'dict'):
262
+ # Simple value - just return the content
263
+ result.setContent(value.getContent()) # type: ignore
264
+
265
+ elif valType == 'object':
266
+ # Object other than ECVariable
267
+ record = self.getVariable(value.getName())
268
+ object = self.getObject(record) # type: ignore
269
+ result = object.getContent() # type: ignore
270
+
271
+ elif valType == 'symbol': # type: ignore
272
+ # If it's a symbol, get its value
273
+ record = self.getVariable(value.getContent()) # type: ignore
274
+ if not 'object' in record: return None # type: ignore
275
+ variable = self.getObject(record) # type: ignore
276
+ result = variable.getValue() # type: ignore
277
+ if isinstance(result, ECValue): return self.evaluate(result)
278
+ if isinstance(result, ECObject): return result.getValue()
279
+ else:
280
+ # See if one of the domains can handle this value
281
+ value = result
282
+ result = None
283
+ for domain in self.domains:
284
+ result = domain.getUnknownValue(value)
285
+ if result != None: break
286
+
133
287
  elif valType == 'cat':
288
+ # Handle concatenation
134
289
  content = ''
135
- for part in value['value']:
136
- val = self.doValue(part)
137
- if val == None:
138
- val = ''
139
- if val != '':
140
- val = str(val['content'])
141
- if val == None:
142
- val = ''
143
- content += val
144
- result['type'] = 'text'
145
- result['content'] = content
146
- elif valType == 'symbol':
147
- name = value['name']
148
- symbolRecord = self.getSymbolRecord(name)
149
- # if symbolRecord['hasValue']:
150
- handler = self.domainIndex[symbolRecord['domain']].valueHandler('symbol')
151
- result = handler(symbolRecord)
152
- # else:
153
- # # Call the given domain to handle a value
154
- # # domain = self.domainIndex[value['domain']]
155
- # handler = domain.valueHandler(value['type'])
156
- # if handler: result = handler(value)
290
+ for part in value.getContent(): # pyright: ignore[reportOptionalMemberAccess]
291
+ val = self.evaluate(part) # pyright: ignore[reportAttributeAccessIssue]
292
+ if val != None:
293
+ if isinstance(val, ECValue): val = str(val.getContent())
294
+ if val == None: val = ''
295
+ else: content += val
296
+ result.setValue(type='str', content=content)
297
+
157
298
  else:
158
299
  # Call the given domain to handle a value
159
- domain = self.domainIndex[value['domain']]
160
- handler = domain.valueHandler(value['type'])
300
+ domainName = value.getDomain() # type: ignore
301
+ if domainName == None: domainName = 'core'
302
+ domain = self.domainIndex[domainName]
303
+ handler = domain.valueHandler(value.getType()) # type: ignore
161
304
  if handler: result = handler(value)
162
305
 
163
306
  return result
164
307
 
165
- def constant(self, content, numeric):
166
- result = {}
167
- result['type'] = 'int' if numeric else 'text'
168
- result['content'] = content
169
- return result
170
-
171
- def evaluate(self, value):
172
- if value == None:
173
- result = {}
174
- result['type'] = 'text'
175
- result['content'] = ''
176
- return result
177
-
178
- result = self.doValue(value)
179
- if result:
180
- return result
181
- return None
182
-
183
- def getValue(self, value):
184
- return self.evaluate(value).content
185
-
186
- def getRuntimeValue(self, value):
308
+ # Get the runtime value of a value object (as a string or integer)
309
+ def textify(self, value):
187
310
  if value is None:
188
311
  return None
189
- v = self.evaluate(value)
190
- if v != None:
191
- content = v['content']
192
- if v['type'] == 'boolean':
193
- return True if content else False
194
- if v['type'] in ['int', 'float', 'text', 'object']:
195
- return content
196
- return ''
197
- return None
198
-
199
- def getSymbolContent(self, symbolRecord):
200
- if len(symbolRecord['value']) == 0:
201
- return None
202
- try: return symbolRecord['value'][symbolRecord['index']]
203
- except: RuntimeError(self, f'Cannot get content of symbol "{symbolRecord["name"]}"')
204
-
205
- def getSymbolValue(self, symbolRecord):
206
- if len(symbolRecord['value']) == 0:
312
+
313
+ if isinstance(value, dict):
314
+ value = value['object']
315
+ if isinstance(value, ECObject):
316
+ value = value.getValue()
317
+ if isinstance(value, ECValue): # type: ignore
318
+ v = self.evaluate(value) # type: ignore
319
+ else:
320
+ v = value
321
+ if v is None: return None
322
+ if isinstance(v, ECValue):
323
+ if v.getType() == 'object':
324
+ return value.getContent() # type: ignore
325
+ return v.getContent()
326
+ return v
327
+
328
+ # Get the content of a symbol
329
+ def getSymbolContent(self, record):
330
+ if len(record['value']) == 0:
207
331
  return None
208
- try: value = symbolRecord['value'][symbolRecord['index']]
209
- except: RuntimeError(self, f'Cannot get value of symbol "{symbolRecord["name"]}"')
210
- copy = deepcopy(value)
332
+ try: return record['value'][record['index']]
333
+ except: raise RuntimeError(self, f'Cannot get content of symbol "{record["name"]}"')
334
+
335
+ # Get the value of a symbol as an ECValue
336
+ def getSymbolValue(self, record):
337
+ object = self.getObject(record)
338
+ self.checkObjectType(object, ECObject)
339
+ value = object.getValue() # type: ignore
340
+ if value is None:
341
+ raise NoValueRuntimeError(self, f'Symbol "{record["name"]}" has no value')
342
+ # copy = deepcopy(value)
343
+ copy = ECValue(domain=value.getDomain(),type=value.getType(),content=deepcopy(value.getContent()))
211
344
  return copy
212
345
 
213
- def putSymbolValue(self, symbolRecord, value):
214
- if symbolRecord['locked']:
215
- name = symbolRecord['name']
216
- RuntimeError(self, f'Symbol "{name}" is locked')
217
- if symbolRecord['value'] == None or symbolRecord['value'] == []:
218
- symbolRecord['value'] = [value]
219
- else:
220
- index = symbolRecord['index']
221
- if index == None:
222
- index = 0
223
- symbolRecord['value'][index] = value
346
+ # Set the value of a symbol to either an ECValue or a raw value
347
+ def putSymbolValue(self, record, value):
348
+ variable = self.getObject(record)
349
+ if variable.isLocked(): # type: ignore
350
+ name = record['name']
351
+ raise RuntimeError(self, f'Symbol "{name}" is locked')
352
+ variable.setValue(self.getValueOf(value)) # type: ignore
224
353
 
225
354
  def encode(self, value):
226
355
  return value
@@ -287,9 +416,9 @@ class Program:
287
416
  return
288
417
 
289
418
  def releaseParent(self):
290
- if self.parent.waiting and self.parent.program.running:
291
- self.parent.waiting = False
292
- self.parent.program.run(self.parent.pc)
419
+ if self.parent and self.parent.waiting and self.parent.program.running: # type: ignore[union-attr]
420
+ self.parent.waiting = False # type: ignore[union-attr]
421
+ self.parent.program.run(self.parent.pc) # type: ignore[union-attr]
293
422
 
294
423
  # Flush the queue
295
424
  def flush(self, pc):
@@ -297,12 +426,21 @@ class Program:
297
426
  self.pc = pc
298
427
  while self.running:
299
428
  command = self.code[self.pc]
429
+
430
+ # Check if debugger wants to halt before executing this command
431
+ if self.debugger != None:
432
+ # pc==1 is the first real command (pc==0 is the debug loader)
433
+ is_first = (self.pc == 1)
434
+ if self.debugger.checkIfHalt(is_first):
435
+ # Debugger says halt - break out and wait for user
436
+ break
437
+
300
438
  domainName = command['domain']
301
439
  if domainName == None:
302
440
  self.pc += 1
303
441
  else:
304
442
  keyword = command['keyword']
305
- if self.debugStep and command['debug']:
443
+ if self.debugStep and 'debug' in command:
306
444
  lino = command['lino'] + 1
307
445
  line = self.script.lines[command['lino']].strip()
308
446
  print(f'{self.name}: Line {lino}: {domainName}:{keyword}: {line}')
@@ -311,7 +449,12 @@ class Program:
311
449
  if handler:
312
450
  command = self.code[self.pc]
313
451
  command['program'] = self
314
- self.pc = handler(command)
452
+ if self.breakpoint:
453
+ pass # Place a breakpoint here for a debugger to catch
454
+ try:
455
+ self.pc = handler(command)
456
+ except Exception as e:
457
+ raise RuntimeError(self, f'Error during execution of {domainName}:{keyword}: {str(e)}')
315
458
  # Deal with 'exit'
316
459
  if self.pc == -1:
317
460
  queue = deque()
@@ -325,63 +468,48 @@ class Program:
325
468
  # Run the script at a given PC value
326
469
  def run(self, pc):
327
470
  global queue
328
- item = Object()
329
- item.program = self
330
- item.pc = pc
471
+ item = ECValue()
472
+ item.program = self # type: ignore
473
+ item.pc = pc # type: ignore
331
474
  queue.append(item)
475
+ self.running = True
332
476
 
333
477
  def kill(self):
334
478
  self.running = False
335
479
  if self.parent != None: self.parent.program.kill()
336
480
 
337
- def setExternalControl(self):
338
- self.externalControl = True
339
-
340
481
  def nonNumericValueError(self):
341
482
  FatalError(self.compiler, 'Non-numeric value')
342
483
 
343
- def variableDoesNotHoldAValueError(self, name):
344
- raise FatalError(self.compiler, f'Variable "{name}" does not hold a value')
345
-
346
- def noneValueError(self, name):
347
- raise FatalError(self.compiler, f'Value is None')
348
-
349
484
  def compare(self, value1, value2):
350
- val1 = self.evaluate(value1)
351
- val2 = self.evaluate(value2)
352
- if val1 == None or val2 == None:
485
+ if value1 == None or value2 == None:
486
+ RuntimeError(self, 'Cannot compare a value with None')
487
+ v1 = self.textify(value1)
488
+ v2 = self.textify(value2)
489
+ if v1 == None or v2 == None:
490
+ raise RuntimeError(self, 'Both items must have a value for comparison')
491
+ if type(v1) == str and type(v2) == str:
492
+ # String comparison
493
+ if v1 < v2: return -1
494
+ if v1 > v2: return 1
353
495
  return 0
354
- v1 = val1['content']
355
- v2 = val2['content']
356
- # if v1 == None and v2 != None or v1 != None and v2 == None:
357
- # return 0
358
- if v1 == None and v2 != None: return -1
359
- elif v2 == None and v1 != None: return 1
360
- if v1 != None and val1['type'] == 'int':
361
- if not val2['type'] == 'int':
362
- if type(v2) is str:
363
- try:
364
- v2 = int(v2)
365
- except:
366
- lino = self.code[self.pc]['lino'] + 1
367
- RuntimeError(None, f'Line {lino}: \'{v2}\' is not an integer')
368
- else:
369
- if v2 != None and val2['type'] == 'int':
370
- v2 = str(v2)
371
- if v1 == None:
372
- v1 = ''
373
- if v2 == None:
374
- v2 = ''
375
- if type(v1) == int:
376
- if type(v2) != int:
377
- v1 = f'{v1}'
378
- if type(v2) == int:
379
- if type(v1) != int:
380
- v2 = f'{v2}'
381
- if v1 > v2:
382
- return 1
383
- if v1 < v2:
496
+
497
+ if type(v1) is str:
498
+ try:
499
+ v1 = int(v1)
500
+ except:
501
+ print(f'{v1} is not an integer')
502
+ return None
503
+ if type(v2) is str:
504
+ try:
505
+ v2 = int(v2)
506
+ except:
507
+ print(f'{v2} is not an integer')
508
+ return None
509
+ if v1 < v2: # type: ignore[operator]
384
510
  return -1
511
+ if v1 > v2: # type: ignore[operator]
512
+ return 1
385
513
  return 0
386
514
 
387
515
  # Set up a message handler
@@ -396,7 +524,7 @@ class Program:
396
524
  # This is the program launcher
397
525
  def Main():
398
526
  if (len(sys.argv) > 1):
399
- Program(sys.argv[1]).start()
527
+ Program(' '.join(sys.argv[1:])).start()
400
528
  else:
401
529
  Program('-v')
402
530
 
easycoder/ec_psutil.py ADDED
@@ -0,0 +1,48 @@
1
+ from easycoder import Handler, ECValue
2
+ import os
3
+ from psutil import Process
4
+
5
+ class PSUtil(Handler):
6
+
7
+ def __init__(self, compiler):
8
+ Handler.__init__(self, compiler)
9
+
10
+ def getName(self):
11
+ return 'psutil'
12
+
13
+ #############################################################################
14
+ # Keyword handlers
15
+
16
+ #############################################################################
17
+ # Compile a value in this domain
18
+ def compileValue(self):
19
+ value = ECValue(domain=self.getName())
20
+ if self.tokenIs('the'):
21
+ self.nextToken()
22
+ token = self.getToken()
23
+ if token in ['mem', 'memory']:
24
+ value.setType('memory')
25
+ return value
26
+ return None
27
+
28
+ #############################################################################
29
+ # Modify a value or leave it unchanged.
30
+ def modifyValue(self, value):
31
+ return value
32
+
33
+ #############################################################################
34
+ # Value handlers
35
+
36
+ def v_memory(self, v):
37
+ process: Process = Process(os.getpid())
38
+ megabytes: float = process.memory_info().rss / (1024 * 1024)
39
+ return ECValue(domain=self.getName(), type='float', content=megabytes)
40
+
41
+ #############################################################################
42
+ # Compile a condition
43
+ def compileCondition(self):
44
+ condition = {}
45
+ return condition
46
+
47
+ #############################################################################
48
+ # Condition handlers
easycoder/ec_timestamp.py CHANGED
@@ -5,6 +5,7 @@ def getTimestamp(t):
5
5
  tz = pytz.timezone('GB') # Localize this!
6
6
  dt = datetime.fromtimestamp(t)
7
7
  # print(f'{dt} + {tz.dst(dt).seconds}')
8
- return int(t) + tz.dst(dt).seconds
8
+ dst = tz.dst(dt)
9
+ return int(t) + (dst.seconds if dst else 0)
9
10
 
10
11
  # Usage: print(getTimestamp(time.time()))