0001"""
0002interpreter for pylogo
0003  Ian Bicking <ianb@colorstudy.com>
0004
0005A Logo interpreter.
0006"""
0007
0008
0009from types import *
0010from pylogo import reader
0011import inspect, os, sys
0012from pylogo.common import *
0013from pylogo.objectintrospect import getlogoattr, update_logo_attrs
0014import imp
0015import threading
0016
0017class Interpreter(object):
0018    """
0019    The interpreter gets tokens (from a reader.TrackingStream) and
0020    runs them.  It holds the namespace, which is dynamically scoped.  
0021    
0022    You execute one expression by calling interpreter.expr(tokenizer),
0023    where tokenizer may be reader.TrackingStream or other tokenizer
0024    instance.  It returns the value of the expression.
0025
0026    The RootFrame and Frame subclasses implement the namespace
0027    operations (this class is abstract).
0028    """
0029
0030    special_forms = {}
0031    "Methods register themselves using this dictionary"
0032
0033    def special(names, special_forms=special_forms):
0034        def decorator(func):
0035            if isinstance(names, basestring):
0036                all_names = [names]
0037            else:
0038                all_names = names
0039            for name in all_names:
0040                special_forms[name] = func
0041            return func
0042        return decorator
0043
0044    def __init__(self, tokenizer=None):
0045        self.tokenizers = []
0046        if tokenizer is not None:
0047            self.tokenizers.append(tokenizer)
0048        self.actors = []
0049
0050    def tokenizer__get(self):
0051        """
0052        Gets the current tokenizer.
0053        """
0054        return self.tokenizers[-1]
0055    tokenizer = property(tokenizer__get)
0056
0057    def push_tokenizer(self, tokenizer):
0058        """
0059        You can stack up multiple tokenizers, as the interpreter goes
0060        from evaluating a file to a list to a sublist, etc.  New
0061        interpreters are created for a new scope.
0062        """
0063        #print "Pushing %r onto %r" % (tokenizer, self)
0064        self.tokenizers.append(tokenizer)
0065
0066    def pop_tokenizer(self):
0067        #print "Popping %r from %r" % (self.tokenizers[-1], self)
0068        self.tokenizers.pop()
0069
0070    def expr(self):
0071        """
0072        Top level expression-getter/evaluator (see also expr_top).
0073        """
0074        try:
0075            val = self.expr_without_error()
0076        except LogoError, e:
0077            # This is used for creating the traceback
0078            e.set_frame(self)
0079            raise
0080        except (LogoControl, SystemExit, KeyboardInterrupt,
0081                StopIteration):
0082            # These exceptions are mostly harmless
0083            raise
0084        except Exception, e:
0085            # Here we wrap other exceptions... this needs some work
0086            #import traceback
0087            #traceback.print_exc()
0088            exc_info = sys.exc_info()
0089            # @@: should add the exception traceback to this somehow
0090            newExc = LogoError(str(e), description=str(e))
0091            newExc.set_frame(self)
0092            raise LogoError, newExc, exc_info[2]
0093        return val
0094
0095    def expr_top(self):
0096        """
0097        Unlike expr(), this ignores empty lines; should only be used
0098        in top-level expressions (including expressions taken from
0099        lists).
0100        """
0101        try:
0102            p = self.tokenizer.peek()
0103        except StopIteration:
0104            p = None
0105        if p == '\n':
0106            self.tokenizer.next()
0107            return None
0108        elif p == ';':
0109            while 1:
0110                p = self.tokenizer.next()
0111                if p == '\n':
0112                    break
0113            return None
0114        elif p is EOF:
0115            return EOF
0116        return self.expr()
0117
0118    def expr_without_error(self):
0119        """
0120        Get a full expression from the tokenizer, execute it, and
0121        return the value.
0122        
0123        expr ::= exprInner <operator> exprInner
0124             ::= exprInner
0125        """
0126        while 1:
0127            # Strip out any comments:
0128            # (typically the reader would do this, but we do it more
0129            # lazily so we can get the comments if we want them)
0130            p = self.tokenizer.peek()
0131            if p == ';':
0132                while 1:
0133                    p = self.tokenizer.next()
0134                    if p == '\n':
0135                        break
0136            else:
0137                break
0138        val = self.expr_inner()
0139        while 1:
0140            # Check if there's any infix operators:
0141            p = self.tokenizer.peek()
0142            if p not in ['/', '*', '+', '-', '>', '<', '=',
0143                         '>=', '=>', '<=', '=<', '<>']:
0144                break
0145            self.tokenizer.next()
0146            e = self.expr_inner()
0147            # @@: no order of precedence
0148            if p == '/':
0149                val = float(val) / e
0150            elif p == '*':
0151                val *= e
0152            elif p == '-':
0153                val -= e
0154            elif p == '+':
0155                val += e
0156            elif p == '<':
0157                val = val < e
0158            elif p == '>':
0159                val = val > e
0160            elif p == '=':
0161                val = val == e
0162            elif p == '>=' or p == '=>':
0163                val = val >= e
0164            elif p == '<=' or p == '=<':
0165                val = val <= e
0166            elif p == '<>':
0167                val = val != e
0168            else:
0169                assert 0, "Unknown symbol: %s" % p
0170        return val
0171
0172
0173    def expr_inner(self, apply=None, get_function=None,
0174                   get_variable=None):
0175        """
0176        An 'inner' expression, an expression that does not include
0177        infix operators.
0178
0179        ::
0180
0181          exprInner ::= <literal int or float>
0182                    ::= '-' expr
0183                    ::= '+' expr
0184                    ::= ('\"' or 'QUOTE') <word>
0185                    ::= ':' <word>
0186                    ::= MAKE (':' or '\"') <word> expr
0187                    ::= MAKE <word> expr
0188                    ::= TO <to expression>
0189                    ::= '[' <list expression> ']'
0190                    ::= '(' <word> <expr> ... <expr> ')'
0191                    ::= <word> <expr> ... <expr>
0192
0193        Things to note:
0194
0195        * ``MAKE :x 10``, ``MAKE \"x 10``, and ``MAKE x 10`` all work
0196          equivalently (make is a special form, unlike in UCBLogo).
0197        * <list expression> is a nested list of tokens.
0198        * <to expression> is TO func_name var1 var2 <int>, where <int>
0199          is the default arity (number of variables).  Variables, like
0200          with make, can be prefixed with : or \", but need not be.
0201        * () is not used to force precedence, but to force execution
0202          with a specific arity.  In other words, () works like Lisp.
0203        """
0204        tok = self.tokenizer.next()
0205        if apply is None:
0206            apply = self.apply
0207        if get_function is None:
0208            get_function = self.get_function
0209        if get_variable is None:
0210            get_variable = self.get_variable
0211
0212        if tok == '\n':
0213            raise LogoEndOfLine("The end of the line was not expected")
0214            return self.expr_inner()
0215
0216        elif tok is EOF:
0217            raise LogoEndOfCode("The end of the code block was not expected")
0218
0219        elif not isinstance(tok, basestring):
0220            # Some other fundamental type (usually int or float)
0221            return tok
0222
0223        elif tok == '-':
0224            # This works really poorly in practice, because "-" usually
0225            # gets interpreted as an infix operator.
0226            return -self.expr()
0227
0228        elif tok == '+':
0229            return self.expr()
0230
0231        elif tok in ('/', '*'):
0232            raise LogoError("Operator not expected: %s" % tok)
0233
0234        elif tok == '"' or tok.lower() == 'quote':
0235            tok = self.tokenizer.next()
0236            return tok
0237
0238        elif tok == ':':
0239            tok = self.tokenizer.next()
0240            return get_variable(tok)
0241
0242        elif tok == '[':
0243            self.tokenizer.push_context('[')
0244            result = self.expr_list()
0245            self.tokenizer.pop_context()
0246            return result
0247
0248        elif tok == ';':
0249            while 1:
0250                tok = self.tokenizer.next()
0251                if tok == '\n' or tok is EOF:
0252                    break
0253
0254        elif tok == '(':
0255            self.tokenizer.push_context('(')
0256            try:
0257                func = self.tokenizer.peek()
0258                if not reader.is_word(func):
0259                    # We don't actually have a function call then, but
0260                    # just a sub-expression.
0261                    val = self.expr()
0262                    if not self.tokenizer.next() == ')':
0263                        raise LogoSyntaxError("')' expected")
0264                    return val
0265                else:
0266                    self.tokenizer.next()
0267                if self.special_forms.has_key(func.lower()):
0268                    special_form = self.special_forms[func.lower()]
0269                    val = special_form(self, greedy=True)
0270                    next_tok = self.tokenizer.next()
0271                    if next_tok != ')':
0272                        raise LogoSyntaxError("')' expected")
0273                    return val
0274                else:
0275                    args = []
0276                    while 1:
0277                        tok = self.tokenizer.peek()
0278                        if tok == ')':
0279                            break
0280                        elif tok == '\n':
0281                            self.tokenizer.next()
0282                            continue
0283                        elif tok is EOF:
0284                            raise LogoEndOfCode("Unexpected end of code (')' expected)")
0285                        args.append(self.expr())
0286                    val = apply(get_function(func), args)
0287                if not self.tokenizer.next() == ')':
0288                    raise LogoSyntaxError("')' was expected.")
0289            finally:
0290                self.tokenizer.pop_context()
0291            return val
0292
0293        else:
0294            if not reader.is_word(tok):
0295                raise LogoSyntaxError("Unknown token: %r" % tok)
0296            if tok.lower() in self.special_forms:
0297                special_form = self.special_forms[tok.lower()]
0298                val = special_form(self, greedy=False)
0299                return val
0300            else:
0301                func_name = tok
0302                func = get_function(func_name)
0303                n = arity(func)
0304                self.tokenizer.push_context('func')
0305                try:
0306                    args = []
0307                    # -1 arity means the function is greedy
0308                    if n == -1:
0309                        while 1:
0310                            tok = self.tokenizer.peek()
0311                            if tok == '\n' or tok is EOF:
0312                                self.tokenizer.next()
0313                                break
0314                            args.append(self.expr())
0315                    else:
0316                        for i in range(n):
0317                            try:
0318                                args.append(self.expr())
0319                            except (LogoEndOfCode, LogoEndOfLine):
0320                                raise LogoEndOfCode(
0321                                    "Not enough arguments provided to %s: got %i and need %i" % (func_name, i, n))
0322                finally:
0323                    self.tokenizer.pop_context()
0324                return apply(func, args)
0325
0326    @special('make')
0327    def special_make(self, greedy):
0328        """
0329        The special MAKE form (special because a variable in the
0330        first argument isn't evaluated).
0331        """
0332        tok = self.tokenizer.next()
0333        if tok in ('"', ':'):
0334            tok = self.tokenizer.next()
0335        self.set_variable(tok, self.expr())
0336
0337    @special('localmake')
0338    def special_localmake(self, greedy):
0339        """
0340        The special LOCALMAKE form
0341        """
0342        tok = self.tokenizer.next()
0343        if tok in ('"', ':'):
0344            tok = self.tokenizer.next()
0345        self.set_variable_local(tok, self.expr())
0346
0347    @special('to')
0348    def special_to(self, greedy):
0349        """
0350        The special TO form.
0351        """
0352        self.tokenizer.push_context('to')
0353        vars = []
0354        default = None
0355        name = self.tokenizer.next()
0356        while 1:
0357            tok = self.tokenizer.next()
0358            if tok == '\n':
0359                break
0360            elif tok == '"' or tok == ':':
0361                continue
0362            elif type(tok) is int:
0363                default = tok
0364                continue
0365            vars.append(tok)
0366        body = []
0367        # END can only occur immediately after a newline, so we keep track
0368        lastNewline = False
0369        while 1:
0370            tok = self.tokenizer.next()
0371            if (lastNewline and isinstance(tok, str)
0372                and tok.lower() == 'end'):
0373                break
0374            lastNewline = (tok == '\n')
0375            if tok is EOF:
0376                raise LogoEndOfCode("The end of the file was not expected in a TO; use END")
0377            body.append(tok)
0378        func = UserFunction(name, vars, default, body)
0379        self.set_function(name.lower(), func)
0380        self.tokenizer.pop_context()
0381        return func
0382
0383    @special('local')
0384    def special_local(self, greedy):
0385        """
0386        The special LOCAL form (with unevaluated variables).  (Should
0387        this be generally greedy?)
0388        """
0389        vars = []
0390        if greedy:
0391            while 1:
0392                tok = self.tokenizer.peek()
0393                if tok in (':', '"'):
0394                    self.tokenizer.next()
0395                    continue
0396                elif not reader.is_word(tok):
0397                    break
0398                vars.append(tok)
0399                self.tokenizer.next()
0400        else:
0401            if self.tokenizer.peek() in (':', '"'):
0402                self.tokenizer.next()
0403            vars = [self.tokenizer.next()]
0404        for v in vars:
0405            self.make_local(v)
0406        return None
0407
0408    @special('for')
0409    def special_for(self, greedy):
0410        """
0411        Special FOR form.  Again with the variable name.
0412        """
0413        varName = self.tokenizer.next()
0414        if varName in (':', '"'):
0415            varName = self.tokenizer.next()
0416        seq = self.expr()
0417        block = self.expr()
0418        try:
0419            for v in seq:
0420                self.set_variable_local(varName, v)
0421                try:
0422                    val = self.eval(block)
0423                except LogoContinue:
0424                    pass
0425        except LogoBreak:
0426            pass
0427        return val
0428
0429    ## OO special forms
0430
0431    @special(['tell', 'ask'])
0432    def special_tell(self, greedy):
0433        obj = self.expr()
0434        tok = self.tokenizer.peek()
0435        if tok == '[' or isinstance(tok, list):
0436            if tok == '[':
0437                block = self.expr()
0438            else:
0439                block = tok
0440                self.tokenizer.next()
0441            interp = self.new()
0442            interp.push_actor(obj)
0443            try:
0444                return interp.eval(block)
0445            finally:
0446                interp.pop_actor()
0447        else:
0448            # Static "tell"
0449            def get_function(func_name):
0450                return getlogoattr(obj, func_name)
0451            def get_variable(var):
0452                try:
0453                    return obj[var]
0454                except (AttributeError, NameError, KeyError, IndexError,
0455                        TypeError, ValueError):
0456                    return getlogoattr(obj, var)
0457            value = self.expr_inner(get_function=get_function,
0458                                    get_variable=get_variable)
0459            return value
0460
0461    @special('makeattr')
0462    def special_makeattr(self, greedy):
0463        obj = self.expr()
0464        tok = self.tokenizer.next()
0465        if tok in ('"', ':'):
0466            tok = self.tokenizer.next()
0467        value = self.expr()
0468        if hasattr(obj, '__setitem__'):
0469            obj[tok] = value
0470        else:
0471            obj.tok = value
0472
0473    def expr_list(self):
0474        """
0475        Grab a list (the '[' has already been grabbed).
0476        """
0477        body = []
0478        while 1:
0479            tok = self.tokenizer.next()
0480            if tok == ']':
0481                return LogoList(body, self.tokenizer.file)
0482            elif tok == '[':
0483                tok = self.expr_list()
0484            if tok is EOF:
0485                raise LogoEndOfCode(
0486                    "The end of the code was not expected (waiting for ])")
0487            body.append(tok)
0488
0489    def eval(self, lst):
0490        """
0491        Evaluate a list in this scope.
0492        """
0493        tokenizer = reader.ListTokenizer(lst)
0494        val = None
0495        self.push_tokenizer(tokenizer)
0496        try:
0497            while 1:
0498                tok = self.tokenizer.peek()
0499                if tok is EOF:
0500                    return val
0501                val = self.expr_top()
0502        finally:
0503            self.pop_tokenizer()
0504        return val
0505
0506    def apply(self, func, args):
0507        """
0508        Apply the `args` to the `func`.  If the function has an
0509        attribute `logo_aware`, which is true, then the first argument
0510        passed to the function will be this interpreter object.  This
0511        allows special functions, like IF or WHILE, to manipulate the
0512        interpreter.
0513        """
0514        if getattr(func, 'logo_aware', 0):
0515            return func(self, *args)
0516        else:
0517            return func(*args)
0518
0519    def import_function(self, import_function, names=None):
0520        """
0521        Inputs the function `func` into the namespace, using the name
0522        it was originally defined with.  The special attribute
0523        `logo_name` overrides the function name, and `aliases` provides
0524        abbreviations for the function (like FD for FORWARD).
0525        """
0526        if isinstance(import_function, (ClassType, type)):
0527            func = import_function.__init__
0528            name = import_function.__name__
0529        else:
0530            func = import_function
0531            name = import_function.func_name
0532        d = func.func_dict
0533        if d.get('logo_hide'):
0534            return
0535        if names is None:
0536            name = d.get('logo_name', name)
0537            if name.startswith('_'): return
0538            names = [name] + list(d.get('aliases', []))
0539        #print "Importing functions %s" % ', '.join(names)
0540        for n in names:
0541            self.set_function(n, import_function)
0542
0543    def import_module(self, mod):
0544        """
0545        Import a module (either a module object, or the string name of
0546        the module), moving all of its exported functions into the
0547        current namespace (nested namespaces are not supported).
0548
0549        If a file ``defs/modulename.logodef`` exists, it will be
0550        loaded to find extra information about the module.  This file
0551        contains one line for each annotated function (# or ; for
0552        comment lines).  See loadDefs for more.
0553        """
0554        if type(mod) is str:
0555            mod = load_module(mod)
0556        #print "Importing %s" % mod.__name__
0557        if os.path.exists('defs/%s.logodef' % mod.__name__):
0558            defs = self.load_defs('defs/%s.logodef' % mod.__name__)
0559        else:
0560            defs = {}
0561        main_name = mod.__name__.split('.')[-1] + '_main'
0562        main_func = None
0563        for n in dir(mod):
0564            obj = getattr(mod, n)
0565            if type(obj) is FunctionType:
0566                if n == main_name:
0567                    main_func = obj
0568                name = obj.func_name
0569                if defs.has_key(name.lower()):
0570                    useName, arity, aliases = defs.get(name.lower())
0571                    if arity is not None:
0572                        obj.arity = arity
0573                    if useName:
0574                        names = [name]
0575                    else:
0576                        names = []
0577                    names.extend(aliases)
0578                    if not names:
0579                        continue
0580                    self.import_function(obj, names)
0581                else:
0582                    self.import_function(obj)
0583            if (type(obj) in (ClassType, type)
0584                and getattr(obj, 'logo_class', False)):
0585                self.import_function(obj, [obj.__name__])
0586        if main_func:
0587            main_func(self)
0588
0589    def load_defs(self, filename):
0590        """
0591        Load function definitions from a file.  This file should have
0592        one function annotation on a line (lines starting with # or ;
0593        are comments).
0594
0595        Each line starts with the function name and a color.  A star
0596        (*) means that the original function name should not be used.
0597        A word like arity:N will set the default arity of the function
0598        to N (if the default arity differs from the number of
0599        arguments the function takes).  Other words are interpreted as
0600        aliases for a function.  E.g.::
0601        
0602            forward: fd
0603            up: * penup pu
0604            ; RGB arguments only:
0605            color: arity:3
0606        """
0607        f = open(filename)
0608        defs = {}
0609        for l in f.readlines():
0610            l = l.strip()
0611            if not l or l.startswith('#') or l.startswith(';'):
0612                continue
0613            funcName, rest = l.split(':', 1)
0614            funcName = funcName.strip().lower()
0615            arity = None
0616            aliases = []
0617            useName = True
0618            for n in rest.split():
0619                if n == '*':
0620                    useName = False
0621                elif n.lower().startswith('arity:'):
0622                    arity = int(n[6:])
0623                else:
0624                    aliases.append(n)
0625            defs[funcName] = useName, arity, aliases
0626        return defs
0627
0628    def import_logo(self, filename):
0629        """
0630        Import a logo file.  This executes the file, including any TO
0631        statements, putting everything into the normal namespace/scope.
0632        """
0633        print "Loading %s." % filename
0634        f = open(filename)
0635        self.import_logo_stream(f)
0636        try:
0637            main_name = os.path.splitext(os.path.basename(filename))[0] + '_main'
0638            func = self.get_function(main_name)
0639        except LogoNameError:
0640            pass
0641        else:
0642            self.apply(func, ())
0643        f.close()
0644
0645    def import_logo_stream(self, stream):
0646        tokenizer = reader.FileTokenizer(stream)
0647        self.push_tokenizer(tokenizer)
0648        try:
0649            while 1:
0650                v = self.expr_top()
0651                if v is EOF: break
0652        finally:
0653            self.pop_tokenizer()
0654
0655    def input_loop(self, input, output):
0656        """
0657        Read-Eval-Print-Repeat loop, i.e., the standard prompt.
0658        """
0659        tokenizer = reader.FileTokenizer(input, output=output,
0660                                         prompt=self.prompts)
0661        self.push_tokenizer(tokenizer)
0662        try:
0663            while 1:
0664                try:
0665                    v = self.expr_top()
0666                except LogoError, e:
0667                    if str(e) != str(e.description):
0668                        print e.description, ':', e
0669                    else:
0670                        print e
0671                    v = None
0672                except KeyboardInterrupt:
0673                    if tokenizer.context:
0674                        tokenizer.context = []
0675                        print 'Aborted'
0676                    else:
0677                        print "Bye"
0678                        break
0679                except SystemExit:
0680                    break
0681                except Exception, e:
0682                    import traceback
0683                    traceback.print_exc()
0684                    v = None
0685                if v is EOF:
0686                    break
0687                if v is not None:
0688                    print "%s" % repr(v)
0689        finally:
0690            self.pop_tokenizer()
0691
0692    # Some standard prompts:
0693    prompts = {
0694        None: '??? ',
0695        'to': 'to? ',
0696        '[': ' [ ? ',
0697        '(': ' ( ? ',
0698        'func': '..? ',
0699        }
0700
0701    ########################################
0702    ## Object-oriented (actor) features
0703    ########################################
0704
0705    def push_actor(self, actor):
0706        update_logo_attrs(actor)
0707        self.actors.insert(0, actor)
0708
0709    def pop_actor(self, actor=None):
0710        if actor is None:
0711            self.actors.pop(0)
0712        else:
0713            self.actors.remove(actor)
0714
0715    def get_variable(self, v):
0716        for actor in self.actors:
0717            try:
0718                return actor[v]
0719            except (AttributeError, NameError, KeyError, IndexError,
0720                    TypeError, ValueError):
0721                pass
0722            try:
0723                return getattr(actor, v)
0724            except (AttributeError, NameError):
0725                pass
0726            logo_attrs = getattr(actor, '_logo_attrs', None)
0727            if logo_attrs is not None and v.lower() in logo_attrs:
0728                return getattr(actor, v, logo_attrs[v.lower()])
0729        return self.get_nonactor_variable(v)
0730
0731    def get_function(self, name):
0732        lower = name.lower()
0733        for actor in self.actors:
0734            attrs = getattr(actor, '_logo_attrs', {})
0735            if lower in attrs:
0736                return getattr(actor, attrs[lower])
0737            func = getattr(actor, name, None)
0738            if func is None:
0739                continue
0740            if hasattr(func, 'logo_aware'):
0741                return func
0742            if isinstance(func, (FunctionType, MethodType)):
0743                return func
0744        return self.get_nonactor_function(name)
0745
0746    del special
0747
0748def arity(func):
0749    """
0750    Get the arity of a function (the number of arguments it takes).
0751    If the function has an `arity` attribute, this will be used as an
0752    override, otherwise `inspect` is used to find the number of
0753    arguments.
0754
0755    Since `logo_aware` functions take an interpreter as the first
0756    argument, the arity of these functions is reduced by one.
0757    """
0758    if isinstance(func, (ClassType, type)):
0759        func = func.__init__
0760    if func is object.__init__:
0761        # Weird special case
0762        return 0
0763    if hasattr(func, 'arity'):
0764        return func.arity
0765    offset = 0
0766    if hasattr(func, 'im_func'):
0767        func = func.im_func
0768        offset = -1
0769    try:
0770        args, varargs, varkw, defaults = inspect.getargspec(func)
0771    except TypeError, e:
0772        e.args += (repr(func),)
0773        raise
0774    a = len(args) - len(defaults or [])
0775    if func.func_dict.get('logo_aware'):
0776        a -= 1
0777    return a + offset
0778
0779class UserFunction(object):
0780
0781    """
0782    A function the user defines, using TO.  When this function is
0783    called, the contents of the TO statement are executed.  The
0784    contents are not interpreted until then -- they are kept as tokens
0785    in a list.
0786    """
0787
0788    def __init__(self, name, vars, default, body):
0789        self.name = name
0790        self.vars = vars
0791        if default is None:
0792            self.arity = len(vars)
0793        else:
0794            self.arity = default
0795        self.body = body
0796        self.logo_aware = 1
0797
0798    def __call__(self, interp, *args, **kw):
0799        if '_extravars' in kw:
0800            extravars = kw.pop('_extravars')
0801        else:
0802            extravars = {}
0803        if '_actor' in kw:
0804            actor = kw.pop('_actor')
0805        else:
0806            actor = None
0807        if kw:
0808            raise TypeError(
0809                'Keyword arguments not supported')
0810        tokenizer = reader.ListTokenizer(LogoList(self.body, None))
0811        interp = interp.new(tokenizer)
0812        if actor is not None:
0813            interp.push_actor(actor)
0814        if len(args) < len(self.vars):
0815            args = args + (None,)*(len(self.vars)-len(args))
0816        for var, arg in zip(self.vars, args):
0817            interp.set_variable_local(var, arg)
0818        if len(args) > len(self.vars):
0819            interp.set_variable_local('rest', args[len(self.vars):])
0820        for name, value in extravars.iteritems():
0821            interp.set_variable_local(name, value)
0822        while 1:
0823            tok = tokenizer.peek()
0824            if tok is EOF:
0825                return
0826            try:
0827                interp.expr_top()
0828            except LogoOutput, exc:
0829                return exc.value
0830
0831    def __get__(self, obj, type=None):
0832        if obj is None:
0833            return self
0834        return BoundUserFunction(self, obj)
0835
0836    def __repr__(self):
0837        return 'Function: %s' % self.name
0838
0839class BoundUserFunction(object):
0840
0841    logo_aware = True
0842
0843    def __init__(self, func, obj):
0844        self.func, self.obj = func, obj
0845        self.arity = self.func.arity
0846
0847    def __call__(self, interp, *args, **kw):
0848        return self.func(
0849            interp, *args,
0850            **{'_extravars': {'self': self.obj},
0851               '_actor': self.obj})
0852
0853    def __repr__(self):
0854        return 'Function: %s for %s' % (
0855            self.func.name, self.obj)
0856
0857class RootFrame(Interpreter):
0858
0859    """
0860    The root/global frame object.  This holds all the function
0861    definitions, and global variables.  All the algorithms end up
0862    being different than for Frame, so they don't even inherit from
0863    each other, though they present the same interface.  Huh.
0864    """
0865
0866    def __init__(self, tokenizer=None):
0867        Interpreter.__init__(self, tokenizer=tokenizer)
0868        self.vars = {}
0869        self.functions = {}
0870        self.root = self
0871
0872    def __repr__(self):
0873        try:
0874            return '<RootFrame %x parsing "%r">' %                      (id(self), self.tokenizer)
0876        except:
0877            return '<RootFrame %x>' % id(self)
0878
0879    def tokenizer_stack(self):
0880        return self.tokenizers[:]
0881
0882    def get_nonactor_variable(self, v):
0883        v = v.lower()
0884        if self.vars.has_key(v):
0885            return self.vars[v]
0886        raise LogoNameError(
0887            "Variable :%s has not been set." % v)
0888
0889    def set_variable(self, v, value):
0890        self.vars[v.lower()] = value
0891
0892    def set_variable_local(self, v, value):
0893        self.vars[v.lower()] = value
0894
0895    def _set_variable_if_present(self, v, value):
0896        if self.vars.has_key(v):
0897            self.vars[v] = value
0898            return 1
0899        else:
0900            return 0
0901
0902    def new(self, tokenizer=None):
0903        return self.Frame(self, tokenizer=tokenizer or self.tokenizer)
0904
0905    def get_nonactor_function(self, name):
0906        try:
0907            return self.functions[name.lower()]
0908        except KeyError:
0909            raise LogoNameError("I don't know how  to %s" % name)
0910
0911    def set_function(self, name, func):
0912        self.functions[name.lower()] = func
0913
0914    def variable_names(self):
0915        return self.vars.keys()
0916
0917    def function_names(self):
0918        names = self.functions.keys()
0919        names.sort()
0920        return names
0921
0922    def _set_variable_names(self, d):
0923        for n in self.vars.keys():
0924            d[n] = 1
0925        return d
0926
0927    def erase_name(self, name):
0928        if self.vars.has_key(name.lower()):
0929            del self.vars[name.lower()]
0930        if self.functions.has_key(name.lower()):
0931            del self.functions[name.lower()]
0932
0933    def make_local(self, v):
0934        raise LogoSyntaxError(
0935            "You can only use LOCAL in a function (TO).")
0936
0937    def add_command(self, func, *args, **kw):
0938        return self.app.add_command(func, *args, **kw)
0939
0940class Frame(RootFrame):
0941
0942    """
0943    A local frame.  This frame usually has a parent frame, which is
0944    the scope that created this scope (usually via a function call).
0945    This chain implements the dynamic scoping; in a more typical
0946    lexically scoped language, the namespace would be attached to the
0947    container, not the previous caller.
0948
0949    Lisps sometimes implement both of these policies (fluid-wind?),
0950    where certain variables are dynamic (usually marked like *this*),
0951    but other variables are lexical.  That might be neat.
0952    """
0953
0954    def __init__(self, parent, tokenizer=None):
0955        Interpreter.__init__(self, tokenizer=tokenizer)
0956        self.parent = parent
0957        self.root = parent.root
0958        self.vars = {}
0959        # Holds the names of variables which have been declared local,
0960        # but may not have been set yet:
0961        self.local_vars = {}
0962
0963    def __repr__(self):
0964        return '<Frame %x parsing "%s">' %                  (id(self), self.tokenizer)
0966
0967    def tokenizer_stack(self):
0968        stack = self.parent.tokenizer_stack()
0969        stack.extend(self.tokenizers)
0970        return stack
0971
0972    def new(self, tokenizer=None):
0973        """
0974        Add a new frame, and return that frame.
0975        """
0976        return self.__class__(self, tokenizer=tokenizer or self.tokenizer)
0977
0978    def get_nonactor_variable(self, v):
0979        v = v.lower()
0980        if self.vars.has_key(v):
0981            return self.vars[v]
0982        if self.local_vars.has_key(v):
0983            raise LogoUndefinedError(
0984                "Variable :%s has not been set, but it has been "
0985                "declared LOCAL." % v)
0986        if self is self.root:
0987            raise LogoNameError(
0988                "Variable :%s has not been set." % v)
0989        return self.parent.get_variable(v)
0990
0991    def set_variable(self, v, value):
0992        v = v.lower()
0993        if not self._set_variable_if_present(v, value):
0994            self.vars[v] = value
0995
0996    def _set_variable_if_present(self, v, value):
0997        if self.local_vars.has_key(v) or self.vars.has_key(v):
0998            self.vars[v] = value
0999            return 1
1000        else:
1001            return self.parent._set_variable_if_present(v, value)
1002
1003    def get_nonactor_function(self, name):
1004        return self.root.get_function(name)
1005
1006    def set_function(self, name, func):
1007        return self.root.set_function(name)
1008
1009    def variable_names(self):
1010        return self._set_variable_names({}).keys()
1011
1012    def function_names(self):
1013        return self.root.function_names()
1014
1015    def _set_variable_names(self, d):
1016        for n in self.vars.keys():
1017            d[n] = 1
1018        return self.parent._set_variable_names(d)
1019
1020    def erase_name(self, name):
1021        if self.vars.has_key(name.lower()):
1022            del self.vars[name.lower()]
1023        self.parent.erase_name(name)
1024
1025    def make_local(self, v):
1026        self.local_vars[v] = None
1027
1028    def set_variable_local(self, v, value):
1029        self.vars[v.lower()] = value
1030
1031    def add_command(self, add_command, *args, **kw):
1032        return self.root.add_command(add_command, *args, **kw)
1033
1034RootFrame.Frame = Frame
1035
1036def load_module(name, path=None):
1037    """
1038    Loads a module given its name, because imp.find_module is
1039    annoying.
1040    """
1041    try:
1042        return sys.modules[name]
1043    except KeyError:
1044        pass
1045    names = name.split('.')
1046    if len(names) == 1:
1047        data = imp.find_module(names[0], path)
1048        mod = imp.load_module(names[0], *data)
1049        return mod
1050    else:
1051        mod = load_module(names[0], path)
1052        return load_module('.'.join(names[1:]), path=mod.__path__)
1053
1054# Logo is the root, global interpreter object:
1055Logo = RootFrame()
1056from pylogo import builtins
1057Logo.import_module(builtins)
1058from pylogo import oobuiltins
1059Logo.import_module(oobuiltins)
1060#if os.path.exists('init.logo'):
1061#    Logo.importLogo('init.logo')
1062
1063if __name__ == '__main__':
1064    import sys
1065    filenames = sys.argv[1:]
1066    for filename in filenames:
1067        Logo.import_logo(filename)
1068    import sys
1069    Logo.input_loop(sys.stdin, sys.stdout)