easycoder 241211.3__py2.py3-none-any.whl → 250116.3__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


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

@@ -0,0 +1,274 @@
1
+ from .ec_classes import FatalError, RuntimeError, Object
2
+ from .ec_handler import Handler
3
+ from .ec_gutils import GUtils
4
+ import PySimpleGUI as psg
5
+ import json
6
+
7
+ class Graphics(Handler):
8
+
9
+ def __init__(self, compiler):
10
+ Handler.__init__(self, compiler)
11
+ self.utils = GUtils()
12
+ self.eventHandlers = {}
13
+
14
+ def getName(self):
15
+ return 'graphics'
16
+
17
+ #############################################################################
18
+ # Keyword handlers
19
+
20
+ def k_add(self, command):
21
+ elements = []
22
+ token = self.nextToken()
23
+ if self.isSymbol():
24
+ symbolRecord = self.getSymbolRecord()
25
+ name = symbolRecord['name']
26
+ if symbolRecord['keyword'] == 'layout':
27
+ elements.append(name)
28
+ command['args'] = name
29
+ else: FatalError(self.compiler.program, f'\'{name}\' is not a layout')
30
+ elif token[0:2] == 'g_':
31
+ command['type'] = token
32
+ command['args'] = self.utils.getArgs(self)
33
+ else: return False
34
+ if self.nextIs('to'):
35
+ if self.nextIsSymbol():
36
+ symbolRecord = self.getSymbolRecord()
37
+ if symbolRecord['keyword'] == 'layout':
38
+ command['target'] = symbolRecord['name']
39
+ self.addCommand(command)
40
+ return True
41
+ return False
42
+
43
+ def r_add(self, command):
44
+ target = self.getVariable(command['target'])
45
+ type = command['type']
46
+ args = command['args']
47
+ if not 'layout' in target:
48
+ target['layout'] = []
49
+ if args[0] == '{':
50
+ layout = json.loads(self.getRuntimeValue(json.loads(args)))
51
+ default = self.utils.getDefaultArgs(type)
52
+ for n in range(0, len(layout)):
53
+ args = self.utils.decode(default, layout[n])
54
+ target['layout'].append(self.utils.createElement(type, args))
55
+ else:
56
+ v = self.getVariable(args)
57
+ target['layout'].append(v['layout'])
58
+ return self.nextPC()
59
+
60
+ def k_capture(self, command):
61
+ if self.nextIs('event'):
62
+ if self.nextIs('as'):
63
+ if self.nextIsSymbol():
64
+ record = self.getSymbolRecord()
65
+ command['target'] = record['name']
66
+ self.addCommand(command)
67
+ return True
68
+ return False
69
+
70
+ def r_capture(self, command):
71
+ target = self.getVariable(command['target'])
72
+ self.putSymbolValue(target, self.getConstant(self.eventValues))
73
+ return self.nextPC()
74
+
75
+ def k_close(self, command):
76
+ if self.nextIsSymbol():
77
+ symbolRecord = self.getSymbolRecord()
78
+ if symbolRecord['keyword'] == 'window':
79
+ command['target'] = symbolRecord['name']
80
+ self.add(command)
81
+ return True
82
+ return False
83
+
84
+ def r_close(self, command):
85
+ target = self.getVariable(command['target'])
86
+ target['window'].close()
87
+ return self.nextPC()
88
+
89
+ # create {window} layout {layout}
90
+ # create {element} {args...}
91
+ def k_create(self, command):
92
+ if self.nextIsSymbol():
93
+ symbolRecord = self.getSymbolRecord()
94
+ type = symbolRecord['keyword']
95
+ command['type'] = type
96
+ command['name'] = symbolRecord['name']
97
+ if type == 'window':
98
+ command['title'] = self.nextValue()
99
+ if self.nextIs('layout'):
100
+ if self.nextIsSymbol():
101
+ symbolRecord = self.getSymbolRecord()
102
+ if symbolRecord['keyword'] == 'layout':
103
+ command['layout'] = symbolRecord['name']
104
+ self.addCommand(command)
105
+ return True
106
+ return False
107
+
108
+ def r_create(self, command):
109
+ type = command['type']
110
+ record = self.getVariable(command['name'])
111
+ if type == 'window':
112
+ layout = self.getVariable(command['layout'])
113
+ title = self.getRuntimeValue(command['title'])
114
+ self.program.window = psg.Window(title, layout['layout'], finalize=True)
115
+ self.program.run(self.nextPC())
116
+ self.mainLoop()
117
+ self.program.kill()
118
+ return 0
119
+ else:
120
+ RuntimeError(self.program, 'Variable is not a window or an element')
121
+
122
+ def k_layout(self, command):
123
+ return self.compileVariable(command)
124
+
125
+ def r_layout(self, command):
126
+ return self.nextPC()
127
+
128
+ def k_on(self, command):
129
+ token = self.nextToken()
130
+ if token == 'event':
131
+ command['key'] = self.nextValue()
132
+ command['goto'] = self.getPC() + 2
133
+ self.add(command)
134
+ self.nextToken()
135
+ pcNext = self.getPC()
136
+ cmd = {}
137
+ cmd['domain'] = 'core'
138
+ cmd['lino'] = command['lino']
139
+ cmd['keyword'] = 'gotoPC'
140
+ cmd['goto'] = 0
141
+ cmd['debug'] = False
142
+ self.addCommand(cmd)
143
+ self.compileOne()
144
+ cmd = {}
145
+ cmd['domain'] = 'core'
146
+ cmd['lino'] = command['lino']
147
+ cmd['keyword'] = 'stop'
148
+ cmd['debug'] = False
149
+ self.addCommand(cmd)
150
+ # Fixup the link
151
+ self.getCommandAt(pcNext)['goto'] = self.getPC()
152
+ return True
153
+ return False
154
+
155
+ def r_on(self, command):
156
+ key = self.getRuntimeValue(command['key'])
157
+ pc = command['goto']
158
+ self.eventHandlers[key] = lambda: self.run(pc)
159
+ return self.nextPC()
160
+
161
+ def k_popup(self, command):
162
+ command['message'] = self.nextValue()
163
+ self.addCommand(command)
164
+ return True
165
+
166
+ def r_popup(self, command):
167
+ psg.popup(self.getRuntimeValue(command['message']))
168
+ return self.nextPC()
169
+
170
+ def k_set(self, command):
171
+ if self.nextIsSymbol():
172
+ record = self.getSymbolRecord()
173
+ keyword = record['keyword']
174
+ if keyword == 'layout':
175
+ command['target'] = record['name']
176
+ if self.peek() == 'to':
177
+ self.nextToken()
178
+ command['type'] = self.nextToken()
179
+ command['args'] = self.utils.getArgs(self)
180
+ else: command['args'] = None
181
+ self.addCommand(command)
182
+ return True
183
+ elif keyword == 'event':
184
+ pass
185
+ return False
186
+
187
+ def r_set(self, command):
188
+ target = self.getVariable(command['target'])
189
+ target['layout'] = []
190
+ type = command['type']
191
+ args = command['args']
192
+ if args != None:
193
+ if args[0] == '{':
194
+ layout = json.loads(self.getRuntimeValue(json.loads(args)))
195
+ default = self.utils.getDefaultArgs(type)
196
+ for n in range(0, len(layout)):
197
+ args = self.utils.decode(default, layout[n])
198
+ target['layout'].append(self.utils.createElement(type, args))
199
+ else:
200
+ v = self.getVariable(args)
201
+ target['layout'].append(v['layout'])
202
+ return self.nextPC()
203
+
204
+ def k_window(self, command):
205
+ return self.compileVariable(command)
206
+
207
+ def r_window(self, command):
208
+ return self.nextPC()
209
+
210
+ #############################################################################
211
+ # Compile a value in this domain
212
+ def compileValue(self):
213
+ value = {}
214
+ value['domain'] = self.getName()
215
+ token = self.getToken()
216
+ if self.isSymbol():
217
+ value['name'] = token
218
+ symbolRecord = self.getSymbolRecord()
219
+ keyword = symbolRecord['keyword']
220
+ if keyword == 'event':
221
+ value['type'] = 'symbol'
222
+ return value
223
+ return None
224
+
225
+ value['type'] = token
226
+
227
+ if token == 'test':
228
+ value = {}
229
+ value['type'] = 'text'
230
+ value['content'] = 'test'
231
+ return value
232
+
233
+ return None
234
+
235
+ #############################################################################
236
+ # Modify a value or leave it unchanged.
237
+ def modifyValue(self, value):
238
+ return value
239
+
240
+ #############################################################################
241
+ # Value handlers
242
+
243
+ # This is used by the expression evaluator to get the value of a symbol
244
+ def v_symbol(self, symbolRecord):
245
+ if symbolRecord['keyword'] == 'event':
246
+ return self.getSymbolValue(symbolRecord)
247
+ else:
248
+ return None
249
+
250
+ def v_test(self, v):
251
+ return v
252
+
253
+ #############################################################################
254
+ # Compile a condition
255
+ def compileCondition(self):
256
+ condition = {}
257
+ return condition
258
+
259
+ #############################################################################
260
+ # Condition handlers
261
+
262
+ #############################################################################
263
+ # The main loop
264
+ def mainLoop(self):
265
+ while True:
266
+ event, values = self.program.window.Read(timeout=100)
267
+ if event == psg.WINDOW_CLOSED or event == "EXIT":
268
+ break
269
+ if event == '__TIMEOUT__': self.program.flushCB()
270
+ else:
271
+ if event in self.eventHandlers:
272
+ self.eventValues = values
273
+ self.eventHandlers[event]()
274
+
easycoder/ec_gutils.py ADDED
@@ -0,0 +1,52 @@
1
+ import PySimpleGUI as psg
2
+ import json
3
+
4
+ class GUtils:
5
+
6
+ # Parse a set of compile-time arguments
7
+ def getArgs(self, handler):
8
+ args = []
9
+ while True:
10
+ key = handler.nextToken()
11
+ value = json.dumps(handler.nextValue())
12
+ args.append(f'{key}={value}')
13
+ if handler.peek() == 'and':
14
+ handler.nextToken()
15
+ else: break
16
+ v = {}
17
+ v['type'] = 'text'
18
+ v['content'] = json.dumps(args)
19
+ return json.dumps(v)
20
+
21
+ # Get the default args for a graphic element
22
+ def getDefaultArgs(self, type):
23
+ args = {}
24
+ if type == 'g_text':
25
+ args['text'] = '(empty)'
26
+ args['expand_x'] = False
27
+ elif type == 'g_input':
28
+ args['key'] = None
29
+ args['size'] = (None, None)
30
+ elif type == 'g_button':
31
+ args['button_text'] = '(empty)'
32
+ return args
33
+
34
+ # Decode an argument at runtime
35
+ def decode(self, args, text):
36
+ p = text.find('=')
37
+ if p > 0:
38
+ key = text[0:p]
39
+ value = json.loads(text[p+1:])['content']
40
+ args[key] = value
41
+ return args
42
+ return None
43
+
44
+ # Create an element
45
+ def createElement(self, type, args):
46
+ if type == 'g_text': return psg.Text(text=args['text'], expand_x=args['expand_x'])
47
+ elif type == 'g_input':
48
+ size = args['size'].split()
49
+ size = (size[0], size[1])
50
+ return psg.Input(key=args['key'], size=size)
51
+ elif type == 'g_button': return psg.Button(button_text=args['button_text'])
52
+ else: return None
easycoder/ec_handler.py CHANGED
@@ -25,6 +25,7 @@ class Handler:
25
25
  self.getCommandAt = compiler.getCommandAt
26
26
  self.compileOne = compiler.compileOne
27
27
  self.compileFromHere = compiler.compileFromHere
28
+ self.compileConstant = compiler.compileConstant
28
29
 
29
30
  self.code = self.program.code
30
31
  self.add = self.program.add
easycoder/ec_program.py CHANGED
@@ -1,29 +1,38 @@
1
1
  import time, json, sys
2
2
  from copy import deepcopy
3
3
  from collections import deque
4
- from .ec_classes import Script, Token, FatalError, RuntimeError
4
+ from .ec_classes import Script, Token, FatalError, RuntimeError, Object
5
5
  from .ec_compiler import Compiler
6
6
  from .ec_core import Core
7
7
  import importlib
8
8
  from importlib.metadata import version
9
9
 
10
+ # Flush the queue
11
+ def flush():
12
+ global queue
13
+ while len(queue):
14
+ item = queue.popleft()
15
+ item.program.flush(item.pc)
16
+
10
17
  class Program:
11
18
 
12
19
  def __init__(self, argv):
20
+ global queue
13
21
  print(f'EasyCoder version {version("easycoder")}')
14
- scriptName = None
15
- if len(argv)>0:
16
- scriptName = argv[0]
17
- else:
22
+ if len(argv) == 0:
18
23
  print('No script supplied')
19
- exit();
24
+ exit()
25
+ self.classes=[Core]
26
+ scriptName = argv
27
+ if scriptName.endswith('.ecg'):
28
+ from .ec_graphics import Graphics
29
+ self.classes.append(Graphics)
20
30
 
21
- # print('Domains:',domains)
22
31
  f = open(scriptName, 'r')
23
32
  source = f.read()
24
33
  f.close()
34
+ queue = deque()
25
35
  self.argv = argv
26
- self.classes=[Core]
27
36
  self.domains = []
28
37
  self.domainIndex = {}
29
38
  self.name = '<anon>'
@@ -38,8 +47,14 @@ class Program:
38
47
  self.value = self.compiler.value
39
48
  self.condition = self.compiler.condition
40
49
  self.processClasses()
41
- self.queue = deque()
50
+ self.externalControl = False
51
+ self.running = True
42
52
 
53
+ def start(self, parent=None, module = None, exports=[]):
54
+ self.parent = parent
55
+ self.exports = exports
56
+ if module != None:
57
+ module['child'] = self
43
58
  startCompile = time.time()
44
59
  self.tokenise(self.script)
45
60
  if self.compiler.compileFrom(0, []):
@@ -57,13 +72,24 @@ class Program:
57
72
  self.run(0)
58
73
  else:
59
74
  self.compiler.showWarnings()
60
- return
75
+
76
+ # If this is the main script and there's no graphics, run a main loop
77
+ if parent == None and self.externalControl == False:
78
+ while True:
79
+ if self.running == True:
80
+ flush()
81
+ time.sleep(0.1)
82
+ else:
83
+ break
61
84
 
62
85
  # Import a plugin
63
86
  def importPlugin(self, source):
64
87
  args=source.split(':')
88
+ if len(args)<2:
89
+ RuntimeError(None, f'Invalid plugin spec "{source}"')
65
90
  idx=args[0].rfind('/')
66
91
  if idx<0:
92
+ sys.path.append('.')
67
93
  module=args[0]
68
94
  else:
69
95
  sys.path.append(args[0][0:idx])
@@ -81,7 +107,7 @@ class Program:
81
107
  handler = clazz(self.compiler)
82
108
  self.domains.append(handler)
83
109
  self.domainIndex[handler.getName()] = handler
84
-
110
+
85
111
  # Get the domain list
86
112
  def getDomains(self):
87
113
  return self.domains
@@ -93,11 +119,11 @@ class Program:
93
119
  def getSymbolRecord(self, name):
94
120
  try:
95
121
  target = self.code[self.symbols[name]]
122
+ if target['import'] != None:
123
+ target = target['import']
124
+ return target
96
125
  except:
97
- RuntimeError(self.compiler.program, f'Unknown symbol \'{name}\'')
98
- return None
99
-
100
- return target
126
+ RuntimeError(self, f'Unknown symbol \'{name}\'')
101
127
 
102
128
  def doValue(self, value):
103
129
  if value == None:
@@ -124,7 +150,7 @@ class Program:
124
150
  name = value['name']
125
151
  symbolRecord = self.getSymbolRecord(name)
126
152
  if symbolRecord['value'] == [None]:
127
- RuntimeWarning(self.compiler.program, f'Variable "{name}" has no value')
153
+ RuntimeWarning(self.program, f'Variable "{name}" has no value')
128
154
  return None
129
155
  handler = self.domainIndex[symbolRecord['domain']].valueHandler('symbol')
130
156
  result = handler(symbolRecord)
@@ -179,6 +205,9 @@ class Program:
179
205
  return copy
180
206
 
181
207
  def putSymbolValue(self, symbolRecord, value):
208
+ if symbolRecord['locked']:
209
+ name = symbolRecord['name']
210
+ RuntimeError(self, f'Symbol "{name}" is locked')
182
211
  if symbolRecord['value'] == None or symbolRecord['value'] == []:
183
212
  symbolRecord['value'] = [value]
184
213
  else:
@@ -195,80 +224,115 @@ class Program:
195
224
 
196
225
  # Tokenise the script
197
226
  def tokenise(self, script):
198
- index = 0
199
- lino = 0
200
- for line in script.lines:
227
+ token = ''
228
+ literal = False
229
+ for lino in range(0, len(script.lines)):
230
+ line = script.lines[lino]
201
231
  length = len(line)
202
- token = ''
203
- inSpace = True
232
+ if length == 0:
233
+ continue
234
+ # Look for the first non-space
204
235
  n = 0
205
- while n < length:
236
+ while n < length and line[n].isspace():
237
+ n += 1
238
+ # The whole line may be empty
239
+ if n == length:
240
+ if literal:
241
+ token += '\n'
242
+ continue
243
+ # If in an unfinished literal, the first char must be a backtick to continue adding to it
244
+ if literal:
245
+ if line[n] != '`':
246
+ # Close the current token
247
+ if len(token) > 0:
248
+ script.tokens.append(Token(lino, token))
249
+ token = ''
250
+ literal = False
251
+ n += 1
252
+ for n in range(n, length):
206
253
  c = line[n]
207
- if len(c.strip()) == 0:
208
- if (inSpace):
209
- n += 1
254
+ # Test if we are in a literal
255
+ if not literal:
256
+ if c.isspace():
257
+ if len(token) > 0:
258
+ script.tokens.append(Token(lino, token))
259
+ token = ''
210
260
  continue
211
- script.tokens.append(Token(lino, token))
212
- index += 1
213
- token = ''
214
- inSpace = True
215
- n += 1
216
- continue
217
- inSpace = False
261
+ elif c == '!':
262
+ break
263
+ # Test for the start or end of a literal
218
264
  if c == '`':
219
- m = n
220
- n += 1
221
- while n < len(line) - 1:
222
- if line[n] == '`':
223
- break
224
- n += 1
225
- # n += 1
226
- token = line[m:n+1]
227
- elif c == '!':
228
- break
265
+ if literal:
266
+ token += c
267
+ literal = False
268
+ else:
269
+ token += c
270
+ literal = True
271
+ m = n
272
+ continue
229
273
  else:
230
274
  token += c
231
- n += 1
232
275
  if len(token) > 0:
233
- script.tokens.append(Token(lino, token))
234
- index += 1
235
- lino += 1
276
+ if literal:
277
+ token += '\n'
278
+ else:
279
+ script.tokens.append(Token(lino, token))
280
+ token = ''
236
281
  return
237
282
 
283
+ def releaseParent(self):
284
+ if self.parent.waiting and self.parent.program.running:
285
+ self.parent.waiting = False
286
+ self.parent.program.run(self.parent.pc)
287
+
288
+ def flushCB(self):
289
+ flush()
290
+
291
+ # Flush the queue
292
+ def flush(self, pc):
293
+ global queue
294
+ self.pc = pc
295
+ while self.running:
296
+ command = self.code[self.pc]
297
+ domainName = command['domain']
298
+ if domainName == None:
299
+ self.pc += 1
300
+ else:
301
+ keyword = command['keyword']
302
+ if self.debugStep and command['debug']:
303
+ lino = command['lino'] + 1
304
+ line = self.script.lines[command['lino']].strip()
305
+ print(f'{self.name}: Line {lino}: {domainName}:{keyword}: {line}')
306
+ domain = self.domainIndex[domainName]
307
+ handler = domain.runHandler(keyword)
308
+ if handler:
309
+ command = self.code[self.pc]
310
+ command['program'] = self
311
+ self.pc = handler(command)
312
+ # Deal with 'exit'
313
+ if self.pc == -1:
314
+ queue = deque()
315
+ if self.parent != None:
316
+ self.releaseParent()
317
+ else:
318
+ self.running = False
319
+ break
320
+ elif self.pc == None or self.pc == 0 or self.pc >= len(self.code):
321
+ break
322
+
238
323
  # Run the script
239
324
  def run(self, pc):
240
- # print(f'Run from {pc}')
241
- length = len(self.queue)
242
- self.queue.append(pc)
243
- if length > 0:
244
- return
245
-
246
- while len(self.queue):
247
- self.pc = self.queue.popleft()
248
- while True:
249
- command = self.code[self.pc]
250
- domainName = command['domain']
251
- if domainName == None:
252
- self.pc += 1
253
- else:
254
- keyword = command['keyword']
255
- if self.debugStep and command['debug']:
256
- lino = command['lino'] + 1
257
- line = self.script.lines[command['lino']].strip()
258
- print(f'{self.name}: Line {lino}: {domainName}:{keyword}: {line}')
259
- domain = self.domainIndex[domainName]
260
- handler = domain.runHandler(keyword)
261
- if handler:
262
- command = self.code[self.pc]
263
- command['program'] = self
264
- self.pc = handler(command)
265
- try:
266
- if self.pc == 0 or self.pc >= len(self.code):
267
- return 0
268
- except:
269
- return 0
270
- if self.pc < 0:
271
- return -1
325
+ global queue
326
+ item = Object()
327
+ item.program = self
328
+ item.pc = pc
329
+ queue.append(item)
330
+
331
+ def kill(self):
332
+ self.running = False
333
+
334
+ def setExternalControl(self):
335
+ self.externalControl = True
272
336
 
273
337
  def nonNumericValueError(self):
274
338
  FatalError(self.compiler, 'Non-numeric value')
@@ -315,9 +379,22 @@ class Program:
315
379
  return -1
316
380
  return 0
317
381
 
382
+ # Set up a message handler
383
+ def onMessage(self, pc):
384
+ self.onMessagePC = pc
385
+
386
+ # Handle a message from our parent program
387
+ def handleMessage(self, message):
388
+ self.message = message
389
+ self.run(self.onMessagePC)
390
+
318
391
  # This is the program launcher
319
392
  def Main():
320
393
  if (len(sys.argv) > 1):
321
- Program(sys.argv[1:])
394
+ Program(sys.argv[1]).start()
322
395
  else:
323
396
  print('Syntax: easycoder <scriptname> [plugins]')
397
+
398
+ if __name__ == '__main__':
399
+ Main()
400
+