logo_primitives.py (plain text)


"""The logo_primitives module contains definitions of Logo primitive procedures.

Note: Some additional primitive procedures are defined in logo.py.
"""

import operator as op
import logo
from buffer import Buffer

try:
    import turtle
except Exception as e:
    print('Cannot import turtle graphics:', e)

def logo_print(x):
    """Implements Logo "print" primitive."""
    logo.logo_type(x)
    print('')

def logo_show(x):
    """Implements Logo "show" primitive."""
    if type(x) == list:
        print('[', end='')
    logo.logo_type(x)
    if type(x) == list:
        print(']', end='')
    print('')

def repeat(n, exp, env):
    """Implements "repeat", which evaluates an exp, n times."""
    for _ in range(int(n)):
        logo.eval_line(Buffer(exp), env)
    return None

def logo_word(x, y):
    """Implements "word", which applies string addition and evaluation."""
    if type(x) == list or type(y) == list:
        raise logo.LogoError('Cannot take a sentence input.')
    return x + y

def logo_sentence(x, y):
    """Implements "sentence", which builds a list (sentence) from two lists,
    converting non-list inputs to lists first."""
    if type(x) != list:
        x = [x]
    if type(y) != list:
        y = [y]
    return x + y

def logo_list(x, y):
    """Implements "list", which constructs a list (sentence) from two
    elements."""
    return [x, y]

def logo_fput(x, y):
    """Implements "fput", which constructs a list (sentence) from an element
    and the rest of the list."""
    if type(y) != list:
        raise logo.LogoError('Second input must be a sentence.')
    return [x] + y

def numeric(f):
    """Return a Logo primitive that has numeric inputs and output."""
    def coerced(*args):
        result = f(*map(to_num, args))
        if result is not None:
            return str(result) 
    return coerced

def logical(f):
    """Return a Logo primitive that has boolean inputs and output."""
    def coerced(*args):
        return str(f(*tuple(map(to_bool, args))))
    return coerced

def to_num(s):
    """Coerce string s to a number."""
    try:
        return int(s)
    except (TypeError, ValueError):
        try:
            return float(s)
        except (TypeError, ValueError):
            raise logo.error(str(s) + ' is not a number')

def to_bool(s):
    """Coerce string s to a bool."""
    if s == 'True':
        return True
    if s == 'False':
        return False
    raise logo.error(str(s) + ' is not a boolean value')

def equal(x, y):
    """Return True if x and y are equal."""
    if x == y:
        return 'True'
    try:
        return str(float(x) == float(y))
    except (TypeError, ValueError):
        return 'False'

def load(make_primitive):
    """Extend the set of primitive Logo procedures."""
    make_primitive('first', 1, lambda l: l[0])
    make_primitive(['butfirst', 'bf'], 1, lambda l: l[1:])
    make_primitive('last', 1, lambda l: l[-1])
    make_primitive(['butlast', 'bl'], 1, lambda l: l[:-1])

    make_primitive('sum', 2, numeric(op.add))
    make_primitive('difference', 2, numeric(op.sub))
    make_primitive('product', 2, numeric(op.mul))
    make_primitive(['div', 'quotient'], 2, numeric(op.truediv))

    make_primitive(['equalp', 'eq', 'equal?'], 2, equal)
    make_primitive(['lessp', 'lt', 'less?'], 2, numeric(op.lt))
    make_primitive(['greaterp', 'gp', 'greater?'], 2, numeric(op.gt))
    make_primitive(['emptyp', 'empty?'], 1, lambda x: str(len(x) == 0))
    make_primitive(['listp', 'list?'], 1, lambda x: str(type(x) == list))
    make_primitive(['wordp', 'word?'], 1, lambda x: str(type(x) == str))

    make_primitive('or', 2, logical(lambda x, y: bool(x or y)))
    make_primitive('and', 2, logical(lambda x, y: bool(x and y)))
    make_primitive('not', 1, logical(lambda x: not x))

    make_primitive('print', 1, logo_print)
    make_primitive('show', 1, logo_show)

    make_primitive('repeat', 2, repeat, needs_env=True)

    make_primitive('word', 2, logo_word)
    make_primitive(['sentence', 'se'], 2, logo_sentence)
    make_primitive('list', 2, logo_list)
    make_primitive('fput', 2, logo_fput)

    load_turtle_graphics(make_primitive)

def turtle_speed(n):
    """Set turtle graphics to draw every n frames (default: 1)."""
    turtle.tracer(n, 0)

def load_turtle_graphics(make_primitive):
    """Extend the set of primitive Logo procedures with turtle graphics.

    See http://docs.python.org/py3k/library/turtle.html for details on what
    these procedures do.
    """
    make_primitive(['forward', 'fd'], 1, numeric(turtle.fd))
    make_primitive(['backward', 'back', 'bk'], 1, numeric(turtle.bk))
    make_primitive(['right', 'rt'], 1, numeric(turtle.rt))
    make_primitive(['left', 'lt'], 1, numeric(turtle.lt))
    make_primitive('circle', 1, numeric(turtle.circle))
    make_primitive(['setpos', 'setposition', 'goto'], 2, numeric(turtle.goto))
    make_primitive(['seth', 'setheading'], 1, numeric(turtle.seth))
    make_primitive(['penup', 'pu'], 0, turtle.up)
    make_primitive(['pendown', 'pd'], 0, turtle.down)
    make_primitive(['showturtle', 'st'], 0, turtle.showturtle)
    make_primitive(['hideturtle', 'ht'], 0, turtle.hideturtle)
    make_primitive('clear', 0, turtle.clear)
    make_primitive('color', 1, turtle.color)  # accepts strings, e.g., 'red'
    make_primitive('begin_fill', 0, turtle.begin_fill)
    make_primitive('end_fill', 0, turtle.end_fill)
    make_primitive('exitonclick', 0, turtle.exitonclick)
    make_primitive('speed', 1, turtle_speed)