Introduced let implementation.

This closes #105 issue on GitHub.
diff --git a/src/njs_builtin.c b/src/njs_builtin.c
index dae0710..abb6cee 100644
--- a/src/njs_builtin.c
+++ b/src/njs_builtin.c
@@ -972,6 +972,10 @@
 
     var = node->variable;
 
+    if (var->type == NJS_VARIABLE_LET) {
+        return NJS_DECLINED;
+    }
+
     value = njs_scope_valid_value(vm, var->index);
 
     if (var->type == NJS_VARIABLE_FUNCTION && njs_is_undefined(value)) {
diff --git a/src/njs_disassembler.c b/src/njs_disassembler.c
index 87d6fdd..3a19387 100644
--- a/src/njs_disassembler.c
+++ b/src/njs_disassembler.c
@@ -139,6 +139,17 @@
     { NJS_VMCODE_THROW, sizeof(njs_vmcode_throw_t),
           njs_str("THROW           ") },
 
+    { NJS_VMCODE_LET, sizeof(njs_vmcode_variable_t),
+          njs_str("LET             ") },
+
+    { NJS_VMCODE_LET_UPDATE, sizeof(njs_vmcode_variable_t),
+          njs_str("LET UPDATE      ") },
+
+    { NJS_VMCODE_INITIALIZATION_TEST, sizeof(njs_vmcode_variable_t),
+          njs_str("INIT TEST       ") },
+
+    { NJS_VMCODE_NOT_INITIALIZED, sizeof(njs_vmcode_variable_t),
+          njs_str("NOT INIT        ") },
 };
 
 
diff --git a/src/njs_generator.c b/src/njs_generator.c
index 4b1e7e3..55f274f 100644
--- a/src/njs_generator.c
+++ b/src/njs_generator.c
@@ -67,6 +67,8 @@
     njs_variable_t **retvar);
 static njs_int_t njs_generate_var_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_let(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node, njs_variable_t *var);
 static njs_int_t njs_generate_if_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_cond_expression(njs_vm_t *vm,
@@ -79,6 +81,10 @@
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_for_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
+static njs_int_t njs_generate_for_let_update(njs_vm_t *vm,
+    njs_generator_t *generator, njs_parser_node_t *node, size_t depth);
+static njs_int_t njs_generate_for_resolve_closure(njs_vm_t *vm,
+    njs_parser_node_t *node, size_t depth);
 static njs_int_t njs_generate_for_in_statement(njs_vm_t *vm,
     njs_generator_t *generator, njs_parser_node_t *node);
 static njs_int_t njs_generate_start_block(njs_vm_t *vm,
@@ -261,6 +267,9 @@
                           ##__VA_ARGS__)
 
 
+#define NJS_GENERATE_MAX_DEPTH  4096
+
+
 static const njs_str_t  no_label     = njs_str("");
 static const njs_str_t  return_label = njs_str("@return");
 /* GCC and Clang complain about NULL argument passed to memcmp(). */
@@ -277,6 +286,7 @@
     switch (node->token_type) {
 
     case NJS_TOKEN_VAR:
+    case NJS_TOKEN_LET:
         return njs_generate_var_statement(vm, generator, node);
 
     case NJS_TOKEN_IF:
@@ -479,7 +489,7 @@
 {
     njs_int_t  ret;
 
-    if (njs_slow_path(generator->count++ > 4096)) {
+    if (njs_slow_path(generator->count++ > NJS_GENERATE_MAX_DEPTH)) {
         njs_range_error(vm, "Maximum call stack size exceeded");
         return NJS_ERROR;
     }
@@ -492,6 +502,25 @@
 }
 
 
+static njs_int_t
+njs_generate_wo_dest(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node)
+{
+    njs_int_t           ret;
+    njs_parser_scope_t  *scope;
+
+    scope = njs_function_scope(node->scope);
+
+    scope->dest_disable = 1;
+
+    ret = njs_generator(vm, generator, node);
+
+    scope->dest_disable = 0;
+
+    return ret;
+}
+
+
 static u_char *
 njs_generate_reserve(njs_vm_t *vm, njs_generator_t *generator, size_t size)
 {
@@ -591,6 +620,8 @@
     njs_parser_node_t *node)
 {
     njs_variable_t              *var;
+    njs_parser_scope_t          *scope;
+    njs_vmcode_variable_t       *variable;
     njs_vmcode_function_copy_t  *copy;
 
     var = njs_variable_reference(vm, node);
@@ -605,6 +636,20 @@
         copy->retval = node->index;
     }
 
+    if (var->init) {
+        return NJS_OK;
+    }
+
+    if (var->type == NJS_VARIABLE_LET) {
+        scope = njs_function_scope(node->scope);
+
+        if (scope->dest_disable) {
+            njs_generate_code(generator, njs_vmcode_variable_t, variable,
+                              NJS_VMCODE_NOT_INITIALIZED, 1, node);
+            variable->dst = node->index;
+        }
+    }
+
     return NJS_OK;
 }
 
@@ -614,6 +659,8 @@
     njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
 {
     njs_variable_t              *var;
+    njs_parser_scope_t          *scope;
+    njs_vmcode_variable_t       *variable;
     njs_vmcode_function_copy_t  *copy;
 
     var = njs_variable_reference(vm, node);
@@ -641,11 +688,44 @@
         copy->retval = node->index;
     }
 
+    if (var->init) {
+        return NJS_OK;
+    }
+
+    if (var->type == NJS_VARIABLE_LET) {
+        scope = njs_function_scope(node->scope);
+
+        if ((!scope->dest_disable && njs_function_scope(var->scope) == scope)) {
+            njs_generate_code(generator, njs_vmcode_variable_t, variable,
+                              NJS_VMCODE_NOT_INITIALIZED, 1, node);
+            variable->dst = node->index;
+        }
+    }
+
     return NJS_OK;
 }
 
 
 static njs_int_t
+njs_generate_variable_wo_dest(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node, njs_reference_type_t type, njs_variable_t **retvar)
+{
+    njs_int_t           ret;
+    njs_parser_scope_t  *scope;
+
+    scope = njs_function_scope(node->scope);
+
+    scope->dest_disable = 1;
+
+    ret = njs_generate_variable(vm, generator, node, type, retvar);
+
+    scope->dest_disable = 0;
+
+    return ret;
+}
+
+
+static njs_int_t
 njs_generate_var_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
@@ -656,26 +736,50 @@
 
     lvalue = node->left;
 
-    ret = njs_generate_variable(vm, generator, lvalue, NJS_DECLARATION, &var);
+    ret = njs_generate_variable_wo_dest(vm, generator, lvalue,
+                                        NJS_DECLARATION, &var);
     if (njs_slow_path(ret != NJS_OK)) {
         return NJS_ERROR;
     }
 
-    lvalue->index = var->index;
     expr = node->right;
 
     if (expr == NULL) {
         /* Variable is only declared. */
+        if (var->type == NJS_VARIABLE_LET) {
+            ret = njs_generate_let(vm, generator, node, var);
+            if (njs_slow_path(ret != NJS_OK)) {
+                return ret;
+            }
+        }
+
+        var->init = 1;
+
         return NJS_OK;
     }
 
-    expr->dest = lvalue;
+    if (var->type == NJS_VARIABLE_LET) {
+        ret = njs_generate_wo_dest(vm, generator, expr);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
 
-    ret = njs_generator(vm, generator, expr);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
+        ret = njs_generate_let(vm, generator, node, var);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+    } else {
+        expr->dest = lvalue;
+
+        ret = njs_generator(vm, generator, expr);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
     }
 
+    var->init = 1;
+
     /*
      * lvalue and expression indexes are equal if the expression is an
      * empty object or expression result is stored directly in variable.
@@ -693,6 +797,20 @@
 
 
 static njs_int_t
+njs_generate_let(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node, njs_variable_t *var)
+{
+    njs_vmcode_variable_t  *code;
+
+    njs_generate_code(generator, njs_vmcode_variable_t, code,
+                      NJS_VMCODE_LET, 0, node);
+    code->dst = var->index;
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
 njs_generate_if_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
@@ -1089,7 +1207,7 @@
 {
     njs_int_t               ret;
     njs_jump_off_t          jump_offset, loop_offset;
-    njs_parser_node_t       *condition, *update;
+    njs_parser_node_t       *condition, *update, *init;
     njs_vmcode_jump_t       *jump;
     njs_vmcode_cond_jump_t  *cond_jump;
 
@@ -1113,9 +1231,20 @@
         return ret;
     }
 
+    init = node->left;
     node = node->right;
     condition = node->left;
 
+    /*
+     * Closures can occur in conditional and loop updates.  This must be
+     * foreseen in order to generate optimized code for let updates.
+     */
+
+    ret = njs_generate_for_resolve_closure(vm, condition, generator->count);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
     /* GCC complains about uninitialized jump_offset. */
     jump_offset = 0;
 
@@ -1142,10 +1271,20 @@
 
     /* The loop update. */
 
-    njs_generate_patch_block(vm, generator, generator->block->continuation);
-
     update = node->right;
 
+    ret = njs_generate_for_resolve_closure(vm, update, generator->count);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    ret = njs_generate_for_let_update(vm, generator, init, generator->count);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    njs_generate_patch_block(vm, generator, generator->block->continuation);
+
     ret = njs_generator(vm, generator, update);
     if (njs_slow_path(ret != NJS_OK)) {
         return ret;
@@ -1186,13 +1325,95 @@
 
 
 static njs_int_t
+njs_generate_for_let_update(njs_vm_t *vm, njs_generator_t *generator,
+    njs_parser_node_t *node, size_t depth)
+{
+    njs_parser_node_t         *let;
+    njs_vmcode_variable_t     *code_var;
+    njs_variable_reference_t  *ref;
+
+    if (node == NULL) {
+        return NJS_OK;
+    }
+
+    if (depth >= NJS_GENERATE_MAX_DEPTH) {
+        return NJS_ERROR;
+    }
+
+    if (node->token_type != NJS_TOKEN_STATEMENT) {
+        return NJS_OK;
+    }
+
+    let = node->right;
+
+    if (let->token_type != NJS_TOKEN_LET) {
+        return NJS_OK;
+    }
+
+    ref = &let->left->u.reference;
+
+    if (ref->variable->closure) {
+        njs_generate_code(generator, njs_vmcode_variable_t, code_var,
+                          NJS_VMCODE_LET_UPDATE, 0, let);
+        code_var->dst = let->left->index;
+    }
+
+    return njs_generate_for_let_update(vm, generator, node->left, depth + 1);
+}
+
+
+static njs_int_t
+njs_generate_for_resolve_closure(njs_vm_t *vm, njs_parser_node_t *node,
+    size_t depth)
+{
+    njs_int_t       ret;
+    njs_bool_t      closure;
+    njs_variable_t  *var;
+
+    if (node == NULL) {
+        return NJS_OK;
+    }
+
+    if (node->token_type == NJS_TOKEN_NAME) {
+        var = njs_variable_resolve(vm, node);
+
+        if (njs_fast_path(var != NULL)) {
+            closure = njs_variable_closure_test(node->scope, var->scope);
+
+            if (closure) {
+                var->closure = 1;
+            }
+        }
+    }
+
+    if (depth >= NJS_GENERATE_MAX_DEPTH) {
+        njs_range_error(vm, "Maximum call stack size exceeded");
+        return NJS_ERROR;
+    }
+
+    ret = njs_generate_for_resolve_closure(vm, node->left, depth + 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    ret = njs_generate_for_resolve_closure(vm, node->right, depth + 1);
+    if (njs_slow_path(ret != NJS_OK)) {
+        return ret;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_int_t
 njs_generate_for_in_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
     njs_int_t                  ret;
     njs_index_t                index;
+    njs_variable_t             *var;
     njs_jump_off_t             loop_offset, prop_offset;
-    njs_parser_node_t          *foreach;
+    njs_parser_node_t          *foreach, *name;
     njs_vmcode_prop_next_t     *prop_next;
     njs_vmcode_prop_foreach_t  *prop_foreach;
 
@@ -1205,15 +1426,36 @@
     /* The object. */
 
     foreach = node->left;
+    name = foreach->left->right;
 
-    ret = njs_generator(vm, generator, foreach->left);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
-    }
+    if (name != NULL) {
+        name = name->left;
 
-    ret = njs_generator(vm, generator, foreach->right);
-    if (njs_slow_path(ret != NJS_OK)) {
-        return ret;
+        ret = njs_generate_variable_wo_dest(vm, generator, name,
+                                            NJS_DECLARATION, &var);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return NJS_ERROR;
+        }
+
+        foreach->left->index = name->index;
+
+        ret = njs_generator(vm, generator, foreach->right);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        var->init = 1;
+
+    } else {
+        ret = njs_generator(vm, generator, foreach->left);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
+        ret = njs_generator(vm, generator, foreach->right);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
     }
 
     njs_generate_code(generator, njs_vmcode_prop_foreach_t, prop_foreach,
@@ -1239,6 +1481,14 @@
 
     /* The loop iterator. */
 
+    if (name != NULL) {
+        ret = njs_generate_for_let_update(vm, generator, foreach->left,
+                                          generator->count);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+    }
+
     njs_generate_patch_block(vm, generator, generator->block->continuation);
 
     njs_code_set_jump_offset(generator, njs_vmcode_prop_foreach_t, prop_offset);
@@ -1564,12 +1814,38 @@
 njs_generate_statement(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_int_t  ret;
+    njs_int_t              ret;
+    njs_variable_t         *var;
+    njs_parser_node_t      *right;
+    njs_vmcode_variable_t  *code;
+
+    right = node->right;
+
+    if (right != NULL && right->token_type == NJS_TOKEN_NAME) {
+        var = njs_variable_reference(vm, right);
+        if (njs_slow_path(var == NULL)) {
+            goto statement;
+        }
+
+        if (!var->init && var->type == NJS_VARIABLE_LET) {
+            njs_generate_code(generator, njs_vmcode_variable_t, code,
+                              NJS_VMCODE_INITIALIZATION_TEST, 0, right);
+            code->dst = right->index;
+        }
+
+        if (node->left == NULL) {
+            return NJS_OK;
+        }
+
+        node = node->left;
+    }
+
+statement:
 
     ret = njs_generate_children(vm, generator, node);
 
     if (njs_fast_path(ret == NJS_OK)) {
-        return njs_generate_node_index_release(vm, generator, node->right);
+        return njs_generate_node_index_release(vm, generator, right);
     }
 
     return ret;
@@ -3311,8 +3587,9 @@
 njs_generate_dest_index(njs_vm_t *vm, njs_generator_t *generator,
     njs_parser_node_t *node)
 {
-    njs_index_t        ret;
-    njs_parser_node_t  *dest;
+    njs_index_t         ret;
+    njs_parser_node_t   *dest;
+    njs_parser_scope_t  *scope;
 
     ret = njs_generate_children_indexes_release(vm, generator, node);
     if (njs_slow_path(ret != NJS_OK)) {
@@ -3322,7 +3599,11 @@
     dest = node->dest;
 
     if (dest != NULL && dest->index != NJS_INDEX_NONE) {
-        return dest->index;
+        scope = njs_function_scope(node->scope);
+
+        if (!scope->dest_disable) {
+            return dest->index;
+        }
     }
 
     return njs_generate_node_temp_index_get(vm, generator, node);
@@ -3386,7 +3667,8 @@
         return NJS_ERROR;
     }
 
-    return njs_scope_index(scope->type, scope->temp++, NJS_LEVEL_TEMP);
+    return njs_scope_index(scope->type, scope->temp++, NJS_LEVEL_TEMP,
+                           NJS_VARIABLE_VAR);
 }
 
 
diff --git a/src/njs_module.c b/src/njs_module.c
index fa3212a..fb67b9b 100644
--- a/src/njs_module.c
+++ b/src/njs_module.c
@@ -574,7 +574,8 @@
     scope = njs_parser_global_scope(parser);
     vm = parser->vm;
 
-    module->index = njs_scope_index(scope->type, scope->items, NJS_LEVEL_LOCAL);
+    module->index = njs_scope_index(scope->type, scope->items, NJS_LEVEL_LOCAL,
+                                    NJS_VARIABLE_VAR);
     scope->items++;
 
     if (vm->modules == NULL) {
diff --git a/src/njs_parser.c b/src/njs_parser.c
index 2b41ae7..616b832 100644
--- a/src/njs_parser.c
+++ b/src/njs_parser.c
@@ -238,6 +238,8 @@
 static njs_int_t njs_parser_statement_list_item(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 
+static njs_int_t njs_parser_lexical_declaration(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_variable_statement(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_variable_declaration_list(njs_parser_t *parser,
@@ -287,7 +289,8 @@
 static njs_int_t njs_parser_iteration_statement_for_map(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
-    njs_lexer_token_t *token, njs_queue_link_t *current);
+    njs_lexer_token_t *token, njs_queue_link_t *current,
+    njs_token_type_t token_type);
 static njs_int_t njs_parser_for_var_in_statement(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_for_var_in_statement_after(njs_parser_t *parser,
@@ -311,6 +314,8 @@
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_switch_block(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
+static njs_int_t njs_parser_switch_block_after(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_switch_case(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_switch_case_wo_def(njs_parser_t *parser,
@@ -362,6 +367,8 @@
     njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_catch_parenthesis(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
+static njs_int_t njs_parser_catch_statement_open_brace(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current);
 static njs_int_t njs_parser_catch_finally(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
 
@@ -506,8 +513,10 @@
 njs_int_t
 njs_parser(njs_vm_t *vm, njs_parser_t *parser)
 {
-    njs_int_t          ret;
-    njs_lexer_token_t  *token;
+    njs_int_t                        ret;
+    njs_str_t                        str;
+    njs_lexer_token_t                *token;
+    const njs_lexer_keyword_entry_t  *keyword;
 
     parser->vm = vm;
 
@@ -526,6 +535,16 @@
         parser->ret = NJS_OK;
     }
 
+    /* Add this as first variable. */
+    njs_string_get(&njs_string_undefined, &str);
+
+    keyword = njs_lexer_keyword(str.start, str.length);
+    if (njs_slow_path(keyword == NULL)) {
+        return NJS_ERROR;
+    }
+
+    parser->undefined_id = (uintptr_t) keyword->value;
+
     njs_queue_init(&parser->stack);
 
     parser->target = NULL;
@@ -645,7 +664,8 @@
                 return NJS_ERROR;
             }
 
-            var->index = njs_scope_index(type, 0, NJS_LEVEL_LOCAL);
+            var->index = njs_scope_index(type, 0, NJS_LEVEL_LOCAL,
+                                         NJS_VARIABLE_VAR);
         }
     }
 
@@ -737,14 +757,6 @@
 
 
 static njs_int_t
-njs_parser_lexical_declaration(njs_parser_t *parser, njs_lexer_token_t *token,
-    njs_queue_link_t *current)
-{
-    return njs_parser_not_supported(parser, token);
-}
-
-
-static njs_int_t
 njs_parser_function_or_generator(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current)
 {
@@ -890,12 +902,14 @@
 
 
 static njs_int_t
-njs_parser_set_line_state(njs_parser_t *parser,
+njs_parser_iteration_statement_for_end(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current)
 {
     parser->node->token_line = (uint32_t) (uintptr_t) parser->target;
     parser->target = NULL;
 
+    njs_parser_scope_end(parser);
+
     return njs_parser_stack_pop(parser);
 }
 
@@ -4534,7 +4548,7 @@
     switch (token->type) {
     case NJS_TOKEN_CLASS:
         njs_parser_next(parser, njs_parser_class_declaration);
-        break;
+        return NJS_OK;
 
     case NJS_TOKEN_LET:
     case NJS_TOKEN_CONST:
@@ -4570,7 +4584,8 @@
         return NJS_DECLINED;
     }
 
-    return NJS_OK;
+    return njs_parser_after(parser, current, parser->node, 1,
+                            njs_parser_statement_after);
 }
 
 
@@ -4733,12 +4748,32 @@
 
 
 /*
+ * 13.3.1 Let and Const Declarations
+ */
+static njs_int_t
+njs_parser_lexical_declaration(njs_parser_t *parser, njs_lexer_token_t *token,
+    njs_queue_link_t *current)
+{
+    parser->var_type = (token->type == NJS_TOKEN_LET) ? NJS_VARIABLE_LET
+                                                      : NJS_VARIABLE_CONST;
+
+    njs_lexer_consume_token(parser->lexer, 1);
+
+    njs_parser_next(parser, njs_parser_variable_declaration_list);
+
+    return njs_parser_after(parser, current, NULL, 1, njs_parser_semicolon);
+}
+
+
+/*
  * 13.3.2 Variable Statement
  */
 static njs_int_t
 njs_parser_variable_statement(njs_parser_t *parser, njs_lexer_token_t *token,
     njs_queue_link_t *current)
 {
+    parser->var_type = NJS_VARIABLE_VAR;
+
     njs_parser_next(parser, njs_parser_variable_declaration_list);
 
     return njs_parser_after(parser, current, NULL, 1, njs_parser_semicolon);
@@ -4789,6 +4824,7 @@
 {
     njs_int_t          ret;
     njs_variable_t     *var;
+    njs_token_type_t   type;
     njs_parser_node_t  *name;
 
     ret = njs_parser_binding_pattern(parser, token, current);
@@ -4807,14 +4843,14 @@
         return NJS_DONE;
     }
 
-    name = njs_parser_variable_node(parser, token->unique_id, NJS_VARIABLE_VAR,
+    name = njs_parser_variable_node(parser, token->unique_id, parser->var_type,
                                     &var);
     if (name == NULL) {
         return NJS_ERROR;
     }
 
     if (var->self) {
-        var->type = NJS_VARIABLE_VAR;
+        var->type = parser->var_type;
         var->self = 0;
     }
 
@@ -4829,7 +4865,21 @@
         return NJS_ERROR;
     }
 
-    ret = njs_parser_initializer_assign(parser, NJS_TOKEN_VAR);
+    switch (parser->var_type) {
+    case NJS_VARIABLE_LET:
+        type = NJS_TOKEN_LET;
+        break;
+
+    case NJS_VARIABLE_CONST:
+        type = NJS_TOKEN_CONST;
+        break;
+
+    default:
+        type = NJS_TOKEN_VAR;
+        break;
+    }
+
+    ret = njs_parser_initializer_assign(parser, type);
     if (ret != NJS_OK) {
         return ret;
     }
@@ -4930,6 +4980,12 @@
             return NJS_ERROR;
         }
 
+        if (token->type == NJS_TOKEN_NAME) {
+            njs_parser_syntax_error(parser, "let declaration cannot appear "
+                                            "in a single-statement context");
+            return NJS_DONE;
+        }
+
         if (token->type == NJS_TOKEN_OPEN_BRACKET) {
             return njs_parser_failed(parser);
         }
@@ -5172,14 +5228,21 @@
 njs_parser_iteration_statement_for(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current)
 {
+    njs_int_t  ret;
+
     if (token->type == NJS_TOKEN_OPEN_PARENTHESIS) {
         njs_lexer_consume_token(parser->lexer, 1);
 
+        ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0);
+        if (njs_slow_path(ret != NJS_OK)) {
+            return ret;
+        }
+
         njs_parser_next(parser, njs_parser_iteration_statement_for_map);
 
         return njs_parser_after(parser, current,
                                 (void *) (uintptr_t) parser->line, 1,
-                                njs_parser_set_line_state);
+                                njs_parser_iteration_statement_for_end);
     }
 
     if (token->type == NJS_TOKEN_AWAIT) {
@@ -5194,8 +5257,9 @@
 njs_parser_iteration_statement_for_map(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current)
 {
-    njs_int_t  ret;
-    njs_str_t  *text;
+    njs_int_t         ret;
+    njs_str_t         *text;
+    njs_token_type_t  token_type;
 
     /*
      * "var" <VariableDeclarationList> ";" <Expression>? ";" <Expression>? ")"
@@ -5242,6 +5306,9 @@
         return NJS_OK;
 
     case NJS_TOKEN_VAR:
+    case NJS_TOKEN_LET:
+        token_type = token->type;
+
         token = njs_lexer_peek_token(parser->lexer, token, 0);
         if (token == NULL) {
             return NJS_ERROR;
@@ -5249,7 +5316,8 @@
 
         njs_lexer_consume_token(parser->lexer, 1);
 
-        ret = njs_parser_for_var_binding_or_var_list(parser, token, current);
+        ret = njs_parser_for_var_binding_or_var_list(parser, token,
+                                                     current, token_type);
         if (ret != NJS_OK) {
             if (ret == NJS_DONE) {
                 return NJS_OK;
@@ -5260,7 +5328,6 @@
 
         break;
 
-    case NJS_TOKEN_LET:
     case NJS_TOKEN_CONST:
         return njs_parser_not_supported(parser, token);
 
@@ -5288,11 +5355,23 @@
 
 static njs_int_t
 njs_parser_for_var_binding_or_var_list(njs_parser_t *parser,
-    njs_lexer_token_t *token, njs_queue_link_t *current)
+    njs_lexer_token_t *token, njs_queue_link_t *current,
+    njs_token_type_t token_type)
 {
-    njs_int_t          ret;
-    njs_lexer_token_t  *next;
-    njs_parser_node_t  *node, *var;
+    njs_int_t            ret;
+    njs_lexer_token_t    *next;
+    njs_parser_node_t    *node, *var, *node_type, *statement;
+    njs_variable_type_t  type;
+
+    switch (token_type) {
+    case NJS_TOKEN_LET:
+        type = NJS_VARIABLE_LET;
+        break;
+
+    default:
+        type = NJS_VARIABLE_VAR;
+        break;
+    }
 
     switch (token->type) {
     /* BindingPattern */
@@ -5318,18 +5397,33 @@
             }
 
             if (next->type != NJS_TOKEN_IN) {
+                parser->var_type = type;
+
                 njs_parser_next(parser, njs_parser_variable_declaration_list);
                 return NJS_OK;
             }
 
+            statement = njs_parser_node_new(parser, NJS_TOKEN_STATEMENT);
+            if (njs_slow_path(statement == NULL)) {
+                return NJS_ERROR;
+            }
+
+            node_type = njs_parser_node_new(parser, token_type);
+            if (njs_slow_path(node_type == NULL)) {
+                return NJS_ERROR;
+            }
+
             var = njs_parser_variable_node(parser, token->unique_id,
-                                            NJS_VARIABLE_VAR, NULL);
+                                           type, NULL);
             if (var == NULL) {
                 return NJS_ERROR;
             }
 
+            node_type->token_line = token->line;
             var->token_line = token->line;
 
+            statement->right = node_type;
+            node_type->left = var;
             parser->node = NULL;
 
             node = njs_parser_node_new(parser, NJS_TOKEN_IN);
@@ -5338,7 +5432,7 @@
             }
 
             node->token_line = next->line;
-            node->left = var;
+            node->left = statement;
 
             njs_parser_next(parser, njs_parser_expression);
 
@@ -5829,6 +5923,8 @@
 njs_parser_switch_block(njs_parser_t *parser, njs_lexer_token_t *token,
     njs_queue_link_t *current)
 {
+    njs_int_t  ret;
+
     if (token->type != NJS_TOKEN_OPEN_BRACE) {
         return njs_parser_failed(parser);
     }
@@ -5837,9 +5933,24 @@
 
     parser->target->left = parser->node;
 
+    ret = njs_parser_scope_begin(parser, NJS_SCOPE_BLOCK, 0);
+    if (ret != NJS_OK) {
+        return NJS_ERROR;
+    }
+
     njs_parser_next(parser, njs_parser_switch_case);
 
-    return NJS_OK;
+    return njs_parser_after(parser, current, NULL, 1,
+                            njs_parser_switch_block_after);
+}
+
+static njs_int_t
+njs_parser_switch_block_after(njs_parser_t *parser, njs_lexer_token_t *token,
+    njs_queue_link_t *current)
+{
+    njs_parser_scope_end(parser);
+
+    return njs_parser_stack_pop(parser);
 }
 
 
@@ -6310,8 +6421,6 @@
 {
     njs_parser_node_t  *node;
 
-    njs_parser_scope_end(parser);
-
     parser->target->right->right = parser->node;
 
     if (token->type == NJS_TOKEN_FINALLY) {
@@ -6355,7 +6464,7 @@
     parser->target->right->right = parser->node;
     parser->node = NULL;
 
-    njs_parser_next(parser, njs_parser_block_statement_open_brace);
+    njs_parser_next(parser, njs_parser_catch_statement_open_brace);
 
     return njs_parser_after(parser, current, parser->target, 1,
                             njs_parser_catch_after);
@@ -6363,6 +6472,42 @@
 
 
 static njs_int_t
+njs_parser_catch_statement_open_brace(njs_parser_t *parser,
+    njs_lexer_token_t *token, njs_queue_link_t *current)
+{
+    void  *target;
+
+    if (token->type != NJS_TOKEN_OPEN_BRACE) {
+        return njs_parser_failed(parser);
+    }
+
+    parser->line = token->line;
+
+    njs_lexer_consume_token(parser->lexer, 1);
+
+    token = njs_lexer_token(parser->lexer, 0);
+    if (token == NULL) {
+        return NJS_ERROR;
+    }
+
+    target = (void *) (uintptr_t) parser->line;
+    parser->node = NULL;
+
+    if (token->type == NJS_TOKEN_CLOSE_BRACE) {
+        parser->target = target;
+
+        njs_parser_next(parser, njs_parser_block_statement_close_brace);
+        return NJS_OK;
+    }
+
+    njs_parser_next(parser, njs_parser_statement_list);
+
+    return njs_parser_after(parser, current, target, 0,
+                            njs_parser_block_statement_close_brace);
+}
+
+
+static njs_int_t
 njs_parser_catch_finally(njs_parser_t *parser, njs_lexer_token_t *token,
     njs_queue_link_t *current)
 {
@@ -6569,7 +6714,7 @@
     var = (njs_variable_t *) parser->target;
 
     var->index = njs_scope_index(var->scope->type, var->scope->items,
-                                 NJS_LEVEL_LOCAL);
+                                 NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR);
     var->scope->items++;
 
     if (var->self) {
@@ -6777,7 +6922,7 @@
         arg->argument = 1;
 
         var->index = njs_scope_index(parser->scope->type, parser->scope->items,
-                                     NJS_LEVEL_LOCAL);
+                                     NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR);
         parser->scope->items++;
 
         lambda->self = var->index;
@@ -6815,7 +6960,7 @@
     *vv = NULL;
 
     var->index = njs_scope_index(var->scope->type, var->scope->items,
-                                 NJS_LEVEL_LOCAL);
+                                 NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR);
     var->scope->items++;
 
     parser->target->u.value.data.u.lambda->self = var->index;
@@ -7474,7 +7619,7 @@
     *vv = NULL;
 
     var->index = njs_scope_index(var->scope->type, var->scope->items,
-                                 NJS_LEVEL_LOCAL);
+                                 NJS_LEVEL_LOCAL, NJS_VARIABLE_VAR);
     var->scope->items++;
 
     parser->node->u.value.data.u.lambda->self = var->index;
@@ -7644,7 +7789,8 @@
             token->unique_id = (uintptr_t) keyword->value;
 
         } else if (!scope->arrow_function) {
-            index = njs_scope_index(scope->type, 0, NJS_LEVEL_LOCAL);
+            index = njs_scope_index(scope->type, 0, NJS_LEVEL_LOCAL,
+                                    NJS_VARIABLE_VAR);
 
             var = njs_variable_scope_add(parser, scope, scope, token->unique_id,
                                          NJS_VARIABLE_VAR, index);
diff --git a/src/njs_parser.h b/src/njs_parser.h
index 854ac9b..e237d1f 100644
--- a/src/njs_parser.h
+++ b/src/njs_parser.h
@@ -29,6 +29,7 @@
     njs_scope_t                     type:8;
     uint8_t                         module;
     uint8_t                         arrow_function;
+    uint8_t                         dest_disable;
 };
 
 
@@ -76,7 +77,9 @@
     njs_parser_node_t               *node;
     njs_parser_node_t               *target;
     njs_parser_scope_t              *scope;
+    njs_variable_type_t             var_type;
     njs_int_t                       ret;
+    uintptr_t                       undefined_id;
     njs_bool_t                      strict_semicolon;
     uint32_t                        line;
 };
@@ -108,6 +111,8 @@
 
 njs_int_t njs_parser_module_lambda(njs_parser_t *parser,
     njs_lexer_token_t *token, njs_queue_link_t *current);
+njs_bool_t njs_variable_closure_test(njs_parser_scope_t *root,
+    njs_parser_scope_t *scope);
 njs_variable_t *njs_variable_resolve(njs_vm_t *vm, njs_parser_node_t *node);
 njs_index_t njs_variable_index(njs_vm_t *vm, njs_parser_node_t *node);
 njs_bool_t njs_parser_has_side_effect(njs_parser_node_t *node);
diff --git a/src/njs_scope.c b/src/njs_scope.c
index 4d903cd..29ab18a 100644
--- a/src/njs_scope.c
+++ b/src/njs_scope.c
@@ -20,7 +20,8 @@
         return NJS_INDEX_ERROR;
     }
 
-    return njs_scope_index(NJS_SCOPE_GLOBAL, scope->temp++, NJS_LEVEL_TEMP);
+    return njs_scope_index(NJS_SCOPE_GLOBAL, scope->temp++, NJS_LEVEL_TEMP,
+                           NJS_VARIABLE_VAR);
 }
 
 
@@ -103,7 +104,8 @@
 
     vm->levels[NJS_LEVEL_STATIC] = vm->scope_absolute->start;
 
-    *retval = njs_scope_index(NJS_SCOPE_GLOBAL, index, NJS_LEVEL_STATIC);
+    *retval = njs_scope_index(NJS_SCOPE_GLOBAL, index, NJS_LEVEL_STATIC,
+                              NJS_VARIABLE_VAR);
 
     return *retval;
 }
diff --git a/src/njs_scope.h b/src/njs_scope.h
index 0303da4..6771e58 100644
--- a/src/njs_scope.h
+++ b/src/njs_scope.h
@@ -8,10 +8,11 @@
 #define _NJS_SCOPE_H_INCLUDED_
 
 
-#define NJS_SCOPE_TYPE_SIZE     4
-#define NJS_SCOPE_VALUE_OFFSET  (NJS_SCOPE_TYPE_SIZE + 1)
+#define NJS_SCOPE_VAR_SIZE      4
+#define NJS_SCOPE_TYPE_OFFSET   (NJS_SCOPE_VAR_SIZE + 4)
+#define NJS_SCOPE_VALUE_OFFSET  (NJS_SCOPE_TYPE_OFFSET + 1)
 #define NJS_SCOPE_VALUE_MAX     ((1 << (32 - NJS_SCOPE_VALUE_OFFSET)) - 1)
-#define NJS_SCOPE_TYPE_MASK     ((NJS_SCOPE_VALUE_MAX) << NJS_SCOPE_TYPE_SIZE)
+#define NJS_SCOPE_TYPE_MASK     ((NJS_SCOPE_VALUE_MAX) << NJS_SCOPE_VAR_SIZE)
 
 #define NJS_INDEX_NONE          ((njs_index_t) 0)
 #define NJS_INDEX_ERROR         ((njs_index_t) -1)
@@ -25,7 +26,8 @@
 
 
 njs_inline njs_index_t
-njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type)
+njs_scope_index(njs_scope_t scope, njs_index_t index, njs_level_type_t type,
+                njs_variable_type_t var_type)
 {
     if (index > NJS_SCOPE_VALUE_MAX || type >= NJS_LEVEL_MAX
         || (scope != NJS_SCOPE_GLOBAL && scope != NJS_SCOPE_FUNCTION))
@@ -37,14 +39,23 @@
         type = NJS_LEVEL_GLOBAL;
     }
 
-    return (index << NJS_SCOPE_VALUE_OFFSET) | type;
+    return (index << NJS_SCOPE_VALUE_OFFSET) | (type << NJS_SCOPE_VAR_SIZE)
+            | var_type;
+}
+
+
+njs_inline njs_variable_type_t
+njs_scope_index_var(njs_index_t index)
+{
+    return (njs_variable_type_t) (index & ~NJS_SCOPE_TYPE_MASK);
 }
 
 
 njs_inline njs_level_type_t
 njs_scope_index_type(njs_index_t index)
 {
-    return (njs_level_type_t) (index & ~NJS_SCOPE_TYPE_MASK);
+    return (njs_level_type_t) ((index >> NJS_SCOPE_VAR_SIZE)
+                               & ~NJS_SCOPE_TYPE_MASK);
 }
 
 
@@ -71,6 +82,12 @@
     value = njs_scope_value(vm, index);
 
     if (!njs_is_valid(value)) {
+        if (njs_scope_index_var(index) == NJS_VARIABLE_LET) {
+            njs_reference_error(vm, "cannot access to variable "
+                                    "before initialization");
+            return NULL;
+        }
+
         njs_set_undefined(value);
     }
 
@@ -115,7 +132,8 @@
 njs_inline njs_index_t
 njs_scope_global_this_index()
 {
-    return njs_scope_index(NJS_SCOPE_GLOBAL, 0, NJS_LEVEL_LOCAL);
+    return njs_scope_index(NJS_SCOPE_GLOBAL, 0, NJS_LEVEL_LOCAL,
+                           NJS_VARIABLE_VAR);
 }
 
 
diff --git a/src/njs_variable.c b/src/njs_variable.c
index ee796cb..5a532fb 100644
--- a/src/njs_variable.c
+++ b/src/njs_variable.c
@@ -75,7 +75,8 @@
 
         *declr = &var->value;
 
-        var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL);
+        var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL,
+                                     type);
         root->items++;
     }
 
@@ -177,15 +178,42 @@
     njs_parser_scope_t       *root;
     const njs_lexer_entry_t  *entry;
 
-    if (type != NJS_VARIABLE_VAR && type != NJS_VARIABLE_FUNCTION) {
-        return scope;
-    }
-
     root = njs_variable_scope(scope, unique_id, &var, type);
     if (njs_slow_path(root == NULL)) {
         return NULL;
     }
 
+    switch (type) {
+    case NJS_VARIABLE_LET:
+        if (scope->type == NJS_SCOPE_GLOBAL
+            && parser->undefined_id == unique_id)
+        {
+            goto failed;
+        }
+
+        if (root != scope) {
+            return scope;
+        }
+
+        if (var != NULL && var->scope == root) {
+            if (var->self) {
+                var->function = 0;
+                return scope;
+            }
+
+            goto failed;
+        }
+
+        return scope;
+
+    case NJS_VARIABLE_VAR:
+    case NJS_VARIABLE_FUNCTION:
+        break;
+
+    default:
+        return scope;
+    }
+
     if (type == NJS_VARIABLE_FUNCTION) {
         root = scope;
     }
@@ -194,6 +222,10 @@
         return root;
     }
 
+    if (var->type == NJS_VARIABLE_LET) {
+        goto failed;
+    }
+
     if (var->original->type == NJS_SCOPE_BLOCK) {
         if (type == NJS_VARIABLE_FUNCTION
             || var->type == NJS_VARIABLE_FUNCTION)
@@ -269,7 +301,8 @@
             return NULL;
         }
 
-        var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL);
+        var->index = njs_scope_index(root->type, root->items, NJS_LEVEL_LOCAL,
+                                     type);
         root->items++;
     }
 
@@ -348,7 +381,7 @@
 }
 
 
-static njs_bool_t
+njs_bool_t
 njs_variable_closure_test(njs_parser_scope_t *root, njs_parser_scope_t *scope)
 {
     if (root == scope) {
@@ -454,7 +487,7 @@
             /* Create new closure for scope. */
 
             index = njs_scope_index(root->type, root->closures->items,
-                                    NJS_LEVEL_CLOSURE);
+                                    NJS_LEVEL_CLOSURE, var->type);
             if (njs_slow_path(index == NJS_INDEX_ERROR)) {
                 return NJS_INDEX_ERROR;
             }
@@ -493,6 +526,7 @@
 njs_variable_t *
 njs_variable_reference(njs_vm_t *vm, njs_parser_node_t *node)
 {
+    njs_bool_t                closure;
     njs_rbtree_node_t         *rb_node;
     njs_parser_scope_t        *scope;
     njs_parser_rbtree_node_t  *parse_node, ref_node;
@@ -510,7 +544,7 @@
         }
     }
 
-    ref->closure = njs_variable_closure_test(node->scope, ref->variable->scope);
+    closure = njs_variable_closure_test(node->scope, ref->variable->scope);
     ref->scope = node->scope;
 
     ref_node.key = ref->unique_id;
@@ -528,12 +562,14 @@
         return ref->variable;
     }
 
-    if (!ref->closure) {
+    if (!closure) {
         node->index = ref->variable->index;
 
         return ref->variable;
     }
 
+    ref->variable->closure = closure;
+
     node->index = njs_variable_closure(vm, ref->variable, scope);
     if (njs_slow_path(node->index == NJS_INDEX_ERROR)) {
         return NULL;
diff --git a/src/njs_variable.h b/src/njs_variable.h
index 8759e04..56d54e9 100644
--- a/src/njs_variable.h
+++ b/src/njs_variable.h
@@ -24,6 +24,8 @@
     njs_bool_t            argument;
     njs_bool_t            arguments_object;
     njs_bool_t            self;
+    njs_bool_t            init;
+    njs_bool_t            closure;
     njs_bool_t            function;
 
     njs_parser_scope_t    *scope;
@@ -47,7 +49,6 @@
     njs_variable_t        *variable;
     njs_parser_scope_t    *scope;
     njs_bool_t            not_defined;
-    njs_bool_t            closure;
 } njs_variable_reference_t;
 
 
diff --git a/src/njs_vmcode.c b/src/njs_vmcode.c
index 8eda085..1a99fc7 100644
--- a/src/njs_vmcode.c
+++ b/src/njs_vmcode.c
@@ -64,7 +64,13 @@
     njs_bool_t ctor);
 
 
-#define njs_vmcode_operand(vm, index)  njs_scope_valid_value(vm, index)
+#define njs_vmcode_operand(vm, index, _retval)                                \
+    do {                                                                      \
+        _retval = njs_scope_valid_value(vm, index);                           \
+        if (njs_slow_path(_retval == NULL)) {                                 \
+            goto error;                                                       \
+        }                                                                     \
+    } while (0)
 
 
 njs_int_t
@@ -87,6 +93,7 @@
     njs_property_next_t          *next;
     njs_vmcode_finally_t         *finally;
     njs_vmcode_generic_t         *vmcode;
+    njs_vmcode_variable_t        *var;
     njs_vmcode_move_arg_t        *move_arg;
     njs_vmcode_prop_get_t        *get;
     njs_vmcode_prop_set_t        *set;
@@ -129,12 +136,12 @@
         switch (vmcode->code.operands) {
 
         case NJS_VMCODE_3OPERANDS:
-            value2 = njs_vmcode_operand(vm, vmcode->operand3);
+            njs_vmcode_operand(vm, vmcode->operand3, value2);
 
             /* Fall through. */
 
         case NJS_VMCODE_2OPERANDS:
-            value1 = njs_vmcode_operand(vm, vmcode->operand2);
+            njs_vmcode_operand(vm, vmcode->operand2, value1);
         }
 
         op = vmcode->code.operation;
@@ -150,7 +157,7 @@
         if (op > NJS_VMCODE_NORET) {
 
             if (op == NJS_VMCODE_MOVE) {
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 *retval = *value1;
 
                 pc += sizeof(njs_vmcode_move_t);
@@ -159,7 +166,7 @@
 
             if (op == NJS_VMCODE_PROPERTY_GET) {
                 get = (njs_vmcode_prop_get_t *) pc;
-                retval = njs_vmcode_operand(vm, get->value);
+                njs_vmcode_operand(vm, get->value, retval);
 
                 ret = njs_value_property(vm, value1, value2, retval);
                 if (njs_slow_path(ret == NJS_ERROR)) {
@@ -190,7 +197,7 @@
                 njs_set_number(value1,
                            num + (1 - 2 * ((op - NJS_VMCODE_INCREMENT) >> 1)));
 
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
 
                 if (op & 1) {
                     njs_set_number(retval, num);
@@ -204,7 +211,7 @@
 
             case NJS_VMCODE_GLOBAL_GET:
                 get = (njs_vmcode_prop_get_t *) pc;
-                retval = njs_vmcode_operand(vm, get->value);
+                njs_vmcode_operand(vm, get->value, retval);
 
                 ret = njs_value_property(vm, value1, value2, retval);
                 if (njs_slow_path(ret == NJS_ERROR)) {
@@ -224,7 +231,7 @@
              * njs_vmcode_finally(), and jumps to the nearest try_break block.
              */
             case NJS_VMCODE_TRY_RETURN:
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 *retval = *value1;
 
                 try_return = (njs_vmcode_try_return_t *) pc;
@@ -266,7 +273,7 @@
                     goto error;
                 }
 
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
 
                 if (op == NJS_VMCODE_ADDITION) {
                     if (njs_fast_path(njs_is_numeric(value1)
@@ -335,7 +342,7 @@
 
                 ret ^= op - NJS_VMCODE_EQUAL;
 
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 njs_set_boolean(retval, ret);
 
                 pc += sizeof(njs_vmcode_3addr_t);
@@ -372,7 +379,7 @@
 
                 num = njs_number(value1);
 
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 pc += sizeof(njs_vmcode_3addr_t);
 
                 switch (op) {
@@ -490,7 +497,7 @@
 
                 ret ^= op - NJS_VMCODE_STRICT_EQUAL;
 
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 njs_set_boolean(retval, ret);
 
                 pc += sizeof(njs_vmcode_3addr_t);
@@ -515,7 +522,7 @@
                     ret = sizeof(njs_vmcode_3addr_t);
                 }
 
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 *retval = *value1;
 
                 pc += ret;
@@ -534,7 +541,7 @@
                 }
 
                 num = njs_number(value1);
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
 
                 switch (op) {
                 case NJS_VMCODE_UNARY_NEGATION:
@@ -553,7 +560,7 @@
                 goto next;
 
             case NJS_VMCODE_LOGICAL_NOT:
-                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                njs_vmcode_operand(vm, vmcode->operand1, retval);
                 njs_set_boolean(retval, !njs_is_true(value1));
 
                 pc += sizeof(njs_vmcode_2addr_t);
@@ -605,7 +612,7 @@
                 break;
             }
 
-            retval = njs_vmcode_operand(vm, vmcode->operand1);
+            njs_vmcode_operand(vm, vmcode->operand1, retval);
             njs_release(vm, retval);
             *retval = vm->retval;
 
@@ -619,7 +626,7 @@
                 hint = move_arg->dst;
 
                 value1 = &native->arguments_offset[hint];
-                value2 = njs_vmcode_operand(vm, move_arg->src);
+                njs_vmcode_operand(vm, move_arg->src, value2);
 
                 *value1 = *value2;
 
@@ -627,7 +634,7 @@
                 break;
 
             case NJS_VMCODE_STOP:
-                value2 = njs_vmcode_operand(vm, (njs_index_t) value2);
+                njs_vmcode_operand(vm, (njs_index_t) value2, value2);
                 vm->retval = *value2;
 
                 return NJS_OK;
@@ -638,7 +645,7 @@
 
             case NJS_VMCODE_PROPERTY_SET:
                 set = (njs_vmcode_prop_set_t *) pc;
-                retval = njs_vmcode_operand(vm, set->value);
+                njs_vmcode_operand(vm, set->value, retval);
 
                 ret = njs_value_property_set(vm, value1, value2, retval);
                 if (njs_slow_path(ret == NJS_ERROR)) {
@@ -650,7 +657,7 @@
 
             case NJS_VMCODE_PROPERTY_ACCESSOR:
                 accessor = (njs_vmcode_prop_accessor_t *) pc;
-                function = njs_vmcode_operand(vm, accessor->value);
+                njs_vmcode_operand(vm, accessor->value, function);
 
                 ret = njs_value_to_key(vm, &name, value2);
                 if (njs_slow_path(ret != NJS_OK)) {
@@ -693,7 +700,7 @@
 
             case NJS_VMCODE_PROPERTY_INIT:
                 set = (njs_vmcode_prop_set_t *) pc;
-                retval = njs_vmcode_operand(vm, set->value);
+                njs_vmcode_operand(vm, set->value, retval);
                 ret = njs_vmcode_property_init(vm, value1, value2, retval);
                 if (njs_slow_path(ret == NJS_ERROR)) {
                     goto error;
@@ -702,7 +709,7 @@
                 break;
 
             case NJS_VMCODE_RETURN:
-                value2 = njs_vmcode_operand(vm, (njs_index_t) value2);
+                njs_vmcode_operand(vm, (njs_index_t) value2, value2);
                 return njs_vmcode_return(vm, NULL, value2);
 
             case NJS_VMCODE_FUNCTION_COPY:
@@ -763,7 +770,7 @@
             case NJS_VMCODE_FUNCTION_CALL:
                 vm->active_frame->native.pc = pc;
 
-                value2 = njs_vmcode_operand(vm, (njs_index_t) value2);
+                njs_vmcode_operand(vm, (njs_index_t) value2, value2);
 
                 ret = njs_function_frame_invoke(vm, value2);
                 if (njs_slow_path(ret == NJS_ERROR)) {
@@ -775,7 +782,7 @@
 
             case NJS_VMCODE_PROPERTY_NEXT:
                 pnext = (njs_vmcode_prop_next_t *) pc;
-                retval = njs_vmcode_operand(vm, pnext->retval);
+                retval = njs_scope_value(vm, pnext->retval);
 
                 next = value2->data.u.next;
 
@@ -801,7 +808,7 @@
 
             case NJS_VMCODE_PROTO_INIT:
                 set = (njs_vmcode_prop_set_t *) pc;
-                retval = njs_vmcode_operand(vm, set->value);
+                njs_vmcode_operand(vm, set->value, retval);
                 ret = njs_vmcode_proto_init(vm, value1, value2, retval);
                 if (njs_slow_path(ret == NJS_ERROR)) {
                     goto error;
@@ -818,7 +825,7 @@
                 break;
 
             case NJS_VMCODE_THROW:
-                value2 = njs_vmcode_operand(vm, (njs_index_t) value2);
+                njs_vmcode_operand(vm, (njs_index_t) value2, value2);
                 vm->retval = *value2;
                 goto error;
 
@@ -877,6 +884,56 @@
 
                 break;
 
+            case NJS_VMCODE_LET:
+                var = (njs_vmcode_variable_t *) pc;
+                value1 = njs_scope_value(vm, var->dst);
+
+                if (njs_is_valid(value1)) {
+                    value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
+                    if (njs_slow_path(value1 == NULL)) {
+                        return NJS_ERROR;
+                    }
+
+                    njs_scope_value_set(vm, var->dst, value1);
+                }
+
+                njs_set_undefined(value1);
+
+                ret = sizeof(njs_vmcode_variable_t);
+                break;
+
+            case NJS_VMCODE_LET_UPDATE:
+                var = (njs_vmcode_variable_t *) pc;
+                value2 = njs_scope_value(vm, var->dst);
+
+                value1 = njs_mp_alloc(vm->mem_pool, sizeof(njs_value_t));
+                if (njs_slow_path(value1 == NULL)) {
+                    return NJS_ERROR;
+                }
+
+                *value1 = *value2;
+
+                njs_scope_value_set(vm, var->dst, value1);
+
+                ret = sizeof(njs_vmcode_variable_t);
+                break;
+
+            case NJS_VMCODE_INITIALIZATION_TEST:
+                var = (njs_vmcode_variable_t *) pc;
+                value1 = njs_scope_value(vm, var->dst);
+
+                if (njs_is_valid(value1)) {
+                    ret = sizeof(njs_vmcode_variable_t);
+                    break;
+                }
+
+                /* Fall through. */
+
+            case NJS_VMCODE_NOT_INITIALIZED:
+                njs_reference_error(vm, "cannot access to variable "
+                                        "before initialization");
+                goto error;
+
             case NJS_VMCODE_ERROR:
                 njs_vmcode_error(vm, pc);
                 goto error;
@@ -1036,7 +1093,11 @@
 
     code = (njs_vmcode_arguments_t *) pc;
 
-    value = njs_vmcode_operand(vm, code->dst);
+    value = njs_scope_valid_value(vm, code->dst);
+    if (njs_slow_path(value == NULL)) {
+        return NJS_ERROR;
+    }
+
     njs_set_object(value, frame->native.arguments_object);
 
     return sizeof(njs_vmcode_arguments_t);
@@ -1077,7 +1138,7 @@
           .u.native = njs_string_prototype_concat
     };
 
-    value = njs_vmcode_operand(vm, (njs_index_t) retval);
+    value = njs_scope_valid_value(vm, (njs_index_t) retval);
 
     if (!njs_is_primitive(value)) {
         array = njs_array(value);
diff --git a/src/njs_vmcode.h b/src/njs_vmcode.h
index 76d1394..f7db975 100644
--- a/src/njs_vmcode.h
+++ b/src/njs_vmcode.h
@@ -57,6 +57,12 @@
     NJS_VMCODE_TRY_END,
     NJS_VMCODE_CATCH,
     NJS_VMCODE_FINALLY,
+
+    NJS_VMCODE_LET,
+    NJS_VMCODE_LET_UPDATE,
+    NJS_VMCODE_INITIALIZATION_TEST,
+    NJS_VMCODE_NOT_INITIALIZED,
+
     NJS_VMCODE_ERROR,
 
     NJS_VMCODE_NORET = 127
@@ -408,6 +414,12 @@
 } njs_vmcode_function_copy_t;
 
 
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                dst;
+} njs_vmcode_variable_t;
+
+
 njs_int_t njs_vmcode_interpreter(njs_vm_t *vm, u_char *pc);
 
 njs_object_t *njs_function_new_object(njs_vm_t *vm, njs_value_t *constructor);
diff --git a/src/test/njs_benchmark.c b/src/test/njs_benchmark.c
index c4385e3..bbe052d 100644
--- a/src/test/njs_benchmark.c
+++ b/src/test/njs_benchmark.c
@@ -213,6 +213,17 @@
       njs_str("100000000"),
       1 },
 
+    { "for let loop 100M",
+      njs_str("let i; for (i = 0; i < 100000000; i++); i"),
+      njs_str("100000000"),
+      1 },
+
+    { "for let closures 1M",
+      njs_str("let a = []; for (let i = 0; i < 1000000; i++) { a.push(() => i); }"
+              "a[5]()"),
+      njs_str("5"),
+      1 },
+
     { "while loop 100M",
       njs_str("var i = 0; while (i < 100000000) { i++ }; i"),
       njs_str("100000000"),
diff --git a/src/test/njs_unit_test.c b/src/test/njs_unit_test.c
index 5d1e3ea..f473dc4 100644
--- a/src/test/njs_unit_test.c
+++ b/src/test/njs_unit_test.c
@@ -19772,6 +19772,243 @@
     { njs_str("var buffer = require('buffer');"
               "typeof buffer.constants.MAX_STRING_LENGTH === 'number' "),
       njs_str("true") },
+
+    /* let */
+
+    { njs_str("let x"),
+      njs_str("undefined") },
+
+    { njs_str("let x = 123; x"),
+      njs_str("123") },
+
+    { njs_str("let x = [123]; x"),
+      njs_str("123") },
+
+    { njs_str("let x = () => x; x()"),
+      njs_str("[object Function]") },
+
+    { njs_str("let x = (() => x)()"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("x; let x"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("x; let x = 123"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let x = x + 123"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let x = (x, 1)"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let x = x"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let x; var x"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("var x; let x"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("let x; function x() {}"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("function x() {} let x"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("function x() {let x; var x}"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("function x() {var x; let x}"),
+      njs_str("SyntaxError: \"x\" has already been declared in 1") },
+
+    { njs_str("var x = function f() {let f}"),
+      njs_str("undefined") },
+
+    { njs_str("let a; let x = 1;"
+              "{let x = 2; a = x}"
+              "[x, a]"),
+      njs_str("1,2") },
+
+    { njs_str("let a; let x = 1;"
+              "if (true) {let x = 2; a = x}"
+              "[x, a]"),
+      njs_str("1,2") },
+
+    { njs_str("var a = 5, b = 10, arr = [];"
+              "{let a = 4; var b = 1; arr.push(a); arr.push(b)}"
+              "arr.push(a); arr.push(b); arr"),
+      njs_str("4,1,5,1") },
+
+    { njs_str("function func() {return x}"
+              "let x = 123;"
+              "func()"),
+      njs_str("123") },
+
+    { njs_str("function func() {return x}"
+              "func();"
+              "let x = 123"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("function func() {return () => x}"
+              "let x = 123;"
+              "func()()"),
+      njs_str("123") },
+
+    { njs_str("function func() {x = x + 1; let x}"),
+      njs_str("undefined") },
+
+    { njs_str("function func() {return () => x}"
+              "func()();"
+              "let x = 123;"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("var arr = [];"
+              ""
+              "for (var i = 0; i < 10; i++) {"
+              "    let x = i;"
+              ""
+              "    arr.push( (n) => {x += n; return x} );"
+              "}"
+              ""
+              "["
+              "    arr[0](2), arr[1](1), arr[2](4), arr[3](7), arr[4](0),"
+              "    arr[5](1), arr[6](2), arr[7](5), arr[8](8), arr[9](10)"
+              "]"),
+      njs_str("2,2,6,10,4,6,8,12,16,19") },
+
+    { njs_str("var arr = [];"
+              ""
+              "for (let i = 0; i < 10; i++) {"
+              "    arr.push( (n) => {i += n; return i} );"
+              "}"
+              ""
+              "["
+              "    arr[0](2), arr[1](1), arr[2](4), arr[3](7), arr[4](0),"
+              "    arr[5](1), arr[6](2), arr[7](5), arr[8](8), arr[9](10)"
+              "]"),
+      njs_str("2,2,6,10,4,6,8,12,16,19") },
+
+    { njs_str("for (let i = 0; i < 1; i++) {"
+              "    let i = i + 2;"
+              "}"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let arr = [], res = [];"
+              "for (let i = 0, f = function() { return i }; i < 5; i++) {"
+              "    arr.push(f);"
+              "}"
+              "for (let i = 0; i < 5; i++) {"
+              "    res.push(arr[i]());"
+              "} res"),
+      njs_str("0,0,0,0,0") },
+
+    { njs_str("let arr = [], res = [];"
+              "for (let i = 0; arr.push(() => i), i < 10; i++) {}"
+              "for (let k = 0; k < 10; k++) {res.push(arr[k]())}"
+              "res"),
+      njs_str("0,1,2,3,4,5,6,7,8,9") },
+
+    { njs_str("let res = [];"
+              "for (let n in [1,2,3]) {res.push(n)}"
+              "res"),
+      njs_str("0,1,2") },
+
+    { njs_str("let arr = [], res = [];"
+              ""
+              "for (let n in [1,2,3]) {"
+              "    arr.push(() => n);"
+              "}"
+              ""
+              "for (let n in arr) {"
+              "    res.push(arr[n]());"
+              "}"
+              "res"),
+      njs_str("0,1,2") },
+
+    { njs_str("let arr = [];"
+              ""
+              "for (let n in [1,2,3]) {"
+              "    let n = 1;"
+              "    arr.push(n);"
+              "}"
+              "arr"),
+      njs_str("1,1,1") },
+
+    { njs_str("for (let n in [1,2,3]) {"
+              "    let n = n + 1;"
+              "}"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("for (let n in [1,2,3]) {}"
+              "n"),
+      njs_str("ReferenceError: \"n\" is not defined") },
+
+    { njs_str("for (let n in [1,n,3]) {}"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("(function() {"
+              "function f() {return x + 1}"
+              "function abc() {f()};"
+              "abc();"
+              "let x;"
+              "}())"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("function func() {var x = 1; {let x = x + 1} } func()"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("if (false) let x = 1"),
+      njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") },
+
+    { njs_str("while (false) let x = 1"),
+      njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") },
+
+    { njs_str("for (;;) let x = 1"),
+      njs_str("SyntaxError: let declaration cannot appear in a single-statement context in 1") },
+
+    { njs_str("try {} catch (e) {let e}"),
+      njs_str("SyntaxError: \"e\" has already been declared in 1") },
+
+    { njs_str("let arr = [], x = 2;"
+              "switch(true) {default: let x = 1; arr.push(x)}"
+              "arr.push(x); arr"),
+      njs_str("1,2") },
+
+    { njs_str("switch(true) {case false: let x = 1; default: x = 2}"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let res;"
+              "switch(true) {case true: let x = 1; default: x = 2; res = x} res"),
+      njs_str("2") },
+
+    { njs_str("let null"),
+      njs_str("SyntaxError: Unexpected token \"null\" in 1") },
+
+    { njs_str("let continue"),
+      njs_str("SyntaxError: Unexpected token \"continue\" in 1") },
+
+    { njs_str("let undefined"),
+      njs_str("SyntaxError: \"undefined\" has already been declared in 1") },
+
+    { njs_str("let a = 1; globalThis.a"),
+      njs_str("undefined") },
+
+    { njs_str("if (false) {x = 2} else {x = 1} let x;"),
+      njs_str("ReferenceError: cannot access to variable before initialization") },
+
+    { njs_str("let let"),
+      njs_str("SyntaxError: Unexpected token \"let\" in 1") },
+
+    { njs_str("let null"),
+      njs_str("SyntaxError: Unexpected token \"null\" in 1") },
+
+    { njs_str("function let() {}"),
+      njs_str("SyntaxError: Unexpected token \"let\" in 1") },
+
+    { njs_str("function static() {}"),
+      njs_str("SyntaxError: Unexpected token \"static\" in 1") },
 };