Browse Source

Add skeleton project for AutoBinder tool.
[ci only: Annotate]

Yao Wei Tjong 姚伟忠 10 years ago
parent
commit
ba837af5a4

+ 7 - 4
CMake/Modules/Urho3D-CMake-common.cmake

@@ -104,8 +104,9 @@ if (CMAKE_PROJECT_NAME STREQUAL Urho3D)
     endif ()
     cmake_dependent_option (URHO3D_LUA_RAW_SCRIPT_LOADER "Prefer loading raw script files from the file system before falling back on Urho3D resource cache. Useful for debugging (e.g. breakpoints), but less performant (Lua/LuaJIT only)" ${URHO3D_DEFAULT_LUA_RAW} "URHO3D_LUA OR URHO3D_LUAJIT" FALSE)
     option (URHO3D_SAMPLES "Build sample applications")
+    option (URHO3D_BINDINGS "Enable API binding generation support for script subystems")
     cmake_dependent_option (URHO3D_CLANG_TOOLS "Build Clang tools (native only)" FALSE "NOT RPI AND NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN" FALSE)
-    mark_as_advanced (URHO3D_CLANG_TOOLS)
+    mark_as_advanced (URHO3D_CLANG_TOOLS URHO3D_BINDINGS)
     cmake_dependent_option (URHO3D_TOOLS "Build tools (native and RPI only)" TRUE "NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN" FALSE)
     cmake_dependent_option (URHO3D_EXTRAS "Build extras (native and RPI only)" FALSE "NOT IOS AND NOT ANDROID AND NOT EMSCRIPTEN" FALSE)
     option (URHO3D_DOCS "Generate documentation as part of normal build")
@@ -251,12 +252,14 @@ if (CMAKE_VERSION VERSION_GREATER 2.8 OR CMAKE_VERSION VERSION_EQUAL 2.8)
 endif()
 
 # Clang tools building
-if (URHO3D_CLANG_TOOLS)
+if (URHO3D_CLANG_TOOLS OR URHO3D_BINDINGS)
     # Ensure LLVM/Clang is installed
     find_program (LLVM_CONFIG NAMES llvm-config llvm-config-64 llvm-config-32 HINTS $ENV{LLVM_CLANG_ROOT}/bin DOC "LLVM config tool" NO_CMAKE_FIND_ROOT_PATH)
     if (NOT LLVM_CONFIG)
         message (FATAL_ERROR "Could not find LLVM/Clang installation")
     endif ()
+endif ()
+if (URHO3D_CLANG_TOOLS)
     # Require C++11 standard and no precompiled-header
     set (URHO3D_C++11 1)
     set (URHO3D_PCH 0)
@@ -607,9 +610,9 @@ endmacro ()
 
 # Macro for setting runtime output directories for tools
 macro (set_tool_output_directories)
-    set_output_directories (${CMAKE_BINARY_DIR}/bin/tool RUNTIME PDB)
+    set_output_directories (${CMAKE_BINARY_DIR}/bin/tool/${ARGN} RUNTIME PDB)
     if (DEST_RUNTIME_DIR STREQUAL bin)
-        set (DEST_RUNTIME_DIR bin/tool)
+        set (DEST_RUNTIME_DIR bin/tool/${ARGN})
     endif ()
 endmacro ()
 

+ 2 - 2
Docs/GettingStarted.dox

@@ -282,11 +282,11 @@ If "URHO3D_DOCS" build option is set then a normal build would not only build Ur
 
 The prerequisites are Doxygen and Graphviz. Tools to dump the \ref ScriptAPI "AngelScript API" for the default \ref Script "scripting" subsystem and the \ref LuaScriptAPI "LuaScript API" (when the LuaScript subsystem is also enabled) are being built at the same time when all the native tools are built.
 
-\section Building_Clang_tools Clang-tools build
+\section Building_Clang_tools Clang-tools build (EXPERIMENTAL)
 
 If "URHO3D_CLANG_TOOLS" build option is set then CMake would generate a special build tree for the purpose of developing the Clang-tools. Before doing this, the development software package of LLVM/Clang version 3.7.0 must be already installed in the host system. Alternatively, you can download LLVM/Clang source files from Clang's SVN repo, build and install it into the host system. If it is not installed in a system-wide installation location then use the LLVM_CLANG_ROOT environment variable to point to the root path of this custom location (the root is the parent directory containing the bin, lib, include and share subdirectories). You may want to follow the [Clang's Getting Started instruction](http://clang.llvm.org/get_started.html) to guide you through this. However, it is recommended to checkout "extra Clang tools" from our own Urho3D repo [here](https://github.com/urho3d/clang-tools-extra) instead of Clang's SVN repo as it contains a patch to fix a tool installation issue.
 
-Perhaps the easiest way to get Clang installed is by using Emscripten-SDK. If you have already built Emscripten-SDK in your host system then you can also use the SDK to install the Fastcomp/Clang by navigating to the Fastcomp/Clang build tree (e.g. emsdk_portable/clang/fastcomp/build_master_64) and issuing a 'make install' command. However, Emscripten-SDK does not automatically checkout "extra Clang tools", so before issue a 'make install' you may want to take a step back to manually checkout the "extra Clang tools" into the corresponding subdirectory in the Fastcomp/Clang source tree (e.g. emsdk_portable/clang/fastcomp/src/tools/clang/tools/extra), then build and install as usual. Using Fastcomp/Clang is fine for our purpose because we are only interested in using Clang as 3rd-party library instead of as compiler.
+Perhaps the easiest way to get Clang installed is by using Emscripten-SDK. If you have already built Emscripten-SDK in your host system then you can also use the SDK to install the Fastcomp/Clang by navigating to the Fastcomp/Clang build tree (e.g. emsdk_portable/clang/fastcomp/build_master_64) and issuing a 'make install' command. However, Emscripten-SDK does not automatically checkout "extra Clang tools", so before issuing a 'make install' you may want to take a step back to manually checkout the "extra Clang tools" into the corresponding subdirectory in the Fastcomp/Clang source tree (e.g. emsdk_portable/clang/fastcomp/src/tools/clang/tools/extra), then rebuild and install as usual. Using Fastcomp/Clang is fine for our purpose because we are only interested in using Clang as 3rd-party library instead of as compiler.
 
 A special note to Linux users, install the development software package for 'libedit' when you want full command line interface features like command history and command editing in clang-query tool. Install it before generating any of the LLVM/Clang or Fastcomp/Clang build tree.
 

+ 30 - 0
License.txt

@@ -366,6 +366,36 @@ misrepresented as being the original software.
    Ryan C. Gordon <[email protected]>
 
 
+Mustache license
+----------------
+
+Copyright 2015 Kevin Wojniak
+
+Boost Software License - Version 1.0 - August 17th, 2003
+
+Permission is hereby granted, free of charge, to any person or organization
+obtaining a copy of the software and accompanying documentation covered by
+this license (the "Software") to use, reproduce, display, distribute,
+execute, and transmit the Software, and to prepare derivative works of the
+Software, and to permit third-parties to whom the Software is furnished to
+do so, all subject to the following:
+
+The copyright notices in the Software and this entire statement, including
+the above license grant, this restriction and the following disclaimer,
+must be included in all copies of the Software, in whole or in part, and
+all derivative works of the Software, unless such copies or derivative
+works are solely in the form of machine-executable object code generated by
+a source language processor.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
+
+
 nanodbc license
 ---------------
 

+ 1 - 0
README.md

@@ -111,6 +111,7 @@ Urho3D uses the following third-party libraries:
 - LuaJIT 2.0.3 (http://www.luajit.org)
 - LZ4 (http://code.google.com/p/lz4/)
 - MojoShader (http://icculus.org/mojoshader/)
+- Mustache 1.0 (http://mustache.github.io/, https://github.com/kainjow/Mustache)
 - nanodbc 2.2.2 (http://lexicalunit.github.io/nanodbc/)
 - Open Asset Import Library (http://assimp.sourceforge.net/)
 - pugixml 1.5 (http://pugixml.org/)

+ 2 - 2
Rakefile

@@ -195,7 +195,7 @@ task :android do
 end
 
 # Usage: NOT intended to be used manually
-desc 'Build and run the Annotate tool (temporary and experimental)'
+desc 'Build and run the Annotate tool (temporary)'
 task :ci_annotate do
   system 'rake cmake URHO3D_CLANG_TOOLS=1 && rake make annotate' or abort 'Failed to annotate'
   system "git config user.name $GIT_NAME && git config user.email $GIT_EMAIL && git remote set-url --push origin https://[email protected]/$TRAVIS_REPO_SLUG.git && if git fetch origin annotated:annotated 2>/dev/null; then git push -qf origin --delete annotated; fi && git checkout -B annotated && git stash -q && git reset --hard HEAD~ && git stash pop -q && sed -i \"s/COVERITY_SCAN_THRESHOLD/URHO3D_BINDINGS=1 COVERITY_SCAN_THRESHOLD/g\" .travis.yml && git add -A .travis.yml Source/Urho3D && if git commit -qm 'Result of Annotator tool. [ci only: annotated]'; then git push -q -u origin annotated >/dev/null 2>&1; fi" or abort 'Failed to push annotated branch'
@@ -205,7 +205,7 @@ end
 desc 'Push the generated binding source files to annotated branch (temporary)'
 task :ci_push_bindings do
   abort "Skipped pushing to #{ENV['TRAVIS_BRANCH']} branch due to moving HEAD" unless `git fetch -qf origin #{ENV['TRAVIS_PULL_REQUEST'] == 'false' ? ENV['TRAVIS_BRANCH'] : %Q{+refs/pull/#{ENV['TRAVIS_PULL_REQUEST']}/head'}}; git log -1 --pretty=format:'%H' FETCH_HEAD` == ENV['TRAVIS_COMMIT']
-  system "git config user.name $GIT_NAME && git config user.email $GIT_EMAIL && git remote set-url --push origin https://[email protected]/$TRAVIS_REPO_SLUG.git && git add -A Source/Urho3D && if git commit -qm 'Result of Autobinder tool. [ci skip]'; then git push -q origin HEAD:#{ENV['TRAVIS_BRANCH']} >/dev/null 2>&1; fi" or abort "Failed to push #{ENV['TRAVIS_BRANCH']} branch"
+  system "git config user.name $GIT_NAME && git config user.email $GIT_EMAIL && git remote set-url --push origin https://[email protected]/$TRAVIS_REPO_SLUG.git && git add -A Source/Urho3D && if git commit -qm 'Result of AutoBinder tool. [ci skip]'; then git push -q origin HEAD:#{ENV['TRAVIS_BRANCH']} >/dev/null 2>&1; fi" or abort "Failed to push #{ENV['TRAVIS_BRANCH']} branch"
 end
 
 # Usage: NOT intended to be used manually (if you insist then try: rake ci)

+ 3 - 3
Source/Clang-Tools/Annotator/Annotator.cpp

@@ -33,8 +33,8 @@ using namespace clang::driver;
 using namespace clang::tooling;
 using namespace llvm;
 
-static cl::extrahelp CommonHelp(CommonOptionsParser::HelpMessage);
-static cl::extrahelp MoreHelp(
+static cl::extrahelp commonHelp(CommonOptionsParser::HelpMessage);
+static cl::extrahelp moreHelp(
     "\tFor example, to run Annotator on all files in a subtree of the\n"
     "\tsource tree, use:\n"
     "\n"
@@ -73,7 +73,7 @@ struct Data
     std::unordered_set<std::string> annotatedSymbols_;
 };
 
-static std::vector<std::string> categories_ = {"class", "enum"};
+static const std::string categories_[] = {"class", "enum"};
 static std::unordered_map<std::string, Data> categoryData_;
 
 class ExtractCallback : public MatchFinder::MatchCallback

+ 130 - 0
Source/Clang-Tools/AutoBinder/AutoBinder.cpp

@@ -0,0 +1,130 @@
+//
+// Copyright (c) 2008-2015 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include <clang/ASTMatchers/ASTMatchFinder.h>
+#include <clang/Driver/Options.h>
+#include <clang/Tooling/CommonOptionsParser.h>
+#include <clang/Tooling/Tooling.h>
+
+#include <Mustache/mustache.hpp>
+
+#include <unordered_map>
+#include <unordered_set>
+
+using namespace clang;
+using namespace clang::ast_matchers;
+using namespace clang::driver;
+using namespace clang::tooling;
+using namespace llvm;
+
+static cl::extrahelp commonHelp(CommonOptionsParser::HelpMessage);
+static cl::extrahelp moreHelp(
+    "\tFor example, to run AutoBinder on all files in a subtree of the\n"
+    "\tsource tree, use:\n"
+    "\n"
+    "\t  find path/in/substree -name '*.cpp'|xargs AutoBinder -p build/path \\\n"
+    "\t  -t template-name -o output/path\n"
+    "\n"
+    "\tNote, that path/in/subtree and current directory should follow the\n"
+    "\trules described above.\n"
+    "\n"
+    "Most probably you want to invoke 'autobinder-*' built-in target instead of invoking this tool\n"
+    "directly. The 'autobinder-*' target invokes this tool in a right context prepared by build system.\n"
+    "\n"
+);
+
+static cl::OptionCategory autobinderCategory("AutoBinder options");
+static std::unique_ptr<opt::OptTable> options(createDriverOptTable());
+static cl::opt<std::string> templateName("t", cl::desc("Template name"), cl::cat(autobinderCategory));
+static cl::opt<std::string> outputPath("o", cl::desc("Output path"), cl::cat(autobinderCategory));
+
+struct Data
+{
+    std::unordered_set<std::string> symbols_;
+};
+
+static const std::string categories_[] = {"class", "enum"};
+static std::unordered_map<std::string, Data> categoryData_;
+
+class ExtractCallback : public MatchFinder::MatchCallback
+{
+public :
+    virtual void run(const MatchFinder::MatchResult& result)
+    {
+        for (auto& i: categories_)
+        {
+            auto symbol = result.Nodes.getNodeAs<StringLiteral>(i);
+            if (symbol)
+                categoryData_[i].symbols_.insert(symbol->getString());
+        }
+    }
+
+    virtual void onStartOfTranslationUnit()
+    {
+        static unsigned count = sizeof("Extracting") / sizeof(char) - 1;
+        outs() << '.' << (++count % 100 ? "" : "\n");   // Sending a heart beat
+    }
+};
+
+static int BindingGenerator()
+{
+    // TODO: WIP
+    return EXIT_SUCCESS;
+}
+
+int main(int argc, const char** argv)
+{
+    // Parse the arguments and pass them to the the internal sub-tools
+    CommonOptionsParser optionsParser(argc, argv, autobinderCategory);
+    ClangTool bindingExtractor(optionsParser.getCompilations(), optionsParser.getSourcePathList());
+
+
+    // Setup finder to match against AST nodes from Urho3D library source files
+    ExtractCallback extractCallback;
+    MatchFinder bindingFinder;
+    // Find exported class declarations with Urho3D namespace
+    bindingFinder.addMatcher(
+        recordDecl(
+            unless(hasAttr(attr::Annotate)),
+#ifndef _MSC_VER
+            hasAttr(attr::Visibility),
+#else
+            hasAttr(attr::DLLExport),
+#endif
+            matchesName("^::Urho3D::")).bind("class"), &extractCallback);
+    // Find enum declarations with Urho3D namespace
+    bindingFinder.addMatcher(
+        enumDecl(
+            unless(hasAttr(attr::Annotate)),
+            matchesName("^::Urho3D::")).bind("enum"), &extractCallback);
+
+    // Unbuffered stdout stream to keep the Travis-CI's log flowing and thus prevent it from killing a potentially long running job
+    outs().SetUnbuffered();
+
+    // Success when both sub-tools are run successfully
+    return (outs() << "Extracting", true) &&
+           bindingExtractor.run(newFrontendActionFactory(&bindingFinder).get()) == EXIT_SUCCESS &&
+           (outs() << "\nBinding", true) &&
+           BindingGenerator() == EXIT_SUCCESS &&
+           (outs() << "\n", true) ?
+        EXIT_SUCCESS : EXIT_FAILURE;
+}

+ 36 - 0
Source/Clang-Tools/AutoBinder/CMakeLists.txt

@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME AutoBinder)
+
+# Define source files
+define_source_files ()
+
+# Define dependency libs
+add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../ThirdParty/Mustache Mustache)
+
+# Setup target
+if (APPLE)
+    setup_macosx_linker_flags (CMAKE_EXE_LINKER_FLAGS)
+endif ()
+setup_executable ()

+ 76 - 32
Source/Clang-Tools/CMakeLists.txt

@@ -21,7 +21,37 @@
 #
 
 # Set project name
-project (Urho3D-Clang-Tools)
+if (CMAKE_PROJECT_NAME STREQUAL Urho3D)
+    project (Urho3D-Clang-Tools)
+else ()
+    project (ExternalProject-${URHO3D_CLANG_TOOLS})
+
+    # Set minimum version
+    cmake_minimum_required (VERSION 2.8.6)
+
+    if (COMMAND cmake_policy)
+        cmake_policy (SET CMP0003 NEW)
+        if (CMAKE_VERSION VERSION_GREATER 2.8.12 OR CMAKE_VERSION VERSION_EQUAL 2.8.12)
+            # INTERFACE_LINK_LIBRARIES defines the link interface
+            cmake_policy (SET CMP0022 NEW)
+        endif ()
+        if (CMAKE_VERSION VERSION_GREATER 3.0.0 OR CMAKE_VERSION VERSION_EQUAL 3.0.0)
+            # Disallow use of the LOCATION target property - therefore we set to OLD as we still need it
+            cmake_policy (SET CMP0026 OLD)
+            # MACOSX_RPATH is enabled by default
+            cmake_policy (SET CMP0042 NEW)
+        endif ()
+    endif ()
+
+    # Set CMake modules search path
+    set (CMAKE_MODULE_PATH ${BAKED_CMAKE_SOURCE_DIR}/CMake/Modules)
+
+    # Include Urho3D Cmake common module
+    include (Urho3D-CMake-common)
+
+    # Setup SDK-like include dir in the build tree for building the Clang-tools
+    file (MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty)
+endif ()
 
 # LLVM/Clang is assumed to be installed in a system-wide location when not explicitily defined using env-var
 if (DEFINED ENV{LLVM_CLANG_ROOT})
@@ -37,14 +67,8 @@ else ()
     set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions -fno-rtti")
 endif ()
 
-# All tools must be natively built and output to bin/tool subdir to differentiate them from target platfrom binaries in the bin directory
-set_tool_output_directories ()
-
-# Define source files for the tools
-get_target_property (SOURCES Urho3D SOURCES)
-string (REGEX REPLACE "[^;]+\\.h" "" SOURCES "${SOURCES}")   # Stringify to preserve the semicolons
-string (REGEX REPLACE "[^;]+generated[^;]+\\.cpp" "" SOURCES "${SOURCES}")
-file (GLOB BINDING_SOURCES RELATIVE ${CMAKE_SOURCE_DIR}/Source/Urho3D ${CMAKE_SOURCE_DIR}/Source/Urho3D/Script/*API.cpp)
+# All Clang-tools must be natively built and output to bin/tool/clang subdir to differentiate them from the rest
+set_tool_output_directories (clang)
 
 # Define common dependency libs
 set (LIBS clangTooling clangFrontend clangDriver clangParse clangSerialization clangSema clangEdit clangAnalysis clangToolingCore
@@ -53,30 +77,50 @@ set (LIBS clangTooling clangFrontend clangDriver clangParse clangSerialization c
 execute_process (COMMAND ${LLVM_CONFIG} --system-libs OUTPUT_VARIABLE LLVM_SYSLIBS OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
 string (REGEX REPLACE " *-l" ";" LLVM_SYSLIBS "${LLVM_SYSLIBS}")   # Stringify against empty output variable
 list (APPEND LIBS ${LLVM_SYSLIBS})
+set (INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${DEST_INCLUDE_DIR}/ThirdParty)
+
+# Clang-tools can be built in two ways: on the fly in normal build one at a time or build all of them in a special Clang-tools build tree (for development)
+if (CMAKE_PROJECT_NAME MATCHES ExternalProject)
+    # Externally build the Clang-tool for actual use in a normal build
+    add_subdirectory (${URHO3D_CLANG_TOOLS})
+else ()
+    # Define source files for the tools
+    get_target_property (SOURCES Urho3D SOURCES)
+    string (REGEX REPLACE "[^;]+\\.h" "" SOURCES "${SOURCES}")   # Stringify to preserve the semicolons
+    string (REGEX REPLACE "[^;]+generated[^;]+\\.cpp" "" SOURCES "${SOURCES}")
+    file (GLOB BINDING_SOURCES RELATIVE ${CMAKE_SOURCE_DIR}/Source/Urho3D ${CMAKE_SOURCE_DIR}/Source/Urho3D/Script/*API.cpp)
 
-# List of tools
-add_subdirectory (Annotator)
+    # List of tools
+    add_subdirectory (Annotator)
+    add_subdirectory (AutoBinder)
 
-# List of targets
-if (EXISTS ${LLVM_BINDIR}/clang-query)  # This tool is from clang-tools-extra repository which user may have not installed
-    add_custom_target (ast-query
-        COMMAND ${CMAKE_COMMAND} -E echo "Building AST for query, please be patient..."
-        COMMAND ${LLVM_BINDIR}/clang-query -p ${CMAKE_BINARY_DIR} $$option ${SOURCES}
+    # List of targets
+    if (EXISTS ${LLVM_BINDIR}/clang-query)  # This tool is from clang-tools-extra repository which user may have not installed
+        add_custom_target (ast-query
+            COMMAND ${CMAKE_COMMAND} -E echo "Building AST for query, please be patient..."
+            COMMAND ${LLVM_BINDIR}/clang-query -p ${CMAKE_BINARY_DIR} $$option ${SOURCES}
+            WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
+            COMMENT "Executing clang-query on Urho3D library source files")
+    endif ()
+    add_custom_target (ast
+        COMMAND ${CMAKE_COMMAND} -E echo "Usage: option=-help make ast"
+        COMMAND ${LLVM_BINDIR}/clang-check -p ${CMAKE_BINARY_DIR} $$option ${SOURCES}
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
+        COMMENT "Executing clang-check on Urho3D library source files")
+    add_custom_target (binding-ast
+        COMMAND ${CMAKE_COMMAND} -E echo "Usage: option=-help make binding-ast"
+        COMMAND ${LLVM_BINDIR}/clang-check -p ${CMAKE_BINARY_DIR} $$option ${BINDING_SOURCES}
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
+        COMMENT "Executing clang-check on (existing) AngelScript API bindings source files")
+    add_custom_target (annotate
+        COMMAND ${CMAKE_BINARY_DIR}/bin/tool/clang/Annotator -p ${CMAKE_BINARY_DIR} ${SOURCES}
+        DEPENDS Annotator
+        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
+        COMMENT "Annotating Urho3D library source files")
+    add_custom_target (autobinder-as
+        COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/Source/Urho3D/generated
+        COMMAND ${CMAKE_BINARY_DIR}/bin/tool/clang/AutoBinder -p ${CMAKE_BINARY_DIR} -t AngelScript -o ${CMAKE_BINARY_DIR}/Source/Urho3D/generated ${SOURCES}
+        DEPENDS AutoBinder
         WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
-        COMMENT "Executing clang-query on Urho3D library source files")
+        COMMENT "Auto-binding for AngelScript")
 endif ()
-add_custom_target (ast
-    COMMAND ${CMAKE_COMMAND} -E echo "Usage: option=-help make ast"
-    COMMAND ${LLVM_BINDIR}/clang-check -p ${CMAKE_BINARY_DIR} $$option ${SOURCES}
-    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
-    COMMENT "Executing clang-check on Urho3D library source files")
-add_custom_target (binding-ast
-    COMMAND ${CMAKE_COMMAND} -E echo "Usage: option=-help make binding-ast"
-    COMMAND ${LLVM_BINDIR}/clang-check -p ${CMAKE_BINARY_DIR} $$option ${BINDING_SOURCES}
-    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
-    COMMENT "Executing clang-check on (existing) AngelScript API bindings source files")
-add_custom_target (annotate
-    COMMAND ${CMAKE_BINARY_DIR}/bin/tool/Annotator -p ${CMAKE_BINARY_DIR} ${SOURCES}
-    DEPENDS Annotator
-    WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/Source/Urho3D
-    COMMENT "Annotating Urho3D library source files")

+ 31 - 0
Source/ThirdParty/Mustache/CMakeLists.txt

@@ -0,0 +1,31 @@
+#
+# Copyright (c) 2008-2015 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME Mustache)
+
+# Setup target
+add_custom_target (${TARGET_NAME} ALL)   # Dummy target just so that its post-build step can install headers to the build tree
+set (STATIC_LIBRARY_TARGETS ${STATIC_LIBRARY_TARGETS} ${TARGET_NAME} PARENT_SCOPE)
+
+# Install headers for building the Urho3D library
+install_header_files (DIRECTORY ./ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/Mustache FILES_MATCHING PATTERN *.hpp BUILD_TREE_ONLY)

+ 836 - 0
Source/ThirdParty/Mustache/mustache.hpp

@@ -0,0 +1,836 @@
+//
+// Copyright 2015 Kevin Wojniak
+//
+// Boost Software License - Version 1.0 - August 17th, 2003
+//
+// Permission is hereby granted, free of charge, to any person or organization
+// obtaining a copy of the software and accompanying documentation covered by
+// this license (the "Software") to use, reproduce, display, distribute,
+// execute, and transmit the Software, and to prepare derivative works of the
+// Software, and to permit third-parties to whom the Software is furnished to
+// do so, all subject to the following:
+//
+// The copyright notices in the Software and this entire statement, including
+// the above license grant, this restriction and the following disclaimer,
+// must be included in all copies of the Software, in whole or in part, and
+// all derivative works of the Software, unless such copies or derivative
+// works are solely in the form of machine-executable object code generated by
+// a source language processor.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
+// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
+// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
+// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+// DEALINGS IN THE SOFTWARE.
+//
+
+#ifndef MUSTACHE_HPP
+#define MUSTACHE_HPP
+
+#include <iostream>
+#include <sstream>
+#include <vector>
+#include <functional>
+#include <unordered_map>
+#include <memory>
+
+namespace Mustache {
+
+template <typename StringType>
+StringType trim(const StringType& s) {
+    auto it = s.begin();
+    while (it != s.end() && isspace(static_cast<int>(*it))) {
+        it++;
+    }
+    auto rit = s.rbegin();
+    while (rit.base() != it && isspace(*rit)) {
+        rit++;
+    }
+    return {it, rit.base()};
+}
+
+template <typename StringType>
+StringType escape(const StringType& s) {
+    StringType ret;
+    ret.reserve(s.size()*2);
+    for (const auto ch : s) {
+        switch (static_cast<char>(ch)) {
+            case '&':
+                ret.append("&amp;");
+                break;
+            case '<':
+                ret.append("&lt;");
+                break;
+            case '>':
+                ret.append("&gt;");
+                break;
+            case '\"':
+                ret.append("&quot;");
+                break;
+            case '\'':
+                ret.append("&apos;");
+                break;
+            default:
+                ret.append(1, ch);
+                break;
+        }
+    }
+    return ret;
+}
+
+template <typename StringType>
+class Data {
+public:
+    enum class Type {
+        Object,
+        String,
+        List,
+        True,
+        False,
+        Partial,
+        Lambda,
+        Invalid,
+    };
+    
+    using ObjectType = std::unordered_map<StringType, Data>;
+    using ListType = std::vector<Data>;
+    using PartialType = std::function<StringType()>;
+    using LambdaType = std::function<Data(const StringType&)>;
+    
+    // Construction
+    Data() : Data(Type::Object) {
+    }
+    Data(const StringType& string) : type_{Type::String} {
+        str_.reset(new StringType(string));
+    }
+    Data(const typename StringType::value_type* string) : type_{Type::String} {
+        str_.reset(new StringType(string));
+    }
+    Data(const ListType& list) : type_{Type::List} {
+        list_.reset(new ListType(list));
+    }
+    Data(Type type) : type_{type} {
+        switch (type_) {
+            case Type::Object:
+                obj_.reset(new ObjectType);
+                break;
+            case Type::String:
+                str_.reset(new StringType);
+                break;
+            case Type::List:
+                list_.reset(new ListType);
+                break;
+            default:
+                break;
+        }
+    }
+    Data(const StringType& name, const Data& var) : Data{} {
+        set(name, var);
+    }
+    Data(const PartialType& partial) : type_{Type::Partial} {
+        partial_.reset(new PartialType(partial));
+    }
+    Data(const LambdaType& lambda) : type_{Type::Lambda} {
+        lambda_.reset(new LambdaType(lambda));
+    }
+    static Data List() {
+        return {Data::Type::List};
+    }
+    
+    // Copying
+    Data(const Data& data) : type_(data.type_) {
+        if (data.obj_) {
+            obj_.reset(new ObjectType(*data.obj_));
+        } else if (data.str_) {
+            str_.reset(new StringType(*data.str_));
+        } else if (data.list_) {
+            list_.reset(new ListType(*data.list_));
+        } else if (data.partial_) {
+            partial_.reset(new PartialType(*data.partial_));
+        } else if (data.lambda_) {
+            lambda_.reset(new LambdaType(*data.lambda_));
+        }
+    }
+    
+    // Assignment
+    Data& operator= (const Data& data) {
+        if (&data != this) {
+            type_ = data.type_;
+            obj_.reset();
+            str_.reset();
+            list_.reset();
+            partial_.reset();
+            lambda_.reset();
+            if (data.obj_) {
+                obj_.reset(new ObjectType(*data.obj_));
+            } else if (data.str_) {
+                str_.reset(new StringType(*data.str_));
+            } else if (data.list_) {
+                list_.reset(new ListType(*data.list_));
+            } else if (data.partial_) {
+                partial_.reset(new PartialType(*data.partial_));
+            } else if (data.lambda_) {
+                lambda_.reset(new LambdaType(*data.lambda_));
+            }
+        }
+        return *this;
+    }
+
+    // Move
+    Data(Data&& data) : type_{data.type_} {
+        if (data.obj_) {
+            obj_ = std::move(data.obj_);
+        } else if (data.str_) {
+            str_ = std::move(data.str_);
+        } else if (data.list_) {
+            list_ = std::move(data.list_);
+        } else if (data.partial_) {
+            partial_ = std::move(data.partial_);
+        } else if (data.lambda_) {
+            lambda_ = std::move(data.lambda_);
+        }
+        data.type_ = Data::Type::Invalid;
+    }
+    Data& operator= (Data&& data) {
+        if (this != &data) {
+            obj_.reset();
+            str_.reset();
+            list_.reset();
+            partial_.reset();
+            lambda_.reset();
+            if (data.obj_) {
+                obj_ = std::move(data.obj_);
+            } else if (data.str_) {
+                str_ = std::move(data.str_);
+            } else if (data.list_) {
+                list_ = std::move(data.list_);
+            } else if (data.partial_) {
+                partial_ = std::move(data.partial_);
+            } else if (data.lambda_) {
+                lambda_ = std::move(data.lambda_);
+            }
+            type_ = data.type_;
+            data.type_ = Data::Type::Invalid;
+        }
+        return *this;
+    }
+    
+    // Type info
+    Type type() const {
+        return type_;
+    }
+    bool isObject() const {
+        return type_ == Type::Object;
+    }
+    bool isString() const {
+        return type_ == Type::String;
+    }
+    bool isList() const {
+        return type_ == Type::List;
+    }
+    bool isBool() const {
+        return type_ == Type::True || type_ == Type::False;
+    }
+    bool isTrue() const {
+        return type_ == Type::True;
+    }
+    bool isFalse() const {
+        return type_ == Type::False;
+    }
+    bool isPartial() const {
+        return type_ == Type::Partial;
+    }
+    bool isLambda() const {
+        return type_ == Type::Lambda;
+    }
+    
+    // Object data
+    void set(const StringType& name, const Data& var) {
+        if (isObject()) {
+            obj_->insert(std::pair<StringType,Data>{name, var});
+        }
+    }
+    bool exists(const StringType& name) const {
+        if (isObject() && obj_->find(name) == obj_->end()) {
+            return true;
+        }
+        return false;
+    }
+    const Data* get(const StringType& name) const {
+        if (!isObject()) {
+            return nullptr;
+        }
+        const auto& it = obj_->find(name);
+        if (it == obj_->end()) {
+            return nullptr;
+        }
+        return &it->second;
+    }
+    
+    // List data
+    void push_back(const Data& var) {
+        if (isList()) {
+            list_->push_back(var);
+        }
+    }
+    const ListType& list() const {
+        return *list_;
+    }
+    bool isEmptyList() const {
+        return isList() && list_->empty();
+    }
+    bool isNonEmptyList() const {
+        return isList() && !list_->empty();
+    }
+    Data& operator<< (const Data& data) {
+        push_back(data);
+        return *this;
+    }
+    
+    // String data
+    const StringType& stringValue() const {
+        return *str_;
+    }
+    
+    Data& operator[] (const StringType& key) {
+        return (*obj_)[key];
+    }
+    
+    const PartialType& partial() const {
+        return (*partial_);
+    }
+    
+    const LambdaType& lambda() const {
+        return (*lambda_);
+    }
+    
+    Data<StringType> callLambda(const StringType& text) const {
+        return (*lambda_)(text);
+    }
+
+private:
+    Type type_;
+    std::unique_ptr<ObjectType> obj_;
+    std::unique_ptr<StringType> str_;
+    std::unique_ptr<ListType> list_;
+    std::unique_ptr<PartialType> partial_;
+    std::unique_ptr<LambdaType> lambda_;
+};
+
+template <typename StringType>
+class Mustache {
+public:
+    Mustache(const StringType& input) {
+        Context ctx;
+        parse(input, ctx);
+    }
+    
+    bool isValid() const {
+        return errorMessage_.empty();
+    }
+
+    const StringType& errorMessage() const {
+        return errorMessage_;
+    }
+    
+    template <typename StreamType>
+	StreamType& render(const Data<StringType>& data, StreamType& stream) {
+		render(data, [&stream](const StringType& str) {
+			stream << str;
+		});
+		return stream;
+    }
+    
+    StringType render(const Data<StringType>& data) {
+        std::basic_ostringstream<typename StringType::value_type> ss;
+        return render(data, ss).str();
+    }
+
+	using RenderHandler = std::function<void(const StringType&)>;
+	void render(const Data<StringType>& data, const RenderHandler& handler) {
+        Context ctx{&data};
+		render(handler, ctx);
+	}
+
+private:
+    using StringSizeType = typename StringType::size_type;
+    
+    class DelimiterSet {
+    public:
+        StringType begin;
+        StringType end;
+        DelimiterSet() {
+            reset();
+        }
+        DelimiterSet(const StringType& b, const StringType& e) : begin(b), end(e) {}
+        bool isDefault() const { return begin == defaultBegin() && end == defaultEnd(); }
+        void reset() {
+            begin = defaultBegin();
+            end = defaultEnd();
+        }
+        static StringType defaultBegin() {
+            return StringType(2, '{');
+        }
+        static StringType defaultEnd() {
+            return StringType(2, '}');
+        }
+    };
+    
+    class Tag {
+    public:
+        enum class Type {
+            Invalid,
+            Variable,
+            UnescapedVariable,
+            SectionBegin,
+            SectionEnd,
+            SectionBeginInverted,
+            Comment,
+            Partial,
+            SetDelimiter,
+        };
+        StringType name;
+        Type type = Type::Invalid;
+        std::shared_ptr<StringType> sectionText;
+        std::shared_ptr<DelimiterSet> delimiterSet;
+        bool isSectionBegin() const {
+            return type == Type::SectionBegin || type == Type::SectionBeginInverted;
+        }
+        bool isSectionEnd() const {
+            return type == Type::SectionEnd;
+        }
+    };
+    
+    class Component {
+    public:
+        StringType text;
+        Tag tag;
+        std::vector<Component> children;
+        StringSizeType position = StringType::npos;
+        bool isText() const {
+            return tag.type == Tag::Type::Invalid;
+        }
+        bool isTag() const {
+            return tag.type != Tag::Type::Invalid;
+        }
+        Component() {}
+        Component(const StringType& t, StringSizeType p) : text(t), position(p) {}
+    };
+    
+    class Context {
+    public:
+        using DataType = Data<StringType>;
+
+        Context(const DataType* data) {
+            push(data);
+        }
+
+        Context() {
+        }
+
+        void push(const DataType* data) {
+            items_.insert(items_.begin(), data);
+        }
+
+        void pop() {
+            items_.erase(items_.begin());
+        }
+        
+        static std::vector<StringType> split(const StringType& s, char delim) {
+            std::vector<StringType> elems;
+            std::stringstream ss(s);
+            std::string item;
+            while (std::getline(ss, item, delim)) {
+                elems.push_back(item);
+            }
+            return elems;
+        }
+
+        const DataType* get(const StringType& name) const {
+            // process {{.}} name
+            if (name.size() == 1 && name.at(0) == '.') {
+                return items_.front();
+            }
+            // process {{a.b.c}} name
+            if (name.find('.', 1) != StringType::npos) {
+                const DataType* var{items_.front()};
+                for (const auto& n : split(name, '.')) {
+                    var = var->get(n);
+                    if (!var) {
+                        return nullptr;
+                    }
+                }
+                return var;
+            }
+            // process normal name
+            for (const auto& item : items_) {
+                const auto var = item->get(name);
+                if (var) {
+                    return var;
+                }
+            }
+            return nullptr;
+        }
+
+        Context(const Context&) = delete;
+        Context& operator= (const Context&) = delete;
+        
+        DelimiterSet delimiterSet;
+
+    private:
+        std::vector<const DataType*> items_;
+    };
+
+    class ContextPusher {
+    public:
+        ContextPusher(Context& ctx, const Data<StringType>* data) : ctx_(ctx) {
+            ctx.push(data);
+        }
+        ~ContextPusher() {
+            ctx_.pop();
+        }
+        ContextPusher(const ContextPusher&) = delete;
+        ContextPusher& operator= (const ContextPusher&) = delete;
+    private:
+        Context& ctx_;
+    };
+    
+    Mustache(const StringType& input, Context& ctx) {
+        parse(input, ctx);
+    }
+
+    void parse(const StringType& input, Context& ctx) {
+        using streamstring = std::basic_ostringstream<typename StringType::value_type>;
+        
+        const StringType braceDelimiterEndUnescaped(3, '}');
+        const StringSizeType inputSize{input.size()};
+        
+        bool currentDelimiterIsBrace{ctx.delimiterSet.isDefault()};
+        
+        std::vector<Component*> sections{&rootComponent_};
+        std::vector<StringSizeType> sectionStarts;
+        
+        StringSizeType inputPosition{0};
+        while (inputPosition != inputSize) {
+            
+            // Find the next tag start delimiter
+            const StringSizeType tagLocationStart{input.find(ctx.delimiterSet.begin, inputPosition)};
+            if (tagLocationStart == StringType::npos) {
+                // No tag found. Add the remaining text.
+                const Component comp{{input, inputPosition, inputSize - inputPosition}, inputPosition};
+                sections.back()->children.push_back(comp);
+                break;
+            } else if (tagLocationStart != inputPosition) {
+                // Tag found, add text up to this tag.
+                const Component comp{{input, inputPosition, tagLocationStart - inputPosition}, inputPosition};
+                sections.back()->children.push_back(comp);
+            }
+            
+            // Find the next tag end delimiter
+            StringSizeType tagContentsLocation{tagLocationStart + ctx.delimiterSet.begin.size()};
+            const bool tagIsUnescapedVar{currentDelimiterIsBrace && tagLocationStart != (inputSize - 2) && input.at(tagContentsLocation) == ctx.delimiterSet.begin.at(0)};
+            const StringType& currentTagDelimiterEnd{tagIsUnescapedVar ? braceDelimiterEndUnescaped : ctx.delimiterSet.end};
+            const auto currentTagDelimiterEndSize = currentTagDelimiterEnd.size();
+            if (tagIsUnescapedVar) {
+                ++tagContentsLocation;
+            }
+            StringSizeType tagLocationEnd{input.find(currentTagDelimiterEnd, tagContentsLocation)};
+            if (tagLocationEnd == StringType::npos) {
+                streamstring ss;
+                ss << "Unclosed tag at " << tagLocationStart;
+                errorMessage_.assign(ss.str());
+                return;
+            }
+            
+            // Parse tag
+            const StringType tagContents{trim(StringType{input, tagContentsLocation, tagLocationEnd - tagContentsLocation})};
+            Component comp;
+            if (!tagContents.empty() && tagContents[0] == '=') {
+                if (!parseSetDelimiterTag(tagContents, ctx.delimiterSet)) {
+                    streamstring ss;
+                    ss << "Invalid set delimiter tag at " << tagLocationStart;
+                    errorMessage_.assign(ss.str());
+                    return;
+                }
+                currentDelimiterIsBrace = ctx.delimiterSet.isDefault();
+                comp.tag.type = Tag::Type::SetDelimiter;
+                comp.tag.delimiterSet.reset(new DelimiterSet(ctx.delimiterSet));
+            }
+            if (comp.tag.type != Tag::Type::SetDelimiter) {
+                parseTagContents(tagIsUnescapedVar, tagContents, comp.tag);
+            }
+            comp.position = tagLocationStart;
+            sections.back()->children.push_back(comp);
+            
+            // Start next search after this tag
+            inputPosition = tagLocationEnd + currentTagDelimiterEndSize;
+
+            // Push or pop sections
+            if (comp.tag.isSectionBegin()) {
+                sections.push_back(&sections.back()->children.back());
+                sectionStarts.push_back(inputPosition);
+            } else if (comp.tag.isSectionEnd()) {
+                if (sections.size() == 1) {
+                    streamstring ss;
+                    ss << "Unopened section \"" << comp.tag.name << "\" at " << comp.position;
+                    errorMessage_.assign(ss.str());
+                    return;
+                }
+                sections.back()->tag.sectionText.reset(new StringType(input.substr(sectionStarts.back(), tagLocationStart - sectionStarts.back())));
+                sections.pop_back();
+                sectionStarts.pop_back();
+            }
+        }
+        
+        // Check for sections without an ending tag
+        walk([this](Component& comp, int) -> WalkControl {
+            if (!comp.tag.isSectionBegin()) {
+                return WalkControl::Continue;
+            }
+            if (comp.children.empty() || !comp.children.back().tag.isSectionEnd() || comp.children.back().tag.name != comp.tag.name) {
+                streamstring ss;
+                ss << "Unclosed section \"" << comp.tag.name << "\" at " << comp.position;
+                errorMessage_.assign(ss.str());
+                return WalkControl::Stop;
+            }
+            comp.children.pop_back(); // remove now useless end section component
+            return WalkControl::Continue;
+        });
+        if (!errorMessage_.empty()) {
+            return;
+        }
+    }
+    
+    enum class WalkControl {
+        Continue,
+        Stop,
+        Skip,
+    };
+    using WalkCallback = std::function<WalkControl(Component&, int)>;
+    
+    void walk(const WalkCallback& callback) const {
+        walkChildren(callback, rootComponent_);
+    }
+
+    void walkChildren(const WalkCallback& callback, const Component& comp) const {
+        for (auto childComp : comp.children) {
+            if (walkComponent(callback, childComp) != WalkControl::Continue) {
+                break;
+            }
+        }
+    }
+    
+    WalkControl walkComponent(const WalkCallback& callback, Component& comp, int depth = 0) const {
+        WalkControl control{callback(comp, depth)};
+        if (control == WalkControl::Stop) {
+            return control;
+        } else if (control == WalkControl::Skip) {
+            return WalkControl::Continue;
+        }
+        ++depth;
+        for (auto childComp : comp.children) {
+            control = walkComponent(callback, childComp, depth);
+            if (control == WalkControl::Stop) {
+                return control;
+            } else if (control == WalkControl::Skip) {
+                control = WalkControl::Continue;
+                break;
+            }
+        }
+        --depth;
+        return control;
+    }
+    
+    bool parseSetDelimiterTag(const StringType& contents, DelimiterSet& delimiterSet) {
+        // Smallest legal tag is "=X X="
+        if (contents.size() < 5) {
+            return false;
+        }
+        if (contents.back() != '=') {
+            return false;
+        }
+        const auto contentsSubstr = trim(contents.substr(1, contents.size() - 2));
+        const auto spacepos = contentsSubstr.find(' ');
+        if (spacepos == StringType::npos) {
+            return false;
+        }
+        const auto nonspace = contentsSubstr.find_first_not_of(' ', spacepos + 1);
+        if (nonspace == StringType::npos) {
+            return false;
+        }
+        delimiterSet.begin = contentsSubstr.substr(0, spacepos);
+        delimiterSet.end = contentsSubstr.substr(nonspace, contentsSubstr.size() - nonspace);
+        return true;
+    }
+    
+    void parseTagContents(bool isUnescapedVar, const StringType& contents, Tag& tag) {
+        if (isUnescapedVar) {
+            tag.type = Tag::Type::UnescapedVariable;
+            tag.name = contents;
+        } else if (contents.empty()) {
+            tag.type = Tag::Type::Variable;
+            tag.name.clear();
+        } else {
+            switch (static_cast<char>(contents.at(0))) {
+                case '#':
+                    tag.type = Tag::Type::SectionBegin;
+                    break;
+                case '^':
+                    tag.type = Tag::Type::SectionBeginInverted;
+                    break;
+                case '/':
+                    tag.type = Tag::Type::SectionEnd;
+                    break;
+                case '>':
+                    tag.type = Tag::Type::Partial;
+                    break;
+                case '&':
+                    tag.type = Tag::Type::UnescapedVariable;
+                    break;
+                case '!':
+                    tag.type = Tag::Type::Comment;
+                    break;
+                default:
+                    tag.type = Tag::Type::Variable;
+                    break;
+            }
+            if (tag.type == Tag::Type::Variable) {
+                tag.name = contents;
+            } else {
+                StringType name{contents};
+                name.erase(name.begin());
+                tag.name = trim(name);
+            }
+        }
+    }
+    
+    void render(const RenderHandler& handler, Context& ctx) {
+        walk([&handler, &ctx, this](Component& comp, int) -> WalkControl {
+            return renderComponent(handler, ctx, comp);
+        });
+    }
+    
+    StringType render(Context& ctx) {
+        std::basic_ostringstream<typename StringType::value_type> ss;
+		render([&ss](const StringType& str) {
+			ss << str;
+		}, ctx);
+		return ss.str();
+    }
+    
+    WalkControl renderComponent(const RenderHandler& handler, Context& ctx, Component& comp) {
+        if (comp.isText()) {
+            handler(comp.text);
+            return WalkControl::Continue;
+        }
+        
+        const Tag& tag{comp.tag};
+        const Data<StringType>* var = nullptr;
+        switch (tag.type) {
+            case Tag::Type::Variable:
+            case Tag::Type::UnescapedVariable:
+                if ((var = ctx.get(tag.name)) != nullptr) {
+                    if (!renderVariable(handler, var, ctx, tag.type == Tag::Type::Variable)) {
+                        return WalkControl::Stop;
+                    }
+                }
+                break;
+            case Tag::Type::SectionBegin:
+                if ((var = ctx.get(tag.name)) != nullptr) {
+                    if (var->isLambda()) {
+                        if (!renderLambda(handler, var, ctx, false, *comp.tag.sectionText, true)) {
+                            return WalkControl::Stop;
+                        }
+                    } else if (!var->isFalse() && !var->isEmptyList()) {
+                        renderSection(handler, ctx, comp, var);
+                    }
+                }
+                return WalkControl::Skip;
+            case Tag::Type::SectionBeginInverted:
+                if ((var = ctx.get(tag.name)) == nullptr || var->isFalse() || var->isEmptyList()) {
+                    renderSection(handler, ctx, comp, var);
+                }
+                return WalkControl::Skip;
+            case Tag::Type::Partial:
+                if ((var = ctx.get(tag.name)) != nullptr && var->isPartial()) {
+                    const auto partial = var->partial();
+                    Mustache tmpl{partial()};
+                    if (!tmpl.isValid()) {
+                        errorMessage_ = tmpl.errorMessage();
+                    } else {
+                        tmpl.render(handler, ctx);
+                        if (!tmpl.isValid()) {
+                            errorMessage_ = tmpl.errorMessage();
+                        }
+                    }
+                    if (!tmpl.isValid()) {
+                        return WalkControl::Stop;
+                    }
+                }
+                break;
+            case Tag::Type::SetDelimiter:
+                ctx.delimiterSet = *comp.tag.delimiterSet;
+                break;
+            default:
+                break;
+        }
+        
+        return WalkControl::Continue;
+    }
+    
+    bool renderLambda(const RenderHandler& handler, const Data<StringType>* var, Context& ctx, bool escaped, const StringType& text, bool parseWithSameContext) {
+        const auto lambdaResult = var->callLambda(text);
+        if (!lambdaResult.isString()) {
+            return true;
+        }
+        Mustache tmpl = parseWithSameContext ? Mustache{lambdaResult.stringValue(), ctx} : Mustache{lambdaResult.stringValue()};
+        if (!tmpl.isValid()) {
+            errorMessage_ = tmpl.errorMessage();
+        } else {
+            const StringType str{tmpl.render(ctx)};
+            if (!tmpl.isValid()) {
+                errorMessage_ = tmpl.errorMessage();
+            } else {
+                handler(escaped ? escape(str) : str);
+            }
+        }
+        return tmpl.isValid();
+    }
+    
+    bool renderVariable(const RenderHandler& handler, const Data<StringType>* var, Context& ctx, bool escaped) {
+        if (var->isString()) {
+            const auto varstr = var->stringValue();
+			handler(escaped ? escape(varstr) : varstr);
+        } else if (var->isLambda()) {
+            return renderLambda(handler, var, ctx, escaped, {}, false);
+        }
+        return true;
+    }
+
+    void renderSection(const RenderHandler& handler, Context& ctx, Component& incomp, const Data<StringType>* var) {
+        const auto callback = [&handler, &ctx, this](Component& comp, int) -> WalkControl {
+            return renderComponent(handler, ctx, comp);
+        };
+        if (var && var->isNonEmptyList()) {
+            for (const auto& item : var->list()) {
+                const ContextPusher ctxpusher{ctx, &item};
+                walkChildren(callback, incomp);
+            }
+        } else if (var && var->isObject()) {
+            const ContextPusher ctxpusher{ctx, var};
+            walkChildren(callback, incomp);
+        } else {
+            walkChildren(callback, incomp);
+        }
+    }
+
+private:
+    StringType errorMessage_;
+    Component rootComponent_;
+};
+
+} // namespace
+
+#endif // MUSTACHE_HPP

+ 23 - 7
Source/Urho3D/CMakeLists.txt

@@ -24,13 +24,17 @@
 set (TARGET_NAME Urho3D)
 
 # Generate JSON compilation database format specification
-if (URHO3D_CLANG_TOOLS)
+if (URHO3D_CLANG_TOOLS OR URHO3D_BINDINGS)
     set (CMAKE_EXPORT_COMPILE_COMMANDS 1)
+    # Clang assumes all their tools to be installed in a relative path to their libs but this is not the case for our own Clang-tools
+    # Clang has a hard-coded way to search for their headers (see http://clang.llvm.org/docs/LibTooling.html#builtin-includes)
+    # In order for our Clang-tools to work correctly we need to make this Clang's assumption to be true
     execute_process (COMMAND ${LLVM_CONFIG} --version OUTPUT_VARIABLE LLVM_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
     string (REGEX REPLACE "([.0123456789]+).*" \\1 LLVM_VERSION "${LLVM_VERSION}")      # Stringify against empty output variable
     execute_process (COMMAND ${LLVM_CONFIG} --libdir OUTPUT_VARIABLE LLVM_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET)
-    include_directories (${LLVM_LIBDIR}/clang/${LLVM_VERSION}/include)
-    # TODO: Remove this workaround when Travis CI VM has been migrated to Ubuntu 14.04 LTS
+    execute_process (COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/bin/tool/lib/clang/${LLVM_VERSION})
+    create_symlink (${LLVM_LIBDIR}/clang/${LLVM_VERSION}/include ${CMAKE_BINARY_DIR}/bin/tool/lib/clang/${LLVM_VERSION}/include)
+    # TODO: Remove this workaround when Travis CI VM has been migrated to Ubuntu 14.04 LTS (ancient glibc header has incorrect defines)
     if (DEFINED ENV{CI})
         add_definitions (-D__extern_always_inline=inline)
     endif ()
@@ -69,13 +73,28 @@ add_custom_command (OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/librevision.h
     WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
     COMMENT "Generating GIT revision number (tag + last commit SHA-1)")
 
+if (URHO3D_ANGELSCRIPT)
+    if (URHO3D_BINDINGS)
+        # Build the Clang-tools as external project (even when we are not cross-compiling) for auto-binding generation
+        include (ExternalProject)
+        if (IOS)
+            # For iOS target, ensure the host environment is cleared first; Also workaround a known CMake/Xcode generator bug which prevents it from installing binaries correctly
+            set (IOS_FIX CMAKE_COMMAND /usr/bin/env -i PATH=$ENV{PATH} ${CMAKE_COMMAND} BUILD_COMMAND bash -c "sed -i '' 's/EFFECTIVE_PLATFORM_NAME//g' CMakeScripts/install_postBuildPhase.make*")
+        endif ()
+        ExternalProject_Add (AutoBinder
+            SOURCE_DIR ${CMAKE_SOURCE_DIR}/Source/Clang-Tools
+            CMAKE_ARGS -DURHO3D_NOABI=1 -DURHO3D_CLANG_TOOLS=AutoBinder -DDEST_RUNTIME_DIR=${CMAKE_BINARY_DIR}/bin/tool/clang -DDEST_INCLUDE_DIR=${DEST_INCLUDE_DIR} -DBAKED_CMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} ${IOS_FIX})
+    endif ()
+else ()
+    list (APPEND EXCLUDED_SOURCE_DIRS Script)
+endif ()
+
 if (URHO3D_LUA)
     # The host tool must be built natively
     if (CMAKE_CROSSCOMPILING OR IOS)
         # When cross-compiling, build the host tool as external project
         include (ExternalProject)
         if (IOS)
-            # For iOS target, ensure the host environment is cleared first; Also workaround a known CMake/Xcode generator bug which prevents it from installing binaries correctly
             set (IOS_FIX CMAKE_COMMAND /usr/bin/env -i PATH=$ENV{PATH} ${CMAKE_COMMAND} BUILD_COMMAND bash -c "sed -i '' 's/EFFECTIVE_PLATFORM_NAME//g' CMakeScripts/install_postBuildPhase.make*")
         endif ()
         ExternalProject_Add (tolua++
@@ -155,9 +174,6 @@ if (MSVC AND URHO3D_LIB_TYPE STREQUAL SHARED)   # MSVC linker does not have forc
 endif ()
 
 # Define source files
-if (NOT URHO3D_ANGELSCRIPT)
-    list (APPEND EXCLUDED_SOURCE_DIRS Script)
-endif ()
 foreach (DIR Navigation Network Physics Urho2D)
     string (TOUPPER URHO3D_${DIR} OPT)
     if (NOT ${OPT})