0001from pylogo.common import *
0002
0003class UCBArray(list):
0004    def __init__(v, origin=1):
0005        list.__init__(v)
0006        self.origin = origin
0007    def __getitem__(self, i):
0008        return list.__getitem__(self, i-self.origin)
0009    def __setitem__(self, i, value):
0010        list.__setitem__(self, i-self.origin, value)
0011
0012def array(size, origin=1):
0013    """
0014    ARRAY size
0015    (ARRAY size origin)
0016    
0017    outputs an array of ``size`` members (must be a positive integer),
0018    each of which initially is an empty list.  Array members can be
0019    selected with ITEM and changed with SETITEM.  The first member of
0020    the array is member number 1 unless an ``origin`` input (must be
0021    an integer) is given, in which case the first member of the array
0022    has that number as its index.  (Typically 0 is used as the origin
0023    if anything.)
0024    """
0025    # UCB: Arrays are printed by PRINT and friends, and can be typed
0026    # in, inside curly braces; indicate an origin with {a b c}@0.
0027    return UCBArray([[]]*size)
0028
0029def mdarray(sizelist, origin=1):
0030    """
0031    MDARRAY sizelist
0032    (MDARRAY sizelist origin)
0033
0034    outputs a multi-dimensional array.  The first input must be a list
0035    of one or more positive integers.  The second input, if present,
0036    must be a single integer that applies to every dimension of the array.
0037    Ex: (MDARRAY [3 5] 0) outputs a two-dimensional array whose members
0038    range from [0 0] to [2 4].
0039    """
0040    if len(sizelist) == 1:
0041        return array(sizelist[0], origin=origin)
0042    else:
0043        result = []
0044        for i in range(sizelist[0]):
0045            result.append(array(sizelist[1:], origin=origin))
0046        return UCBArray(result, origin=origin)
0047
0048def listtoarray(lst, origin=1):
0049    """
0050    LISTTOARRAY list
0051    (LISTTOARRAY list origin)
0052
0053    outputs an array of the same size as the input list, whose members
0054    are the members of the input list.
0055    """
0056    return UCBArray(lst, origin=origin)
0057
0058def arraytolist(array):
0059    """
0060    ARRAYTOLIST array
0061
0062    outputs a list whose members are the members of the input array.
0063    The first member of the output is the first member of the array,
0064    regardless of the array's origin.
0065    """
0066    return list(array)
0067
0068def mditem(indexlst, array):
0069    """
0070    MDITEM indexlist array
0071
0072    outputs the member of the multidimensional ``array`` selected by
0073    the list of numbers ``indexlist``.
0074    """
0075    while indexlst:
0076        array = array[indexlst[0]]
0077        indexlst = indexlst[1:]
0078    return array
0079
0080def quoted(v):
0081    """
0082    QUOTED thing
0083
0084    outputs its input, if a list; outputs its input with a quotation
0085    mark prepended, if a word.
0086    """
0087    ## @@: I'm not sure this is really meaningful in the same way as
0088    ## it would be in UCBLogo
0089    if isinstance(v, str):
0090        return '"%s' % v
0091    else:
0092        return v
0093
0094def mdsetitem(indexlst, array, value):
0095    """
0096    MDSETITEM indexlist array value
0097
0098    command.  Replaces the member of ``array`` chosen by ``indexlist``
0099    with the new ``value``.
0100    """
0101    if len(indexlst) == 1:
0102        array[indexlst[0]] = value
0103    else:
0104        mdsetitem(indexlst[1:], array[indexlst[0]], value)
0105
0106
0107@logofunc(name='.setitem')
0108def dotsetitem(index, array, value):
0109    """
0110    .SETITEM index array value
0111    
0112    command.  Changes the ``index``th member of ``array`` to be
0113    ``value``, like SETITEM, but without checking for circularity.
0114    WARNING: Primitives whose names start with a period are DANGEROUS.
0115    Their use by non-experts is not recommended.  The use of .SETITEM
0116    can lead to circular arrays, which will get some Logo primitives
0117    into infinite loops; and the loss of memory if a circular
0118    structure is released.
0119    """
0120    ## @@: None of this applies to PyLogo, which doesn't do those
0121    ## checks anyway, and can handle circular structures (though those
0122    ## structures can cause infinite loops, but that's not an uncommon
0123    ## bug anyway)
0124    setitem(index, array, value)
0125
0126@logofunc(aware=True)
0127def push(interp, stackname, thing):
0128    """
0129    PUSH stackname thing
0130
0131    command.  Adds the ``thing`` to the stack that is the value of the
0132    variable whose name is ``stackname``.  This variable must have a
0133    list as its value; the initial value should be the empty list.
0134    New members are added at the front of the list.
0135    """
0136    var = interp.get_variable(stackname)
0137    var.append(thing)
0138
0139@logofunc(aware=True)
0140def pop(interp, stackname):
0141    """
0142    POP stackname
0143
0144    outputs the most recently PUSHed member of the stack that is the
0145    value of the variable whose name is ``stackname`` and removes that
0146    member from the stack.
0147    """
0148    var = interp.get_variable(stackname)
0149    return var.pop()
0150
0151@logofunc(aware=True)
0152def queue(interp, queuename, thing):
0153    """
0154    QUEUE queuename thing
0155
0156    command.  Adds the ``thing`` to the queue that is the value of the
0157    variable whose name is ``queuename``.  This variable must have a
0158    list as its value; the initial value should be the empty list.
0159    New members are added at the back of the list.
0160    """
0161    var = interp.get_variable(queuename)
0162    var.append(thing)
0163
0164@logofunc(aware=True)
0165def dequeue(interp, queuename):
0166    """
0167    DEQUEUE queuename
0168
0169    outputs the least recently QUEUEd member of the queue that is the
0170    value of the variable whose name is ``queuename`` and removes that
0171    member from the queue.
0172    """
0173    var = interp.get_variable(queuename)
0174    return var.pop(0)
0175
0176@logofunc(aliases=['array?'])
0177def arrayp(v):
0178    """
0179    ARRAYP thing
0180    ARRAY? thing
0181    
0182    outputs TRUE if the input is an array, FALSE otherwise.
0183    """
0184    return isinstance(v, UCBArray)
0185
0186@logofunc(aliases=['backslashed?'])
0187def backslashedp(c):
0188    """
0189    BACKSLASHEDP char
0190    BACKSLASHED? char
0191    """
0192    ## outputs TRUE if the input character was originally entered into
0193    ## Logo with a backslash (\) before it or within vertical bars (|)
0194    ## to prevent its usual special syntactic meaning, FALSE
0195    ## otherwise.  (Outputs TRUE only if the character is a
0196    ## backslashed space, tab, newline, or one of ()[]+-*/=<>\":;\\~?|
0197    ## )
0198    ## @@: doesn't make sense for us.
0199    return False
0200
0201def rawascii(c):
0202    """
0203    RAWASCII char
0204    
0205    outputs the integer (between 0 and 255) that represents the input
0206    character in the ASCII code.
0207    """
0208    ## @@: Interprets control characters as representing themselves.
0209    ## To find out the ASCII code of an arbitrary keystroke, use
0210    ## RAWASCII RC.
0211    return ord(c)
0212
0213_prefix = ''
0214def setprefix(s):
0215    """
0216    SETPREFIX string
0217
0218    command.  Sets a prefix that will be used as the implicit beginning
0219    of filenames in OPENREAD, OPENWRITE, OPENAPPEND, OPENUPDATE, LOAD,
0220    and SAVE commands.  Logo will put the appropriate separator
0221    character (slash for Unix, backslash for DOS/Windows, colon for
0222    MacOS) between the prefix and the filename entered by the user.
0223    The input to SETPREFIX must be a word, unless it is the empty list,
0224    to indicate that there should be no prefix.
0225    """
0226    global _prefix
0227    if s:
0228        _prefix = s
0229    else:
0230        _prefix = ''
0231
0232def prefix():
0233    """
0234    PREFIX
0235
0236    outputs the current file prefix, or [] if there is no prefix.
0237    See SETPREFIX.
0238    """
0239    return _prefix or LogoList([])
0240
0241_files = {}
0242
0243def openread(filename):
0244    """
0245    OPENREAD filename
0246
0247    command.  Opens the named file for reading.  The read position is
0248    initially at the beginning of the file.
0249    """
0250    _files[filename] = open(_prefix + filename)
0251
0252def openwrite(filename):
0253    """
0254    OPENWRITE filename
0255
0256    command.  Opens the named file for writing.  If the file already
0257    existed, the old version is deleted and a new, empty file created.
0258    """
0259    _files[filename] = open(_prefix + filename, 'w')
0260
0261def openappend(filename):
0262    """
0263    OPENAPPEND filename
0264
0265    command.  Opens the named file for writing.  If the file already
0266    exists, the write position is initially set to the end of the old
0267    file, so that newly written data will be appended to it.
0268    """
0269    _files[filename] = open(_prefix + filename, 'a')
0270
0271def openupdate(filename):
0272    """
0273    OPENUPDATE filename
0274
0275    command.  Opens the named file for reading and writing.  The read and
0276    write position is initially set to the end of the old file, if any.
0277    Note: each open file has only one position, for both reading and
0278    writing.  If a file opened for update is both READER and WRITER at
0279    the same time, then SETREADPOS will also affect WRITEPOS and vice
0280    versa.  Also, if you alternate reading and writing the same file,
0281    you must SETREADPOS between a write and a read, and SETWRITEPOS
0282    between a read and a write.
0283    """
0284    _files[filename] = open(_prefix + filename, 'a+')
0285
0286def close(filename):
0287    """
0288    CLOSE filename
0289    
0290    command.  Closes the named file.
0291    """
0292    _files[filename].close()
0293
0294def allopen():
0295    """
0296    ALLOPEN
0297
0298    outputs a list whose members are the names of all files currently open.
0299    This list does not include the dribble file, if any.
0300    """
0301    return LogoList(_files.keys())
0302
0303def closeall():
0304    """
0305    CLOSEALL
0306
0307    command.  Closes all open files.  Abbreviates
0308    FOREACH ALLOPEN [CLOSE ?]
0309    """
0310    for key, value in _files.items():
0311        del _files[key]
0312        value.close()
0313
0314@logofunc(aliases=['erf'])
0315def erasefile(filename):
0316    """
0317    ERASEFILE filename
0318    ERF filename
0319    
0320    command.  Erases (deletes, removes) the named file, which should not
0321    currently be open.
0322    """
0323    os.unlink(_prefix + filename)
0324
0325_in_dribble = False
0326
0327class Dribbler:
0328
0329    def __init__(self, capture, fileobj):
0330        self.capture = capture
0331        self.file = fileobj
0332        for n in ['read', 'readline', 'readlines']:
0333            setattr(self, n, self.cap(n))
0334        for n in ['write']:
0335            setattr(self, n, self.copy(n))
0336
0337    def cap(self, attr):
0338        def func(*args):
0339            v = getattr(self.capture, attr)(*args)
0340            self.file.write(v)
0341            return v
0342        return func
0343
0344    def copy(self, attr):
0345        def func(*args):
0346            self.write.write(''.join(args))
0347            getattr(self.capture, attr)(*args)
0348        return func
0349
0350    def close(self):
0351        self.capture.close()
0352        self.file.close()
0353
0354    def nodribble(self):
0355        self.file.close()
0356        return self.capture
0357
0358def dribble(filename):
0359    """
0360    DRIBBLE filename
0361
0362    command.  Creates a new file whose name is the input, like OPENWRITE,
0363    and begins recording in that file everything that is read from the
0364    keyboard or written to the terminal.  That is, this writing is in
0365    addition to the writing to WRITER.  The intent is to create a
0366    transcript of a Logo session, including things like prompt
0367    characters and interactions.
0368    """
0369    out = open(filename, 'w')
0370    sys.stdout = Dribbler(sys.stdout, out)
0371    sys.stdin = Dribbler(sys.stdin)
0372    # @@... unfinished
0373
0374_plists = {}
0375
0376def _getplist(plistname):
0377    if not _plists.has_key(plistname.lower()):
0378        _plists[plistname.lower()] = {}
0379    return _plists[plistname.lower()]
0380
0381def pprop(plistname, propname, value):
0382    """
0383    PPROP plistname propname value
0384
0385    command.  Adds a property to the ``plistname`` property list with
0386    name ``propname`` and value ``value``.
0387    """
0388    _getplist(plistname)[propname.lower()] = value
0389
0390def gprop(plistname, propname):
0391    """
0392    GPROP plistname propname
0393
0394    outputs the value of the ``propname`` property in the
0395    ``plistname`` property list, or the empty list if there is no such
0396    property.
0397    """
0398    try:
0399        return _getplist(plistname)[propname.lower()]
0400    except KeyError:
0401        return LogoList([])
0402
0403def remprop(plistname, propname):
0404    """
0405    REMPROP plistname propname
0406
0407    command.  Removes the property named ``propname`` from the
0408    property list named ``plistname``.
0409    """
0410    try:
0411        del _getplist(plistname)[propname.lower()]
0412    except KeyError:
0413        pass
0414
0415def plist(plistname):
0416    """
0417    PLIST plistname
0418
0419    outputs a list whose odd-numbered members are the names, and
0420    whose even-numbered members are the values, of the properties
0421    in the property list named ``plistname``.  The output is a copy
0422    of the actual property list; changing properties later will not
0423    magically change a list output earlier by PLIST.
0424    """
0425    v = []
0426    for key, value in _getplist(plistname):
0427        v.extend([key, value])
0428    return LogoList(v)
0429
0430@logofunc(aliases=['plist?'])
0431def plistp(plistname):
0432    """
0433    PLISTP name
0434    PLIST? name
0435
0436    outputs TRUE if the input is the name of a *nonempty* property
0437    list.  (In principle every word is the name of a property list; if
0438    you haven't put any properties in it, PLIST of that name outputs
0439    an empty list, rather than giving an error message.)
0440    """
0441    if not _plists.has_key(plistname.lower()):
0442        return False
0443    return not not _getplist(plistname)
0444
0445def erpls():
0446    """
0447    ERPLS
0448
0449    command.  Erases all unburied property lists from the workspace.
0450    Abbreviates ERASE PLISTS.
0451    """
0452    global _plists
0453    _plists = {}