def test_index_scope_ok(self): self.ast_description = """ file: TestTemplate #def test_function #set $foo = {} #if True #set $foo[1] = 1 #end if #end def """ ast_root, function_node = self._build_function_template() assign_node1 = ast.AssignNode( ast.IdentifierNode('foo'), ast.DictLiteralNode()) function_node.append(assign_node1) if_node = ast.IfNode(ast.LiteralNode(True)) assign_node2 = ast.AssignNode( ast.SliceNode( ast.IdentifierNode('foo'), ast.LiteralNode(1)), ast.LiteralNode(1)) if_node.append(assign_node2) function_node.append(if_node) optimization_analyzer = self._get_analyzer(ast_root) try: optimization_analyzer.visit_ast(ast_root) except analyzer.SemanticAnalyzerError: self.fail('visit_ast raised SemanticAnalyzerError unexpectedly.')
def test_partial_nested_else_if(self): self.ast_description = """ file: TestTemplate #def test_function #if True #set $foo = 1 #else #if True #set $foo = 2 #end if #end if $foo #end def """ ast_root, function_node, if_node = self._build_if_template() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) if_node_2 = ast.IfNode(ast.LiteralNode(True)) if_node_2.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(2))) if_node.else_.append(if_node_2) function_node.append(ast.PlaceholderNode('foo')) optimization_analyzer = self._get_analyzer(ast_root) self.assertRaises(analyzer.SemanticAnalyzerError, optimization_analyzer.visit_ast, ast_root)
def test_nested_else(self): self.ast_description = """ file: TestTemplate #def test_function #if True #set $foo = 1 #else #if #set $foo = 2 #else #set $foo = 3 #end if #end if $foo #end def """ ast_root, function_node, if_node = self._build_if_template() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) if_node_2 = ast.IfNode(ast.LiteralNode(True)) if_node_2.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(2))) if_node_2.else_.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(3))) if_node.else_.append(if_node_2) function_node.append(ast.PlaceholderNode('foo')) optimization_analyzer = self._get_analyzer(ast_root) try: optimization_analyzer.visit_ast(ast_root) except analyzer.SemanticAnalyzerError: self.fail('visit_ast raised SemanticAnalyzerError unexpectedly.')
def assign_after_filter_fails(self): self.ast_description = """ file: TestTemplate #def test_function #set $foo = 'foo' $foo #set $foo = 'bar' $foo #end def """ ast_root, function_node = self._build_function_template() first_assign = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode('foo')) function_node.append(first_assign) first_use = ast.FilterNode(ast.IdentifierNode('foo')) function_node.append(first_use) second_assign = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode('bar')) function_node.append(second_assign) second_use = ast.FilterNode(ast.IdentifierNode('foo')) function_node.append(second_use) optimization_analyzer = optimizer.OptimizationAnalyzer( ast_root, self.compiler.analyzer_options, self.compiler) optimization_analyzer.visit_ast = test_util.RecordedFunction( optimization_analyzer.visit_ast) self.assertRaises(compiler.Warning, optimization_analyzer.visit_ast, ast_root)
def double_assign_ok(self): self.ast_description = """ file: TestTemplate #def test_function #set $foo = 'foo' #set $foo = 'bar' $foo #end def """ ast_root, function_node = self._build_function_template() first_assign = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode('foo')) function_node.append(first_assign) second_assign = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode('bar')) function_node.append(second_assign) first_use = ast.FilterNode(ast.IdentifierNode('foo')) function_node.append(first_use) optimization_analyzer = optimizer.OptimizationAnalyzer( ast_root, self.compiler.analyzer_options, self.compiler) optimization_analyzer.visit_ast = test_util.RecordedFunction( optimization_analyzer.visit_ast) try: optimization_analyzer.visit_ast(ast_root) except compiler.Warning: self.fail('visit_ast raised WarningError unexpectedly.')
def test_list_elements_are_optimized(self): self.ast_description = """ Input: [1, 2, 3] """ ast_root = ast.ListLiteralNode('list') ast_root.child_nodes.append(ast.LiteralNode(1)) ast_root.child_nodes.append(ast.LiteralNode(2)) ast_root.child_nodes.append(ast.LiteralNode(3)) optimization_analyzer = self._get_analyzer(ast_root) optimization_analyzer.visit_ast(ast_root) self.assertEqual(len(optimization_analyzer.visit_ast.GetCalls()), 4)
def test_slice_non_identifier_error(self): self.ast_description = """ file: TestTemplate #def test_function #set 1[1] = 1 #end def """ ast_root, def_node = self._build_function_template() assign_node = ast.AssignNode( ast.SliceNode( ast.LiteralNode(1), ast.LiteralNode(1)), ast.LiteralNode(1)) def_node.append(assign_node) semantic_analyzer = self._get_analyzer(ast_root) self.assertRaises(analyzer.SemanticAnalyzerError, semantic_analyzer.get_ast)
def test_index_before_assign_error(self): self.ast_description = """ file: TestTemplate #def test_function #set $foo[1] = 1 #end def """ ast_root, function_node = self._build_function_template() assign_node = ast.AssignNode( ast.SliceNode( ast.IdentifierNode('foo'), ast.LiteralNode(1)), ast.LiteralNode(1)) function_node.append(assign_node) optimization_analyzer = self._get_analyzer(ast_root) self.assertRaises(analyzer.SemanticAnalyzerError, optimization_analyzer.visit_ast, ast_root)
def test_collect_writes_join_if(self): self.ast_description = """ file: TestTemplate #def test_function foo bar #if True #set $foo = 1 #end if baz boo #end def """ ast_root, function_node = self._build_function_template() function_node.append(ast.BufferWrite(ast.LiteralNode('foo'))) function_node.append(ast.BufferWrite(ast.LiteralNode('bar'))) if_node = ast.IfNode() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) function_node.append(if_node) function_node.append(ast.BufferWrite(ast.LiteralNode('baz'))) function_node.append(ast.BufferWrite(ast.LiteralNode('boo'))) optimization_analyzer = self._get_analyzer(ast_root) ast_root, function_node = self._build_function_template() tuple_node = ast.TupleLiteralNode() tuple_node.append(ast.LiteralNode('foo')) tuple_node.append(ast.LiteralNode('bar')) function_node.append(ast.BufferExtend(tuple_node)) if_node = ast.IfNode() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) function_node.append(if_node) tuple_node = ast.TupleLiteralNode() tuple_node.append(ast.LiteralNode('baz')) tuple_node.append(ast.LiteralNode('boo')) function_node.append(ast.BufferExtend(tuple_node)) expected_hash = hash(ast_root) got_hash = hash(optimization_analyzer.optimize_ast()) self.assertEqual(expected_hash, got_hash)
def test_slice_identifier_ok(self): self.ast_description = """ file: TestTemplate #def test_function #set $foo[1] = 1 #end def """ ast_root, def_node = self._build_function_template() assign_node = ast.AssignNode( ast.SliceNode( ast.IdentifierNode('foo'), ast.LiteralNode(1)), ast.LiteralNode(1)) def_node.append(assign_node) semantic_analyzer = self._get_analyzer(ast_root) try: semantic_analyzer.get_ast() except analyzer.SemanticAnalyzerError: self.fail('get_ast raised SemanticAnalyzerError unexpectedly.')
def analyzeTextNode(self, pnode): if pnode.child_nodes: self.compiler.error( SemanticAnalyzerError("ast.TextNode can't have children"), pos=pnode.pos) value = pnode.value if self.options.normalize_whitespace: value = text.normalize_whitespace(value) literal_node = ast.LiteralNode(value, pos=pnode.pos) buffer_write = ast.BufferWrite(literal_node, pos=pnode.pos) return [buffer_write]
def test_empty_elif_fails(self): self.ast_description = """ file: TestTemplate #def test_function #if True #set $foo = True #elif False #end if #end def """ ast_root, def_node, if_node = self._build_if_template() assign_node = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(True)) if_node.append(assign_node) elif_node = ast.IfNode(ast.LiteralNode(False)) if_node.else_.append(elif_node) semantic_analyzer = self._get_analyzer(ast_root) self.assertRaises(analyzer.SemanticAnalyzerError, semantic_analyzer.get_ast)
def _build_if_template(self, condition=None): """ Build a simple template with a function and an if statement. file: TestTemplate #def test_function #if True #end if #end def """ ast_root, function_node = self._build_function_template() condition_node = condition or ast.LiteralNode(True) if_node = ast.IfNode(condition_node) function_node.append(if_node) return (ast_root, function_node, if_node)
def test_collect_writes_join_simple(self): self.ast_description = """ file: TestTemplate #def test_function foo bar #end def """ ast_root, function_node = self._build_function_template() function_node.append(ast.BufferWrite(ast.LiteralNode('foo'))) function_node.append(ast.BufferWrite(ast.LiteralNode('bar'))) optimization_analyzer = self._get_analyzer(ast_root) ast_root, function_node = self._build_function_template() tuple_node = ast.TupleLiteralNode() tuple_node.append(ast.LiteralNode('foo')) tuple_node.append(ast.LiteralNode('bar')) function_node.append(ast.BufferExtend(tuple_node)) expected_hash = hash(ast_root) got_hash = hash(optimization_analyzer.optimize_ast()) self.assertEqual(expected_hash, got_hash)
def test_collect_writes_no_change(self): self.ast_description = """ file: TestTemplate #def test_function foo #end def """ ast_root, function_node = self._build_function_template() function_node.append(ast.BufferWrite(ast.LiteralNode('foo'))) expected_hash = hash(ast_root) optimization_analyzer = self._get_analyzer(ast_root) got_hash = hash(optimization_analyzer.optimize_ast()) self.assertEqual(expected_hash, got_hash)
def test_attr_error(self): self.ast_description = """ file: TestTemplate #implements library #attr $foo = True #def test_function #end def """ ast_root, def_node = self._build_function_template_library() attr_node = ast.AttributeNode('foo', default=ast.LiteralNode(True)) ast_root.insert_before(def_node, attr_node) semantic_analyzer = self._get_analyzer(ast_root) self.assertRaises(analyzer.SemanticAnalyzerError, semantic_analyzer.get_ast)
def test_set_error(self): self.ast_description = """ file: TestTemplate #implements library #set $foo = True #def test_function #end def """ ast_root, def_node = self._build_function_template_library() assign_node = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(True)) ast_root.insert_before(def_node, assign_node) semantic_analyzer = self._get_analyzer(ast_root) self.assertRaises(analyzer.SemanticAnalyzerError, semantic_analyzer.get_ast)
def _build_for_template(self): """ Build a simple template with a function and a for loop. file: TestTemplate #def test_function #for $i in [] #end for #end def """ ast_root, def_node = self._build_function_template() target_list = ast.TargetListNode() target_list.append(ast.PlaceholderNode('foo')) expression_list = ast.ExpressionListNode() expression_list.append(ast.LiteralNode([])) for_node = ast.ForNode(target_list=target_list, expression_list=expression_list) def_node.append(for_node) return (ast_root, def_node, for_node)
def test_non_empty_for_ok(self): self.ast_description = """ file: TestTemplate #def test_function #for $i in [] #set $foo = True #end for #end def """ ast_root, def_node, for_node = self._build_for_template() assign_node = ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(True)) for_node.append(assign_node) semantic_analyzer = self._get_analyzer(ast_root) try: semantic_analyzer.get_ast() except analyzer.SemanticAnalyzerError: self.fail('get_ast raised SemanticAnalyzerError unexpectedly.')
def analyzePlaceholderSubstitutionNode(self, pnode): # print ' '.join(analyzePlaceholderSubstitutionNode', pnode, # pnode.parameter_list.get_arg_map()) node_list = [] ph_expression = self.build_ast(pnode.expression)[0] # If the expression contained a macro that was parsed as a # fragment, the expression is now a statement and can be moved # outside of the ast.PlaceholderSubstitutionNode. # # This is a hack to get around design decisions that were made # early on. It is up to the macro authors to correctly decide how # the macro should be parsed and the compiler should throw errors # if there is an odd state where nodes are somewhere unexpected. if isinstance(ph_expression, ast.statement_nodes): return [ph_expression] arg_map = pnode.parameter_list.get_arg_map() default_format_string = '%s' format_string = arg_map.get('format_string', default_format_string) skip_filter = False cache_forever = False registered_function = False function_has_only_literal_args = False never_cache = False if isinstance(ph_expression, ast.CallFunctionNode): fname = ph_expression.expression.name if self.compiler.registry_contains(fname): function_has_only_literal_args = ( ph_expression.arg_list and not [ _arg for _arg in ph_expression.arg_list if not isinstance(_arg, ast.LiteralNode) ]) skip_filter = self.compiler.get_registry_value( fname, 'skip_filter') skip_unless_baked = self.compiler.get_registry_value( fname, 'skip_filter_unless_baked') skip_filter = skip_filter or (not self.template.baked and skip_unless_baked) cache_forever = self.compiler.get_registry_value( fname, 'cache_forever') never_cache = self.compiler.get_registry_value( fname, 'never_cache') elif ph_expression.library_function: # Don't escape function calls into library templates. skip_filter = True if (self.compiler.enable_filters and format_string == default_format_string and not isinstance(ph_expression, ast.LiteralNode)): arg_node_map = pnode.parameter_list.get_arg_node_map() if 'raw' in arg_map: # If this is a |raw usage and the template does not allow raw, # raise an error. self.uses_raw = True if (self.options.no_raw and not self.template.explicitly_allow_raw): err_msg = ('|raw is not allowed in templates compiled ' 'with the --no-raw flag.') self.compiler.error(SemanticAnalyzerError(err_msg), pos=pnode.pos) else: # if we need to filter, wrap up the node and wait for further # analysis later on if skip_filter: # explicitly set the filter to none here - this means we # will cache expensive pseudo-filtered nodes ph_expression = ast.FilterNode(ph_expression, None, pos=pnode.pos) else: ph_expression = ast.FilterNode( ph_expression, arg_node_map.get('filter', ast.DefaultFilterFunction), pos=pnode.pos) # if this is a literal node, we still might want to filter it # but the output should always be the same - so do it once and # cache FIXME: could fold this and apply the function at # compile-time if (not never_cache and (registered_function and function_has_only_literal_args) or cache_forever or 'cache' in arg_map): cache_expression = ast.CacheNode(ph_expression, pos=pnode.pos) self.template.cached_identifiers.add(cache_expression) node_list.append(cache_expression) ph_expression = ast.IdentifierNode(cache_expression.name, pos=pnode.pos) if isinstance(ph_expression, ast.LiteralNode): buffer_write = ast.BufferWrite(ph_expression, pos=pnode.pos) node_list.append(buffer_write) elif (self.compiler.enable_filters and format_string == default_format_string): # we are already filtering, don't bother creating a new string buffer_write = ast.BufferWrite(ph_expression, pos=pnode.pos) node_list.append(buffer_write) else: buffer_write = ast.BufferWrite(ast.BinOpNode( '%', ast.LiteralNode(format_string, pos=pnode.pos), ph_expression), pos=pnode.pos) node_list.append(buffer_write) return node_list
def test_duplicate_node_collect(self): self.ast_description = """ file: TestTemplate #def test_function foo bar #if True #set $foo = 1 #end if baz boo #if True #set $foo = 1 #end if #end def NOTE: This test will break if collect_writes is written using ast.ASTNode.insert_before. """ ast_root, function_node = self._build_function_template() function_node.append(ast.BufferWrite(ast.LiteralNode('foo'))) function_node.append(ast.BufferWrite(ast.LiteralNode('bar'))) if_node = ast.IfNode() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) function_node.append(if_node) function_node.append(ast.BufferWrite(ast.LiteralNode('baz'))) function_node.append(ast.BufferWrite(ast.LiteralNode('boo'))) if_node = ast.IfNode() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) optimization_analyzer = self._get_analyzer(ast_root) ast_root, function_node = self._build_function_template() tuple_node = ast.TupleLiteralNode() tuple_node.append(ast.LiteralNode('foo')) tuple_node.append(ast.LiteralNode('bar')) function_node.append(ast.BufferExtend(tuple_node)) if_node = ast.IfNode() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) function_node.append(if_node) tuple_node = ast.TupleLiteralNode() tuple_node.append(ast.LiteralNode('baz')) tuple_node.append(ast.LiteralNode('boo')) function_node.append(ast.BufferExtend(tuple_node)) if_node = ast.IfNode() if_node.append(ast.AssignNode( ast.IdentifierNode('foo'), ast.LiteralNode(1))) expected_hash = hash(ast_root) got_hash = hash(optimization_analyzer.optimize_ast()) self.assertEqual(expected_hash, got_hash)
def codegenASTFunctionNode(self, node): name = node.name node.uses_globals = False node.uses_filter_function = False node.uses_private_filter_function = False node.uses_buffer_write = False node.uses_buffer_extend = False self.function_stack.append(node) if node.parameter_list: parameter_list = self.generate_python( self.build_code(node.parameter_list)[0]) else: parameter_list = '' decorator_node = CodeNode('@template_method') # NOTE: for Cheetah compatibility, we have to handle the case where # Cheetah tries to pass a 'transaction' object through. hopefully this # doesn't have some other baggage coming with it. if self.options and self.options.cheetah_compatibility: if parameter_list: code_node = CodeNode( 'def %(name)s(%(parameter_list)s, **kargs):' % vars(), input_pos=node.pos) else: code_node = CodeNode('def %(name)s(**kargs):' % vars(), input_pos=node.pos) else: code_node = CodeNode('def %(name)s(%(parameter_list)s):' % vars(), input_pos=node.pos) needs_globals_added = True child_nodes = node.child_nodes if self.options and self.options.cheetah_compatibility: if_cheetah = CodeNode("if 'trans' in kargs:") code_node.append(if_cheetah) if_cheetah.append(CodeNode("_buffer = kargs['trans'].response()")) else_spitfire = CodeNode('else:') else_spitfire.append(CodeNode('_buffer = self.new_buffer()')) code_node.append(else_spitfire) else: # If the first node is an if statement with no else AND there's no # other statements, generate the if code first. That way, we can # avoid doing extra work when the condition is false. ie, avoid the # overhead of creating a new list setting up useless local variables # and joining all to get an empty string. Disable this in baked mode # until I figure out how to handle this. TODO: Do not perform # sanitization inside of a test_expression. Then we can remove this # baked_mode check. if child_nodes and len(child_nodes) == 1 and not self.baked_mode: if_node = child_nodes[0] if isinstance(if_node, ast.IfNode) and not if_node.else_.child_nodes: child_nodes = if_node.child_nodes # Insert code that does: # if not ($test_expression): # return '' new_if_condition = ast.IfNode( ast.UnaryOpNode('not', if_node.test_expression)) new_if_condition.append(ast.ReturnNode( ast.LiteralNode(''))) new_code = self.build_code(new_if_condition) if node.uses_globals: needs_globals_added = False code_node.append(CodeNode('_globals = globals()')) code_node.extend(new_code) code_node.append(CodeNode('_buffer = self.new_buffer()')) # Save the point where _globals and self_filter_funtion will go if used. # We don't append these here because we have to determine if these two # functions are used in the scope of the current ast.FunctionNode. insertion_point = len(code_node.child_nodes) if self.options and self.options.cheetah_cheats: node.uses_globals = True if_cheetah = CodeNode('if self.search_list:') if_cheetah.append( CodeNode('_self_search_list = self.search_list + [_globals]')) else_cheetah = CodeNode('else:') else_cheetah.append(CodeNode('_self_search_list = [_globals]')) code_node.append(if_cheetah) code_node.append(else_cheetah) for n in child_nodes: code_child_nodes = self.build_code(n) code_node.extend(code_child_nodes) if node.uses_globals and needs_globals_added: code_node.insert(insertion_point, CodeNode('_globals = globals()')) insertion_point += 1 if node.uses_filter_function: code_node.insert( insertion_point, CodeNode('_self_filter_function = self.filter_function')) insertion_point += 1 if node.uses_private_filter_function: code_node.insert( insertion_point, CodeNode( '_self_private_filter_function = self._filter_function')) insertion_point += 1 if node.uses_buffer_write: code_node.insert(insertion_point, CodeNode('_buffer_write = _buffer.write')) insertion_point += 1 if node.uses_buffer_extend: code_node.insert(insertion_point, CodeNode('_buffer_extend = _buffer.extend')) if self.options.cheetah_compatibility: if_cheetah = CodeNode("if 'trans' not in kargs:") if_cheetah.append(CodeNode('return _buffer.getvalue()')) code_node.append(if_cheetah) else: code_node.append(CodeNode('return _buffer.getvalue()')) self.function_stack.pop() return [decorator_node, code_node]