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
0064 self.tokenizers.append(tokenizer)
0065
0066 def pop_tokenizer(self):
0067
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
0078 e.set_frame(self)
0079 raise
0080 except (LogoControl, SystemExit, KeyboardInterrupt,
0081 StopIteration):
0082
0083 raise
0084 except Exception, e:
0085
0086
0087
0088 exc_info = sys.exc_info()
0089
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
0128
0129
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
0141 p = self.tokenizer.peek()
0142 if p not in ['/', '*', '+', '-', '>', '<', '=',
0143 '>=', '=>', '<=', '=<', '<>']:
0144 break
0145 self.tokenizer.next()
0146 e = self.expr_inner()
0147
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
0221 return tok
0222
0223 elif tok == '-':
0224
0225
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
0260
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
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
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
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
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