|
0 |
#!/usr/bin/env python
|
|
1 |
# -*- coding: utf-8 -*-
|
|
2 |
|
|
3 |
"""
|
|
4 |
Interpreter for the Pophery Programming Language v0.1 or something.
|
|
5 |
|
|
6 |
"""
|
|
7 |
|
|
8 |
LICENSE = """\
|
|
9 |
Copyright (c)2011 Chris Pressey, Cat's Eye Technologies.
|
|
10 |
All rights reserved.
|
|
11 |
|
|
12 |
Redistribution and use in source and binary forms, with or without
|
|
13 |
modification, are permitted provided that the following conditions
|
|
14 |
are met:
|
|
15 |
|
|
16 |
1. Redistributions of source code must retain the above copyright
|
|
17 |
notices, this list of conditions and the following disclaimer.
|
|
18 |
2. Redistributions in binary form must reproduce the above copyright
|
|
19 |
notices, this list of conditions, and the following disclaimer in
|
|
20 |
the documentation and/or other materials provided with the
|
|
21 |
distribution.
|
|
22 |
3. Neither the names of the copyright holders nor the names of their
|
|
23 |
contributors may be used to endorse or promote products derived
|
|
24 |
from this software without specific prior written permission.
|
|
25 |
|
|
26 |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
27 |
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT
|
|
28 |
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
29 |
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
30 |
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
31 |
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
32 |
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
33 |
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
34 |
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
35 |
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
36 |
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
37 |
POSSIBILITY OF SUCH DAMAGE.
|
|
38 |
"""
|
|
39 |
|
|
40 |
|
|
41 |
import sys
|
|
42 |
from optparse import OptionParser
|
|
43 |
|
|
44 |
|
|
45 |
class UndefinedLocatorError(Exception):
|
|
46 |
"""Indicates a given locator was not found in a MutableString."""
|
|
47 |
pass
|
|
48 |
|
|
49 |
|
|
50 |
class MutableString(object):
|
|
51 |
"""String-like object which may be updated in place.
|
|
52 |
|
|
53 |
A MutableString emulates a Python unicode datatype in many ways,
|
|
54 |
with the most notable exception being that its contents may
|
|
55 |
change over time. In addition, it supports a rich set of operations
|
|
56 |
for enacting these changes.
|
|
57 |
|
|
58 |
Changes are often made relative to one or more locators.
|
|
59 |
A locator string uniquely locates a position within a MutableString.
|
|
60 |
A locator is a substring which is unique within the MutableString.
|
|
61 |
If the locator is not unique, the behaviour of a change made
|
|
62 |
relative to it is undefined.
|
|
63 |
|
|
64 |
"""
|
|
65 |
def __init__(self, initial):
|
|
66 |
self.string = unicode(initial)
|
|
67 |
|
|
68 |
def __str__(self):
|
|
69 |
return self.__unicode__()
|
|
70 |
|
|
71 |
def __unicode__(self):
|
|
72 |
return self.string
|
|
73 |
|
|
74 |
def __len__(self):
|
|
75 |
return len(self.string)
|
|
76 |
|
|
77 |
def __getitem__(self, index):
|
|
78 |
return self.string[index]
|
|
79 |
|
|
80 |
def __getslice__(self, i, j):
|
|
81 |
return self.string[i:j]
|
|
82 |
|
|
83 |
def find(self, sub):
|
|
84 |
return self.string.find(sub)
|
|
85 |
|
|
86 |
def set(self, string):
|
|
87 |
self.string = unicode(string)
|
|
88 |
|
|
89 |
def pos_left(self, locator, delta):
|
|
90 |
"""Return the 0-based position within this MutableString of the
|
|
91 |
first character of the given locator, plus the given offset.
|
|
92 |
|
|
93 |
Note that the returned value is ephemeral and should not be
|
|
94 |
stored, as it is subject to change at any time the MutableString
|
|
95 |
is changed.
|
|
96 |
|
|
97 |
>>> a = MutableString("Mom(*)entous")
|
|
98 |
>>> print a.pos_left("(*)", 0)
|
|
99 |
3
|
|
100 |
|
|
101 |
"""
|
|
102 |
pos = self.find(locator)
|
|
103 |
if pos == -1:
|
|
104 |
raise UndefinedLocatorError(locator)
|
|
105 |
return pos - delta
|
|
106 |
|
|
107 |
def pos_right(self, locator, delta):
|
|
108 |
"""Return the 0-based position within this MutableString of the
|
|
109 |
first character to the right of the given locator, plus the given
|
|
110 |
offset.
|
|
111 |
|
|
112 |
Note that the returned value is ephemeral and should not be
|
|
113 |
stored, as it is subject to change at any time the MutableString
|
|
114 |
is changed.
|
|
115 |
|
|
116 |
>>> a = MutableString("Mom(*)entous")
|
|
117 |
>>> print a.pos_right("(*)", 0)
|
|
118 |
6
|
|
119 |
|
|
120 |
"""
|
|
121 |
pos = self.find(locator)
|
|
122 |
if pos == -1:
|
|
123 |
raise UndefinedLocatorError(locator)
|
|
124 |
return pos + len(locator) + delta
|
|
125 |
|
|
126 |
def insert_locator(self, locator, pos):
|
|
127 |
"""Insert the given locator at the given position in this string.
|
|
128 |
|
|
129 |
Note that this will blithely insert the new locator inside an
|
|
130 |
existing locator.
|
|
131 |
|
|
132 |
>>> a = MutableString("Momentous")
|
|
133 |
>>> a.insert_locator("(*)", 3)
|
|
134 |
>>> print str(a)
|
|
135 |
Mom(*)entous
|
|
136 |
|
|
137 |
"""
|
|
138 |
self.set(self[:pos] + unicode(locator) + self[pos:])
|
|
139 |
|
|
140 |
def remove_locator(self, locator):
|
|
141 |
"""Remove the given locator from this string.
|
|
142 |
|
|
143 |
>>> a = MutableString("Mom(*)entous")
|
|
144 |
>>> a.remove_locator("(*)")
|
|
145 |
>>> print str(a)
|
|
146 |
Momentous
|
|
147 |
|
|
148 |
"""
|
|
149 |
locator = unicode(locator)
|
|
150 |
posl = self.pos_left(locator, 0)
|
|
151 |
posr = self.pos_right(locator, 0)
|
|
152 |
self.set(self[:posl] + self[posr:])
|
|
153 |
|
|
154 |
def move_locator(self, locator, delta):
|
|
155 |
"""Change the position of the given locator by the given delta.
|
|
156 |
|
|
157 |
Note that this will not skip over intervening locators; i.e. it will
|
|
158 |
allow the locator to end up inside another locator.
|
|
159 |
|
|
160 |
>>> a = MutableString("Mom(*)entous")
|
|
161 |
>>> a.move_locator("(*)", +3)
|
|
162 |
>>> print str(a)
|
|
163 |
Moment(*)ous
|
|
164 |
|
|
165 |
"""
|
|
166 |
locator = unicode(locator)
|
|
167 |
posl = self.pos_left(locator, 0)
|
|
168 |
posr = self.pos_right(locator, 0)
|
|
169 |
self.set(self[:posl] + self[posr:])
|
|
170 |
posl = posl + delta
|
|
171 |
self.set(self[:posl] + locator + self[posl:])
|
|
172 |
|
|
173 |
def slide_locator(self, locator, delta):
|
|
174 |
"""Slide the position of the given locator by the given delta.
|
|
175 |
|
|
176 |
Note that this will skip over intervening locators; i.e. it will
|
|
177 |
avoid having the locator end up inside another locator.
|
|
178 |
|
|
179 |
Delta must be +1 or -1.
|
|
180 |
|
|
181 |
>>> a = MutableString("Mom(*)en(+)tous")
|
|
182 |
>>> a.slide_locator("(*)", +1)
|
|
183 |
>>> print str(a)
|
|
184 |
Mome(*)n(+)tous
|
|
185 |
>>> a.slide_locator("(*)", -1)
|
|
186 |
>>> print str(a)
|
|
187 |
Mom(*)en(+)tous
|
|
188 |
|
|
189 |
>>> b = MutableString("(-)Cassowary(+)")
|
|
190 |
>>> b.slide_locator("(+)", +1)
|
|
191 |
>>> print str(b)
|
|
192 |
(-)Cassowary(+)
|
|
193 |
>>> b.slide_locator("(-)", -1)
|
|
194 |
>>> print str(b)
|
|
195 |
(-)Cassowary(+)
|
|
196 |
|
|
197 |
>>> c = MutableString("Imb(+)r(%)oglio")
|
|
198 |
>>> c.slide_locator("(+)", +1)
|
|
199 |
>>> print str(c)
|
|
200 |
Imbr(+)(%)oglio
|
|
201 |
|
|
202 |
"""
|
|
203 |
locator = unicode(locator)
|
|
204 |
if delta == +1:
|
|
205 |
matching = True
|
|
206 |
target = self.pos_right(locator, 0)
|
|
207 |
advance = 1
|
|
208 |
while matching is not None and target < len(self):
|
|
209 |
matching = self.find_matching(target)
|
|
210 |
if matching is not None:
|
|
211 |
advance += (matching - target) + 1
|
|
212 |
target = matching + 1
|
|
213 |
if target < len(self):
|
|
214 |
self.move_locator(locator, advance)
|
|
215 |
elif delta == -1:
|
|
216 |
matching = True
|
|
217 |
target = self.pos_left(locator, 0) - 1
|
|
218 |
advance = -1
|
|
219 |
while matching is not None and target >= 0:
|
|
220 |
matching = self.find_matching(target)
|
|
221 |
if matching is not None:
|
|
222 |
advance -= (target - matching) + 1
|
|
223 |
target = matching - 1
|
|
224 |
if target >= 0:
|
|
225 |
self.move_locator(locator, advance)
|
|
226 |
else:
|
|
227 |
raise NotImplementedError
|
|
228 |
|
|
229 |
def read(self, left, right):
|
|
230 |
"""Retrieve the substring between the two given locators.
|
|
231 |
|
|
232 |
>>> a = MutableString("This is (a)my string(b) you know.")
|
|
233 |
>>> print a.read("(a)", "(b)")
|
|
234 |
my string
|
|
235 |
|
|
236 |
"""
|
|
237 |
a = self.pos_right(left, 0)
|
|
238 |
b = self.pos_left(right, 0)
|
|
239 |
return self.string[a:b]
|
|
240 |
|
|
241 |
def update(self, left, right, string):
|
|
242 |
"""Change the substring between the two given locators.
|
|
243 |
|
|
244 |
>>> a = MutableString("This is (a)my string(b) you know.")
|
|
245 |
>>> a.update("(a)", "(b)", "crazy talk")
|
|
246 |
>>> print str(a)
|
|
247 |
This is (a)crazy talk(b) you know.
|
|
248 |
|
|
249 |
"""
|
|
250 |
a = self.pos_right(left, 0)
|
|
251 |
b = self.pos_left(right, 0)
|
|
252 |
self.set(self.string[:a] + unicode(string) + self.string[b:])
|
|
253 |
|
|
254 |
def find_matching(self, pos):
|
|
255 |
"""Find the parenthesis which matches the parenthesis at the given
|
|
256 |
position.
|
|
257 |
|
|
258 |
Returns the position of the matching parenthesis, or None if no
|
|
259 |
matching parenthesis was found, or if the character at the given
|
|
260 |
position isn't a parenthesis.
|
|
261 |
|
|
262 |
>>> a = MutableString("This (is (my[))] string.")
|
|
263 |
>>> a.find_matching(5)
|
|
264 |
14
|
|
265 |
>>> a.find_matching(9)
|
|
266 |
13
|
|
267 |
>>> a.find_matching(12) is None
|
|
268 |
True
|
|
269 |
>>> a.find_matching(14)
|
|
270 |
5
|
|
271 |
>>> a.find_matching(13)
|
|
272 |
9
|
|
273 |
>>> a.find_matching(15) is None
|
|
274 |
True
|
|
275 |
|
|
276 |
>>> a = MutableString("a(")
|
|
277 |
>>> a.find_matching(0) is None
|
|
278 |
True
|
|
279 |
>>> a.find_matching(1) is None
|
|
280 |
True
|
|
281 |
|
|
282 |
"""
|
|
283 |
opener = self.string[pos]
|
|
284 |
if opener == u'(':
|
|
285 |
closer = u')'
|
|
286 |
dir = +1
|
|
287 |
elif opener == u')':
|
|
288 |
closer = u'('
|
|
289 |
dir = -1
|
|
290 |
else:
|
|
291 |
return None
|
|
292 |
level = 0
|
|
293 |
while pos < len(self.string):
|
|
294 |
if self.string[pos] == opener:
|
|
295 |
level += 1
|
|
296 |
elif self.string[pos] == closer:
|
|
297 |
level -= 1
|
|
298 |
if level == 0:
|
|
299 |
return pos
|
|
300 |
pos += dir
|
|
301 |
return None
|
|
302 |
|
|
303 |
|
|
304 |
class SlottedString(MutableString):
|
|
305 |
|
|
306 |
def __init__(self, initial):
|
|
307 |
super(SlottedString, self).__init__(initial)
|
|
308 |
|
|
309 |
def read_slot(self, slot_name):
|
|
310 |
"""
|
|
311 |
|
|
312 |
>>> a = SlottedString("This is (^a)my slot(a$) you know.")
|
|
313 |
>>> a.update_slot('a', 'good stuff')
|
|
314 |
>>> print str(a)
|
|
315 |
This is (^a)good stuff(a$) you know.
|
|
316 |
>>> a.update_slot('z', 'bad stuff')
|
|
317 |
Traceback (most recent call last):
|
|
318 |
...
|
|
319 |
UndefinedLocatorError: (^z)
|
|
320 |
|
|
321 |
"""
|
|
322 |
slot_name = unicode(slot_name)
|
|
323 |
return self.read(u"(^%s)" % slot_name, u"(%s$)" % slot_name)
|
|
324 |
|
|
325 |
def read_slot_indirect(self, slot_name):
|
|
326 |
"""
|
|
327 |
>>> p = SlottedString("...(^A)M(A$)...(^R)A(R$)...")
|
|
328 |
>>> print p.read_slot_indirect('R')
|
|
329 |
M
|
|
330 |
>>> print p.read_slot_indirect('A')
|
|
331 |
Traceback (most recent call last):
|
|
332 |
...
|
|
333 |
UndefinedLocatorError: (^M)
|
|
334 |
|
|
335 |
"""
|
|
336 |
slot_name = unicode(slot_name)
|
|
337 |
slot_name = self.read_slot(slot_name)
|
|
338 |
return self.read_slot(slot_name)
|
|
339 |
|
|
340 |
def update_slot(self, slot_name, string):
|
|
341 |
"""
|
|
342 |
|
|
343 |
>>> a = SlottedString("This is (^a)my slot(a$) you know.")
|
|
344 |
>>> a.update_slot('a', 'good stuff')
|
|
345 |
>>> print str(a)
|
|
346 |
This is (^a)good stuff(a$) you know.
|
|
347 |
>>> a.update_slot('a', MutableString('mutable stuff'))
|
|
348 |
>>> print str(a)
|
|
349 |
This is (^a)mutable stuff(a$) you know.
|
|
350 |
>>> a.update_slot('z', 'bad stuff')
|
|
351 |
Traceback (most recent call last):
|
|
352 |
...
|
|
353 |
UndefinedLocatorError: (^z)
|
|
354 |
|
|
355 |
"""
|
|
356 |
slot_name = unicode(slot_name)
|
|
357 |
string = unicode(string)
|
|
358 |
return self.update(u"(^%s)" % slot_name, u"(%s$)" % slot_name, string)
|
|
359 |
|
|
360 |
def update_slot_indirect(self, slot_name, string):
|
|
361 |
"""
|
|
362 |
>>> p = SlottedString("Dolphin(^A)M(A$)Dolphin(^R)A(R$)Dolphin")
|
|
363 |
>>> p.update_slot_indirect('R', 'Porphyry')
|
|
364 |
>>> print str(p)
|
|
365 |
Dolphin(^A)Porphyry(A$)Dolphin(^R)A(R$)Dolphin
|
|
366 |
|
|
367 |
"""
|
|
368 |
slot_name = self.read_slot(slot_name)
|
|
369 |
self.update_slot(slot_name, string)
|
|
370 |
|
|
371 |
def get_slot_name(self, slot_name):
|
|
372 |
"""
|
|
373 |
|
|
374 |
>>> a = SlottedString("(^G)?(G$) (^P)_(P$) (^`P)Q(`P$) (^`K)(^/)Madge(/$)(`K$)")
|
|
375 |
>>> print a.get_slot_name('M')
|
|
376 |
M
|
|
377 |
>>> print a.get_slot_name('G')
|
|
378 |
G
|
|
379 |
>>> print a.get_slot_name('P')
|
|
380 |
Q
|
|
381 |
>>> print a.get_slot_name('K')
|
|
382 |
Madge
|
|
383 |
|
|
384 |
"""
|
|
385 |
slot_name = unicode(slot_name)
|
|
386 |
name_slot = u"`%s" % slot_name
|
|
387 |
try:
|
|
388 |
slot_name = self.read_slot(name_slot)
|
|
389 |
except (UndefinedLocatorError):
|
|
390 |
pass
|
|
391 |
slot_name = self.strip_all_locators(slot_name)
|
|
392 |
return slot_name
|
|
393 |
|
|
394 |
def strip_all_locators(self, content):
|
|
395 |
"""
|
|
396 |
>>> p = Program('')
|
|
397 |
>>> print p.strip_all_locators('')
|
|
398 |
None
|
|
399 |
>>> print p.strip_all_locators('X')
|
|
400 |
X
|
|
401 |
>>> print p.strip_all_locators('Well-tempered')
|
|
402 |
Well-tempered
|
|
403 |
>>> print p.strip_all_locators('(^8)(^7)(7$)CAT(8$)')
|
|
404 |
CAT
|
|
405 |
>>> print p.strip_all_locators('(^8(beat))D')
|
|
406 |
D
|
|
407 |
>>> print p.strip_all_locators('(^8)(^7)(7$)(8$)')
|
|
408 |
None
|
|
409 |
|
|
410 |
"""
|
|
411 |
if len(content) == 0:
|
|
412 |
return None
|
|
413 |
else:
|
|
414 |
pos = 0
|
|
415 |
level = 0
|
|
416 |
acc = ''
|
|
417 |
while pos < len(content):
|
|
418 |
if content[pos] == '(':
|
|
419 |
level += 1
|
|
420 |
elif content[pos] == ')':
|
|
421 |
level -= 1
|
|
422 |
elif level == 0:
|
|
423 |
acc += content[pos]
|
|
424 |
pos += 1
|
|
425 |
return acc or None
|
|
426 |
|
|
427 |
def slide_slot(self, slot_name, delta):
|
|
428 |
"""
|
|
429 |
|
|
430 |
>>> a = SlottedString("This is my (^a)slot(a$) (^b)y(b$)ou know.")
|
|
431 |
>>> a.slide_slot('a', +1)
|
|
432 |
>>> print str(a)
|
|
433 |
This is my s(^a)lot (a$)(^b)y(b$)ou know.
|
|
434 |
>>> a.slide_slot('b', -1)
|
|
435 |
>>> print str(a)
|
|
436 |
This is my s(^a)lot(^b) (a$)(b$)you know.
|
|
437 |
|
|
438 |
"""
|
|
439 |
slot_name = unicode(slot_name)
|
|
440 |
if delta > 0:
|
|
441 |
self.slide_locator("(%s$)" % slot_name, delta)
|
|
442 |
self.slide_locator("(^%s)" % slot_name, delta)
|
|
443 |
else:
|
|
444 |
self.slide_locator("(^%s)" % slot_name, delta)
|
|
445 |
self.slide_locator("(%s$)" % slot_name, delta)
|
|
446 |
|
|
447 |
|
|
448 |
class Program(SlottedString):
|
|
449 |
|
|
450 |
def __init__(self, initial):
|
|
451 |
super(Program, self).__init__(initial)
|
|
452 |
self.input = sys.stdin
|
|
453 |
self.output = sys.stdout
|
|
454 |
|
|
455 |
def load(self, filename):
|
|
456 |
"""Load the program source from a Tranzy file."""
|
|
457 |
file = open(filename, 'r')
|
|
458 |
done = False
|
|
459 |
string = ''
|
|
460 |
for line in file.readlines():
|
|
461 |
line = unicode(line, 'utf-8') # for now
|
|
462 |
if line.endswith('\n'):
|
|
463 |
line = line[:-1]
|
|
464 |
if line.startswith('#'):
|
|
465 |
pass
|
|
466 |
else:
|
|
467 |
string += line
|
|
468 |
self.set(string)
|
|
469 |
file.close()
|
|
470 |
|
|
471 |
def advance(self):
|
|
472 |
"""Slide the instruction slot rightward.
|
|
473 |
|
|
474 |
>>> p = Program("(^!)A(!$)B(^M)C(M$)D")
|
|
475 |
>>> p.advance()
|
|
476 |
>>> print str(p)
|
|
477 |
A(^!)B(!$)(^M)C(M$)D
|
|
478 |
>>> p.advance()
|
|
479 |
>>> print str(p)
|
|
480 |
AB(^!)(^M)C(!$)(M$)D
|
|
481 |
>>> p.advance()
|
|
482 |
>>> print str(p)
|
|
483 |
AB(^M)C(^!)(M$)D(!$)
|
|
484 |
>>> p.advance()
|
|
485 |
>>> print str(p)
|
|
486 |
AB(^M)C(M$)D(^!)(!$)
|
|
487 |
|
|
488 |
>>> p = Program("(^!)A(!$)(^Moo)(^Gar)(Gar$)B(Moo$)")
|
|
489 |
>>> p.advance()
|
|
490 |
>>> print str(p)
|
|
491 |
A(^!)(^Moo)(^Gar)(Gar$)B(!$)(Moo$)
|
|
492 |
>>> p.advance()
|
|
493 |
>>> print str(p)
|
|
494 |
A(^Moo)(^Gar)(Gar$)B(^!)(!$)(Moo$)
|
|
495 |
|
|
496 |
"""
|
|
497 |
self.slide_slot(self.get_slot_name('!'), +1)
|
|
498 |
|
|
499 |
def clean_instruction(self, instruction):
|
|
500 |
"""
|
|
501 |
>>> p = Program('')
|
|
502 |
>>> print p.clean_instruction('')
|
|
503 |
None
|
|
504 |
>>> print p.clean_instruction('X')
|
|
505 |
X
|
|
506 |
>>> print p.clean_instruction('Well-tempered')
|
|
507 |
W
|
|
508 |
>>> print p.clean_instruction('(^8)(^7)(7$)CAT(8$)')
|
|
509 |
C
|
|
510 |
>>> print p.clean_instruction('(^8(beat))D')
|
|
511 |
D
|
|
512 |
>>> print p.clean_instruction('(^8)(^7)(7$)(8$)')
|
|
513 |
None
|
|
514 |
|
|
515 |
"""
|
|
516 |
if len(instruction) == 0:
|
|
517 |
return None
|
|
518 |
else:
|
|
519 |
pos = 0
|
|
520 |
level = 0
|
|
521 |
while instruction[pos] == '(':
|
|
522 |
while True:
|
|
523 |
if instruction[pos] == '(':
|
|
524 |
level += 1
|
|
525 |
elif instruction[pos] == ')':
|
|
526 |
level -= 1
|
|
527 |
pos += 1
|
|
528 |
if level == 0 or pos >= len(instruction):
|
|
529 |
break
|
|
530 |
if pos >= len(instruction):
|
|
531 |
return None
|
|
532 |
return instruction[pos]
|
|
533 |
|
|
534 |
def execute(self, instruction):
|
|
535 |
raise NotImplementedError
|
|
536 |
|
|
537 |
def step(self):
|
|
538 |
"""Execute one step of this Pophery program."""
|
|
539 |
instruction = self.read_slot(self.get_slot_name('!'))
|
|
540 |
instruction = self.clean_instruction(instruction)
|
|
541 |
if instruction is None:
|
|
542 |
return False
|
|
543 |
else:
|
|
544 |
self.execute(instruction)
|
|
545 |
self.advance()
|
|
546 |
return True
|
|
547 |
|
|
548 |
def run(self):
|
|
549 |
"""Execute this Pophery program and return only when it terminates."""
|
|
550 |
keep_going = self.step()
|
|
551 |
while keep_going:
|
|
552 |
keep_going = self.step()
|
|
553 |
|
|
554 |
|
|
555 |
class Semantics(Program):
|
|
556 |
def deselect(self):
|
|
557 |
locator_name = self.get_slot_name('/')
|
|
558 |
try:
|
|
559 |
self.remove_locator('(^%s)' % locator_name)
|
|
560 |
except UndefinedLocatorError:
|
|
561 |
pass
|
|
562 |
try:
|
|
563 |
self.remove_locator('(%s$)' % locator_name)
|
|
564 |
except UndefinedLocatorError:
|
|
565 |
pass
|
|
566 |
|
|
567 |
def execute(self, instruction):
|
|
568 |
"""Apply the semantics of the given instruction to this Program.
|
|
569 |
|
|
570 |
* 0 through 9 update the accumulator to the literal strings 0 through
|
|
571 |
9, respectively.
|
|
572 |
|
|
573 |
>>> p = Semantics("(^?)(?$)")
|
|
574 |
>>> p.execute('0')
|
|
575 |
>>> print str(p)
|
|
576 |
(^?)0(?$)
|
|
577 |
|
|
578 |
* X ("cut") erases (updates with the zero-length string) the selection.
|
|
579 |
|
|
580 |
>>> p = Semantics("(^/)hi(/$)")
|
|
581 |
>>> p.execute('X')
|
|
582 |
>>> print str(p)
|
|
583 |
(^/)(/$)
|
|
584 |
>>> p = Semantics("(^`/)X(`/$)(^X)hi(X$)")
|
|
585 |
>>> p.execute('X')
|
|
586 |
>>> print str(p)
|
|
587 |
(^`/)X(`/$)(^X)(X$)
|
|
588 |
|
|
589 |
* C ("copy") updates the contents of the clipboard with the contents
|
|
590 |
of the selection.
|
|
591 |
|
|
592 |
>>> p = Semantics("(^/)hi(/$)(^%)lo(%$)")
|
|
593 |
>>> p.execute('C')
|
|
594 |
>>> print str(p)
|
|
595 |
(^/)hi(/$)(^%)hi(%$)
|
|
596 |
>>> p = Semantics("(^/)hi(/$)(^J)lo(J$)(^`%)J(`%$)")
|
|
597 |
>>> p.execute('C')
|
|
598 |
>>> print str(p)
|
|
599 |
(^/)hi(/$)(^J)hi(J$)(^`%)J(`%$)
|
|
600 |
|
|
601 |
* V ("paste") updates the contents of the selection with the contents
|
|
602 |
of the clipboard.
|
|
603 |
|
|
604 |
>>> p = Semantics("(^/)hi(/$)(^%)lo(%$)")
|
|
605 |
>>> p.execute('V')
|
|
606 |
>>> print str(p)
|
|
607 |
(^/)lo(/$)(^%)lo(%$)
|
|
608 |
>>> p = Semantics("(^C)lo(C$)(^J)hi(J$)(^`/)J(`/$)(^`%)C(`%$)")
|
|
609 |
>>> p.execute('V')
|
|
610 |
>>> print str(p)
|
|
611 |
(^C)lo(C$)(^J)lo(J$)(^`/)J(`/$)(^`%)C(`%$)
|
|
612 |
|
|
613 |
* S ("select") selects the contents of the slot indirect by the
|
|
614 |
accumulator.
|
|
615 |
|
|
616 |
>>> p = Semantics("(^/)foo(/$)(^?)A(?$)(^A)Some text.(A$)")
|
|
617 |
>>> p.execute('S')
|
|
618 |
>>> print str(p)
|
|
619 |
foo(^?)A(?$)(^A)(^/)Some text.(/$)(A$)
|
|
620 |
>>> p = Semantics("(^`/)k(`/$)(^k)foo(k$)(^?)A(?$)(^A)Some text.(A$)")
|
|
621 |
>>> p.execute('S')
|
|
622 |
>>> print str(p)
|
|
623 |
(^`/)k(`/$)foo(^?)A(?$)(^A)(^k)Some text.(k$)(A$)
|
|
624 |
|
|
625 |
* A ("select all") selects the contents of the accumulator.
|
|
626 |
|
|
627 |
>>> p = Semantics("(^/)foo(/$)(^?)A(?$)(^A)Some text.(A$)")
|
|
628 |
>>> p.execute('A')
|
|
629 |
>>> print str(p)
|
|
630 |
foo(^?)(^/)A(/$)(?$)(^A)Some text.(A$)
|
|
631 |
>>> p = Semantics("(^`/)r(`/$)(^r)foo(r$)(^?)A(?$)(^A)Some text.(A$)")
|
|
632 |
>>> p.execute('A')
|
|
633 |
>>> print str(p)
|
|
634 |
(^`/)r(`/$)foo(^?)(^r)A(r$)(?$)(^A)Some text.(A$)
|
|
635 |
|
|
636 |
* L ("left") slides the left locator of the selection leftward.
|
|
637 |
|
|
638 |
>>> p = Semantics("foo(^/)bar(/$)")
|
|
639 |
>>> p.execute('L')
|
|
640 |
>>> print str(p)
|
|
641 |
fo(^/)obar(/$)
|
|
642 |
>>> p = Semantics("(^/)foobar(/$)")
|
|
643 |
>>> p.execute('L')
|
|
644 |
>>> print str(p)
|
|
645 |
(^/)foobar(/$)
|
|
646 |
>>> p = Semantics("foo(^C)bar(C$)(^`/)C(`/$)")
|
|
647 |
>>> p.execute('L')
|
|
648 |
>>> print str(p)
|
|
649 |
fo(^C)obar(C$)(^`/)C(`/$)
|
|
650 |
>>> p = Semantics("The last time I saw Charlie")
|
|
651 |
>>> p.execute('L')
|
|
652 |
Traceback (most recent call last):
|
|
653 |
...
|
|
654 |
UndefinedLocatorError: (^/)
|
|
655 |
|
|
656 |
* R ("right") slides the left locator of the selection rightward.
|
|
657 |
|
|
658 |
>>> p = Semantics("foo(^/)bar(/$)")
|
|
659 |
>>> p.execute('R')
|
|
660 |
>>> print str(p)
|
|
661 |
foob(^/)ar(/$)
|
|
662 |
>>> p = Semantics("foo(^/)(/$)bar")
|
|
663 |
>>> p.execute('R')
|
|
664 |
>>> print str(p)
|
|
665 |
foo(^/)(/$)bar
|
|
666 |
>>> p = Semantics("foo(^C)bar(C$)(^`/)C(`/$)")
|
|
667 |
>>> p.execute('R')
|
|
668 |
>>> print str(p)
|
|
669 |
foob(^C)ar(C$)(^`/)C(`/$)
|
|
670 |
>>> p = Semantics("The last time I saw Charlie")
|
|
671 |
>>> p.execute('R')
|
|
672 |
Traceback (most recent call last):
|
|
673 |
...
|
|
674 |
UndefinedLocatorError: (^/)
|
|
675 |
|
|
676 |
* E ("end") moves the left locator of the selection to immediately
|
|
677 |
to the left of the right locator of the selection, resulting in
|
|
678 |
the selection containing the zero-length string.
|
|
679 |
|
|
680 |
>>> p = Semantics("foo(^/)bar(/$)baz")
|
|
681 |
>>> p.execute('E')
|
|
682 |
>>> print str(p)
|
|
683 |
foobar(^/)(/$)baz
|
|
684 |
>>> p = Semantics("foo(^a)b(^`/)a(`/$)r(a$)baz")
|
|
685 |
>>> p.execute('E')
|
|
686 |
>>> print str(p)
|
|
687 |
foob(^`/)a(`/$)r(^a)(a$)baz
|
|
688 |
>>> p = Semantics("The last time I saw Charlie")
|
|
689 |
>>> p.execute('E')
|
|
690 |
Traceback (most recent call last):
|
|
691 |
...
|
|
692 |
UndefinedLocatorError: (^/)
|
|
693 |
|
|
694 |
* F ("find") searches everywhere in the contents of the accumulator
|
|
695 |
for the contents of the clipboard. If found, that substring is
|
|
696 |
selected.
|
|
697 |
|
|
698 |
>>> p = Semantics("(^?)By hook or by crook, we will.(?$)(^%)ook(%$)")
|
|
699 |
>>> p.execute('F')
|
|
700 |
>>> print str(p)
|
|
701 |
(^?)By h(^/)ook(/$) or by crook, we will.(?$)(^%)ook(%$)
|
|
702 |
|
|
703 |
* D ("drag-and-drop") moves the selection to the accumulator.
|
|
704 |
|
|
705 |
>>> p = Semantics("(^/)hi(/$)(^?)lo(?$)")
|
|
706 |
>>> p.execute('D')
|
|
707 |
>>> print str(p)
|
|
708 |
hi(^?)(^/)hi(/$)(?$)
|
|
709 |
>>> p = Semantics("(^C)lo(C$)(^J)hi(J$)(^`/)J(`/$)(^`?)C(`?$)")
|
|
710 |
>>> p.execute('D')
|
|
711 |
>>> print str(p)
|
|
712 |
(^C)(^J)hi(J$)(C$)hi(^`/)J(`/$)(^`?)C(`?$)
|
|
713 |
|
|
714 |
* I ("input") waits for a line to appear on standard input, then
|
|
715 |
places it (sans newline) in the accumulator.
|
|
716 |
|
|
717 |
>>> from StringIO import StringIO
|
|
718 |
>>> p = Semantics("(^?)(?$)")
|
|
719 |
>>> p.input = StringIO(chr(10).join(["Line.", "Line!", "LINE!"]))
|
|
720 |
>>> p.execute('I')
|
|
721 |
>>> print str(p)
|
|
722 |
(^?)Line.(?$)
|
|
723 |
>>> p.execute('I')
|
|
724 |
>>> print str(p)
|
|
725 |
(^?)Line!(?$)
|
|
726 |
>>> p.execute('I')
|
|
727 |
>>> print str(p)
|
|
728 |
(^?)LINE!(?$)
|
|
729 |
>>> p.execute('I')
|
|
730 |
>>> print str(p)
|
|
731 |
(^?)(?$)
|
|
732 |
|
|
733 |
* O ("output") outputs the string in the accumulator to standard
|
|
734 |
output, followed by a newline.
|
|
735 |
|
|
736 |
>>> p = Semantics("(^?)Hello, world!(?$)")
|
|
737 |
>>> p.execute('O')
|
|
738 |
Hello, world!
|
|
739 |
>>> print str(p)
|
|
740 |
(^?)Hello, world!(?$)
|
|
741 |
|
|
742 |
Now we demonstrate some idioms.
|
|
743 |
|
|
744 |
Assume the inital program defines some slots to contain initial
|
|
745 |
data. That data can then be loaded into the accumulator:
|
|
746 |
|
|
747 |
>>> p = Semantics("(^0)data(0$)(^%)(%$)(^?)(?$)(^!)0(!$)SCAV")
|
|
748 |
>>> p.run()
|
|
749 |
>>> print str(p)
|
|
750 |
(^0)data(0$)(^%)data(%$)(^?)(^/)data(/$)(?$)0SCAV(^!)(!$)
|
|
751 |
|
|
752 |
New data, say the literal string 1, can be stored into slot 0 with:
|
|
753 |
|
|
754 |
>>> p = Semantics("(^0)data(0$)(^%)(%$)(^?)(?$)(^!)1(!$)AC0SV")
|
|
755 |
>>> p.run()
|
|
756 |
>>> print str(p)
|
|
757 |
(^0)(^/)1(/$)(0$)(^%)1(%$)(^?)0(?$)1AC0SV(^!)(!$)
|
|
758 |
|
|
759 |
To copy from any arbitrary slot (say 0) to another (say 1), we can say:
|
|
760 |
|
|
761 |
>>> p = Semantics("(^0)hi(0$)(^1)(1$)(^%)(%$)(^?)(?$)(^!)0(!$)SC1SV")
|
|
762 |
>>> p.run()
|
|
763 |
>>> print str(p)
|
|
764 |
(^0)hi(0$)(^1)(^/)hi(/$)(1$)(^%)hi(%$)(^?)1(?$)0SC1SV(^!)(!$)
|
|
765 |
|
|
766 |
Accessing a slot with a longer name, such as (^123)xyz(123$), can be
|
|
767 |
done with the help of a free slot like 0:
|
|
768 |
|
|
769 |
>>> p = Semantics("(^0)(0$)(^123)xyz(123$)(^%)(%$)(^?)(?$)(^!)1(!$)AC0SV2AC0SEV3AC0SEV0SCAVSD")
|
|
770 |
>>> p.run()
|
|
771 |
>>> print str(p)
|
|
772 |
(^0)123(0$)(^123)xyz(123$)(^%)123(%$)(^?)(^/)xyz(/$)(?$)1AC0SV2AC0SEV3AC0SEV0SCAVSD(^!)(!$)
|
|
773 |
|
|
774 |
To write data, say (^8)foo(8$), into a slot whose name is stored in
|
|
775 |
another slot, such as (^9)jim(9$), we can say:
|
|
776 |
|
|
777 |
>>> p = Semantics("(^8)foo(8$)(^9)jim(9$)(^jim)(jim$)(^%)(%$)(^?)(?$)(^!)8(!$)SC9SDSV")
|
|
778 |
>>> p.run()
|
|
779 |
>>> print str(p)
|
|
780 |
(^8)foo(8$)(^9)jim(9$)(^jim)(^/)foo(/$)(jim$)(^%)foo(%$)(^?)jim(?$)8SC9SDSV(^!)(!$)
|
|
781 |
|
|
782 |
Finally, a complete, if simple, program:
|
|
783 |
|
|
784 |
>>> p = Semantics("(^?)Hello, world!(?$)(^!)O(!$)")
|
|
785 |
>>> p.run()
|
|
786 |
Hello, world!
|
|
787 |
|
|
788 |
"""
|
|
789 |
if instruction >= '0' and instruction <= '9':
|
|
790 |
self.update_slot(self.get_slot_name('?'), instruction)
|
|
791 |
elif instruction == 'X':
|
|
792 |
self.update_slot(self.get_slot_name('/'), '')
|
|
793 |
elif instruction == 'C':
|
|
794 |
self.update_slot(self.get_slot_name('%'), self.read_slot(self.get_slot_name('/')))
|
|
795 |
elif instruction == 'V':
|
|
796 |
self.update_slot(self.get_slot_name('/'), self.read_slot(self.get_slot_name('%')))
|
|
797 |
elif instruction == 'S':
|
|
798 |
self.deselect()
|
|
799 |
locator_name = self.get_slot_name('/')
|
|
800 |
new_selection = '(^%s)%s(%s$)' % (
|
|
801 |
locator_name,
|
|
802 |
self.read_slot_indirect(self.get_slot_name('?')),
|
|
803 |
locator_name
|
|
804 |
)
|
|
805 |
self.update_slot_indirect(self.get_slot_name('?'), new_selection)
|
|
806 |
elif instruction == 'A':
|
|
807 |
self.deselect()
|
|
808 |
locator_name = self.get_slot_name('/')
|
|
809 |
new_selection = '(^%s)%s(%s$)' % (
|
|
810 |
locator_name,
|
|
811 |
self.read_slot(self.get_slot_name('?')),
|
|
812 |
locator_name
|
|
813 |
)
|
|
814 |
self.update_slot(self.get_slot_name('?'), new_selection)
|
|
815 |
elif instruction == 'L':
|
|
816 |
locator_name = self.get_slot_name('/')
|
|
817 |
self.slide_locator('(^%s)' % locator_name, -1)
|
|
818 |
elif instruction == 'R':
|
|
819 |
locator_name = self.get_slot_name('/')
|
|
820 |
if self.read_slot(locator_name) != '':
|
|
821 |
self.slide_locator('(^%s)' % locator_name, +1)
|
|
822 |
elif instruction == 'E':
|
|
823 |
locator_name = self.get_slot_name('/')
|
|
824 |
self.remove_locator('(^%s)' % locator_name)
|
|
825 |
pos = self.pos_left('(%s$)' % locator_name, 0)
|
|
826 |
self.insert_locator('(^%s)' % locator_name, pos)
|
|
827 |
elif instruction == 'F':
|
|
828 |
accumulator = self.read_slot(self.get_slot_name('?'))
|
|
829 |
clipboard = self.read_slot(self.get_slot_name('%'))
|
|
830 |
pos = accumulator.find(clipboard)
|
|
831 |
if pos >= 0:
|
|
832 |
self.deselect()
|
|
833 |
locator_name = self.get_slot_name('/')
|
|
834 |
accumulator = MutableString(accumulator)
|
|
835 |
accumulator.insert_locator('(^%s)' % locator_name, pos)
|
|
836 |
pos_right = accumulator.pos_right('(^%s)' % locator_name, 0)
|
|
837 |
accumulator.insert_locator('(%s$)' % locator_name,
|
|
838 |
pos_right + len(clipboard))
|
|
839 |
self.update_slot(self.get_slot_name('?'), accumulator)
|
|
840 |
else:
|
|
841 |
pass
|
|
842 |
elif instruction == 'D':
|
|
843 |
locator_name = self.get_slot_name('/')
|
|
844 |
selection = self.read_slot(locator_name)
|
|
845 |
self.deselect()
|
|
846 |
new_selection = '(^%s)%s(%s$)' % (
|
|
847 |
locator_name,
|
|
848 |
selection,
|
|
849 |
locator_name
|
|
850 |
)
|
|
851 |
self.update_slot(self.get_slot_name('?'), new_selection)
|
|
852 |
elif instruction == 'O':
|
|
853 |
line = self.read_slot('?') + "\n"
|
|
854 |
try:
|
|
855 |
self.output.write(line.encode('UTF-8'))
|
|
856 |
except UnicodeEncodeError:
|
|
857 |
self.output.write(line.encode('ascii', 'xmlcharrefreplace'))
|
|
858 |
elif instruction == 'I':
|
|
859 |
text = self.input.readline()
|
|
860 |
if text.endswith('\n'):
|
|
861 |
text = text[:-1]
|
|
862 |
self.update_slot(self.get_slot_name('?'), text)
|
|
863 |
else:
|
|
864 |
pass
|
|
865 |
|
|
866 |
def step(self):
|
|
867 |
"""Execute one step of this Pophery program.
|
|
868 |
|
|
869 |
"""
|
|
870 |
return super(Semantics, self).step()
|
|
871 |
|
|
872 |
def run(self):
|
|
873 |
"""Execute this Pophery program and return only when it terminates.
|
|
874 |
|
|
875 |
"""
|
|
876 |
return super(Semantics, self).run()
|
|
877 |
|
|
878 |
|
|
879 |
class TracedProgram(Semantics):
|
|
880 |
"""
|
|
881 |
|
|
882 |
>>> p = TracedProgram("(^?)Hello, world!(?$)(^!)O(!$)OO")
|
|
883 |
>>> p.run()
|
|
884 |
[(^?)Hello, world!(?$)(^!)O(!$)OO]
|
|
885 |
Hello, world!
|
|
886 |
[(^?)Hello, world!(?$)O(^!)O(!$)O]
|
|
887 |
Hello, world!
|
|
888 |
[(^?)Hello, world!(?$)OO(^!)O(!$)]
|
|
889 |
Hello, world!
|
|
890 |
[(^?)Hello, world!(?$)OOO(^!)(!$)]
|
|
891 |
|
|
892 |
"""
|
|
893 |
|
|
894 |
def __init__(self, initial):
|
|
895 |
super(TracedProgram, self).__init__(initial)
|
|
896 |
|
|
897 |
def run(self):
|
|
898 |
print "[%s]" % str(self)
|
|
899 |
super(TracedProgram, self).run()
|
|
900 |
|
|
901 |
def step(self):
|
|
902 |
result = super(TracedProgram, self).step()
|
|
903 |
if result:
|
|
904 |
print "[%s]" % str(self)
|
|
905 |
return result
|
|
906 |
|
|
907 |
|
|
908 |
|
|
909 |
def main(argv):
|
|
910 |
optparser = OptionParser("[python] %prog {options} {source.tranzy}\n" + __doc__)
|
|
911 |
optparser.add_option("-e", "--evaluate",
|
|
912 |
action="store", type="string", dest="program", default=None,
|
|
913 |
help="evaluate Pophery program on command line")
|
|
914 |
optparser.add_option("-l", "--show-license",
|
|
915 |
action="store_true", dest="show_license", default=False,
|
|
916 |
help="show product license and exit")
|
|
917 |
optparser.add_option("-t", "--trace",
|
|
918 |
action="store_true", dest="trace", default=False,
|
|
919 |
help="trace execution during run")
|
|
920 |
optparser.add_option("-T", "--run-tests",
|
|
921 |
action="store_true", dest="run_tests", default=False,
|
|
922 |
help="run self-tests and exit")
|
|
923 |
(options, args) = optparser.parse_args(argv[1:])
|
|
924 |
exit_code = None
|
|
925 |
if options.show_license:
|
|
926 |
print sys.argv[0]
|
|
927 |
print __doc__
|
|
928 |
print LICENSE
|
|
929 |
exit_code = 0
|
|
930 |
if options.run_tests:
|
|
931 |
import doctest
|
|
932 |
(fails, something) = doctest.testmod()
|
|
933 |
if fails == 0:
|
|
934 |
print "All tests passed."
|
|
935 |
exit_code = 0
|
|
936 |
else:
|
|
937 |
exit_code = 1
|
|
938 |
|
|
939 |
if exit_code is not None:
|
|
940 |
sys.exit(exit_code)
|
|
941 |
|
|
942 |
klass = Semantics
|
|
943 |
if options.trace:
|
|
944 |
klass = TracedProgram
|
|
945 |
|
|
946 |
if options.program is not None:
|
|
947 |
klass(options.program).run()
|
|
948 |
|
|
949 |
for filename in args:
|
|
950 |
program = klass('')
|
|
951 |
program.load(filename)
|
|
952 |
program.run()
|
|
953 |
|
|
954 |
|
|
955 |
if __name__ == "__main__":
|
|
956 |
main(sys.argv)
|