Skip to content

Commit 7dbd922

Browse files
author
allendowney
committed
Updating TurtleWorld
1 parent 31bd935 commit 7dbd922

5 files changed

Lines changed: 185 additions & 60 deletions

File tree

‎python2/Gui.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
import tkFont
6969

7070
from Tkinter import N, S, E, W
71-
from Tkinter import TOP, BOTTOM, LEFT, RIGHT, END
71+
from Tkinter import TOP, BOTTOM, LEFT, RIGHT, END, ALL
7272

7373

7474
class Gui(Tkinter.Tk):

‎python2/Makefile‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
PYDOC = pydoc
22

3+
DEST = /home/downey/public_html/greent/thinkpython
4+
35
pydoc:
46
$(PYDOC) -w Gui
57
$(PYDOC) -w World
@@ -9,19 +11,21 @@ pydoc:
911
$(PYDOC) -w AmoebaWorld
1012
$(PYDOC) -w Sync
1113
$(PYDOC) -w Lumpy
14+
rsync -a *.html $(DEST)
1215

1316
FILES = \
1417
AmoebaWorld.py Gui_test.py Lumpy_test.py TurmiteWorld_test.py\
1518
AmoebaWorld_test.py lumpy_example1.py mutex.py turtle_code.py\
1619
CellWorld.py lumpy_example2.py Sync.py TurtleWorld.py\
1720
CellWorld_test.py lumpy_example3.py Sync_test.py World.py\
1821
Gui.py Lumpy.py TurmiteWorld.py World_test.py\
19-
danger.gif words.txt TurtleWorld_test.py sync_code
22+
danger.gif words.txt TurtleWorld_test.py
2023

2124

2225

2326
python3:
24-
cp -r $(FILES) ../python3
27+
cp $(FILES) ../python3
28+
cp sync_code/*.py ../python3/sync_code
2529
cd ../python3; 2to3 -wn Gui.py
2630
cd ../python3; 2to3 -wn World.py
2731
cd ../python3; 2to3 -wn TurtleWorld.py

‎python2/Sync.py‎

Lines changed: 122 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@
1313
import string
1414
import time
1515

16-
from Tkinter import N, S, E, W, TOP, BOTTOM, LEFT, RIGHT, END
17-
from Gui import Gui, GuiCanvas
18-
1916
# the following definitions can be accessed in the simulator
2017

18+
current_thread = None
19+
2120
class Semaphore:
2221
"""Represents a semaphore in the simulator.
2322
@@ -94,16 +93,19 @@ def num_threads():
9493
sync = current_thread.column.p
9594
return len(sync.threads)
9695

97-
current_thread = None
9896

9997

10098
# make globals and locals for the simulator
10199

102-
sim_globals = globals()
103-
sim_locals = locals()
100+
sim_globals = copy.copy(globals())
101+
sim_locals = dict()
104102

105103
# anything defined after this point is not available inside the simulator
106104

105+
from Tkinter import N, S, E, W, TOP, BOTTOM, LEFT, RIGHT, END
106+
from Gui import Gui, GuiCanvas
107+
108+
107109
# get the version of Python
108110
v = sys.version.split()[0].split('.')
109111
major = int(v[0])
@@ -125,6 +127,11 @@ def __init__(self, filename=None):
125127
Gui.__init__(self)
126128
#self.geometry('1260x800+74+32')
127129
self.filename = filename
130+
self.namer = Namer()
131+
132+
self.locals = sim_locals
133+
self.globals = sim_globals
134+
128135
self.views = {}
129136
self.w = self
130137
self.threads = []
@@ -135,6 +142,18 @@ def __init__(self, filename=None):
135142
for col in self.cols:
136143
col.create_thread()
137144

145+
def get_name(self, name=None):
146+
return self.namer.next(name)
147+
148+
def get_threads(self):
149+
return self.threads
150+
151+
def set_global(self, **kwds):
152+
self.globals.update(kwds)
153+
154+
def get_global(self, attr):
155+
return self.globals[attr]
156+
138157
def destroy(self):
139158
"""Closes the top window."""
140159
self.running = False
@@ -244,9 +263,6 @@ def run_init(self):
244263
self.clear_views()
245264
self.views = {}
246265

247-
self.locals = copy.copy(sim_locals)
248-
self.globals = copy.copy(sim_globals)
249-
250266
thread = Thread(self.topcol, name='0')
251267
while 1:
252268
thread.step()
@@ -283,7 +299,7 @@ def subtract(d1, d2):
283299
return d
284300

285301

286-
def diff(d1, d2):
302+
def diff_dict(d1, d2):
287303
"""Diffs two dictionaries.
288304
289305
Returns two dictionaries: the first contains all the keys
@@ -484,108 +500,159 @@ def display_text(self, text):
484500
tag = self.text([15, 15], text, font=font)
485501
return tag
486502

503+
class Namer(object):
504+
def __init__(self):
505+
self.names = all_thread_names
506+
self.next_name = 0
507+
self.colors = ['red', 'orange', 'yellow', 'greenyellow',
508+
'green', 'mediumseagreen', 'skyblue',
509+
'violet', 'magenta']
510+
self.next_color = 0
511+
512+
def next(self, name=None):
513+
if name == None:
514+
name = self.names[self.next_name]
515+
self.next_name += 1
516+
self.next_name %= len(self.names)
517+
518+
color = self.colors[self.next_color]
519+
self.next_color += 1
520+
self.next_color %= len(self.colors)
521+
return name, color
522+
else:
523+
return name, 'white'
524+
487525

488526
class Thread:
489527
"""Represents simulated threads."""
490-
names = all_thread_names
491-
next_name = 0
492-
colors = ['red', 'orange', 'yellow', 'greenyellow',
493-
'green', 'mediumseagreen', 'skyblue',
494-
'violet', 'magenta']
495-
next_color = 0
496528

497529
def __init__(self, column, name=None):
498530
self.column = column
499-
if name == None:
500-
self.name = Thread.names[Thread.next_name]
501-
Thread.next_name += 1
502-
Thread.next_name %= len(Thread.names)
503-
504-
self.color = Thread.colors[Thread.next_color]
505-
Thread.next_color += 1
506-
Thread.next_color %= len(Thread.colors)
507-
else:
508-
self.name = name
509-
self.color = 'white'
510-
511-
sync = self.column.p
512-
sync.register(self)
531+
self.sync = column.p
532+
self.name, self.color = self.sync.get_name(name)
533+
self.sync.register(self)
513534
self.start()
514535

536+
def get_column(self):
537+
return self.column
538+
515539
def __str__(self):
516540
return '<' + self.name + '>'
517541

518542
def enqueue(self):
543+
"""Puts this thread into queue."""
519544
self.queued = True
520545
self.row.remove_thread(self)
521546
self.row.enqueue_thread(self)
522547

523548
def dequeue(self):
549+
"""Removes this thread from queue."""
524550
self.queued = False
525551
self.row.dequeue_thread(self)
526552
self.row.add_thread(self)
527553

528554
def start(self):
555+
"""Moves this thread to the top of the column."""
529556
self.queued = False
530557
self.iter = self.column.__iter__()
531558
self.row = None
532559
self.next_loop()
533560

534561
def next_row(self):
535-
"move this thread to the next row in the column"
536-
if self.queued: return
562+
"""Moves this thread to the next row in the column."""
563+
if self.queued:
564+
return
565+
537566
if self.row:
538567
self.row.remove_thread(self)
568+
539569
try:
540570
self.row = self.iter.next()
541571
self.row.add_thread(self)
542572
except StopIteration:
543573
self.row = None
544574

575+
def skip_body(self):
576+
"""Skips the body of a conditional."""
577+
self.next_row()
578+
545579
def next_loop(self):
546-
"move to the next row, looping to the top if necessary"
580+
"""Moves to the next row, looping to the top if necessary."""
547581
self.next_row()
548-
if self.row == None: self.start()
582+
if self.row == None:
583+
self.start()
549584

550585
def step(self, event=None):
551-
"""execute the current line of code, then move to the next row.
586+
"""Executes the current line of code, then move to the next row.
587+
552588
The current limitation of this simulator is that each row
553589
has to contain a complete Python statement. Also, each line
554590
of code is executed atomically.
591+
592+
Args:
593+
event: unused, provided so that this method can be used
594+
as a binding callback
595+
596+
Returns:
597+
line of code that executed or None
555598
"""
556-
if self.queued: return
599+
if self.queued:
600+
return None
601+
557602
source = self.row.get()
558-
source = source.strip()
559-
print self, source
560603

561-
code = compile(source, '<user-provided code>', 'exec')
604+
before = copy.copy(self.sync.locals)
562605

563-
# debugging code for catching certain exceptions
564-
# try:
565-
# except:
566-
# (type, value, traceback) = sys.exc_info()
567-
# print traceback
568-
# print type, value
569-
# self.next()
570-
# return
606+
flag = self.exec_line(source, self.sync)
571607

572-
global current_thread
573-
current_thread = self
608+
if flag:
609+
self.next_row()
610+
else:
611+
self.skip_body()
612+
613+
return source
574614

575-
sync = self.column.p
576-
before = copy.copy(sync.locals)
577-
exec code in sync.globals, sync.locals
615+
after = self.sync.locals
616+
defined, changed = diff_dict(after, before)
578617

579-
defined, changed = diff(sync.locals, before)
580618
for key in defined:
581619
sync.views[key] = self.row
582620

583621
sync.update_views()
584-
self.next_row()
622+
623+
return source
624+
625+
def exec_line(self, source, sync):
626+
source = source.strip()
627+
print self, source
628+
629+
global current_thread
630+
current_thread = self
631+
632+
try:
633+
code = compile(source, '<user-provided code>', 'exec')
634+
exec code in sync.globals, sync.locals
635+
return True
636+
except SyntaxError as error:
637+
# this had better be an if statement
638+
pass
639+
640+
flag = self.handle_conditional(source, sync)
641+
return flag
642+
643+
def handle_conditional(self, source, sync):
644+
if not (source.startswith('if') and source.endswith(':')):
645+
raise SyntaxError('Python blocks must be if statements')
646+
647+
condition = source[2:-1].strip()
648+
flag = eval(condition, sync.globals, sync.locals)
649+
650+
return flag
585651

586652
def step_loop(self, event=None):
587653
self.step()
588-
if self.row == None: self.start()
654+
if self.row == None:
655+
self.start()
589656

590657
def run(self):
591658
while 1:

‎python2/Sync_test.py‎

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,45 @@
1111

1212
class Tests(unittest.TestCase):
1313

14-
def test_sync(self):
14+
def test_sync_mutex(self):
1515
sync = Sync.Sync('mutex.py')
1616

17+
threads = sync.get_threads()
18+
threadA = threads[0]
19+
column = threadA.get_column()
20+
21+
source = threadA.step()
22+
self.assertEqual(source, 'mutex.wait()')
23+
24+
threadB = Sync.Thread(column)
25+
source = threadB.step()
26+
self.assertEqual(source, 'mutex.wait()')
27+
28+
self.assertFalse(threadA.queued)
29+
self.assertTrue(threadB.queued)
30+
31+
source = threadB.step()
32+
self.assertEqual(source, None)
33+
34+
source = threadA.step()
35+
source = threadA.step()
36+
source = threadA.step()
37+
self.assertEqual(source, 'mutex.signal()')
38+
39+
self.assertFalse(threadA.queued)
40+
self.assertFalse(threadB.queued)
41+
42+
source = threadA.exec_line('pid = pid()', sync)
43+
self.assertEqual(sync.locals['pid'], 'A')
44+
45+
def test_sync_conditional(self):
46+
sync = Sync.Sync('sync_code/conditional.py')
47+
threads = sync.get_threads()
48+
threadA = threads[0]
49+
50+
source = threadA.step()
51+
self.assertEqual(source, 'if counter == 0:')
52+
1753

1854
if __name__ == '__main__':
1955
unittest.main()

0 commit comments

Comments
 (0)