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