git @ Cat's Eye Technologies Pophery / 609687d
Initial import of Pophery version 0.1 or something sources. catseye 11 years ago
5 changed file(s) with 1476 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Pophery
1 =======
2
3 Version 0.1 or something
4 Chris Pressey, Cat's Eye Technologies
5
6 Introduction
7 ------------
8
9 _Pophery_ is an imperative string-rewriting language. I know right?
10
11 In Pophery, each program state is a single string, and a program is simply
12 the initial program state. As execution proceeds, the string is rewritten
13 based on instructions found within the string. Pophery is a "visible"
14 programming language, in the sense that there is no program state that is
15 not part of the string.
16
17 Pophery provides primitive instructions which allow the programmer to
18 construct their own control flow mechanisms, including at least conventional
19 backwards-branch looping, but possibly also permitting alternative
20 techniques such as [SMITH](http://catseye.tc/projects/smith/)-style
21 program-extension and [Muriel](http://catseye.tc/projects/muriel/)-style
22 quine-continuation.
23
24 As a reaction against the proliferation of stack-based esolangs, Pophery's
25 design explicitly avoids having a stack, preferring instead register-like
26 storage in the form of delimited substrings, called _slots_, which may be
27 accessed directly and indirectly, updated, created, destroyed, and moved
28 about.
29
30 Additionally, Pophery has, incidentally during the course of its design,
31 become centrally oriented around the editing metaphors provided both by
32 classic word processors and modern graphical user interfaces — the
33 so-called "copy and paste" operations.
34
35 Program Structure
36 -----------------
37
38 All program state (instructions and variables) are encoded in a single
39 string, which is a finite but unbounded sequence of non-combining Unicode
40 code points. The string may contain any number of _locators_, which
41 take the form `(α)` where `α` is any string which does not contain `(` or
42 `)` symbols. (In the sequel, Greek letters will denote variables for
43 similar such strings.) Only the rightmost occurrence of the sequence `(α)`
44 is regarded as the locator, for the purposes of operations on that locator —
45 any other occurrences are ignored.
46
47 A pair of locators of the form `(^α)` and `(α$)`, where `(^α)` occurs to the
48 left of `(α$)`, is caled a _slot_. In the sequence `(^α)β(α$)`, `α` is
49 called the _name_ of the slot, and `β` is called the _contents_ of the slot.
50
51 A slot may contain any number of other locators. In fact, slots can overlap,
52 in the sense that a slot may contain one locator of another slot, but not the
53 other.
54
55 A slot can also be referenced indirectly, in which case the contents of the
56 slot gives the name of another slot which is the actual subject of the
57 operation. For example, `(^α)β(α$)` might refer to a slot `(^β)(β$)`
58 elsewhere in the same string. We use the terminology _slot β_ to refer to
59 direct access to the slot named `β`, and _slot indirect by β_ to refer to
60 access to a slot named by the contents of the slot named `β`.
61
62 While the programmer may define, create, and destroy slots as they like,
63 some slots have meaning to Pophery's execution model. Each of these
64 _built-in_ slots has a default name by which it is accessed. However, if a
65 _name slot_ for the built-in slot is present in the program, access is
66 indirect by the name slot. The name slot of a built-in slot named `β` is
67 named `` `β ``. (A clarifying example will appear shortly.)
68
69 A single locator can also sometimes be referenced indirectly; in this case,
70 a slot contains the substring `β` identifying the locator `(β)`. Locators
71 also support an operation called _sliding_; they may slide leftward or slide
72 rightward. When sliding rightward (resp. leftward), the character
73 immediately to the right (resp. left) of the locator is transferred to
74 immediately left (resp. right) of that locator. However, there are two
75 exceptions: other locators are disregarded when sliding (they are slid over,
76 and not counted as characters); and when there are no characters to the
77 right of the locator when sliding rightward (resp. left and leftward),
78 neither the locator nor any character moves.
79
80 Examples follow. In the program `J(X)A`, if `(X)` were to slide leftward the
81 result would be `(X)JA` and if it were to slide rightward the result would
82 be `JA(X)`.
83
84 In `J(X)(C)A(D)`, if `(X)` were to slide rightward we would have
85 `J(C)A(D)(X)`.
86
87 In `JA(X)`, if `(X)` were to slide rightward, we would still have
88 `JA(X)`.
89
90 Finally, in `JA(X)(Y)`, if `(X)` were to slide rightward, we would still
91 have `JA(X)(Y)`.
92
93 An entire slot slides leftward (resp. rightward) when both of its locators
94 slide leftward (resp. rightward.)
95
96 Built-in Slots
97 --------------
98
99 The most central built-in slot is the _instruction slot_, from which is
100 fetched the instruction to be executed on any particular rewrite step. The
101 default name of the instruction slot is `!`. Therefore, in the program
102 `(^!)M(!$)`, the next instruction to be executed will be `M`. Further, in
103 the program ``(^`!)k(`!$)(^k)b(k$)``, the instruction slot, accessed
104 indirectly by `` `! ``, is named `k`, and the next instruction to be executed
105 is `b`.
106
107 Other built-in slots are:
108
109 * The _accumulator_, by default named `?`;
110 * The _clipboard_, by default named `%`; and
111 * The _selection_, by default named `/`.
112
113 Pursuant to this last built-in slot, when we say a substring is _selected_,
114 we mean that the selection locators are inserted on either side of it
115 (`(^/)` on the left and `(/$)` on the right), and that all other occurrences
116 of these locators elsewhere in the string are removed.
117
118 Execution Model
119 ---------------
120
121 At each rewriting step, the contents of the instruction slot, called the
122 _current instruction_, are examined. The string is rewritten according to
123 the current instruction. The instruction slot then slides rightward in the
124 string.
125
126 Execution halts when there is no instruction slot in the program, or when
127 the contents of the instruction slot have zero length.
128
129 When examining the current instruction to determine the command which is
130 executed and how the string will be re-written, we interpret it as follows.
131 We ignore any locators in the current instruction, and we assume it to be
132 one character long — if it is longer, we only regard the leftmost
133 character in it.
134
135 ### Commands ###
136
137 * `0` through `9` update the accumulator to the literal strings `0` through
138 `9`, respectively.
139 * `X` ("cut") erases (updates with the zero-length string) the selection.
140 * `C` ("copy") updates the contents of the clipboard with the contents of
141 the selection.
142 * `V` ("paste") updates the contents of the selection with the contents of
143 the clipboard.
144 * `S` ("select") selects the contents of the slot indirect by the
145 accumulator.
146 * `A` ("select all") selects the contents of the accumulator.
147 * `L` ("left") slides the left locator of the selection leftward.
148 * `R` ("right") slides the left locator of the selection rightward.
149 * `E` ("end") moves the left locator of the selection to immediately to the
150 left of the right locator of the selection, resulting in the selection
151 containing the zero-length string.
152 * `F` ("find") searches everywhere in the contents of the accumulator for the
153 contents of the clipboard. If found, that substring is selected.
154 * `D` ("drag-and-drop") updates the contents of the accumulator with the
155 contents of the selection, then selects the contents of the accumulator.
156 * `I` ("input") waits for a line to appear on standard input, then places it
157 (sans newline) in the accumulator.
158 * `O` ("output") outputs the string in the accumulator to standard output,
159 followed by a newline.
160
161 Note that the concepts "standard input" and "standard output" are defined
162 solely by the operating system.
163
164 ### Idioms ###
165
166 We pause to consider some useful idioms constructed from the commands
167 presented thus far.
168
169 Assume the inital program defines some slots such as `(^0)data(0$)` to
170 contain initial data. That data can then be loaded into the accumulator
171 with the sequence `0SCAV`, and new data, say the literal string `1`, can be
172 stored into slot `0` with `1AC0SV`.
173
174 To copy from any arbitrary slot (say `0`) to another (say `1`), we can say
175 `0SC1SV`.
176
177 Accessing a slot with a longer name, such as `(^123)xyz(123$)`, can be done
178 with the help of a free slot like `0` and a program fragment such as
179 `1AC0SV2AC0SEV3AC0SEV0SCAVSD`.
180
181 To write data, say `(^8)foo(8$)`, into a slot whose name is stored in
182 another slot, such as `(^9)jim(9$)`, we can say: `8SC9SDSV`.
183
184 Finally, a complete, if simple, program: the ubiquitous "Hello, world!" can
185 be accomplished very simply like so: `(^?)Hello, world!(?$)(^!)O(!$)`.
186
187 Constructing Control Flow Mechanisms
188 ------------------------------------
189
190 To perform a conditional branch in the program, one would ensure there are
191 slots at the start of each alternative block of code to execute: call them
192 `α` and `β`. One would then update slot `` `! `` to contain either `α` or
193 `β`, making that slot the new instruction slot.
194
195 Unfortunately, you can't do that in Pophery as it stands, basically because
196 there aren't enough built-in slots to say "put the value from slot blah
197 into the slot named by the accumulator." TODO: add another built-in slot,
198 and an instruction to either swap its contents with, or copy its contents
199 to or from, and existing slot.
200
201 The slots `α` (and of course `β` as well) could be anywhere in the program,
202 so a backward branch, and thus a loop, may be affected. The only issue is
203 that the `α` slot must be re-inserted each time, as, when it is used as the
204 instruction slot, it will begin to move rightward through the program. Also,
205 as it needs to have a different name from the instruction slot currently in
206 use, switching back and forth between two instruction slot names would be a
207 necessity of such a loop.
208
209 Pophery Carrier Format: "Tranzy"
210 --------------------------------
211
212 Pophery also defines a file format for Pophery programs and their metadata;
213 this file format is called _Tranzy_. A Tranzy file is a text file consisting
214 of a number of lines. The encoding of characters is not specified. Each
215 line may begin with a `#` character, or not. Lines which do begin with `#`
216 are "comment" lines in which metadata may be embedded; these are lines which
217 are being carried in the Tranzy file, but which do not form any part of the
218 Pophery program. The non-comment lines are concatenated (sans newlines, but
219 including other whitespace) to form the Pophery program.
220
221 Tranzy does not currently define any metadata which can reside in comment
222 lines, but acknowledges and permits metadata defined by external standards
223 (de facto or otherwise). An example Tranzy file is depicted below.
224
225 #!/usr/bin/my-pophery-interpreter -w
226 # encoding: UTF-8
227 0@SLX1@SL@SXS
228 (^0)(0$)(^1)!(1$)
229
230 Notes
231 -----
232
233 Pophery came more or less into its present form on or about September 6th,
234 2010. It has lain about since then, collecting dust.
235
236 The name _Pophery_ is a mutant hybrid of the ancient Greek _Porphyry_,
237 meaning "purple", and _Poreef_, itself a mutant hybrid of pork and beef
238 featured in a certain _The Kids in the Hall_ skit. (Arguably, my
239 [Unlikely](http://catseye.tc/projects/unlikely/) language would have been a
240 better candidate for the moniker "Poreef", but unfortunately, I missed that
241 opportunity, and this is the way things turned out.)
242
243 While working up to the current design, a design plateau was reached; it had
244 a more machine-language-like feel to it, with slots called the _accumulator_,
245 the _index slot_, and the _ancillary slot_. (For posterity, I've called the
246 language that follows this design _Pophery version -1.0_, and have retained
247 its implementation in this distribution as `minus-one.py`, but it is not my
248 focus of interest and will discuss it no further here.) The ancillary slot
249 happened to have operations devised for it which resembled cut, copy, and
250 paste, and was renamed the clipboard for this reason; then everything you
251 see now kind of followed from that.
252
253 The "string rewriting" part of the description kind of has a double meaning
254 now. Not only is the imperative execution described in terms of string
255 rewriting (to the point where you could probably implement it
256 straightforwardly, or nearly so, in a language like
257 [Thue](http://catseye.tc/projects/thue/),) but the imperative instructions
258 also perform operations which are recognizably text editing — which is just
259 a politically correct way of saying "string rewriting", *n'est-ce pas*?
260
261 TODO: Find a way to manipulate data satisfactorily.
262
263 One of the original goals of the language design was to support the
264 construction of multiple control flow mechanisms from simple primitives.
265 Due to time and concentration constraints, only the conventional
266 looping-by-backwards-branching mechanism of control flow was explored.
267 However, we will speculate on two other mechanisms, which may be
268 implementable in Pophery or a modest extension thereof, in the next two
269 paragraphs.
270
271 To affect SMITH-style self-extension, one would need only make sure there is
272 a slot named `β` located to the right of the currently-executing code, and
273 then write instructions into it. The instruction slot will eventually slide
274 rightward into it. For authentic SMITH-like behavior, the instructions would
275 be written into `β` by having a slot `ζ` encompass the currently executing
276 block of code, and copying the contents of `ζ` wholesale into `β`.
277
278 To affect Muriel-style quine-continuation, one would need to establish a
279 buffer slot named `β`, write instructions into it piecemeal until it looks
280 like the desired next leg of the program, and then replace the entire
281 running program with it. This can be done by having a slot named `ζ`
282 encompass the entire program, and copying `β` into it. The only subtlety is
283 the instruction slot; once the contents of `β` have become the entire
284 program, you will want the intended instruction slot in `β` to become the
285 active instruction slot, which means switching the instruction slot at the
286 same time `β` is copied into `ζ`. This would probably necessitate an
287 extension to Pophery.
288
289 Happy re: righting!
290 Chris Pressey
291 September 6, 2010
292 Evanston, IL
0 #!/usr/bin/env pophery.py
1 # encoding: UTF-8
2 # data slots
3 (^0)this(0$)(^1)andthat(1$)(^2)P(2$)(^3)`!(3$)
4 # clipboard
5 (^%)(%$)
6 # accumulator
7 (^?)(?$)
8 # name of currently executing instruction slot
9 (^`!)!(`!$)
10 # begin program. put contents of slot `3` into accumulator
11 (^!)3(!$)SCAV
12 # select slot named by the accumulator and... put the
13 # contents of the accumulator in it. No, no! We need these
14 # to be two different names! Argh!
15 SV
16 # put contents of slot `3` into accumulator, again
17 3SCAV
18 2SC3SDSV
19 (^P)0(P$)SCAVO
20 (^Q)1(Q$)SCAVO
0 #!/usr/bin/env pophery.py
1 # -- encoding: UTF-8
2 (^?)Hello, ωorld
3 !
4 (?$)(^!)O(!$)OO
0 # -*- coding: utf-8 -*-
1
2 """
3 Semantics for the Pophery Programming Language, Version Minus One.
4
5 These are the original semantics I devised for Pophery, but ended up not
6 liking. But I couldn't bear to throw them away either, so I stuffed them
7 into this (basically throw-away) language.
8
9 I don't think these semantics are Turing-complete.
10
11 Actually getting this to run will take a little hacking around with this file
12 and pophery.py, but should not be very hard.
13
14 """
15
16
17 from pophery import Program
18
19
20 class Semantics(Program):
21 def execute(self, instruction):
22 """Apply the semantics of the given instruction to this Program.
23
24 >>> p = Semantics("(^?)(?$)")
25 >>> p.execute('0')
26 >>> print str(p)
27 (^?)0(?$)
28
29 >>> p = Semantics("(^@)(@$)")
30 >>> p.execute('@')
31 >>> print str(p)
32 (^@)@(@$)
33 >>> p = Semantics("(^`@)Jim(`@$)(^Jim)?(Jim$)")
34 >>> p.execute('@')
35 >>> print str(p)
36 (^`@)Jim(`@$)(^Jim)Jim(Jim$)
37
38 >>> p = Semantics("(^?)(?$)(^@)0(@$)(^0)Seven(0$)")
39 >>> p.execute('G')
40 >>> print str(p)
41 (^?)Seven(?$)(^@)0(@$)(^0)Seven(0$)
42
43 >>> p = Semantics("(^?)Meerkat(?$)(^@)0(@$)(^0)Seven(0$)")
44 >>> p.execute('P')
45 >>> print str(p)
46 (^?)Meerkat(?$)(^@)0(@$)(^0)Meerkat(0$)
47
48 >>> p = Semantics("(^?)!(?$)(^@)0(@$)(^0)Fenesrate(0$)")
49 >>> p.execute('A')
50 >>> print str(p)
51 (^?)!(?$)(^@)0(@$)(^0)!Fenesrate(0$)
52 >>> p.execute('Z')
53 >>> print str(p)
54 (^?)!(?$)(^@)0(@$)(^0)!Fenesrate!(0$)
55
56 >>> p = Semantics("(^@)0(@$)(^0)Seven(0$)")
57 >>> p.execute('X')
58 >>> print str(p)
59 (^@)0(@$)(^0)(0$)
60
61 >>> p = Semantics("(^@)0(@$)(^0)Licorice(0$)(^%)(%$)")
62 >>> p.execute('C')
63 >>> print str(p)
64 (^@)0(@$)(^0)Licorice(0$)(^%)Licorice(%$)
65
66 >>> p = Semantics("(^@)0(@$)(^0)Rock(0$)(^%)well(%$)")
67 >>> p.execute('V')
68 >>> print str(p)
69 (^@)0(@$)(^0)Rockwell(0$)(^%)well(%$)
70
71 >>> p = Semantics("(^@)0(@$)(^0)Rock(0$)(^%)well(%$)")
72 >>> p.execute('V')
73 >>> print str(p)
74 (^@)0(@$)(^0)Rockwell(0$)(^%)well(%$)
75
76 >>> p = Semantics("(^?)Hello, world!(?$)")
77 >>> p.execute('O')
78 Hello, world!
79 >>> print str(p)
80 (^?)Hello, world!(?$)
81
82 >>> from StringIO import StringIO
83 >>> p = Semantics("(^?)(?$)")
84 >>> p.input = StringIO(chr(10).join(["Line.", "Line!", "LINE!"]))
85 >>> p.execute('I')
86 >>> print str(p)
87 (^?)Line.(?$)
88 >>> p.execute('I')
89 >>> print str(p)
90 (^?)Line!(?$)
91 >>> p.execute('I')
92 >>> print str(p)
93 (^?)LINE!(?$)
94 >>> p.execute('I')
95 >>> print str(p)
96 (^?)(?$)
97
98 """
99 if instruction >= '0' and instruction <= '9':
100 self.update_slot(self.get_slot_name('?'), instruction)
101 elif instruction == '@':
102 self.update_slot(self.get_slot_name('@'), self.get_slot_name('@'))
103 elif instruction == 'G':
104 self.update_slot(self.get_slot_name('?'), self.read_slot_indirect(self.get_slot_name('@')))
105 elif instruction == 'P':
106 self.update_slot_indirect(self.get_slot_name('@'), self.read_slot('?'))
107 elif instruction == 'A':
108 value = self.read_slot('?') + self.read_slot_indirect(self.get_slot_name('@'))
109 self.update_slot_indirect(self.get_slot_name('@'), value)
110 elif instruction == 'Z':
111 value = self.read_slot_indirect(self.get_slot_name('@')) + self.read_slot('?')
112 self.update_slot_indirect(self.get_slot_name('@'), value)
113 elif instruction == 'X':
114 self.update_slot_indirect(self.get_slot_name('@'), '')
115 elif instruction == 'C':
116 self.update_slot(self.get_slot_name('%'), self.read_slot_indirect(self.get_slot_name('@')))
117 elif instruction == 'V':
118 value = self.read_slot_indirect(self.get_slot_name('@')) + self.read_slot('%')
119 self.update_slot_indirect(self.get_slot_name('@'), value)
120 elif instruction == 'O':
121 line = self.read_slot('?') + "\n"
122 try:
123 self.output.write(line)
124 except UnicodeEncodeError:
125 self.output.write(line.encode('ascii', 'xmlcharrefreplace'))
126 elif instruction == 'I':
127 text = self.input.readline()
128 if text.endswith('\n'):
129 text = text[:-1]
130 self.update_slot(self.get_slot_name('?'), text)
131 else:
132 pass
133
134 def step(self):
135 """Execute one step of this Pophery program.
136
137 >>> p = Semantics("(^?)Hello, world!(?$)(^!)O(!$)")
138 >>> p.step()
139 Hello, world!
140 True
141 >>> print str(p)
142 (^?)Hello, world!(?$)O(^!)(!$)
143 >>> p.step()
144 False
145 >>> print str(p)
146 (^?)Hello, world!(?$)O(^!)(!$)
147
148 """
149 return super(Semantics, self).step()
150
151 def run(self):
152 """Execute this Pophery program and return only when it terminates.
153
154 >>> p = Semantics("(^?)Hello, world!(?$)(^!)O(!$)OO")
155 >>> p.run()
156 Hello, world!
157 Hello, world!
158 Hello, world!
159
160 >>> p = Semantics("(^0)Load Me(0$)(^?)(?$)(^@)(@$) (^!)0(!$)@PG")
161 >>> p.run()
162 >>> print str(p)
163 (^0)Load Me(0$)(^?)Load Me(?$)(^@)0(@$) 0@PG(^!)(!$)
164
165 >>> p = Semantics("(^0)Overwrite Me(0$)(^?)(?$)(^@)(@$) (^!)0(!$)@P1P")
166 >>> p.run()
167 >>> print str(p)
168 (^0)1(0$)(^?)1(?$)(^@)0(@$) 0@P1P(^!)(!$)
169
170 Accessing a slot with a longer name can be done with the help of a free slot:
171
172 >>> p = Semantics("(^123)xyz(123$)(^0)(0$)(^?)(?$)(^@)(@$) (^!)0(!$)@P1P2Z3ZG@PG")
173 >>> p.run()
174 >>> print str(p)
175 (^123)xyz(123$)(^0)123(0$)(^?)xyz(?$)(^@)123(@$) 0@P1P2Z3ZG@PG(^!)(!$)
176
177 Let's use the clipboard to construct that name instead:
178
179 >>> p = Semantics("(^123)xyz(123$)(^0)(0$)(^1)(1$)(^?)(?$)(^@)(@$)(^%)(%$) (^!) (!$)0@P1PC1@PV 0@P2PC1@PV 0@P3PC1@PV 1@PG@PG")
180 >>> p.run()
181 >>> print str(p)
182 (^123)xyz(123$)(^0)3(0$)(^1)123(1$)(^?)xyz(?$)(^@)123(@$)(^%)3(%$) 0@P1PC1@PV 0@P2PC1@PV 0@P3PC1@PV 1@PG@PG(^!)(!$)
183
184 To copy from one arbitrary slot to another, we can use the clipboard:
185
186 >>> p = Semantics("(^0)Copy Me(0$)(^1)Overwrite Me(1$)(^?)(?$)(^@)(@$)(^%)(%$) (^!)0(!$)0@PC1@PXV")
187 >>> p.run()
188 >>> print str(p)
189 (^0)Copy Me(0$)(^1)Copy Me(1$)(^?)1(?$)(^@)1(@$)(^%)Copy Me(%$) 00@PC1@PXV(^!)(!$)
190
191 We can also use the clipboard "cut" command to halt the program, like so:
192
193 >>> p = Semantics("(^0)!(0$)(^?)(?$)(^@)(@$) (^!)0(!$)0@PG@PXOOOOO")
194 >>> p.run()
195 >>> print str(p)
196 (^0)!(0$)(^?)!(?$)(^@)!(@$) 00@PG@PO(^!)(!$)OOOO
197
198 """
199 return super(Semantics, self).run()
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)