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 = 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
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
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
0693 prompts = {
0694 None: '??? ',
0695 'to': 'to? ',
0696 '[': ' [ ? ',
0697 '(': ' ( ? ',
0698 'func': '..? ',
0699 }
0700
0701
0702
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
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
0960
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
1055Logo = RootFrame()
1056from pylogo import builtins
1057Logo.import_module(builtins)
1058from pylogo import oobuiltins
1059Logo.import_module(oobuiltins)
1060
1061
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)