Changeset 24

Show
Ignore:
Timestamp:
19/08/04 12:05:48 (8 years ago)
Author:
steve
Message:

macros!

Location:
trunk
Files:
2 modified

Legend:

Unmodified
Added
Removed
  • trunk/airspeed.py

    r23 r24  
    11#!/usr/bin/env python 
    22 
    3 import re 
    4 import cStringIO as StringIO 
    5 import operator 
     3import re, operator 
     4 
     5try: import cStringIO as StringIO 
     6except ImportError: import StringIO 
    67 
    78__all__ = ['TemplateSyntaxError', 'Template'] 
     
    9192        m = pattern.match(self._full_text, self.end) 
    9293        if not m: raise NoMatch() 
     94        self.end = m.start(pattern.groups) 
     95        return m.groups()[:-1] 
     96 
     97    def next_match(self, pattern): 
     98        m = pattern.match(self._full_text, self.end) 
     99        if not m: return False 
    93100        self.end = m.start(pattern.groups) 
    94101        return m.groups()[:-1] 
     
    212219    def parse(self): 
    213220        self.identity_match(self.DOT) 
    214         self.expression = self.next_element(Expression) 
     221        self.expression = self.next_element(VariableExpression) 
    215222 
    216223    def calculate(self, current_object, global_namespace): 
     
    218225 
    219226 
    220 class Expression(_Element): 
     227class VariableExpression(_Element): 
    221228    subexpression = None 
    222229 
     
    260267    def parse(self): 
    261268        self.silent, self.braces = self.identity_match(self.START) 
    262         self.expression = self.require_next_element(Expression, 'expression') 
     269        self.expression = self.require_next_element(VariableExpression, 'expression') 
    263270        if self.braces: self.require_match(self.CLOSING_BRACE, '}') 
    264271 
     
    276283    def parse(self): 
    277284        self.identity_match(self.LEADING_DOLLAR) 
    278         self.expression = self.require_next_element(Expression, 'name') 
     285        self.expression = self.require_next_element(VariableExpression, 'name') 
    279286        self.calculate = self.expression.calculate 
    280287 
     
    392399 
    393400 
     401class MacroDefinition(_Element): 
     402    START = re.compile(r'#macro\b(.*)', re.S + re.I) 
     403    OPEN_PAREN = re.compile(r'[ \t]*\(\s*(.*)$', re.S) 
     404    NAME = re.compile(r'\s*([a-z][a-z_0-9]*)\b(.*)', re.S + re.I) 
     405    CLOSE_PAREN = re.compile(r'[ \t]*\)(.*)$', re.S) 
     406    ARG_NAME = re.compile(r'[ \t]+\$([a-z][a-z_0-9]*)(.*)$', re.S + re.I) 
     407    RESERVED_NAMES = ('if', 'else', 'elseif', 'set', 'macro', 'foreach', 'parse', 'include', 'stop', 'end') 
     408    def parse(self): 
     409        self.identity_match(self.START) 
     410        self.require_match(self.OPEN_PAREN, '(') 
     411        self.macro_name, = self.require_match(self.NAME, 'macro name') 
     412        if self.macro_name.lower() in self.RESERVED_NAMES: 
     413            raise self.syntax_error('non-reserved name') 
     414        self.arg_names = [] 
     415        while True: 
     416            m = self.next_match(self.ARG_NAME) 
     417            if not m: break 
     418            self.arg_names.append(m[0]) 
     419        self.require_match(self.CLOSE_PAREN, ') or arg name') 
     420        self.block = self.require_next_element(Block, 'block') 
     421        self.require_next_element(End, 'block') 
     422 
     423    def evaluate(self, namespace, stream): 
     424        macro_key = '#' + self.macro_name.lower() 
     425        if macro_key in namespace: 
     426            raise Exception("cannot redefine macro") 
     427        namespace[macro_key] = self 
     428 
     429    def execute_macro(self, namespace, stream, arg_value_elements): 
     430        if len(arg_value_elements) != len(self.arg_names): 
     431            raise Exception("expected %d arguments, got %d" % (len(self.arg_names), len(arg_value_elements))) 
     432        macro_namespace = LocalNamespace(namespace) 
     433        for arg_name, arg_value in zip(self.arg_names, arg_value_elements): 
     434            macro_namespace[arg_name] = arg_value.calculate(namespace) 
     435        self.block.evaluate(macro_namespace, stream) 
     436 
     437 
     438class MacroCall(_Element): 
     439    START = re.compile(r'#([a-z][a-z_0-9]*)\b(.*)', re.S + re.I) 
     440    OPEN_PAREN = re.compile(r'[ \t]*\(\s*(.*)$', re.S) 
     441    CLOSE_PAREN = re.compile(r'[ \t]*\)(.*)$', re.S) 
     442    SPACE = re.compile(r'[ \t]+(.*)$', re.S) 
     443 
     444    def parse(self): 
     445        self.macro_name, = self.identity_match(self.START) 
     446        self.macro_name = self.macro_name.lower() 
     447        self.args = [] 
     448        if self.macro_name in MacroDefinition.RESERVED_NAMES or self.macro_name.startswith('end'): 
     449            raise NoMatch() 
     450        self.require_match(self.OPEN_PAREN, '(') 
     451        while True: 
     452            try: self.args.append(self.next_element(Value)) 
     453            except NoMatch: break 
     454            if not self.optional_match(self.SPACE): break 
     455        self.require_match(self.CLOSE_PAREN, 'argument value or )') 
     456 
     457    def evaluate(self, namespace, stream): 
     458        try: macro = namespace['#' + self.macro_name] 
     459        except KeyError: raise Exception('no such macro: ' + self.macro_name) 
     460        macro.execute_macro(namespace, stream, self.args) 
     461 
     462 
    394463class SetDirective(_Element): 
    395464    START = re.compile(r'#set\b(.*)', re.S + re.I) 
     
    441510        self.children = [] 
    442511        while True: 
    443             try: 
    444                 self.children.append(self.next_element((Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective))) 
    445             except NoMatch: 
    446                 break 
     512            try: self.children.append(self.next_element((Text, Placeholder, Comment, IfDirective, SetDirective, ForeachDirective, MacroDefinition, MacroCall))) 
     513            except NoMatch: break 
    447514 
    448515    def evaluate(self, namespace, stream): 
  • trunk/airspeed_test.py

    r22 r24  
    295295        self.assertEquals('yes', template.merge({'value': 1})) 
    296296        self.assertEquals('', template.merge({'value': 2})) 
     297 
     298    def test_cannot_define_macro_to_override_reserved_statements(self): 
     299        for reserved in ('if', 'else', 'elseif', 'set', 'macro', 'foreach', 'parse', 'include', 'stop', 'end'): 
     300            template = airspeed.Template('#macro ( %s $value) $value #end' % reserved) 
     301            self.assertRaises(airspeed.TemplateSyntaxError, template.merge, {}) 
     302 
     303    def test_cannot_call_undefined_macro(self): 
     304        template = airspeed.Template('#undefined()') 
     305        self.assertRaises(Exception, template.merge, {}) 
     306 
     307    def test_define_and_use_macro_with_no_parameters(self): 
     308        template = airspeed.Template('#macro ( hello)hi#end#hello ()#hello()') 
     309        self.assertEquals('hihi', template.merge({'text': 'hello'})) 
     310 
     311    def test_define_and_use_macro_with_one_parameter(self): 
     312        template = airspeed.Template('#macro ( bold $value)<strong>$value</strong>#end#bold ($text)') 
     313        self.assertEquals('<strong>hello</strong>', template.merge({'text': 'hello'})) 
     314 
     315    def test_use_of_macro_name_is_case_insensitive(self): 
     316        template = airspeed.Template('#macro ( bold $value)<strong>$value</strong>#end#BoLd ($text)') 
     317        self.assertEquals('<strong>hello</strong>', template.merge({'text': 'hello'})) 
     318 
     319    def test_define_and_use_macro_with_two_parameter(self): 
     320        template = airspeed.Template('#macro (addition $value1 $value2 )$value1+$value2#end#addition (1 2)') 
     321        self.assertEquals('1+2', template.merge({})) 
     322        template = airspeed.Template('#macro (addition $value1 $value2 )$value1+$value2#end#addition( $one   $two )') 
     323        self.assertEquals('ONE+TWO', template.merge({'one': 'ONE', 'two': 'TWO'})) 
     324 
     325    def test_cannot_redefine_macro(self): 
     326        template = airspeed.Template('#macro ( hello)hi#end#macro(hello)again#end') 
     327        self.assertRaises(Exception, template.merge, {}) ## Should this be TemplateSyntaxError? 
     328 
    297329# 
    298330# TODO: 
    299331# 
    300 #  Comparative operators >=, ==, !=, <=, ! 
    301332#  Math expressions 
    302333#  Gobbling up whitespace (tricky!) 
     
    304335#  list literals 
    305336#  #parse, #include 
    306 #  #macro 
     337#  Bind #macro calls at compile time? 
     338#  Interpolated strings 
    307339#  Directives inside string literals 
    308340#  map literals