http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/ide-settings/Eclipse/Formatter-profile-FreeMarker.xml
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/ide-settings/Eclipse/Formatter-profile-FreeMarker.xml 
b/freemarker-core/src/ide-settings/Eclipse/Formatter-profile-FreeMarker.xml
new file mode 100644
index 0000000..2070004
--- /dev/null
+++ b/freemarker-core/src/ide-settings/Eclipse/Formatter-profile-FreeMarker.xml
@@ -0,0 +1,313 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<profiles version="12">
+<profile kind="CodeFormatterProfile" name="FreeMarker" version="12">
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_ellipsis" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_declarations"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_annotation_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_allocation_expression"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_at_in_annotation_type_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.new_lines_at_block_boundaries" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_parameters"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_for_parameter" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_package" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_enum_constant"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_imports" 
value="1"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_while" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.insert_new_line_before_root_tags" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_annotation_type_member_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_throws"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_javadoc_comments" 
value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.indentation.size" value="4"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_postfix_operator" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_increments" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_arguments" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_inits" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_anonymous_type_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_for" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.disabling_tag" 
value="@formatter:off"/>
+<setting id="org.eclipse.jdt.core.formatter.continuation_indentation" 
value="2"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_enum_constants" 
value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_imports" 
value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_after_package" 
value="1"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_binary_operator" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_local_declarations"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant" 
value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_parameterized_type_reference"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.indent_root_tags" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.wrap_before_or_operator_multicatch" 
value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.enabling_tag" 
value="@formatter:on"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_brace_in_block" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_return"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_parameter"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_then_statement_on_same_line" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_field" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_explicitconstructorcall_arguments"
 value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_block" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_prefix_operator" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.blank_lines_between_type_declarations" 
value="1"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_brace_in_array_initializer"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_for" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_catch" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_arguments"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_method" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_switch" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_anonymous_type_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_parenthesized_expression"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.never_indent_line_comments_on_first_column" 
value="false"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.enumIdentifier" 
value="error"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_and_in_type_parameter" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_for_inits" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_block" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_anonymous_type_declaration"
 value="end_of_line"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_wildcard" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_invocation_arguments"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_switch" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.line_length" value="120"/>
+<setting id="org.eclipse.jdt.core.formatter.use_on_off_tags" value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_brackets_in_array_allocation_expression"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_constant"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_invocation"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_assignment_operator" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_type_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_for" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.preserve_white_space_between_code_and_line_comments"
 value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_local_variable"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_method_declaration" 
value="end_of_line"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_invocation"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_union_type_in_multicatch" 
value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_for" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.number_of_blank_lines_at_beginning_of_method_body"
 value="0"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_arguments"
 value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_else_statement_on_same_line" 
value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_binary_expression" 
value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_parameterized_type_reference"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_array_initializer"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_multiple_field_declarations"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_annotation" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_annotation_declaration_header"
 value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_superinterfaces" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_default" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_conditional" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block" 
value="end_of_line"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_constructor_declaration" 
value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_lambda_body" 
value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.compact_else_if" value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_parameters"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_catch" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_invocation"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.put_empty_statement_on_new_line" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_invocation_arguments"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration"
 value="16"/>
+<setting id="org.eclipse.jdt.core.compiler.problem.assertIdentifier" 
value="error"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_block_comment" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_catch_in_try_statement"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_try" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_at_end_of_file_if_missing" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.clear_blank_lines_in_javadoc_comment"
 value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_array_initializer"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_binary_operator" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_unary_operator" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.format_line_comment_starting_on_first_column"
 value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.number_of_empty_lines_to_preserve" 
value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_case" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_ellipsis" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_try_resources"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_assert" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_if" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_type_arguments" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_and_in_type_parameter" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_type_declaration" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_parenthesized_expression"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_line_comments" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_labeled_statement"
 value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.align_type_members_on_columns" 
value="false"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_assignment" 
value="0"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_method_body" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_type_header"
 value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_method_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_enum_constant"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration"
 value="0"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_conditional_expression" 
value="80"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_closing_brace_in_array_initializer"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_parameters"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.format_guardian_clause_on_one_line" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_if" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_annotation_on_type" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_block" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_enum_declaration" 
value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_block_in_case" 
value="end_of_line"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_constructor_declaration"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_header" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_invocation"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_while" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode" 
value="enabled"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_switch" 
value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_method_declaration" 
value="0"/>
+<setting id="org.eclipse.jdt.core.formatter.join_wrapped_lines" value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_parens_in_constructor_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_cases" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_allocation_expression"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_synchronized"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.new_lines_at_javadoc_boundaries" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_annotation_type_declaration"
 value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_for" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_resources_in_try" 
value="80"/>
+<setting 
id="org.eclipse.jdt.core.formatter.use_tabs_only_for_leading_indentations" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation" 
value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.never_indent_block_comments_on_first_column" 
value="false"/>
+<setting id="org.eclipse.jdt.core.compiler.source" value="1.8"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_synchronized"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_constructor_declaration_throws"
 value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.size" value="4"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_constant" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_allocation_expression"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_colon_in_conditional" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_source_code" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_array_initializer"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_try" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_semicolon_in_try_resources"
 value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_field" 
value="0"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.continuation_indentation_for_array_initializer"
 value="2"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_question_in_wildcard" 
value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_method" 
value="1"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_parenthesized_expression_in_throw"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_labeled_statement"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.codegen.targetPlatform" 
value="1.8"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_switch" 
value="end_of_line"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_superinterfaces"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_parameters"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_type_annotation" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_brace_in_array_initializer"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_parenthesized_expression"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_html" value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_at_in_annotation_type_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_angle_bracket_in_type_parameters"
 value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_compact_if" 
value="16"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_empty_lines" value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_parameterized_type_reference"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_unary_operator" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_enum_constant"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation" 
value="0"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_declarations"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.keep_empty_array_initializer_on_one_line" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_switchstatements_compare_to_switch" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_else_in_if_statement" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_assignment_operator" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_constructor_declaration"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk" 
value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_new_line_after_label" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_declaration_header"
 value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_bracket_in_array_allocation_expression"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_constructor_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_conditional" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_parameterized_type_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_method_declaration_parameters"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_arguments"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_cast" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_assert" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_member_type" 
value="1"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_while_in_do_statement"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_type_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_parameterized_type_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_after_opening_brace_in_array_initializer"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_in_empty_enum_declaration" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_breaks_compare_to_cases" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_method_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_if" 
value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_postfix_operator" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_try" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_arguments"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_cast" 
value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.comment.format_block_comments" 
value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_lambda_arrow" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_method_declaration"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.keep_imple_if_on_one_line" 
value="false"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_enum_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration"
 value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_brackets_in_array_type_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_angle_bracket_in_type_parameters"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_semicolon_in_for" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_method_declaration_throws"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_allocation_expression"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.indent_statements_compare_to_body" 
value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.alignment_for_multiple_fields" 
value="16"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_enum_constant_arguments"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_prefix_operator" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_array_initializer" 
value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.wrap_before_binary_operator" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_method_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_type_parameters" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_catch" 
value="do not insert"/>
+<setting id="org.eclipse.jdt.core.compiler.compliance" value="1.8"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_bracket_in_array_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_comma_in_annotation" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_enum_constant_arguments"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_between_empty_braces_in_array_initializer"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_before_colon_in_case" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_local_declarations"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_annotation_type_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_bracket_in_array_reference"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_method_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.wrap_outer_expressions_when_nested" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_closing_paren_in_cast" 
value="insert"/>
+<setting id="org.eclipse.jdt.core.formatter.brace_position_for_enum_constant" 
value="end_of_line"/>
+<setting 
id="org.eclipse.jdt.core.formatter.brace_position_for_type_declaration" 
value="end_of_line"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_before_package" 
value="0"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_for" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_synchronized"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_for_increments" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_annotation_type_member_declaration"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_while" 
value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_enum_constant"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_explicitconstructorcall_arguments"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_paren_in_annotation"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_angle_bracket_in_type_parameters"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.indent_body_declarations_compare_to_enum_constant_header"
 value="true"/>
+<setting id="org.eclipse.jdt.core.formatter.insert_space_after_lambda_arrow" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_brace_in_constructor_declaration"
 value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_constructor_declaration_throws"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.join_lines_in_comments" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_closing_angle_bracket_in_type_parameters"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_question_in_conditional" 
value="insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.comment.indent_parameter_description" 
value="true"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_new_line_before_finally_in_try_statement"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.tabulation.char" value="space"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_comma_in_multiple_field_declarations"
 value="do not insert"/>
+<setting id="org.eclipse.jdt.core.formatter.blank_lines_between_import_groups" 
value="1"/>
+<setting id="org.eclipse.jdt.core.formatter.lineSplit" value="120"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_after_opening_paren_in_annotation"
 value="do not insert"/>
+<setting 
id="org.eclipse.jdt.core.formatter.insert_space_before_opening_paren_in_switch" 
value="insert"/>
+</profile>
+</profiles>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
 
b/freemarker-core/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
new file mode 100644
index 0000000..4b40e57
--- /dev/null
+++ 
b/freemarker-core/src/ide-settings/IntelliJ-IDEA/Editor-Inspections-FreeMarker.xml
@@ -0,0 +1,33 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="FreeMarker" />
+    <inspection_tool class="LoggerInitializedWithForeignClass" enabled="false" 
level="WARNING" enabled_by_default="false">
+      <option name="loggerClassName" 
value="org.apache.log4j.Logger,org.slf4j.LoggerFactory,org.apache.commons.logging.LogFactory,java.util.logging.Logger"
 />
+      <option name="loggerFactoryMethodName" 
value="getLogger,getLogger,getLog,getLogger" />
+    </inspection_tool>
+    <inspection_tool class="MissingOverrideAnnotation" enabled="true" 
level="WARNING" enabled_by_default="true">
+      <option name="ignoreObjectMethods" value="true" />
+      <option name="ignoreAnonymousClassMethods" value="false" />
+    </inspection_tool>
+    <inspection_tool class="RawTypeCanBeGeneric" enabled="true" 
level="WARNING" enabled_by_default="true" />
+    <inspection_tool class="RawUseOfParameterizedType" enabled="true" 
level="WARNING" enabled_by_default="true" />
+  </profile>
+</component>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml 
b/freemarker-core/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
new file mode 100644
index 0000000..983f742
--- /dev/null
+++ 
b/freemarker-core/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
@@ -0,0 +1,66 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<code_scheme name="FreeMarker">
+  <option name="LINE_SEPARATOR" value="&#xA;" />
+  <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
+  <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="1" />
+  <option name="IMPORT_LAYOUT_TABLE">
+    <value>
+      <package name="" withSubpackages="true" static="true" />
+      <emptyLine />
+      <package name="java" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="javax" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="org" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="com" withSubpackages="true" static="false" />
+      <emptyLine />
+      <package name="" withSubpackages="true" static="false" />
+      <emptyLine />
+    </value>
+  </option>
+  <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
+  <option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" />
+  <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" />
+  <option name="JD_KEEP_EMPTY_PARAMETER" value="false" />
+  <option name="JD_KEEP_EMPTY_EXCEPTION" value="false" />
+  <option name="JD_KEEP_EMPTY_RETURN" value="false" />
+  <option name="JD_PARAM_DESCRIPTION_ON_NEW_LINE" value="true" />
+  <option name="WRAP_COMMENTS" value="true" />
+  <JavaCodeStyleSettings>
+    <option name="ANNOTATION_PARAMETER_WRAP" value="1" />
+    <option name="CLASS_NAMES_IN_JAVADOC" value="3" />
+  </JavaCodeStyleSettings>
+  <codeStyleSettings language="JAVA">
+    <option name="RIGHT_MARGIN" value="120" />
+    <option name="CALL_PARAMETERS_WRAP" value="1" />
+    <option name="EXTENDS_LIST_WRAP" value="1" />
+    <option name="THROWS_LIST_WRAP" value="1" />
+    <option name="EXTENDS_KEYWORD_WRAP" value="1" />
+    <option name="BINARY_OPERATION_WRAP" value="1" />
+    <option name="TERNARY_OPERATION_WRAP" value="1" />
+    <option name="TERNARY_OPERATION_SIGNS_ON_NEXT_LINE" value="true" />
+    <option name="ARRAY_INITIALIZER_WRAP" value="1" />
+    <option name="ASSIGNMENT_WRAP" value="1" />
+    <option name="WRAP_LONG_LINES" value="true" />
+    <option name="PARAMETER_ANNOTATION_WRAP" value="1" />
+    <option name="VARIABLE_ANNOTATION_WRAP" value="1" />
+  </codeStyleSettings>
+</code_scheme>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/APINotSupportedTemplateException.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/APINotSupportedTemplateException.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/APINotSupportedTemplateException.java
new file mode 100644
index 0000000..732f5e2
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/APINotSupportedTemplateException.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Thrown when {@code ?api} is not supported by a value.
+ */
+class APINotSupportedTemplateException extends TemplateException {
+
+    APINotSupportedTemplateException(Environment env, ASTExpression 
blamedExpr, TemplateModel model) {
+        super(null, env, blamedExpr, buildDescription(env, blamedExpr, model));
+    }
+
+    protected static _ErrorDescriptionBuilder buildDescription(Environment 
env, ASTExpression blamedExpr,
+            TemplateModel tm) {
+        final _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "The value doesn't support ?api. See requirements in the 
FreeMarker Manual. ("
+                + "FTL type: ", new _DelayedFTLTypeDescription(tm),
+                ", TemplateModel class: ", new 
_DelayedShortClassName(tm.getClass()),
+                ", ObjectWapper: ", new 
_DelayedToString(env.getObjectWrapper()), ")"
+        ).blame(blamedExpr);
+
+        if (blamedExpr.isLiteral()) {
+            desc.tip("Only adapted Java objects can possibly have API, not 
values created inside templates.");
+        }
+
+        return desc;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
new file mode 100644
index 0000000..7ee2695
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTComment.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST comment node
+ */
+final class ASTComment extends ASTElement {
+
+    private final String text;
+
+    ASTComment(String text) {
+        this.text = text;
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) {
+        // do nothing, skip the body
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            return "<#--" + text + "-->";
+        } else {
+            return "comment " + _StringUtil.jQuote(text.trim());
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#--...--";
+    }
+    
+
+    @Override
+    int getParameterCount() {
+        return 1;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        if (idx != 0) throw new IndexOutOfBoundsException();
+        return text;
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        if (idx != 0) throw new IndexOutOfBoundsException();
+        return ParameterRole.CONTENT;
+    }
+
+    public String getText() {
+        return text;
+    }
+
+    @Override
+    boolean isOutputCacheable() {
+        return true;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
new file mode 100644
index 0000000..fe42f41
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDebugBreak.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.debug._DebuggerService;
+
+/**
+ * AST node: A debug breakpoint
+ */
+class ASTDebugBreak extends ASTElement {
+    public ASTDebugBreak(ASTElement nestedBlock) {
+        addChild(nestedBlock);
+        copyLocationFrom(nestedBlock);
+    }
+    
+    @Override
+    protected ASTElement[] accept(Environment env) throws TemplateException, 
IOException {
+        if (!_DebuggerService.suspendEnvironment(
+                env, getTemplate().getSourceName(), 
getChild(0).getBeginLine())) {
+            return getChild(0).accept(env);
+        } else {
+            throw new StopException(env, "Stopped by debugger");
+        }
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            StringBuilder sb = new StringBuilder();
+            sb.append("<#-- ");
+            sb.append("debug break");
+            if (getChildCount() == 0) {
+                sb.append(" /-->");
+            } else {
+                sb.append(" -->");
+                sb.append(getChild(0).getCanonicalForm());                
+                sb.append("<#--/ debug break -->");
+            }
+            return sb.toString();
+        } else {
+            return "debug break";
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#debug_break";
+    }
+
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+        
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
new file mode 100644
index 0000000..4961f7f
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignment.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * AST directive node: An instruction that makes a single assignment, like 
{@code <#local x=1>}, {@code <#global x=1>},
+ * {@code <#assign x=1>}.
+ * This is also used as the child of {@link ASTDirAssignmentsContainer}, if 
there are multiple assignments in the same
+ * tag, like in {@code <#local x=1 y=2>}.
+ */
+final class ASTDirAssignment extends ASTDirective {
+
+    // These must not clash with ArithmeticExpression.TYPE_... constants: 
+    private static final int OPERATOR_TYPE_EQUALS = 0x10000;
+    private static final int OPERATOR_TYPE_PLUS_EQUALS = 0x10001;
+    private static final int OPERATOR_TYPE_PLUS_PLUS = 0x10002;
+    private static final int OPERATOR_TYPE_MINUS_MINUS = 0x10003;
+    
+    private final int/*enum*/ scope;
+    private final String variableName;
+    private final int operatorType;
+    private final ASTExpression valueExp;
+    private ASTExpression namespaceExp;
+
+    static final int NAMESPACE = 1;
+    static final int LOCAL = 2;
+    static final int GLOBAL = 3;
+    
+    private static final Number ONE = Integer.valueOf(1);
+
+    /**
+     * @param variableName the variable name to assign to.
+     * @param valueExp the expression to assign.
+     * @param scope the scope of the assignment, one of NAMESPACE, LOCAL, or 
GLOBAL
+     */
+    ASTDirAssignment(String variableName,
+            int operator,
+            ASTExpression valueExp,
+            int scope) {
+        this.scope = scope;
+        
+        this.variableName = variableName;
+        
+        if (operator == FMParserConstants.EQUALS) {
+            operatorType = OPERATOR_TYPE_EQUALS;
+        } else {
+            switch (operator) {
+            case FMParserConstants.PLUS_PLUS:
+                operatorType = OPERATOR_TYPE_PLUS_PLUS;
+                break;
+            case FMParserConstants.MINUS_MINUS:
+                operatorType = OPERATOR_TYPE_MINUS_MINUS;
+                break;
+            case FMParserConstants.PLUS_EQUALS:
+                operatorType = OPERATOR_TYPE_PLUS_EQUALS;
+                break;
+            case FMParserConstants.MINUS_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_SUBSTRACTION;
+                break;
+            case FMParserConstants.TIMES_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_MULTIPLICATION;
+                break;
+            case FMParserConstants.DIV_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_DIVISION;
+                break;
+            case FMParserConstants.MOD_EQUALS:
+                operatorType = ArithmeticExpression.TYPE_MODULO;
+                break;
+            default:
+                throw new BugException();
+            }
+        }
+        
+        this.valueExp = valueExp;
+    }
+    
+    void setNamespaceExp(ASTExpression namespaceExp) {
+        if (scope != NAMESPACE && namespaceExp != null) throw new 
BugException();
+        this.namespaceExp =  namespaceExp;
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException {
+        final Environment.Namespace namespace;
+        if (namespaceExp == null) {
+            switch (scope) {
+            case LOCAL:
+                namespace = null;
+                break;
+            case GLOBAL:
+                namespace = env.getGlobalNamespace();
+                break;
+            case NAMESPACE:
+                namespace = env.getCurrentNamespace();
+                break;
+            default:
+                throw new BugException("Unexpected scope type: " + scope);
+            }
+        } else {
+            TemplateModel namespaceTM = namespaceExp.eval(env);
+            try {
+                namespace = (Environment.Namespace) namespaceTM;
+            } catch (ClassCastException e) {
+                throw new NonNamespaceException(namespaceExp, namespaceTM, 
env);
+            }
+            if (namespace == null) {
+                throw InvalidReferenceException.getInstance(namespaceExp, env);
+            }
+        }
+        
+        TemplateModel value;
+        if (operatorType == OPERATOR_TYPE_EQUALS) {
+            value = valueExp.eval(env);
+            valueExp.assertNonNull(value, env);
+        } else {
+            TemplateModel lhoValue;
+            if (namespace == null) {
+                lhoValue = env.getLocalVariable(variableName);
+            } else {
+                lhoValue = namespace.get(variableName);
+            }
+            
+            if (operatorType == OPERATOR_TYPE_PLUS_EQUALS) {  // Add or concat 
operation
+                if (lhoValue == null) {
+                    throw InvalidReferenceException.getInstance(
+                            variableName, getOperatorTypeAsString(), env);
+                }
+                
+                value = valueExp.eval(env);
+                valueExp.assertNonNull(value, env);
+                value = ASTExpAddOrConcat._eval(env, namespaceExp, null, 
lhoValue, valueExp, value);
+            } else {  // Numerical operation
+                Number lhoNumber;
+                if (lhoValue instanceof TemplateNumberModel) {
+                    lhoNumber = _EvalUtil.modelToNumber((TemplateNumberModel) 
lhoValue, null);
+                } else if (lhoValue == null) {
+                    throw InvalidReferenceException.getInstance(variableName, 
getOperatorTypeAsString(), env);
+                } else {
+                    throw new NonNumericalException(variableName, lhoValue, 
null, env);
+                }
+
+                if (operatorType == OPERATOR_TYPE_PLUS_PLUS) {
+                    value  = ASTExpAddOrConcat._evalOnNumbers(env, 
getParent(), lhoNumber, ONE);
+                } else if (operatorType == OPERATOR_TYPE_MINUS_MINUS) {
+                    value = ArithmeticExpression._eval(
+                            env, getParent(), lhoNumber, 
ArithmeticExpression.TYPE_SUBSTRACTION, ONE);
+                } else { // operatorType == ArithmeticExpression.TYPE_...
+                    Number rhoNumber = valueExp.evalToNumber(env);
+                    value = ArithmeticExpression._eval(env, this, lhoNumber, 
operatorType, rhoNumber);
+                }
+            }
+        }
+        
+        if (namespace == null) {
+            env.setLocalVariable(variableName, value);
+        } else {
+            namespace.put(variableName, value);
+        }
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        String dn = getParent() instanceof ASTDirAssignmentsContainer ? null : 
getNodeTypeSymbol();
+        if (dn != null) {
+            if (canonical) buf.append("<");
+            buf.append(dn);
+            buf.append(' ');
+        }
+        
+        buf.append(_StringUtil.toFTLTopLevelTragetIdentifier(variableName));
+        
+        if (valueExp != null) {
+            buf.append(' ');
+        }
+        buf.append(getOperatorTypeAsString());
+        if (valueExp != null) {
+            buf.append(' ');
+            buf.append(valueExp.getCanonicalForm());
+        }
+        if (dn != null) {
+            if (namespaceExp != null) {
+                buf.append(" in ");
+                buf.append(namespaceExp.getCanonicalForm());
+            }
+            if (canonical) buf.append(">");
+        }
+        return buf.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return getDirectiveName(scope);
+    }
+    
+    static String getDirectiveName(int scope) {
+        if (scope == ASTDirAssignment.LOCAL) {
+            return "#local";
+        } else if (scope == ASTDirAssignment.GLOBAL) {
+            return "#global";
+        } else if (scope == ASTDirAssignment.NAMESPACE) {
+            return "#assign";
+        } else {
+            return "#{unknown_assignment_type}";
+        }
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 5;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return variableName;
+        case 1: return getOperatorTypeAsString();
+        case 2: return valueExp;
+        case 3: return Integer.valueOf(scope);
+        case 4: return namespaceExp;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.ASSIGNMENT_TARGET;
+        case 1: return ParameterRole.ASSIGNMENT_OPERATOR;
+        case 2: return ParameterRole.ASSIGNMENT_SOURCE;
+        case 3: return ParameterRole.VARIABLE_SCOPE;
+        case 4: return ParameterRole.NAMESPACE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+    private String getOperatorTypeAsString() {
+        if (operatorType == OPERATOR_TYPE_EQUALS) {
+            return "=";
+        } else if (operatorType == OPERATOR_TYPE_PLUS_EQUALS) {
+            return "+=";
+        } else if (operatorType == OPERATOR_TYPE_PLUS_PLUS) {
+            return "++";
+        } else if (operatorType == OPERATOR_TYPE_MINUS_MINUS) {
+            return "--";
+        } else {
+            return ArithmeticExpression.getOperatorSymbol(operatorType) + "=";
+        }
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
new file mode 100644
index 0000000..fd2873d
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAssignmentsContainer.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+/**
+ * AST directive node: An instruction that does multiple assignments, like 
[#local x=1 x=2].
+ * Each assignment is represented by a {@link ASTDirAssignment} child element.
+ * If there's only one assignment, its usually just a {@link ASTDirAssignment} 
without parent {@link ASTDirAssignmentsContainer}.
+ */
+final class ASTDirAssignmentsContainer extends ASTDirective {
+
+    private int scope;
+    private ASTExpression namespaceExp;
+
+    ASTDirAssignmentsContainer(int scope) {
+        this.scope = scope;
+        setChildBufferCapacity(1);
+    }
+
+    void addAssignment(ASTDirAssignment assignment) {
+        addChild(assignment);
+    }
+    
+    void setNamespaceExp(ASTExpression namespaceExp) {
+        this.namespaceExp = namespaceExp;
+        int ln = getChildCount();
+        for (int i = 0; i < ln; i++) {
+            ((ASTDirAssignment) getChild(i)).setNamespaceExp(namespaceExp);
+        }
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException 
{
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder buf = new StringBuilder();
+        if (canonical) buf.append('<');
+        buf.append(ASTDirAssignment.getDirectiveName(scope));
+        if (canonical) {
+            buf.append(' ');
+            int ln = getChildCount();
+            for (int i = 0; i < ln; i++) {
+                if (i != 0) {
+                    buf.append(", ");
+                }
+                ASTDirAssignment assignment = (ASTDirAssignment) getChild(i);
+                buf.append(assignment.getCanonicalForm());
+            }
+        } else {
+            buf.append("-container");
+        }
+        if (namespaceExp != null) {
+            buf.append(" in ");
+            buf.append(namespaceExp.getCanonicalForm());
+        }
+        if (canonical) buf.append(">");
+        return buf.toString();
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return Integer.valueOf(scope);
+        case 1: return namespaceExp;
+        default: return null;
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.VARIABLE_SCOPE;
+        case 1: return ParameterRole.NAMESPACE;
+        default: return null;
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return ASTDirAssignment.getDirectiveName(scope);
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
new file mode 100644
index 0000000..c01c453
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAttemptRecoverContainer.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+/**
+ * AST directive node: Holder for the attempted section of the {@code 
#attempt} element and of the nested
+ * {@code #recover} element ({@link ASTDirRecover}).
+ */
+final class ASTDirAttemptRecoverContainer extends ASTDirective {
+    
+    private ASTElement attemptedSection;
+    private ASTDirRecover recoverySection;
+    
+    ASTDirAttemptRecoverContainer(TemplateElements attemptedSectionChildren, 
ASTDirRecover recoverySection) {
+        ASTElement attemptedSection = 
attemptedSectionChildren.asSingleElement();
+        this.attemptedSection = attemptedSection;
+        this.recoverySection = recoverySection;
+        setChildBufferCapacity(2);
+        addChild(attemptedSection); // for backward compatibility
+        addChild(recoverySection);
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException 
{
+        env.visitAttemptRecover(this, attemptedSection, recoverySection);
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (!canonical) {
+            return getNodeTypeSymbol();
+        } else {
+            StringBuilder buf = new StringBuilder();
+            buf.append("<").append(getNodeTypeSymbol()).append(">");
+            buf.append(getChildrenCanonicalForm());            
+            buf.append("</").append(getNodeTypeSymbol()).append(">");
+            return buf.toString();
+        }
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 1;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        if (idx != 0) throw new IndexOutOfBoundsException();
+        return recoverySection;
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        if (idx != 0) throw new IndexOutOfBoundsException();
+        return ParameterRole.ERROR_HANDLER;
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#attempt";
+    }
+    
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
new file mode 100644
index 0000000..b27dc0a
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirAutoEsc.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+/**
+ * AST directive node: {@code #autoEsc}
+ */
+final class ASTDirAutoEsc extends ASTDirective {
+    
+    ASTDirAutoEsc(TemplateElements children) { 
+        setChildren(children);
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException 
{
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            return "<" + getNodeTypeSymbol() + "\">" + 
getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#autoesc";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
new file mode 100644
index 0000000..19a0c25
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirBreak.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+/**
+ * AST directive node: {@code #break}
+ */
+final class ASTDirBreak extends ASTDirective {
+
+    @Override
+    ASTElement[] accept(Environment env) {
+        throw Break.INSTANCE;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        return canonical ? "<" + getNodeTypeSymbol() + "/>" : 
getNodeTypeSymbol();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#break";
+    }
+
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+    
+    static class Break extends RuntimeException {
+        static final Break INSTANCE = new Break();
+        private Break() {
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}
+
+

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
new file mode 100644
index 0000000..9aa5eab
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCapturingAssignment.java
@@ -0,0 +1,184 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.util.Map;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateTransformModel;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+
+/**
+ * AST directive node: Like {@code <#local x>...</#local>}.
+ */
+final class ASTDirCapturingAssignment extends ASTDirective {
+
+    private final String varName;
+    private final ASTExpression namespaceExp;
+    private final int scope;
+    private final MarkupOutputFormat<?> markupOutputFormat;
+
+    ASTDirCapturingAssignment(TemplateElements children, String varName, int 
scope, ASTExpression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
+        setChildren(children);
+        this.varName = varName;
+        this.namespaceExp = namespaceExp;
+        this.scope = scope;
+        this.markupOutputFormat = markupOutputFormat;
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException 
{
+        ASTElement[] children = getChildBuffer();
+        if (children != null) {
+            env.visitAndTransform(children, new CaptureOutput(env), null);
+        } else {
+            TemplateModel value = capturedStringToModel("");
+            if (namespaceExp != null) {
+                Environment.Namespace ns = (Environment.Namespace) 
namespaceExp.eval(env);
+                ns.put(varName, value);
+            } else if (scope == ASTDirAssignment.NAMESPACE) {
+                env.setVariable(varName, value);
+            } else if (scope == ASTDirAssignment.GLOBAL) {
+                env.setGlobalVariable(varName, value);
+            } else if (scope == ASTDirAssignment.LOCAL) {
+                env.setLocalVariable(varName, value);
+            }
+        }
+        return null;
+    }
+
+    private TemplateModel capturedStringToModel(String s) throws 
TemplateModelException {
+        return markupOutputFormat == null ? new SimpleScalar(s) : 
markupOutputFormat.fromMarkup(s);
+    }
+
+    private class CaptureOutput implements TemplateTransformModel {
+        private final Environment env;
+        private final Environment.Namespace fnsModel;
+        
+        CaptureOutput(Environment env) throws TemplateException {
+            this.env = env;
+            TemplateModel nsModel = null;
+            if (namespaceExp != null) {
+                nsModel = namespaceExp.eval(env);
+                if (!(nsModel instanceof Environment.Namespace)) {
+                    throw new NonNamespaceException(namespaceExp, nsModel, 
env);
+                }
+            }
+            fnsModel = (Environment.Namespace ) nsModel; 
+        }
+        
+        @Override
+        public Writer getWriter(Writer out, Map args) {
+            return new StringWriter() {
+                @Override
+                public void close() throws IOException {
+                    TemplateModel result;
+                    try {
+                        result = capturedStringToModel(toString());
+                    } catch (TemplateModelException e) {
+                        // [Java 1.6] e to cause
+                        throw new IOException("Failed to invoke FTL value from 
captured string: " + e);
+                    }
+                    switch(scope) {
+                        case ASTDirAssignment.NAMESPACE: {
+                            if (fnsModel != null) {
+                                fnsModel.put(varName, result);
+                            } else {
+                                env.setVariable(varName, result);
+                            }
+                            break;
+                        }
+                        case ASTDirAssignment.LOCAL: {
+                            env.setLocalVariable(varName, result);
+                            break;
+                        }
+                        case ASTDirAssignment.GLOBAL: {
+                            env.setGlobalVariable(varName, result);
+                            break;
+                        }
+                    }
+                }
+            };
+        }
+    }
+    
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append("<");
+        sb.append(getNodeTypeSymbol());
+        sb.append(' ');
+        sb.append(varName);
+        if (namespaceExp != null) {
+            sb.append(" in ");
+            sb.append(namespaceExp.getCanonicalForm());
+        }
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+            sb.append("</");
+            sb.append(getNodeTypeSymbol());
+            sb.append('>');
+        } else {
+            sb.append(" = .nested_output");
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return ASTDirAssignment.getDirectiveName(scope);
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 3;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return varName;
+        case 1: return Integer.valueOf(scope);
+        case 2: return namespaceExp;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.ASSIGNMENT_TARGET;
+        case 1: return ParameterRole.VARIABLE_SCOPE;
+        case 2: return ParameterRole.NAMESPACE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
new file mode 100644
index 0000000..0f87778
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCase.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+/**
+ * AST directive node: {@code #case} (inside a {@code #switch}) 
+ */
+final class ASTDirCase extends ASTDirective {
+
+    final int TYPE_CASE = 0;
+    final int TYPE_DEFAULT = 1;
+    
+    ASTExpression condition;
+
+    ASTDirCase(ASTExpression matchingValue, TemplateElements children) {
+        condition = matchingValue;
+        setChildren(children);
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) {
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append(getNodeTypeSymbol());
+        if (condition != null) {
+            sb.append(' ');
+            sb.append(condition.getCanonicalForm());
+        }
+        if (canonical) {
+            sb.append('>');
+            sb.append(getChildrenCanonicalForm());
+        }
+        return sb.toString();
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return condition != null ? "#case" : "#default";
+    }
+
+    @Override
+    int getParameterCount() {
+        return 2;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        switch (idx) {
+        case 0: return condition;
+        case 1: return Integer.valueOf(condition != null ? TYPE_CASE : 
TYPE_DEFAULT);
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        switch (idx) {
+        case 0: return ParameterRole.CONDITION;
+        case 1: return ParameterRole.AST_NODE_SUBTYPE;
+        default: throw new IndexOutOfBoundsException();
+        }
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+        
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
new file mode 100644
index 0000000..8810381
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirCompress.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.util.StandardCompress;
+
+/**
+ * AST directive node: {@code #compress}.
+ * An instruction that reduces all sequences of whitespace to a single
+ * space or newline. In addition, leading and trailing whitespace is removed.
+ * 
+ * @see org.apache.freemarker.core.util.StandardCompress
+ */
+final class ASTDirCompress extends ASTDirective {
+
+    ASTDirCompress(TemplateElements children) { 
+        setChildren(children);
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException 
{
+        ASTElement[] childBuffer = getChildBuffer();
+        if (childBuffer != null) {
+            env.visitAndTransform(childBuffer, StandardCompress.INSTANCE, 
null);
+        }
+        return null;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            return "<" + getNodeTypeSymbol() + ">" + 
getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+    
+    @Override
+    String getNodeTypeSymbol() {
+        return "#compress";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isIgnorable(boolean stripWhitespace) {
+        return getChildCount() == 0 && getParameterCount() == 0;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
new file mode 100644
index 0000000..7aafd2f
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirElseOfList.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+
+/**
+ * AST directive node: {@code #else} inside a {@code  #list}.
+ */
+final class ASTDirElseOfList extends ASTDirective {
+    
+    ASTDirElseOfList(TemplateElements children) {
+        setChildren(children);
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException 
{
+        return getChildBuffer();
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        if (canonical) {
+            StringBuilder buf = new StringBuilder();
+            buf.append('<').append(getNodeTypeSymbol()).append('>');
+            buf.append(getChildrenCanonicalForm());            
+            return buf.toString();
+        } else {
+            return getNodeTypeSymbol();
+        }
+    }
+
+    @Override
+    String getNodeTypeSymbol() {
+        return "#else";
+    }
+    
+    @Override
+    int getParameterCount() {
+        return 0;
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        throw new IndexOutOfBoundsException();
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return false;
+    }
+    
+}

Reply via email to