Commits

Anonymous committed a8e5e57

Initial import of Pophery version 0.1 or something sources.

  • Participants

Comments (0)

Files changed (5)

File doc/Pophery.markdown

+Pophery
+=======
+
+Version 0.1 or something
+Chris Pressey, Cat's Eye Technologies
+
+Introduction
+------------
+
+_Pophery_ is an imperative string-rewriting language.  I know right?
+
+In Pophery, each program state is a single string, and a program is simply
+the initial program state.  As execution proceeds, the string is rewritten
+based on instructions found within the string.  Pophery is a "visible"
+programming language, in the sense that there is no program state that is
+not part of the string.
+
+Pophery provides primitive instructions which allow the programmer to
+construct their own control flow mechanisms, including at least conventional
+backwards-branch looping, but possibly also permitting alternative
+techniques such as [SMITH](http://catseye.tc/projects/smith/)-style
+program-extension and [Muriel](http://catseye.tc/projects/muriel/)-style
+quine-continuation.
+
+As a reaction against the proliferation of stack-based esolangs, Pophery's
+design explicitly avoids having a stack, preferring instead register-like
+storage in the form of delimited substrings, called _slots_, which may be
+accessed directly and indirectly, updated, created, destroyed, and moved
+about.
+
+Additionally, Pophery has, incidentally during the course of its design,
+become centrally oriented around the editing metaphors provided both by
+classic word processors and modern graphical user interfaces — the
+so-called "copy and paste" operations.
+
+Program Structure
+-----------------
+
+All program state (instructions and variables) are encoded in a single
+string, which is a finite but unbounded sequence of non-combining Unicode
+code points.  The string may contain any number of _locators_, which
+take the form `(α)` where `α` is any string which does not contain `(` or
+`)` symbols.  (In the sequel, Greek letters will denote variables for
+similar such strings.)  Only the rightmost occurrence of the sequence `(α)`
+is regarded as the locator, for the purposes of operations on that locator —
+any other occurrences are ignored.
+
+A pair of locators of the form `(^α)` and `(α$)`, where `(^α)` occurs to the
+left of `(α$)`, is caled a _slot_.  In the sequence `(^α)β(α$)`, `α` is
+called the _name_ of the slot, and `β` is called the _contents_ of the slot.
+
+A slot may contain any number of other locators.  In fact, slots can overlap,
+in the sense that a slot may contain one locator of another slot, but not the
+other.
+
+A slot can also be referenced indirectly, in which case the contents of the
+slot gives the name of another slot which is the actual subject of the
+operation.  For example, `(^α)β(α$)` might refer to a slot `(^β)(β$)`
+elsewhere in the same string.  We use the terminology _slot β_ to refer to
+direct access to the slot named `β`, and _slot indirect by β_ to refer to
+access to a slot named by the contents of the slot named `β`.
+
+While the programmer may define, create, and destroy slots as they like,
+some slots have meaning to Pophery's execution model.  Each of these
+_built-in_ slots has a default name by which it is accessed.  However, if a
+_name slot_ for the built-in slot is present in the program, access is
+indirect by the name slot.  The name slot of a built-in slot named `β` is
+named `` `β ``.  (A clarifying example will appear shortly.)
+
+A single locator can also sometimes be referenced indirectly; in this case,
+a slot contains the substring `β` identifying the locator `(β)`.  Locators
+also support an operation called _sliding_; they may slide leftward or slide
+rightward.  When sliding rightward (resp. leftward), the character
+immediately to the right (resp. left) of the locator is transferred to
+immediately left (resp. right) of that locator.   However, there are two
+exceptions: other locators are disregarded when sliding (they are slid over,
+and not counted as characters); and when there are no characters to the
+right of the locator when sliding rightward (resp. left and leftward),
+neither the locator nor any character moves.
+
+Examples follow. In the program `J(X)A`, if `(X)` were to slide leftward the
+result would be `(X)JA` and if it were to slide rightward the result would
+be `JA(X)`.
+
+In `J(X)(C)A(D)`, if `(X)` were to slide rightward we would have
+`J(C)A(D)(X)`.
+
+In `JA(X)`, if `(X)` were to slide rightward, we would still have
+`JA(X)`.
+
+Finally, in `JA(X)(Y)`, if `(X)` were to slide rightward, we would still
+have `JA(X)(Y)`.
+
+An entire slot slides leftward (resp. rightward) when both of its locators
+slide leftward (resp. rightward.)
+
+Built-in Slots
+--------------
+
+The most central built-in slot is the _instruction slot_, from which is
+fetched the instruction to be executed on any particular rewrite step.  The
+default name of the instruction slot is `!`.  Therefore, in the program
+`(^!)M(!$)`, the next instruction to be executed will be `M`.  Further, in
+the program ``(^`!)k(`!$)(^k)b(k$)``, the instruction slot, accessed
+indirectly by `` `! ``, is named `k`, and the next instruction to be executed
+is `b`.
+
+Other built-in slots are:
+
+* The _accumulator_, by default named `?`;
+* The _clipboard_, by default named `%`; and
+* The _selection_, by default named `/`.
+
+Pursuant to this last built-in slot, when we say a substring is _selected_,
+we mean that the selection locators are inserted on either side of it
+(`(^/)` on the left and `(/$)` on the right), and that all other occurrences
+of these locators elsewhere in the string are removed.
+
+Execution Model
+---------------
+
+At each rewriting step, the contents of the instruction slot, called the
+_current instruction_, are examined.  The string is rewritten according to
+the current instruction.  The instruction slot then slides rightward in the
+string.
+
+Execution halts when there is no instruction slot in the program, or when
+the contents of the instruction slot have zero length.
+
+When examining the current instruction to determine the command which is
+executed and how the string will be re-written, we interpret it as follows.
+We ignore any locators in the current instruction, and we assume it to be
+one character long — if it is longer, we only regard the leftmost
+character in it.
+
+### Commands ###
+
+* `0` through `9` update the accumulator to the literal strings `0` through
+  `9`, respectively.
+* `X` ("cut") erases (updates with the zero-length string) the selection.
+* `C` ("copy") updates the contents of the clipboard with the contents of
+  the selection.
+* `V` ("paste") updates the contents of the selection with the contents of
+  the clipboard.
+* `S` ("select") selects the contents of the slot indirect by the
+  accumulator.
+* `A` ("select all") selects the contents of the accumulator.
+* `L` ("left") slides the left locator of the selection leftward.
+* `R` ("right") slides the left locator of the selection rightward.
+* `E` ("end") moves the left locator of the selection to immediately to the
+  left of the right locator of the selection, resulting in the selection
+  containing the zero-length string.
+* `F` ("find") searches everywhere in the contents of the accumulator for the
+  contents of the clipboard.  If found, that substring is selected.
+* `D` ("drag-and-drop") updates the contents of the accumulator with the
+  contents of the selection, then selects the contents of the accumulator.
+* `I` ("input") waits for a line to appear on standard input, then places it
+  (sans newline) in the accumulator.
+* `O` ("output") outputs the string in the accumulator to standard output,
+  followed by a newline.
+
+Note that the concepts "standard input" and "standard output" are defined
+solely by the operating system.
+
+### Idioms ###
+
+We pause to consider some useful idioms constructed from the commands
+presented thus far.
+
+Assume the inital program defines some slots such as `(^0)data(0$)` to
+contain initial data.  That data can then be loaded into the accumulator
+with the sequence `0SCAV`, and new data, say the literal string `1`, can be
+stored into slot `0` with `1AC0SV`.
+
+To copy from any arbitrary slot (say `0`) to another (say `1`), we can say
+`0SC1SV`.
+
+Accessing a slot with a longer name, such as `(^123)xyz(123$)`, can be done
+with the help of a free slot like `0` and a program fragment such as
+`1AC0SV2AC0SEV3AC0SEV0SCAVSD`.
+
+To write data, say `(^8)foo(8$)`, into a slot whose name is stored in
+another slot, such as `(^9)jim(9$)`, we can say: `8SC9SDSV`.
+
+Finally, a complete, if simple, program: the ubiquitous "Hello, world!" can
+be accomplished very simply like so: `(^?)Hello, world!(?$)(^!)O(!$)`.
+
+Constructing Control Flow Mechanisms
+------------------------------------
+
+To perform a conditional branch in the program, one would ensure there are
+slots at the start of each alternative block of code to execute: call them
+`α` and `β`.  One would then update slot `` `! `` to contain either `α` or
+`β`, making that slot the new instruction slot.
+
+Unfortunately, you can't do that in Pophery as it stands, basically because
+there aren't enough built-in slots to say "put the value from slot blah
+into the slot named by the accumulator."  TODO: add another built-in slot,
+and an instruction to either swap its contents with, or copy its contents
+to or from, and existing slot.
+
+The slots `α` (and of course `β` as well) could be anywhere in the program,
+so a backward branch, and thus a loop, may be affected.  The only issue is
+that the `α` slot must be re-inserted each time, as, when it is used as the
+instruction slot, it will begin to move rightward through the program.  Also,
+as it needs to have a different name from the instruction slot currently in
+use, switching back and forth between two instruction slot names would be a
+necessity of such a loop.
+
+Pophery Carrier Format: "Tranzy"
+--------------------------------
+
+Pophery also defines a file format for Pophery programs and their metadata;
+this file format is called _Tranzy_.  A Tranzy file is a text file consisting
+of a number of lines.  The encoding of characters is not specified.  Each
+line may begin with a `#` character, or not.  Lines which do begin with `#`
+are "comment" lines in which metadata may be embedded; these are lines which
+are being carried in the Tranzy file, but which do not form any part of the
+Pophery program.  The non-comment lines are concatenated (sans newlines, but
+including other whitespace) to form the Pophery program.
+
+Tranzy does not currently define any metadata which can reside in comment
+lines, but acknowledges and permits metadata defined by external standards
+(de facto or otherwise).  An example Tranzy file is depicted below.
+
+    #!/usr/bin/my-pophery-interpreter -w
+    # encoding: UTF-8
+    0@SLX1@SL@SXS
+    (^0)(0$)(^1)!(1$)
+
+Notes
+-----
+
+Pophery came more or less into its present form on or about September 6th,
+2010.  It has lain about since then, collecting dust.
+
+The name _Pophery_ is a mutant hybrid of the ancient Greek _Porphyry_,
+meaning "purple", and _Poreef_, itself a mutant hybrid of pork and beef
+featured in a certain _The Kids in the Hall_ skit.  (Arguably, my
+[Unlikely](http://catseye.tc/projects/unlikely/) language would have been a
+better candidate for the moniker "Poreef", but unfortunately, I missed that
+opportunity, and this is the way things turned out.)
+
+While working up to the current design, a design plateau was reached; it had
+a more machine-language-like feel to it, with slots called the _accumulator_,
+the _index slot_, and the _ancillary slot_.  (For posterity, I've called the
+language that follows this design _Pophery version -1.0_, and have retained
+its implementation in this distribution as `minus-one.py`, but it is not my
+focus of interest and will discuss it no further here.)  The ancillary slot
+happened to have operations devised for it which resembled cut, copy, and
+paste, and was renamed the clipboard for this reason; then everything you
+see now kind of followed from that.
+
+The "string rewriting" part of the description kind of has a double meaning
+now.  Not only is the imperative execution described in terms of string
+rewriting (to the point where you could probably implement it
+straightforwardly, or nearly so, in a language like
+[Thue](http://catseye.tc/projects/thue/),) but the imperative instructions
+also perform operations which are recognizably text editing — which is just
+a politically correct way of saying "string rewriting", *n'est-ce pas*?
+
+TODO: Find a way to manipulate data satisfactorily.
+
+One of the original goals of the language design was to support the
+construction of multiple control flow mechanisms from simple primitives.
+Due to time and concentration constraints, only the conventional
+looping-by-backwards-branching mechanism of control flow was explored.
+However, we will speculate on two other mechanisms, which may be
+implementable in Pophery or a modest extension thereof, in the next two
+paragraphs.
+
+To affect SMITH-style self-extension, one would need only make sure there is
+a slot named `β` located to the right of the currently-executing code, and
+then write instructions into it.  The instruction slot will eventually slide
+rightward into it.  For authentic SMITH-like behavior, the instructions would
+be written into `β` by having a slot `ζ` encompass the currently executing
+block of code, and copying the contents of `ζ` wholesale into `β`.
+
+To affect Muriel-style quine-continuation, one would need to establish a
+buffer slot named `β`, write instructions into it piecemeal until it looks
+like the desired next leg of the program, and then replace the entire
+running program with it.  This can be done by having a slot named `ζ`
+encompass the entire program, and copying `β` into it.  The only subtlety is
+the instruction slot; once the contents of `β` have become the entire
+program, you will want the intended instruction slot in `β` to become the
+active instruction slot, which means switching the instruction slot at the
+same time `β` is copied into `ζ`.  This would probably necessitate an
+extension to Pophery.
+
+Happy re: righting!  
+Chris Pressey  
+September 6, 2010  
+Evanston, IL

File eg/branch.tranzy

+#!/usr/bin/env pophery.py
+# encoding: UTF-8
+# data slots
+(^0)this(0$)(^1)andthat(1$)(^2)P(2$)(^3)`!(3$)
+# clipboard
+(^%)(%$)
+# accumulator
+(^?)(?$)
+# name of currently executing instruction slot
+(^`!)!(`!$)
+# begin program.  put contents of slot `3` into accumulator
+(^!)3(!$)SCAV
+# select slot named by the accumulator and... put the
+# contents of the accumulator in it.  No, no!  We need these
+# to be two different names!  Argh!
+SV
+# put contents of slot `3` into accumulator, again
+3SCAV
+2SC3SDSV
+(^P)0(P$)SCAVO
+(^Q)1(Q$)SCAVO

File eg/hello.tranzy

+#!/usr/bin/env pophery.py
+# -- encoding: UTF-8
+(^?)Hello, ωorld
+!
+(?$)(^!)O(!$)OO

File src/minus-one.py

+# -*- coding: utf-8 -*-
+
+"""
+Semantics for the Pophery Programming Language, Version Minus One.
+
+These are the original semantics I devised for Pophery, but ended up not
+liking.  But I couldn't bear to throw them away either, so I stuffed them
+into this (basically throw-away) language.
+
+I don't think these semantics are Turing-complete.
+
+Actually getting this to run will take a little hacking around with this file
+and pophery.py, but should not be very hard.
+
+"""
+
+
+from pophery import Program
+
+
+class Semantics(Program):
+    def execute(self, instruction):
+        """Apply the semantics of the given instruction to this Program.
+
+        >>> p = Semantics("(^?)(?$)")
+        >>> p.execute('0')
+        >>> print str(p)
+        (^?)0(?$)
+
+        >>> p = Semantics("(^@)(@$)")
+        >>> p.execute('@')
+        >>> print str(p)
+        (^@)@(@$)
+        >>> p = Semantics("(^`@)Jim(`@$)(^Jim)?(Jim$)")
+        >>> p.execute('@')
+        >>> print str(p)
+        (^`@)Jim(`@$)(^Jim)Jim(Jim$)
+
+        >>> p = Semantics("(^?)(?$)(^@)0(@$)(^0)Seven(0$)")
+        >>> p.execute('G')
+        >>> print str(p)
+        (^?)Seven(?$)(^@)0(@$)(^0)Seven(0$)
+
+        >>> p = Semantics("(^?)Meerkat(?$)(^@)0(@$)(^0)Seven(0$)")
+        >>> p.execute('P')
+        >>> print str(p)
+        (^?)Meerkat(?$)(^@)0(@$)(^0)Meerkat(0$)
+
+        >>> p = Semantics("(^?)!(?$)(^@)0(@$)(^0)Fenesrate(0$)")
+        >>> p.execute('A')
+        >>> print str(p)
+        (^?)!(?$)(^@)0(@$)(^0)!Fenesrate(0$)
+        >>> p.execute('Z')
+        >>> print str(p)
+        (^?)!(?$)(^@)0(@$)(^0)!Fenesrate!(0$)
+
+        >>> p = Semantics("(^@)0(@$)(^0)Seven(0$)")
+        >>> p.execute('X')
+        >>> print str(p)
+        (^@)0(@$)(^0)(0$)
+
+        >>> p = Semantics("(^@)0(@$)(^0)Licorice(0$)(^%)(%$)")
+        >>> p.execute('C')
+        >>> print str(p)
+        (^@)0(@$)(^0)Licorice(0$)(^%)Licorice(%$)
+
+        >>> p = Semantics("(^@)0(@$)(^0)Rock(0$)(^%)well(%$)")
+        >>> p.execute('V')
+        >>> print str(p)
+        (^@)0(@$)(^0)Rockwell(0$)(^%)well(%$)
+
+        >>> p = Semantics("(^@)0(@$)(^0)Rock(0$)(^%)well(%$)")
+        >>> p.execute('V')
+        >>> print str(p)
+        (^@)0(@$)(^0)Rockwell(0$)(^%)well(%$)
+
+        >>> p = Semantics("(^?)Hello, world!(?$)")
+        >>> p.execute('O')
+        Hello, world!
+        >>> print str(p)
+        (^?)Hello, world!(?$)
+
+        >>> from StringIO import StringIO
+        >>> p = Semantics("(^?)(?$)")
+        >>> p.input = StringIO(chr(10).join(["Line.", "Line!", "LINE!"]))
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)Line.(?$)
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)Line!(?$)
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)LINE!(?$)
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)(?$)
+
+        """
+        if instruction >= '0' and instruction <= '9':
+            self.update_slot(self.get_slot_name('?'), instruction)
+        elif instruction == '@':
+            self.update_slot(self.get_slot_name('@'), self.get_slot_name('@'))
+        elif instruction == 'G':
+            self.update_slot(self.get_slot_name('?'), self.read_slot_indirect(self.get_slot_name('@')))
+        elif instruction == 'P':
+            self.update_slot_indirect(self.get_slot_name('@'), self.read_slot('?'))
+        elif instruction == 'A':
+            value = self.read_slot('?') + self.read_slot_indirect(self.get_slot_name('@'))
+            self.update_slot_indirect(self.get_slot_name('@'), value)
+        elif instruction == 'Z':
+            value = self.read_slot_indirect(self.get_slot_name('@')) + self.read_slot('?') 
+            self.update_slot_indirect(self.get_slot_name('@'), value)
+        elif instruction == 'X':
+            self.update_slot_indirect(self.get_slot_name('@'), '')
+        elif instruction == 'C':
+            self.update_slot(self.get_slot_name('%'), self.read_slot_indirect(self.get_slot_name('@')))
+        elif instruction == 'V':
+            value = self.read_slot_indirect(self.get_slot_name('@')) + self.read_slot('%')
+            self.update_slot_indirect(self.get_slot_name('@'), value)
+        elif instruction == 'O':
+            line = self.read_slot('?') + "\n"
+            try:
+                self.output.write(line)
+            except UnicodeEncodeError:
+                self.output.write(line.encode('ascii', 'xmlcharrefreplace'))
+        elif instruction == 'I':
+            text = self.input.readline()
+            if text.endswith('\n'):
+                text = text[:-1]
+            self.update_slot(self.get_slot_name('?'), text)
+        else:
+            pass
+
+    def step(self):
+        """Execute one step of this Pophery program.
+
+        >>> p = Semantics("(^?)Hello, world!(?$)(^!)O(!$)")
+        >>> p.step()
+        Hello, world!
+        True
+        >>> print str(p)
+        (^?)Hello, world!(?$)O(^!)(!$)
+        >>> p.step()
+        False
+        >>> print str(p)
+        (^?)Hello, world!(?$)O(^!)(!$)
+
+        """
+        return super(Semantics, self).step()
+
+    def run(self):
+        """Execute this Pophery program and return only when it terminates.
+
+        >>> p = Semantics("(^?)Hello, world!(?$)(^!)O(!$)OO")
+        >>> p.run()
+        Hello, world!
+        Hello, world!
+        Hello, world!
+
+        >>> p = Semantics("(^0)Load Me(0$)(^?)(?$)(^@)(@$) (^!)0(!$)@PG")
+        >>> p.run()
+        >>> print str(p)
+        (^0)Load Me(0$)(^?)Load Me(?$)(^@)0(@$) 0@PG(^!)(!$)
+
+        >>> p = Semantics("(^0)Overwrite Me(0$)(^?)(?$)(^@)(@$) (^!)0(!$)@P1P")
+        >>> p.run()
+        >>> print str(p)
+        (^0)1(0$)(^?)1(?$)(^@)0(@$) 0@P1P(^!)(!$)
+
+        Accessing a slot with a longer name can be done with the help of a free slot:
+        
+        >>> p = Semantics("(^123)xyz(123$)(^0)(0$)(^?)(?$)(^@)(@$) (^!)0(!$)@P1P2Z3ZG@PG")
+        >>> p.run()
+        >>> print str(p)
+        (^123)xyz(123$)(^0)123(0$)(^?)xyz(?$)(^@)123(@$) 0@P1P2Z3ZG@PG(^!)(!$)
+
+        Let's use the clipboard to construct that name instead:
+
+        >>> p = Semantics("(^123)xyz(123$)(^0)(0$)(^1)(1$)(^?)(?$)(^@)(@$)(^%)(%$) (^!) (!$)0@P1PC1@PV 0@P2PC1@PV 0@P3PC1@PV 1@PG@PG")
+        >>> p.run()
+        >>> print str(p)
+        (^123)xyz(123$)(^0)3(0$)(^1)123(1$)(^?)xyz(?$)(^@)123(@$)(^%)3(%$)  0@P1PC1@PV 0@P2PC1@PV 0@P3PC1@PV 1@PG@PG(^!)(!$)
+
+        To copy from one arbitrary slot to another, we can use the clipboard:
+
+        >>> p = Semantics("(^0)Copy Me(0$)(^1)Overwrite Me(1$)(^?)(?$)(^@)(@$)(^%)(%$) (^!)0(!$)0@PC1@PXV")
+        >>> p.run()
+        >>> print str(p)
+        (^0)Copy Me(0$)(^1)Copy Me(1$)(^?)1(?$)(^@)1(@$)(^%)Copy Me(%$) 00@PC1@PXV(^!)(!$)
+
+        We can also use the clipboard "cut" command to halt the program, like so:
+
+        >>> p = Semantics("(^0)!(0$)(^?)(?$)(^@)(@$) (^!)0(!$)0@PG@PXOOOOO")
+        >>> p.run()
+        >>> print str(p)
+        (^0)!(0$)(^?)!(?$)(^@)!(@$) 00@PG@PO(^!)(!$)OOOO
+
+        """
+        return super(Semantics, self).run()

File src/pophery.py

+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Interpreter for the Pophery Programming Language v0.1 or something.
+
+"""
+
+LICENSE = """\
+Copyright (c)2011 Chris Pressey, Cat's Eye Technologies.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+  1. Redistributions of source code must retain the above copyright
+     notices, this list of conditions and the following disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright
+     notices, this list of conditions, and the following disclaimer in
+     the documentation and/or other materials provided with the
+     distribution.
+  3. Neither the names of the copyright holders nor the names of their
+     contributors may be used to endorse or promote products derived
+     from this software without specific prior written permission. 
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
+COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+"""
+
+
+import sys
+from optparse import OptionParser
+
+
+class UndefinedLocatorError(Exception):
+    """Indicates a given locator was not found in a MutableString."""
+    pass
+
+
+class MutableString(object):
+    """String-like object which may be updated in place.
+
+    A MutableString emulates a Python unicode datatype in many ways,
+    with the most notable exception being that its contents may
+    change over time.  In addition, it supports a rich set of operations
+    for enacting these changes.
+
+    Changes are often made relative to one or more locators.
+    A locator string uniquely locates a position within a MutableString.
+    A locator is a substring which is unique within the MutableString.
+    If the locator is not unique, the behaviour of a change made
+    relative to it is undefined.
+
+    """
+    def __init__(self, initial):
+        self.string = unicode(initial)
+
+    def __str__(self):
+        return self.__unicode__()
+
+    def __unicode__(self):
+        return self.string
+
+    def __len__(self):
+        return len(self.string)
+    
+    def __getitem__(self, index):
+        return self.string[index]
+
+    def __getslice__(self, i, j):
+        return self.string[i:j]
+
+    def find(self, sub):
+        return self.string.find(sub)
+    
+    def set(self, string):
+        self.string = unicode(string)
+
+    def pos_left(self, locator, delta):
+        """Return the 0-based position within this MutableString of the
+        first character of the given locator, plus the given offset.
+
+        Note that the returned value is ephemeral and should not be
+        stored, as it is subject to change at any time the MutableString
+        is changed.
+
+        >>> a = MutableString("Mom(*)entous")
+        >>> print a.pos_left("(*)", 0)
+        3
+
+        """
+        pos = self.find(locator)
+        if pos == -1:
+            raise UndefinedLocatorError(locator)
+        return pos - delta
+
+    def pos_right(self, locator, delta):
+        """Return the 0-based position within this MutableString of the
+        first character to the right of the given locator, plus the given
+        offset.
+
+        Note that the returned value is ephemeral and should not be
+        stored, as it is subject to change at any time the MutableString
+        is changed.
+
+        >>> a = MutableString("Mom(*)entous")
+        >>> print a.pos_right("(*)", 0)
+        6
+
+        """
+        pos = self.find(locator)
+        if pos == -1:
+            raise UndefinedLocatorError(locator)
+        return pos + len(locator) + delta
+
+    def insert_locator(self, locator, pos):
+        """Insert the given locator at the given position in this string.
+
+        Note that this will blithely insert the new locator inside an
+        existing locator.
+
+        >>> a = MutableString("Momentous")
+        >>> a.insert_locator("(*)", 3)
+        >>> print str(a)
+        Mom(*)entous
+
+        """
+        self.set(self[:pos] + unicode(locator) + self[pos:])
+
+    def remove_locator(self, locator):
+        """Remove the given locator from this string.
+
+        >>> a = MutableString("Mom(*)entous")
+        >>> a.remove_locator("(*)")
+        >>> print str(a)
+        Momentous
+
+        """
+        locator = unicode(locator)
+        posl = self.pos_left(locator, 0)
+        posr = self.pos_right(locator, 0)
+        self.set(self[:posl] + self[posr:])
+
+    def move_locator(self, locator, delta):
+        """Change the position of the given locator by the given delta.
+
+        Note that this will not skip over intervening locators; i.e. it will
+        allow the locator to end up inside another locator.
+
+        >>> a = MutableString("Mom(*)entous")
+        >>> a.move_locator("(*)", +3)
+        >>> print str(a)
+        Moment(*)ous
+
+        """
+        locator = unicode(locator)
+        posl = self.pos_left(locator, 0)
+        posr = self.pos_right(locator, 0)
+        self.set(self[:posl] + self[posr:])
+        posl = posl + delta
+        self.set(self[:posl] + locator + self[posl:])
+
+    def slide_locator(self, locator, delta):
+        """Slide the position of the given locator by the given delta.
+
+        Note that this will skip over intervening locators; i.e. it will
+        avoid having the locator end up inside another locator.
+        
+        Delta must be +1 or -1.
+
+        >>> a = MutableString("Mom(*)en(+)tous")
+        >>> a.slide_locator("(*)", +1)
+        >>> print str(a)
+        Mome(*)n(+)tous
+        >>> a.slide_locator("(*)", -1)
+        >>> print str(a)
+        Mom(*)en(+)tous
+
+        >>> b = MutableString("(-)Cassowary(+)")
+        >>> b.slide_locator("(+)", +1)
+        >>> print str(b)
+        (-)Cassowary(+)
+        >>> b.slide_locator("(-)", -1)
+        >>> print str(b)
+        (-)Cassowary(+)
+
+        >>> c = MutableString("Imb(+)r(%)oglio")
+        >>> c.slide_locator("(+)", +1)
+        >>> print str(c)
+        Imbr(+)(%)oglio
+
+        """
+        locator = unicode(locator)
+        if delta == +1:
+            matching = True
+            target = self.pos_right(locator, 0)
+            advance = 1
+            while matching is not None and target < len(self):
+                matching = self.find_matching(target)
+                if matching is not None:
+                    advance += (matching - target) + 1
+                    target = matching + 1
+            if target < len(self):
+                self.move_locator(locator, advance)
+        elif delta == -1:
+            matching = True
+            target = self.pos_left(locator, 0) - 1
+            advance = -1
+            while matching is not None and target >= 0:
+                matching = self.find_matching(target)
+                if matching is not None:
+                    advance -= (target - matching) + 1
+                    target = matching - 1
+            if target >= 0:
+                self.move_locator(locator, advance)
+        else:
+            raise NotImplementedError
+
+    def read(self, left, right):
+        """Retrieve the substring between the two given locators.
+
+        >>> a = MutableString("This is (a)my string(b) you know.")
+        >>> print a.read("(a)", "(b)")
+        my string
+
+        """
+        a = self.pos_right(left, 0)
+        b = self.pos_left(right, 0)
+        return self.string[a:b]
+
+    def update(self, left, right, string):
+        """Change the substring between the two given locators.
+
+        >>> a = MutableString("This is (a)my string(b) you know.")
+        >>> a.update("(a)", "(b)", "crazy talk")
+        >>> print str(a)
+        This is (a)crazy talk(b) you know.
+
+        """
+        a = self.pos_right(left, 0)
+        b = self.pos_left(right, 0)
+        self.set(self.string[:a] + unicode(string) + self.string[b:])
+
+    def find_matching(self, pos):
+        """Find the parenthesis which matches the parenthesis at the given
+        position.
+
+        Returns the position of the matching parenthesis, or None if no
+        matching parenthesis was found, or if the character at the given
+        position isn't a parenthesis.
+
+        >>> a = MutableString("This (is (my[))] string.")
+        >>> a.find_matching(5)
+        14
+        >>> a.find_matching(9)
+        13
+        >>> a.find_matching(12) is None
+        True
+        >>> a.find_matching(14)
+        5
+        >>> a.find_matching(13)
+        9
+        >>> a.find_matching(15) is None
+        True
+
+        >>> a = MutableString("a(")
+        >>> a.find_matching(0) is None
+        True
+        >>> a.find_matching(1) is None
+        True
+
+        """
+        opener = self.string[pos]
+        if opener == u'(':
+            closer = u')'
+            dir = +1
+        elif opener == u')':
+            closer = u'('
+            dir = -1
+        else:
+            return None
+        level = 0
+        while pos < len(self.string):
+            if self.string[pos] == opener:
+                level += 1
+            elif self.string[pos] == closer:
+                level -= 1
+                if level == 0:
+                    return pos
+            pos += dir
+        return None
+
+
+class SlottedString(MutableString):
+
+    def __init__(self, initial):
+        super(SlottedString, self).__init__(initial)
+
+    def read_slot(self, slot_name):
+        """
+        
+        >>> a = SlottedString("This is (^a)my slot(a$) you know.")
+        >>> a.update_slot('a', 'good stuff')
+        >>> print str(a)
+        This is (^a)good stuff(a$) you know.
+        >>> a.update_slot('z', 'bad stuff')
+        Traceback (most recent call last):
+        ...
+        UndefinedLocatorError: (^z)
+
+        """
+        slot_name = unicode(slot_name)
+        return self.read(u"(^%s)" % slot_name, u"(%s$)" % slot_name)
+
+    def read_slot_indirect(self, slot_name):
+        """
+        >>> p = SlottedString("...(^A)M(A$)...(^R)A(R$)...")
+        >>> print p.read_slot_indirect('R')
+        M
+        >>> print p.read_slot_indirect('A')
+        Traceback (most recent call last):
+        ...
+        UndefinedLocatorError: (^M)
+
+        """
+        slot_name = unicode(slot_name)
+        slot_name = self.read_slot(slot_name)
+        return self.read_slot(slot_name)
+
+    def update_slot(self, slot_name, string):
+        """
+
+        >>> a = SlottedString("This is (^a)my slot(a$) you know.")
+        >>> a.update_slot('a', 'good stuff')
+        >>> print str(a)
+        This is (^a)good stuff(a$) you know.
+        >>> a.update_slot('a', MutableString('mutable stuff'))
+        >>> print str(a)
+        This is (^a)mutable stuff(a$) you know.
+        >>> a.update_slot('z', 'bad stuff')
+        Traceback (most recent call last):
+        ...
+        UndefinedLocatorError: (^z)
+
+        """
+        slot_name = unicode(slot_name)
+        string = unicode(string)
+        return self.update(u"(^%s)" % slot_name, u"(%s$)" % slot_name, string)
+
+    def update_slot_indirect(self, slot_name, string):
+        """
+        >>> p = SlottedString("Dolphin(^A)M(A$)Dolphin(^R)A(R$)Dolphin")
+        >>> p.update_slot_indirect('R', 'Porphyry')
+        >>> print str(p)
+        Dolphin(^A)Porphyry(A$)Dolphin(^R)A(R$)Dolphin
+
+        """
+        slot_name = self.read_slot(slot_name)
+        self.update_slot(slot_name, string)
+
+    def get_slot_name(self, slot_name):
+        """
+
+        >>> a = SlottedString("(^G)?(G$) (^P)_(P$) (^`P)Q(`P$) (^`K)(^/)Madge(/$)(`K$)")
+        >>> print a.get_slot_name('M')
+        M
+        >>> print a.get_slot_name('G')
+        G
+        >>> print a.get_slot_name('P')
+        Q
+        >>> print a.get_slot_name('K')
+        Madge
+
+        """
+        slot_name = unicode(slot_name)
+        name_slot = u"`%s" % slot_name
+        try:
+            slot_name = self.read_slot(name_slot)
+        except (UndefinedLocatorError):
+            pass
+        slot_name = self.strip_all_locators(slot_name)
+        return slot_name
+
+    def strip_all_locators(self, content):
+        """
+        >>> p = Program('')
+        >>> print p.strip_all_locators('')
+        None
+        >>> print p.strip_all_locators('X')
+        X
+        >>> print p.strip_all_locators('Well-tempered')
+        Well-tempered
+        >>> print p.strip_all_locators('(^8)(^7)(7$)CAT(8$)')
+        CAT
+        >>> print p.strip_all_locators('(^8(beat))D')
+        D
+        >>> print p.strip_all_locators('(^8)(^7)(7$)(8$)')
+        None
+
+        """
+        if len(content) == 0:
+            return None
+        else:
+            pos = 0
+            level = 0
+            acc = ''
+            while pos < len(content):
+                if content[pos] == '(':
+                    level += 1
+                elif content[pos] == ')':
+                    level -= 1
+                elif level == 0:
+                    acc += content[pos]
+                pos += 1
+            return acc or None
+
+    def slide_slot(self, slot_name, delta):
+        """
+
+        >>> a = SlottedString("This is my (^a)slot(a$) (^b)y(b$)ou know.")
+        >>> a.slide_slot('a', +1)
+        >>> print str(a)
+        This is my s(^a)lot (a$)(^b)y(b$)ou know.
+        >>> a.slide_slot('b', -1)
+        >>> print str(a)
+        This is my s(^a)lot(^b) (a$)(b$)you know.
+
+        """
+        slot_name = unicode(slot_name)
+        if delta > 0:
+            self.slide_locator("(%s$)" % slot_name, delta)
+            self.slide_locator("(^%s)" % slot_name, delta)
+        else:
+            self.slide_locator("(^%s)" % slot_name, delta)
+            self.slide_locator("(%s$)" % slot_name, delta)
+
+
+class Program(SlottedString):
+
+    def __init__(self, initial):
+        super(Program, self).__init__(initial)
+        self.input = sys.stdin
+        self.output = sys.stdout
+
+    def load(self, filename):
+        """Load the program source from a Tranzy file."""
+        file = open(filename, 'r')
+        done = False
+        string = ''
+        for line in file.readlines():
+            line = unicode(line, 'utf-8')  # for now
+            if line.endswith('\n'):
+                line = line[:-1]
+            if line.startswith('#'):
+                pass
+            else:
+                string += line
+        self.set(string)
+        file.close()
+
+    def advance(self):
+        """Slide the instruction slot rightward.
+
+        >>> p = Program("(^!)A(!$)B(^M)C(M$)D")
+        >>> p.advance()
+        >>> print str(p)
+        A(^!)B(!$)(^M)C(M$)D
+        >>> p.advance()
+        >>> print str(p)
+        AB(^!)(^M)C(!$)(M$)D
+        >>> p.advance()
+        >>> print str(p)
+        AB(^M)C(^!)(M$)D(!$)
+        >>> p.advance()
+        >>> print str(p)
+        AB(^M)C(M$)D(^!)(!$)
+
+        >>> p = Program("(^!)A(!$)(^Moo)(^Gar)(Gar$)B(Moo$)")
+        >>> p.advance()
+        >>> print str(p)
+        A(^!)(^Moo)(^Gar)(Gar$)B(!$)(Moo$)
+        >>> p.advance()
+        >>> print str(p)
+        A(^Moo)(^Gar)(Gar$)B(^!)(!$)(Moo$)
+
+        """
+        self.slide_slot(self.get_slot_name('!'), +1)
+
+    def clean_instruction(self, instruction):
+        """
+        >>> p = Program('')
+        >>> print p.clean_instruction('')
+        None
+        >>> print p.clean_instruction('X')
+        X
+        >>> print p.clean_instruction('Well-tempered')
+        W
+        >>> print p.clean_instruction('(^8)(^7)(7$)CAT(8$)')
+        C
+        >>> print p.clean_instruction('(^8(beat))D')
+        D
+        >>> print p.clean_instruction('(^8)(^7)(7$)(8$)')
+        None
+
+        """
+        if len(instruction) == 0:
+            return None
+        else:
+            pos = 0
+            level = 0
+            while instruction[pos] == '(':
+                while True:
+                    if instruction[pos] == '(':
+                        level += 1
+                    elif instruction[pos] == ')':
+                        level -= 1
+                    pos += 1
+                    if level == 0 or pos >= len(instruction):
+                        break
+                if pos >= len(instruction):
+                    return None
+            return instruction[pos]
+    
+    def execute(self, instruction):
+        raise NotImplementedError
+
+    def step(self):
+        """Execute one step of this Pophery program."""
+        instruction = self.read_slot(self.get_slot_name('!'))
+        instruction = self.clean_instruction(instruction)
+        if instruction is None:
+            return False
+        else:
+            self.execute(instruction)
+            self.advance()
+            return True
+
+    def run(self):
+        """Execute this Pophery program and return only when it terminates."""
+        keep_going = self.step()
+        while keep_going:
+            keep_going = self.step()
+
+
+class Semantics(Program):
+    def deselect(self):
+        locator_name = self.get_slot_name('/')
+        try:
+            self.remove_locator('(^%s)' % locator_name)
+        except UndefinedLocatorError:
+            pass
+        try:
+            self.remove_locator('(%s$)' % locator_name)
+        except UndefinedLocatorError:
+            pass
+
+    def execute(self, instruction):
+        """Apply the semantics of the given instruction to this Program.
+
+        * 0 through 9 update the accumulator to the literal strings 0 through
+          9, respectively.
+
+        >>> p = Semantics("(^?)(?$)")
+        >>> p.execute('0')
+        >>> print str(p)
+        (^?)0(?$)
+
+        * X ("cut") erases (updates with the zero-length string) the selection.
+
+        >>> p = Semantics("(^/)hi(/$)")
+        >>> p.execute('X')
+        >>> print str(p)
+        (^/)(/$)
+        >>> p = Semantics("(^`/)X(`/$)(^X)hi(X$)")
+        >>> p.execute('X')
+        >>> print str(p)
+        (^`/)X(`/$)(^X)(X$)
+
+        * C ("copy") updates the contents of the clipboard with the contents
+          of the selection.
+
+        >>> p = Semantics("(^/)hi(/$)(^%)lo(%$)")
+        >>> p.execute('C')
+        >>> print str(p)
+        (^/)hi(/$)(^%)hi(%$)
+        >>> p = Semantics("(^/)hi(/$)(^J)lo(J$)(^`%)J(`%$)")
+        >>> p.execute('C')
+        >>> print str(p)
+        (^/)hi(/$)(^J)hi(J$)(^`%)J(`%$)
+
+        * V ("paste") updates the contents of the selection with the contents
+          of the clipboard.
+
+        >>> p = Semantics("(^/)hi(/$)(^%)lo(%$)")
+        >>> p.execute('V')
+        >>> print str(p)
+        (^/)lo(/$)(^%)lo(%$)
+        >>> p = Semantics("(^C)lo(C$)(^J)hi(J$)(^`/)J(`/$)(^`%)C(`%$)")
+        >>> p.execute('V')
+        >>> print str(p)
+        (^C)lo(C$)(^J)lo(J$)(^`/)J(`/$)(^`%)C(`%$)
+
+        * S ("select") selects the contents of the slot indirect by the
+          accumulator.
+
+        >>> p = Semantics("(^/)foo(/$)(^?)A(?$)(^A)Some text.(A$)")
+        >>> p.execute('S')
+        >>> print str(p)
+        foo(^?)A(?$)(^A)(^/)Some text.(/$)(A$)
+        >>> p = Semantics("(^`/)k(`/$)(^k)foo(k$)(^?)A(?$)(^A)Some text.(A$)")
+        >>> p.execute('S')
+        >>> print str(p)
+        (^`/)k(`/$)foo(^?)A(?$)(^A)(^k)Some text.(k$)(A$)
+
+        * A ("select all") selects the contents of the accumulator.
+
+        >>> p = Semantics("(^/)foo(/$)(^?)A(?$)(^A)Some text.(A$)")
+        >>> p.execute('A')
+        >>> print str(p)
+        foo(^?)(^/)A(/$)(?$)(^A)Some text.(A$)
+        >>> p = Semantics("(^`/)r(`/$)(^r)foo(r$)(^?)A(?$)(^A)Some text.(A$)")
+        >>> p.execute('A')
+        >>> print str(p)
+        (^`/)r(`/$)foo(^?)(^r)A(r$)(?$)(^A)Some text.(A$)
+
+        * L ("left") slides the left locator of the selection leftward.
+
+        >>> p = Semantics("foo(^/)bar(/$)")
+        >>> p.execute('L')
+        >>> print str(p)
+        fo(^/)obar(/$)
+        >>> p = Semantics("(^/)foobar(/$)")
+        >>> p.execute('L')
+        >>> print str(p)
+        (^/)foobar(/$)
+        >>> p = Semantics("foo(^C)bar(C$)(^`/)C(`/$)")
+        >>> p.execute('L')
+        >>> print str(p)
+        fo(^C)obar(C$)(^`/)C(`/$)
+        >>> p = Semantics("The last time I saw Charlie")
+        >>> p.execute('L')
+        Traceback (most recent call last):
+        ...
+        UndefinedLocatorError: (^/)
+
+        * R ("right") slides the left locator of the selection rightward.
+
+        >>> p = Semantics("foo(^/)bar(/$)")
+        >>> p.execute('R')
+        >>> print str(p)
+        foob(^/)ar(/$)
+        >>> p = Semantics("foo(^/)(/$)bar")
+        >>> p.execute('R')
+        >>> print str(p)
+        foo(^/)(/$)bar
+        >>> p = Semantics("foo(^C)bar(C$)(^`/)C(`/$)")
+        >>> p.execute('R')
+        >>> print str(p)
+        foob(^C)ar(C$)(^`/)C(`/$)
+        >>> p = Semantics("The last time I saw Charlie")
+        >>> p.execute('R')
+        Traceback (most recent call last):
+        ...
+        UndefinedLocatorError: (^/)
+
+        * E ("end") moves the left locator of the selection to immediately
+          to the left of the right locator of the selection, resulting in
+          the selection containing the zero-length string.
+
+        >>> p = Semantics("foo(^/)bar(/$)baz")
+        >>> p.execute('E')
+        >>> print str(p)
+        foobar(^/)(/$)baz
+        >>> p = Semantics("foo(^a)b(^`/)a(`/$)r(a$)baz")
+        >>> p.execute('E')
+        >>> print str(p)
+        foob(^`/)a(`/$)r(^a)(a$)baz
+        >>> p = Semantics("The last time I saw Charlie")
+        >>> p.execute('E')
+        Traceback (most recent call last):
+        ...
+        UndefinedLocatorError: (^/)
+
+        * F ("find") searches everywhere in the contents of the accumulator
+          for the contents of the clipboard. If found, that substring is
+          selected.
+
+        >>> p = Semantics("(^?)By hook or by crook, we will.(?$)(^%)ook(%$)")
+        >>> p.execute('F')
+        >>> print str(p)
+        (^?)By h(^/)ook(/$) or by crook, we will.(?$)(^%)ook(%$)
+
+        * D ("drag-and-drop") moves the selection to the accumulator.
+
+        >>> p = Semantics("(^/)hi(/$)(^?)lo(?$)")
+        >>> p.execute('D')
+        >>> print str(p)
+        hi(^?)(^/)hi(/$)(?$)
+        >>> p = Semantics("(^C)lo(C$)(^J)hi(J$)(^`/)J(`/$)(^`?)C(`?$)")
+        >>> p.execute('D')
+        >>> print str(p)
+        (^C)(^J)hi(J$)(C$)hi(^`/)J(`/$)(^`?)C(`?$)
+
+        * I ("input") waits for a line to appear on standard input, then
+          places it (sans newline) in the accumulator.
+
+        >>> from StringIO import StringIO
+        >>> p = Semantics("(^?)(?$)")
+        >>> p.input = StringIO(chr(10).join(["Line.", "Line!", "LINE!"]))
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)Line.(?$)
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)Line!(?$)
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)LINE!(?$)
+        >>> p.execute('I')
+        >>> print str(p)
+        (^?)(?$)
+
+        * O ("output") outputs the string in the accumulator to standard
+          output, followed by a newline.
+
+        >>> p = Semantics("(^?)Hello, world!(?$)")
+        >>> p.execute('O')
+        Hello, world!
+        >>> print str(p)
+        (^?)Hello, world!(?$)
+
+        Now we demonstrate some idioms.
+
+        Assume the inital program defines some slots to contain initial
+        data.  That data can then be loaded into the accumulator:
+
+        >>> p = Semantics("(^0)data(0$)(^%)(%$)(^?)(?$)(^!)0(!$)SCAV")
+        >>> p.run()
+        >>> print str(p)
+        (^0)data(0$)(^%)data(%$)(^?)(^/)data(/$)(?$)0SCAV(^!)(!$)
+
+        New data, say the literal string 1, can be stored into slot 0 with:
+
+        >>> p = Semantics("(^0)data(0$)(^%)(%$)(^?)(?$)(^!)1(!$)AC0SV")
+        >>> p.run()
+        >>> print str(p)
+        (^0)(^/)1(/$)(0$)(^%)1(%$)(^?)0(?$)1AC0SV(^!)(!$)
+
+        To copy from any arbitrary slot (say 0) to another (say 1), we can say:
+
+        >>> p = Semantics("(^0)hi(0$)(^1)(1$)(^%)(%$)(^?)(?$)(^!)0(!$)SC1SV")
+        >>> p.run()
+        >>> print str(p)
+        (^0)hi(0$)(^1)(^/)hi(/$)(1$)(^%)hi(%$)(^?)1(?$)0SC1SV(^!)(!$)
+
+        Accessing a slot with a longer name, such as (^123)xyz(123$), can be
+        done with the help of a free slot like 0:
+
+        >>> p = Semantics("(^0)(0$)(^123)xyz(123$)(^%)(%$)(^?)(?$)(^!)1(!$)AC0SV2AC0SEV3AC0SEV0SCAVSD")
+        >>> p.run()
+        >>> print str(p)
+        (^0)123(0$)(^123)xyz(123$)(^%)123(%$)(^?)(^/)xyz(/$)(?$)1AC0SV2AC0SEV3AC0SEV0SCAVSD(^!)(!$)
+
+        To write data, say (^8)foo(8$), into a slot whose name is stored in
+        another slot, such as (^9)jim(9$), we can say:
+
+        >>> p = Semantics("(^8)foo(8$)(^9)jim(9$)(^jim)(jim$)(^%)(%$)(^?)(?$)(^!)8(!$)SC9SDSV")
+        >>> p.run()
+        >>> print str(p)
+        (^8)foo(8$)(^9)jim(9$)(^jim)(^/)foo(/$)(jim$)(^%)foo(%$)(^?)jim(?$)8SC9SDSV(^!)(!$)
+
+        Finally, a complete, if simple, program:
+
+        >>> p = Semantics("(^?)Hello, world!(?$)(^!)O(!$)")
+        >>> p.run()
+        Hello, world!
+
+        """
+        if instruction >= '0' and instruction <= '9':
+            self.update_slot(self.get_slot_name('?'), instruction)
+        elif instruction == 'X':
+            self.update_slot(self.get_slot_name('/'), '')
+        elif instruction == 'C':
+            self.update_slot(self.get_slot_name('%'), self.read_slot(self.get_slot_name('/')))
+        elif instruction == 'V':
+            self.update_slot(self.get_slot_name('/'), self.read_slot(self.get_slot_name('%')))
+        elif instruction == 'S':
+            self.deselect()
+            locator_name = self.get_slot_name('/')
+            new_selection = '(^%s)%s(%s$)' % (
+                locator_name,
+                self.read_slot_indirect(self.get_slot_name('?')),
+                locator_name
+            )
+            self.update_slot_indirect(self.get_slot_name('?'), new_selection)
+        elif instruction == 'A':
+            self.deselect()
+            locator_name = self.get_slot_name('/')
+            new_selection = '(^%s)%s(%s$)' % (
+                locator_name,
+                self.read_slot(self.get_slot_name('?')),
+                locator_name
+            )
+            self.update_slot(self.get_slot_name('?'), new_selection)
+        elif instruction == 'L':
+            locator_name = self.get_slot_name('/')
+            self.slide_locator('(^%s)' % locator_name, -1)
+        elif instruction == 'R':
+            locator_name = self.get_slot_name('/')
+            if self.read_slot(locator_name) != '':
+                self.slide_locator('(^%s)' % locator_name, +1)
+        elif instruction == 'E':
+            locator_name = self.get_slot_name('/')
+            self.remove_locator('(^%s)' % locator_name)
+            pos = self.pos_left('(%s$)' % locator_name, 0)
+            self.insert_locator('(^%s)' % locator_name, pos)
+        elif instruction == 'F':
+            accumulator = self.read_slot(self.get_slot_name('?'))
+            clipboard = self.read_slot(self.get_slot_name('%'))
+            pos = accumulator.find(clipboard)
+            if pos >= 0:
+                self.deselect()
+                locator_name = self.get_slot_name('/')
+                accumulator = MutableString(accumulator)
+                accumulator.insert_locator('(^%s)' % locator_name, pos)
+                pos_right = accumulator.pos_right('(^%s)' % locator_name, 0)
+                accumulator.insert_locator('(%s$)' % locator_name,
+                    pos_right + len(clipboard))
+                self.update_slot(self.get_slot_name('?'), accumulator)
+            else:
+                pass
+        elif instruction == 'D':
+            locator_name = self.get_slot_name('/')
+            selection = self.read_slot(locator_name)
+            self.deselect()
+            new_selection = '(^%s)%s(%s$)' % (
+                locator_name,
+                selection,
+                locator_name
+            )
+            self.update_slot(self.get_slot_name('?'), new_selection)
+        elif instruction == 'O':
+            line = self.read_slot('?') + "\n"
+            try:
+                self.output.write(line.encode('UTF-8'))
+            except UnicodeEncodeError:
+                self.output.write(line.encode('ascii', 'xmlcharrefreplace'))
+        elif instruction == 'I':
+            text = self.input.readline()
+            if text.endswith('\n'):
+                text = text[:-1]
+            self.update_slot(self.get_slot_name('?'), text)
+        else:
+            pass
+
+    def step(self):
+        """Execute one step of this Pophery program.
+
+        """
+        return super(Semantics, self).step()
+
+    def run(self):
+        """Execute this Pophery program and return only when it terminates.
+
+        """
+        return super(Semantics, self).run()
+
+
+class TracedProgram(Semantics):
+    """
+
+    >>> p = TracedProgram("(^?)Hello, world!(?$)(^!)O(!$)OO")
+    >>> p.run()
+    [(^?)Hello, world!(?$)(^!)O(!$)OO]
+    Hello, world!
+    [(^?)Hello, world!(?$)O(^!)O(!$)O]
+    Hello, world!
+    [(^?)Hello, world!(?$)OO(^!)O(!$)]
+    Hello, world!
+    [(^?)Hello, world!(?$)OOO(^!)(!$)]
+
+    """
+
+    def __init__(self, initial):
+        super(TracedProgram, self).__init__(initial)
+
+    def run(self):
+        print "[%s]" % str(self)
+        super(TracedProgram, self).run()
+
+    def step(self):
+        result = super(TracedProgram, self).step()
+        if result:
+            print "[%s]" % str(self)
+        return result
+
+
+
+def main(argv):
+    optparser = OptionParser("[python] %prog {options} {source.tranzy}\n" + __doc__)
+    optparser.add_option("-e", "--evaluate",
+                         action="store", type="string", dest="program", default=None,
+                         help="evaluate Pophery program on command line")
+    optparser.add_option("-l", "--show-license",
+                         action="store_true", dest="show_license", default=False,
+                         help="show product license and exit")
+    optparser.add_option("-t", "--trace",
+                         action="store_true", dest="trace", default=False,
+                         help="trace execution during run")
+    optparser.add_option("-T", "--run-tests",
+                         action="store_true", dest="run_tests", default=False,
+                         help="run self-tests and exit")
+    (options, args) = optparser.parse_args(argv[1:])
+    exit_code = None
+    if options.show_license:
+        print sys.argv[0]
+        print __doc__
+        print LICENSE
+        exit_code = 0
+    if options.run_tests:
+        import doctest
+        (fails, something) = doctest.testmod()
+        if fails == 0:
+            print "All tests passed."
+            exit_code = 0
+        else:
+            exit_code = 1
+
+    if exit_code is not None:
+        sys.exit(exit_code)
+
+    klass = Semantics
+    if options.trace:
+        klass = TracedProgram
+
+    if options.program is not None:
+        klass(options.program).run()
+
+    for filename in args:
+        program = klass('')
+        program.load(filename)
+        program.run()
+
+
+if __name__ == "__main__":
+    main(sys.argv)