Permalink
Browse files

added 'def' plugin and some related fixes

  • Loading branch information...
1 parent 3fed81d commit 33f920b17da49b67f631dfe2a8ffbeef460aba1d @colinta committed Jan 19, 2013
View
@@ -2,6 +2,7 @@
Stores environment settings, e.g. options, and built-in functions.
"""
from plywood.values import PlywoodRuntime, PlywoodFunction, PlywoodHtmlPlugin
+from plywood.scope import Scope
class PlywoodEnv(object):
@@ -13,7 +14,7 @@ class PlywoodEnv(object):
def __init__(self, options):
self.options = options
- self.scope = scope = {}
+ self.scope = scope = Scope()
scope['__runtime'] = self
scope['__separator'] = options.get('separator', "\n")
View
@@ -33,7 +33,10 @@ def this_line(input, location):
break
while newline_end < len(input):
newline_end += 1
- if input[newline_end] == "\n":
+ try:
+ if input[newline_end] == "\n":
+ break
+ except IndexError:
break
line_no = input[:newline_start].count("\n")
return line_no, input[newline_start:newline_end]
View
@@ -67,7 +67,9 @@ def greater_than_or_equal(left, right, scope):
@PlywoodOperator.register('in')
def boolean_in(left, right, scope):
- return left.python_value(scope) in right.python_value(scope)
+ left = left.python_value(scope)
+ right = right.python_value(scope)
+ return left in right
@PlywoodOperator.register('is')
@@ -6,6 +6,7 @@
from ._for import *
from ._while import *
from ._import import *
+from ._def import *
from .include import *
from .extends import *
from .entitize import *
View
@@ -0,0 +1,94 @@
+'''
+Implements a ``def`` keyword.
+'''
+from plywood.env import PlywoodEnv
+from plywood.values import PlywoodCallOperator, PlywoodVariable, PlywoodOperator, PlywoodRuntime
+from plywood.runtime import Continue
+from plywood.exceptions import InvalidArguments
+
+
+@PlywoodEnv.register_runtime('def')
+def _def(states, scope, arguments, block):
+ '''
+ Example:
+ def foo(bar, baz, quux='value')
+ foo => name of function
+ [bar, baz] => arguments, no default values. These must be variable names.
+
+ Sorry, no support for *args and **kwargs just yet.
+ '''
+ if not len(block.lines):
+ raise InvalidArguments('A block is required in `def`')
+ if len(arguments.args) != 1 \
+ or len(arguments.kwargs) \
+ or not isinstance(arguments.args[0], PlywoodCallOperator) \
+ or arguments.args[0].operator != '()':
+ raise InvalidArguments('`def` should be followed by a function definition')
+ function_name = arguments.args[0].left.get_name()
+ arglist = arguments.args[0].right.args
+ kwarglist = arguments.args[0].right.kwargs
+ final_arglist = []
+ defaults = {}
+ # all arglist.args should be a varname
+ for var_name in arglist:
+ if not isinstance(var_name, PlywoodVariable):
+ raise InvalidArguments('`def` should be given a list of variable names. '
+ '{0!r} is invalid'.format(var_name.python_value(scope)))
+ final_arglist.append(var_name.get_name())
+
+ for kwarg in kwarglist:
+ var_name = kwarg.key.python_value(scope)
+ final_arglist.append(var_name)
+ value = kwarg.value.python_value(scope)
+ defaults[var_name] = value
+
+ # Now we have to re-produce python's way of accepting arguments, but I'm
+ # gonna get away with being sloppy about it...
+ # First, any kwargs can be assigned, and removed from the final_arglist.
+ # Then, args is scanned and values are assigned to corresponding names in
+ # final_arglist. If any values are left over in final_arglist, args, or
+ # kwargs, something went wrong.
+ #
+ # The sloppy part comes because unlike python, you can specify args and
+ # kwargs in pretty much any order. All kwargs are assigned first, then
+ # the args are assigned to whatever is left in the local_arglist, then
+ # defaults. Only after all that is local_arglist checked to make sure it is
+ # empty.
+ def the_function(states, called_scope, called_arguments, called_block):
+ args = called_arguments.args
+ kwargs = called_arguments.kwargs
+ local_arglist = [] + final_arglist
+ called_scope.push()
+ for plywood_kwarg in kwargs:
+ local_value = plywood_kwarg.value.python_value(scope)
+ local_var_name = plywood_kwarg.key.python_value(scope)
+ if local_var_name in local_arglist:
+ local_arglist.remove(local_var_name)
+ else:
+ raise InvalidArguments('Unknown keyword argument {0!r}'.format(local_var_name))
+ called_scope[local_var_name] = local_value
+
+ for plywood_value in args:
+ local_value = plywood_value.python_value(called_scope)
+ if not local_arglist:
+ raise InvalidArguments('Too many arguments passed to {0}'.format(function_name))
+ local_var_name = local_arglist.pop(0)
+ called_scope[local_var_name] = local_value
+
+ for local_var_name, local_value in defaults.iteritems():
+ if local_var_name in local_arglist:
+ local_arglist.remove(local_var_name)
+ called_scope[local_var_name] = local_value
+
+ if local_arglist:
+ raise InvalidArguments('Unassigned values: {0}'.format(', '.join(local_arglist)))
+ retval = block.get_value(called_scope)
+ called_scope.pop()
+ print("""=============== _def.py at line {0} ===============
+retval: {1!r}
+""".format(__import__('sys')._getframe().f_lineno - 2, retval, ))
+ return [Continue()], retval
+ the_function.__name__ = function_name
+
+ scope[function_name] = PlywoodRuntime(the_function)
+ return [Continue()], ''
View
@@ -21,7 +21,7 @@ def _for(states, scope, arguments, block):
or len(arguments.kwargs) \
or not isinstance(arguments.args[0], PlywoodOperator) \
or arguments.args[0].operator != 'in':
- raise InvalidArguments('`for` only accepts an "in" operation')
+ raise InvalidArguments('`for` only accepts an `in` operation')
var = arguments.args[0].left
if not isinstance(var, PlywoodVariable) and not isinstance(var, PlywoodParens):
raise InvalidArguments('`for` expects a variable name or tuple of variable names')
View
@@ -14,8 +14,8 @@ class ElseState(Runtime):
def _if(states, scope, arguments, block):
if not len(block.lines):
raise InvalidArguments('A block is required in `if`')
- if not len(arguments.args):
- raise InvalidArguments('A condition is required in `if`')
+ if len(arguments.args) != 1:
+ raise InvalidArguments('A condition (and only one condition) is required in `if`')
if len(arguments.args) != 1 or len(arguments.kwargs):
raise InvalidArguments('`if` only accepts one argument')
arg = arguments.args[0].python_value(scope)
@@ -28,8 +28,8 @@ def _if(states, scope, arguments, block):
def _elif(states, scope, arguments, block):
if not len(block.lines):
raise InvalidArguments('A block is required in `elif`')
- if not len(arguments.args):
- raise InvalidArguments('A condition is required in `elif`')
+ if len(arguments.args) != 1:
+ raise InvalidArguments('A condition (and only one condition) is required in `elif`')
if len(arguments.args) != 1 or len(arguments.kwargs):
raise InvalidArguments('`elif` only accepts one argument')
arg = arguments.args[0].python_value(scope)
@@ -15,6 +15,8 @@ def include(states, scope, arguments, block):
raise InvalidArguments('`include` only accepts one argument')
if len(block.lines):
raise InvalidArguments('`include` does not accept a block')
+
+ scope.push()
restore_scope = {}
delete_scope = []
context = scope['self']
@@ -35,10 +37,10 @@ def include(states, scope, arguments, block):
retval = ''
with open(template_path) as f:
input = f.read()
- old_input = scope['__input']
+ scope.push()
scope['__input'] = input
retval = Plywood(input).run(context, scope['__runtime'])
- scope['__input'] = old_input
+ scope.pop()
if len(arguments.kwargs):
for key, value in restore_scope.iteritems():
View
@@ -2,25 +2,35 @@
@PlywoodEnv.register_fn('str')
-def _str(val):
- return str(val)
+def _str(*args, **kwargs):
+ return str(*args, **kwargs)
@PlywoodEnv.register_fn('unicode')
-def _unicode(val):
- return unicode(val)
+def _unicode(*args, **kwargs):
+ return unicode(*args, **kwargs)
@PlywoodEnv.register_fn('int')
-def _int(val):
- return int(val)
+def _int(*args, **kwargs):
+ return int(*args, **kwargs)
@PlywoodEnv.register_fn('bool')
-def _bool(val):
- return bool(val)
+def _bool(*args, **kwargs):
+ return bool(*args, **kwargs)
@PlywoodEnv.register_fn('float')
-def _float(val):
- return float(val)
+def _float(*args, **kwargs):
+ return float(*args, **kwargs)
+
+
+@PlywoodEnv.register_fn('list')
+def _list(*args, **kwargs):
+ return list(*args, **kwargs)
+
+
+@PlywoodEnv.register_fn('dict')
+def _dict(*args, **kwargs):
+ return dict(*args, **kwargs)
View
@@ -26,6 +26,7 @@
)
from plywood.env import PlywoodEnv
from exceptions import UnindentException
+from scope import Scope
def plywood(input, context={}, **options):
@@ -48,7 +49,7 @@ def __repr__(self):
def run(self, context, runtime):
parsed = self.compile()
runtime.scope['__input'] = self.input
- runtime.scope['self'] = context
+ runtime.scope['self'] = Scope(context)
return parsed.python_value(runtime.scope)
def compile(self):
View
@@ -1,19 +1,48 @@
class Scope(object):
- def __init__(self, values):
- self.values = values
+ def __init__(self, values=None):
+ if values is None:
+ self.values = {}
+ else:
+ self.values = values
self.restore_scope = [{}]
self.delete_scope = [[]]
- def __setitem__(self, key):
- self.values.__setitem__(key)
+ def tracking(self, key):
+ return key in self.delete_scope[-1] and key in self.restore_scope[-1]
+
+ def track(self, key):
+ if key in self.values:
+ self.restore_scope[-1][key] = self.values[key]
+ else:
+ self.delete_scope[-1].append(key)
+
+ def __setitem__(self, key, value):
+ if not self.tracking(key):
+ self.track(key)
+ self.values.__setitem__(key, value)
def __getitem__(self, key):
return self.values.__getitem__(key)
+ def __getattr__(self, attr):
+ try:
+ return self.values.__getattribute__(attr)
+ except AttributeError:
+ return self.values[attr]
+
+ def __contains__(self, attr):
+ return self.values.__contains__(attr)
+
def push(self):
- pass
+ self.restore_scope.append({})
+ self.delete_scope.append([])
def pop(self):
- pass
+ restore = self.restore_scope.pop()
+ delete = self.delete_scope.pop()
+ for key, value in restore.iteritems():
+ self.values[key] = value
+ for key in delete:
+ del self.values[key]
Oops, something went wrong.

0 comments on commit 33f920b

Please sign in to comment.