add set statements
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8769b83..0aee13a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.5)
 
 
-project(inja LANGUAGES CXX VERSION 3.0.0)
+project(inja LANGUAGES CXX VERSION 3.1.0)
 
 
 option(INJA_USE_EMBEDDED_JSON "Use the shipped json header if not available on the system" ON)
diff --git a/README.md b/README.md
index 8f3a0a8..bd2b14b 100644
--- a/README.md
+++ b/README.md
@@ -38,7 +38,7 @@
 
 ## Integration
 
-Inja is a headers only library, which can be downloaded from the [releases](https://github.com/pantor/inja/releases) or directly from the `include/` or `single_include/` folder. Inja uses `nlohmann/json.hpp` as its single dependency, so make sure it can be included from `inja.hpp`. json can be downloaded [here](https://github.com/nlohmann/json/releases). Then integration is as easy as:
+Inja is a headers only library, which can be downloaded from the [releases](https://github.com/pantor/inja/releases) or directly from the `include/` or `single_include/` folder. Inja uses `nlohmann/json.hpp` (>= v3.8.0) as its single dependency, so make sure it can be included from `inja.hpp`. json can be downloaded [here](https://github.com/nlohmann/json/releases). Then integration is as easy as:
 
 ```.cpp
 #include <inja.hpp>
@@ -308,6 +308,13 @@
 ```
 
 
+### Set Statements
+
+Variables can also be defined within the template using the set statment.
+```.cpp
+render("{% set new_hour=23 %}{{ new_hour }}pm", data); // "23pm"
+```
+
 ## Supported compilers
 
 Inja uses `string_view` from C++17, but includes the [polyfill](https://github.com/martinmoene/string-view-lite) from martinmoene. This way, the minimum version is C++11. Currently, the following compilers are tested:
diff --git a/include/inja/inja.hpp b/include/inja/inja.hpp
index c0509dc..3075b51 100644
--- a/include/inja/inja.hpp
+++ b/include/inja/inja.hpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Pantor. All rights reserved.
+// Copyright (c) 2020 Pantor. All rights reserved.
 
 #ifndef INCLUDE_INJA_INJA_HPP_
 #define INCLUDE_INJA_INJA_HPP_
diff --git a/include/inja/node.hpp b/include/inja/node.hpp
index 19314eb..84a99ca 100644
--- a/include/inja/node.hpp
+++ b/include/inja/node.hpp
@@ -28,6 +28,7 @@
 class ForObjectStatementNode;
 class IfStatementNode;
 class IncludeStatementNode;
+class SetStatementNode;
 
 
 class NodeVisitor {
@@ -45,6 +46,7 @@
   virtual void visit(const ForObjectStatementNode& node) = 0;
   virtual void visit(const IfStatementNode& node) = 0;
   virtual void visit(const IncludeStatementNode& node) = 0;
+  virtual void visit(const SetStatementNode& node) = 0;
 };
 
 /*!
@@ -311,6 +313,18 @@
   };
 };
 
+class SetStatementNode : public StatementNode {
+public:
+  std::string key;
+  ExpressionListNode expression;
+
+  explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { }
+
+  void accept(NodeVisitor& v) const {
+    v.visit(*this);
+  };
+};
+
 } // namespace inja
 
 #endif // INCLUDE_INJA_NODE_HPP_
diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp
index 6441910..0cc51e5 100644
--- a/include/inja/parser.hpp
+++ b/include/inja/parser.hpp
@@ -449,6 +449,29 @@
 
       get_next_token();
 
+    } else if (tok.text == static_cast<decltype(tok.text)>("set")) {
+      get_next_token();
+
+      if (tok.kind != Token::Kind::Id) {
+        throw_parser_error("expected variable name, got '" + tok.describe() + "'");
+      }
+
+      std::string key = static_cast<std::string>(tok.text);
+      get_next_token();
+
+      auto set_statement_node = std::make_shared<SetStatementNode>(key, tok.text.data() - tmpl.content.c_str());
+      current_block->nodes.emplace_back(set_statement_node);
+      current_expression_list = &set_statement_node->expression;
+
+      if (tok.text != static_cast<decltype(tok.text)>("=")) {
+        throw_parser_error("expected '=', got '" + tok.describe() + "'");
+      }
+      get_next_token();
+
+      if (!parse_expression(tmpl, closing)) {
+        return false;
+      }
+
     } else {
       return false;
     }
diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp
index d62bdb6..5c0ec10 100644
--- a/include/inja/renderer.hpp
+++ b/include/inja/renderer.hpp
@@ -33,8 +33,8 @@
   const json *json_input;
   std::ostream *output_stream;
 
-  json json_loop_data;
-  json* current_loop_data = &json_loop_data["loop"];
+  json json_additional_data;
+  json* current_loop_data = &json_additional_data["loop"];
 
   std::vector<std::shared_ptr<json>> json_tmp_stack;
   std::stack<const json*> json_eval_stack;
@@ -161,8 +161,8 @@
 
     try {
       // First try to evaluate as a loop variable
-      if (json_loop_data.contains(ptr)) {
-        json_eval_stack.push(&json_loop_data.at(ptr));
+      if (json_additional_data.contains(ptr)) {
+        json_eval_stack.push(&json_additional_data.at(ptr));
       } else {
         json_eval_stack.push(&json_input->at(ptr));
       }
@@ -502,7 +502,7 @@
 
     size_t index = 0;
     for (auto it = result->begin(); it != result->end(); ++it) {
-      json_loop_data[static_cast<std::string>(node.value)] = *it;
+      json_additional_data[static_cast<std::string>(node.value)] = *it;
 
       (*current_loop_data)["index"] = index;
       (*current_loop_data)["index1"] = index + 1;
@@ -513,12 +513,12 @@
       ++index;
     }
 
-    json_loop_data[static_cast<std::string>(node.value)].clear();
+    json_additional_data[static_cast<std::string>(node.value)].clear();
     if (!(*current_loop_data)["parent"].empty()) {
       auto tmp = (*current_loop_data)["parent"];
       *current_loop_data = std::move(tmp);
     } else {
-      current_loop_data = &json_loop_data["loop"];
+      current_loop_data = &json_additional_data["loop"];
     }
   }
 
@@ -534,8 +534,8 @@
 
     size_t index = 0;
     for (auto it = result->begin(); it != result->end(); ++it) {
-      json_loop_data[static_cast<std::string>(node.key)] = it.key();
-      json_loop_data[static_cast<std::string>(node.value)] = it.value();
+      json_additional_data[static_cast<std::string>(node.key)] = it.key();
+      json_additional_data[static_cast<std::string>(node.value)] = it.value();
 
       (*current_loop_data)["index"] = index;
       (*current_loop_data)["index1"] = index + 1;
@@ -546,12 +546,12 @@
       ++index;
     }
 
-    json_loop_data[static_cast<std::string>(node.key)].clear();
-    json_loop_data[static_cast<std::string>(node.value)].clear();
+    json_additional_data[static_cast<std::string>(node.key)].clear();
+    json_additional_data[static_cast<std::string>(node.value)].clear();
     if (!(*current_loop_data)["parent"].empty()) {
       *current_loop_data = std::move((*current_loop_data)["parent"]);
     } else {
-      current_loop_data = &json_loop_data["loop"];
+      current_loop_data = &json_additional_data["loop"];
     }
   }
 
@@ -569,12 +569,16 @@
     auto included_template_it = template_storage.find(node.file);
 
     if (included_template_it != template_storage.end()) {
-      sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_loop_data);
+      sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data);
     } else if (config.throw_at_missing_includes) {
       throw_renderer_error("include '" + node.file + "' not found", node);
     }
   }
 
+  void visit(const SetStatementNode& node) {
+    json_additional_data[node.key] = *eval_expression_list(node.expression);
+  }
+
 public:
   Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage)
       : config(config), template_storage(template_storage), function_storage(function_storage) { }
@@ -584,7 +588,7 @@
     current_template = &tmpl;
     json_input = &data;
     if (loop_data) {
-      json_loop_data = *loop_data;
+      json_additional_data = *loop_data;
     }
 
     current_template->root.accept(*this);
diff --git a/include/inja/statistics.hpp b/include/inja/statistics.hpp
index c8be1b3..71fc719 100644
--- a/include/inja/statistics.hpp
+++ b/include/inja/statistics.hpp
@@ -55,6 +55,8 @@
 
   void visit(const IncludeStatementNode&) { }
 
+  void visit(const SetStatementNode&) { }
+
 public:
   unsigned int variable_counter;
 
diff --git a/test/test-renderer.cpp b/test/test-renderer.cpp
index a25694e..7345197 100644
--- a/test/test-renderer.cpp
+++ b/test/test-renderer.cpp
@@ -122,6 +122,13 @@
                       "[inja.exception.parser_error] (at 1:43) expected statement, got 'end'");
   }
 
+  SUBCASE("set statements") {
+    CHECK(env.render("{% set predefined=true %}{% if predefined %}a{% endif %}", data) == "a");
+    CHECK(env.render("{% set predefined=false %}{% if predefined %}a{% endif %}", data) == "");
+    CHECK_THROWS_WITH(env.render("{% if predefined %}{% endif %}", data), 
+                      "[inja.exception.render_error] (at 1:7) variable 'predefined' not found");
+  }
+
   SUBCASE("line statements") {
     CHECK(env.render(R""""(## if is_happy
 Yeah!