11"""For running Python code that could interrupt itself at any time
2-
32in order to, for example, ask for a read on stdin, or a write on stdout
43
5- The CodeRunner spawns a greenlet to run code it, and that code can suspend
6- its own execution to ask the main thread to refresh the display or get information.
4+ The CodeRunner spawns a greenlet to run code in, and that code can suspend
5+ its own execution to ask the main greenlet to refresh the display or get information.
6+
7+ Greenlets are basically threads that can explicitly switch control to each other.
8+ You can replace the word "greenlet" with "thread" in these docs if that makes more
9+ sense to you.
710"""
811
912import code
1316import logging
1417
1518class SigintHappened (object ):
16- """If this class is returned, a SIGINT happened while the main thread"""
19+ """If this class is returned, a SIGINT happened while the main greenlet"""
20+
21+ class SystemExitFromCodeGreenlet (SystemExit ):
22+ """If this class is returned, a SystemExit happened while in the code greenlet"""
23+
24+
25+ class RequestFromCodeGreenlet (object ):
26+ """Message from the code greenlet"""
27+
28+ class Wait (RequestFromCodeGreenlet ):
29+ """Running code would like the main loop to run for a bit"""
1730
18- class SystemExitFromCodeThread (SystemExit ):
19- """If this class is returned, a SystemExit happened while in the code thread"""
20- pass
31+ class Refresh (RequestFromCodeGreenlet ):
32+ """Running code would like the main loop to refresh the display"""
33+
34+ class Done (RequestFromCodeGreenlet ):
35+ """Running code is done running"""
36+
37+ class Unfinished (RequestFromCodeGreenlet ):
38+ """Source code wasn't executed because it wasn't fully formed"""
39+
40+ class SystemExitRequest (RequestFromCodeGreenlet ):
41+ """Running code raised a SystemExit"""
2142
2243class CodeRunner (object ):
2344 """Runs user code in an interpreter.
2445
2546 Running code requests a refresh by calling refresh_and_get_value(), which
26- suspends execution of the code and switches back to the main thread
47+ suspends execution of the code and switches back to the main greenlet
2748
2849 After load_code() is called with the source code to be run,
2950 the run_code() method should be called to start running the code.
@@ -37,10 +58,12 @@ class CodeRunner(object):
3758
3859 Once the screen refresh has occurred or the requested user input
3960 has been gathered, run_code() should be called again, passing in any
40- requested user input. This continues until run_code returns 'done' .
61+ requested user input. This continues until run_code returns Done .
4162
42- Question: How does the caller of run_code know that user input ought
43- to be returned?
63+ The code greenlet is responsible for telling the main greenlet
64+ what it wants returned in the next run_code call - CodeRunner
65+ just passes whatever is passed in to run_code(for_code) to the
66+ code greenlet
4467 """
4568 def __init__ (self , interp = None , stuff_a_refresh_request = lambda :None ):
4669 """
@@ -56,7 +79,7 @@ def __init__(self, interp=None, stuff_a_refresh_request=lambda:None):
5679 self .code_greenlet = None
5780 self .stuff_a_refresh_request = stuff_a_refresh_request
5881 self .code_is_waiting = False # waiting for response from main thread
59- self .sigint_happened = False
82+ self .sigint_happened_in_main_greenlet = False # sigint happened while in main thread
6083 self .orig_sigint_handler = None
6184
6285 @property
@@ -93,27 +116,27 @@ def run_code(self, for_code=None):
93116 assert self .code_is_waiting
94117 self .code_is_waiting = False
95118 signal .signal (signal .SIGINT , self .sigint_handler )
96- if self .sigint_happened :
97- self .sigint_happened = False
119+ if self .sigint_happened_in_main_greenlet :
120+ self .sigint_happened_in_main_greenlet = False
98121 request = self .code_greenlet .switch (SigintHappened )
99122 else :
100123 request = self .code_greenlet .switch (for_code )
101124
102- if request in ['wait' , 'refresh' ]:
125+ if not issubclass (request , RequestFromCodeGreenlet ):
126+ raise ValueError ("Not a valid value from code greenlet: %r" % request )
127+ if request in [Wait , Refresh ]:
103128 self .code_is_waiting = True
104- if request == 'refresh' :
129+ if request == Refresh :
105130 self .stuff_a_refresh_request ()
106131 return False
107- elif request in ['done' , 'unfinished' ]:
132+ elif request in [Done , Unfinished ]:
108133 self ._unload_code ()
109134 signal .signal (signal .SIGINT , self .orig_sigint_handler )
110135 self .orig_sigint_handler = None
111136 return request
112- elif request in ['SystemExit' ]: #use the object?
137+ elif request in [SystemExitRequest ]:
113138 self ._unload_code ()
114- raise SystemExitFromCodeThread ()
115- else :
116- raise ValueError ("Not a valid value from code greenlet: %r" % request )
139+ raise SystemExitFromCodeGreenlet ()
117140
118141 def sigint_handler (self , * args ):
119142 """SIGINT handler to use while code is running or request being fufilled"""
@@ -128,22 +151,22 @@ def _blocking_run_code(self):
128151 try :
129152 unfinished = self .interp .runsource (self .source )
130153 except SystemExit :
131- return 'SystemExit'
132- return 'unfinished' if unfinished else 'done'
154+ return SystemExitRequest
155+ return Unfinished if unfinished else Done
133156
134157 def wait_and_get_value (self ):
135158 """Return the argument passed in to .run_code(for_code)
136159
137160 Nothing means calls to run_code must be...
138161 """
139- value = self .main_greenlet .switch ('wait' )
162+ value = self .main_greenlet .switch (Wait )
140163 if value is SigintHappened :
141164 raise KeyboardInterrupt ()
142165 return value
143166
144167 def refresh_and_get_value (self ):
145168 """Returns the argument passed in to .run_code(for_code) """
146- value = self .main_greenlet .switch ('refresh' )
169+ value = self .main_greenlet .switch (Refresh )
147170 if value is SigintHappened :
148171 raise KeyboardInterrupt ()
149172 return value
0 commit comments