Skip to content

Commit 00a3b6b

Browse files
committed
v241231.1
1 parent 50fad39 commit 00a3b6b

10 files changed

+275
-50
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,17 @@ Here in the repository is a folder called `scripts` containing some sample scrip
4949
`fizzbuzz.ecs` is a simple programming challenge often given at job interviews
5050

5151
## Graphical programmming
52-
**_EasyCoder_** is currently being extended to include a graphical programming environment. A single demo script `graphics-demo.ecs` is included in the `scripts` directory. To run it, first install the Python graphics library if it's not already present on your system. On Linux this is done with `sudo apt install python3-tk`. On Windows it's `pip install tk`. Then give the command `easycoder -g scripts/graphics-demo.ecs`.
52+
**_EasyCoder_** includes a graphical programming environment that is in the early stages of development. A couple of demo scripts are included in the `scripts` directory. To run them, first install the Python `kivy` graphics library if it's not already present on your system. This is done with `pip install kivy`. Then run a script using `easycoder {scriptname}.ecg`.
5353

54-
As development progresses this demo script will be extended to include new features as they are added. **_EasyCoder_** graphics are handled by a library module, `ec_renderer` that can be used outside of the **_EasyCoder_** environment, in other Python programs.
54+
Graphical scripts look much like any other script but their file names must use the extension `.ecg` to signal to **_EasyCoder_** that it needs to load the graphics module. This allows the **_EasyCoder_** application to be used wherever Python is installed, in either a command-line or a graphical environment (but graphics will of course not be available in the former).
55+
56+
A couple of demo scripts are included in the `scripts` directory:
57+
58+
`graphics-demo.ecg` shows some of the elements that can be created, and demonstrates a variety of the graphical features of the language such as detecting when elements are clicked.
59+
60+
`wave.ecg` is a "Mexican Wave" simulation.
61+
62+
**_EasyCoder_** graphics are handled by a library module, `ec_renderer` that can be used outside of the **_EasyCoder_** environment, in other Python programs.
5563

5664
## EasyCoder programming reference
5765

-31.1 KB
Binary file not shown.
31.5 KB
Binary file not shown.
2.51 MB
Binary file not shown.

easycoder/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@
1010
from .ec_timestamp import *
1111
from .ec_value import *
1212

13-
__version__ = "241230.1"
13+
__version__ = "241231.1"

easycoder/ec_core.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,29 @@ def r_multiply(self, command):
696696
self.putSymbolValue(target, value)
697697
return self.nextPC()
698698

699+
# Negate a variable
700+
def k_negate(self, command):
701+
if self.nextIsSymbol():
702+
symbolRecord = self.getSymbolRecord()
703+
if symbolRecord['valueHolder']:
704+
command['target'] = self.getToken()
705+
self.add(command)
706+
return True
707+
self.warning(f'Core.negate: Variable "{symbolRecord["name"]}" does not hold a value')
708+
return False
709+
710+
def r_negate(self, command):
711+
symbolRecord = self.getVariable(command['target'])
712+
if not symbolRecord['valueHolder']:
713+
RuntimeError(self.program, f'{symbolRecord["name"]} does not hold a value')
714+
return None
715+
value = self.getSymbolValue(symbolRecord)
716+
if value == None:
717+
RuntimeError(self.program, f'{symbolRecord["name"]} has not been initialised')
718+
value['content'] *= -1
719+
self.putSymbolValue(symbolRecord, value)
720+
return self.nextPC()
721+
699722
# Define an object variable
700723
def k_object(self, command):
701724
return self.compileVariable(command)
@@ -976,7 +999,7 @@ def r_replace(self, command):
976999
content = self.getSymbolValue(templateRecord)['content']
9771000
original = self.getRuntimeValue(command['original'])
9781001
replacement = self.getRuntimeValue(command['replacement'])
979-
content = content.replace(original, replacement)
1002+
content = content.replace(original, str(replacement))
9801003
value = {}
9811004
value['type'] = 'text'
9821005
value['numeric'] = False
@@ -1417,7 +1440,7 @@ def incdec(self, command, mode):
14171440
# Compile a value in this domain
14181441
def compileValue(self):
14191442
value = {}
1420-
value['domain'] = 'core'
1443+
value['domain'] = self.getName()
14211444
token = self.getToken()
14221445
if self.isSymbol():
14231446
value['name'] = token

easycoder/ec_graphics.py

Lines changed: 93 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def __init__(self, compiler):
99
Handler.__init__(self, compiler)
1010

1111
def getName(self):
12-
return 'kivy'
12+
return 'graphics'
1313

1414
#############################################################################
1515
# Keyword handlers
@@ -204,12 +204,47 @@ def k_ellipse(self, command):
204204
def r_ellipse(self, command):
205205
return self.nextPC()
206206

207+
def r_getui(self, command):
208+
self.ui = self.renderer.getUI()
209+
return self.nextPC()
210+
207211
def k_image(self, command):
208212
return self.compileVariable(command)
209213

210214
def r_image(self, command):
211215
return self.nextPC()
212216

217+
# move an element
218+
def k_move(self, command):
219+
if self.nextIsSymbol():
220+
record = self.getSymbolRecord()
221+
type = record['keyword']
222+
if type in ['ellipse', 'rectangle']:
223+
command['target'] = record['id']
224+
token = self.nextToken()
225+
if token == 'to':
226+
command['x'] = self.nextValue()
227+
command['y'] = self.nextValue()
228+
self.add(command)
229+
return True
230+
elif token == 'by':
231+
command['keyword'] = 'moveBy'
232+
command['dx'] = self.nextValue()
233+
command['dy'] = self.nextValue()
234+
self.add(command)
235+
return True
236+
return False
237+
238+
def r_move(self, command):
239+
pos = (self.getRuntimeValue(command['x']), self.getRuntimeValue(command['y']))
240+
self.ui.moveElementTo(self.getRuntimeValue(command['target']), pos)
241+
return self.nextPC()
242+
243+
def r_moveBy(self, command):
244+
dist = (self.getRuntimeValue(command['dx']), self.getRuntimeValue(command['dy']))
245+
self.ui.moveElementBy(self.getRuntimeValue(command['target']), dist)
246+
return self.nextPC()
247+
213248
def k_on(self, command):
214249
token = self.nextToken()
215250
if token in ['click', 'tap']:
@@ -255,37 +290,6 @@ def r_on(self, command):
255290
RuntimeError(self.program, f'{record['name']} is not a clickable object')
256291
return self.nextPC()
257292

258-
# move an element
259-
def k_move(self, command):
260-
if self.nextIsSymbol():
261-
record = self.getSymbolRecord()
262-
type = record['keyword']
263-
if type in ['ellipse', 'rectangle']:
264-
command['target'] = record['id']
265-
token = self.nextToken()
266-
if token == 'to':
267-
command['x'] = self.nextValue()
268-
command['y'] = self.nextValue()
269-
self.add(command)
270-
return True
271-
elif token == 'by':
272-
command['keyword'] = 'moveBy'
273-
command['dx'] = self.nextValue()
274-
command['dy'] = self.nextValue()
275-
self.add(command)
276-
return True
277-
return False
278-
279-
def r_move(self, command):
280-
pos = (self.getRuntimeValue(command['x']), self.getRuntimeValue(command['y']))
281-
self.ui.moveElementTo(self.getRuntimeValue(command['target']), pos)
282-
return self.nextPC()
283-
284-
def r_moveBy(self, command):
285-
dist = (self.getRuntimeValue(command['dx']), self.getRuntimeValue(command['dy']))
286-
self.ui.moveElementBy(self.getRuntimeValue(command['target']), dist)
287-
return self.nextPC()
288-
289293
def k_rectangle(self, command):
290294
return self.compileVariable(command)
291295

@@ -306,13 +310,22 @@ def k_render(self, command):
306310

307311
def r_render(self, command):
308312
self.ui = self.renderer.getUI()
309-
ScreenSpec().render(self.getRuntimeValue(command['spec']), self.ui)
313+
try:
314+
ScreenSpec().render(self.getRuntimeValue(command['spec']), self.ui)
315+
except Exception as e:
316+
RuntimeError(self.program, e)
310317
return self.nextPC()
311318

312319
# run graphics
313320
def k_run(self, command):
314321
if self.nextIs('graphics'):
315322
self.add(command)
323+
cmd = {}
324+
cmd['domain'] = 'graphics'
325+
cmd['lino'] = command['lino'] + 1
326+
cmd['keyword'] = 'getui'
327+
cmd['debug'] = False
328+
self.addCommand(cmd)
316329
return True
317330
return False
318331

@@ -323,6 +336,33 @@ def r_run(self, command):
323336
self.program.run(self.nextPC())
324337
self.renderer.run()
325338

339+
# Set something
340+
def k_set(self, command):
341+
if self.nextIs('attribute'):
342+
command['attribute'] = self.nextValue()
343+
if self.nextIs('of'):
344+
if self.nextIsSymbol():
345+
record = self.getSymbolRecord()
346+
if record['keyword'] in ['ellipse', 'rectangle', 'text', 'image']:
347+
command['target'] = record['name']
348+
if self.nextIs('to'):
349+
command['value'] = self.nextValue()
350+
self.addCommand(command)
351+
return True
352+
else:
353+
FatalError(self.program.compiler, f'Invalid type: {record['keyword']}')
354+
else:
355+
FatalError(self.program.compiler, f'\'{self.getToken()}\' is not a variable')
356+
return False
357+
358+
def r_set(self, command):
359+
attribute = self.getRuntimeValue(command['attribute'])
360+
target = self.getVariable(command['target'])
361+
id = target['value'][target['index']]['content']
362+
value = self.getRuntimeValue(command['value'])
363+
self.ui.setAttribute(id, attribute, value)
364+
return self.nextPC()
365+
326366
#############################################################################
327367
# Modify a value or leave it unchanged.
328368
def modifyValue(self, value):
@@ -333,16 +373,24 @@ def modifyValue(self, value):
333373
def compileValue(self):
334374
value = {}
335375
value['domain'] = self.getName()
336-
if self.tokenIs('attribute'):
376+
if self.tokenIs('the'):
377+
self.nextToken()
378+
kwd = self.getToken()
379+
value['type'] = kwd
380+
if kwd == 'attribute':
337381
attribute = self.nextValue()
338382
if self.nextIs('of'):
339383
if self.nextIsSymbol():
340384
record = self.getSymbolRecord()
341385
if record['keyword'] in ['ellipse', 'rectangle']:
342-
value['type'] = 'attribute'
343386
value['attribute'] = attribute
344387
value['target'] = record['name']
345388
return value
389+
elif kwd == 'window':
390+
attribute = self.nextToken()
391+
if attribute in ['left', 'top', 'width', 'height']:
392+
value['attribute'] = attribute
393+
return value
346394
return None
347395

348396
#############################################################################
@@ -361,6 +409,16 @@ def v_attribute(self, v):
361409
except Exception as e:
362410
RuntimeError(self.program, e)
363411

412+
def v_window(self, v):
413+
try:
414+
attribute = v['attribute']
415+
value = {}
416+
value['type'] = 'int'
417+
value['content'] = int(round(self.ui.getWindowAttribute(attribute)))
418+
return value
419+
except Exception as e:
420+
RuntimeError(self.program, e)
421+
364422
#############################################################################
365423
# Compile a condition
366424
def compileCondition(self):

easycoder/ec_renderer.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ def getReal(val):
9797
return val * n / 100
9898
return val
9999

100-
print(spec.id)
101100
with self.canvas:
102101
if hasattr(spec, 'fill'):
103102
c = spec.fill
@@ -177,6 +176,18 @@ def on_touch_down(self, touch):
177176
def setOnClick(self, id, callback):
178177
self.getElement(id).cb = callback
179178

179+
def getWindowAttribute(self, attribute):
180+
if attribute == 'left':
181+
return Window.left
182+
elif attribute == 'top':
183+
return Window.top
184+
elif attribute == 'width':
185+
return Window.size[0]
186+
elif attribute == 'height':
187+
return Window.size[1]
188+
else:
189+
raise Exception(f'Unknown attribute: {attribute}')
190+
180191
def getAttribute(self, id, attribute):
181192
spec = self.getElement(id).spec
182193
if attribute == 'left':
@@ -189,16 +200,23 @@ def getAttribute(self, id, attribute):
189200
return spec.realsize[1]
190201
else:
191202
raise Exception(f'Unknown attribute: {attribute}')
192-
193-
def getWindowAttribute(self, attribute):
203+
204+
def setAttribute(self, id, attribute, value):
205+
spec = self.getElement(id).spec
194206
if attribute == 'left':
195-
return Window.left
196-
elif attribute == 'top':
197-
return Window.top
207+
spec.realpos = (value, spec.realsize[0])
208+
spec.item.pos = (value, spec.realsize[0])
209+
elif attribute == 'bottom':
210+
spec.realpos = (spec.realsize[0], value)
211+
spec.item.pos = (spec.realsize[0], value)
198212
elif attribute == 'width':
199-
return Window.size[0]
213+
spec.realsize = (value, spec.realsize[0])
214+
spec.item.size = (value, spec.realsize[0])
200215
elif attribute == 'height':
201-
return Window.size[1]
216+
spec.realsize = (spec.realsize[0], value)
217+
spec.item.size = (spec.realsize[0], value)
218+
else:
219+
raise Exception(f'Unknown attribute: {attribute}')
202220

203221
class Renderer(App):
204222

@@ -213,7 +231,7 @@ def flushQueue(self, dt):
213231
self.flush()
214232

215233
def build(self):
216-
Clock.schedule_interval(self.flushQueue, 0.05)
234+
Clock.schedule_interval(self.flushQueue, 0.01)
217235
self.ui = UI()
218236
return self.ui
219237

easycoder/ec_screenspec.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,3 @@ def render(self, spec, ui):
7575

7676
else:
7777
raise Exception('Spec is an unknown type')
78-

0 commit comments

Comments
 (0)