]> git.kaiwu.me - njs.git/commitdiff
Initial import of nJScript.
authorIgor Sysoev <igor@sysoev.ru>
Wed, 23 Sep 2015 00:31:27 +0000 (03:31 +0300)
committerIgor Sysoev <igor@sysoev.ru>
Wed, 23 Sep 2015 00:31:27 +0000 (03:31 +0300)
76 files changed:
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README [new file with mode: 0644]
configure [new file with mode: 0755]
nginx/config [new file with mode: 0644]
nginx/config.make [new file with mode: 0644]
nginx/ngx_http_js_module.c [new file with mode: 0644]
njs/njs_array.c [new file with mode: 0644]
njs/njs_array.h [new file with mode: 0644]
njs/njs_disassembler.c [new file with mode: 0644]
njs/njs_extern.c [new file with mode: 0644]
njs/njs_extern.h [new file with mode: 0644]
njs/njs_function.c [new file with mode: 0644]
njs/njs_function.h [new file with mode: 0644]
njs/njs_generator.c [new file with mode: 0644]
njs/njs_lexer.c [new file with mode: 0644]
njs/njs_lexer_keyword.c [new file with mode: 0644]
njs/njs_nonrecursive_parser.c [new file with mode: 0644]
njs/njs_number.c [new file with mode: 0644]
njs/njs_number.h [new file with mode: 0644]
njs/njs_object.c [new file with mode: 0644]
njs/njs_object.h [new file with mode: 0644]
njs/njs_object_hash.h [new file with mode: 0644]
njs/njs_parser.c [new file with mode: 0644]
njs/njs_parser.h [new file with mode: 0644]
njs/njs_parser_expression.c [new file with mode: 0644]
njs/njs_regexp.c [new file with mode: 0644]
njs/njs_regexp.h [new file with mode: 0644]
njs/njs_regexp_pattern.h [new file with mode: 0644]
njs/njs_shared.c [new file with mode: 0644]
njs/njs_string.c [new file with mode: 0644]
njs/njs_string.h [new file with mode: 0644]
njs/njs_variable.c [new file with mode: 0644]
njs/njs_variable.h [new file with mode: 0644]
njs/njs_vm.c [new file with mode: 0644]
njs/njs_vm.h [new file with mode: 0644]
njs/njscript.c [new file with mode: 0644]
njs/njscript.h [new file with mode: 0644]
njs/test/njs_unit_test.c [new file with mode: 0644]
nxt/Makefile [new file with mode: 0644]
nxt/auto/clang [new file with mode: 0644]
nxt/auto/configure [new file with mode: 0755]
nxt/auto/define [new file with mode: 0644]
nxt/auto/echo [new file with mode: 0755]
nxt/auto/feature [new file with mode: 0644]
nxt/auto/memalign [new file with mode: 0644]
nxt/auto/os [new file with mode: 0644]
nxt/auto/pcre [new file with mode: 0644]
nxt/nxt_alignment.h [new file with mode: 0644]
nxt/nxt_array.c [new file with mode: 0644]
nxt/nxt_array.h [new file with mode: 0644]
nxt/nxt_clang.h [new file with mode: 0644]
nxt/nxt_djb_hash.c [new file with mode: 0644]
nxt/nxt_djb_hash.h [new file with mode: 0644]
nxt/nxt_lvlhsh.c [new file with mode: 0644]
nxt/nxt_lvlhsh.h [new file with mode: 0644]
nxt/nxt_malloc.c [new file with mode: 0644]
nxt/nxt_malloc.h [new file with mode: 0644]
nxt/nxt_mem_cache_pool.c [new file with mode: 0644]
nxt/nxt_mem_cache_pool.h [new file with mode: 0644]
nxt/nxt_murmur_hash.c [new file with mode: 0644]
nxt/nxt_murmur_hash.h [new file with mode: 0644]
nxt/nxt_queue.c [new file with mode: 0644]
nxt/nxt_queue.h [new file with mode: 0644]
nxt/nxt_rbtree.c [new file with mode: 0644]
nxt/nxt_rbtree.h [new file with mode: 0644]
nxt/nxt_stub.h [new file with mode: 0644]
nxt/nxt_types.h [new file with mode: 0644]
nxt/nxt_unicode_lowcase.h [new file with mode: 0644]
nxt/nxt_unicode_lowcase.pl [new file with mode: 0755]
nxt/nxt_utf8.c [new file with mode: 0644]
nxt/nxt_utf8.h [new file with mode: 0644]
nxt/test/Makefile [new file with mode: 0644]
nxt/test/lvlhsh_unit_test.c [new file with mode: 0644]
nxt/test/rbtree_unit_test.c [new file with mode: 0644]
nxt/test/utf8_unit_test.c [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..d1fa27b
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 Igor Sysoev
+ * Copyright (C) 2015 NGINX, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..5d9645f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,299 @@
+
+NJS_VER =      20150922
+
+NXT_LIB =      nxt
+
+include $(NXT_LIB)/Makefile.conf
+
+NXT_BUILDDIR = build
+
+
+$(NXT_BUILDDIR)/libnjs.a: \
+       $(NXT_BUILDDIR)/njscript.o \
+       $(NXT_BUILDDIR)/njs_vm.o \
+       $(NXT_BUILDDIR)/njs_number.o \
+       $(NXT_BUILDDIR)/njs_string.o \
+       $(NXT_BUILDDIR)/njs_object.o \
+       $(NXT_BUILDDIR)/njs_array.o \
+       $(NXT_BUILDDIR)/njs_function.o \
+       $(NXT_BUILDDIR)/njs_regexp.o \
+       $(NXT_BUILDDIR)/njs_variable.o \
+       $(NXT_BUILDDIR)/njs_extern.o \
+       $(NXT_BUILDDIR)/njs_shared.o \
+       $(NXT_BUILDDIR)/njs_lexer.o \
+       $(NXT_BUILDDIR)/njs_lexer_keyword.o \
+       $(NXT_BUILDDIR)/njs_nonrecursive_parser.o \
+       $(NXT_BUILDDIR)/njs_parser.o \
+       $(NXT_BUILDDIR)/njs_parser_expression.o \
+       $(NXT_BUILDDIR)/njs_generator.o \
+       $(NXT_BUILDDIR)/njs_disassembler.o \
+       $(NXT_BUILDDIR)/nxt_djb_hash.o \
+       $(NXT_BUILDDIR)/nxt_utf8.o \
+       $(NXT_BUILDDIR)/nxt_array.o \
+       $(NXT_BUILDDIR)/nxt_rbtree.o \
+       $(NXT_BUILDDIR)/nxt_lvlhsh.o \
+       $(NXT_BUILDDIR)/nxt_malloc.o \
+       $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \
+
+       ar -r -c $(NXT_BUILDDIR)/libnjs.a \
+               $(NXT_BUILDDIR)/njscript.o \
+               $(NXT_BUILDDIR)/njs_vm.o \
+               $(NXT_BUILDDIR)/njs_number.o \
+               $(NXT_BUILDDIR)/njs_string.o \
+               $(NXT_BUILDDIR)/njs_object.o \
+               $(NXT_BUILDDIR)/njs_array.o \
+               $(NXT_BUILDDIR)/njs_function.o \
+               $(NXT_BUILDDIR)/njs_regexp.o \
+               $(NXT_BUILDDIR)/njs_variable.o \
+               $(NXT_BUILDDIR)/njs_extern.o \
+               $(NXT_BUILDDIR)/njs_shared.o \
+               $(NXT_BUILDDIR)/njs_lexer.o \
+               $(NXT_BUILDDIR)/njs_lexer_keyword.o \
+               $(NXT_BUILDDIR)/njs_nonrecursive_parser.o \
+               $(NXT_BUILDDIR)/njs_parser.o \
+               $(NXT_BUILDDIR)/njs_parser_expression.o \
+               $(NXT_BUILDDIR)/njs_generator.o \
+               $(NXT_BUILDDIR)/njs_disassembler.o \
+               $(NXT_BUILDDIR)/nxt_djb_hash.o \
+               $(NXT_BUILDDIR)/nxt_utf8.o \
+               $(NXT_BUILDDIR)/nxt_array.o \
+               $(NXT_BUILDDIR)/nxt_rbtree.o \
+               $(NXT_BUILDDIR)/nxt_lvlhsh.o \
+               $(NXT_BUILDDIR)/nxt_malloc.o \
+               $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \
+
+all:   test lib_test
+
+test:  \
+       $(NXT_BUILDDIR)/njs_unit_test \
+
+       $(NXT_BUILDDIR)/njs_unit_test
+
+clean:
+       rm -rf $(NXT_BUILDDIR)
+       rm $(NXT_LIB)/Makefile.conf $(NXT_LIB)/nxt_auto_config.h
+
+tarball:
+       make clean
+       mkdir njs-$(NJS_VER)
+       cp -rp configure Makefile LICENSE README $(NXT_LIB) njs nginx \
+               njs-$(NJS_VER)
+       tar czf njs-$(NJS_VER).tar.gz njs-$(NJS_VER)
+       rm -rf njs-$(NJS_VER)
+
+$(NXT_BUILDDIR)/njscript.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njscript.h \
+       njs/njscript.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njscript.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njscript.c
+
+$(NXT_BUILDDIR)/njs_vm.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_parser.h \
+       njs/njs_object_hash.h \
+       njs/njs_vm.h \
+       njs/njs_vm.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_vm.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_vm.c
+
+$(NXT_BUILDDIR)/njs_number.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_number.h \
+       njs/njs_number.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_number.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_number.c
+
+$(NXT_BUILDDIR)/njs_string.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_object_hash.h \
+       njs/njs_string.h \
+       njs/njs_string.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_string.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs $(NXT_PCRE_CFLAGS) \
+               njs/njs_string.c
+
+$(NXT_BUILDDIR)/njs_object.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_object_hash.h \
+       njs/njs_object.h \
+       njs/njs_object.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_object.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_object.c
+
+$(NXT_BUILDDIR)/njs_array.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_object_hash.h \
+       njs/njs_array.h \
+       njs/njs_array.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_array.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_array.c
+
+$(NXT_BUILDDIR)/njs_function.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_function.h \
+       njs/njs_function.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_function.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_function.c
+
+$(NXT_BUILDDIR)/njs_regexp.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_object_hash.h \
+       njs/njs_regexp.h \
+       njs/njs_regexp.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_regexp.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs $(NXT_PCRE_CFLAGS) \
+               njs/njs_regexp.c
+
+$(NXT_BUILDDIR)/njs_variable.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_variable.h \
+       njs/njs_variable.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_variable.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_variable.c
+
+$(NXT_BUILDDIR)/njs_extern.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_extern.h \
+       njs/njs_extern.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_extern.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_extern.c
+
+$(NXT_BUILDDIR)/njs_shared.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_shared.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_shared.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_shared.c
+
+$(NXT_BUILDDIR)/njs_lexer.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_lexer.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_lexer.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_lexer.c
+
+$(NXT_BUILDDIR)/njs_lexer_keyword.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_lexer_keyword.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_lexer_keyword.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_lexer_keyword.c
+
+$(NXT_BUILDDIR)/njs_nonrecursive_parser.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_nonrecursive_parser.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_nonrecursive_parser.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_nonrecursive_parser.c
+
+$(NXT_BUILDDIR)/njs_parser.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_parser.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_parser.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_parser.c \
+
+$(NXT_BUILDDIR)/njs_parser_expression.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_parser_expression.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_parser_expression.o \
+               $(NXT_CFLAGS) -I$(NXT_LIB) -Injs \
+               njs/njs_parser_expression.c
+
+$(NXT_BUILDDIR)/njs_generator.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_generator.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_generator.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_generator.c
+
+$(NXT_BUILDDIR)/njs_disassembler.o: \
+       $(NXT_BUILDDIR)/libnxt.a \
+       njs/njscript.h \
+       njs/njs_vm.h \
+       njs/njs_parser.h \
+       njs/njs_disassembler.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/njs_disassembler.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/njs_disassembler.c
+
+$(NXT_BUILDDIR)/njs_unit_test: \
+       $(NXT_BUILDDIR)/libnjs.a \
+       njs/test/njs_unit_test.c \
+
+       $(NXT_CC) -o $(NXT_BUILDDIR)/njs_unit_test $(NXT_CFLAGS) \
+               -I$(NXT_LIB) -Injs \
+               njs/test/njs_unit_test.c \
+               $(NXT_BUILDDIR)/libnjs.a -lm $(NXT_PCRE_LIB)
+
+include $(NXT_LIB)/Makefile
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..f8a6e22
--- /dev/null
+++ b/README
@@ -0,0 +1,118 @@
+
+Configure nginx with HTTP JavaScript module using the --add-module option:
+
+    ./configure --add-module=<path-to-njs>/nginx
+
+Please report your experiences to the NGINX development mailing list
+nginx-devel@nginx.org (http://mailman.nginx.org/mailman/listinfo/nginx-devel).
+
+JavaScript objects
+------------------
+
+$r
+|- uri
+|- method
+|- httpVersion
+|- remoteAddress
+|- headers{}
+|- args{}
+|- response
+  |- status
+  |- headers{}
+  |- contentType
+  |- contentLength
+  |- sendHeader()
+  |- send(data)
+  |- finish()
+
+
+Example
+-------
+
+Create nginx.conf:
+
+    worker_processes 1;
+    pid logs/nginx.pid;
+
+    events {
+        worker_connections  256;
+    }
+
+    http {
+        js_set $summary "
+            var a, s, h;
+
+            s = 'JS summary\n\n';
+
+            s += 'Method: ' + $r.method + '\n';
+            s += 'HTTP version: ' + $r.httpVersion + '\n';
+            s += 'Host: ' + $r.headers.host + '\n';
+            s += 'Remote Address: ' + $r.remoteAddress + '\n';
+            s += 'URI: ' + $r.uri + '\n';
+
+            s += 'Headers:\n';
+            for (h in $r.headers) {
+                s += '  header \"' + h + '\" is \"' + $r.headers[h] + '\"\n';
+            }
+
+            s += 'Args:\n';
+            for (a in $r.args) {
+                s += '  arg \"' + a + '\" is \"' + $r.args[a] + '\"\n';
+            }
+
+            s;
+            ";
+
+        server {
+            listen 8000;
+
+            location / {
+                js_run "
+                    var res;
+                    res = $r.response;
+                    res.headers.foo = 1234;
+                    res.status = 302;
+                    res.contentType = 'text/plain; charset=utf-8';
+                    res.contentLength = 11;
+                    res.sendHeader();
+                    res.send('nginx');
+                    res.send('java');
+                    res.send('script');
+                    res.finish();
+                    ";
+            }
+
+            location /summary {
+                return 200 $summary;
+            }
+        }
+    }
+
+Run nginx & test the output:
+
+$ curl 127.0.0.1:8000
+
+nginxjavascript
+
+$ curl -H "Foo: 1099" '127.0.0.1:8000/summary?a=1&fooo=bar&zyx=xyz'
+
+JS summary
+
+Method: GET
+HTTP version: 1.1
+Host: 127.0.0.1:8000
+Remote Address: 127.0.0.1
+URI: /summary
+Headers:
+  header "Host" is "127.0.0.1:8000"
+  header "User-Agent" is "curl/7.43.0"
+  header "Accept" is "*/*"
+  header "Foo" is "1099"
+Args:
+  arg "a" is "1"
+  arg "fooo" is "bar"
+  arg "zyx" is "xyz"
+
+
+--
+NGINX, Inc., http://nginx.com
diff --git a/configure b/configure
new file mode 100755 (executable)
index 0000000..01b07c7
--- /dev/null
+++ b/configure
@@ -0,0 +1,21 @@
+#!/bin/sh
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+# Disable localized program messages.
+LANG=C
+export LANG
+
+# Stop on error exit status.
+set -e
+# Stop on uninitialized variable.
+set -u
+
+CC=${CC:-}
+NXT_BUILDDIR=${NXT_BUILDDIR:-build}
+
+test -d $NXT_BUILDDIR || mkdir $NXT_BUILDDIR
+
+cd nxt && NXT_BUILDDIR=../${NXT_BUILDDIR} CC=${CC} ./auto/configure
diff --git a/nginx/config b/nginx/config
new file mode 100644 (file)
index 0000000..a625100
--- /dev/null
@@ -0,0 +1,10 @@
+ngx_addon_name="ngx_http_js_module"
+
+USE_PCRE=YES
+
+HTTP_MODULES="$HTTP_MODULES ngx_http_js_module"
+NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_js_module.c"
+
+LINK_DEPS="$LINK_DEPS $ngx_addon_dir/../build/libnjs.a"
+CORE_LIBS="$CORE_LIBS $ngx_addon_dir/../build/libnjs.a -lm"
+CORE_INCS="$CORE_INCS $ngx_addon_dir/../nxt $ngx_addon_dir/../njs"
diff --git a/nginx/config.make b/nginx/config.make
new file mode 100644 (file)
index 0000000..f252ca4
--- /dev/null
@@ -0,0 +1,9 @@
+cat << END                                            >> $NGX_MAKEFILE
+
+$ngx_addon_dir/../build/libnjs.a:
+       cd $ngx_addon_dir/.. \\
+       && if [ -f nxt/Makefile.conf ]; then \$(MAKE) clean; fi \\
+       && CFLAGS="\$(CFLAGS)" ./configure \\
+       && \$(MAKE)
+
+END
diff --git a/nginx/ngx_http_js_module.c b/nginx/ngx_http_js_module.c
new file mode 100644 (file)
index 0000000..a888744
--- /dev/null
@@ -0,0 +1,1205 @@
+
+/*
+ * Copyright (C) Roman Arutyunyan
+ * Copyright (C) NGINX, Inc.
+ */
+
+
+#include <ngx_config.h>
+#include <ngx_core.h>
+#include <ngx_http.h>
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+
+
+#define NGX_HTTP_JS_MCP_CLUSTER_SIZE    (2 * ngx_pagesize)
+#define NGX_HTTP_JS_MCP_PAGE_ALIGNMENT  128
+#define NGX_HTTP_JS_MCP_PAGE_SIZE       512
+#define NGX_HTTP_JS_MCP_MIN_CHUNK_SIZE  16
+
+
+#define ngx_http_js_create_mem_cache_pool()                                   \
+    nxt_mem_cache_pool_create(&ngx_http_js_mem_cache_pool_proto, NULL, NULL,  \
+                             NGX_HTTP_JS_MCP_CLUSTER_SIZE,                    \
+                             NGX_HTTP_JS_MCP_PAGE_ALIGNMENT,                  \
+                             NGX_HTTP_JS_MCP_PAGE_SIZE,                       \
+                             NGX_HTTP_JS_MCP_MIN_CHUNK_SIZE)
+
+
+typedef struct {
+    njs_vm_t         *vm;
+} ngx_http_js_loc_conf_t;
+
+
+typedef struct {
+    ngx_list_part_t  *part;
+    ngx_uint_t        item;
+} ngx_http_js_table_entry_t;
+
+
+static ngx_int_t ngx_http_js_handler(ngx_http_request_t *r);
+static ngx_int_t ngx_http_js_variable(ngx_http_request_t *r,
+    ngx_http_variable_value_t *v, uintptr_t data);
+static void ngx_http_js_cleanup_mem_cache_pool(void *data);
+
+static void *ngx_http_js_alloc(void *mem, size_t size);
+static void *ngx_http_js_calloc(void *mem, size_t size);
+static void *ngx_http_js_memalign(void *mem, size_t alignment, size_t size);
+static void ngx_http_js_free(void *mem, void *p);
+
+static njs_ret_t ngx_http_js_ext_undefined(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_get_string(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_string(njs_vm_t *vm, void *obj,
+    uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_each_header_start(njs_vm_t *vm, void *obj,
+    void *each, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_each_header(njs_vm_t *vm, njs_value_t *value,
+    void *obj, void *each);
+static ngx_table_elt_t *ngx_http_js_get_header(ngx_list_part_t *part,
+    u_char *data, size_t len);
+static njs_ret_t ngx_http_js_ext_get_header_out(njs_vm_t *vm,
+    njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_header_out(njs_vm_t *vm, void *obj,
+    uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_each_header_out_start(njs_vm_t *vm, void *obj,
+    void *each); /*FIXME*/
+static njs_ret_t ngx_http_js_ext_get_status(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_status(njs_vm_t *vm, void *obj,
+    uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_get_content_length(njs_vm_t *vm,
+    njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_set_content_length(njs_vm_t *vm, void *obj,
+    uintptr_t data, nxt_str_t *value);
+static njs_ret_t ngx_http_js_ext_send_header(njs_vm_t *vm, njs_param_t *param);
+static njs_ret_t ngx_http_js_ext_send(njs_vm_t *vm, njs_param_t *param);
+static njs_ret_t ngx_http_js_ext_finish(njs_vm_t *vm, njs_param_t *param);
+static njs_ret_t ngx_http_js_ext_get_http_version(njs_vm_t *vm,
+    njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_get_remote_address(njs_vm_t *vm,
+    njs_value_t *value, void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_get_header_in(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_each_header_in_start(njs_vm_t *vm, void *obj,
+    void *each); /*FIXME*/
+static njs_ret_t ngx_http_js_ext_get_arg(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data);
+static njs_ret_t ngx_http_js_ext_each_arg_start(njs_vm_t *vm, void *obj,
+    void *each);
+static njs_ret_t ngx_http_js_ext_each_arg(njs_vm_t *vm, njs_value_t *value,
+    void *obj, void *each);
+
+static char *ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static char *ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+static njs_vm_t *ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script);
+static void *ngx_http_js_create_loc_conf(ngx_conf_t *cf);
+static char *ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent,
+    void *child);
+
+
+static ngx_command_t  ngx_http_js_commands[] = {
+
+    { ngx_string("js_run"),
+      NGX_HTTP_LOC_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1,
+      ngx_http_js_run,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    { ngx_string("js_set"),
+      NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE2,
+      ngx_http_js_set,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
+    ngx_null_command
+};
+
+
+static ngx_http_module_t  ngx_http_js_module_ctx = {
+    NULL,                          /* preconfiguration */
+    NULL,                          /* postconfiguration */
+
+    NULL,                          /* create main configuration */
+    NULL,                          /* init main configuration */
+
+    NULL,                          /* create server configuration */
+    NULL,                          /* merge server configuration */
+
+    ngx_http_js_create_loc_conf,   /* create location configuration */
+    ngx_http_js_merge_loc_conf     /* merge location configuration */
+};
+
+
+ngx_module_t  ngx_http_js_module = {
+    NGX_MODULE_V1,
+    &ngx_http_js_module_ctx,       /* module context */
+    ngx_http_js_commands,          /* module directives */
+    NGX_HTTP_MODULE,               /* module type */
+    NULL,                          /* init master */
+    NULL,                          /* init module */
+    NULL,                          /* init process */
+    NULL,                          /* init thread */
+    NULL,                          /* exit thread */
+    NULL,                          /* exit process */
+    NULL,                          /* exit master */
+    NGX_MODULE_V1_PADDING
+};
+
+
+static const nxt_mem_proto_t  ngx_http_js_mem_cache_pool_proto = {
+    ngx_http_js_alloc,
+    ngx_http_js_calloc,
+    ngx_http_js_memalign,
+    NULL,
+    ngx_http_js_free,
+    NULL,
+    NULL,
+};
+
+
+static njs_external_t  ngx_http_js_ext_response[] = {
+
+    { nxt_string("headers"),
+      NJS_EXTERN_OBJECT,
+      NULL,
+      0,
+      ngx_http_js_ext_get_header_out,
+      ngx_http_js_ext_set_header_out,
+      NULL,
+      ngx_http_js_ext_each_header_out_start,
+      ngx_http_js_ext_each_header,
+      NULL,
+      0 },
+
+    { nxt_string("status"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_status,
+      ngx_http_js_ext_set_status,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      offsetof(ngx_http_request_t, headers_out.status) },
+
+    { nxt_string("contentType"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_string,
+      ngx_http_js_ext_set_string,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      offsetof(ngx_http_request_t, headers_out.content_type) },
+
+    { nxt_string("contentLength"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_content_length,
+      ngx_http_js_ext_set_content_length,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("sendHeader"),
+      NJS_EXTERN_METHOD,
+      NULL,
+      0,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      ngx_http_js_ext_send_header,
+      0 },
+
+    { nxt_string("send"),
+      NJS_EXTERN_METHOD,
+      NULL,
+      0,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      ngx_http_js_ext_send,
+      0 },
+
+    { nxt_string("finish"),
+      NJS_EXTERN_METHOD,
+      NULL,
+      0,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      ngx_http_js_ext_finish,
+      0 },
+};
+
+
+static njs_external_t  ngx_http_js_ext_request[] = {
+
+    { nxt_string("response"),
+      NJS_EXTERN_OBJECT,
+      ngx_http_js_ext_response,
+      nxt_nitems(ngx_http_js_ext_response),
+      ngx_http_js_ext_undefined,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("uri"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_string,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      offsetof(ngx_http_request_t, uri) },
+
+    { nxt_string("method"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_string,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      offsetof(ngx_http_request_t, method_name) },
+
+    { nxt_string("httpVersion"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_http_version,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("remoteAddress"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      ngx_http_js_ext_get_remote_address,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("headers"),
+      NJS_EXTERN_OBJECT,
+      NULL,
+      0,
+      ngx_http_js_ext_get_header_in,
+      NULL,
+      NULL,
+      ngx_http_js_ext_each_header_in_start,
+      ngx_http_js_ext_each_header,
+      NULL,
+      0 },
+
+    { nxt_string("args"),
+      NJS_EXTERN_OBJECT,
+      NULL,
+      0,
+      ngx_http_js_ext_get_arg,
+      NULL,
+      NULL,
+      ngx_http_js_ext_each_arg_start,
+      ngx_http_js_ext_each_arg,
+      NULL,
+      0 },
+};
+
+
+static njs_external_t  ngx_http_js_externals[] = {
+
+    { nxt_string("$r"),
+      NJS_EXTERN_OBJECT,
+      ngx_http_js_ext_request,
+      nxt_nitems(ngx_http_js_ext_request),
+      ngx_http_js_ext_undefined,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+};
+
+
+static ngx_int_t
+ngx_http_js_handler(ngx_http_request_t *r)
+{
+    nxt_str_t                 value;
+    njs_vm_t                *nvm;
+    ngx_pool_cleanup_t      *cln;
+    nxt_mem_cache_pool_t     *mcp;
+    ngx_http_js_loc_conf_t  *jlcf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js handler");
+
+    mcp = ngx_http_js_create_mem_cache_pool();
+    if (mcp == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(r->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_http_js_cleanup_mem_cache_pool;
+    cln->data = mcp;
+
+    jlcf = ngx_http_get_module_loc_conf(r, ngx_http_js_module);
+
+    nvm = njs_vm_clone(jlcf->vm, mcp, (void **) &r);
+    if (nvm == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (njs_vm_run(nvm) != NJS_OK) {
+        njs_vm_exception(nvm, &value);
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "js exception: %*s", value.len, value.data);
+
+        return NGX_ERROR;
+    }
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_http_js_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v,
+    uintptr_t data)
+{
+    njs_vm_t *vm = (njs_vm_t *) data;
+
+    nxt_str_t              value;
+    njs_vm_t             *nvm;
+    ngx_pool_cleanup_t   *cln;
+    nxt_mem_cache_pool_t  *mcp;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js variable handler");
+
+    mcp = ngx_http_js_create_mem_cache_pool();
+    if (mcp == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln = ngx_pool_cleanup_add(r->pool, 0);
+    if (cln == NULL) {
+        return NGX_ERROR;
+    }
+
+    cln->handler = ngx_http_js_cleanup_mem_cache_pool;
+    cln->data = mcp;
+
+    nvm = njs_vm_clone(vm, mcp, (void **) &r);
+    if (nvm == NULL) {
+        return NGX_ERROR;
+    }
+
+    if (njs_vm_run(nvm) == NJS_OK) {
+        if (njs_vm_retval(nvm, &value) != NJS_OK) {
+            return NGX_ERROR;
+        }
+
+        v->len = value.len;
+        v->valid = 1;
+        v->no_cacheable = 0;
+        v->not_found = 0;
+        v->data = value.data;
+
+    } else {
+        njs_vm_exception(nvm, &value);
+
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "js exception: %*s", value.len, value.data);
+
+        v->not_found = 1;
+    }
+
+    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http js variable done");
+
+    return NGX_OK;
+}
+
+
+static void
+ngx_http_js_cleanup_mem_cache_pool(void *data)
+{
+    nxt_mem_cache_pool_t *mcp = data;
+
+    nxt_mem_cache_pool_destroy(mcp);
+}
+
+
+static void *
+ngx_http_js_alloc(void *mem, size_t size)
+{
+    return ngx_alloc(size, ngx_cycle->log);
+}
+
+
+static void *
+ngx_http_js_calloc(void *mem, size_t size)
+{
+    return ngx_calloc(size, ngx_cycle->log);
+}
+
+
+static void *
+ngx_http_js_memalign(void *mem, size_t alignment, size_t size)
+{
+    return ngx_memalign(alignment, size, ngx_cycle->log);
+}
+
+
+static void
+ngx_http_js_free(void *mem, void *p)
+{
+    ngx_free(p);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_undefined(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    njs_void_set(value);
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_string(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    char *p = obj;
+
+    ngx_str_t  *field;
+
+    field = (ngx_str_t *) (p + data);
+
+    return njs_string_create(vm, value, field->data, field->len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_string(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_str_t *value)
+{
+    char *p = obj;
+
+    ngx_str_t           *field;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+
+    field = (ngx_str_t *) (p + data);
+    field->len = value->len;
+
+    field->data = ngx_pnalloc(r->pool, value->len);
+    if (field->data == NULL) {
+        return NJS_ERROR;
+    }
+
+    ngx_memcpy(field->data, value->data, value->len);
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header_start(njs_vm_t *vm, void *obj, void *each,
+    uintptr_t data)
+{
+    char *p = obj;
+
+    ngx_list_t                 *headers;
+    ngx_http_request_t         *r;
+    ngx_http_js_table_entry_t  *entry, **e;
+
+    r = (ngx_http_request_t *) obj;
+
+    entry = ngx_palloc(r->pool, sizeof(ngx_http_js_table_entry_t));
+    if (entry == NULL) {
+        return NJS_ERROR;
+    }
+
+    headers = (ngx_list_t *) (p + data);
+
+    entry->part = &headers->part;
+    entry->item = 0;
+
+    e = (ngx_http_js_table_entry_t **) each;
+    *e = entry;
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header(njs_vm_t *vm, njs_value_t *value, void *obj,
+    void *each)
+{
+    ngx_http_js_table_entry_t **e = each;
+
+    ngx_table_elt_t            *header, *h;
+    ngx_http_js_table_entry_t  *entry;
+
+    entry = *e;
+
+    while (entry->part) {
+
+        if (entry->item >= entry->part->nelts) {
+            entry->part = entry->part->next;
+            entry->item = 0;
+            continue;
+        }
+
+        header = entry->part->elts;
+        h = &header[entry->item++];
+
+        return njs_string_create(vm, value, h->key.data, h->key.len, 0);
+    }
+
+    return NJS_DONE;
+}
+
+
+static ngx_table_elt_t *
+ngx_http_js_get_header(ngx_list_part_t *part, u_char *data, size_t len)
+{
+    ngx_uint_t        i;
+    ngx_table_elt_t  *header, *h;
+
+    header = part->elts;
+
+    for (i = 0; /* void */ ; i++) {
+
+        if (i >= part->nelts) {
+            if (part->next == NULL) {
+                break;
+            }
+
+            part = part->next;
+            header = part->elts;
+            i = 0;
+        }
+
+        h = &header[i];
+
+        if (h->hash == 0) {
+            continue;
+        }
+
+        if (h->key.len == len && ngx_strncasecmp(h->key.data, data, len) == 0) {
+            return h;
+        }
+    }
+
+    return NULL;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_header_out(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    nxt_str_t            *v;
+    ngx_table_elt_t     *h;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+    v = (nxt_str_t *) data;
+
+    h = ngx_http_js_get_header(&r->headers_out.headers.part, v->data, v->len);
+    if (h == NULL) {
+        return njs_string_create(vm, value, NULL, 0, 0);
+    }
+
+    return njs_string_create(vm, value, h->value.data, h->value.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_header_out(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_str_t *value)
+{
+    u_char              *p;
+    nxt_str_t            *v;
+    ngx_table_elt_t     *h;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+    v = (nxt_str_t *) data;
+
+    h = ngx_http_js_get_header(&r->headers_out.headers.part, v->data, v->len);
+
+    if (h == NULL || h->hash == 0) {
+        h = ngx_list_push(&r->headers_out.headers);
+        if (h == NULL) {
+            return NJS_ERROR;
+        }
+
+        p = ngx_pnalloc(r->pool, v->len);
+        if (p == NULL) {
+            return NJS_ERROR;
+        }
+
+        ngx_memcpy(p, v->data, v->len);
+
+        h->key.data = p;
+        h->key.len = v->len;
+        h->hash = 1;
+    }
+
+
+    p = ngx_pnalloc(r->pool, value->len);
+    if (p == NULL) {
+        return NJS_ERROR;
+    }
+
+    ngx_memcpy(p, value->data, value->len);
+
+    h->value.data = p;
+    h->value.len = value->len;
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header_out_start(njs_vm_t *vm, void *obj, void *each)
+{
+    return ngx_http_js_ext_each_header_start(vm, obj, each,
+                             offsetof(ngx_http_request_t, headers_out.headers));
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_status(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    size_t               len;
+    u_char              *p;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+
+    p = ngx_pnalloc(r->pool, 3);
+    if (p == NULL) {
+        return NJS_ERROR;
+    }
+
+    len = ngx_snprintf(p, 3, "%ui", r->headers_out.status) - p;
+
+    return njs_string_create(vm, value, p, len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_status(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_str_t *value)
+{
+    ngx_int_t            n;
+    ngx_http_request_t  *r;
+
+    n = ngx_atoi(value->data, value->len);
+    if (n == NGX_ERROR) {
+        return NJS_ERROR;
+    }
+
+    r = (ngx_http_request_t *) obj;
+
+    r->headers_out.status = n;
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_content_length(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    size_t               len;
+    u_char              *p;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+
+    p = ngx_pnalloc(r->pool, NGX_OFF_T_LEN);
+    if (p == NULL) {
+        return NJS_ERROR;
+    }
+
+    len = ngx_sprintf(p, "%O", r->headers_out.content_length_n) - p;
+
+    return njs_string_create(vm, value, p, len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_set_content_length(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_str_t *value)
+{
+    ngx_int_t            n;
+    ngx_http_request_t  *r;
+
+    n = ngx_atoi(value->data, value->len);
+    if (n == NGX_ERROR) {
+        return NJS_ERROR;
+    }
+
+    r = (ngx_http_request_t *) obj;
+
+    r->headers_out.content_length_n = n;
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_send_header(njs_vm_t *vm, njs_param_t *param)
+{
+    ngx_http_request_t  *r;
+
+    r = njs_value_data(param->object);
+
+    if (ngx_http_send_header(r) == NGX_ERROR) {
+        return NJS_ERROR;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_send(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_int_t             ret;
+    nxt_str_t             s;
+    ngx_buf_t           *b;
+    uintptr_t            nargs, next;
+    ngx_uint_t           n;
+    njs_value_t         *args;
+    ngx_chain_t         *out, *cl, **ll;
+    ngx_http_request_t  *r;
+
+    r = njs_value_data(param->object);
+
+    out = NULL;
+    ll = &out;
+
+    args = param->args;
+    nargs = param->nargs;
+
+    for (n = 0; n < nargs; n++) {
+        next = 0;
+
+        for ( ;; ) {
+            ret = njs_value_string_copy(vm, &s, njs_argument(args, n), &next);
+
+            if (ret == NJS_DECLINED) {
+                break;
+            }
+
+            if (ret == NJS_ERROR) {
+                return NJS_ERROR;
+            }
+
+            /* TODO: njs_value_release(vm, value) in buf completion */
+
+            ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http js send: \"%*s\"", s.len, s.data);
+
+            b = ngx_calloc_buf(r->pool);
+            if (b == NULL) {
+                return NJS_ERROR;
+            }
+
+            b->start = s.data;
+            b->pos = b->start;
+            b->end = s.data + s.len;
+            b->last = b->end;
+            b->memory = 1;
+
+            cl = ngx_alloc_chain_link(r->pool);
+            if (cl == NULL) {
+                return NJS_ERROR;
+            }
+
+            cl->buf = b;
+
+            *ll = cl;
+            ll = &cl->next;
+        }
+    }
+
+    *ll = NULL;
+
+    if (ngx_http_output_filter(r, out) == NGX_ERROR) {
+        return NJS_ERROR;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_finish(njs_vm_t *vm, njs_param_t *param)
+{
+    ngx_http_request_t  *r;
+
+    r = njs_value_data(param->object);
+
+    if (ngx_http_send_special(r, NGX_HTTP_LAST) == NGX_ERROR) {
+        return NJS_ERROR;
+    }
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_http_version(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    ngx_str_t            v;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+
+    switch (r->http_version) {
+
+    case NGX_HTTP_VERSION_9:
+        ngx_str_set(&v, "0.9");
+        break;
+
+    case NGX_HTTP_VERSION_10:
+        ngx_str_set(&v, "1.0");
+        break;
+
+    default: /* NGX_HTTP_VERSION_11 */
+        ngx_str_set(&v, "1.1");
+        break;
+    }
+
+    return njs_string_create(vm, value, v.data, v.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_remote_address(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    ngx_connection_t    *c;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+    c = r->connection;
+
+    return njs_string_create(vm, value, c->addr_text.data, c->addr_text.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_get_header_in(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    nxt_str_t            *v;
+    ngx_table_elt_t     *h;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+    v = (nxt_str_t *) data;
+
+    h = ngx_http_js_get_header(&r->headers_in.headers.part, v->data, v->len);
+    if (h == NULL) {
+        return njs_string_create(vm, value, NULL, 0, 0);
+    }
+
+    return njs_string_create(vm, value, h->value.data, h->value.len, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_header_in_start(njs_vm_t *vm, void *obj, void *each)
+{
+    return ngx_http_js_ext_each_header_start(vm, obj, each,
+                              offsetof(ngx_http_request_t, headers_in.headers));
+}
+
+static njs_ret_t
+ngx_http_js_ext_get_arg(njs_vm_t *vm, njs_value_t *value, void *obj,
+    uintptr_t data)
+{
+    nxt_str_t            *v;
+    ngx_str_t            arg;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+    v = (nxt_str_t *) data;
+
+    if (ngx_http_arg(r, v->data, v->len, &arg) == NGX_OK) {
+        return njs_string_create(vm, value, arg.data, arg.len, 0);
+    }
+
+    return njs_string_create(vm, value, NULL, 0, 0);
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_arg_start(njs_vm_t *vm, void *obj, void *each)
+{
+    ngx_str_t           *entry, **e;
+    ngx_http_request_t  *r;
+
+    r = (ngx_http_request_t *) obj;
+
+    entry = ngx_palloc(r->pool, sizeof(ngx_str_t));
+    if (entry == NULL) {
+        return NJS_ERROR;
+    }
+
+    *entry = r->args;
+
+    e = (ngx_str_t **) each;
+    *e = entry;
+
+    return NJS_OK;
+}
+
+
+static njs_ret_t
+ngx_http_js_ext_each_arg(njs_vm_t *vm, njs_value_t *value, void *obj,
+    void *each)
+{
+    ngx_str_t **e = each;
+
+    size_t      len;
+    u_char     *p, *start, *end;
+    ngx_str_t  *entry;
+
+    entry = *e;
+
+    if (entry->len == 0) {
+        return NJS_DONE;
+    }
+
+    start = entry->data;
+    end = start + entry->len;
+
+    p = ngx_strlchr(start, end, '=');
+    if (p == NULL) {
+        return NJS_ERROR;
+    }
+
+    len = p - start;
+    p++;
+
+    p = ngx_strlchr(p, end, '&');
+
+    if (p) {
+        entry->data = &p[1];
+        entry->len = end - entry->data;
+
+    } else {
+        entry->len = 0;
+    }
+
+    return njs_string_create(vm, value, start, len, 0);
+}
+
+
+static char *
+ngx_http_js_run(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_js_loc_conf_t *jlcf = conf;
+
+    ngx_str_t                 *value;
+    ngx_http_core_loc_conf_t  *clcf;
+
+    value = cf->args->elts;
+
+    if (jlcf->vm) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "duplicate js handler \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    jlcf->vm = ngx_http_js_compile(cf, &value[1]);
+    if (jlcf->vm == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
+    clcf->handler = ngx_http_js_handler;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
+ngx_http_js_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    njs_vm_t             *vm;
+    ngx_str_t            *value;
+    ngx_http_variable_t  *v;
+
+    value = cf->args->elts;
+
+    if (value[1].data[0] != '$') {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid variable name \"%V\"", &value[1]);
+        return NGX_CONF_ERROR;
+    }
+
+    value[1].len--;
+    value[1].data++;
+
+    v = ngx_http_add_variable(cf, &value[1], NGX_HTTP_VAR_CHANGEABLE);
+    if (v == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    vm = ngx_http_js_compile(cf, &value[2]);
+    if (vm == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    v->get_handler = ngx_http_js_variable;
+    v->data = (uintptr_t) vm;
+
+    return NGX_CONF_OK;
+}
+
+
+static njs_vm_t *
+ngx_http_js_compile(ngx_conf_t *cf, ngx_str_t *script)
+{
+    u_char               *start, *end;
+    nxt_int_t              rc;
+    nxt_str_t              s;
+    njs_vm_t             *vm;
+    nxt_lvlhsh_t           externals;
+    njs_vm_shared_t      *shared;
+    nxt_mem_cache_pool_t  *mcp;
+
+    mcp = ngx_http_js_create_mem_cache_pool();
+    if (mcp == NULL) {
+        return NULL;
+    }
+
+    shared = NULL;
+
+    nxt_lvlhsh_init(&externals);
+
+    if (njs_add_external(&externals, mcp, 0, ngx_http_js_externals,
+                         nxt_nitems(ngx_http_js_externals))
+        != NJS_OK)
+    {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "could not add js externals");
+        return NULL;
+    }
+
+    vm = njs_vm_create(mcp, &shared, &externals);
+    if (vm == NULL) {
+        return NULL;
+    }
+
+    start = script->data;
+    end = start + script->len;
+
+    rc = njs_vm_compile(vm, &start, end);
+
+    if (rc != NJS_OK) {
+        njs_vm_exception(vm, &s);
+
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "js compilation error: \"%*s\"", s.len, s.data);
+        return NULL;
+    }
+
+    if (start != end) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "extra characters in js script: \"%*s\"",
+                           end - start, start);
+        return NULL;
+    }
+
+    return vm;
+}
+
+
+static void *
+ngx_http_js_create_loc_conf(ngx_conf_t *cf)
+{
+    ngx_http_js_loc_conf_t  *conf;
+
+    conf = ngx_palloc(cf->pool, sizeof(ngx_http_js_loc_conf_t));
+    if (conf == NULL) {
+        return NULL;
+    }
+
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->vm = NULL;
+     */
+
+    return conf;
+}
+
+
+static char *
+ngx_http_js_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
+{
+    ngx_http_js_loc_conf_t *prev = parent;
+    ngx_http_js_loc_conf_t *conf = child;
+
+    if (conf->vm == NULL) {
+        conf->vm = prev->vm;
+    }
+
+    return NGX_CONF_OK;
+}
diff --git a/njs/njs_array.c b/njs/njs_array.c
new file mode 100644 (file)
index 0000000..641d012
--- /dev/null
@@ -0,0 +1,944 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <string.h>
+
+
+typedef struct {
+    njs_value_t  retval;
+    int32_t      index;
+    uint32_t     length;
+} njs_array_each_t;
+
+
+static nxt_noinline njs_value_t *njs_array_copy(njs_value_t *dst,
+    njs_value_t *src);
+static nxt_int_t njs_array_next(njs_value_t *value, nxt_uint_t n,
+    nxt_uint_t length);
+
+
+njs_value_t *
+njs_array_add(njs_vm_t *vm, njs_value_t *value, u_char *start, size_t size)
+{
+    njs_ret_t    ret;
+    njs_array_t  *array;
+
+    if (value != NULL) {
+        array = value->data.u.array;
+
+        if (array->size == array->length) {
+            ret = njs_array_realloc(vm, array, 0, array->size + 1);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NULL;
+            }
+        }
+
+    } else {
+        value = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                    sizeof(njs_value_t));
+
+        if (nxt_slow_path(value == NULL)) {
+            return NULL;
+        }
+
+        array = njs_array_alloc(vm, 0, NJS_ARRAY_SPARE);
+        if (nxt_slow_path(array == NULL)) {
+            return NULL;
+        }
+
+        value->data.u.array = array;
+        value->type = NJS_ARRAY;
+        value->data.truth = 1;
+    }
+
+    ret = njs_string_create(vm, &array->start[array->length++], start, size, 0);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        return value;
+    }
+
+    return NULL;
+}
+
+
+nxt_noinline njs_array_t *
+njs_array_alloc(njs_vm_t *vm, uint32_t length, uint32_t spare)
+{
+    uint32_t     size;
+    njs_array_t  *array;
+
+    array = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                sizeof(njs_array_t));
+
+    if (nxt_slow_path(array == NULL)) {
+        return NULL;
+    }
+
+    size = length + spare;
+
+    array->data = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                      size * sizeof(njs_value_t));
+    if (nxt_slow_path(array->data == NULL)) {
+        return NULL;
+    }
+
+    array->start = array->data;
+    nxt_lvlhsh_init(&array->object.hash);
+    nxt_lvlhsh_init(&array->object.shared_hash);
+    array->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_ARRAY];
+    array->size = size;
+    array->length = length;
+
+    return array;
+}
+
+
+njs_ret_t
+njs_array_realloc(njs_vm_t *vm, njs_array_t *array, uint32_t prepend,
+    uint32_t size)
+{
+    nxt_uint_t    n;
+    njs_value_t  *value;
+
+    if (size != array->size) {
+        if (size < 16) {
+            size *= 2;
+
+        } else {
+            size += size / 2;
+        }
+    }
+
+    value = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                (prepend + size) * sizeof(njs_value_t));
+    if (nxt_slow_path(value == NULL)) {
+        return NXT_ERROR;
+    }
+
+    /* GC: old = array->data */
+
+    array->data = value;
+
+    while (prepend != 0) {
+        njs_set_invalid(value);
+        value++;
+        prepend--;
+    }
+
+    memcpy(value, array->start, array->size * sizeof(njs_value_t));
+
+    array->start = value;
+    n = array->size;
+    array->size = size;
+
+    value += n;
+    size -= n;
+
+    while (size != 0) {
+        njs_set_invalid(value);
+        value++;
+        size--;
+    }
+
+    /* GC: free old pointer. */
+
+    return NXT_OK;
+}
+
+
+njs_ret_t
+njs_array_function(njs_vm_t *vm, njs_param_t *param)
+{
+    double       num;
+    uint32_t     size;
+    njs_value_t  *value, *args;
+    njs_array_t  *array;
+
+    args = param->args;
+    size = param->nargs;
+
+    if (size == 1 && njs_is_number(&args[0])) {
+        num = args[0].data.u.number;
+        size = (uint32_t) num;
+
+        if ((double) size != num) {
+            vm->exception = &njs_exception_range_error;
+            return NXT_ERROR;
+        }
+
+        args = NULL;
+    }
+
+    array = njs_array_alloc(vm, size, NJS_ARRAY_SPARE);
+
+    if (nxt_fast_path(array != NULL)) {
+
+        vm->retval.data.u.array = array;
+        value = array->start;
+
+        if (args == NULL) {
+            while (size != 0) {
+                njs_set_invalid(value);
+                value++;
+                size--;
+            }
+
+        } else {
+            while (size != 0) {
+                njs_retain(args);
+                *value++ = *args++;
+                size--;
+            }
+        }
+
+        vm->retval.type = NJS_ARRAY;
+        vm->retval.data.truth = 1;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static const njs_object_prop_t  njs_array_function_properties[] =
+{
+    { njs_string("Array"),
+      njs_string("name"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_value(NJS_NUMBER, 1, 1.0),
+      njs_string("length"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_prototype),
+      njs_string("prototype"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_array_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_array_function_properties,
+                                  nxt_nitems(njs_array_function_properties));
+}
+
+
+static njs_ret_t
+njs_array_prototype_length(njs_vm_t *vm, njs_value_t *array)
+{
+    njs_number_set(&vm->retval, array->data.u.array->length);
+
+    njs_release(vm, array);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_slice(njs_vm_t *vm, njs_param_t *param)
+{
+    int32_t      start, end, length;
+    uint32_t     n;
+    uintptr_t    nargs;
+    njs_array_t  *array;
+    njs_value_t  *object, *args, *value;
+
+    start = 0;
+    length = 0;
+    object = param->object;
+
+    if (njs_is_array(object)) {
+        length = object->data.u.array->length;
+        nargs = param->nargs;
+
+        if (nargs != 0) {
+            args = param->args;
+            start = njs_value_to_number(&args[0]);
+
+            if (start < 0) {
+                start += length;
+
+                if (start < 0) {
+                    start = 0;
+                }
+            }
+
+            end = length;
+
+            if (nargs > 1) {
+                end = njs_value_to_number(&args[1]);
+
+                if (end < 0) {
+                    end += length;
+                }
+            }
+
+            length = end - start;
+
+            if (length < 0) {
+                start = 0;
+                length = 0;
+            }
+        }
+    }
+
+    array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE);
+    if (nxt_slow_path(array == NULL)) {
+        return NXT_ERROR;
+    }
+
+    vm->retval.data.u.array = array;
+    vm->retval.type = NJS_ARRAY;
+    vm->retval.data.truth = 1;
+
+    if (length != 0) {
+        value = object->data.u.array->start;
+        n = 0;
+
+        do {
+            /* GC: retain long string and object in values[start]. */
+            array->start[n++] = value[start++];
+            length--;
+        } while (length != 0);
+    }
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_push(njs_vm_t *vm, njs_param_t *param)
+{
+    uintptr_t    i, nargs;
+    njs_ret_t    ret;
+    njs_value_t  *args;
+    njs_array_t  *array;
+
+    if (njs_is_array(param->object)) {
+        array = param->object->data.u.array;
+        nargs = param->nargs;
+
+        if (nargs != 0) {
+            if (nargs > array->size - array->length) {
+                ret = njs_array_realloc(vm, array, 0, array->size + nargs);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
+            }
+
+            args = param->args;
+
+            for (i = 0; i < nargs; i++) {
+                /* GC: njs_retain(&args[i]); */
+                array->start[array->length++] = args[i];
+            }
+        }
+
+        njs_number_set(&vm->retval, array->length);
+    }
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_pop(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_array_t        *array;
+    const njs_value_t  *retval, *value;
+
+    retval = &njs_value_void;
+
+    if (njs_is_array(param->object)) {
+        array = param->object->data.u.array;
+
+        if (array->length != 0) {
+            array->length--;
+            value = &array->start[array->length];
+
+            if (njs_is_valid(value)) {
+                retval = value;
+            }
+        }
+    }
+
+    vm->retval = *retval;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_unshift(njs_vm_t *vm, njs_param_t *param)
+{
+    uintptr_t    nargs;
+    njs_ret_t    ret;
+    njs_value_t  *args;
+    njs_array_t  *array;
+
+    if (njs_is_array(param->object)) {
+        array = param->object->data.u.array;
+        nargs = param->nargs;
+
+        if (nargs != 0) {
+            if ((intptr_t) nargs > (array->start - array->data)) {
+                ret = njs_array_realloc(vm, array, nargs, array->size);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
+            }
+
+            array->length += nargs;
+            args = param->args;
+
+            do {
+                nargs--;
+                /* GC: njs_retain(&args[nargs]); */
+                array->start--;
+                array->start[0] = args[nargs];
+            } while (nargs != 0);
+        }
+
+        njs_number_set(&vm->retval, array->length);
+    }
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_shift(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_array_t        *array;
+    const njs_value_t  *retval, *value;
+
+    retval = &njs_value_void;
+
+    if (njs_is_array(param->object)) {
+        array = param->object->data.u.array;
+
+        if (array->length != 0) {
+            array->length--;
+
+            value = &array->start[0];
+            array->start++;
+
+            if (njs_is_valid(value)) {
+                retval = value;
+            }
+        }
+    }
+
+    vm->retval = *retval;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_to_string(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_object_t        *object;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = NJS_JOIN_HASH;
+    lhq.key.len = sizeof("join") - 1;
+    lhq.key.data = (u_char *) "join";
+
+    object = param->object->data.u.object;
+
+    prop = njs_object_property(vm, object, &lhq);
+
+    if (nxt_fast_path(prop != NULL
+                      && (njs_is_function(&prop->value)
+                          || njs_is_native(&prop->value))))
+    {
+        return njs_function_apply(vm, &prop->value, param);
+    }
+
+    lhq.key_hash = NJS_TO_STRING_HASH;
+    lhq.key.len = sizeof("toString") - 1;
+    lhq.key.data = (u_char *) "toString";
+
+    object = &vm->prototypes[NJS_PROTOTYPE_OBJECT];
+
+    prop = njs_object_property(vm, object, &lhq);
+
+    if (nxt_fast_path(prop != NULL)) {
+        return njs_function_apply(vm, &prop->value, param);
+    }
+
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_array_prototype_join(njs_vm_t *vm, njs_param_t *param)
+{
+    u_char             *p;
+    size_t             size, length;
+    nxt_int_t          ret;
+    nxt_uint_t         i, n, max;
+    njs_array_t        *array;
+    njs_value_t        *value, *values;
+    njs_string_prop_t  separator, string;
+
+    if (!njs_is_array(param->object)) {
+        goto empty;
+    }
+
+    array = param->object->data.u.array;
+
+    if (array->length == 0) {
+        goto empty;
+    }
+
+    if (param->nargs != 0) {
+        value = &param->args[0];
+
+    } else {
+        value = (njs_value_t *) &njs_string_comma;
+    }
+
+    (void) njs_string_prop(&separator, value);
+
+    max = 0;
+
+    for (i = 0; i < array->length; i++) {
+        value = &array->start[i];
+        if (njs_is_valid(value) && !njs_is_string(value)) {
+            max++;
+        }
+    }
+
+    values = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                 sizeof(njs_value_t) * max);
+    if (nxt_slow_path(values == NULL)) {
+        return NXT_ERROR;
+    }
+
+    size = separator.size * (array->length - 1);
+    length = separator.length * (array->length - 1);
+    n = 0;
+
+    for (i = 0; i < array->length; i++) {
+        value = &array->start[i];
+
+        if (njs_is_valid(value)) {
+
+            if (!njs_is_string(value)) {
+                ret = njs_value_to_string(vm, &values[n], value);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return NXT_ERROR;
+                }
+
+                value = &values[n++];
+            }
+
+            (void) njs_string_prop(&string, value);
+
+            size += string.size;
+            length += string.length;
+        }
+    }
+
+    p = njs_string_alloc(vm, &vm->retval, size, length);
+    if (nxt_slow_path(p == NULL)) {
+        return NXT_ERROR;
+    }
+
+    n = 0;
+
+    for (i = 0; i < array->length; i++) {
+        value = &array->start[i];
+
+        if (njs_is_valid(value)) {
+            if (!njs_is_string(value)) {
+                value = &values[n++];
+            }
+
+            (void) njs_string_prop(&string, value);
+
+            p = memcpy(p, string.start, string.size);
+            p += string.size;
+        }
+
+        if (i < array->length - 1) {
+            p = memcpy(p, separator.start, separator.size);
+            p += separator.size;
+        }
+    }
+
+    for (i = 0; i < max; i++) {
+        njs_release(vm, &values[i]);
+    }
+
+    nxt_mem_cache_free(vm->mem_cache_pool, values);
+
+    return NXT_OK;
+
+empty:
+
+    vm->retval = njs_string_empty;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_array_prototype_concat(njs_vm_t *vm, njs_param_t *param)
+{
+    size_t       length;
+    uintptr_t    nargs;
+    nxt_uint_t    i;
+    njs_value_t  *object, *args, *value;
+    njs_array_t  *array;
+
+    object = param->object;
+
+    if (njs_is_array(object)) {
+        length = object->data.u.array->length;
+
+    } else {
+        length = 1;
+    }
+
+    nargs = param->nargs;
+    args = param->args;
+
+    for (i = 0; i < nargs; i++) {
+        if (njs_is_array(&args[i])) {
+            length += args[i].data.u.array->length;
+
+        } else {
+            length++;
+        }
+    }
+
+    array = njs_array_alloc(vm, length, NJS_ARRAY_SPARE);
+    if (nxt_slow_path(array == NULL)) {
+        return NXT_ERROR;
+    }
+
+    vm->retval.data.u.array = array;
+    vm->retval.type = NJS_ARRAY;
+    vm->retval.data.truth = 1;
+
+    value = njs_array_copy(array->start, object);
+
+    for (i = 0; i < nargs; i++) {
+        value = njs_array_copy(value, &args[i]);
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_noinline njs_value_t *
+njs_array_copy(njs_value_t *dst, njs_value_t *src)
+{
+    nxt_uint_t  n;
+
+    n = 1;
+
+    if (njs_is_array(src)) {
+        n = src->data.u.array->length;
+        src = src->data.u.array->start;
+    }
+
+    while (n != 0) {
+        /* GC: njs_retain src */
+        *dst++ = *src++;
+        n--;
+    }
+
+    return dst;
+}
+
+
+static njs_ret_t
+njs_array_prototype_for_each(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_int_t         n;
+    uintptr_t         nargs;
+    njs_param_t       p;
+    njs_array_t       *array;
+    njs_value_t       *object, *args, *func, arguments[3];
+    njs_array_each_t  *each;
+
+    object = param->object;
+
+    if (!vm->frame->reentrant) {
+        vm->frame->reentrant = 1;
+
+        if (!njs_is_array(object)) {
+            vm->exception = &njs_exception_type_error;
+            return NXT_ERROR;
+        }
+
+        array = object->data.u.array;
+        n = njs_array_next(array->start, 0, array->length);
+
+        if (n < 0) {
+            vm->retval = njs_value_void;
+            return NXT_OK;
+        }
+
+        each = njs_native_data(vm->frame);
+        each->index = n;
+        each->length = array->length;
+    }
+
+    each = njs_native_data(vm->frame);
+    n = each->index;
+
+    /* GC: array elt, array */
+    array = object->data.u.array;
+    arguments[0] = array->start[n];
+    njs_number_set(&arguments[1], n);
+    arguments[2] = *object;
+
+    n = njs_array_next(array->start, ++n, each->length);
+    each->index = n;
+
+    if (n > 0) {
+        vm->current -= sizeof(njs_vmcode_call_t);
+    }
+
+    nargs = param->nargs;
+    args = param->args;
+
+    p.object = (nargs > 1) ? &args[1] : (njs_value_t *) &njs_value_void;
+    p.args = arguments;
+    p.nargs = 3;
+    p.retval = (njs_index_t) &each->retval;
+
+    func = (nargs != 0) ? &args[0] : (njs_value_t *) &njs_value_void;
+
+    return njs_function_apply(vm, func, &p);
+}
+
+
+static njs_ret_t
+njs_array_prototype_some(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_int_t          n;
+    uintptr_t         nargs;
+    njs_param_t       p;
+    njs_array_t       *array;
+    njs_value_t       *object, *args, *func, arguments[3];
+    njs_array_each_t  *each;
+
+    object = param->object;
+
+    if (!vm->frame->reentrant) {
+        vm->frame->reentrant = 1;
+
+        if (!njs_is_array(object)) {
+            vm->exception = &njs_exception_type_error;
+            return NXT_ERROR;
+        }
+
+        array = object->data.u.array;
+        n = njs_array_next(array->start, 0, array->length);
+        each = njs_native_data(vm->frame);
+        each->index = n;
+        each->length = array->length;
+
+    } else {
+        each = njs_native_data(vm->frame);
+
+        if (njs_is_true(&each->retval)) {
+            vm->retval = njs_value_true;
+            return NXT_OK;
+        }
+    }
+
+    n = each->index;
+
+    if (n < 0) {
+        vm->retval = njs_value_false;
+        return NXT_OK;
+    }
+
+    /* GC: array elt, array */
+    array = object->data.u.array;
+    arguments[0] = array->start[n];
+    njs_number_set(&arguments[1], n);
+    arguments[2] = *object;
+
+    each->index = njs_array_next(array->start, ++n, each->length);
+
+    nargs = param->nargs;
+    args = param->args;
+
+    p.object = (nargs > 1) ? &args[1] : (njs_value_t *) &njs_value_void;
+    p.args = arguments;
+    p.nargs = 3;
+    p.retval = (njs_index_t) &each->retval;
+
+    func = (nargs != 0) ? &args[0] : (njs_value_t *) &njs_value_void;
+
+    vm->current -= sizeof(njs_vmcode_call_t);
+
+    return njs_function_apply(vm, func, &p);
+}
+
+
+static njs_ret_t
+njs_array_prototype_every(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_int_t          n;
+    uintptr_t         nargs;
+    njs_param_t       p;
+    njs_array_t       *array;
+    njs_value_t       *object, *args, *func, arguments[3];
+    njs_array_each_t  *each;
+
+    object = param->object;
+
+    if (!vm->frame->reentrant) {
+        vm->frame->reentrant = 1;
+
+        if (!njs_is_array(object)) {
+            vm->exception = &njs_exception_type_error;
+            return NXT_ERROR;
+        }
+
+        array = object->data.u.array;
+        n = njs_array_next(array->start, 0, array->length);
+        each = njs_native_data(vm->frame);
+        each->index = n;
+        each->length = array->length;
+
+    } else {
+        each = njs_native_data(vm->frame);
+
+        if (!njs_is_true(&each->retval)) {
+            vm->retval = njs_value_false;
+            return NXT_OK;
+        }
+    }
+
+    n = each->index;
+
+    if (n < 0) {
+        vm->retval = njs_value_true;
+        return NXT_OK;
+    }
+
+    /* GC: array elt, array */
+    array = object->data.u.array;
+    arguments[0] = array->start[n];
+    njs_number_set(&arguments[1], n);
+    arguments[2] = *object;
+
+    each->index = njs_array_next(array->start, ++n, each->length);
+
+    nargs = param->nargs;
+    args = param->args;
+
+    p.object = (nargs > 1) ? &args[1] : (njs_value_t *) &njs_value_void;
+    p.args = arguments;
+    p.nargs = 3;
+    p.retval = (njs_index_t) &each->retval;
+
+    func = (nargs != 0) ? &args[0] : (njs_value_t *) &njs_value_void;
+
+    vm->current -= sizeof(njs_vmcode_call_t);
+
+    return njs_function_apply(vm, func, &p);
+}
+
+
+static nxt_int_t
+njs_array_next(njs_value_t *value, nxt_uint_t n, nxt_uint_t length)
+{
+    while (n < length) {
+        if (njs_is_valid(&value[n])) {
+            return n;
+        }
+
+        n++;
+    }
+
+    return -1;
+}
+
+
+static const njs_object_prop_t  njs_array_prototype_properties[] =
+{
+    { njs_getter(njs_array_prototype_length),
+      njs_string("length"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_slice, 0),
+      njs_string("slice"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_push, 0),
+      njs_string("push"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_pop, 0),
+      njs_string("pop"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_unshift, 0),
+      njs_string("unshift"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_shift, 0),
+      njs_string("shift"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_to_string, 0),
+      njs_string("toString"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_join, 0),
+      njs_string("join"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_concat, 0),
+      njs_string("concat"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_for_each,
+                          njs_method_data_size(sizeof(njs_array_each_t))),
+      njs_string("forEach"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_some,
+                          njs_method_data_size(sizeof(njs_array_each_t))),
+      njs_string("some"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_array_prototype_every,
+                          njs_method_data_size(sizeof(njs_array_each_t))),
+      njs_string("every"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_array_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_array_prototype_properties,
+                                  nxt_nitems(njs_array_prototype_properties));
+}
diff --git a/njs/njs_array.h b/njs/njs_array.h
new file mode 100644 (file)
index 0000000..7aa3db7
--- /dev/null
@@ -0,0 +1,31 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_ARRAY_H_INCLUDED_
+#define _NJS_ARRAY_H_INCLUDED_
+
+
+#define NJS_ARRAY_SPARE  8
+
+struct njs_array_s {
+    /* Must be aligned to njs_value_t. */
+    njs_object_t         object;
+    uint32_t             size;
+    uint32_t             length;
+    njs_value_t          *start;
+    njs_value_t          *data;
+};
+
+
+njs_array_t *njs_array_alloc(njs_vm_t *vm, uint32_t length, uint32_t spare);
+njs_ret_t njs_array_realloc(njs_vm_t *vm, njs_array_t *array, uint32_t prepend,
+    uint32_t size);
+njs_ret_t njs_array_function(njs_vm_t *vm, njs_param_t *param);
+nxt_int_t njs_array_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+nxt_int_t njs_array_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+#endif /* _NJS_ARRAY_H_INCLUDED_ */
diff --git a/njs/njs_disassembler.c b/njs/njs_disassembler.c
new file mode 100644 (file)
index 0000000..a2f125b
--- /dev/null
@@ -0,0 +1,236 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+
+
+typedef struct {
+    njs_vmcode_operation_t     operation;
+    size_t                     size;
+    nxt_str_t                  name;
+} njs_code_name_t;
+
+
+static njs_code_name_t  code_names[] = {
+
+    { njs_vmcode_object_create, sizeof(njs_vmcode_object_t),
+          nxt_string("OBJECT CREATE   ") },
+    { njs_vmcode_array_create, sizeof(njs_vmcode_array_t),
+          nxt_string("ARRAY CREATE    ") },
+    { njs_vmcode_function_create, sizeof(njs_vmcode_function_create_t),
+          nxt_string("FUNCTION CREATE ") },
+    { njs_vmcode_regexp_create, sizeof(njs_vmcode_regexp_t),
+          nxt_string("REGEXP CREATE   ") },
+
+    { njs_vmcode_property_get, sizeof(njs_vmcode_prop_get_t),
+          nxt_string("PROPERTY GET    ") },
+    { njs_vmcode_property_set, sizeof(njs_vmcode_prop_set_t),
+          nxt_string("PROPERTY SET    ") },
+    { njs_vmcode_property_in, sizeof(njs_vmcode_3addr_t),
+          nxt_string("PROPERTY IN     ") },
+    { njs_vmcode_property_delete, sizeof(njs_vmcode_3addr_t),
+          nxt_string("PROPERTY DELETE ") },
+    { njs_vmcode_property_each_start, sizeof(njs_vmcode_prop_start_t),
+          nxt_string("PROPERTY START  ") },
+    { njs_vmcode_property_each, sizeof(njs_vmcode_prop_each_t),
+          nxt_string("PROPERTY EACH   ") },
+    { njs_vmcode_instance_of, sizeof(njs_vmcode_instance_of_t),
+          nxt_string("INSTANCE OF     ") },
+
+    { njs_vmcode_function, sizeof(njs_vmcode_function_t),
+          nxt_string("FUNCTION        ") },
+    { njs_vmcode_method, sizeof(njs_vmcode_method_t),
+          nxt_string("METHOD          ") },
+    { njs_vmcode_call, sizeof(njs_vmcode_call_t),
+          nxt_string("CALL            ") },
+    { njs_vmcode_return, sizeof(njs_vmcode_stop_t),
+          nxt_string("RETURN          ") },
+
+    { njs_vmcode_increment, sizeof(njs_vmcode_3addr_t),
+          nxt_string("INC             ") },
+    { njs_vmcode_decrement, sizeof(njs_vmcode_3addr_t),
+          nxt_string("DEC             ") },
+    { njs_vmcode_post_increment, sizeof(njs_vmcode_3addr_t),
+          nxt_string("POST INC        ") },
+    { njs_vmcode_post_decrement, sizeof(njs_vmcode_3addr_t),
+          nxt_string("POST DEC        ") },
+
+    { njs_vmcode_delete, sizeof(njs_vmcode_2addr_t),
+          nxt_string("DELETE          ") },
+    { njs_vmcode_void, sizeof(njs_vmcode_2addr_t),
+          nxt_string("VOID            ") },
+    { njs_vmcode_typeof, sizeof(njs_vmcode_2addr_t),
+          nxt_string("TYPEOF          ") },
+
+    { njs_vmcode_unary_plus, sizeof(njs_vmcode_2addr_t),
+          nxt_string("PLUS            ") },
+    { njs_vmcode_unary_negation, sizeof(njs_vmcode_2addr_t),
+          nxt_string("NEGATION        ") },
+
+    { njs_vmcode_addition, sizeof(njs_vmcode_3addr_t),
+          nxt_string("ADD             ") },
+    { njs_vmcode_substraction, sizeof(njs_vmcode_3addr_t),
+          nxt_string("SUBSTRACT       ") },
+    { njs_vmcode_multiplication, sizeof(njs_vmcode_3addr_t),
+          nxt_string("MULTIPLY        ") },
+    { njs_vmcode_division, sizeof(njs_vmcode_3addr_t),
+          nxt_string("DIVIDE          ") },
+    { njs_vmcode_remainder, sizeof(njs_vmcode_3addr_t),
+          nxt_string("REMAINDER       ") },
+
+    { njs_vmcode_left_shift, sizeof(njs_vmcode_3addr_t),
+          nxt_string("LEFT SHIFT      ") },
+    { njs_vmcode_right_shift, sizeof(njs_vmcode_3addr_t),
+          nxt_string("RIGHT SHIFT     ") },
+    { njs_vmcode_unsigned_right_shift, sizeof(njs_vmcode_3addr_t),
+          nxt_string("UNS RIGHT SHIFT ") },
+
+    { njs_vmcode_logical_not, sizeof(njs_vmcode_2addr_t),
+          nxt_string("LOGICAL NOT     ") },
+    { njs_vmcode_logical_and, sizeof(njs_vmcode_3addr_t),
+          nxt_string("LOGICAL AND     ") },
+    { njs_vmcode_logical_or, sizeof(njs_vmcode_3addr_t),
+          nxt_string("LOGICAL OR      ") },
+
+    { njs_vmcode_bitwise_not, sizeof(njs_vmcode_2addr_t),
+          nxt_string("BINARY NOT      ") },
+    { njs_vmcode_bitwise_and, sizeof(njs_vmcode_3addr_t),
+          nxt_string("BINARY AND      ") },
+    { njs_vmcode_bitwise_xor, sizeof(njs_vmcode_3addr_t),
+          nxt_string("BINARY XOR      ") },
+    { njs_vmcode_bitwise_or, sizeof(njs_vmcode_3addr_t),
+          nxt_string("BINARY OR       ") },
+
+    { njs_vmcode_equal, sizeof(njs_vmcode_3addr_t),
+          nxt_string("EQUAL           ") },
+    { njs_vmcode_not_equal, sizeof(njs_vmcode_3addr_t),
+          nxt_string("NOT EQUAL       ") },
+    { njs_vmcode_less, sizeof(njs_vmcode_3addr_t),
+          nxt_string("LESS            ") },
+    { njs_vmcode_less_or_equal, sizeof(njs_vmcode_3addr_t),
+          nxt_string("LESS OR EQUAL   ") },
+    { njs_vmcode_greater, sizeof(njs_vmcode_3addr_t),
+          nxt_string("GREATER         ") },
+    { njs_vmcode_greater_or_equal, sizeof(njs_vmcode_3addr_t),
+          nxt_string("GREATER OR EQUAL") },
+
+    { njs_vmcode_strict_equal, sizeof(njs_vmcode_3addr_t),
+          nxt_string("STRICT EQUAL    ") },
+    { njs_vmcode_strict_not_equal, sizeof(njs_vmcode_3addr_t),
+          nxt_string("STRICT NOT EQUAL") },
+
+    { njs_vmcode_move, sizeof(njs_vmcode_move_t),
+          nxt_string("MOVE            ") },
+    { njs_vmcode_validate, sizeof(njs_vmcode_validate_t),
+          nxt_string("VALIDATE        ") },
+
+    { njs_vmcode_if_true_jump, sizeof(njs_vmcode_cond_jump_t),
+          nxt_string("JUMP IF TRUE    ") },
+    { njs_vmcode_if_false_jump, sizeof(njs_vmcode_cond_jump_t),
+          nxt_string("JUMP IF FALSE   ") },
+    { njs_vmcode_jump, sizeof(njs_vmcode_jump_t),
+          nxt_string("JUMP            ") },
+    { njs_vmcode_stop, sizeof(njs_vmcode_stop_t),
+          nxt_string("STOP            ") },
+
+    { njs_vmcode_try_start, sizeof(njs_vmcode_try_start_t),
+          nxt_string("TRY START       ") },
+    { njs_vmcode_try_end, sizeof(njs_vmcode_try_end_t),
+          nxt_string("TRY END         ") },
+    { njs_vmcode_throw, sizeof(njs_vmcode_throw_t),
+          nxt_string("THROW           ") },
+    { njs_vmcode_catch, sizeof(njs_vmcode_catch_t),
+          nxt_string("CATCH           ") },
+    { njs_vmcode_finally, sizeof(njs_vmcode_finally_t),
+          nxt_string("FINALLY         ") },
+
+};
+
+
+void
+njs_disassembler(u_char *start, u_char *end, nxt_str_t *text)
+{
+    u_char                  *p;
+    nxt_uint_t               n;
+    nxt_str_t                *name;
+    njs_vmcode_1addr_t      *code1;
+    njs_vmcode_2addr_t      *code2;
+    njs_vmcode_3addr_t      *code3;
+    njs_vmcode_method_t     *method;
+    njs_code_name_t         *code_name;
+    njs_vmcode_operation_t  operation;
+
+    static nxt_str_t         unknown = nxt_string("UNKOWN");
+
+    (void) name;
+    (void) code1;
+    (void) code2;
+    (void) code3;
+    (void) method;
+
+    p = start;
+
+    while (p < end) {
+        operation = *(njs_vmcode_operation_t *) p;
+        code_name = code_names;
+        n = nxt_nitems(code_names);
+
+        do {
+             if (operation == code_name->operation) {
+                 name = &code_name->name;
+
+                 if (code_name->size == sizeof(njs_vmcode_method_t)) {
+                     method = (njs_vmcode_method_t *) p;
+                     nxt_log_error(NXT_LOG_INFO, log, "%V  %p %p %p %p",
+                                   name, method->function, method->object,
+                                   method->method, method->code.nargs);
+
+                 } else if (code_name->size == sizeof(njs_vmcode_3addr_t)) {
+                     code3 = (njs_vmcode_3addr_t *) p;
+                     nxt_log_error(NXT_LOG_INFO, log, "%V  %p %p %p",
+                                   name, code3->dst, code3->src1, code3->src2);
+
+                 } else if (code_name->size == sizeof(njs_vmcode_2addr_t)) {
+                     code2 = (njs_vmcode_2addr_t *) p;
+                     nxt_log_error(NXT_LOG_INFO, log, "%V  %p %p",
+                                   name, code2->dst, code2->src);
+
+                 } else if (code_name->size == sizeof(njs_vmcode_1addr_t)) {
+                     code1 = (njs_vmcode_1addr_t *) p;
+                     nxt_log_error(NXT_LOG_INFO, log, "%V  %p",
+                                   name, code1->index);
+                 }
+
+                 p += code_name->size;
+
+                 goto next;
+             }
+
+             code_name++;
+             n--;
+
+        } while (n != 0);
+
+        p += sizeof(njs_vmcode_operation_t);
+        name = &unknown;
+
+        nxt_log_error(NXT_LOG_INFO, log, "%V  %p", name, operation);
+
+    next:
+
+        continue;
+    }
+}
diff --git a/njs/njs_extern.c b/njs/njs_extern.c
new file mode 100644 (file)
index 0000000..76d54d0
--- /dev/null
@@ -0,0 +1,133 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_utf8.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+static nxt_int_t
+njs_extern_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    njs_extern_t  *ext;
+
+    ext = data;
+
+// STUB
+//    if (nxt_strcasestr_eq(&lhq->key, &ext->name)) {
+    if (nxt_strstr_eq(&lhq->key, &ext->name)) {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+const nxt_lvlhsh_proto_t  njs_extern_hash_proto
+    nxt_aligned(64) =
+{
+    NXT_LVLHSH_DEFAULT,
+    NXT_LVLHSH_BATCH_ALLOC,
+    njs_extern_hash_test,
+    njs_lvlhsh_alloc,
+    njs_lvlhsh_free,
+};
+
+
+nxt_int_t
+njs_add_external(nxt_lvlhsh_t *hash, nxt_mem_cache_pool_t *mcp, uintptr_t object,
+    njs_external_t *external, nxt_uint_t n)
+{
+    nxt_int_t           ret;
+    njs_extern_t       *ext;
+    nxt_lvlhsh_query_t  lhq;
+
+    do {
+        ext = nxt_mem_cache_align(mcp, sizeof(njs_value_t),
+                                 sizeof(njs_extern_t));
+        if (nxt_slow_path(ext == NULL)) {
+            return NXT_ERROR;
+        }
+
+        ext->name.len = external->name.len;
+        ext->name.data = nxt_mem_cache_alloc(mcp, external->name.len);
+        if (nxt_slow_path(ext->name.data == NULL)) {
+            return NXT_ERROR;
+        }
+
+        memcpy(ext->name.data, external->name.data, external->name.len);
+
+        ext->value.type = NJS_EXTERNAL;
+        ext->value.data.truth = 1;
+        ext->value.data.u.external = ext;
+
+        nxt_lvlhsh_init(&ext->hash);
+        ext->type = external->type;
+        ext->get = external->get;
+        ext->set = external->set;
+        ext->find = external->find;
+        ext->each_start = external->each_start;
+        ext->each = external->each;
+        ext->method = external->method;
+        ext->object = object;
+        ext->data = external->data;
+
+        lhq.key_hash = nxt_djb_hash(external->name.data, external->name.len);
+        lhq.key = ext->name;
+        lhq.replace = 0;
+        lhq.value = ext;
+        lhq.pool = mcp;
+        lhq.proto = &njs_extern_hash_proto;
+
+        ret = nxt_lvlhsh_insert(hash, &lhq);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        if (external->properties != NULL) {
+            ret = njs_add_external(&ext->hash, mcp, object,
+                                   external->properties, external->nproperties);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+        }
+
+        external++;
+        n--;
+
+    } while (n != 0);
+
+    return NXT_OK;
+}
+
+
+njs_extern_t *
+njs_parser_external(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = parser->lexer->key_hash;
+    lhq.key = parser->lexer->text;
+    lhq.proto = &njs_extern_hash_proto;
+
+    if (nxt_lvlhsh_find(&vm->externals_hash, &lhq) == NXT_OK) {
+        return lhq.value;
+    }
+
+    return NULL;
+}
diff --git a/njs/njs_extern.h b/njs/njs_extern.h
new file mode 100644 (file)
index 0000000..fb512b3
--- /dev/null
@@ -0,0 +1,37 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_EXTERN_H_INCLUDED_
+#define _NJS_EXTERN_H_INCLUDED_
+
+
+struct njs_extern_s {
+    njs_value_t                  value;
+
+    /* A hash of inclusive njs_extern_t. */
+    nxt_lvlhsh_t                 hash;
+
+    uintptr_t                    type;
+    nxt_str_t                    name;
+
+    njs_extern_get_t             get;
+    njs_extern_set_t             set;
+    njs_extern_find_t            find;
+
+    njs_extern_each_start_t      each_start;
+    njs_extern_each_t            each;
+
+    njs_extern_method_t          method;
+
+    uintptr_t                    object;
+    uintptr_t                    data;
+};
+
+
+extern const nxt_lvlhsh_proto_t  njs_extern_hash_proto;
+
+
+#endif /* _NJS_EXTERN_H_INCLUDED_ */
diff --git a/njs/njs_function.c b/njs/njs_function.c
new file mode 100644 (file)
index 0000000..d9f1ad4
--- /dev/null
@@ -0,0 +1,518 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <string.h>
+
+
+njs_function_t *
+njs_function_alloc(njs_vm_t *vm)
+{
+    njs_function_t         *func;
+    njs_function_script_t  *script;
+
+    func = nxt_mem_cache_zalign(vm->mem_cache_pool, sizeof(njs_value_t),
+                                sizeof(njs_function_t));
+
+    if (nxt_fast_path(func != NULL)) {
+        func->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+        func->args_offset = 1;
+
+        script = nxt_mem_cache_zalloc(vm->mem_cache_pool,
+                                      sizeof(njs_function_script_t));
+        if (nxt_slow_path(script == NULL)) {
+            return NULL;
+        }
+
+        func->code.script = script;
+    }
+
+    return func;
+}
+
+
+nxt_noinline njs_value_t *
+njs_vmcode_native_frame(njs_vm_t *vm, njs_value_t *method, uintptr_t nargs,
+    nxt_bool_t ctor)
+{
+    size_t              size, spare_size;
+    njs_value_t         *this;
+    njs_native_frame_t  *frame;
+
+    size= NJS_NATIVE_FRAME_SIZE
+           + method->data.string_size
+           + nargs * sizeof(njs_value_t);
+
+    if (nxt_fast_path(size <= vm->frame->size)) {
+        frame = (njs_native_frame_t *) vm->frame->last;
+        frame->size = vm->frame->size - size;
+        frame->start = 0;
+
+    } else {
+        spare_size = size + NJS_FRAME_SPARE_SIZE;
+        spare_size = nxt_align_size(spare_size, NJS_FRAME_SPARE_SIZE);
+
+        frame = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                    spare_size);
+        if (nxt_slow_path(frame == NULL)) {
+            return NULL;
+        }
+
+        frame->size = spare_size - size;
+        frame->start = 1;
+    }
+
+    frame->ctor = ctor;
+    frame->reentrant = 0;
+    frame->lvalue = 0;
+
+    frame->u.exception.next = NULL;
+    frame->u.exception.catch = NULL;
+
+    frame->last = (u_char *) frame + size;
+    frame->previous = vm->frame;
+    vm->frame = frame;
+
+    this = (njs_value_t *)
+               ((u_char *) njs_native_data(frame) + method->data.string_size);
+    frame->arguments = this + 1;
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = frame->arguments;
+
+    return this;
+}
+
+
+njs_ret_t
+njs_vmcode_trap(njs_vm_t *vm, u_char *trap, njs_value_t *value1,
+    njs_value_t *value2, nxt_bool_t lvalue)
+{
+    size_t              size, spare_size;
+    njs_value_t         *data;
+    njs_native_frame_t  *frame;
+
+    size = NJS_NATIVE_FRAME_SIZE + 3 * sizeof(njs_value_t);
+
+    if (nxt_fast_path(size <= vm->frame->size)) {
+        frame = (njs_native_frame_t *) vm->frame->last;
+        frame->size = vm->frame->size - size;
+        frame->start = 0;
+
+    } else {
+        spare_size = size + NJS_FRAME_SPARE_SIZE;
+        spare_size = nxt_align_size(spare_size, NJS_FRAME_SPARE_SIZE);
+
+        frame = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                    spare_size);
+        if (nxt_slow_path(frame == NULL)) {
+            return NXT_ERROR;
+        }
+
+        frame->size = spare_size - size;
+        frame->start = 1;
+    }
+
+    frame->ctor = 0;
+    frame->reentrant = 0;
+    frame->lvalue = lvalue;
+
+    data = njs_native_data(frame);
+    njs_set_invalid(&data[0]);
+    data[1] = *value1;
+
+    if (lvalue) {
+        data[2].data.u.value = value2;
+
+    } else {
+        data[2] = *value2;
+    }
+
+    frame->u.exception.catch = NULL;
+    frame->u.restart = vm->current;
+    vm->current = trap;
+
+    frame->last = (u_char *) frame + size;
+    frame->previous = vm->frame;
+    vm->frame = frame;
+
+    return NXT_OK;
+}
+
+
+nxt_noinline njs_ret_t
+njs_function_apply(njs_vm_t *vm, njs_value_t *name, njs_param_t *param)
+{
+    njs_ret_t  ret;
+
+    if (njs_is_native(name)) {
+        return name->data.u.method(vm, param);
+
+    } else if (njs_is_function(name)) {
+
+        if (name->data.u.function->native) {
+            return name->data.u.function->code.native(vm, param);
+        }
+
+        ret = njs_vmcode_function_frame(vm, name, param, 0);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            vm->retval = njs_value_void;
+
+            return njs_function_call(vm, name->data.u.function, param->retval);
+        }
+    }
+
+    return NXT_ERROR;
+}
+
+
+nxt_noinline njs_ret_t
+njs_vmcode_function_frame(njs_vm_t *vm, njs_value_t *name, njs_param_t *param,
+    nxt_bool_t ctor)
+{
+    size_t          size, spare_size;
+    uintptr_t       nargs, n;
+    njs_value_t     *args, *arguments;
+    njs_frame_t     *frame;
+    njs_function_t  *func;
+
+    func = name->data.u.function;
+    nargs = nxt_max(param->nargs, func->code.script->nargs);
+
+    size = NJS_FRAME_SIZE
+           + nargs * sizeof(njs_value_t)
+           + func->code.script->local_size;
+    spare_size = size + func->code.script->spare_size;
+
+    if (spare_size <= vm->frame->size) {
+        frame = (njs_frame_t *) vm->frame->last;
+        frame->native.size = vm->frame->size - size;
+        frame->native.start = 0;
+
+    } else {
+        if (func->code.script->spare_size != 0) {
+            spare_size = size + NJS_FRAME_SPARE_SIZE;
+            spare_size = nxt_align_size(spare_size, NJS_FRAME_SPARE_SIZE);
+        }
+
+        frame = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                    spare_size);
+        if (nxt_slow_path(frame == NULL)) {
+            return NXT_ERROR;
+        }
+
+        frame->native.size = spare_size - size;
+        frame->native.start = 1;
+    }
+
+    frame->native.ctor = ctor;
+    frame->native.reentrant = 0;
+    frame->native.lvalue = 0;
+
+    frame->native.u.exception.next = NULL;
+    frame->native.u.exception.catch = NULL;
+
+    frame->native.last = (u_char *) frame + size;
+    frame->native.previous = vm->frame;
+    vm->frame = &frame->native;
+
+    args = (njs_value_t *) ((u_char *) frame + NJS_FRAME_SIZE);
+    frame->native.arguments = args + func->args_offset;
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = frame->native.arguments;
+
+    frame->local = &args[nargs];
+
+    *args++ = *param->object;
+    nargs--;
+
+    arguments = param->args;
+
+    if (arguments != NULL) {
+        n = param->nargs;
+
+        while (n != 0) {
+            *args++ = *arguments++;
+            nargs--;
+            n--;
+        }
+    }
+
+    while (nargs != 0) {
+        *args++ = njs_value_void;
+        nargs--;
+    }
+
+    memcpy(frame->local, func->code.script->local_scope,
+           func->code.script->local_size);
+
+    vm->retval = *name;
+
+    return NXT_OK;
+}
+
+
+nxt_noinline njs_ret_t
+njs_function_call(njs_vm_t *vm, njs_function_t *func, njs_index_t retval)
+{
+    njs_frame_t  *frame;
+
+    frame = (njs_frame_t *) vm->frame;
+
+    frame->retval = retval;
+
+    frame->return_address = vm->current;
+
+    vm->current = func->code.script->u.code;
+
+    frame->prev_arguments = vm->scopes[NJS_SCOPE_ARGUMENTS];
+    vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->native.arguments
+                                      - func->args_offset;
+#if (NXT_DEBUG)
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = NULL;
+#endif
+    frame->prev_local = vm->scopes[NJS_SCOPE_LOCAL];
+    vm->scopes[NJS_SCOPE_LOCAL] = frame->local;
+
+    return NJS_PASS;
+}
+
+
+static const njs_object_prop_t  njs_function_function_properties[] =
+{
+    { njs_string("Function"),
+      njs_string("name"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_value(NJS_NUMBER, 0, 0.0),
+      njs_string("length"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_prototype),
+      njs_string("prototype"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_function_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_function_function_properties,
+                                  nxt_nitems(njs_function_function_properties));
+}
+
+
+static njs_ret_t
+njs_function_prototype_call(njs_vm_t *vm, njs_param_t *param)
+{
+    uintptr_t          nargs;
+    njs_ret_t          ret;
+    njs_param_t        p;
+    njs_value_t        *func;
+    njs_vmcode_call_t  *call;
+
+    p.object = &param->args[0];
+    p.args = &param->args[1];
+
+    func = param->object;
+    nargs = param->nargs;
+
+    if (njs_is_native(func)) {
+
+        if (nargs != 0) {
+            p.nargs = nargs - 1;
+            p.retval = param->retval;
+
+            return func->data.u.method(vm, &p);
+        }
+
+        vm->exception = &njs_exception_type_error;
+        return NXT_ERROR;
+    }
+
+    if (func->data.u.function->native) {
+
+        if (nargs != 0) {
+            p.nargs = nargs - 1;
+            p.retval = param->retval;
+
+            return func->data.u.function->code.native(vm, &p);
+        }
+
+        vm->exception = &njs_exception_type_error;
+        return NXT_ERROR;
+    }
+
+    if (nargs != 0) {
+        nargs--;
+
+    } else {
+        p.object = (njs_value_t *) &njs_value_void;
+    }
+
+    p.nargs = nargs;
+
+    ret = njs_vmcode_function_frame(vm, func, &p, 0);
+
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NXT_ERROR;
+    }
+
+    /* Skip the "call" method frame. */
+    vm->frame->previous = vm->frame->previous->previous;
+
+    call = (njs_vmcode_call_t *) vm->current;
+
+    return njs_function_call(vm, func->data.u.function, call->retval);
+}
+
+
+static njs_ret_t
+njs_function_prototype_apply(njs_vm_t *vm, njs_param_t *param)
+{
+    uintptr_t          nargs;
+    njs_ret_t          ret;
+    njs_param_t        p;
+    njs_array_t        *array;
+    njs_value_t        *func, *args;
+    njs_vmcode_call_t  *code;
+
+    args = param->args;
+    p.object = &args[0];
+
+    nargs = param->nargs;
+    p.nargs = nargs;
+
+    if (nargs > 1) {
+        if (!njs_is_array(&args[1])) {
+            goto type_error;
+        }
+
+        array = args[1].data.u.array;
+        p.args = array->start;
+        p.nargs = array->length;
+    }
+
+    func = param->object;
+
+    if (njs_is_native(func)) {
+        p.retval = param->retval;
+
+        if (nargs < 2) {
+            if (nargs != 0) {
+                p.args = &args[1];
+                p.nargs = nargs - 1;
+
+            } else {
+                goto type_error;
+            }
+        }
+
+        return func->data.u.method(vm, &p);
+    }
+
+    if (func->data.u.function->native) {
+        p.retval = param->retval;
+
+        if (nargs < 2) {
+            if (nargs != 0) {
+                p.args = &args[1];
+                p.nargs = nargs - 1;
+
+            } else {
+                goto type_error;
+            }
+        }
+
+        return func->data.u.function->code.native(vm, &p);
+    }
+
+    if (nargs < 2) {
+        if (nargs != 0) {
+            p.nargs = 0;
+
+        } else {
+            p.object = (njs_value_t *) &njs_value_void;
+        }
+    }
+
+    ret = njs_vmcode_function_frame(vm, func, &p, 0);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        /* Skip the "apply" method frame. */
+        vm->frame->previous = vm->frame->previous->previous;
+
+        code = (njs_vmcode_call_t *) vm->current;
+
+        return njs_function_call(vm, func->data.u.function, code->retval);
+    }
+
+    return NXT_ERROR;
+
+type_error:
+
+    vm->exception = &njs_exception_type_error;
+
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_function_prototype_bind(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_value_t     *func;
+    njs_function_t  *bound;
+
+    bound = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                sizeof(njs_function_t));
+
+    if (nxt_fast_path(bound != NULL)) {
+        nxt_lvlhsh_init(&bound->object.hash);
+        nxt_lvlhsh_init(&bound->object.shared_hash);
+        bound->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+        bound->args_offset = 1;
+
+        func = param->object;
+        bound->code.script = func->data.u.function->code.script;
+
+        vm->retval.data.u.function = bound;
+        vm->retval.type = NJS_FUNCTION;
+        vm->retval.data.truth = 1;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static const njs_object_prop_t  njs_function_prototype_properties[] =
+{
+    { njs_native_function(njs_function_prototype_call, 0),
+      njs_string("call"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_function_prototype_apply, 0),
+      njs_string("apply"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_function_prototype_bind, 0),
+      njs_string("bind"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_function_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_function_prototype_properties,
+                           nxt_nitems(njs_function_prototype_properties));
+}
diff --git a/njs/njs_function.h b/njs/njs_function.h
new file mode 100644 (file)
index 0000000..be85628
--- /dev/null
@@ -0,0 +1,134 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_FUNCTION_H_INCLUDED_
+#define _NJS_FUNCTION_H_INCLUDED_
+
+
+typedef struct {
+    uint32_t                       nargs;
+    uint32_t                       local_size;
+    /*
+     * Native methods do not allocate frame space so calling function
+     * reserves space in its scope for method frame and arguments.
+     */
+    uint32_t                       spare_size;
+
+    /* Initial values of local scope. */
+    njs_value_t                    *local_scope;
+
+    union {
+        u_char                     *code;
+        njs_parser_t               *parser;
+    } u;
+} njs_function_script_t;
+
+
+struct njs_function_s {
+    njs_object_t                   object;
+
+#if (NXT_64BIT)
+    uint32_t                       native;
+    uint32_t                       args_offset;
+#else
+    uint8_t                        native;
+    uint16_t                       args_offset;
+#endif
+
+    union {
+        njs_function_script_t      *script;
+        njs_native_t               native;
+    } code;
+
+    njs_value_t                    *args;
+};
+
+
+/* The frame size must be aligned to njs_value_t. */
+#define NJS_NATIVE_FRAME_SIZE                                                 \
+    nxt_align_size(sizeof(njs_native_frame_t), sizeof(njs_value_t))
+
+/* The frame size must be aligned to njs_value_t. */
+#define NJS_FRAME_SIZE                                                        \
+    nxt_align_size(sizeof(njs_frame_t), sizeof(njs_value_t))
+
+/* The retval and return_address fields are not used in the global frame. */
+#define NJS_GLOBAL_FRAME_SIZE                                                 \
+    nxt_align_size(offsetof(njs_frame_t, retval), sizeof(njs_value_t))
+
+#define NJS_FRAME_SPARE_SIZE       512
+
+#define                                                                       \
+njs_method_data_size(size)                                                    \
+    nxt_align_size(size, sizeof(njs_value_t))
+
+#define                                                                       \
+njs_native_data(frame)                                                        \
+    (void *) ((u_char *) frame + NJS_NATIVE_FRAME_SIZE)
+
+
+typedef struct njs_exception_s     njs_exception_t;
+
+struct njs_exception_s {
+    /*
+     * The next field must be the first to alias it with restart address
+     * because it is not used to detect catch block existance in the frame.
+     */
+    njs_exception_t                *next;
+    u_char                         *catch;
+};
+
+
+typedef struct njs_native_frame_s  njs_native_frame_t;
+
+struct njs_native_frame_s {
+    u_char                         *last;
+    njs_native_frame_t             *previous;
+    njs_value_t                    *arguments;
+
+    union {
+        u_char                     *restart;
+        njs_exception_t            exception;
+    } u;
+
+    uint32_t                       size;
+
+    uint8_t                        start;      /* 1 bit */
+    uint8_t                        ctor;       /* 1 bit */
+    uint8_t                        reentrant;  /* 1 bit */
+    uint8_t                        lvalue;     /* 1 bit */
+};
+
+
+typedef struct {
+    njs_native_frame_t             native;
+
+    njs_value_t                    *prev_arguments;
+    njs_value_t                    *prev_local;
+    njs_value_t                    *local;
+    njs_value_t                    *closure;
+
+    njs_index_t                    retval;
+    u_char                         *return_address;
+} njs_frame_t;
+
+
+njs_function_t *njs_function_alloc(njs_vm_t *vm);
+njs_ret_t njs_function_apply(njs_vm_t *vm, njs_value_t *name,
+    njs_param_t *param);
+njs_value_t *njs_vmcode_native_frame(njs_vm_t *vm, njs_value_t *method,
+    uintptr_t nargs, nxt_bool_t ctor);
+njs_ret_t njs_vmcode_trap(njs_vm_t *vm, u_char *trap, njs_value_t *value1,
+    njs_value_t *value2, nxt_bool_t lvalue);
+njs_ret_t njs_vmcode_function_frame(njs_vm_t *vm, njs_value_t *name,
+    njs_param_t *param, nxt_bool_t ctor);
+njs_ret_t njs_function_call(njs_vm_t *vm, njs_function_t *func,
+    njs_index_t retval);
+nxt_int_t njs_function_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+nxt_int_t njs_function_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+#endif /* _NJS_FUNCTION_H_INCLUDED_ */
diff --git a/njs/njs_generator.c b/njs/njs_generator.c
new file mode 100644 (file)
index 0000000..e6c3c6e
--- /dev/null
@@ -0,0 +1,2101 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+static nxt_int_t njs_generator(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_variable(njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_if_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_cond_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_while_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_do_while_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_for_in_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_assignment(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_operation_assignment(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_object(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_array(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_function(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_regexp(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_delete(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_3addr_operation(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_2addr_operation(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_inc_dec_operation(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node, nxt_bool_t post);
+static nxt_int_t njs_generate_function_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_return_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generate_function_call(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_try_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static nxt_int_t njs_generate_throw_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static njs_index_t njs_generator_dest_index(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node);
+static njs_index_t njs_generator_temp_index_get(njs_parser_t *parser);
+static nxt_int_t njs_generator_children_indexes_release(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generator_node_index_release(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *node);
+static nxt_int_t njs_generator_index_release(njs_vm_t *vm, njs_parser_t *parser,
+    njs_index_t index);
+
+
+static nxt_int_t
+njs_generator(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    nxt_int_t  ret;
+
+    if (node == NULL) {
+        return NXT_OK;
+    }
+
+    switch (node->token) {
+
+    case NJS_TOKEN_IF:
+        return njs_generate_if_statement(vm, parser, node);
+
+    case NJS_TOKEN_CONDITIONAL:
+        return njs_generate_cond_expression(vm, parser, node);
+
+    case NJS_TOKEN_WHILE:
+        return njs_generate_while_statement(vm, parser, node);
+
+    case NJS_TOKEN_DO:
+        return njs_generate_do_while_statement(vm, parser, node);
+
+    case NJS_TOKEN_FOR:
+        return njs_generate_for_statement(vm, parser, node);
+
+    case NJS_TOKEN_FOR_IN:
+        return njs_generate_for_in_statement(vm, parser, node);
+
+    case NJS_TOKEN_STATEMENT:
+    case NJS_TOKEN_COMMA:
+
+        if (node->left != NULL) {
+            ret = njs_generator(vm, parser, node->left);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+            node->index = node->left->index;
+
+            ret = njs_generator_node_index_release(vm, parser, node->left);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+        }
+
+        ret = njs_generator(vm, parser, node->right);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        node->index = node->right->index;
+
+        return njs_generator_node_index_release(vm, parser, node->right);
+
+    case NJS_TOKEN_ASSIGNMENT:
+        return njs_generate_assignment(vm, parser, node);
+
+    case NJS_TOKEN_BITWISE_OR_ASSIGNMENT:
+    case NJS_TOKEN_BITWISE_XOR_ASSIGNMENT:
+    case NJS_TOKEN_BITWISE_AND_ASSIGNMENT:
+    case NJS_TOKEN_LEFT_SHIFT_ASSIGNMENT:
+    case NJS_TOKEN_RIGHT_SHIFT_ASSIGNMENT:
+    case NJS_TOKEN_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+    case NJS_TOKEN_ADDITION_ASSIGNMENT:
+    case NJS_TOKEN_SUBSTRACTION_ASSIGNMENT:
+    case NJS_TOKEN_MULTIPLICATION_ASSIGNMENT:
+    case NJS_TOKEN_DIVISION_ASSIGNMENT:
+    case NJS_TOKEN_REMAINDER_ASSIGNMENT:
+        return njs_generate_operation_assignment(vm, parser, node);
+
+    case NJS_TOKEN_LOGICAL_OR:
+    case NJS_TOKEN_LOGICAL_AND:
+    case NJS_TOKEN_BITWISE_OR:
+    case NJS_TOKEN_BITWISE_XOR:
+    case NJS_TOKEN_BITWISE_AND:
+    case NJS_TOKEN_EQUAL:
+    case NJS_TOKEN_NOT_EQUAL:
+    case NJS_TOKEN_STRICT_EQUAL:
+    case NJS_TOKEN_STRICT_NOT_EQUAL:
+    case NJS_TOKEN_IN:
+    case NJS_TOKEN_INSTANCEOF:
+    case NJS_TOKEN_LESS:
+    case NJS_TOKEN_LESS_OR_EQUAL:
+    case NJS_TOKEN_GREATER:
+    case NJS_TOKEN_GREATER_OR_EQUAL:
+    case NJS_TOKEN_LEFT_SHIFT:
+    case NJS_TOKEN_RIGHT_SHIFT:
+    case NJS_TOKEN_UNSIGNED_RIGHT_SHIFT:
+    case NJS_TOKEN_ADDITION:
+    case NJS_TOKEN_SUBSTRACTION:
+    case NJS_TOKEN_MULTIPLICATION:
+    case NJS_TOKEN_DIVISION:
+    case NJS_TOKEN_REMAINDER:
+    case NJS_TOKEN_PROPERTY_DELETE:
+    case NJS_TOKEN_PROPERTY:
+        return njs_generate_3addr_operation(vm, parser, node);
+
+    case NJS_TOKEN_DELETE:
+        return njs_generate_delete(vm, parser, node);
+
+    case NJS_TOKEN_VOID:
+    case NJS_TOKEN_TYPEOF:
+    case NJS_TOKEN_UNARY_PLUS:
+    case NJS_TOKEN_UNARY_NEGATION:
+    case NJS_TOKEN_LOGICAL_NOT:
+    case NJS_TOKEN_BITWISE_NOT:
+        return njs_generate_2addr_operation(vm, parser, node);
+
+    case NJS_TOKEN_INCREMENT:
+    case NJS_TOKEN_DECREMENT:
+        return njs_generate_inc_dec_operation(vm, parser, node, 0);
+
+    case NJS_TOKEN_POST_INCREMENT:
+    case NJS_TOKEN_POST_DECREMENT:
+        return njs_generate_inc_dec_operation(vm, parser, node, 1);
+
+    case NJS_TOKEN_UNDEFINED:
+    case NJS_TOKEN_NULL:
+    case NJS_TOKEN_BOOLEAN:
+    case NJS_TOKEN_NUMBER:
+    case NJS_TOKEN_STRING:
+        node->index = njs_value_index(vm, parser, &node->u.value);
+
+        if (nxt_fast_path(node->index != NJS_INDEX_NONE)) {
+            return NXT_OK;
+        }
+
+        return NXT_ERROR;
+
+    case NJS_TOKEN_OBJECT_LITERAL:
+        node->index = node->u.object->index;
+        return NXT_OK;
+
+    case NJS_TOKEN_OBJECT_CREATE:
+        return njs_generate_object(vm, parser, node);
+
+    case NJS_TOKEN_ARRAY_CREATE:
+        return njs_generate_array(vm, parser, node);
+
+    case NJS_TOKEN_FUNCTION_CREATE:
+        return njs_generate_function(vm, parser, node);
+
+    case NJS_TOKEN_REGEXP_LITERAL:
+        return njs_generate_regexp(vm, parser, node);
+
+    case NJS_TOKEN_THIS:
+    case NJS_TOKEN_OBJECT_FUNCTION:
+    case NJS_TOKEN_ARRAY_FUNCTION:
+    case NJS_TOKEN_NUMBER_FUNCTION:
+    case NJS_TOKEN_BOOLEAN_FUNCTION:
+    case NJS_TOKEN_STRING_FUNCTION:
+    case NJS_TOKEN_FUNCTION_FUNCTION:
+    case NJS_TOKEN_REGEXP_FUNCTION:
+    case NJS_TOKEN_EVAL:
+    case NJS_TOKEN_EXTERNAL:
+        return NXT_OK;
+
+    case NJS_TOKEN_NAME:
+        return njs_generate_variable(parser, node);
+
+    case NJS_TOKEN_FUNCTION:
+        return njs_generate_function_statement(vm, parser, node);
+
+    case NJS_TOKEN_FUNCTION_CALL:
+        return njs_generate_function_call(vm, parser, node);
+
+    case NJS_TOKEN_RETURN:
+        return njs_generate_return_statement(vm, parser, node);
+
+    case NJS_TOKEN_METHOD_CALL:
+        return njs_generate_method_call(vm, parser, node);
+
+    case NJS_TOKEN_TRY:
+        return njs_generate_try_statement(vm, parser, node);
+
+    case NJS_TOKEN_THROW:
+        return njs_generate_throw_statement(vm, parser, node);
+
+    default:
+        nxt_thread_log_debug("unknown token: %d", node->token);
+        vm->exception = &njs_exception_syntax_error;
+        return NXT_ERROR;
+    }
+}
+
+
+static nxt_int_t
+njs_generate_variable(njs_parser_t *parser, njs_parser_node_t *node)
+{
+    njs_value_t            *value;
+    njs_vmcode_validate_t  *validate;
+
+    node->index = node->u.variable->index;
+
+    if (node->state == NJS_VARIABLE_NORMAL
+        && node->u.variable->state < NJS_VARIABLE_SET)
+    {
+        njs_generate_code(parser, njs_vmcode_validate_t, validate);
+        validate->code.operation = njs_vmcode_validate;
+        validate->code.operands = NJS_VMCODE_NO_OPERAND;
+        validate->code.retval = NJS_VMCODE_NO_RETVAL;
+        validate->index = node->index;
+
+        value = njs_variable_value(parser, node->index);
+        njs_set_invalid(value);
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_if_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    u_char                  *start;
+    nxt_int_t                ret;
+    njs_ret_t               *label;
+    njs_vmcode_jump_t       *jump;
+    njs_vmcode_cond_jump_t  *cond_jump;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_cond_jump_t, cond_jump);
+    cond_jump->code.operation = njs_vmcode_if_false_jump;
+    cond_jump->code.operands = NJS_VMCODE_2OPERANDS;
+    cond_jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    cond_jump->cond = node->left->index;
+
+    start = (u_char *) cond_jump;
+    label = &cond_jump->offset;
+
+    if (node->right->token == NJS_TOKEN_ELSE) {
+
+        node = node->right;
+
+        ret = njs_generator(vm, parser, node->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        njs_generate_code(parser, njs_vmcode_jump_t, jump);
+        jump->code.operation = njs_vmcode_jump;
+        jump->code.operands = NJS_VMCODE_NO_OPERAND;
+        jump->code.retval = NJS_VMCODE_NO_RETVAL;
+
+        *label = parser->code_last - start;
+        start = (u_char *) jump;
+        label = &jump->offset;
+    }
+
+    ret = njs_generator(vm, parser, node->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    *label = parser->code_last - start;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_cond_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t               ret;
+    njs_index_t             index, *left;
+    njs_parser_node_t       *branch;
+    njs_vmcode_move_t       *move;
+    njs_vmcode_jump_t       *jump;
+    njs_vmcode_cond_jump_t  *cond_jump;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_cond_jump_t, cond_jump);
+    cond_jump->code.operation = njs_vmcode_if_false_jump;
+    cond_jump->code.operands = NJS_VMCODE_2OPERANDS;
+    cond_jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    cond_jump->cond = node->left->index;
+
+    branch = node->right;
+
+    ret = njs_generator(vm, parser, branch->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_move_t, move);
+    move->code.operation = njs_vmcode_move;
+    move->code.operands = NJS_VMCODE_2OPERANDS;
+    move->code.retval = NJS_VMCODE_RETVAL;
+    move->src = branch->left->index;
+
+    left = &move->dst;
+
+    njs_generate_code(parser, njs_vmcode_jump_t, jump);
+    jump->code.operation = njs_vmcode_jump;
+    jump->code.operands = NJS_VMCODE_NO_OPERAND;
+    jump->code.retval = NJS_VMCODE_NO_RETVAL;
+
+    cond_jump->offset = parser->code_last - (u_char *) cond_jump;
+
+    ret = njs_generator(vm, parser, branch->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_move_t, move);
+    move->code.operation = njs_vmcode_move;
+    move->code.operands = NJS_VMCODE_2OPERANDS;
+    move->code.retval = NJS_VMCODE_RETVAL;
+    move->src = branch->right->index;
+
+    jump->offset = parser->code_last - (u_char *) jump;
+
+    index = njs_generator_dest_index(vm, parser, node);
+
+    if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+        return index;
+    }
+
+    if (index == NJS_INDEX_NONE) {
+        node->temporary = 1;
+
+        if (node->left->temporary) {
+            index = node->left->index;
+
+            ret = njs_generator_children_indexes_release(vm, parser, branch);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+        } else if (branch->left->temporary) {
+            index = branch->left->index;
+
+            ret = njs_generator_node_index_release(vm, parser, branch->right);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+        } else if (branch->right->temporary) {
+            index = branch->right->index;
+
+        } else {
+            index = njs_generator_temp_index_get(parser);
+        }
+
+    } else {
+        ret = njs_generator_children_indexes_release(vm, parser, branch);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+    }
+
+    *left = index;
+    move->dst = index;
+    node->index = index;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_while_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    u_char                  *loop;
+    nxt_int_t               ret;
+    njs_vmcode_jump_t       *jump;
+    njs_vmcode_cond_jump_t  *cond_jump;
+
+    /*
+     * Set a jump to the loop condition.  This jump is executed once just on
+     * the loop enter and eliminates execution of one additional jump inside
+     * the loop per each iteration.
+     */
+
+    njs_generate_code(parser, njs_vmcode_jump_t, jump);
+    jump->code.operation = njs_vmcode_jump;
+    jump->code.operands = NJS_VMCODE_NO_OPERAND;
+    jump->code.retval = NJS_VMCODE_NO_RETVAL;
+
+    /* The loop body. */
+
+    loop = parser->code_last;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* The loop condition. */
+
+    jump->offset = parser->code_last - (u_char *) jump;
+
+    ret = njs_generator(vm, parser, node->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_cond_jump_t, cond_jump);
+    cond_jump->code.operation = njs_vmcode_if_true_jump;
+    cond_jump->code.operands = NJS_VMCODE_2OPERANDS;
+    cond_jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    cond_jump->cond = node->right->index;
+    cond_jump->offset = loop - (u_char *) cond_jump;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_do_while_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    u_char                  *loop;
+    nxt_int_t               ret;
+    njs_vmcode_cond_jump_t  *cond_jump;
+
+    /* The loop body. */
+
+    loop = parser->code_last;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* The loop condition. */
+
+    ret = njs_generator(vm, parser, node->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_cond_jump_t, cond_jump);
+    cond_jump->code.operation = njs_vmcode_if_true_jump;
+    cond_jump->code.operands = NJS_VMCODE_2OPERANDS;
+    cond_jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    cond_jump->cond = node->right->index;
+    cond_jump->offset = loop - (u_char *) cond_jump;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_for_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    u_char                  *loop;
+    nxt_int_t               ret;
+    njs_parser_node_t       *condition;
+    njs_vmcode_jump_t       *jump;
+    njs_vmcode_cond_jump_t  *cond_jump;
+
+    jump = NULL;
+
+    /* The loop initialization. */
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    node = node->right;
+    condition = node->left;
+
+    if (condition != NULL) {
+        /*
+         * The loop condition presents so set a jump to it.  This jump is
+         * executed once just after the loop initialization and eliminates
+         * execution of one additional jump inside the loop per each iteration.
+         */
+        njs_generate_code(parser, njs_vmcode_jump_t, jump);
+        jump->code.operation = njs_vmcode_jump;
+        jump->code.operands = NJS_VMCODE_NO_OPERAND;
+        jump->code.retval = NJS_VMCODE_NO_RETVAL;
+    }
+
+    /* The loop body. */
+
+    loop = parser->code_last;
+
+    node = node->right;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* The loop update. */
+
+    ret = njs_generator(vm, parser, node->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* The loop condition. */
+
+    if (condition != NULL) {
+        jump->offset = parser->code_last - (u_char *) jump;
+
+        ret = njs_generator(vm, parser, condition);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        njs_generate_code(parser, njs_vmcode_cond_jump_t, cond_jump);
+        cond_jump->code.operation = njs_vmcode_if_true_jump;
+        cond_jump->code.operands = NJS_VMCODE_2OPERANDS;
+        cond_jump->code.retval = NJS_VMCODE_NO_RETVAL;
+        cond_jump->cond = condition->index;
+        cond_jump->offset = loop - (u_char *) cond_jump;
+
+    } else {
+        njs_generate_code(parser, njs_vmcode_jump_t, jump);
+        jump->code.operation = njs_vmcode_jump;
+        jump->code.operands = NJS_VMCODE_NO_OPERAND;
+        jump->code.retval = NJS_VMCODE_NO_RETVAL;
+        jump->offset = loop - (u_char *) jump;
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_for_in_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    u_char                   *loop;
+    nxt_int_t                ret;
+    njs_index_t              index;
+    njs_vmcode_prop_each_t   *prop_each;
+    njs_vmcode_prop_start_t  *start;
+
+    ret = njs_generator(vm, parser, node->left->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_prop_start_t, start);
+    start->code.operation = njs_vmcode_property_each_start;
+    start->code.operands = NJS_VMCODE_2OPERANDS;
+    start->code.retval = NJS_VMCODE_RETVAL;
+    index = njs_generator_temp_index_get(parser);
+    start->each = index;
+    start->object = node->left->right->index;
+
+    /* The loop body. */
+
+    loop = parser->code_last;
+
+    ret = njs_generator(vm, parser, node->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* The loop iterator. */
+
+    start->offset = parser->code_last - (u_char *) start;
+
+    ret = njs_generator(vm, parser, node->left->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_prop_each_t, prop_each);
+    prop_each->code.operation = njs_vmcode_property_each;
+    prop_each->code.operands = NJS_VMCODE_3OPERANDS;
+    prop_each->code.retval = NJS_VMCODE_RETVAL;
+    prop_each->retval = node->left->left->index;
+    prop_each->object = node->left->right->index;
+    prop_each->each = index;
+    prop_each->offset = loop - (u_char *) prop_each;
+
+    return njs_generator_index_release(vm, parser, index);
+}
+
+
+static nxt_int_t
+njs_generate_assignment(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t              ret;
+    njs_value_t            *value;
+    njs_parser_node_t      *lvalue, *expr, *obj, *prop;
+    njs_vmcode_move_t      *move;
+    njs_vmcode_prop_set_t  *prop_set;
+
+    lvalue = node->left;
+    expr = node->right;
+    expr->dest = NULL;
+
+    if (lvalue->token == NJS_TOKEN_NAME) {
+
+        lvalue->index = lvalue->u.variable->index;
+
+        if (expr->token >= NJS_TOKEN_FIRST_CONST
+            && expr->token <= NJS_TOKEN_LAST_CONST)
+        {
+            ret = njs_generator(vm, parser, expr);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+            if (lvalue->state == NJS_VARIABLE_FIRST_ASSIGNMENT) {
+                /* Use a constant value is stored as variable initial value. */
+                lvalue->lvalue = NJS_LVALUE_ASSIGNED;
+
+                value = njs_variable_value(parser, lvalue->index);
+                *value = expr->u.value;
+                node->index = expr->index;
+
+                return NXT_OK;
+            }
+        }
+
+        expr->dest = lvalue;
+
+        ret = njs_generator(vm, parser, expr);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        /*
+         * lvalue and expression indexes are equal if the expression is an
+         * empty object or expression result is stored directly in variable.
+         */
+        if (lvalue->index != expr->index) {
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            move->dst = lvalue->index;
+            move->src = expr->index;
+        }
+
+        node->index = expr->index;
+
+        return njs_generator_node_index_release(vm, parser, expr);
+    }
+
+    /* lvalue->token == NJS_TOKEN_PROPERTY */
+
+    /* Object. */
+
+    ret = njs_generator(vm, parser, lvalue->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* Property. */
+
+    ret = njs_generator(vm, parser, lvalue->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    if (nxt_slow_path(njs_parser_has_side_effect(expr))) {
+        /* Preserve object and property if they can be changed by expression. */
+
+        obj = lvalue->left;
+
+        if (obj->token == NJS_TOKEN_NAME) {
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            move->dst = njs_generator_temp_index_get(parser);
+            move->src = obj->index;
+            obj->index = move->dst;
+            obj->temporary = 1;
+        }
+
+        prop = lvalue->right;
+
+        if (prop->token == NJS_TOKEN_NAME) {
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            move->dst = njs_generator_temp_index_get(parser);
+            move->src = prop->index;
+            prop->index = move->dst;
+            prop->temporary = 1;
+        }
+    }
+
+    ret = njs_generator(vm, parser, expr);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_prop_set_t, prop_set);
+    prop_set->code.operation = njs_vmcode_property_set;
+    prop_set->code.operands = NJS_VMCODE_3OPERANDS;
+    prop_set->code.retval = NJS_VMCODE_NO_RETVAL;
+    node->index = expr->index;
+    prop_set->value = expr->index;
+    prop_set->object = lvalue->left->index;
+    prop_set->property = lvalue->right->index;
+
+    return njs_generator_children_indexes_release(vm, parser, node);
+}
+
+
+static nxt_int_t
+njs_generate_operation_assignment(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t              ret;
+    njs_index_t            index;
+    njs_parser_node_t      *lvalue, *expr;
+    njs_vmcode_move_t      *move;
+    njs_vmcode_3addr_t     *code;
+    njs_vmcode_prop_get_t  *prop_get;
+    njs_vmcode_prop_set_t  *prop_set;
+
+    lvalue = node->left;
+
+    if (lvalue->token == NJS_TOKEN_NAME) {
+        ret = njs_generate_variable(parser, lvalue);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        index = lvalue->index;
+        node->index = index;
+        expr = node->right;
+
+        if (nxt_slow_path(njs_parser_has_side_effect(expr))) {
+            /* Preserve variable value if it may be changed by expression. */
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            index = njs_generator_temp_index_get(parser);
+            move->dst = index;
+            move->src = lvalue->index;
+            lvalue->temporary = 1;
+        }
+
+        ret = njs_generator(vm, parser, expr);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        njs_generate_code(parser, njs_vmcode_3addr_t, code);
+        code->code.operation = node->u.operation;
+        code->code.operands = NJS_VMCODE_3OPERANDS;
+        code->code.retval = NJS_VMCODE_RETVAL;
+        code->dst = lvalue->index;
+        code->src1 = index;
+        code->src2 = expr->index;
+
+        return njs_generator_node_index_release(vm, parser, expr);
+    }
+
+    /* lvalue->token == NJS_TOKEN_PROPERTY */
+
+    /* Object. */
+
+    ret = njs_generator(vm, parser, lvalue->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* Property. */
+
+    ret = njs_generator(vm, parser, lvalue->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_prop_get_t, prop_get);
+    prop_get->code.operation = njs_vmcode_property_get;
+    prop_get->code.operands = NJS_VMCODE_3OPERANDS;
+    prop_get->code.retval = NJS_VMCODE_RETVAL;
+    index = njs_generator_temp_index_get(parser);
+    prop_get->value = index;
+    node->index = index;
+    prop_get->object = lvalue->left->index;
+    prop_get->property = lvalue->right->index;
+
+    expr = node->right;
+
+    ret = njs_generator(vm, parser, expr);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_3addr_t, code);
+    code->code.operation = node->u.operation;
+    code->code.operands = NJS_VMCODE_3OPERANDS;
+    code->code.retval = NJS_VMCODE_RETVAL;
+    code->dst = index;
+    code->src1 = index;
+    code->src2 = expr->index;
+
+    njs_generate_code(parser, njs_vmcode_prop_set_t, prop_set);
+    prop_set->code.operation = njs_vmcode_property_set;
+    prop_set->code.operands = NJS_VMCODE_3OPERANDS;
+    prop_set->code.retval = NJS_VMCODE_NO_RETVAL;
+    prop_set->value = index;
+    prop_set->object = lvalue->left->index;
+    prop_set->property = lvalue->right->index;
+
+    ret = njs_generator_node_index_release(vm, parser, expr);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    return njs_generator_children_indexes_release(vm, parser, lvalue);
+}
+
+
+static nxt_int_t
+njs_generate_object(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    nxt_int_t            ret;
+    njs_index_t          index;
+    njs_vmcode_object_t  *obj;
+
+    index = NJS_INDEX_NONE;
+
+    if (node->left == NULL) {
+        /* An empty object, try to assign directly to variable. */
+
+        index = njs_generator_dest_index(vm, parser, node);
+        if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+            return index;
+        }
+    }
+
+    if (index == NJS_INDEX_NONE) {
+        index = njs_generator_temp_index_get(parser);
+    }
+
+    njs_generate_code(parser, njs_vmcode_object_t, obj);
+    obj->code.operation = njs_vmcode_object_create;
+    obj->code.operands = NJS_VMCODE_1OPERAND;
+    obj->code.retval = NJS_VMCODE_RETVAL;
+    obj->retval = index;
+
+    node->index = index;
+
+    if (node->left != NULL) {
+        /* Initialize object. */
+
+        ret = njs_generator(vm, parser, node->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        /*
+         * Mark the node as a node with temporary result to allow reuse
+         * its index.  The node can not be marked earlier because this
+         * index may be used during initialization of the object.
+         */
+        node->temporary = 1;
+    }
+
+    nxt_thread_log_debug("OBJECT %p", node->index);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_array(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    nxt_int_t           ret;
+    njs_index_t         index;
+    njs_vmcode_array_t  *array;
+
+    index = NJS_INDEX_NONE;
+
+    if (node->left == NULL) {
+        /* An empty object, try to assign directly to variable. */
+
+        index = njs_generator_dest_index(vm, parser, node);
+        if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+            return index;
+        }
+    }
+
+    if (index == NJS_INDEX_NONE) {
+        index = njs_generator_temp_index_get(parser);
+    }
+
+    njs_generate_code(parser, njs_vmcode_array_t, array);
+    array->code.operation = njs_vmcode_array_create;
+    array->code.operands = NJS_VMCODE_1OPERAND;
+    array->code.retval = NJS_VMCODE_RETVAL;
+    array->retval = index;
+    array->length = node->u.length;
+
+    node->index = index;
+
+    if (node->left != NULL) {
+        /* Initialize object. */
+
+        ret = njs_generator(vm, parser, node->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        /*
+         * Mark the node as a node with temporary result to allow reuse
+         * its index.  The node can not be marked earlier because this
+         * index may be used during initialization of the object.
+         */
+        node->temporary = 1;
+    }
+
+    nxt_thread_log_debug("ARRAY %p", node->index);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_function(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t                     ret;
+    njs_index_t                   index;
+    njs_parser_node_t             *body;
+    njs_function_script_t         *func;
+    njs_vmcode_operation_t        last;
+    njs_vmcode_function_create_t  *function;
+
+    body = node->right;
+
+    if (body != NULL
+        && body->right != NULL
+        && body->right->token == NJS_TOKEN_RETURN)
+    {
+        last = NULL;
+
+    } else {
+        last = njs_vmcode_return;
+    }
+
+    func = node->u.value.data.u.data;
+
+    ret = njs_generate_scope(vm, func->u.parser, body, last);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        func->local_size = func->u.parser->scope_size;
+        func->spare_size = func->u.parser->method_arguments_size;
+        func->local_scope = func->u.parser->local_scope;
+        func->u.code = func->u.parser->code_start;
+
+        /* Try to assign directly to variable. */
+
+        index = njs_generator_dest_index(vm, parser, node);
+        if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+            return index;
+        }
+
+        if (index == NJS_INDEX_NONE) {
+            index = njs_generator_temp_index_get(parser);
+        }
+
+        njs_generate_code(parser, njs_vmcode_function_create_t, function);
+        function->code.operation = njs_vmcode_function_create;
+        function->code.operands = NJS_VMCODE_1OPERAND;
+        function->code.retval = NJS_VMCODE_RETVAL;
+        function->retval = index;
+        function->function = func;
+
+        node->index = index;
+
+        nxt_thread_log_debug("FUNCTION %p", node->index);
+    }
+
+    return ret;
+}
+
+
+static nxt_int_t
+njs_generate_regexp(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    njs_index_t          index;
+    njs_vmcode_regexp_t  *regexp;
+
+    /* Try to assign directly to variable. */
+
+    index = njs_generator_dest_index(vm, parser, node);
+    if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+        return index;
+    }
+
+    if (index == NJS_INDEX_NONE) {
+        index = njs_generator_temp_index_get(parser);
+    }
+
+    njs_generate_code(parser, njs_vmcode_regexp_t, regexp);
+    regexp->code.operation = njs_vmcode_regexp_create;
+    regexp->code.operands = NJS_VMCODE_1OPERAND;
+    regexp->code.retval = NJS_VMCODE_RETVAL;
+    regexp->retval = index;
+    regexp->pattern = node->u.value.data.u.data;
+
+    node->index = index;
+
+    nxt_thread_log_debug("REGEXP %p", node->index);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_delete(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node)
+{
+    double              n;
+    nxt_int_t           ret;
+    njs_index_t         index;
+    njs_parser_node_t   *left;
+    njs_vmcode_2addr_t  *delete;
+
+    left = node->left;
+
+    /*
+     * The "delete" operator returns "false" for "undefined", "NaN", and
+     * "Infinity" constants but not for values, expressions and "-Infinity".
+     */
+
+    switch (left->token) {
+
+    case NJS_TOKEN_NAME:
+        if (left->u.variable->state == NJS_VARIABLE_DECLARED) {
+            index = njs_value_index(vm, parser, &njs_value_false);
+            goto done;
+        }
+
+        /* A property of the global object. */
+
+        node->temporary = 1;
+        node->index = njs_generator_temp_index_get(parser);
+
+        njs_generate_code(parser, njs_vmcode_2addr_t, delete);
+        delete->code.operation = njs_vmcode_delete;
+        delete->code.operands = NJS_VMCODE_2OPERANDS;
+        delete->code.retval = NJS_VMCODE_RETVAL;
+        delete->dst = node->index;
+        delete->src = left->u.variable->index;
+
+        return NXT_OK;
+
+    case NJS_TOKEN_NUMBER:
+        n = left->u.value.data.u.number;
+
+        if (!njs_is_nan(n) && !(njs_is_infinity(n) && n > 0.0)) {
+            break;
+        }
+
+        /* Fall through. */
+
+    case NJS_TOKEN_UNDEFINED:
+        index = njs_value_index(vm, parser, &njs_value_false);
+        goto done;
+
+    default:
+        ret = njs_generator(vm, parser, left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        ret = njs_generator_node_index_release(vm, parser, left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        break;
+    }
+
+    index = njs_value_index(vm, parser, &njs_value_true);
+
+done:
+
+    node->index = index;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_3addr_operation(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t           ret;
+    njs_index_t         index;
+    njs_parser_node_t   *left, *right;
+    njs_vmcode_move_t   *move;
+    njs_vmcode_3addr_t  *code;
+
+    left = node->left;
+
+    ret = njs_generator(vm, parser, left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    right = node->right;
+
+    if (left->token == NJS_TOKEN_NAME) {
+
+        if (nxt_slow_path(njs_parser_has_side_effect(right))) {
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            move->src = left->index;
+            left->index = njs_generator_temp_index_get(parser);
+            move->dst = left->index;
+
+            left->temporary = 1;
+        }
+    }
+
+    ret = njs_generator(vm, parser, right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_3addr_t, code);
+    code->code.operation = node->u.operation;
+    code->code.operands = NJS_VMCODE_3OPERANDS;
+    code->code.retval = NJS_VMCODE_RETVAL;
+    code->src1 = left->index;
+    code->src2 = right->index;
+
+    index = njs_generator_dest_index(vm, parser, node);
+
+    if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+        return index;
+    }
+
+    if (index == NJS_INDEX_NONE) {
+        node->temporary = 1;
+
+        if (left->temporary) {
+            index = left->index;
+
+            ret = njs_generator_node_index_release(vm, parser, right);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+        } else if (right->temporary) {
+            index = right->index;
+
+        } else {
+            index = njs_generator_temp_index_get(parser);
+        }
+    }
+
+    node->index = index;
+    code->dst = index;
+
+    nxt_thread_log_debug("CODE3  %p, %p, %p",
+                         code->dst, code->src1, code->src2);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_2addr_operation(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t           ret;
+    njs_index_t         index;
+    njs_vmcode_2addr_t  *code;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_2addr_t, code);
+    code->code.operation = node->u.operation;
+    code->code.operands = NJS_VMCODE_2OPERANDS;
+    code->code.retval = NJS_VMCODE_RETVAL;
+    code->src = node->left->index;
+
+    index = njs_generator_dest_index(vm, parser, node);
+
+    if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+        return index;
+    }
+
+    if (index == NJS_INDEX_NONE) {
+        node->temporary = 1;
+
+        if (node->left->temporary) {
+            index = node->left->index;
+
+        } else {
+            index = njs_generator_temp_index_get(parser);
+        }
+    }
+
+    node->index = index;
+    code->dst = index;
+
+    nxt_thread_log_debug("CODE2  %p, %p", code->dst, code->src);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_inc_dec_operation(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node, nxt_bool_t post)
+{
+    nxt_int_t              ret;
+    njs_index_t            index, dest_index;
+    njs_parser_node_t      *lvalue;
+    njs_vmcode_3addr_t     *code;
+    njs_vmcode_prop_get_t  *prop_get;
+    njs_vmcode_prop_set_t  *prop_set;
+
+    lvalue = node->left;
+
+    if (lvalue->token == NJS_TOKEN_NAME) {
+
+        ret = njs_generate_variable(parser, lvalue);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        index = njs_generator_dest_index(vm, parser, node);
+
+        if (nxt_slow_path(index == NJS_INDEX_ERROR)) {
+            return index;
+        }
+
+        if (index == NJS_INDEX_NONE) {
+            node->temporary = 1;
+            index = njs_generator_temp_index_get(parser);
+        }
+
+        node->index = index;
+
+        njs_generate_code(parser, njs_vmcode_3addr_t, code);
+        code->code.operation = node->u.operation;
+        code->code.operands = NJS_VMCODE_3OPERANDS;
+        code->code.retval = NJS_VMCODE_RETVAL;
+        code->dst = index;
+        code->src1 = lvalue->index;
+        code->src2 = lvalue->index;
+
+        return NXT_OK;
+    }
+
+    /* lvalue->token == NJS_TOKEN_PROPERTY */
+
+    /* Object. */
+
+    ret = njs_generator(vm, parser, lvalue->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* Property. */
+
+    ret = njs_generator(vm, parser, lvalue->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    dest_index = njs_generator_dest_index(vm, parser, node);
+
+    if (nxt_slow_path(dest_index == NJS_INDEX_ERROR)) {
+        return dest_index;
+    }
+
+    if (dest_index == NJS_INDEX_NONE
+        || dest_index == lvalue->left->index
+        || dest_index == lvalue->right->index)
+    {
+        node->temporary = 1;
+        dest_index = njs_generator_temp_index_get(parser);
+    }
+
+    node->index = dest_index;
+
+    index = post ? njs_generator_temp_index_get(parser) : dest_index;
+
+    njs_generate_code(parser, njs_vmcode_prop_get_t, prop_get);
+    prop_get->code.operation = njs_vmcode_property_get;
+    prop_get->code.operands = NJS_VMCODE_3OPERANDS;
+    prop_get->code.retval = NJS_VMCODE_RETVAL;
+    prop_get->value = index;
+    prop_get->object = lvalue->left->index;
+    prop_get->property = lvalue->right->index;
+
+    njs_generate_code(parser, njs_vmcode_3addr_t, code);
+    code->code.operation = node->u.operation;
+    code->code.operands = NJS_VMCODE_3OPERANDS;
+    code->code.retval = NJS_VMCODE_RETVAL;
+    code->dst = dest_index;
+    code->src1 = index;
+    code->src2 = index;
+
+    njs_generate_code(parser, njs_vmcode_prop_set_t, prop_set);
+    prop_set->code.operation = njs_vmcode_property_set;
+    prop_set->code.operands = NJS_VMCODE_3OPERANDS;
+    prop_set->code.retval = NJS_VMCODE_NO_RETVAL;
+    prop_set->value = index;
+    prop_set->object = lvalue->left->index;
+    prop_set->property = lvalue->right->index;
+
+    if (post) {
+        ret = njs_generator_index_release(vm, parser, index);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+    }
+
+    return njs_generator_children_indexes_release(vm, parser, lvalue);
+}
+
+
+static nxt_int_t
+njs_generate_function_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t               ret;
+    njs_value_t             *value;
+    njs_function_t          *func;
+    njs_parser_node_t       *body;
+    njs_vmcode_operation_t  last;
+
+    value = njs_variable_value(parser, node->index);
+    func = value->data.u.function;
+
+    body = node->right;
+
+    if (body != NULL
+        && body->right != NULL
+        && body->right->token == NJS_TOKEN_RETURN)
+    {
+        last = NULL;
+
+    } else {
+        last = njs_vmcode_return;
+    }
+
+    ret = njs_generate_scope(vm, func->code.script->u.parser, body, last);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        parser = func->code.script->u.parser;
+
+        func->code.script->local_size = parser->scope_size;
+        func->code.script->spare_size = parser->method_arguments_size;
+        func->code.script->local_scope = parser->local_scope;
+        func->code.script->u.code = parser->code_start;
+        node->u.value = *value;
+    }
+
+    return ret;
+}
+
+
+nxt_int_t
+njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser, njs_parser_node_t *node,
+    njs_vmcode_operation_t last)
+{
+    size_t             code_size, size;
+    u_char             *p;
+    uintptr_t          scope_size;
+    nxt_uint_t         n;
+    njs_index_t        index;
+    njs_value_t        *value;
+    njs_vmcode_stop_t  *stop;
+
+    p = nxt_mem_cache_alloc(vm->mem_cache_pool, parser->code_size);
+    if (nxt_slow_path(p == NULL)) {
+        return NXT_ERROR;
+    }
+
+    parser->code_start = p;
+    parser->code_last = p;
+
+    if (node != NULL) {
+        if (nxt_slow_path(njs_generator(vm, parser, node) != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
+    if (last != NULL) {
+        njs_generate_code(parser, njs_vmcode_stop_t, stop);
+        stop->code.operation = last;
+        stop->code.operands = NJS_VMCODE_1OPERAND;
+        stop->code.retval = NJS_VMCODE_NO_RETVAL;
+
+        index = njs_value_index(vm, parser, &njs_value_void);
+
+        if (last == njs_vmcode_stop && node->index != 0) {
+            index = node->index;
+        }
+
+        stop->retval = index;
+    }
+
+    code_size = parser->code_last - parser->code_start;
+
+    nxt_thread_log_debug("SCOPE CODE SIZE: %uz %uz",
+                         parser->code_size, code_size);
+
+    if (nxt_slow_path(parser->code_size < code_size)) {
+        return NXT_ERROR;
+    }
+
+    scope_size = parser->index[parser->scope - NJS_INDEX_CACHE]
+                 - parser->scope_offset;
+
+    parser->local_scope = nxt_mem_cache_alloc(vm->mem_cache_pool, scope_size);
+    if (nxt_slow_path(parser->local_scope == NULL)) {
+        return NXT_ERROR;
+    }
+
+    parser->scope_size = scope_size;
+
+    size = parser->scope_values->items * sizeof(njs_value_t);
+
+    nxt_thread_log_debug("SCOPE SIZE: %uz %uz", size, scope_size);
+
+    p = memcpy(parser->local_scope, parser->scope_values->start, size);
+    value = (njs_value_t *) (p + size);
+
+    for (n = scope_size - size; n != 0; n -= sizeof(njs_value_t)) {
+        *value++ = njs_value_void;
+    }
+
+    nxt_thread_log_debug("SCOPE CODE:");
+
+    njs_disassembler(parser->code_start, parser->code_last, NULL);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generate_return_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t          ret;
+    njs_vmcode_stop_t  *code;
+
+    ret = njs_generator(vm, parser, node->right);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        njs_generate_code(parser, njs_vmcode_stop_t, code);
+        code->code.operation = njs_vmcode_return;
+        code->code.operands = NJS_VMCODE_1OPERAND;
+        code->code.retval = NJS_VMCODE_NO_RETVAL;
+        code->retval = node->right->index;
+        node->index = node->right->index;
+    }
+
+    return ret;
+}
+
+
+static nxt_int_t
+njs_generate_function_call(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t               ret;
+    uintptr_t               nargs;
+    njs_index_t             retval, index, name;
+    njs_parser_node_t       *arg;
+    njs_vmcode_call_t       *call;
+    njs_vmcode_move_t       *move;
+    njs_vmcode_function_t   *func;
+
+    if (node->left != NULL) {
+        /* Generate function code in function expression. */
+        ret = njs_generator(vm, parser, node->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        name = node->left->index;
+
+    } else {
+        /* njs_generate_variable() always returns NXT_OK. */
+        (void) njs_generate_variable(parser, node);
+        name = node->index;
+    }
+
+    njs_generate_code(parser, njs_vmcode_function_t, func);
+    func->code.operation = njs_vmcode_function;
+    func->code.operands = NJS_VMCODE_2OPERANDS;
+    func->code.retval = NJS_VMCODE_RETVAL;
+    func->code.ctor = node->ctor;
+    func->name = name;
+
+    index = njs_generator_temp_index_get(parser);
+    func->function = index;
+
+    nargs = 1;
+
+    for (arg = node->right; arg != NULL; arg = arg->right) {
+        nargs++;
+
+        ret = njs_generator(vm, parser, arg->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        if (arg->index != arg->left->index) {
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            move->dst = arg->index;
+            move->src = arg->left->index;
+        }
+    }
+
+    func->code.nargs = nargs;
+
+    retval = njs_generator_dest_index(vm, parser, node);
+
+    if (nxt_slow_path(retval == NJS_INDEX_ERROR)) {
+        return retval;
+    }
+
+    if (retval == NJS_INDEX_NONE) {
+        node->temporary = 1;
+        retval = index;
+    }
+
+    node->index = retval;
+
+    njs_generate_code(parser, njs_vmcode_call_t, call);
+    call->code.operation = njs_vmcode_call;
+    call->code.operands = NJS_VMCODE_2OPERANDS;
+    call->code.retval = NJS_VMCODE_NO_RETVAL;
+    call->code.nargs = nargs;
+    call->function = index;
+    call->retval = retval;
+
+    if (retval == index) {
+        return NXT_OK;
+    }
+
+    return njs_generator_index_release(vm, parser, index);
+}
+
+
+static nxt_int_t
+njs_generate_method_call(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t            ret;
+    uintptr_t            nargs;
+    njs_index_t          retval, index;
+    njs_parser_node_t    *arg, *prop;
+    njs_vmcode_call_t    *call;
+    njs_vmcode_move_t    *move;
+    njs_vmcode_method_t  *method;
+
+    prop = node->left;
+
+    /* Object. */
+
+    ret = njs_generator(vm, parser, prop->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    /* Method name. */
+
+    ret = njs_generator(vm, parser, prop->right);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    if (prop->left->temporary) {
+        index = prop->left->index;
+
+        ret = njs_generator_node_index_release(vm, parser, prop->right);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+    } else if (prop->right->temporary) {
+        index = prop->right->index;
+
+    } else {
+        index = njs_generator_temp_index_get(parser);
+    }
+
+    njs_generate_code(parser, njs_vmcode_method_t, method);
+    method->code.operation = njs_vmcode_method;
+    method->code.operands = NJS_VMCODE_3OPERANDS;
+    method->code.retval = NJS_VMCODE_RETVAL;
+    method->code.ctor = node->ctor;
+    method->function = index;
+    method->object = prop->left->index;
+    method->method = prop->right->index;
+
+    nargs = 1;
+
+    for (arg = node->right; arg != NULL; arg = arg->right) {
+        nargs++;
+
+        ret = njs_generator(vm, parser, arg->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        if (arg->index != arg->left->index) {
+            njs_generate_code(parser, njs_vmcode_move_t, move);
+            move->code.operation = njs_vmcode_move;
+            move->code.operands = NJS_VMCODE_2OPERANDS;
+            move->code.retval = NJS_VMCODE_RETVAL;
+            move->dst = arg->index;
+            move->src = arg->left->index;
+        }
+    }
+
+    method->code.nargs = nargs;
+
+    retval = njs_generator_dest_index(vm, parser, node);
+
+    if (nxt_slow_path(retval == NJS_INDEX_ERROR)) {
+        return retval;
+    }
+
+    if (retval == NJS_INDEX_NONE) {
+        node->temporary = 1;
+        retval = index;
+    }
+
+    node->index = retval;
+
+    njs_generate_code(parser, njs_vmcode_call_t, call);
+    call->code.operation = njs_vmcode_call;
+    call->code.operands = NJS_VMCODE_2OPERANDS;
+    call->code.retval = NJS_VMCODE_NO_RETVAL;
+    call->code.nargs = nargs;
+    call->function = index;
+    call->retval = retval;
+
+    if (retval == index) {
+        return NXT_OK;
+    }
+
+    return njs_generator_index_release(vm, parser, index);
+}
+
+
+static nxt_int_t
+njs_generate_try_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t               ret;
+    njs_index_t             index;
+    njs_vmcode_catch_t      *catch;
+    njs_vmcode_finally_t    *finally;
+    njs_vmcode_try_end_t    *try_end, *catch_end;
+    njs_vmcode_try_start_t  *try_start;
+
+    njs_generate_code(parser, njs_vmcode_try_start_t, try_start);
+    try_start->code.operation = njs_vmcode_try_start;
+    try_start->code.operands = NJS_VMCODE_2OPERANDS;
+    try_start->code.retval = NJS_VMCODE_NO_RETVAL;
+    index = njs_generator_temp_index_get(parser);
+    try_start->value = index;
+
+    ret = njs_generator(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    njs_generate_code(parser, njs_vmcode_try_end_t, try_end);
+    try_end->code.operation = njs_vmcode_try_end;
+    try_end->code.operands = NJS_VMCODE_NO_OPERAND;
+    try_end->code.retval = NJS_VMCODE_NO_RETVAL;
+
+    try_start->offset = parser->code_last - (u_char *) try_start;
+
+    node = node->right;
+
+    if (node->token == NJS_TOKEN_CATCH) {
+        /* A try/catch case. */
+
+        ret = njs_generator(vm, parser, node->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        njs_generate_code(parser, njs_vmcode_catch_t, catch);
+        catch->code.operation = njs_vmcode_catch;
+        catch->code.operands = NJS_VMCODE_2OPERANDS;
+        catch->code.retval = NJS_VMCODE_NO_RETVAL;
+        catch->offset = sizeof(njs_vmcode_catch_t);
+        catch->exception = node->left->index;
+
+        ret = njs_generator(vm, parser, node->right);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        try_end->offset = parser->code_last - (u_char *) try_end;
+
+        /* TODO: release exception variable index. */
+
+    } else {
+        if (node->left != NULL) {
+            /* A try/catch/finally case. */
+
+            ret = njs_generator(vm, parser, node->left->left);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+            njs_generate_code(parser, njs_vmcode_catch_t, catch);
+            catch->code.operation = njs_vmcode_catch;
+            catch->code.operands = NJS_VMCODE_2OPERANDS;
+            catch->code.retval = NJS_VMCODE_NO_RETVAL;
+            catch->exception = node->left->left->index;
+
+            ret = njs_generator(vm, parser, node->left->right);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+            njs_generate_code(parser, njs_vmcode_try_end_t, catch_end);
+            catch_end->code.operation = njs_vmcode_try_end;
+            catch_end->code.operands = NJS_VMCODE_NO_OPERAND;
+            catch_end->code.retval = NJS_VMCODE_NO_RETVAL;
+
+            catch->offset = parser->code_last - (u_char *) catch;
+
+            /* TODO: release exception variable index. */
+
+            njs_generate_code(parser, njs_vmcode_catch_t, catch);
+            catch->code.operation = njs_vmcode_catch;
+            catch->code.operands = NJS_VMCODE_2OPERANDS;
+            catch->code.retval = NJS_VMCODE_NO_RETVAL;
+            catch->offset = sizeof(njs_vmcode_catch_t);
+            catch->exception = index;
+
+            catch_end->offset = parser->code_last - (u_char *) catch_end;
+
+        } else {
+            /* A try/finally case. */
+
+            njs_generate_code(parser, njs_vmcode_catch_t, catch);
+            catch->code.operation = njs_vmcode_catch;
+            catch->code.operands = NJS_VMCODE_2OPERANDS;
+            catch->code.retval = NJS_VMCODE_NO_RETVAL;
+            catch->offset = sizeof(njs_vmcode_catch_t);
+            catch->exception = index;
+        }
+
+        try_end->offset = parser->code_last - (u_char *) try_end;
+
+        ret = njs_generator(vm, parser, node->right);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        njs_generate_code(parser, njs_vmcode_finally_t, finally);
+        finally->code.operation = njs_vmcode_finally;
+        finally->code.operands = NJS_VMCODE_1OPERAND;
+        finally->code.retval = NJS_VMCODE_NO_RETVAL;
+        finally->retval = index;
+    }
+
+    return njs_generator_index_release(vm, parser, index);
+}
+
+
+static nxt_int_t
+njs_generate_throw_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t           ret;
+    njs_vmcode_throw_t  *code;
+
+    ret = njs_generator(vm, parser, node->right);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        njs_generate_code(parser, njs_vmcode_throw_t, code);
+        code->code.operation = njs_vmcode_throw;
+        code->code.operands = NJS_VMCODE_1OPERAND;
+        code->code.retval = NJS_VMCODE_NO_RETVAL;
+        code->retval = node->right->index;
+        node->index = node->right->index;
+    }
+
+    return ret;
+}
+
+
+static njs_index_t
+njs_generator_dest_index(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    njs_index_t        ret;
+    njs_parser_node_t  *dest;
+
+    dest = node->dest;
+
+    if (dest == NULL) {
+        return NJS_INDEX_NONE;
+    }
+
+    if (dest->token == NJS_TOKEN_PROPERTY) {
+        ret = njs_generator_index_release(vm, parser, dest->index);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+    }
+
+    if (node->left != NULL) {
+        ret = njs_generator_node_index_release(vm, parser, node->left);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+    }
+
+    if (node->right != NULL) {
+        ret = njs_generator_node_index_release(vm, parser, node->right);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+    }
+
+    dest->lvalue = NJS_LVALUE_ASSIGNED;
+
+    return dest->index;
+}
+
+
+static njs_index_t
+njs_generator_temp_index_get(njs_parser_t *parser)
+{
+    nxt_uint_t    n;
+    njs_index_t   index, *last;
+    nxt_vector_t  *cache;
+
+    cache = parser->index_cache;
+
+    if (cache != NULL && cache->items != 0) {
+        last = nxt_vector_remove_last(cache);
+
+        nxt_thread_log_debug("CACHE %p", *last);
+
+        return *last;
+    }
+
+    /* Skip absolute and propery scopes. */
+    n = parser->scope - NJS_INDEX_CACHE;
+
+    index = parser->index[n];
+    parser->index[n] += sizeof(njs_value_t);
+
+    index |= parser->scope;
+
+    nxt_thread_log_debug("GET %p", index);
+
+    return index;
+}
+
+
+static nxt_int_t
+njs_generator_children_indexes_release(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    nxt_int_t  ret;
+
+    ret = njs_generator_node_index_release(vm, parser, node->left);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return ret;
+    }
+
+    return njs_generator_node_index_release(vm, parser, node->right);
+}
+
+
+static nxt_int_t
+njs_generator_node_index_release(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node)
+{
+    if (node->temporary) {
+        return njs_generator_index_release(vm, parser, node->index);
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_generator_index_release(njs_vm_t *vm, njs_parser_t *parser,
+    njs_index_t index)
+{
+    njs_index_t   *last;
+    nxt_vector_t  *cache;
+
+    nxt_thread_log_debug("RELEASE %p", index);
+
+    cache = parser->index_cache;
+
+    if (cache == NULL) {
+        cache = nxt_vector_create(4, sizeof(njs_value_t *), &njs_array_mem_proto,
+                                 vm->mem_cache_pool);
+        if (nxt_slow_path(cache == NULL)) {
+            return NXT_ERROR;
+        }
+
+        parser->index_cache = cache;
+    }
+
+    last = nxt_vector_add(cache, &njs_array_mem_proto, vm->mem_cache_pool);
+    if (nxt_fast_path(last != NULL)) {
+        *last = index;
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+u_char *
+njs_number_trap_create(njs_vm_t *vm)
+{
+    u_char                  *p, *code;
+    size_t                  size;
+    njs_vmcode_restart_t    *restart;
+    njs_vmcode_to_number_t  *to_number;
+
+    size = 2 * sizeof(njs_vmcode_to_number_t) + sizeof(njs_vmcode_restart_t);
+
+    code = nxt_mem_cache_alloc(vm->mem_cache_pool, size);
+
+    if (nxt_fast_path(code != NULL)) {
+        p = code;
+
+        to_number = (njs_vmcode_to_number_t *) p;
+        p += sizeof(njs_vmcode_to_number_t);
+        to_number->code.operation = njs_vmcode_to_number;
+        to_number->code.operands = NJS_VMCODE_1OPERAND;
+        to_number->code.retval = NJS_VMCODE_NO_RETVAL;
+        to_number->narg = 1;
+
+        to_number = (njs_vmcode_to_number_t *) p;
+        p += sizeof(njs_vmcode_to_number_t);
+        to_number->code.operation = njs_vmcode_to_number;
+        to_number->code.operands = NJS_VMCODE_1OPERAND;
+        to_number->code.retval = NJS_VMCODE_NO_RETVAL;
+        to_number->narg = 0;
+
+        restart = (njs_vmcode_restart_t *) p;
+        p += sizeof(njs_vmcode_restart_t);
+        restart->code.operation = njs_vmcode_restart;
+        restart->code.operands = NJS_VMCODE_NO_OPERAND;
+        restart->code.retval = NJS_VMCODE_NO_RETVAL;
+    }
+
+    return code;
+}
+
+
+u_char *
+njs_string_trap_create(njs_vm_t *vm)
+{
+    u_char                  *p, *code;
+    size_t                  size;
+    njs_vmcode_restart_t    *restart;
+    njs_vmcode_to_string_t  *to_string;
+
+    size = 2 * sizeof(njs_vmcode_to_string_t) + sizeof(njs_vmcode_restart_t);
+
+    code = nxt_mem_cache_alloc(vm->mem_cache_pool, size);
+
+    if (nxt_fast_path(code != NULL)) {
+        p = code;
+
+        to_string = (njs_vmcode_to_string_t *) p;
+        p += sizeof(njs_vmcode_to_string_t);
+        to_string->code.operation = njs_vmcode_to_string;
+        to_string->code.operands = NJS_VMCODE_1OPERAND;
+        to_string->code.retval = NJS_VMCODE_NO_RETVAL;
+        to_string->narg = 0;
+
+        to_string = (njs_vmcode_to_string_t *) p;
+        p += sizeof(njs_vmcode_to_string_t);
+        to_string->code.operation = njs_vmcode_to_string;
+        to_string->code.operands = NJS_VMCODE_1OPERAND;
+        to_string->code.retval = NJS_VMCODE_NO_RETVAL;
+        to_string->narg = 1;
+
+        restart = (njs_vmcode_restart_t *) p;
+        p += sizeof(njs_vmcode_restart_t);
+        restart->code.operation = njs_vmcode_restart;
+        restart->code.operands = NJS_VMCODE_NO_OPERAND;
+        restart->code.retval = NJS_VMCODE_NO_RETVAL;
+    }
+
+    return code;
+}
diff --git a/njs/njs_lexer.c b/njs/njs_lexer.c
new file mode 100644 (file)
index 0000000..e5a190b
--- /dev/null
@@ -0,0 +1,708 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+typedef struct njs_lexer_multi_s  njs_lexer_multi_t;
+
+struct njs_lexer_multi_s {
+    uint8_t                  symbol;
+    uint8_t                  token;
+    uint8_t                  count;
+    const njs_lexer_multi_t  *next;
+};
+
+
+static njs_token_t njs_lexer_next_token(njs_lexer_t *lexer);
+static njs_token_t njs_lexer_word(njs_lexer_t *lexer, u_char c);
+static njs_token_t njs_lexer_string(njs_lexer_t *lexer,
+    u_char quote);
+static njs_token_t njs_lexer_number(njs_lexer_t *lexer);
+static njs_token_t njs_lexer_multi(njs_lexer_t *lexer,
+    njs_token_t token, nxt_uint_t n, const njs_lexer_multi_t *multi);
+static njs_token_t njs_lexer_division(njs_lexer_t *lexer,
+    njs_token_t token);
+
+
+static const uint8_t  njs_tokens[256]  nxt_aligned(64) = {
+
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+    /* \t */    NJS_TOKEN_ILLEGAL,           NJS_TOKEN_SPACE,
+    /* \n */    NJS_TOKEN_LINE_END,          NJS_TOKEN_ILLEGAL,
+    /* \r */    NJS_TOKEN_ILLEGAL,           NJS_TOKEN_LINE_END,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0x10 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /*   ! */   NJS_TOKEN_SPACE,             NJS_TOKEN_LOGICAL_NOT,
+    /* " # */   NJS_TOKEN_DOUBLE_QUOTE,      NJS_TOKEN_ILLEGAL,
+    /* $ % */   NJS_TOKEN_LETTER,            NJS_TOKEN_REMAINDER,
+    /* & ' */   NJS_TOKEN_BITWISE_AND,       NJS_TOKEN_SINGLE_QUOTE,
+    /* ( ) */   NJS_TOKEN_OPEN_PARENTHESIS,  NJS_TOKEN_CLOSE_PARENTHESIS,
+    /* * + */   NJS_TOKEN_MULTIPLICATION,    NJS_TOKEN_ADDITION,
+    /* , - */   NJS_TOKEN_COMMA,             NJS_TOKEN_SUBSTRACTION,
+    /* . / */   NJS_TOKEN_DOT,               NJS_TOKEN_DIVISION,
+
+    /* 0 1 */   NJS_TOKEN_DIGIT,             NJS_TOKEN_DIGIT,
+    /* 2 3 */   NJS_TOKEN_DIGIT,             NJS_TOKEN_DIGIT,
+    /* 4 5 */   NJS_TOKEN_DIGIT,             NJS_TOKEN_DIGIT,
+    /* 6 7 */   NJS_TOKEN_DIGIT,             NJS_TOKEN_DIGIT,
+    /* 8 9 */   NJS_TOKEN_DIGIT,             NJS_TOKEN_DIGIT,
+    /* : ; */   NJS_TOKEN_COLON,             NJS_TOKEN_SEMICOLON,
+    /* < = */   NJS_TOKEN_LESS,              NJS_TOKEN_ASSIGNMENT,
+    /* > ? */   NJS_TOKEN_GREATER,           NJS_TOKEN_CONDITIONAL,
+
+    /* @ A */   NJS_TOKEN_ILLEGAL,           NJS_TOKEN_LETTER,
+    /* B C */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* D E */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* F G */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* H I */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* J K */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* L M */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* N O */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+
+    /* P Q */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* R S */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* T U */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* V W */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* X Y */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* Z [ */   NJS_TOKEN_LETTER,            NJS_TOKEN_OPEN_BRACKET,
+    /* \ ] */   NJS_TOKEN_ILLEGAL,           NJS_TOKEN_CLOSE_BRACKET,
+    /* ^ _ */   NJS_TOKEN_BITWISE_XOR,       NJS_TOKEN_LETTER,
+
+    /* ` a */   NJS_TOKEN_ILLEGAL,           NJS_TOKEN_LETTER,
+    /* b c */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* d e */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* f g */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* h i */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* j k */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* l m */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* n o */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+
+    /* p q */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* r s */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* t u */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* v w */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* x y */   NJS_TOKEN_LETTER,            NJS_TOKEN_LETTER,
+    /* z { */   NJS_TOKEN_LETTER,            NJS_TOKEN_OPEN_BRACE,
+    /* | } */   NJS_TOKEN_BITWISE_OR,        NJS_TOKEN_CLOSE_BRACE,
+    /* ~   */   NJS_TOKEN_BITWISE_NOT,       NJS_TOKEN_ILLEGAL,
+
+    /* 0x80 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0x90 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0xA0 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0xB0 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* TODO: the first byte of valid UTF-8: 0xC2 - 0xF4. */
+
+    /* 0xC0 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0xD0 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0xE0 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+
+    /* 0xF0 */  NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+                NJS_TOKEN_ILLEGAL,           NJS_TOKEN_ILLEGAL,
+};
+
+
+static const njs_lexer_multi_t  njs_addition_token[] = {
+    { '+', NJS_TOKEN_INCREMENT, 0, NULL },
+    { '=', NJS_TOKEN_ADDITION_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_substraction_token[] = {
+    { '-', NJS_TOKEN_DECREMENT, 0, NULL },
+    { '=', NJS_TOKEN_SUBSTRACTION_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_multiplication_token[] = {
+    { '=', NJS_TOKEN_MULTIPLICATION_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_remainder_token[] = {
+    { '=', NJS_TOKEN_REMAINDER_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_bitwise_and_token[] = {
+    { '&', NJS_TOKEN_LOGICAL_AND, 0, NULL },
+    { '=', NJS_TOKEN_BITWISE_AND_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_bitwise_xor_token[] = {
+    { '=', NJS_TOKEN_BITWISE_XOR_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_bitwise_or_token[] = {
+    { '|', NJS_TOKEN_LOGICAL_OR, 0, NULL },
+    { '=', NJS_TOKEN_BITWISE_OR_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_strict_not_equal_token[] = {
+    { '=', NJS_TOKEN_STRICT_NOT_EQUAL, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_logical_not_token[] = {
+    { '=', NJS_TOKEN_NOT_EQUAL, 1, njs_strict_not_equal_token },
+};
+
+
+static const njs_lexer_multi_t  njs_less_shift_token[] = {
+    { '=', NJS_TOKEN_LEFT_SHIFT_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_less_token[] = {
+    { '=', NJS_TOKEN_LESS_OR_EQUAL, 0, NULL },
+    { '<', NJS_TOKEN_LEFT_SHIFT, 1, njs_less_shift_token },
+};
+
+
+static const njs_lexer_multi_t  njs_less_equal_token[] = {
+    { '=', NJS_TOKEN_STRICT_EQUAL, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_unsigned_right_shift_token[] = {
+    { '=', NJS_TOKEN_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, 0, NULL },
+};
+
+
+static const njs_lexer_multi_t  njs_right_shift_token[] = {
+    { '=', NJS_TOKEN_RIGHT_SHIFT_ASSIGNMENT, 0, NULL },
+    { '>', NJS_TOKEN_UNSIGNED_RIGHT_SHIFT, 1,
+           njs_unsigned_right_shift_token },
+};
+
+
+static const njs_lexer_multi_t  njs_greater_token[] = {
+    { '=', NJS_TOKEN_GREATER_OR_EQUAL, 0, NULL },
+    { '>', NJS_TOKEN_RIGHT_SHIFT, 2, njs_right_shift_token },
+};
+
+
+static const njs_lexer_multi_t  njs_assignment_token[] = {
+    { '=', NJS_TOKEN_EQUAL, 1, njs_less_equal_token },
+};
+
+
+njs_token_t
+njs_lexer_token(njs_lexer_t *lexer)
+{
+    njs_token_t  token;
+
+    lexer->prev_token = lexer->token;
+
+    token = njs_lexer_next_token(lexer);
+
+    lexer->token = token;
+
+    return token;
+}
+
+
+static njs_token_t
+njs_lexer_next_token(njs_lexer_t *lexer)
+{
+    u_char                   c;
+    nxt_uint_t                n;
+    njs_token_t              token;
+    const njs_lexer_multi_t  *multi;
+
+    while (lexer->start < lexer->end) {
+        c = *lexer->start++;
+
+        token = njs_tokens[c];
+
+        switch (token) {
+
+        case NJS_TOKEN_SPACE:
+            continue;
+
+        case NJS_TOKEN_LETTER:
+            return njs_lexer_word(lexer, c);
+
+        case NJS_TOKEN_DOUBLE_QUOTE:
+        case NJS_TOKEN_SINGLE_QUOTE:
+            return njs_lexer_string(lexer, c);
+
+        case NJS_TOKEN_DIGIT:
+            return njs_lexer_number(lexer);
+
+        case NJS_TOKEN_ASSIGNMENT:
+            n = nxt_nitems(njs_assignment_token),
+            multi = njs_assignment_token;
+
+            goto multi;
+
+        case NJS_TOKEN_ADDITION:
+            n = nxt_nitems(njs_addition_token),
+            multi = njs_addition_token;
+
+            goto multi;
+
+        case NJS_TOKEN_SUBSTRACTION:
+            n = nxt_nitems(njs_substraction_token),
+            multi = njs_substraction_token;
+
+            goto multi;
+
+        case NJS_TOKEN_MULTIPLICATION:
+            n = nxt_nitems(njs_multiplication_token),
+            multi = njs_multiplication_token;
+
+            goto multi;
+
+        case NJS_TOKEN_DIVISION:
+            token = njs_lexer_division(lexer, token);
+
+            if (token != NJS_TOKEN_AGAIN) {
+                return token;
+            }
+
+            continue;
+
+        case NJS_TOKEN_REMAINDER:
+            n = nxt_nitems(njs_remainder_token),
+            multi = njs_remainder_token;
+
+            goto multi;
+
+        case NJS_TOKEN_BITWISE_AND:
+            n = nxt_nitems(njs_bitwise_and_token),
+            multi = njs_bitwise_and_token;
+
+            goto multi;
+
+        case NJS_TOKEN_BITWISE_XOR:
+            n = nxt_nitems(njs_bitwise_xor_token),
+            multi = njs_bitwise_xor_token;
+
+            goto multi;
+
+        case NJS_TOKEN_BITWISE_OR:
+            n = nxt_nitems(njs_bitwise_or_token),
+            multi = njs_bitwise_or_token;
+
+            goto multi;
+
+        case NJS_TOKEN_LOGICAL_NOT:
+            n = nxt_nitems(njs_logical_not_token),
+            multi = njs_logical_not_token;
+
+            goto multi;
+
+        case NJS_TOKEN_LESS:
+            n = nxt_nitems(njs_less_token),
+            multi = njs_less_token;
+
+            goto multi;
+
+        case NJS_TOKEN_GREATER:
+            n = nxt_nitems(njs_greater_token),
+            multi = njs_greater_token;
+
+            goto multi;
+
+        case NJS_TOKEN_LINE_END:
+        case NJS_TOKEN_BITWISE_NOT:
+        case NJS_TOKEN_OPEN_PARENTHESIS:
+        case NJS_TOKEN_CLOSE_PARENTHESIS:
+        case NJS_TOKEN_OPEN_BRACKET:
+        case NJS_TOKEN_CLOSE_BRACKET:
+        case NJS_TOKEN_OPEN_BRACE:
+        case NJS_TOKEN_CLOSE_BRACE:
+        case NJS_TOKEN_DOT:
+        case NJS_TOKEN_COMMA:
+        case NJS_TOKEN_COLON:
+        case NJS_TOKEN_SEMICOLON:
+        case NJS_TOKEN_CONDITIONAL:
+            return token;
+
+        default:  /* NJS_TOKEN_ILLEGAL */
+            lexer->start--;
+            return token;
+        }
+
+    multi:
+
+        return njs_lexer_multi(lexer, token, n, multi);
+    }
+
+    return NJS_TOKEN_END;
+}
+
+
+static njs_token_t
+njs_lexer_word(njs_lexer_t *lexer, u_char c)
+{
+    u_char  *p;
+
+    /* TODO: UTF-8 */
+
+    static const uint8_t  letter_digit[32]  nxt_aligned(32) = {
+        0x00, 0x00, 0x00, 0x00, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+
+                                /* '&%$ #"!  /.-, |*)(  7654 3210 ?>=< ;:98 */
+        0x10, 0x00, 0xff, 0x03, /* 0001 0000 0000 0000  1111 1111 0000 0011 */
+
+                                /* GFED CBA@ ONML KJIH  WVUT SRQP _^]\ [ZYX */
+        0xfe, 0xff, 0xff, 0x87, /* 1111 1110 1111 1111  1111 1111 1000 0111 */
+
+                                /* gfed cba` onml kjih  wvut srqp  ~}| {zyx */
+        0xfe, 0xff, 0xff, 0x07, /* 1111 1110 1111 1111  1111 1111 0000 0111 */
+
+        0x00, 0x00, 0x00, 0x00, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+        0x00, 0x00, 0x00, 0x00, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+        0x00, 0x00, 0x00, 0x00, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+        0x00, 0x00, 0x00, 0x00, /* 0000 0000 0000 0000  0000 0000 0000 0000 */
+    };
+
+    lexer->key_hash = nxt_djb_hash_add(NXT_DJB_HASH_INIT, c);
+    lexer->text.data = lexer->start - 1;
+
+    for (p = lexer->start; p < lexer->end; p++) {
+        c = *p;
+
+        if ((letter_digit[c / 8] & (1 << (c & 7))) == 0) {
+            break;
+        }
+
+        lexer->key_hash = nxt_djb_hash_add(lexer->key_hash, c);
+    }
+
+    lexer->start = p;
+    lexer->text.len = p - lexer->text.data;
+
+    return njs_lexer_keyword(lexer);
+}
+
+
+static njs_token_t
+njs_lexer_string(njs_lexer_t *lexer, u_char quote)
+{
+    u_char  *p, ch;
+
+    lexer->text.data = lexer->start;
+    p = lexer->start;
+
+    while (p < lexer->end) {
+
+        /* TODO: end of line, backslash. */
+
+        ch = *p++;
+
+        if (ch == '\\') {
+            if (p == lexer->end) {
+                return NJS_TOKEN_ILLEGAL;
+            }
+
+            /* STUB: reallocate. */
+
+            p++;
+            continue;
+        }
+
+        if (ch == quote) {
+            lexer->start = p;
+            lexer->text.len = (p - 1) - lexer->text.data;
+
+            return NJS_TOKEN_STRING;
+        }
+    }
+
+    return NJS_TOKEN_ILLEGAL;
+}
+
+
+static njs_token_t
+njs_lexer_number(njs_lexer_t *lexer)
+{
+    u_char  c, *p;
+    double  num, frac, scale;
+
+    /* TODO: "1e2" */
+
+    p = lexer->start;
+    c = p[-1];
+
+    /* Values below '0' become >= 208. */
+    c = c - '0';
+
+    num = c;
+
+    if (c != 0) {
+
+        while (p < lexer->end) {
+            c = *p;
+
+            /* Values below '0' become >= 208. */
+            c = c - '0';
+
+            if (nxt_slow_path(c > 9)) {
+                break;
+            }
+
+            num = num * 10 + c;
+            p++;
+        }
+    }
+
+    if (*p == '.') {
+
+        frac = 0;
+        scale = 1;
+
+        for (p++; p < lexer->end; p++) {
+            c = *p;
+
+            /* Values below '0' become >= 208. */
+            c = c - '0';
+
+            if (nxt_slow_path(c > 9)) {
+                break;
+            }
+
+            frac = frac * 10 + c;
+            scale *= 10;
+        }
+
+        num += frac / scale;
+    }
+
+    lexer->number = num;
+    lexer->start = p;
+
+    return NJS_TOKEN_NUMBER;
+}
+
+
+static njs_token_t
+njs_lexer_multi(njs_lexer_t *lexer, njs_token_t token, nxt_uint_t n,
+    const njs_lexer_multi_t *multi)
+{
+    u_char  c;
+
+    if (lexer->start < lexer->end) {
+        c = lexer->start[0];
+
+        do {
+            if (c == multi->symbol) {
+                lexer->start++;
+
+                if (multi->count == 0) {
+                    return multi->token;
+                }
+
+                return njs_lexer_multi(lexer, multi->token,
+                                            multi->count, multi->next);
+            }
+
+            multi++;
+            n--;
+
+        } while (n != 0);
+    }
+
+    return token;
+}
+
+
+static njs_token_t
+njs_lexer_division(njs_lexer_t *lexer, njs_token_t token)
+{
+    u_char  c, *p;
+
+    if (lexer->start < lexer->end) {
+        c = lexer->start[0];
+
+        if (c == '/') {
+            token = NJS_TOKEN_END;
+            lexer->start++;
+
+            for (p = lexer->start; p < lexer->end; p++) {
+
+                if (*p == '\r' || *p == '\n') {
+                    lexer->start = p + 1;
+                    return NJS_TOKEN_LINE_END;
+                }
+            }
+
+        } else if (c == '*') {
+            lexer->start++;
+
+            for (p = lexer->start; p < lexer->end; p++) {
+
+                if (*p == '*') {
+                    p++;
+
+                    if (p < lexer->end && *p == '/') {
+                        lexer->start = p + 1;
+                        return NJS_TOKEN_AGAIN;
+                    }
+                }
+            }
+
+            return NJS_TOKEN_ILLEGAL;
+
+        } else if (c == '=') {
+            lexer->start++;
+            token = NJS_TOKEN_DIVISION_ASSIGNMENT;
+        }
+    }
+
+    return token;
+}
+
+
+njs_token_t
+njs_lexer_regexp(njs_lexer_t *lexer, njs_regexp_flags_t *flags)
+{
+    u_char              *p;
+    njs_regexp_flags_t  _flags, flag;
+
+    for (p = lexer->start; p < lexer->end; p++) {
+
+        if (*p == '\\') {
+            p++;
+            continue;
+        }
+
+        if (*p == '/') {
+
+            lexer->text.data = lexer->start;
+            lexer->text.len = p - lexer->text.data;
+            p++;
+
+            _flags = 0;
+
+            while (p < lexer->end) {
+                switch (*p) {
+
+                case 'i':
+                   flag = NJS_REGEXP_IGNORE_CASE;
+                   break;
+
+                case 'g':
+                   flag = NJS_REGEXP_GLOBAL;
+                   break;
+
+                case 'm':
+                   flag = NJS_REGEXP_MULTILINE;
+                   break;
+
+                default:
+                   goto done;
+                }
+
+                if (nxt_slow_path((_flags & flag) != 0)) {
+                    return NJS_TOKEN_ILLEGAL;
+                }
+
+                _flags |= flag;
+                p++;
+            }
+
+        done:
+
+            *flags = _flags;
+            lexer->start = p;
+
+            return NJS_TOKEN_REGEXP_LITERAL;
+        }
+    }
+
+    return NJS_TOKEN_ILLEGAL;
+}
diff --git a/njs/njs_lexer_keyword.c b/njs/njs_lexer_keyword.c
new file mode 100644 (file)
index 0000000..ef6b3e2
--- /dev/null
@@ -0,0 +1,198 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+typedef struct {
+    nxt_str_t         name;
+    njs_token_t      token;
+    double           number;
+} njs_keyword_t;
+
+
+static const njs_keyword_t  njs_keywords[] = {
+
+    /* Values. */
+
+    { nxt_string("undefined"),     NJS_TOKEN_UNDEFINED, 0 },
+    { nxt_string("null"),          NJS_TOKEN_NULL, 0 },
+    { nxt_string("false"),         NJS_TOKEN_BOOLEAN, 0 },
+    { nxt_string("true"),          NJS_TOKEN_BOOLEAN, 1 },
+    { nxt_string("NaN"),           NJS_TOKEN_NUMBER, NJS_NAN },
+    { nxt_string("Infinity"),      NJS_TOKEN_NUMBER, NJS_INFINITY },
+
+    /* Operators. */
+
+    { nxt_string("in"),            NJS_TOKEN_IN, 0 },
+    { nxt_string("typeof"),        NJS_TOKEN_TYPEOF, 0 },
+    { nxt_string("instanceof"),    NJS_TOKEN_INSTANCEOF, 0 },
+    { nxt_string("void"),          NJS_TOKEN_VOID, 0 },
+    { nxt_string("new"),           NJS_TOKEN_NEW, 0 },
+    { nxt_string("delete"),        NJS_TOKEN_DELETE, 0 },
+    { nxt_string("yield"),         NJS_TOKEN_YIELD, 0 },
+
+    /* Statements. */
+
+    { nxt_string("var"),           NJS_TOKEN_VAR, 0 },
+    { nxt_string("if"),            NJS_TOKEN_IF, 0 },
+    { nxt_string("else"),          NJS_TOKEN_ELSE, 0 },
+    { nxt_string("while"),         NJS_TOKEN_WHILE, 0 },
+    { nxt_string("do"),            NJS_TOKEN_DO, 0 },
+    { nxt_string("for"),           NJS_TOKEN_FOR, 0 },
+    { nxt_string("break"),         NJS_TOKEN_BREAK, 0 },
+    { nxt_string("continue"),      NJS_TOKEN_CONTINUE, 0 },
+    { nxt_string("switch"),        NJS_TOKEN_SWITCH, 0 },
+    { nxt_string("case"),          NJS_TOKEN_CASE, 0 },
+    { nxt_string("default"),       NJS_TOKEN_DEFAULT, 0 },
+    { nxt_string("function"),      NJS_TOKEN_FUNCTION, 0 },
+    { nxt_string("return"),        NJS_TOKEN_RETURN, 0 },
+    { nxt_string("with"),          NJS_TOKEN_WITH, 0 },
+    { nxt_string("try"),           NJS_TOKEN_TRY, 0 },
+    { nxt_string("catch"),         NJS_TOKEN_CATCH, 0 },
+    { nxt_string("finally"),       NJS_TOKEN_FINALLY, 0 },
+    { nxt_string("throw"),         NJS_TOKEN_THROW, 0 },
+
+    /* Builtin objects. */
+
+    { nxt_string("this"),          NJS_TOKEN_THIS, 0 },
+
+    /* Builtin functions. */
+
+    { nxt_string("Object"),        NJS_TOKEN_OBJECT_FUNCTION, 0 },
+    { nxt_string("Array"),         NJS_TOKEN_ARRAY_FUNCTION, 0 },
+    { nxt_string("Boolean"),       NJS_TOKEN_BOOLEAN_FUNCTION, 0 },
+    { nxt_string("Number"),        NJS_TOKEN_NUMBER_FUNCTION, 0 },
+    { nxt_string("String"),        NJS_TOKEN_STRING_FUNCTION, 0 },
+    { nxt_string("Function"),      NJS_TOKEN_FUNCTION_FUNCTION, 0 },
+    { nxt_string("RegExp"),        NJS_TOKEN_REGEXP_FUNCTION, 0 },
+    { nxt_string("eval"),          NJS_TOKEN_EVAL, 0 },
+
+    /* Reserved words. */
+
+    { nxt_string("abstract"),      NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("boolean"),       NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("byte"),          NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("char"),          NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("class"),         NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("const"),         NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("debugger"),      NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("double"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("enum"),          NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("export"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("extends"),       NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("final"),         NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("float"),         NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("goto"),          NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("implements"),    NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("import"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("int"),           NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("interface"),     NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("long"),          NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("native"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("package"),       NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("private"),       NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("protected"),     NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("public"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("short"),         NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("static"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("super"),         NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("synchronized"),  NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("throws"),        NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("transient"),     NJS_TOKEN_RESERVED, 0 },
+    { nxt_string("volatile"),      NJS_TOKEN_RESERVED, 0 },
+};
+
+
+static nxt_int_t
+njs_keyword_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    njs_keyword_t  *keyword;
+
+    keyword = data;
+
+    if (nxt_strstr_eq(&lhq->key, &keyword->name)) {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static const nxt_lvlhsh_proto_t  njs_keyword_hash_proto
+    nxt_aligned(64) =
+{
+    NXT_LVLHSH_DEFAULT,
+    0,
+    njs_keyword_hash_test,
+    njs_lvlhsh_alloc,
+    njs_lvlhsh_free,
+};
+
+
+nxt_int_t
+njs_lexer_keywords_init(nxt_mem_cache_pool_t *mcp, nxt_lvlhsh_t *hash)
+{
+    nxt_uint_t                n;
+    nxt_lvlhsh_query_t    lhq;
+    const njs_keyword_t  *keyword;
+
+    keyword = njs_keywords;
+    n = nxt_nitems(njs_keywords);
+
+    lhq.replace = 0;
+    lhq.proto = &njs_keyword_hash_proto;
+    lhq.pool = mcp;
+
+    do {
+        lhq.key_hash = nxt_djb_hash(keyword->name.data, keyword->name.len);
+        lhq.key = keyword->name;
+        lhq.value = (void *) keyword;
+
+        if (nxt_slow_path(nxt_lvlhsh_insert(hash, &lhq) != NXT_OK)) {
+            return NXT_ERROR;
+        }
+
+        keyword++;
+        n--;
+
+    } while (n != 0);
+
+    return NXT_OK;
+}
+
+
+njs_token_t
+njs_lexer_keyword(njs_lexer_t *lexer)
+{
+    njs_keyword_t      *keyword;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = lexer->key_hash;
+    lhq.key = lexer->text;
+    lhq.proto = &njs_keyword_hash_proto;
+
+    if (nxt_lvlhsh_find(&lexer->keywords_hash, &lhq) == NXT_OK) {
+        keyword = lhq.value;
+        lexer->number = keyword->number;
+
+        return keyword->token;
+    }
+
+    return NJS_TOKEN_NAME;
+}
diff --git a/njs/njs_nonrecursive_parser.c b/njs/njs_nonrecursive_parser.c
new file mode 100644 (file)
index 0000000..f1d7c7a
--- /dev/null
@@ -0,0 +1,1549 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+typedef nxt_int_t (*njs_parser_operation_t)(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token, const void *data);
+
+typedef nxt_int_t (*njs_parser_stack_operation_t)(njs_vm_t *vm,
+    njs_parser_t *parser, const void *data);
+
+
+#define NJS_TOKEN_ANY    NJS_TOKEN_ILLEGAL
+#define NJS_PARSER_NODE  ((void *) -1)
+#define NJS_PARSER_VOID  ((void *) -2)
+
+
+typedef struct {
+    njs_token_t                  token;
+    njs_parser_operation_t       operation;
+    const void                   *data;
+    const void                   *primed;
+} njs_parser_terminal_t;
+
+
+#define NJS_PARSER_IGNORE_LINE_END  0
+#define NJS_PARSER_TEST_LINE_END    1
+
+
+typedef struct {
+    uint8_t                      take_line_end;  /* 1 bit */
+    uint8_t                      count;
+#if (NXT_SUNC)
+    /*
+     * SunC supports C99 flexible array members but does not allow
+     * static struct's initialization with arbitrary number of members.
+     */
+    const njs_parser_terminal_t  terminal[9];
+#else
+    const njs_parser_terminal_t  terminal[];
+#endif
+} njs_parser_switch_t;
+
+
+njs_token_t njs_parser_token(njs_parser_t *parser);
+static void *njs_parser_stack_pop(njs_parser_t *parser);
+static nxt_int_t njs_parser_stack_push(njs_vm_t *vm, njs_parser_t *parser,
+    const void *data);
+
+static const void *const  njs_parser_statement[];
+static const void *const  njs_parser_expression0[];
+
+
+/* STUB */
+static nxt_int_t                top = -1;
+static void                     *stack[1024];
+/**/
+
+
+njs_parser_node_t *
+njs_nonrecursive_parser(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_int_t                     ret;
+    njs_token_t                   token;
+    njs_parser_stack_operation_t  operation;
+
+    if (top < 0) {
+        njs_parser_stack_push(vm, parser, njs_parser_statement);
+    }
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        /* TODO: NJS_TOKEN_AGAIN */
+        return NULL;
+    }
+
+    do {
+        operation = (njs_parser_stack_operation_t) njs_parser_stack_pop(parser);
+
+        if (operation == NULL) {
+
+            if (parser->lexer->token == NJS_TOKEN_END) {
+                return parser->node;
+            }
+
+            break;
+        }
+
+        ret = operation(vm, parser, njs_parser_stack_pop(parser));
+
+    } while (ret == NXT_OK);
+
+    nxt_thread_log_error(NXT_LOG_ERR, "unexpected token");
+
+    return NULL;
+}
+
+
+njs_token_t
+njs_parser_token(njs_parser_t *parser)
+{
+    njs_token_t  token;
+
+    do {
+        token = njs_lexer_token(parser->lexer);
+
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+    } while (nxt_slow_path(token == NJS_TOKEN_LINE_END));
+
+    return token;
+}
+
+
+static void *
+njs_parser_stack_pop(njs_parser_t *parser)
+{
+    if (top < 0) {
+        return NULL;
+    }
+
+    return stack[top--];
+}
+
+
+static nxt_int_t
+njs_parser_stack_push(njs_vm_t *vm, njs_parser_t *parser, const void *data)
+{
+    void *const  *next;
+
+    next = data;
+
+    if (next != NULL) {
+
+        do {
+            top++;
+
+            if (*next != NJS_PARSER_NODE) {
+                stack[top] = *next;
+
+            } else {
+                stack[top] = parser->node;
+            }
+
+            next++;
+
+        } while (*next != NULL);
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_switch(njs_vm_t *vm, njs_parser_t *parser, void *data)
+{
+    nxt_int_t                    ret;
+    nxt_uint_t                   n;
+    njs_token_t                  token;
+    njs_parser_switch_t          *swtch;
+    const njs_parser_terminal_t  *term;
+
+    swtch = data;
+    token = parser->lexer->token;
+
+    n = swtch->count;
+    term = swtch->terminal;
+
+    do {
+        if (token == term->token || term->token == NJS_TOKEN_ANY) {
+            ret = term->operation(vm, parser, token, term->data);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NXT_ERROR;
+            }
+
+            ret = njs_parser_stack_push(vm, parser, term->primed);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NXT_ERROR;
+            }
+
+            if (term->token != NJS_TOKEN_ANY) {
+                token = njs_parser_token(parser);
+                if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                    /* TODO: NJS_TOKEN_AGAIN */
+                    return NXT_ERROR;
+                }
+            }
+
+            return NXT_OK;
+        }
+
+        term++;
+        n--;
+
+    } while (n != 0);
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_statement_semicolon(njs_vm_t *vm, njs_parser_t *parser,
+    void *data)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node;
+
+    node = data;
+
+    switch (parser->lexer->token) {
+
+    case NJS_TOKEN_SEMICOLON:
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            /* TODO: NJS_TOKEN_AGAIN */
+            return NXT_ERROR;
+        }
+
+        /* Fall through. */
+
+    case NJS_TOKEN_END:
+
+        node->right = parser->node;
+        parser->node = node;
+
+        return NXT_OK;
+
+    default:
+        break;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_test_token(njs_vm_t *vm, njs_parser_t *parser, void *data)
+{
+    njs_token_t       token;
+
+    token = (njs_token_t) data;
+
+    if (parser->lexer->token == token) {
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            /* TODO: NJS_TOKEN_AGAIN */
+            return NXT_ERROR;
+        }
+
+        return NXT_OK;
+    }
+
+    vm->exception = &njs_exception_syntax_error;
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_node(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token,
+    const void *data)
+{
+    njs_parser_node_t  *node;
+
+    token = (njs_token_t) data;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->left = parser->node;
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_noop(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token,
+    const void *data)
+{
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_link_left(njs_vm_t *vm, njs_parser_t *parser, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = (njs_parser_node_t *) data;
+
+    node->left = parser->node;
+    parser->node = node;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_link_right(njs_vm_t *vm, njs_parser_t *parser, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = (njs_parser_node_t *) data;
+
+    node->right = parser->node;
+    parser->node = node;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_condition_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->left = parser->node;
+        parser->node = node;
+        parser->code_size += sizeof(njs_vmcode_cond_jump_t)
+                             + sizeof(njs_vmcode_move_t)
+                             + sizeof(njs_vmcode_jump_t)
+                             + sizeof(njs_vmcode_move_t);
+        parser->branch = 1;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_binary_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    operation = (njs_vmcode_operation_t) data;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.operation = operation;
+        node->left = parser->node;
+        parser->node = node;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_post_unary_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    operation = (njs_vmcode_operation_t) data;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.operation = operation;
+        node->left = parser->node;
+        parser->node = node;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_unary_expression0(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    operation = (njs_vmcode_operation_t) data;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.operation = operation;
+        parser->node = node;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_unary_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    operation = (njs_vmcode_operation_t) data;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.operation = operation;
+        parser->node = node;
+        parser->code_size += sizeof(njs_vmcode_2addr_t);
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_unary_plus_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    return njs_parser_unary_expression(vm, parser,
+                                       NJS_TOKEN_UNARY_PLUS, data);
+}
+
+
+static nxt_int_t
+njs_parser_unary_plus_link(njs_vm_t *vm, njs_parser_t *parser,
+    const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = (njs_parser_node_t *) data;
+
+    /* Skip the unary plus of number. */
+
+    if (parser->node->token != NJS_TOKEN_NUMBER) {
+        node->left = parser->node;
+        parser->node = node;
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_unary_negation_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    return njs_parser_unary_expression(vm, parser,
+                                       NJS_TOKEN_UNARY_NEGATION, data);
+}
+
+
+static nxt_int_t
+njs_parser_unary_negative_link(njs_vm_t *vm, njs_parser_t *parser,
+    void *data)
+{
+    double             num;
+    njs_parser_node_t  *node;
+
+    node = data;
+
+    if (parser->node->token == NJS_TOKEN_NUMBER) {
+        /* Optimization of common negative number. */
+        node = parser->node;
+        num = -node->u.value.data.u.number;
+        node->u.value.data.u.number = num;
+        node->u.value.data.truth = njs_is_number_true(num);
+
+    } else {
+        node->left = parser->node;
+        parser->node = node;
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_name_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    nxt_uint_t         level;
+    njs_extern_t       *ext;
+    njs_variable_t     *var;
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        ext = njs_parser_external(vm, parser);
+
+        if (ext != NULL) {
+            node->token = NJS_TOKEN_EXTERNAL;
+            node->u.value.type = NJS_EXTERNAL;
+            node->u.value.data.truth = 1;
+            node->index = (njs_index_t) ext;
+
+        } else {
+            node->token = token;
+
+            var = njs_parser_variable(vm, parser, &level);
+            if (nxt_slow_path(var == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            switch (var->state) {
+
+            case NJS_VARIABLE_CREATED:
+                var->state = NJS_VARIABLE_PENDING;
+                parser->code_size += sizeof(njs_vmcode_1addr_t);
+                break;
+
+            case NJS_VARIABLE_PENDING:
+                var->state = NJS_VARIABLE_USED;
+                parser->code_size += sizeof(njs_vmcode_1addr_t);
+                break;
+
+            case NJS_VARIABLE_USED:
+                parser->code_size += sizeof(njs_vmcode_1addr_t);
+                break;
+
+            case NJS_VARIABLE_SET:
+            case NJS_VARIABLE_DECLARED:
+                break;
+            }
+
+            node->lvalue = NJS_LVALUE_ENABLED;
+            node->u.variable = var;
+        }
+    }
+
+    parser->node = node;
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+njs_parser_var_name(njs_vm_t *vm, njs_parser_t *parser, void *data)
+{
+    /* TODO disable NJS_TOKEN_EXTERNAL */
+    return njs_parser_name_expression(vm, parser, NJS_TOKEN_NAME, data);
+}
+
+
+static nxt_int_t
+njs_parser_this_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->index = NJS_INDEX_THIS;
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_string_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    nxt_int_t          ret;
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+
+        ret = njs_parser_string_create(vm, &node->u.value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_number_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    double             num;
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        num = parser->lexer->number;
+        node->u.value.data.u.number = num;
+        node->u.value.type = NJS_NUMBER;
+        node->u.value.data.truth = njs_is_number_true(num);
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_boolean_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.value = (parser->lexer->number == 0.0) ? njs_value_false:
+                                                         njs_value_true;
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_undefined_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.value = njs_value_void;
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_null_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+
+    if (nxt_fast_path(node != NULL)) {
+        node->token = token;
+        node->u.value = njs_value_null;
+        parser->node = node;
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+njs_parser_syntax_error(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token, const void *data)
+{
+    vm->exception = &njs_exception_syntax_error;
+
+    return NXT_ERROR;
+}
+
+
+/*
+ * The variables and literal values.
+ *
+ * VALUE = "(" EXPRESSION ")"
+ *         [ NAME create_node ]
+ *         [ "this" create_node ]
+ *         [ STRING create_node ]
+ *         [ NUMBER create_node ]
+ *         [ BOOLEAN create_node ]
+ *         [ "undefined" create_node ]
+ *         [ "null" create_node ]
+ *         ERROR
+ */
+
+static const void *const  njs_parser_grouping_expression[] = {
+    (void *) NJS_TOKEN_CLOSE_PARENTHESIS, (void *) njs_parser_test_token,
+    &njs_parser_expression0, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_value_expression_switch = {
+    NJS_PARSER_IGNORE_LINE_END,
+    9, {
+        { NJS_TOKEN_OPEN_PARENTHESIS, njs_parser_noop, NULL,
+          &njs_parser_grouping_expression },
+
+        { NJS_TOKEN_NAME, njs_parser_name_expression, NULL, NULL },
+        { NJS_TOKEN_THIS, njs_parser_this_expression, NULL, NULL },
+        { NJS_TOKEN_STRING, njs_parser_string_expression, NULL, NULL },
+        { NJS_TOKEN_NUMBER, njs_parser_number_expression, NULL, NULL },
+        { NJS_TOKEN_BOOLEAN, njs_parser_boolean_expression, NULL, NULL },
+
+        { NJS_TOKEN_UNDEFINED,
+          njs_parser_undefined_expression, NULL, NULL },
+        { NJS_TOKEN_NULL, njs_parser_null_expression, NULL, NULL },
+
+        { NJS_TOKEN_ANY, njs_parser_syntax_error, NULL, NULL },
+    }
+};
+
+
+#if 0
+
+static const void *const  njs_parser_value_expression[] = {
+    &njs_parser_value_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+#endif
+
+
+/*
+ * The postfix increment and decrement operations.
+ *
+ * POSTFIX_INC_DEC = VALUE [ "++" create_node ]
+ *                   VALUE [ "--" create_node ]
+ *                   <>
+ */
+
+
+static const njs_parser_switch_t  njs_parser_post_inc_dec_expression_switch = {
+    NJS_PARSER_IGNORE_LINE_END,
+    2, {
+        { NJS_TOKEN_INCREMENT,
+          njs_parser_post_unary_expression,
+          (void *) njs_vmcode_post_increment, NULL },
+
+        { NJS_TOKEN_DECREMENT,
+          njs_parser_post_unary_expression,
+          (void *) njs_vmcode_post_decrement, NULL },
+    }
+};
+
+
+static const void *const  njs_parser_post_inc_dec_expression[] = {
+    &njs_parser_post_inc_dec_expression_switch, (void *) njs_parser_switch,
+    &njs_parser_value_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+/*
+ * The prefix increment and decrement operations.
+ *
+ * PREFIX_INC_DEC = [ "++" create_node ] POSTFIX_INC_DEC link_left
+ *                  [ "--" create_node ] POSTFIX_INC_DEC link_left
+ *                  <>                   POSTFIX_INC_DEC
+ */
+
+static const void *const  njs_parser_inc_dec_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_link_left,
+    &njs_parser_value_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_inc_dec_expression_switch = {
+    NJS_PARSER_IGNORE_LINE_END,
+    3, {
+        { NJS_TOKEN_INCREMENT,
+          njs_parser_unary_expression0, (void *) njs_vmcode_increment,
+          njs_parser_inc_dec_expression_primed },
+
+        { NJS_TOKEN_DECREMENT,
+          njs_parser_unary_expression0, (void *) njs_vmcode_decrement,
+          njs_parser_inc_dec_expression_primed },
+
+        { NJS_TOKEN_ANY, njs_parser_noop, NULL,
+          njs_parser_post_inc_dec_expression },
+    }
+};
+
+
+static const void *const  njs_parser_inc_dec_expression[] = {
+    &njs_parser_inc_dec_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+/*
+ * The unary operations.
+ *
+ * UNARY = [ "+"      create_node ] UNARY   plus_link_left
+ *         [ "-"      create_node ] UNARY   negation_link_left
+ *         [ "!"      create_node ] UNARY   link_left
+ *         [ "~"      create_node ] UNARY   link_left
+ *         [ "typeof" create_node ] UNARY   link_left
+ *         [ "void"   create_node ] UNARY   link_left
+ *         [ "delete" create_node ] UNARY   link_left
+ *         <>                       INC_DEC
+ */
+
+static const njs_parser_switch_t  njs_parser_unary_expression_switch;
+
+static const void *const  njs_parser_unary_plus_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_unary_plus_link,
+    &njs_parser_unary_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+static const void *const  njs_parser_unary_negative_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_unary_negative_link,
+    &njs_parser_unary_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+static const void *const  njs_parser_unary_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_link_left,
+    &njs_parser_unary_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_unary_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    8, {
+        { NJS_TOKEN_ADDITION,
+          njs_parser_unary_plus_expression, (void *) njs_vmcode_unary_plus,
+          njs_parser_unary_plus_expression_primed },
+
+        { NJS_TOKEN_SUBSTRACTION,
+          njs_parser_unary_negation_expression,
+          (void *) njs_vmcode_unary_negation,
+          njs_parser_unary_negative_expression_primed },
+
+        { NJS_TOKEN_LOGICAL_NOT,
+          njs_parser_unary_expression, (void *) njs_vmcode_logical_not,
+          njs_parser_unary_expression_primed },
+
+        { NJS_TOKEN_BITWISE_NOT,
+          njs_parser_unary_expression, (void *) njs_vmcode_bitwise_not,
+          njs_parser_unary_expression_primed },
+
+        { NJS_TOKEN_TYPEOF,
+          njs_parser_unary_expression, (void *) njs_vmcode_typeof,
+          njs_parser_unary_expression_primed },
+
+        { NJS_TOKEN_VOID,
+          njs_parser_unary_expression, (void *) njs_vmcode_void,
+          njs_parser_unary_expression_primed },
+
+        { NJS_TOKEN_DELETE,
+          njs_parser_unary_expression, (void *) njs_vmcode_delete,
+          njs_parser_unary_expression_primed },
+
+        { NJS_TOKEN_ANY, njs_parser_noop, NULL,
+          njs_parser_inc_dec_expression },
+    }
+};
+
+
+/*
+ * The left associative multiplication, division and remainder operations.
+ *
+ * MULTIPLICATION  =                     UNARY            MULTIPLICATION'
+ * MULTIPLICATION' = [ "*" create_node ] UNARY link_right MULTIPLICATION'
+ *                   [ "/" create_node ] UNARY link_right MULTIPLICATION'
+ *                   [ "%" create_node ] UNARY link_right MULTIPLICATION'
+ *                   <>
+ */
+
+static const njs_parser_switch_t  njs_parser_multiplicative_expression_switch;
+
+static const void *const  njs_parser_multiplicative_expression_primed[] = {
+    &njs_parser_multiplicative_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_unary_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+static const njs_parser_switch_t
+    njs_parser_multiplicative_expression_switch =
+{
+    NJS_PARSER_TEST_LINE_END,
+    3, {
+        { NJS_TOKEN_MULTIPLICATION,
+          njs_parser_binary_expression, (void *) njs_vmcode_multiplication,
+          njs_parser_multiplicative_expression_primed },
+
+        { NJS_TOKEN_DIVISION,
+          njs_parser_binary_expression, (void *) njs_vmcode_division,
+          njs_parser_multiplicative_expression_primed },
+
+        { NJS_TOKEN_REMAINDER,
+          njs_parser_binary_expression, (void *) njs_vmcode_remainder,
+          njs_parser_multiplicative_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_multiplicative_expression[] = {
+    &njs_parser_multiplicative_expression_switch, (void *) njs_parser_switch,
+    &njs_parser_unary_expression_switch, (void *) njs_parser_switch,
+    NULL,
+};
+
+
+/*
+ * The left associative addition and substraction operations.
+ *
+ * ADDITION  =                     MULTIPLICATION            ADDITION'
+ * ADDITION' = [ "+" create_node ] MULTIPLICATION link_right ADDITION'
+ *             [ "-" create_node ] MULTIPLICATION link_right ADDITION'
+ *             <>
+ */
+
+static const njs_parser_switch_t  njs_parser_additive_expression_switch;
+
+static const void *const  njs_parser_additive_expression_primed[] = {
+    &njs_parser_additive_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    njs_parser_multiplicative_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_additive_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    2, {
+        { NJS_TOKEN_ADDITION,
+          njs_parser_binary_expression, (void *) njs_vmcode_addition,
+          njs_parser_additive_expression_primed },
+
+        { NJS_TOKEN_SUBSTRACTION,
+          njs_parser_binary_expression, (void *) njs_vmcode_substraction,
+          njs_parser_additive_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_additive_expression[] = {
+    &njs_parser_additive_expression_switch, (void *) njs_parser_switch,
+    njs_parser_multiplicative_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative bitwise shift operations.
+ *
+ * BITWISE_SHIFT  =                       ADDITION            BITWISE_SHIFT'
+ * BITWISE_SHIFT' = [ "<<"  create_node ] ADDITION link_right BITWISE_SHIFT'
+ *                  [ ">>"  create_node ] ADDITION link_right BITWISE_SHIFT'
+ *                  [ ">>>" create_node ] ADDITION link_right BITWISE_SHIFT'
+ *                  <>
+ */
+
+static const njs_parser_switch_t  njs_parser_bitwise_shift_expression_switch;
+
+static const void *const  njs_parser_bitwise_shift_expression_primed[] = {
+    &njs_parser_bitwise_shift_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_additive_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_bitwise_shift_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    3, {
+        { NJS_TOKEN_LEFT_SHIFT,
+          njs_parser_binary_expression, (void *) njs_vmcode_left_shift,
+          njs_parser_bitwise_shift_expression_primed },
+
+        { NJS_TOKEN_RIGHT_SHIFT,
+          njs_parser_binary_expression, (void *) njs_vmcode_right_shift,
+          njs_parser_bitwise_shift_expression_primed },
+
+        { NJS_TOKEN_UNSIGNED_RIGHT_SHIFT,
+          njs_parser_binary_expression,
+          (void *) njs_vmcode_unsigned_right_shift,
+          njs_parser_bitwise_shift_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_bitwise_shift_expression[] = {
+    &njs_parser_bitwise_shift_expression_switch, (void *) njs_parser_switch,
+    njs_parser_additive_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative relational operations.
+ *
+ * RELATIONAL  =                      BITWISE_SHIFT            RELATIONAL'
+ * RELATIONAL' = [ "<   create_node ] BITWISE_SHIFT link_right RELATIONAL'
+ *               [ "<=" create_node ] BITWISE_SHIFT link_right RELATIONAL'
+ *               [ ">"  create_node ] BITWISE_SHIFT link_right RELATIONAL'
+ *               [ ">=" create_node ] BITWISE_SHIFT link_right RELATIONAL'
+ *               [ "in" create_node ] BITWISE_SHIFT link_right RELATIONAL'
+ *               <>
+ */
+
+static const njs_parser_switch_t  njs_parser_relational_expression_switch;
+
+static const void *const  njs_parser_relational_expression_primed[] = {
+    &njs_parser_relational_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_bitwise_shift_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_relational_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    6, {
+        { NJS_TOKEN_LESS,
+          njs_parser_binary_expression, (void *) njs_vmcode_less,
+          njs_parser_relational_expression_primed },
+
+        { NJS_TOKEN_LESS_OR_EQUAL,
+          njs_parser_binary_expression, (void *) njs_vmcode_less_or_equal,
+          njs_parser_relational_expression_primed },
+
+        { NJS_TOKEN_GREATER,
+          njs_parser_binary_expression, (void *) njs_vmcode_greater,
+          njs_parser_relational_expression_primed },
+
+        { NJS_TOKEN_GREATER_OR_EQUAL,
+          njs_parser_binary_expression, (void *) njs_vmcode_greater_or_equal,
+          njs_parser_relational_expression_primed },
+
+        { NJS_TOKEN_IN,
+          njs_parser_binary_expression, (void *) njs_vmcode_property_in,
+          njs_parser_relational_expression_primed },
+
+        { NJS_TOKEN_INSTANCEOF,
+          njs_parser_binary_expression, (void *) njs_vmcode_instance_of,
+          njs_parser_relational_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_relational_expression[] = {
+    &njs_parser_relational_expression_switch, (void *) njs_parser_switch,
+    njs_parser_bitwise_shift_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative equality operations.
+ *
+ * EQUALITY  =                       RELATION            EQUALITY'
+ * EQUALITY' = [ "=="  create_node ] RELATION link_right EQUALITY'
+ *             [ "!="  create_node ] RELATION link_right EQUALITY'
+ *             [ "===" create_node ] RELATION link_right EQUALITY'
+ *             [ "!==" create_node ] RELATION link_right EQUALITY'
+ *             <>
+ */
+
+static const njs_parser_switch_t  njs_parser_equality_expression_switch;
+
+static const void *const  njs_parser_equality_expression_primed[] = {
+    &njs_parser_equality_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_relational_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_equality_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    4, {
+        { NJS_TOKEN_EQUAL,
+          njs_parser_binary_expression, (void *) njs_vmcode_equal,
+          njs_parser_equality_expression_primed },
+
+        { NJS_TOKEN_NOT_EQUAL,
+          njs_parser_binary_expression, (void *) njs_vmcode_not_equal,
+          njs_parser_equality_expression_primed },
+
+        { NJS_TOKEN_STRICT_EQUAL,
+          njs_parser_binary_expression, (void *) njs_vmcode_strict_equal,
+          njs_parser_equality_expression_primed },
+
+        { NJS_TOKEN_STRICT_NOT_EQUAL,
+          njs_parser_binary_expression, (void *) njs_vmcode_strict_not_equal,
+          njs_parser_equality_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_equality_expression[] = {
+    &njs_parser_equality_expression_switch, (void *) njs_parser_switch,
+    njs_parser_relational_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative bitwise AND.
+ *
+ * BITWISE_AND  =                      EQUALITY            BITWISE_AND'
+ * BITWISE_AND' = [ "&"  create_node ] EQUALITY link_right BITWISE_AND'
+ *                <>
+ */
+
+static const njs_parser_switch_t  njs_parser_bitwise_and_expression_switch;
+
+static const void *const  njs_parser_bitwise_and_expression_primed[] = {
+    &njs_parser_bitwise_and_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_bitwise_shift_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_bitwise_and_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_BITWISE_AND,
+          njs_parser_binary_expression, (void *) njs_vmcode_bitwise_and,
+          njs_parser_bitwise_and_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_bitwise_and_expression[] = {
+    &njs_parser_bitwise_and_expression_switch, (void *) njs_parser_switch,
+    njs_parser_equality_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative bitwise XOR.
+ *
+ * BITWISE_XOR  =                      BITWISE_AND            BITWISE_XOR'
+ * BITWISE_XOR' = [ "^"  create_node ] BITWISE_AND link_right BITWISE_XOR'
+ *                <>
+ */
+
+static const njs_parser_switch_t  njs_parser_bitwise_xor_expression_switch;
+
+static const void *const  njs_parser_bitwise_xor_expression_primed[] = {
+    &njs_parser_bitwise_xor_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_bitwise_and_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_bitwise_xor_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_BITWISE_XOR,
+          njs_parser_binary_expression, (void *) njs_vmcode_bitwise_xor,
+          njs_parser_bitwise_xor_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_bitwise_xor_expression[] = {
+    &njs_parser_bitwise_xor_expression_switch, (void *) njs_parser_switch,
+    njs_parser_bitwise_and_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative bitwise OR.
+ *
+ * BITWISE_OR  =                      BITWISE_XOR            BITWISE_OR'
+ * BITWISE_OR' = [ "|"  create_node ] BITWISE_XOR link_right BITWISE_OR'
+ *               <>
+ */
+
+static const njs_parser_switch_t  njs_parser_bitwise_or_expression_switch;
+
+static const void *const  njs_parser_bitwise_or_expression_primed[] = {
+    &njs_parser_bitwise_or_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_bitwise_xor_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_bitwise_or_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_BITWISE_OR,
+          njs_parser_binary_expression, (void *) njs_vmcode_bitwise_or,
+          njs_parser_bitwise_or_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_bitwise_or_expression[] = {
+    &njs_parser_bitwise_or_expression_switch, (void *) njs_parser_switch,
+    njs_parser_bitwise_xor_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative logical AND.
+ *
+ * LOGICAL_AND  =                       BITWISE_OR            LOGICAL_AND'
+ * LOGICAL_AND' = [ "&&"  create_node ] BITWISE_OR link_right LOGICAL_AND'
+ *                <>
+ */
+
+static const njs_parser_switch_t  njs_parser_logical_and_expression_switch;
+
+static const void *const  njs_parser_logical_and_expression_primed[] = {
+    &njs_parser_logical_and_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_bitwise_or_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_logical_and_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_LOGICAL_AND,
+          njs_parser_binary_expression, (void *) njs_vmcode_logical_and,
+          njs_parser_logical_and_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_logical_and_expression[] = {
+    &njs_parser_logical_and_expression_switch, (void *) njs_parser_switch,
+    njs_parser_bitwise_or_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative logical OR.
+ *
+ * LOGICAL_OR  =                       LOGICAL_AND            LOGICAL_OR'
+ * LOGICAL_OR' = [ "||"  create_node ] LOGICAL_AND link_right LOGICAL_OR'
+ *               <>
+ */
+
+static const njs_parser_switch_t  njs_parser_logical_or_expression_switch;
+
+static const void *const  njs_parser_logical_or_expression_primed[] = {
+    &njs_parser_logical_or_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_logical_and_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_logical_or_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_LOGICAL_OR,
+          njs_parser_binary_expression, (void *) njs_vmcode_logical_or,
+          njs_parser_logical_or_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_logical_or_expression[] = {
+    &njs_parser_logical_or_expression_switch, (void *) njs_parser_switch,
+    njs_parser_logical_and_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The right associative condition operation.
+ *
+ * CONDITION  =                     LOGICAL_OR CONDITION'
+ * CONDITION' = [ "?" create_node ] ASSIGNMENT COLON      link_right
+ *              <>
+ * COLON      = [ ":" create_node ] ASSIGNMENT            link_right
+ *              ERROR
+ */
+
+static const void *const  njs_parser_assignment_expression[];
+
+static const void *const  njs_parser_colon_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_assignment_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_colon_expression_switch = {
+    NJS_PARSER_IGNORE_LINE_END,
+    2, {
+        { NJS_TOKEN_COLON, njs_parser_node, (void *) NJS_TOKEN_ELSE,
+          njs_parser_colon_expression_primed },
+
+        { NJS_TOKEN_ANY, njs_parser_syntax_error, NULL, NULL },
+    }
+};
+
+
+static const void *const  njs_parser_conditional_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_colon_expression_switch, (void *) njs_parser_switch,
+    &njs_parser_assignment_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_conditional_expression_switch = {
+    NJS_PARSER_IGNORE_LINE_END,
+    1, {
+        { NJS_TOKEN_CONDITIONAL,
+          njs_parser_condition_expression, (void *) NJS_TOKEN_CONDITIONAL,
+          njs_parser_conditional_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_conditional_expression0[] = {
+    &njs_parser_conditional_expression_switch, (void *) njs_parser_switch,
+    njs_parser_logical_or_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The right associative assignment operations.
+ *
+ * ASSIGNMENT  =                     CONDITION ASSIGNMENT'
+ * ASSIGNMENT' = [ "=" create_node ] CONDITION ASSIGNMENT' link_right
+ *               <>
+ */
+
+static const void *const  njs_parser_assignment_expression_primed[] = {
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_assignment_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_assignment_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_ASSIGNMENT,
+          /* STUB */ njs_parser_binary_expression, (void *) njs_vmcode_move,
+          njs_parser_assignment_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_assignment_expression[] = {
+    &njs_parser_assignment_expression_switch, (void *) njs_parser_switch,
+    njs_parser_conditional_expression0, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The left associative comma.
+ *
+ * EXPRESSION  =                      ASSIGNMENT            EXPRESSION'
+ * EXPRESSION' = [ ","  create_node ] ASSIGNMENT link_right EXPRESSION'
+ *               <>
+ */
+
+static const njs_parser_switch_t  njs_parser_comma_expression_switch;
+
+static const void *const  njs_parser_expression_primed[] = {
+    &njs_parser_comma_expression_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_assignment_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_comma_expression_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_COMMA,
+          njs_parser_binary_expression, NULL, njs_parser_expression_primed },
+    }
+};
+
+
+static const void *const  njs_parser_expression0[] = {
+    &njs_parser_comma_expression_switch, (void *) njs_parser_switch,
+    njs_parser_assignment_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+/*
+ * The variable declarations and initializations.
+ *
+ * VAR      = [ NAME create_node ]              VAR_INIT
+ * VAR_INIT = "=" ASSIGNMENT     [ link_right ] VAR_NEXT
+ *            "," VAR
+ *            <>
+ * VAR_NEXT = "," VAR
+ *            <>
+ */
+
+static const void *const  njs_parser_var_statement[];
+
+static const njs_parser_switch_t  njs_parser_var_next_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    1, {
+        { NJS_TOKEN_COMMA,
+          njs_parser_noop, NULL, njs_parser_var_statement },
+    }
+};
+
+
+static const void *const  njs_parser_var_init_expression[] = {
+    &njs_parser_var_next_switch, (void *) njs_parser_switch,
+    NJS_PARSER_NODE, (void *) njs_parser_link_right,
+    &njs_parser_assignment_expression, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_var_init_switch = {
+    NJS_PARSER_TEST_LINE_END,
+    2, {
+        { NJS_TOKEN_ASSIGNMENT,
+          /* TODO */ njs_parser_binary_expression, (void *) njs_vmcode_move,
+          njs_parser_var_init_expression },
+
+        { NJS_TOKEN_COMMA, /* TODO: free node */
+          njs_parser_noop, NULL, njs_parser_var_statement },
+    }
+};
+
+
+static const void *const  njs_parser_var_statement[] = {
+    &njs_parser_var_init_switch, (void *) njs_parser_switch,
+    NJS_PARSER_VOID, (void *) njs_parser_var_name,
+    NULL,
+};
+
+
+/*
+ * The statements.
+ *
+ * STATEMENT = <END>
+ *             "var"  VAR
+ *             ";"    STATEMENT
+ *             <*>    create_node EXPRESSION [ ";" link_right ] STATEMENT
+ */
+
+static const void *const  njs_parser_statement[];
+
+static const void *const  njs_parser_expression_statement[] = {
+    &njs_parser_statement, (void *) njs_parser_stack_push,
+    NJS_PARSER_NODE, (void *) njs_parser_statement_semicolon,
+    &njs_parser_expression0, (void *) njs_parser_stack_push,
+    NULL,
+};
+
+
+static const njs_parser_switch_t  njs_parser_statement_switch = {
+    NJS_PARSER_IGNORE_LINE_END,
+//    4, {
+    3, {
+        { NJS_TOKEN_VAR, njs_parser_noop, NULL, njs_parser_var_statement },
+        { NJS_TOKEN_END, njs_parser_noop, NULL, NULL },
+#if 0
+        { NJS_TOKEN_SEMICOLON,
+          njs_parser_node, (void *) NJS_TOKEN_STATEMENT, NULL },
+#endif
+
+        { NJS_TOKEN_ANY, njs_parser_node, (void *) NJS_TOKEN_STATEMENT,
+          njs_parser_expression_statement },
+    }
+};
+
+
+static const void *const  njs_parser_statement[] = {
+    &njs_parser_statement_switch, (void *) njs_parser_switch,
+    NULL,
+};
diff --git a/njs/njs_number.c b/njs/njs_number.c
new file mode 100644 (file)
index 0000000..8fd6016
--- /dev/null
@@ -0,0 +1,287 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_number.h>
+#include <string.h>
+#include <stdio.h>
+
+
+double
+njs_value_to_number(njs_value_t *value)
+{
+    njs_array_t  *array;
+
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        return value->data.u.number;
+    }
+
+    if (njs_is_string(value)) {
+        return njs_string_to_number(value);
+    }
+
+    if (njs_is_array(value)) {
+
+        array = value->data.u.array;
+
+        if (nxt_lvlhsh_is_empty(&array->object.hash)) {
+
+            if (array->length == 0) {
+                /* An empty array value is zero. */
+                return 0.0;
+            }
+
+            if (array->length == 1 && njs_is_valid(&array->start[0])) {
+                /* A single value array is the zeroth array value. */
+                return njs_value_to_number(&array->start[0]);
+            }
+        }
+    }
+
+    return NJS_NAN;
+}
+
+
+double
+njs_number_parse(const u_char **start, const u_char *end)
+{
+    u_char        c;
+    double        num, frac, scale;
+    const u_char  *p;
+
+    /* TODO: "1e2" */
+
+    p = *start;
+    c = *p++;
+
+    /* Values below '0' become >= 208. */
+    c = c - '0';
+
+    num = c;
+
+    while (p < end) {
+        c = *p;
+
+        /* Values below '0' become >= 208. */
+        c = c - '0';
+
+        if (nxt_slow_path(c > 9)) {
+            break;
+        }
+
+        num = num * 10 + c;
+        p++;
+    }
+
+    if (*p == '.') {
+
+        frac = 0;
+        scale = 1;
+
+        for (p++; p < end; p++) {
+            c = *p;
+
+            /* Values below '0' become >= 208. */
+            c = c - '0';
+
+            if (nxt_slow_path(c > 9)) {
+                break;
+            }
+
+            frac = frac * 10 + c;
+            scale *= 10;
+        }
+
+        num += frac / scale;
+    }
+
+    *start = p;
+
+    return num;
+}
+
+
+njs_ret_t
+njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
+    const njs_value_t *number)
+{
+    u_char             *p;
+    double             num;
+    size_t             size;
+    const char         *fmt;
+    const njs_value_t  *value;
+    char               buf[128];
+
+    num = number->data.u.number;
+
+    if (njs_is_nan(num)) {
+        value = &njs_string_nan;
+
+    } else if (njs_is_infinity(num)) {
+
+        if (num < 0) {
+            value = &njs_string_minus_infinity;
+
+        } else {
+            value = &njs_string_plus_infinity;
+        }
+
+    } else {
+        if (fabs(num) < 1000000) {
+            fmt = "%g";
+
+        } else if (fabs(num) < 1e20) {
+            fmt = "%1.f";
+
+        } else {
+            fmt = "%1.e";
+        }
+
+        size = snprintf(buf, sizeof(buf), fmt, num);
+
+        p = njs_string_alloc(vm, string, size, size);
+
+        if (nxt_fast_path(p != NULL)) {
+            memcpy(p, buf, size);
+            return NXT_OK;
+        }
+
+        return NXT_ERROR;
+    }
+
+    *string = *value;
+
+    return NXT_OK;
+}
+
+
+njs_ret_t
+njs_number_function(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_object_t       *object;
+    const njs_value_t  *value;
+
+    if (param->nargs == 0) {
+        value = &njs_value_zero;
+
+    } else {
+        /* TODO: to_number. */
+        value = &param->args[0];
+    }
+
+    if (vm->frame->ctor) {
+        /* value->type is the same as prototype offset. */
+        object = njs_object_value_alloc(vm, value, value->type);
+        if (nxt_slow_path(object == NULL)) {
+            return NXT_ERROR;
+        }
+
+        vm->retval.data.u.object = object;
+        vm->retval.type = NJS_OBJECT_NUMBER;
+        vm->retval.data.truth = 1;
+
+    } else {
+        vm->retval = *value;
+    }
+
+    return NXT_OK;
+}
+
+
+static const njs_object_prop_t  njs_number_function_properties[] =
+{
+    { njs_string("Number"),
+      njs_string("name"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_value(NJS_NUMBER, 1, 1.0),
+      njs_string("length"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_prototype),
+      njs_string("prototype"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_number_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_number_function_properties,
+                                  nxt_nitems(njs_number_function_properties));
+}
+
+
+static njs_ret_t
+njs_number_prototype_value_of(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_value_t  *value;
+
+    value = param->object;
+
+    if (value->type != NJS_NUMBER) {
+
+        if (value->type == NJS_OBJECT_NUMBER) {
+            value = &value->data.u.object_value->value;
+
+        } else {
+            vm->exception = &njs_exception_type_error;
+            return NXT_ERROR;
+        }
+    }
+
+    vm->retval = *value;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_number_prototype_to_string(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_value_t  *value;
+
+    value = param->object;
+
+    if (value->type != NJS_NUMBER) {
+
+        if (value->type == NJS_OBJECT_NUMBER) {
+            value = &value->data.u.object_value->value;
+
+        } else {
+            vm->exception = &njs_exception_type_error;
+            return NXT_ERROR;
+        }
+    }
+
+    return njs_number_to_string(vm, &vm->retval, value);
+}
+
+
+static const njs_object_prop_t  njs_number_prototype_properties[] =
+{
+    { njs_native_function(njs_number_prototype_value_of, 0),
+      njs_string("valueOf"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_number_prototype_to_string, 0),
+      njs_string("toString"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_number_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_number_prototype_properties,
+                                  nxt_nitems(njs_number_prototype_properties));
+}
diff --git a/njs/njs_number.h b/njs/njs_number.h
new file mode 100644 (file)
index 0000000..2e7aa78
--- /dev/null
@@ -0,0 +1,35 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_NUMBER_H_INCLUDED_
+#define _NJS_NUMBER_H_INCLUDED_
+
+
+#include <math.h>
+
+
+#define NJS_NAN       NAN
+#define NJS_INFINITY  INFINITY
+
+
+#define njs_is_infinity(n)                                                    \
+    isinf(n)
+
+
+#define njs_is_nan(n)                                                         \
+    isnan(n)
+
+
+double njs_value_to_number(njs_value_t *value);
+double njs_number_parse(const u_char **start, const u_char *end);
+njs_ret_t njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
+    const njs_value_t *number);
+njs_ret_t njs_number_function(njs_vm_t *vm, njs_param_t *param);
+nxt_int_t njs_number_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+nxt_int_t njs_number_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+#endif /* _NJS_NUMBER_H_INCLUDED_ */
diff --git a/njs/njs_object.c b/njs/njs_object.c
new file mode 100644 (file)
index 0000000..800b757
--- /dev/null
@@ -0,0 +1,593 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <string.h>
+
+
+static nxt_int_t njs_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data);
+static nxt_int_t njs_object_null_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+nxt_noinline njs_object_t *
+njs_object_alloc(njs_vm_t *vm)
+{
+    njs_object_t  *obj;
+
+    obj = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                              sizeof(njs_object_t));
+
+    if (nxt_fast_path(obj != NULL)) {
+        nxt_lvlhsh_init(&obj->hash);
+        nxt_lvlhsh_init(&obj->shared_hash);
+        obj->__proto__ = &vm->prototypes[NJS_PROTOTYPE_OBJECT];
+    }
+
+    return obj;
+}
+
+
+nxt_noinline njs_object_t *
+njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value,
+    nxt_uint_t prototype)
+{
+    njs_object_value_t  *obj;
+
+    obj = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                              sizeof(njs_object_value_t));
+
+    if (nxt_fast_path(obj != NULL)) {
+        nxt_lvlhsh_init(&obj->object.hash);
+        nxt_lvlhsh_init(&obj->object.shared_hash);
+        obj->object.__proto__ = &vm->prototypes[prototype];
+        obj->value = *value;
+    }
+
+    return &obj->object;
+}
+
+
+nxt_int_t
+njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
+    const njs_object_prop_t *prop, nxt_uint_t n)
+{
+    nxt_int_t           ret;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.replace = 0;
+    lhq.proto = &njs_object_hash_proto;
+    lhq.pool = vm->mem_cache_pool;
+
+    do {
+        lhq.key.len = prop->name.short_string.size;
+
+        if (lhq.key.len != NJS_STRING_LONG) {
+            lhq.key.data = (u_char *) prop->name.short_string.start;
+
+        } else {
+            lhq.key.len = prop->name.data.string_size;
+            lhq.key.data = prop->name.data.u.string->start;
+        }
+
+        lhq.key_hash = nxt_djb_hash(lhq.key.data, lhq.key.len);
+        lhq.value = (void *) prop;
+
+        ret = nxt_lvlhsh_insert(hash, &lhq);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+
+        prop++;
+        n--;
+    } while (n != 0);
+
+    return NXT_OK;
+}
+
+
+const nxt_lvlhsh_proto_t  njs_object_hash_proto
+    nxt_aligned(64) =
+{
+    NXT_LVLHSH_DEFAULT,
+    0,
+    njs_object_hash_test,
+    njs_lvlhsh_alloc,
+    njs_lvlhsh_free,
+};
+
+
+static nxt_int_t
+njs_object_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    size_t             size;
+    u_char             *start;
+    njs_object_prop_t  *prop;
+
+    prop = data;
+
+    size = prop->name.short_string.size;
+
+    if (size != NJS_STRING_LONG) {
+        if (lhq->key.len != size) {
+            return NXT_DECLINED;
+        }
+
+        start = prop->name.short_string.start;
+
+    } else {
+        if (lhq->key.len != prop->name.data.string_size) {
+            return NXT_DECLINED;
+        }
+
+        start = prop->name.data.u.string->start;
+    }
+
+    if (memcmp(start, lhq->key.data, lhq->key.len) == 0) {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+njs_object_prop_t *
+njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name)
+{
+    njs_object_prop_t  *prop;
+
+    prop = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                               sizeof(njs_object_prop_t));
+
+    if (nxt_fast_path(prop != NULL)) {
+        prop->value = njs_value_void;
+
+        /* GC: retain. */
+        prop->name = *name;
+
+        prop->type = NJS_PROPERTY;
+        prop->enumerable = 1;
+        prop->writable = 1;
+        prop->configurable = 1;
+    }
+
+    return prop;
+}
+
+
+nxt_noinline njs_ret_t
+njs_object_method(njs_vm_t *vm, njs_param_t *param, nxt_lvlhsh_query_t *lhq)
+{
+    njs_object_prop_t  *prop;
+
+    prop = njs_object_property(vm, param->object->data.u.object, lhq);
+
+    if (nxt_fast_path(prop != NULL)) {
+        return njs_function_apply(vm, &prop->value, param);
+    }
+
+    return NXT_ERROR;
+}
+
+
+nxt_noinline njs_object_prop_t *
+njs_object_property(njs_vm_t *vm, njs_object_t *obj, nxt_lvlhsh_query_t *lhq)
+{
+    nxt_int_t  ret;
+
+    lhq->proto = &njs_object_hash_proto;
+
+    do {
+        ret = nxt_lvlhsh_find(&obj->hash, lhq);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            return lhq->value;
+        }
+
+        ret = nxt_lvlhsh_find(&obj->shared_hash, lhq);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            return lhq->value;
+        }
+
+        obj = obj->__proto__;
+
+    } while (obj != NULL);
+
+    vm->exception = &njs_exception_type_error;
+
+    return NULL;
+}
+
+
+njs_ret_t
+njs_object_function(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_uint_t    type;
+    njs_value_t   *value;
+    njs_object_t  *object;
+
+    type = NJS_OBJECT;
+
+    if (param->nargs == 0 || njs_is_null_or_void(&param->args[0])) {
+
+        object = njs_object_alloc(vm);
+        if (nxt_slow_path(object == NULL)) {
+            return NXT_ERROR;
+        }
+
+    } else {
+        value = &param->args[0];
+
+        if (njs_is_object(value)) {
+            object = value->data.u.object;
+
+        } else if (njs_is_primitive(value)) {
+
+            /* value->type is the same as prototype offset. */
+            object = njs_object_value_alloc(vm, value, value->type);
+            if (nxt_slow_path(object == NULL)) {
+                return NXT_ERROR;
+            }
+
+            type = NJS_OBJECT + value->type;
+
+        } else {
+            vm->exception = &njs_exception_type_error;
+
+            return NXT_ERROR;
+        }
+    }
+
+    vm->retval.data.u.object = object;
+    vm->retval.type = type;
+    vm->retval.data.truth = 1;
+
+    return NXT_OK;
+}
+
+
+/* TODO: properties with attributes. */
+
+static njs_ret_t
+njs_object_create(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_value_t          *args;
+    njs_object_t         *obj;
+
+    /* STUB: move to shared create to avoid threads locks. */
+    static nxt_lvlhsh_t  njs_null_proto_shared_hash;
+
+    if (param->nargs != 0) {
+        args = param->args;
+
+        if (njs_is_object(&args[0]) || njs_is_null(&args[0])) {
+
+            obj = njs_object_alloc(vm);
+            if (nxt_slow_path(obj == NULL)) {
+                return NXT_ERROR;
+            }
+
+            if (njs_is_null(&args[0])) {
+                if (nxt_lvlhsh_is_empty(&njs_null_proto_shared_hash)) {
+                    /* STUB */
+                    njs_object_null_hash(vm, &njs_null_proto_shared_hash);
+                }
+
+                obj->shared_hash = njs_null_proto_shared_hash;
+                obj->__proto__ = NULL;
+
+            } else {
+                /* GC */
+                obj->__proto__ = args[0].data.u.object;
+            }
+
+            vm->retval.data.u.object = obj;
+            vm->retval.type = NJS_OBJECT;
+            vm->retval.data.truth = 1;
+
+            return NXT_OK;
+        }
+    }
+
+    vm->exception = &njs_exception_type_error;
+
+    return NXT_ERROR;
+}
+
+
+static const njs_object_prop_t  njs_object_null_properties[] =
+{
+    { njs_value(NJS_NULL, 0, 0.0),
+      njs_string("__proto__"),
+      NJS_WHITEOUT, 0, 0, 0, },
+};
+
+
+static nxt_int_t
+njs_object_null_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_object_null_properties,
+                                  nxt_nitems(njs_object_null_properties));
+}
+
+
+/*
+ * The "prototype" property of Object(), Array() and other functions is
+ * created on demand in the functions' private hash by the "prototype"
+ * getter.  The properties are set to appropriate prototype.
+ */
+
+njs_ret_t
+njs_object_prototype_create_prototype(njs_vm_t *vm, njs_value_t *value)
+{
+    int32_t            index;
+    nxt_int_t           ret;
+    njs_object_t       *prototype;
+    njs_function_t     *function;
+    njs_object_prop_t  *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    function = value->data.u.function;
+    index = function - vm->functions;
+
+    if (index < 0 && index > NJS_PROTOTYPE_MAX) {
+        vm->retval = njs_value_void;
+        return NXT_OK;
+    }
+
+    prop = njs_object_prop_alloc(vm, &njs_string_prototype);
+    if (nxt_slow_path(prop == NULL)) {
+        return NXT_ERROR;
+    }
+
+    prototype = &vm->prototypes[index];
+
+    prop->value.data.u.object = prototype;
+    prop->value.type = NJS_OBJECT;
+    prop->value.data.truth = 1;
+
+    prop->enumerable = 0;
+    prop->writable = 0;
+    prop->configurable = 0;
+
+    lhq.value = prop;
+    lhq.key_hash = NJS_PROTOTYPE_HASH;
+    lhq.key.len = sizeof("prototype") - 1;
+    lhq.key.data = (u_char *) "prototype";
+    lhq.replace = 0;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.proto = &njs_object_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&function->object.hash, &lhq);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        vm->retval = prop->value;
+    }
+
+    /* TODO: exception NXT_ERROR. */
+
+    return ret;
+}
+
+
+static const njs_object_prop_t  njs_object_function_properties[] =
+{
+    { njs_string("Object"),
+      njs_string("name"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_prototype),
+      njs_string("prototype"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_native_function(njs_object_create, 0),
+      njs_string("create"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_object_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_object_function_properties,
+                                  nxt_nitems(njs_object_function_properties));
+}
+
+
+static njs_ret_t
+njs_object_prototype_get_proto(njs_vm_t *vm, njs_value_t *value)
+{
+    njs_object_t  *proto;
+
+    proto = value->data.u.object->__proto__;
+
+    if (nxt_fast_path(proto != NULL)) {
+        vm->retval.data.u.object = proto;
+        vm->retval.type = NJS_OBJECT;
+        vm->retval.data.truth = 1;
+
+    } else {
+        vm->retval = njs_value_null;
+    }
+
+    return NXT_OK;
+}
+
+
+/*
+ * The "constructor" property of Object(), Array() and other functions
+ * prototypes is created on demand in the prototypes' private hash by the
+ * "constructor" getter.  The properties are set to appropriate function.
+ */
+
+static njs_ret_t
+njs_object_prototype_create_constructor(njs_vm_t *vm, njs_value_t *value)
+{
+    int32_t             index;
+    nxt_int_t           ret;
+    njs_value_t         *constructor;
+    njs_object_t        *prototype;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    if (njs_is_object(value)) {
+        prototype = value->data.u.object;
+
+        do {
+            index = prototype - vm->prototypes;
+
+            if (index >= 0 && index < NJS_PROTOTYPE_MAX) {
+                goto found;
+            }
+
+            prototype = prototype->__proto__;
+
+        } while (prototype != NULL);
+
+        nxt_thread_log_alert("prototype not found");
+
+        return NXT_ERROR;
+
+    } else {
+        index = NJS_PROTOTYPE_BOOLEAN + (value->type - NJS_BOOLEAN);
+        prototype = &vm->prototypes[index];
+    }
+
+found:
+
+    prop = njs_object_prop_alloc(vm, &njs_string_constructor);
+    if (nxt_slow_path(prop == NULL)) {
+        return NXT_ERROR;
+    }
+
+    /* GC */
+
+    constructor = &vm->scopes[NJS_SCOPE_GLOBAL][index];
+    prop->value = *constructor;
+
+    prop->enumerable = 0;
+
+    lhq.value = prop;
+    lhq.key_hash = NJS_CONSTRUCTOR_HASH;
+    lhq.key.len = sizeof("constructor") - 1;
+    lhq.key.data = (u_char *) "constructor";
+    lhq.replace = 0;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.proto = &njs_object_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&prototype->hash, &lhq);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        vm->retval = *constructor;
+    }
+
+    return ret;
+}
+
+
+static njs_ret_t
+njs_object_prototype_value_of(njs_vm_t *vm, njs_param_t *param)
+{
+    vm->retval = *param->object;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_object_prototype_to_string(njs_vm_t *vm, njs_param_t *param)
+{
+    int32_t       index;
+    njs_value_t   *value;
+    njs_object_t  *prototype;
+
+    static const njs_value_t  *class_name[] = {
+        /* Primitives. */
+        &njs_string_object_null,
+        &njs_string_object_undefined,
+        &njs_string_object_boolean,
+        &njs_string_object_number,
+        &njs_string_object_string,
+
+        &njs_string_object_function,
+        &njs_string_object_function,
+        &njs_string_empty,
+
+        /* Objects. */
+        &njs_string_object_object,
+        &njs_string_object_array,
+        &njs_string_object_boolean,
+        &njs_string_object_number,
+        &njs_string_object_string,
+        &njs_string_object_function,
+        &njs_string_object_regexp,
+    };
+
+    value = param->object;
+    index = value->type;
+
+    if (njs_is_object(value)) {
+        prototype = value->data.u.object;
+
+        do {
+            index = prototype - vm->prototypes;
+
+            if (index >= 0 && index < NJS_PROTOTYPE_MAX) {
+                index += NJS_OBJECT;
+                goto found;
+            }
+
+            prototype = prototype->__proto__;
+
+        } while (prototype != NULL);
+
+        nxt_thread_log_alert("prototype not found");
+
+        return NXT_ERROR;
+    }
+
+found:
+
+    vm->retval = *class_name[index];
+
+    return NXT_OK;
+}
+
+
+static const njs_object_prop_t  njs_object_prototype_properties[] =
+{
+    { njs_getter(njs_object_prototype_get_proto),
+      njs_string("__proto__"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_constructor),
+      njs_string("constructor"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_native_function(njs_object_prototype_value_of, 0),
+      njs_string("valueOf"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_object_prototype_to_string, 0),
+      njs_string("toString"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_object_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_object_prototype_properties,
+                                  nxt_nitems(njs_object_prototype_properties));
+}
diff --git a/njs/njs_object.h b/njs/njs_object.h
new file mode 100644 (file)
index 0000000..eb7f9e0
--- /dev/null
@@ -0,0 +1,69 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_OBJECT_H_INCLUDED_
+#define _NJS_OBJECT_H_INCLUDED_
+
+
+struct njs_object_s {
+    /* A private hash of njs_object_prop_t. */
+    nxt_lvlhsh_t                hash;
+
+    /* A shared hash of njs_object_prop_t. */
+    nxt_lvlhsh_t                shared_hash;
+
+    /* An object __proto__. */
+    njs_object_t                *__proto__;
+};
+
+
+struct njs_object_value_s {
+    njs_object_t                object;
+    njs_value_t                 value;
+};
+
+
+typedef enum {
+    NJS_PROPERTY = 0,
+    NJS_GETTER,
+    NJS_SETTER,
+    NJS_METHOD,
+    NJS_NATIVE_GETTER,
+    NJS_NATIVE_SETTER,
+    NJS_WHITEOUT,
+} njs_ojbect_property_type_t;
+
+
+typedef struct {
+    /* Must be aligned to njs_value_t. */
+    njs_value_t                 value;
+    njs_value_t                 name;
+
+    njs_ojbect_property_type_t  type:8;        /* 3 bits */
+    uint8_t                     enumerable;    /* 1 bit  */
+    uint8_t                     writable;      /* 1 bit  */
+    uint8_t                     configurable;  /* 1 bit  */
+} njs_object_prop_t;
+
+
+njs_object_t *njs_object_alloc(njs_vm_t *vm);
+njs_object_t *njs_object_value_alloc(njs_vm_t *vm, const njs_value_t *value,
+    nxt_uint_t prototype);
+njs_ret_t njs_object_method(njs_vm_t *vm, njs_param_t *param,
+    nxt_lvlhsh_query_t *lhq);
+njs_object_prop_t *njs_object_property(njs_vm_t *vm, njs_object_t *obj,
+    nxt_lvlhsh_query_t *lhq);
+nxt_int_t njs_object_hash_create(njs_vm_t *vm, nxt_lvlhsh_t *hash,
+    const njs_object_prop_t *prop, nxt_uint_t n);
+njs_ret_t njs_object_function(njs_vm_t *vm, njs_param_t *param);
+njs_object_prop_t *njs_object_prop_alloc(njs_vm_t *vm, const njs_value_t *name);
+njs_ret_t njs_object_prototype_create_prototype(njs_vm_t *vm,
+    njs_value_t *value);
+nxt_int_t njs_object_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+nxt_int_t njs_object_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+#endif /* _NJS_OBJECT_H_INCLUDED_ */
diff --git a/njs/njs_object_hash.h b/njs/njs_object_hash.h
new file mode 100644 (file)
index 0000000..6a55458
--- /dev/null
@@ -0,0 +1,88 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_OBJECT_HASH_H_INCLUDED_
+#define _NJS_OBJECT_HASH_H_INCLUDED_
+
+
+#define NJS_CONSTRUCTOR_HASH                                                  \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'c'), 'o'), 'n'), 's'), 't'), 'r'), 'u'), 'c'), 't'), 'o'), 'r')
+
+
+#define NJS_INDEX_HASH                                                        \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'i'), 'n'), 'd'), 'e'), 'x')
+
+
+#define NJS_INPUT_HASH                                                        \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'i'), 'n'), 'p'), 'u'), 't')
+
+
+#define NJS_JOIN_HASH                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'j'), 'o'), 'i'), 'n')
+
+
+#define NJS_PROTOTYPE_HASH                                                    \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'p'), 'r'), 'o'), 't'), 'o'), 't'), 'y'), 'p'), 'e')
+
+
+#define NJS_TO_STRING_HASH                                                    \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        't'), 'o'), 'S'), 't'), 'r'), 'i'), 'n'), 'g')
+
+
+#define NJS_VALUE_OF_HASH                                                     \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(                                                         \
+    nxt_djb_hash_add(NXT_DJB_HASH_INIT,                                       \
+        'v'), 'a'), 'l'), 'u'), 'e'), 'O'), 'f')
+
+
+#endif /* _NJS_OBJECT_HASH_H_INCLUDED_ */
diff --git a/njs/njs_parser.c b/njs/njs_parser.c
new file mode 100644 (file)
index 0000000..01a27f2
--- /dev/null
@@ -0,0 +1,1718 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_utf8.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+/*
+ * The LL(2) parser.  The two lookahead tokens are required because
+ * JavaScript inserts automatically semicolon at the end of line in
+ *    a = 1
+ *    b = 2
+ * whilst
+ *    a = 1
+ *    + b
+ * is treated as a single expiression.
+ */
+
+static njs_token_t njs_parser_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_block(njs_vm_t *vm, njs_parser_t *parser);
+static njs_token_t njs_parser_function_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_return_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_var_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_if_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_while_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_do_while_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_for_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_try_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_block0(njs_vm_t *vm, njs_parser_t *parser);
+static njs_token_t njs_parser_throw_statement(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_grouping_expression(njs_vm_t *vm,
+    njs_parser_t *parser);
+static njs_token_t njs_parser_object(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *obj);
+static njs_token_t njs_parser_array(njs_vm_t *vm,
+    njs_parser_t *parser, njs_parser_node_t *obj);
+
+
+njs_parser_node_t *
+njs_parser(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node, *left;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return NULL;
+    }
+
+    left = NULL;
+
+    for ( ;; ) {
+        token = njs_parser_statement(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+
+            if (vm->exception == NULL) {
+                vm->exception = &njs_exception_syntax_error;
+            }
+
+            nxt_thread_log_error(NXT_LOG_ERR, "ERROR");
+            return NULL;
+        }
+
+        if (parser->node != NULL && parser->node != left) {
+            /*
+             * The statement is not empty block, not just semicolon,
+             * and not a "var" declaration without initialization.
+             */
+            node = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(node == NULL)) {
+                return NULL;
+            }
+
+            node->token = NJS_TOKEN_STATEMENT;
+            node->left = left;
+            node->right = parser->node;
+            parser->node = node;
+
+            left = node;
+        }
+
+        if (token == NJS_TOKEN_CLOSE_BRACE) {
+            parser->lexer->start--;
+            break;
+        }
+
+        if (token == NJS_TOKEN_END) {
+            break;
+        }
+    }
+
+    return parser->node;
+}
+
+
+static njs_token_t
+njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    switch (token) {
+
+    case NJS_TOKEN_FUNCTION:
+        return njs_parser_function_statement(vm, parser);
+
+    case NJS_TOKEN_RETURN:
+        return njs_parser_return_statement(vm, parser);
+
+    case NJS_TOKEN_VAR:
+        return njs_parser_var_statement(vm, parser);
+
+    case NJS_TOKEN_IF:
+        return njs_parser_if_statement(vm, parser);
+
+    case NJS_TOKEN_WHILE:
+        return njs_parser_while_statement(vm, parser);
+
+    case NJS_TOKEN_DO:
+        return njs_parser_do_while_statement(vm, parser);
+
+    case NJS_TOKEN_FOR:
+        return njs_parser_for_statement(vm, parser);
+
+    case NJS_TOKEN_TRY:
+        return njs_parser_try_statement(vm, parser);
+
+    case NJS_TOKEN_THROW:
+        return njs_parser_throw_statement(vm, parser);
+
+    case NJS_TOKEN_SEMICOLON:
+        parser->node = NULL;
+        return njs_parser_token(parser);
+
+    case NJS_TOKEN_OPEN_BRACE:
+        return njs_parser_block(vm, parser);
+
+    case NJS_TOKEN_CLOSE_BRACE:
+        parser->node = NULL;
+        nxt_thread_log_debug("BLOCK END");
+        return token;
+
+    default:
+        token = njs_parser_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        /*
+         * An expression must be terminated by semicolon,
+         * or by a close curly brace or by the end of line.
+         */
+        switch (token) {
+
+        case NJS_TOKEN_SEMICOLON:
+            return njs_parser_token(parser);
+
+        case NJS_TOKEN_CLOSE_BRACE:
+        case NJS_TOKEN_END:
+            return token;
+
+        default:
+            if (parser->lexer->prev_token == NJS_TOKEN_LINE_END) {
+                return token;
+            }
+
+            return NJS_TOKEN_ILLEGAL;
+        }
+    }
+}
+
+
+static njs_token_t
+njs_parser_block(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node, *left;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    left = NULL;
+
+    while (token != NJS_TOKEN_CLOSE_BRACE) {
+        token = njs_parser_statement(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (parser->node != NULL) {
+            /* The statement is not empty block and is not just semicolon. */
+
+            node = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(node == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            node->token = NJS_TOKEN_STATEMENT;
+            node->left = left;
+            node->right = parser->node;
+            parser->node = node;
+
+            left = node;
+        }
+    }
+
+    return njs_parser_token(parser);
+}
+
+
+nxt_inline njs_token_t
+njs_parser_match(njs_parser_t *parser, njs_token_t token,
+    njs_token_t match)
+{
+    if (nxt_fast_path(token == match)) {
+        return njs_parser_token(parser);
+    }
+
+    return NJS_TOKEN_ILLEGAL;
+}
+
+
+static njs_token_t
+njs_parser_function_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_str_t          *name;
+    nxt_uint_t         level;
+    njs_index_t        index;
+    njs_value_t        *value;
+    njs_token_t        token;
+    njs_parser_t       *fn_parser;
+    njs_variable_t     *arg, *var;
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_FUNCTION;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token == NJS_TOKEN_NAME) {
+        nxt_thread_log_debug("function: %V", &parser->lexer->text);
+
+        var = njs_parser_variable(vm, parser, &level);
+        if (nxt_slow_path(var == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        var->state = NJS_VARIABLE_DECLARED;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        index = var->index;
+        value = njs_variable_value(parser, index);
+
+    } else {
+        /* Anonymous function. */
+        value = nxt_vector_add(parser->scope_values, &njs_array_mem_proto,
+                               vm->mem_cache_pool);
+        if (nxt_slow_path(value == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        index = njs_parser_index(parser, parser->scope);
+    }
+
+    value->type = NJS_FUNCTION;
+    node->index = index;
+
+    token = njs_parser_match(parser, token, NJS_TOKEN_OPEN_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    value->data.u.function = njs_function_alloc(vm);
+    if (nxt_slow_path(value->data.u.function == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    fn_parser = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_parser_t));
+    if (nxt_slow_path(fn_parser == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    value->data.u.function->code.script->u.parser = fn_parser;
+
+    fn_parser->lexer = parser->lexer;
+    fn_parser->values_hash = vm->shared->values_hash;
+
+    /* njs_return() size. */
+    fn_parser->code_size = sizeof(njs_vmcode_stop_t);
+
+    fn_parser->arguments = nxt_vector_create(4, sizeof(njs_variable_t),
+                                            &njs_array_mem_proto,
+                                            vm->mem_cache_pool);
+    if (nxt_slow_path(fn_parser->arguments == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    fn_parser->parent = parser;
+
+    vm->parser = fn_parser;
+
+    index = NJS_SCOPE_ARGUMENTS;
+
+    /* A "this" reservation. */
+    index += sizeof(njs_value_t);
+
+    while (token != NJS_TOKEN_CLOSE_PARENTHESIS) {
+
+        if (nxt_slow_path(token != NJS_TOKEN_NAME)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        name = &fn_parser->lexer->text;
+
+        nxt_thread_log_debug("arg: %V", name);
+
+        arg = nxt_vector_add(fn_parser->arguments, &njs_array_mem_proto,
+                            vm->mem_cache_pool);
+        if (nxt_slow_path(arg == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        arg->name_start = nxt_mem_cache_alloc(vm->mem_cache_pool, name->len);
+        if (nxt_slow_path(arg->name_start == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        memcpy(arg->name_start, name->data, name->len);
+        arg->name_len = name->len;
+
+        arg->state = NJS_VARIABLE_DECLARED;
+        arg->index = index;
+        index += sizeof(njs_value_t);
+
+        token = njs_parser_token(fn_parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token == NJS_TOKEN_COMMA) {
+            token = njs_parser_token(fn_parser);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+        }
+    }
+
+    value->data.u.function->code.script->nargs =
+                                   njs_index_size(index) / sizeof(njs_value_t);
+
+    token = njs_parser_token(fn_parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (nxt_slow_path(token != NJS_TOKEN_OPEN_BRACE)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    fn_parser->scope_values = nxt_vector_create(4, sizeof(njs_value_t),
+                                               &njs_array_mem_proto,
+                                               vm->mem_cache_pool);
+    if (nxt_slow_path(fn_parser->scope_values == NULL)) {
+        return NXT_ERROR;
+    }
+
+    fn_parser->scope = NJS_SCOPE_LOCAL;
+
+    token = njs_parser_block(vm, fn_parser);
+
+    vm->parser = parser;
+    node->right = fn_parser->node;
+
+    parser->node = node;
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_function_expression(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_str_t              *name;
+    nxt_uint_t             level;
+    njs_index_t            index;
+    njs_value_t            *value;
+    njs_token_t            token;
+    njs_parser_t           *fn_parser;
+    njs_variable_t         *arg, *var;
+    njs_parser_node_t      *node;
+    njs_function_script_t  *func;
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_FUNCTION_CREATE;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token == NJS_TOKEN_NAME) {
+        nxt_thread_log_debug("function: %V", &parser->lexer->text);
+
+        var = njs_parser_variable(vm, parser, &level);
+        if (nxt_slow_path(var == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+    }
+
+    value = &node->u.value;
+
+    token = njs_parser_match(parser, token, NJS_TOKEN_OPEN_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    func = nxt_mem_cache_zalloc(vm->mem_cache_pool,
+                                sizeof(njs_function_script_t));
+    if (nxt_slow_path(func == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    value->data.u.data = func;
+
+    fn_parser = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_parser_t));
+    if (nxt_slow_path(fn_parser == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    func->u.parser = fn_parser;
+
+    fn_parser->lexer = parser->lexer;
+    fn_parser->values_hash = vm->shared->values_hash;
+
+    /* njs_return() size. */
+    fn_parser->code_size = sizeof(njs_vmcode_stop_t);
+
+    fn_parser->arguments = nxt_vector_create(4, sizeof(njs_variable_t),
+                                            &njs_array_mem_proto,
+                                            vm->mem_cache_pool);
+    if (nxt_slow_path(fn_parser->arguments == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    fn_parser->parent = parser;
+
+    vm->parser = fn_parser;
+
+    index = NJS_SCOPE_ARGUMENTS;
+
+    /* A "this" reservation. */
+    index += sizeof(njs_value_t);
+
+    while (token != NJS_TOKEN_CLOSE_PARENTHESIS) {
+
+        if (nxt_slow_path(token != NJS_TOKEN_NAME)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        name = &fn_parser->lexer->text;
+
+        nxt_thread_log_debug("arg: %V", name);
+
+        arg = nxt_vector_add(fn_parser->arguments, &njs_array_mem_proto,
+                            vm->mem_cache_pool);
+        if (nxt_slow_path(arg == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        arg->name_start = nxt_mem_cache_alloc(vm->mem_cache_pool, name->len);
+        if (nxt_slow_path(arg->name_start == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        memcpy(arg->name_start, name->data, name->len);
+        arg->name_len = name->len;
+
+        arg->state = NJS_VARIABLE_DECLARED;
+        arg->index = index;
+        index += sizeof(njs_value_t);
+
+        token = njs_parser_token(fn_parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token == NJS_TOKEN_COMMA) {
+            token = njs_parser_token(fn_parser);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+        }
+    }
+
+    func->nargs = njs_index_size(index) / sizeof(njs_value_t);
+
+    token = njs_parser_token(fn_parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (nxt_slow_path(token != NJS_TOKEN_OPEN_BRACE)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    fn_parser->scope_values = nxt_vector_create(4, sizeof(njs_value_t),
+                                               &njs_array_mem_proto,
+                                               vm->mem_cache_pool);
+    if (nxt_slow_path(fn_parser->scope_values == NULL)) {
+        return NXT_ERROR;
+    }
+
+    fn_parser->scope = NJS_SCOPE_LOCAL;
+
+    token = njs_parser_block(vm, fn_parser);
+
+    vm->parser = parser;
+    node->right = fn_parser->node;
+
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_function_create_t);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_return_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_RETURN;
+
+    token = njs_parser_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (parser->node->token == NJS_TOKEN_FUNCTION) {
+        /* TODO: test closure */
+    }
+
+    node->right = parser->node;
+    parser->node = node;
+
+    parser->code_size += sizeof(njs_vmcode_stop_t);
+
+    if (token != NJS_TOKEN_SEMICOLON) {
+        return token;
+    }
+
+    return njs_parser_token(parser);
+}
+
+
+static njs_token_t
+njs_parser_var_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_bool_t         first;
+    nxt_uint_t         level;
+    njs_token_t        token;
+    njs_variable_t     *var;
+    njs_parser_node_t  *left, *stmt, *name, *assign;
+
+    left = NULL;
+
+    do {
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token != NJS_TOKEN_NAME) {
+            return token;
+        }
+
+        nxt_thread_log_debug("JS: %V", &parser->lexer->text);
+
+        var = njs_parser_variable(vm, parser, &level);
+        if (nxt_slow_path(var == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        first = (var->state == NJS_VARIABLE_CREATED);
+
+        var->state = NJS_VARIABLE_DECLARED;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token == NJS_TOKEN_ASSIGNMENT) {
+
+            token = njs_parser_token(parser);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+
+            token = njs_parser_var_expression(vm, parser, token);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+
+            name = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(name == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            name->token = NJS_TOKEN_NAME;
+            name->lvalue = NJS_LVALUE_ENABLED;
+            name->u.variable = var;
+
+            if (first) {
+                name->state = NJS_VARIABLE_FIRST_ASSIGNMENT;
+            }
+
+            assign = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(assign == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            assign->token = NJS_TOKEN_ASSIGNMENT;
+            assign->u.operation = njs_vmcode_move;
+            assign->left = name;
+            assign->right = parser->node;
+
+            stmt = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(stmt == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            stmt->token = NJS_TOKEN_STATEMENT;
+            stmt->left = left;
+            stmt->right = assign;
+            parser->node = stmt;
+            parser->code_size += sizeof(njs_vmcode_2addr_t);
+
+            left = stmt;
+        }
+
+    } while (token == NJS_TOKEN_COMMA);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_if_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node, *cond, *stmt;
+
+    parser->branch = 1;
+
+    token = njs_parser_grouping_expression(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    cond = parser->node;
+
+    token = njs_parser_statement(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token == NJS_TOKEN_ELSE) {
+
+        stmt = parser->node;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_statement(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = NJS_TOKEN_ELSE;
+        node->left = stmt;
+        node->right = parser->node;
+        parser->node = node;
+        parser->code_size += sizeof(njs_vmcode_jump_t);
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_IF;
+    node->left = cond;
+    node->right = parser->node;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_cond_jump_t);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_while_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node, *cond;
+
+    parser->branch = 1;
+
+    token = njs_parser_grouping_expression(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    cond = parser->node;
+
+    token = njs_parser_statement(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_WHILE;
+    node->left = parser->node;
+    node->right = cond;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_jump_t)
+                         + sizeof(njs_vmcode_cond_jump_t);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_do_while_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node, *stmt;
+
+    parser->branch = 1;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_statement(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    stmt = parser->node;
+
+    if (nxt_slow_path(token != NJS_TOKEN_WHILE)) {
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    token = njs_parser_grouping_expression(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    node->token = NJS_TOKEN_DO;
+    node->left = stmt;
+    node->right = parser->node;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_cond_jump_t);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_for_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node, *init, *condition, *update, *cond, *body;
+
+    parser->branch = 1;
+
+    init = NULL;
+    condition = NULL;
+    update = NULL;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_match(parser, token,
+                                  NJS_TOKEN_OPEN_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token != NJS_TOKEN_SEMICOLON) {
+
+        token = njs_parser_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        init = parser->node;
+
+        if (init->token == NJS_TOKEN_IN) {
+            return njs_parser_for_in_statement(vm, parser, token);
+        }
+    }
+
+    token = njs_parser_match(parser, token, NJS_TOKEN_SEMICOLON);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token != NJS_TOKEN_SEMICOLON) {
+
+        token = njs_parser_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        condition = parser->node;
+    }
+
+    token = njs_parser_match(parser, token, NJS_TOKEN_SEMICOLON);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (token != NJS_TOKEN_CLOSE_PARENTHESIS) {
+
+        token = njs_parser_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        update = parser->node;
+    }
+
+    token = njs_parser_match(parser, token,
+                                  NJS_TOKEN_CLOSE_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_statement(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    cond = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(cond == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    body = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(body == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_FOR;
+    node->left = init;
+    node->right = cond;
+
+    cond->token = NJS_TOKEN_FOR_CONDITION;
+    cond->left = condition;
+    cond->right = body;
+
+    body->token = NJS_TOKEN_FOR_BODY;
+    body->left = parser->node;
+    body->right = update;
+
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_jump_t)
+                         + sizeof(njs_vmcode_cond_jump_t);
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    njs_parser_node_t  *node;
+
+    if (parser->node->left->token != NJS_TOKEN_NAME) {
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    parser->node->left->u.variable->state = NJS_VARIABLE_DECLARED;
+    parser->node->token = NJS_TOKEN_PROPERTY_EACH;
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_FOR_IN;
+    node->left = parser->node;
+
+    token = njs_parser_match(parser, token,
+                                  NJS_TOKEN_CLOSE_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_statement(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node->right = parser->node;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_prop_start_t)
+                         + sizeof(njs_vmcode_prop_each_t);
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    nxt_uint_t         level;
+    njs_token_t        token;
+    njs_variable_t     *var;
+    njs_parser_node_t  *node, *try, *catch;
+
+    parser->branch = 1;
+
+    token = njs_parser_block0(vm, parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    try = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(try == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    try->token = NJS_TOKEN_TRY;
+    try->left = parser->node;
+    parser->code_size += sizeof(njs_vmcode_try_start_t)
+                         + sizeof(njs_vmcode_try_end_t);
+
+    if (token == NJS_TOKEN_CATCH) {
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_match(parser, token,
+                                      NJS_TOKEN_OPEN_PARENTHESIS);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token != NJS_TOKEN_NAME) {
+            return NJS_TOKEN_ILLEGAL;
+        }
+
+        nxt_thread_log_debug("CATCH: %V", &parser->lexer->text);
+
+        catch = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(catch == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        catch->token = NJS_TOKEN_CATCH;
+        try->right = catch;
+
+        var = njs_parser_variable(vm, parser, &level);
+        if (nxt_slow_path(var == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        var->state = NJS_VARIABLE_DECLARED;
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = NJS_TOKEN_NAME;
+        node->u.variable = var;
+
+        catch->left = node;
+
+        parser->code_size += sizeof(njs_vmcode_catch_t);
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (nxt_slow_path(token != NJS_TOKEN_CLOSE_PARENTHESIS)) {
+            return token;
+        }
+
+        token = njs_parser_block0(vm, parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        catch->right = parser->node;
+
+        /* TODO: remove variable from scope. */
+    }
+
+    if (token == NJS_TOKEN_FINALLY) {
+
+        token = njs_parser_block0(vm, parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = NJS_TOKEN_FINALLY;
+        node->right = parser->node;
+
+        if (try->right != NULL) {
+            node->left = try->right;
+            parser->code_size += sizeof(njs_vmcode_try_end_t);
+        }
+
+        try->right = node;
+        parser->code_size += sizeof(njs_vmcode_catch_t)
+                             + sizeof(njs_vmcode_finally_t);
+    }
+
+    if (try->right == NULL) {
+        /* TODO: message */
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    parser->node = try;
+
+    return token;
+
+}
+
+
+static njs_token_t
+njs_parser_block0(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t  token;
+
+    token = njs_parser_token(parser);
+
+    if (nxt_fast_path(token == NJS_TOKEN_OPEN_BRACE)) {
+        parser->node = NULL;
+        return njs_parser_block(vm, parser);
+    }
+
+    return NJS_TOKEN_ILLEGAL;
+}
+
+
+static njs_token_t
+njs_parser_throw_statement(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *node;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_THROW;
+
+    token = njs_parser_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    node->right = parser->node;
+    parser->node = node;
+
+    parser->code_size += sizeof(njs_vmcode_throw_t);
+
+    return token;
+}
+
+
+static njs_token_t
+njs_parser_grouping_expression(njs_vm_t *vm, njs_parser_t *parser)
+{
+    njs_token_t  token;
+
+    token = njs_parser_token(parser);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_match(parser, token,
+                                  NJS_TOKEN_OPEN_PARENTHESIS);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    token = njs_parser_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    return njs_parser_match(parser, token,
+                                 NJS_TOKEN_CLOSE_PARENTHESIS);
+}
+
+
+njs_token_t
+njs_parser_token(njs_parser_t *parser)
+{
+    njs_token_t  token;
+
+    do {
+        token = njs_lexer_token(parser->lexer);
+
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+    } while (nxt_slow_path(token == NJS_TOKEN_LINE_END));
+
+    return token;
+}
+
+
+njs_token_t
+njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    double                num;
+    nxt_int_t             ret;
+    nxt_uint_t            level;
+    njs_extern_t          *ext;
+    njs_variable_t        *var;
+    njs_parser_node_t     *node;
+    njs_regexp_flags_t    flags;
+    njs_regexp_pattern_t  *pattern;
+
+    if (token == NJS_TOKEN_OPEN_PARENTHESIS) {
+
+        token = njs_lexer_token(parser->lexer);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        return njs_parser_match(parser, token,
+                                     NJS_TOKEN_CLOSE_PARENTHESIS);
+    }
+
+    if (token == NJS_TOKEN_FUNCTION) {
+        return njs_parser_function_expression(vm, parser);
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = token;
+
+    switch (token) {
+
+    case NJS_TOKEN_NAME:
+        nxt_thread_log_debug("JS: %V", &parser->lexer->text);
+
+        ext = njs_parser_external(vm, parser);
+
+        if (ext != NULL) {
+            node->token = NJS_TOKEN_EXTERNAL;
+            node->u.value.type = NJS_EXTERNAL;
+            node->u.value.data.truth = 1;
+            node->index = (njs_index_t) ext;
+            break;
+        }
+
+        var = njs_parser_variable(vm, parser, &level);
+        if (nxt_slow_path(var == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        switch (var->state) {
+
+        case NJS_VARIABLE_CREATED:
+            var->state = NJS_VARIABLE_PENDING;
+            parser->code_size += sizeof(njs_vmcode_1addr_t);
+            break;
+
+        case NJS_VARIABLE_PENDING:
+            var->state = NJS_VARIABLE_USED;
+            parser->code_size += sizeof(njs_vmcode_1addr_t);
+            break;
+
+        case NJS_VARIABLE_USED:
+            parser->code_size += sizeof(njs_vmcode_1addr_t);
+            break;
+
+        case NJS_VARIABLE_SET:
+        case NJS_VARIABLE_DECLARED:
+            break;
+        }
+
+        node->lvalue = NJS_LVALUE_ENABLED;
+        node->u.variable = var;
+        break;
+
+    case NJS_TOKEN_OPEN_BRACE:
+        node->token = NJS_TOKEN_OBJECT_CREATE;
+
+        nxt_thread_log_debug("JS: OBJECT");
+
+        parser->node = node;
+
+        token = njs_parser_object(vm, parser, node);
+
+        if (parser->node != node) {
+            /* The object is not empty. */
+            node->left = parser->node;
+            parser->node = node;
+        }
+
+        parser->code_size += sizeof(njs_vmcode_1addr_t);
+
+        return token;
+
+    case NJS_TOKEN_OPEN_BRACKET:
+        node->token = NJS_TOKEN_ARRAY_CREATE;
+
+        nxt_thread_log_debug("JS: ARRAY");
+
+        parser->node = node;
+
+        token = njs_parser_array(vm, parser, node);
+
+        if (parser->node != node) {
+            /* The array is not empty. */
+            node->left = parser->node;
+            parser->node = node;
+        }
+
+        parser->code_size += sizeof(njs_vmcode_2addr_t);
+
+        return token;
+
+    case NJS_TOKEN_DIVISION:
+        token = njs_lexer_regexp(parser->lexer, &flags);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node->token = token;
+
+        nxt_thread_log_debug("REGEX: '%V'", &parser->lexer->text);
+
+        pattern = njs_regexp_pattern_create(vm, &parser->lexer->text, flags);
+        if (nxt_slow_path(pattern == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->u.value.data.u.data = pattern;
+        parser->code_size += sizeof(njs_vmcode_regexp_t);
+
+        break;
+
+    case NJS_TOKEN_STRING:
+        nxt_thread_log_debug("JS: '%V'", &parser->lexer->text);
+
+        ret = njs_parser_string_create(vm, &node->u.value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        break;
+
+    case NJS_TOKEN_NUMBER:
+        nxt_thread_log_debug("JS: %f", parser->lexer->number);
+
+        num = parser->lexer->number;
+        node->u.value.data.u.number = num;
+        node->u.value.type = NJS_NUMBER;
+        node->u.value.data.truth = njs_is_number_true(num);
+
+        break;
+
+    case NJS_TOKEN_BOOLEAN:
+        nxt_thread_log_debug("JS: boolean: %V", &parser->lexer->text);
+
+        if (parser->lexer->number == 0) {
+            node->u.value = njs_value_false;
+
+        } else {
+            node->u.value = njs_value_true;
+        }
+
+        break;
+
+    case NJS_TOKEN_NULL:
+        nxt_thread_log_debug("JS: null");
+
+        node->u.value = njs_value_null;
+        break;
+
+    case NJS_TOKEN_UNDEFINED:
+        nxt_thread_log_debug("JS: undefined");
+
+        node->u.value = njs_value_void;
+        break;
+
+    case NJS_TOKEN_THIS:
+        nxt_thread_log_debug("JS: this");
+
+        node->index = NJS_INDEX_THIS;
+        break;
+
+    case NJS_TOKEN_OBJECT_FUNCTION:
+        node->index = NJS_INDEX_OBJECT;
+        break;
+
+    case NJS_TOKEN_ARRAY_FUNCTION:
+        node->index = NJS_INDEX_ARRAY;
+        break;
+
+    case NJS_TOKEN_BOOLEAN_FUNCTION:
+        node->index = NJS_INDEX_BOOLEAN;
+        break;
+
+    case NJS_TOKEN_NUMBER_FUNCTION:
+        node->index = NJS_INDEX_NUMBER;
+        break;
+
+    case NJS_TOKEN_STRING_FUNCTION:
+        node->index = NJS_INDEX_STRING;
+        break;
+
+    case NJS_TOKEN_FUNCTION_FUNCTION:
+        node->index = NJS_INDEX_FUNCTION;
+        break;
+
+    case NJS_TOKEN_REGEXP_FUNCTION:
+        node->index = NJS_INDEX_REGEXP;
+        break;
+
+    case NJS_TOKEN_EVAL:
+        node->index = NJS_INDEX_EVAL;
+        break;
+
+    default:
+        vm->exception = &njs_exception_syntax_error;
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    parser->node = node;
+
+    return njs_lexer_token(parser->lexer);
+}
+
+
+static njs_token_t
+njs_parser_object(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *obj)
+{
+    njs_token_t        token;
+    njs_parser_node_t  *stmt, *assign, *object, *propref, *left;
+
+    left = NULL;
+
+    for ( ;; ) {
+        token = njs_parser_token(parser);
+
+        switch (token) {
+
+        case NJS_TOKEN_CLOSE_BRACE:
+            return njs_parser_token(parser);
+
+        case NJS_TOKEN_NAME:
+            token = njs_parser_property_name(vm, parser, token);
+            break;
+
+        default:
+            token = njs_parser_terminal(vm, parser, token);
+            break;
+        }
+
+        object = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(object == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        object->token = NJS_TOKEN_OBJECT_LITERAL;
+        object->u.object = obj;
+
+        propref = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(propref == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        propref->token = NJS_TOKEN_PROPERTY;
+        propref->left = object;
+        propref->right = parser->node;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_match(parser, token, NJS_TOKEN_COLON);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_conditional_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        assign = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(assign == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        assign->token = NJS_TOKEN_ASSIGNMENT;
+        assign->u.operation = njs_vmcode_move;
+        assign->left = propref;
+        assign->right = parser->node;
+
+        stmt = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(stmt == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        stmt->token = NJS_TOKEN_STATEMENT;
+        stmt->left = left;
+        stmt->right = assign;
+
+        parser->code_size += sizeof(njs_vmcode_2addr_t);
+        parser->node = stmt;
+
+        left = stmt;
+
+        if (token == NJS_TOKEN_CLOSE_BRACE) {
+            return njs_parser_token(parser);
+        }
+
+        if (nxt_slow_path(token != NJS_TOKEN_COMMA)) {
+            return NJS_TOKEN_ILLEGAL;
+        }
+    }
+}
+
+
+static njs_token_t
+njs_parser_array(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *obj)
+{
+    nxt_uint_t         index;
+    njs_token_t        token;
+    njs_parser_node_t  *stmt, *assign, *object, *propref, *left, *node;
+
+    index = 0;
+    left = NULL;
+
+    for ( ;; ) {
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token == NJS_TOKEN_CLOSE_BRACKET) {
+            break;
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = NJS_TOKEN_NUMBER;
+        node->u.value.data.u.number = index;
+        node->u.value.type = NJS_NUMBER;
+        node->u.value.data.truth = (index != 0);
+        index++;
+
+        object = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(object == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        object->token = NJS_TOKEN_OBJECT_LITERAL;
+        object->u.object = obj;
+
+        propref = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(propref == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        propref->token = NJS_TOKEN_PROPERTY;
+        propref->left = object;
+        propref->right = node;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        token = njs_parser_conditional_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        assign = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(assign == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        assign->token = NJS_TOKEN_ASSIGNMENT;
+        assign->u.operation = njs_vmcode_move;
+        assign->left = propref;
+        assign->right = parser->node;
+
+        stmt = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(stmt == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        stmt->token = NJS_TOKEN_STATEMENT;
+        stmt->left = left;
+        stmt->right = assign;
+
+        parser->code_size += sizeof(njs_vmcode_2addr_t);
+        parser->node = stmt;
+
+        left = stmt;
+
+        if (token == NJS_TOKEN_CLOSE_BRACKET) {
+            break;
+        }
+
+        if (nxt_slow_path(token != NJS_TOKEN_COMMA)) {
+            return NJS_TOKEN_ILLEGAL;
+        }
+    }
+
+    obj->u.length = index;
+
+    return njs_parser_token(parser);
+}
+
+
+nxt_int_t
+njs_parser_string_create(njs_vm_t *vm, njs_value_t *value)
+{
+    u_char     *p;
+    ssize_t    length;
+    nxt_str_t  *src;
+
+    src = &vm->parser->lexer->text;
+
+    length = nxt_utf8_length(src->data, src->len);
+
+    if (nxt_slow_path(length < 0)) {
+        length = 0;
+    }
+
+    p = njs_string_alloc(vm, value, src->len, length);
+
+    if (nxt_fast_path(p != NULL)) {
+        memcpy(p, src->data, src->len);
+
+        if (length > NJS_STRING_MAP_OFFSET && (size_t) length != src->len) {
+            njs_string_offset_map_init(p, src->len);
+        }
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_index_t
+njs_parser_index(njs_parser_t *parser, uint32_t scope)
+{
+    nxt_uint_t   n;
+    njs_index_t  index;
+
+    /* Skip absolute scope. */
+    n = scope - NJS_INDEX_CACHE;
+
+    index = parser->index[n];
+    parser->index[n] += sizeof(njs_value_t);
+
+    index |= scope;
+
+    nxt_thread_log_debug("GET %p", index);
+
+    return index;
+}
+
+
+nxt_bool_t
+njs_parser_has_side_effect(njs_parser_node_t *node)
+{
+    nxt_bool_t  side_effect;
+
+    if (node == NULL) {
+        return 0;
+    }
+
+    if (node->token >= NJS_TOKEN_ASSIGNMENT
+        && node->token <= NJS_TOKEN_LAST_ASSIGNMENT)
+    {
+        return 1;
+    }
+
+    if (node->token == NJS_TOKEN_FUNCTION_CALL
+        || node->token == NJS_TOKEN_METHOD_CALL)
+    {
+        return 1;
+    }
+
+    side_effect = njs_parser_has_side_effect(node->left);
+
+    if (nxt_fast_path(!side_effect)) {
+        return njs_parser_has_side_effect(node->right);
+    }
+
+    return side_effect;
+}
diff --git a/njs/njs_parser.h b/njs/njs_parser.h
new file mode 100644 (file)
index 0000000..ab07e95
--- /dev/null
@@ -0,0 +1,311 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_PARSER_H_INCLUDED_
+#define _NJS_PARSER_H_INCLUDED_
+
+
+typedef enum {
+    NJS_TOKEN_AGAIN = -2,
+    NJS_TOKEN_ERROR = -1,
+    NJS_TOKEN_ILLEGAL = 0,
+
+    NJS_TOKEN_END,
+    NJS_TOKEN_SPACE,
+    NJS_TOKEN_LINE_END,
+
+    NJS_TOKEN_DOUBLE_QUOTE,
+    NJS_TOKEN_SINGLE_QUOTE,
+
+    NJS_TOKEN_OPEN_PARENTHESIS,
+    NJS_TOKEN_CLOSE_PARENTHESIS,
+    NJS_TOKEN_OPEN_BRACKET,
+    NJS_TOKEN_CLOSE_BRACKET,
+    NJS_TOKEN_OPEN_BRACE,
+    NJS_TOKEN_CLOSE_BRACE,
+
+    NJS_TOKEN_COMMA,
+    NJS_TOKEN_DOT,
+    NJS_TOKEN_SEMICOLON,
+
+#define NJS_TOKEN_FIRST_OPERATOR    NJS_TOKEN_COLON
+
+    NJS_TOKEN_COLON,
+    NJS_TOKEN_CONDITIONAL,
+
+    NJS_TOKEN_ASSIGNMENT,
+    NJS_TOKEN_ADDITION_ASSIGNMENT,
+    NJS_TOKEN_SUBSTRACTION_ASSIGNMENT,
+    NJS_TOKEN_MULTIPLICATION_ASSIGNMENT,
+    NJS_TOKEN_DIVISION_ASSIGNMENT,
+    NJS_TOKEN_REMAINDER_ASSIGNMENT,
+    NJS_TOKEN_LEFT_SHIFT_ASSIGNMENT,
+    NJS_TOKEN_RIGHT_SHIFT_ASSIGNMENT,
+    NJS_TOKEN_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT,
+    NJS_TOKEN_BITWISE_OR_ASSIGNMENT,
+    NJS_TOKEN_BITWISE_XOR_ASSIGNMENT,
+    NJS_TOKEN_BITWISE_AND_ASSIGNMENT,
+
+#define NJS_TOKEN_LAST_ASSIGNMENT   NJS_TOKEN_BITWISE_AND_ASSIGNMENT
+
+    NJS_TOKEN_EQUAL,
+    NJS_TOKEN_STRICT_EQUAL,
+    NJS_TOKEN_NOT_EQUAL,
+    NJS_TOKEN_STRICT_NOT_EQUAL,
+
+    NJS_TOKEN_ADDITION,
+    NJS_TOKEN_UNARY_PLUS,
+    NJS_TOKEN_INCREMENT,
+    NJS_TOKEN_POST_INCREMENT,
+
+    NJS_TOKEN_SUBSTRACTION,
+    NJS_TOKEN_UNARY_NEGATION,
+    NJS_TOKEN_DECREMENT,
+    NJS_TOKEN_POST_DECREMENT,
+
+    NJS_TOKEN_MULTIPLICATION,
+
+    NJS_TOKEN_DIVISION,
+
+    NJS_TOKEN_REMAINDER,
+
+    NJS_TOKEN_LESS,
+    NJS_TOKEN_LESS_OR_EQUAL,
+    NJS_TOKEN_LEFT_SHIFT,
+
+    NJS_TOKEN_GREATER,
+    NJS_TOKEN_GREATER_OR_EQUAL,
+    NJS_TOKEN_RIGHT_SHIFT,
+    NJS_TOKEN_UNSIGNED_RIGHT_SHIFT,
+
+    NJS_TOKEN_BITWISE_OR,
+    NJS_TOKEN_LOGICAL_OR,
+
+    NJS_TOKEN_BITWISE_XOR,
+
+    NJS_TOKEN_BITWISE_AND,
+    NJS_TOKEN_LOGICAL_AND,
+
+    NJS_TOKEN_BITWISE_NOT,
+    NJS_TOKEN_LOGICAL_NOT,
+
+    NJS_TOKEN_IN,
+    NJS_TOKEN_INSTANCEOF,
+    NJS_TOKEN_TYPEOF,
+    NJS_TOKEN_VOID,
+    NJS_TOKEN_NEW,
+    NJS_TOKEN_DELETE,
+    NJS_TOKEN_YIELD,
+
+#define NJS_TOKEN_LAST_OPERATOR   NJS_TOKEN_YIELD
+
+    NJS_TOKEN_DIGIT,
+    NJS_TOKEN_LETTER,
+
+#define NJS_TOKEN_FIRST_CONST     NJS_TOKEN_UNDEFINED
+
+    NJS_TOKEN_UNDEFINED,
+    NJS_TOKEN_NULL,
+    NJS_TOKEN_NUMBER,
+    NJS_TOKEN_BOOLEAN,
+    NJS_TOKEN_STRING,
+
+#define NJS_TOKEN_LAST_CONST      NJS_TOKEN_STRING
+
+    NJS_TOKEN_NAME,
+
+    NJS_TOKEN_OBJECT_CREATE,
+    NJS_TOKEN_OBJECT_LITERAL,
+    NJS_TOKEN_PROPERTY,
+    NJS_TOKEN_PROPERTY_DELETE,
+
+    NJS_TOKEN_ARRAY_CREATE,
+
+    NJS_TOKEN_FUNCTION_CREATE,
+    NJS_TOKEN_FUNCTION,
+    NJS_TOKEN_FUNCTION_CALL,
+    NJS_TOKEN_METHOD_CALL,
+    NJS_TOKEN_ARGUMENT,
+    NJS_TOKEN_RETURN,
+
+    NJS_TOKEN_REGEXP_LITERAL,
+
+    NJS_TOKEN_EXTERNAL,
+
+    NJS_TOKEN_STATEMENT,
+    NJS_TOKEN_VAR,
+    NJS_TOKEN_IF,
+    NJS_TOKEN_ELSE,
+    NJS_TOKEN_WHILE,
+    NJS_TOKEN_DO,
+    NJS_TOKEN_FOR,
+    NJS_TOKEN_FOR_CONDITION,
+    NJS_TOKEN_FOR_BODY,
+    NJS_TOKEN_FOR_IN,
+    NJS_TOKEN_PROPERTY_EACH,
+    NJS_TOKEN_BREAK,
+    NJS_TOKEN_CONTINUE,
+    NJS_TOKEN_SWITCH,
+    NJS_TOKEN_CASE,
+    NJS_TOKEN_DEFAULT,
+    NJS_TOKEN_WITH,
+    NJS_TOKEN_TRY,
+    NJS_TOKEN_CATCH,
+    NJS_TOKEN_FINALLY,
+    NJS_TOKEN_THROW,
+
+    NJS_TOKEN_THIS,
+
+    NJS_TOKEN_OBJECT_FUNCTION,
+    NJS_TOKEN_ARRAY_FUNCTION,
+    NJS_TOKEN_FUNCTION_FUNCTION,
+    NJS_TOKEN_REGEXP_FUNCTION,
+    NJS_TOKEN_BOOLEAN_FUNCTION,
+    NJS_TOKEN_NUMBER_FUNCTION,
+    NJS_TOKEN_STRING_FUNCTION,
+    NJS_TOKEN_EVAL,
+
+    NJS_TOKEN_RESERVED,
+} njs_token_t;
+
+
+typedef struct {
+    njs_token_t                     token:8;
+    njs_token_t                     prev_token:8;
+    uint32_t                        key_hash;
+
+    nxt_str_t                       text;
+    double                          number;
+
+    nxt_lvlhsh_t                    keywords_hash;
+
+    u_char                          *start;
+    u_char                          *end;
+} njs_lexer_t;
+
+
+typedef enum {
+    NJS_VARIABLE_NORMAL = 0,
+    NJS_VARIABLE_FIRST_ASSIGNMENT,
+    NJS_VARIABLE_ASSIGNMENT,
+    NJS_VARIABLE_TYPEOF,
+} njs_variable_node_state_t;
+
+
+typedef enum {
+    NJS_LVALUE_NONE = 0,
+    NJS_LVALUE_ENABLED,
+    NJS_LVALUE_ASSIGNED,
+} njs_lvalue_state_t;
+
+
+typedef struct njs_parser_node_s    njs_parser_node_t;
+
+struct njs_parser_node_s {
+    njs_token_t                     token:8;
+    njs_variable_node_state_t       state:8;    /* 2 bits */
+    njs_lvalue_state_t              lvalue:2;   /* 2 bits */
+    uint8_t                         ctor:1;     /* 1 bit  */
+    uint8_t                         temporary;  /* 1 bit  */
+
+    union {
+        uint32_t                    length;
+        njs_vmcode_operation_t      operation;
+        njs_value_t                 value;
+        njs_variable_t              *variable;
+        njs_parser_node_t           *object;
+        njs_extern_t                *external;
+    } u;
+
+    njs_index_t                     index;
+
+    njs_parser_node_t               *left;
+    njs_parser_node_t               *right;
+    njs_parser_node_t               *dest;
+};
+
+
+#define                                                                       \
+njs_parser_node_alloc(vm)                                                     \
+    nxt_mem_cache_zalloc((vm)->mem_cache_pool, sizeof(njs_parser_node_t))
+
+
+struct njs_parser_s {
+    njs_lexer_t                     *lexer;
+    njs_parser_node_t               *node;
+
+    /* Vector of njs_variable_t. */
+    nxt_vector_t                     *arguments;
+
+    nxt_lvlhsh_t                     variables_hash;
+    nxt_lvlhsh_t                     values_hash;
+    nxt_lvlhsh_t                     functions_hash;
+
+    nxt_vector_t                     *index_cache;
+    njs_index_t                     index[NJS_SCOPES - NJS_INDEX_CACHE];
+
+    nxt_vector_t                     *scope_values;
+
+    uint8_t                         scope;        /* 4 bits */
+    uint8_t                         branch;       /* 1 bit */
+
+    size_t                          code_size;
+
+    /* Generator. */
+
+    njs_value_t                     *local_scope;
+    size_t                          scope_size;
+    size_t                          scope_offset;
+
+    uint32_t                        nesting_arguments;
+    uint32_t                        nesting_arguments_size;
+    uint32_t                        method_arguments_size;
+
+    u_char                          *code_start;
+    u_char                          *code_last;
+
+    njs_parser_t                    *parent;
+};
+
+
+njs_token_t njs_lexer_token(njs_lexer_t *lexer);
+njs_token_t njs_lexer_regexp(njs_lexer_t *lexer, njs_regexp_flags_t *flags);
+nxt_int_t njs_lexer_keywords_init(nxt_mem_cache_pool_t *mcp,
+    nxt_lvlhsh_t *hash);
+njs_token_t njs_lexer_keyword(njs_lexer_t *lexer);
+
+njs_extern_t *njs_parser_external(njs_vm_t *vm, njs_parser_t *parser);
+
+njs_parser_node_t *njs_parser(njs_vm_t *vm, njs_parser_t *parser);
+njs_parser_node_t *njs_nonrecursive_parser(njs_vm_t *vm, njs_parser_t *parser);
+njs_token_t njs_parser_arguments(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *parent);
+njs_token_t njs_parser_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token);
+njs_token_t njs_parser_var_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token);
+njs_token_t njs_parser_conditional_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+njs_token_t njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token);
+njs_token_t njs_parser_property_name(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token);
+njs_token_t njs_parser_token(njs_parser_t *parser);
+nxt_int_t njs_parser_string_create(njs_vm_t *vm, njs_value_t *value);
+njs_index_t njs_parser_index(njs_parser_t *parser, uint32_t scope);
+nxt_bool_t njs_parser_has_side_effect(njs_parser_node_t *node);
+nxt_int_t njs_generate_scope(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *node, njs_vmcode_operation_t last);
+
+
+#define                                                                       \
+njs_generate_code(parser, type, code)                                         \
+    do {                                                                      \
+        code = (type *) parser->code_last; parser->code_last += sizeof(type); \
+    } while (0)
+
+
+#endif /* _NJS_PARSER_H_INCLUDED_ */
diff --git a/njs/njs_parser_expression.c b/njs/njs_parser_expression.c
new file mode 100644 (file)
index 0000000..290ede7
--- /dev/null
@@ -0,0 +1,1126 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+typedef struct {
+    njs_token_t                        token;
+    njs_vmcode_operation_t             operation;
+} njs_parser_operation_t;
+
+
+typedef struct njs_parser_expression_s
+    njs_parser_expression_t;
+
+struct njs_parser_expression_s {
+    njs_token_t                        (*next)(njs_vm_t *,
+                                           njs_parser_t *,
+                                           const njs_parser_expression_t *,
+                                           njs_token_t);
+    const njs_parser_expression_t  *expression;
+    nxt_uint_t                          count;
+
+#if (NXT_SUNC)
+    /*
+     * SunC supports C99 flexible array members but does not allow
+     * static struct's initialization with arbitrary number of members.
+     */
+    njs_parser_operation_t         op[6];
+#else
+    njs_parser_operation_t         op[];
+#endif
+};
+
+
+static njs_token_t njs_parser_assignment_expression(njs_vm_t *vm,
+    njs_parser_t *parser, const njs_parser_expression_t *expr,
+    njs_token_t token);
+static njs_token_t njs_parser_binary_expression(njs_vm_t *vm,
+    njs_parser_t *parser, const njs_parser_expression_t *expr,
+    njs_token_t token);
+static njs_token_t njs_parser_unary_expression(njs_vm_t *vm,
+    njs_parser_t *parser, const njs_parser_expression_t *expr,
+    njs_token_t token);
+static njs_token_t njs_parser_inc_dec_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_post_inc_dec_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_call_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_property_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+static njs_token_t njs_parser_property_brackets(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token);
+
+
+static const njs_parser_expression_t
+    njs_parser_factor_expression =
+{
+    njs_parser_unary_expression,
+    NULL,
+    3, {
+        { NJS_TOKEN_MULTIPLICATION, njs_vmcode_multiplication },
+        { NJS_TOKEN_DIVISION, njs_vmcode_division },
+        { NJS_TOKEN_REMAINDER, njs_vmcode_remainder },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_addition_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_factor_expression,
+    2, {
+        { NJS_TOKEN_ADDITION, njs_vmcode_addition },
+        { NJS_TOKEN_SUBSTRACTION, njs_vmcode_substraction },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_bitwise_shift_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_addition_expression,
+    3, {
+        { NJS_TOKEN_LEFT_SHIFT, njs_vmcode_left_shift },
+        { NJS_TOKEN_RIGHT_SHIFT, njs_vmcode_right_shift },
+        { NJS_TOKEN_UNSIGNED_RIGHT_SHIFT,
+          njs_vmcode_unsigned_right_shift },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_relational_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_bitwise_shift_expression,
+    6, {
+        { NJS_TOKEN_LESS, njs_vmcode_less },
+        { NJS_TOKEN_LESS_OR_EQUAL, njs_vmcode_less_or_equal },
+        { NJS_TOKEN_GREATER, njs_vmcode_greater },
+        { NJS_TOKEN_GREATER_OR_EQUAL, njs_vmcode_greater_or_equal },
+        { NJS_TOKEN_IN, njs_vmcode_property_in },
+        { NJS_TOKEN_INSTANCEOF, njs_vmcode_instance_of },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_equality_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_relational_expression,
+    4, {
+        { NJS_TOKEN_EQUAL, njs_vmcode_equal },
+        { NJS_TOKEN_NOT_EQUAL, njs_vmcode_not_equal },
+        { NJS_TOKEN_STRICT_EQUAL, njs_vmcode_strict_equal },
+        { NJS_TOKEN_STRICT_NOT_EQUAL, njs_vmcode_strict_not_equal },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_bitwise_and_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_equality_expression,
+    1, {
+        { NJS_TOKEN_BITWISE_AND, njs_vmcode_bitwise_and },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_bitwise_xor_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_bitwise_and_expression,
+    1, {
+        { NJS_TOKEN_BITWISE_XOR, njs_vmcode_bitwise_xor },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_bitwise_or_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_bitwise_xor_expression,
+    1, {
+        { NJS_TOKEN_BITWISE_OR, njs_vmcode_bitwise_or },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_logical_and_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_bitwise_or_expression,
+    1, {
+        { NJS_TOKEN_LOGICAL_AND, njs_vmcode_logical_and },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_logical_or_expression =
+{
+    njs_parser_binary_expression,
+    &njs_parser_logical_and_expression,
+    1, {
+        { NJS_TOKEN_LOGICAL_OR, njs_vmcode_logical_or },
+    }
+};
+
+
+static const njs_parser_expression_t
+    njs_parser_comma_expression =
+{
+    njs_parser_assignment_expression,
+    NULL,
+    1, {
+        { NJS_TOKEN_COMMA, NULL },
+    }
+};
+
+
+njs_token_t
+njs_parser_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    return njs_parser_binary_expression(vm, parser,
+                                             &njs_parser_comma_expression,
+                                             token);
+}
+
+
+nxt_inline nxt_bool_t
+njs_parser_expression_operator(njs_token_t token)
+{
+    return (token >= NJS_TOKEN_FIRST_OPERATOR
+            && token <= NJS_TOKEN_LAST_OPERATOR);
+}
+
+
+njs_token_t
+njs_parser_var_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    size_t                  size;
+    njs_parser_node_t       *node, *pending;
+    njs_vmcode_operation_t  operation;
+
+    token = njs_parser_conditional_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    for ( ;; ) {
+        size = sizeof(njs_vmcode_3addr_t);
+
+        switch (token) {
+
+        case NJS_TOKEN_ASSIGNMENT:
+            nxt_thread_log_debug("JS: =");
+            operation = njs_vmcode_move;
+            size = sizeof(njs_vmcode_move_t);
+            break;
+
+        case NJS_TOKEN_LINE_END:
+            token = njs_lexer_token(parser->lexer);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+
+            if (njs_parser_expression_operator(token)) {
+                continue;
+            }
+
+            /* Fall through. */
+
+        default:
+            return token;
+        }
+
+        node = parser->node;
+
+        if (node->lvalue == NJS_LVALUE_NONE) {
+            nxt_thread_log_error(NXT_LOG_ALERT, "lvalue required");
+            return NJS_TOKEN_ILLEGAL;
+        }
+
+        pending = NULL;
+
+        if (node->token == NJS_TOKEN_NAME) {
+            node->state = NJS_VARIABLE_ASSIGNMENT;
+
+            if (node->u.variable->state == NJS_VARIABLE_PENDING) {
+                pending = node;
+            }
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = token;
+        node->u.operation = operation;
+        node->left = parser->node;
+        parser->code_size += size;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_var_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node->right = parser->node;
+        parser->node = node;
+
+        if (pending != NULL
+            && pending->u.variable->state == NJS_VARIABLE_PENDING)
+        {
+            pending->u.variable->state = NJS_VARIABLE_SET;
+            parser->code_size -= sizeof(njs_vmcode_1addr_t);
+
+            if (!parser->branch) {
+                pending->state = NJS_VARIABLE_FIRST_ASSIGNMENT;
+            }
+        }
+    }
+}
+
+
+static njs_token_t
+njs_parser_assignment_expression(njs_vm_t *vm, njs_parser_t *parser,
+    const njs_parser_expression_t *expr, njs_token_t token)
+{
+    size_t                  size;
+    njs_parser_node_t       *node, *pending;
+    njs_vmcode_operation_t  operation;
+
+    token = njs_parser_conditional_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    for ( ;; ) {
+        switch (token) {
+
+        case NJS_TOKEN_ASSIGNMENT:
+            nxt_thread_log_debug("JS: =");
+            operation = njs_vmcode_move;
+            break;
+
+        case NJS_TOKEN_ADDITION_ASSIGNMENT:
+            nxt_thread_log_debug("JS: +=");
+            operation = njs_vmcode_addition;
+            break;
+
+        case NJS_TOKEN_SUBSTRACTION_ASSIGNMENT:
+            nxt_thread_log_debug("JS: -=");
+            operation = njs_vmcode_substraction;
+            break;
+
+        case NJS_TOKEN_MULTIPLICATION_ASSIGNMENT:
+            nxt_thread_log_debug("JS: *=");
+            operation = njs_vmcode_multiplication;
+            break;
+
+        case NJS_TOKEN_DIVISION_ASSIGNMENT:
+            nxt_thread_log_debug("JS: /=");
+            operation = njs_vmcode_division;
+            break;
+
+        case NJS_TOKEN_REMAINDER_ASSIGNMENT:
+            nxt_thread_log_debug("JS: %=");
+            operation = njs_vmcode_remainder;
+            break;
+
+        case NJS_TOKEN_LEFT_SHIFT_ASSIGNMENT:
+            nxt_thread_log_debug("JS: <<=");
+            operation = njs_vmcode_left_shift;
+            break;
+
+        case NJS_TOKEN_RIGHT_SHIFT_ASSIGNMENT:
+            nxt_thread_log_debug("JS: >>=");
+            operation = njs_vmcode_right_shift;
+            break;
+
+        case NJS_TOKEN_UNSIGNED_RIGHT_SHIFT_ASSIGNMENT:
+            nxt_thread_log_debug("JS: >>=");
+            operation = njs_vmcode_unsigned_right_shift;
+            break;
+
+        case NJS_TOKEN_BITWISE_AND_ASSIGNMENT:
+            nxt_thread_log_debug("JS: &=");
+            operation = njs_vmcode_bitwise_and;
+            break;
+
+        case NJS_TOKEN_BITWISE_XOR_ASSIGNMENT:
+            nxt_thread_log_debug("JS: ^=");
+            operation = njs_vmcode_bitwise_xor;
+            break;
+
+        case NJS_TOKEN_BITWISE_OR_ASSIGNMENT:
+            nxt_thread_log_debug("JS: |=");
+            operation = njs_vmcode_bitwise_or;
+            break;
+
+        case NJS_TOKEN_LINE_END:
+            token = njs_lexer_token(parser->lexer);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+
+            if (njs_parser_expression_operator(token)) {
+                continue;
+            }
+
+            /* Fall through. */
+
+        default:
+            return token;
+        }
+
+        node = parser->node;
+
+        if (node->lvalue == NJS_LVALUE_NONE) {
+            nxt_thread_log_error(NXT_LOG_ALERT, "lvalue required");
+            return NJS_TOKEN_ILLEGAL;
+        }
+
+        pending = NULL;
+
+        if (node->token == NJS_TOKEN_NAME) {
+
+            if (token == NJS_TOKEN_ASSIGNMENT) {
+                node->state = NJS_VARIABLE_ASSIGNMENT;
+
+                if (node->u.variable->state == NJS_VARIABLE_PENDING) {
+                    pending = node;
+                }
+
+            } else if (node->u.variable->state == NJS_VARIABLE_PENDING) {
+                node->u.variable->state = NJS_VARIABLE_USED;
+            }
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = token;
+        node->u.operation = operation;
+        node->left = parser->node;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_assignment_expression(vm, parser, NULL, token);
+
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node->right = parser->node;
+        parser->node = node;
+
+        if (node->left->token == NJS_TOKEN_NAME) {
+
+            if (node->token == NJS_TOKEN_ASSIGNMENT) {
+                size = sizeof(njs_vmcode_move_t);
+
+            } else {
+                if (njs_parser_has_side_effect(node->right)) {
+                    size = sizeof(njs_vmcode_move_t)
+                           + sizeof(njs_vmcode_3addr_t);
+                } else {
+                    size = sizeof(njs_vmcode_3addr_t);
+                }
+            }
+
+        } else {
+            if (node->token == NJS_TOKEN_ASSIGNMENT) {
+                size = sizeof(njs_vmcode_prop_set_t);
+
+                if (njs_parser_has_side_effect(node->right)) {
+                    size += 2 * sizeof(njs_vmcode_move_t);
+                }
+
+            } else {
+                size = sizeof(njs_vmcode_prop_get_t)
+                       + sizeof(njs_vmcode_3addr_t)
+                       + sizeof(njs_vmcode_prop_set_t);
+            }
+        }
+
+        parser->code_size += size;
+
+        if (pending != NULL
+            && pending->u.variable->state == NJS_VARIABLE_PENDING)
+        {
+            pending->u.variable->state = NJS_VARIABLE_SET;
+
+            if (!parser->branch) {
+                pending->state = NJS_VARIABLE_FIRST_ASSIGNMENT;
+            }
+        }
+    }
+}
+
+
+njs_token_t
+njs_parser_conditional_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token)
+{
+    njs_parser_node_t  *node, *cond;
+
+    token = njs_parser_binary_expression(vm, parser,
+                                   &njs_parser_logical_or_expression,
+                                   token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    for ( ;; ) {
+        if (token != NJS_TOKEN_CONDITIONAL) {
+            return token;
+        }
+
+        parser->branch = 1;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        cond = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(cond == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        cond->token = NJS_TOKEN_CONDITIONAL;
+        cond->left = parser->node;
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        cond->right = node;
+        node->token = NJS_TOKEN_ELSE;
+
+        token = njs_parser_assignment_expression(vm, parser, NULL, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (nxt_slow_path(token != NJS_TOKEN_COLON)) {
+            return NJS_TOKEN_ILLEGAL;
+        }
+
+        node->left = parser->node;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = njs_parser_assignment_expression(vm, parser, NULL, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node->right = parser->node;
+
+        parser->node = cond;
+        parser->code_size += sizeof(njs_vmcode_cond_jump_t)
+                             + sizeof(njs_vmcode_move_t)
+                             + sizeof(njs_vmcode_jump_t)
+                             + sizeof(njs_vmcode_move_t);
+    }
+}
+
+
+static njs_token_t
+njs_parser_binary_expression(njs_vm_t *vm, njs_parser_t *parser,
+    const njs_parser_expression_t *expr, njs_token_t token)
+{
+    nxt_int_t                          n;
+    njs_parser_node_t                  *node;
+    njs_vmcode_operation_t             operation;
+    const njs_parser_operation_t  *op;
+
+    token = expr->next(vm, parser, expr->expression, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    for ( ;; ) {
+        n = expr->count;
+        op = expr->op;
+
+        do {
+            if (op->token == token) {
+                operation = op->operation;
+                goto found;
+            }
+
+            op++;
+            n--;
+
+        } while (n != 0);
+
+        if (token == NJS_TOKEN_LINE_END) {
+
+            token = njs_lexer_token(parser->lexer);
+            if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+                return token;
+            }
+
+            if (njs_parser_expression_operator(token)) {
+                continue;
+            }
+        }
+
+        return token;
+
+    found:
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = token;
+        node->u.operation = operation;
+        node->left = parser->node;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        token = expr->next(vm, parser, expr->expression, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node->right = parser->node;
+        parser->node = node;
+    }
+}
+
+
+static njs_token_t
+njs_parser_unary_expression(njs_vm_t *vm, njs_parser_t *parser,
+    const njs_parser_expression_t *expr, njs_token_t token)
+{
+    double                  num;
+    njs_token_t             next;
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    switch (token) {
+
+    case NJS_TOKEN_ADDITION:
+        token = NJS_TOKEN_UNARY_PLUS;
+        operation = njs_vmcode_unary_plus;
+        break;
+
+    case NJS_TOKEN_SUBSTRACTION:
+        token = NJS_TOKEN_UNARY_NEGATION;
+        operation = njs_vmcode_unary_negation;
+        break;
+
+    case NJS_TOKEN_LOGICAL_NOT:
+        operation = njs_vmcode_logical_not;
+        break;
+
+    case NJS_TOKEN_BITWISE_NOT:
+        operation = njs_vmcode_bitwise_not;
+        break;
+
+    case NJS_TOKEN_TYPEOF:
+        operation = njs_vmcode_typeof;
+        break;
+
+    case NJS_TOKEN_VOID:
+        operation = njs_vmcode_void;
+        break;
+
+    case NJS_TOKEN_DELETE:
+        operation = njs_vmcode_delete;
+        break;
+
+    default:
+        return njs_parser_inc_dec_expression(vm, parser, token);
+    }
+
+    next = njs_parser_token(parser);
+    if (nxt_slow_path(next <= NJS_TOKEN_ILLEGAL)) {
+        return next;
+    }
+
+    next = njs_parser_unary_expression(vm, parser, NULL, next);
+    if (nxt_slow_path(next <= NJS_TOKEN_ILLEGAL)) {
+        return next;
+    }
+
+    if (token == NJS_TOKEN_UNARY_PLUS
+        && parser->node->token == NJS_TOKEN_NUMBER)
+    {
+        /* Skip the unary plus of number. */
+        return next;
+    }
+
+    if (token == NJS_TOKEN_UNARY_NEGATION
+        && parser->node->token == NJS_TOKEN_NUMBER)
+    {
+        /* Optimization of common negative number. */
+
+        node = parser->node;
+        num = -node->u.value.data.u.number;
+        node->u.value.data.u.number = num;
+        node->u.value.data.truth = njs_is_number_true(num);
+
+        return next;
+    }
+
+    if (token == NJS_TOKEN_TYPEOF
+        && parser->node->token == NJS_TOKEN_NAME)
+    {
+        parser->node->state = NJS_VARIABLE_TYPEOF;
+
+    } else if (token == NJS_TOKEN_DELETE
+        && parser->node->token == NJS_TOKEN_PROPERTY)
+    {
+        parser->node->token = NJS_TOKEN_PROPERTY_DELETE;
+        parser->node->u.operation = njs_vmcode_property_delete;
+        parser->code_size += sizeof(njs_vmcode_3addr_t);
+
+        return next;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = token;
+    node->u.operation = operation;
+    node->left = parser->node;
+    parser->node = node;
+    parser->code_size += sizeof(njs_vmcode_2addr_t);
+
+    return next;
+}
+
+
+static njs_token_t
+njs_parser_inc_dec_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    njs_token_t             next;
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    switch (token) {
+
+    case NJS_TOKEN_INCREMENT:
+        operation = njs_vmcode_increment;
+        break;
+
+    case NJS_TOKEN_DECREMENT:
+        operation = njs_vmcode_decrement;
+        break;
+
+    default:
+        return njs_parser_post_inc_dec_expression(vm, parser, token);
+    }
+
+    next = njs_parser_token(parser);
+    if (nxt_slow_path(next <= NJS_TOKEN_ILLEGAL)) {
+        return next;
+    }
+
+    next = njs_parser_call_expression(vm, parser, next);
+    if (nxt_slow_path(next <= NJS_TOKEN_ILLEGAL)) {
+        return next;
+    }
+
+    if (parser->node->lvalue == NJS_LVALUE_NONE) {
+        nxt_thread_log_error(NXT_LOG_ALERT, "lvalue required");
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = token;
+    node->u.operation = operation;
+    node->left = parser->node;
+    parser->node = node;
+
+    parser->code_size += (parser->node->token == NJS_TOKEN_NAME) ?
+                             sizeof(njs_vmcode_3addr_t):
+                             sizeof(njs_vmcode_prop_get_t)
+                             + sizeof(njs_vmcode_3addr_t)
+                             + sizeof(njs_vmcode_prop_set_t);
+
+    return next;
+}
+
+
+static njs_token_t
+njs_parser_post_inc_dec_expression(njs_vm_t *vm,
+    njs_parser_t *parser, njs_token_t token)
+{
+    njs_parser_node_t       *node;
+    njs_vmcode_operation_t  operation;
+
+    token = njs_parser_call_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    switch (token) {
+
+    case NJS_TOKEN_INCREMENT:
+        token = NJS_TOKEN_POST_INCREMENT;
+        operation = njs_vmcode_post_increment;
+        break;
+
+    case NJS_TOKEN_DECREMENT:
+        token = NJS_TOKEN_POST_DECREMENT;
+        operation = njs_vmcode_post_decrement;
+        break;
+
+    default:
+        return token;
+    }
+
+    if (parser->node->lvalue == NJS_LVALUE_NONE) {
+        nxt_thread_log_error(NXT_LOG_ALERT, "lvalue required");
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = token;
+    node->u.operation = operation;
+    node->left = parser->node;
+    parser->node = node;
+
+    parser->code_size += (parser->node->token == NJS_TOKEN_NAME) ?
+                             sizeof(njs_vmcode_3addr_t):
+                             sizeof(njs_vmcode_prop_get_t)
+                             + sizeof(njs_vmcode_3addr_t)
+                             + sizeof(njs_vmcode_prop_set_t);
+
+    return njs_parser_token(parser);
+}
+
+
+static njs_token_t
+njs_parser_call_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    nxt_bool_t         ctor;
+    njs_parser_node_t  *func, *node;
+
+    ctor = 0;
+
+    if (token == NJS_TOKEN_NEW) {
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        ctor = 1;
+    }
+
+    token = njs_parser_terminal(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    for ( ;; ) {
+
+        token = njs_parser_property_expression(vm, parser, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node = parser->node;
+
+        if (token != NJS_TOKEN_OPEN_PARENTHESIS) {
+            /* TODO: var o = Object; var o = new Object() */
+            node->ctor = ctor;
+            return token;
+        }
+
+        switch (node->token) {
+
+        case NJS_TOKEN_NAME:
+            func = node;
+            func->token = NJS_TOKEN_FUNCTION_CALL;
+            parser->code_size += sizeof(njs_vmcode_function_t)
+                                 + sizeof(njs_vmcode_call_t);
+            break;
+
+        case NJS_TOKEN_FUNCTION_CREATE:
+            func = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(func == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            func->token = NJS_TOKEN_FUNCTION_CALL;
+            func->left = node;
+            func->index = node->index;
+            parser->code_size += sizeof(njs_vmcode_function_t)
+                                 + sizeof(njs_vmcode_call_t);
+            break;
+
+        case NJS_TOKEN_OBJECT_FUNCTION:
+        case NJS_TOKEN_ARRAY_FUNCTION:
+        case NJS_TOKEN_BOOLEAN_FUNCTION:
+        case NJS_TOKEN_NUMBER_FUNCTION:
+        case NJS_TOKEN_STRING_FUNCTION:
+        case NJS_TOKEN_FUNCTION_FUNCTION:
+        case NJS_TOKEN_REGEXP_FUNCTION:
+        case NJS_TOKEN_EVAL:
+            func = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(func == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            func->token = NJS_TOKEN_FUNCTION_CALL;
+            func->left = node;
+            parser->code_size += sizeof(njs_vmcode_method_t)
+                                 + sizeof(njs_vmcode_call_t);
+            break;
+
+        default:
+            func = njs_parser_node_alloc(vm);
+            if (nxt_slow_path(func == NULL)) {
+                return NJS_TOKEN_ERROR;
+            }
+
+            func->token = NJS_TOKEN_METHOD_CALL;
+            func->left = node;
+            parser->code_size += sizeof(njs_vmcode_method_t)
+                                 + sizeof(njs_vmcode_call_t);
+        }
+
+        token = njs_parser_arguments(vm, parser, func);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        parser->node = func;
+
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+    }
+}
+
+
+static njs_token_t
+njs_parser_property_expression(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    njs_token_t        next;
+    njs_parser_node_t  *node;
+
+    for ( ;; ) {
+        if (token != NJS_TOKEN_DOT
+            && token != NJS_TOKEN_OPEN_BRACKET)
+        {
+            return token;
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = NJS_TOKEN_PROPERTY;
+        node->lvalue = NJS_LVALUE_ENABLED;
+        node->u.operation = njs_vmcode_property_get;
+        node->left = parser->node;
+
+        next = njs_parser_token(parser);
+        if (nxt_slow_path(next <= NJS_TOKEN_ILLEGAL)) {
+            return next;
+        }
+
+        if (token == NJS_TOKEN_DOT) {
+
+            if (next != NJS_TOKEN_NAME) {
+                return NJS_TOKEN_ILLEGAL;
+            }
+
+            token = njs_parser_property_name(vm, parser, next);
+
+        } else {
+            token = njs_parser_property_brackets(vm, parser, next);
+        }
+
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node->right = parser->node;
+        parser->node = node;
+
+        parser->code_size += sizeof(njs_vmcode_prop_get_t);
+    }
+}
+
+
+njs_token_t
+njs_parser_property_name(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    nxt_int_t          ret;
+    njs_parser_node_t  *node;
+
+    node = njs_parser_node_alloc(vm);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node->token = NJS_TOKEN_STRING;
+
+    ret = njs_parser_string_create(vm, &node->u.value);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    parser->node = node;
+
+    return njs_parser_token(parser);
+}
+
+
+static njs_token_t
+njs_parser_property_brackets(njs_vm_t *vm, njs_parser_t *parser,
+    njs_token_t token)
+{
+    token = njs_parser_expression(vm, parser, token);
+    if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+        return token;
+    }
+
+    if (nxt_slow_path(token != NJS_TOKEN_CLOSE_BRACKET)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    return njs_parser_token(parser);
+}
+
+
+njs_token_t
+njs_parser_arguments(njs_vm_t *vm, njs_parser_t *parser,
+    njs_parser_node_t *parent)
+{
+    njs_token_t        token;
+    njs_index_t        index;
+    njs_parser_node_t  *node;
+
+    parser->nesting_arguments++;
+    index = NJS_SCOPE_CALLEE_ARGUMENTS;
+
+    do {
+        token = njs_parser_token(parser);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        if (token == NJS_TOKEN_CLOSE_PARENTHESIS) {
+            break;
+        }
+
+        token = njs_parser_assignment_expression(vm, parser, NULL, token);
+        if (nxt_slow_path(token <= NJS_TOKEN_ILLEGAL)) {
+            return token;
+        }
+
+        node = njs_parser_node_alloc(vm);
+        if (nxt_slow_path(node == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        node->token = NJS_TOKEN_ARGUMENT;
+        node->index = index;
+        index += sizeof(njs_value_t);
+
+        node->left = parser->node;
+        parser->node->dest = node;
+        parent->right = node;
+        parent = node;
+
+        parser->code_size += sizeof(njs_vmcode_move_t);
+
+    } while (token == NJS_TOKEN_COMMA);
+
+    if (nxt_slow_path(token != NJS_TOKEN_CLOSE_PARENTHESIS)) {
+        return NJS_TOKEN_ILLEGAL;
+    }
+
+    index = njs_index_size(index);
+    index += NJS_NATIVE_FRAME_SIZE + sizeof(njs_value_t);
+
+    parser->nesting_arguments_size += index;
+
+    parser->nesting_arguments--;
+
+    if (parser->nesting_arguments == 0) {
+
+        if (parser->method_arguments_size < parser->nesting_arguments_size) {
+            parser->method_arguments_size = parser->nesting_arguments_size;
+        }
+
+        parser->nesting_arguments_size = 0;
+    }
+
+    return token;
+}
diff --git a/njs/njs_regexp.c b/njs/njs_regexp.c
new file mode 100644 (file)
index 0000000..3446525
--- /dev/null
@@ -0,0 +1,544 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_utf8.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_malloc.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_regexp_pattern.h>
+#include <string.h>
+
+
+static int njs_regexp_pattern_compile(pcre **code, pcre_extra **extra,
+    u_char *source, int options);
+static njs_ret_t njs_regexp_exec_result(njs_vm_t *vm, njs_regexp_t *regexp,
+    u_char *string, int *captures, nxt_uint_t utf8);
+
+
+njs_regexp_t *
+njs_regexp_alloc(njs_vm_t *vm, njs_regexp_pattern_t *pattern)
+{
+    njs_regexp_t  *regexp;
+
+    regexp = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                 sizeof(njs_regexp_t));
+
+    if (nxt_fast_path(regexp != NULL)) {
+        nxt_lvlhsh_init(&regexp->object.hash);
+        nxt_lvlhsh_init(&regexp->object.shared_hash);
+        regexp->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_REGEXP];
+        regexp->last_index = 0;
+        regexp->pattern = pattern;
+    }
+
+    return regexp;
+}
+
+
+njs_regexp_pattern_t *
+njs_regexp_pattern_create(njs_vm_t *vm, nxt_str_t *source,
+    njs_regexp_flags_t flags)
+{
+    int                   options, ret;
+    u_char                *p;
+    njs_regexp_pattern_t  *pattern;
+
+    /* TODO: pcre_malloc */
+
+    pattern = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                            sizeof(njs_regexp_pattern_t) + source->len + 1);
+    if (nxt_slow_path(pattern == NULL)) {
+        return NULL;
+    }
+
+    p = (u_char *) pattern + sizeof(njs_regexp_pattern_t);
+    pattern->source = p;
+
+    p = memcpy(p, source->data, source->len);
+    p += source->len;
+    *p = '\0';
+
+    pattern->ncaptures = 0;
+
+    pattern->global = ((flags & NJS_REGEXP_GLOBAL) != 0);
+
+#ifdef PCRE_JAVASCRIPT_COMPAT
+    /* JavaScript compatibility has been introduced in PCRE-7.7. */
+    options = PCRE_JAVASCRIPT_COMPAT;
+#else
+    options = 0;
+#endif
+
+    if ((flags & NJS_REGEXP_IGNORE_CASE) != 0) {
+         pattern->ignore_case = 1;
+         options |= PCRE_CASELESS;
+    }
+
+    if ((flags & NJS_REGEXP_MULTILINE) != 0) {
+         pattern->multiline = 1;
+         options |= PCRE_MULTILINE;
+    }
+
+    ret = njs_regexp_pattern_compile(&pattern->code[0], &pattern->extra[0],
+                                     pattern->source, options);
+
+    if (nxt_slow_path(ret < 0)) {
+        return NULL;
+    }
+
+    pattern->ncaptures = ret;
+
+    ret = njs_regexp_pattern_compile(&pattern->code[1], &pattern->extra[1],
+                                     pattern->source, options | PCRE_UTF8);
+
+    if (nxt_slow_path(ret < 0)) {
+
+        if (ret == NXT_DECLINED) {
+            return pattern;
+        }
+
+        return NULL;
+    }
+
+    if (nxt_fast_path((unsigned) ret == pattern->ncaptures)) {
+        return pattern;
+    }
+
+    nxt_thread_log_error(NXT_LOG_ERR, "numbers of byte and UTF-8 captures "
+                         "in RegExp \"%s\" vary: %d vs %d",
+                         pattern->source, pattern->ncaptures, ret);
+
+    return NULL;
+}
+
+
+static int
+njs_regexp_pattern_compile(pcre **code, pcre_extra **extra, u_char *source,
+    int options)
+{
+    int         ret, erroff, captures;
+    u_char      *error;
+    const char  *errstr;
+
+    *code = pcre_compile((char *) source, options, &errstr, &erroff, NULL);
+
+    if (nxt_slow_path(*code == NULL)) {
+
+        if ((options & PCRE_UTF8) != 0) {
+            return NXT_DECLINED;
+        }
+
+        error = source + erroff;
+
+        if (*error != '\0') {
+            nxt_thread_log_error(NXT_LOG_ERR,
+                                 "pcre_compile(\"%s\") failed: %s at \"%s\"",
+                                 source, errstr, error);
+        } else {
+            nxt_thread_log_error(NXT_LOG_ERR,
+                                 "pcre_compile(\"%s\") failed: %s",
+                                 source, errstr);
+        }
+
+        return NXT_ERROR;
+    }
+
+    *extra = pcre_study(*code, 0, &errstr);
+
+    if (nxt_slow_path(errstr != NULL)) {
+        nxt_thread_log_error(NXT_LOG_ERR, "pcre_study(\"%s\") failed: %s",
+                             source, errstr);
+        return NXT_ERROR;
+    }
+
+    ret = pcre_fullinfo(*code, NULL, PCRE_INFO_CAPTURECOUNT, &captures);
+
+    if (nxt_fast_path(ret >= 0)) {
+        /* Reserve additional elements for the first "$0" capture. */
+        return captures + 1;
+    }
+
+    nxt_thread_log_error(NXT_LOG_ERR,
+                   "pcre_fullinfo(\"%s\", PCRE_INFO_CAPTURECOUNT) failed: %d",
+                   source, ret);
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_regexp_function(njs_vm_t *vm, njs_param_t *param)
+{
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_regexp_prototype_last_index(njs_vm_t *vm, njs_value_t *value)
+{
+    uint32_t           index;
+    njs_regexp_t       *regexp;
+    njs_string_prop_t  string;
+
+    njs_release(vm, value);
+
+    regexp = value->data.u.regexp;
+
+    (void) njs_string_prop(&string, &regexp->string);
+
+    index = njs_string_index(&string, regexp->last_index);
+    njs_number_set(&vm->retval, index);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_regexp_prototype_ignore_case(njs_vm_t *vm, njs_value_t *regexp)
+{
+    njs_regexp_pattern_t  *pattern;
+
+    pattern = regexp->data.u.regexp->pattern;
+    vm->retval = pattern->ignore_case ? njs_value_true : njs_value_false;
+    njs_release(vm, regexp);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_regexp_prototype_global(njs_vm_t *vm, njs_value_t *regexp)
+{
+    njs_regexp_pattern_t  *pattern;
+
+    pattern = regexp->data.u.regexp->pattern;
+    vm->retval = pattern->global ? njs_value_true : njs_value_false;
+    njs_release(vm, regexp);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_regexp_prototype_multiline(njs_vm_t *vm, njs_value_t *regexp)
+{
+    njs_regexp_pattern_t  *pattern;
+
+    pattern = regexp->data.u.regexp->pattern;
+    vm->retval = pattern->multiline ? njs_value_true : njs_value_false;
+    njs_release(vm, regexp);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_regexp_prototype_source(njs_vm_t *vm, njs_value_t *regexp)
+{
+    size_t                length;
+    u_char                *source;
+    njs_regexp_pattern_t  *pattern;
+
+    pattern = regexp->data.u.regexp->pattern;
+
+    /*
+     * The pattern source is stored not as value but as C string even
+     * without length, because retrieving it is very seldom operation.
+     */
+    source = pattern->source;
+
+    /* TODO: can regexp string be UTF-8? */
+    length = strlen((char *) source);
+
+    return njs_string_create(vm, &vm->retval, source, length, length);
+}
+
+
+static njs_ret_t
+njs_regexp_prototype_test(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_uint_t             n;
+    njs_ret_t             ret;
+    njs_value_t           val;
+    const njs_value_t     *retval;
+    njs_string_prop_t     string;
+    njs_regexp_pattern_t  *pattern;
+
+    if (param->nargs != 0) {
+        ret = njs_value_to_string(vm, &val, &param->args[0]);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            retval = &njs_value_false;
+
+            (void) njs_string_prop(&string, &val);
+
+            n = (string.length != 0 && string.length != string.size);
+            pattern = param->object->data.u.regexp->pattern;
+
+            if (pattern->code[n] != NULL) {
+                ret = pcre_exec(pattern->code[n], pattern->extra[n],
+                                (char *) string.start, string.size,
+                                0, 0, NULL, 0);
+
+                if (ret >= 0) {
+                    retval = &njs_value_true;
+
+                } else if (ret != PCRE_ERROR_NOMATCH) {
+                    /* TODO: exception */
+                    return NXT_ERROR;
+                }
+            }
+
+            vm->retval = *retval;
+
+            return NXT_OK;
+        }
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_regexp_prototype_exec(njs_vm_t *vm, njs_param_t *param)
+{
+    int                   *captures, ncaptures;
+    nxt_uint_t             n, utf8;
+    njs_ret_t             ret;
+    njs_regexp_t          *regexp;
+    njs_string_prop_t     string;
+    njs_regexp_pattern_t  *pattern;
+
+    if (param->nargs != 0) {
+        regexp = param->object->data.u.regexp;
+
+        ret = njs_value_to_string(vm, &regexp->string, &param->args[0]);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            (void) njs_string_prop(&string, &regexp->string);
+
+            utf8 = 0;
+            n = 0;
+
+            if (string.length != 0) {
+                utf8 = 1;
+                n = 1;
+
+                if (string.length != string.size) {
+                    utf8 = 2;
+                }
+            }
+
+            pattern = regexp->pattern;
+
+            if (pattern->code[n] != NULL) {
+                string.start += regexp->last_index;
+                string.size -= regexp->last_index;
+
+                /* Each capture is stored in 3 vector elements. */
+                ncaptures = pattern->ncaptures * 3;
+
+                captures = alloca(ncaptures * sizeof(int));
+
+                ret = pcre_exec(pattern->code[n], pattern->extra[n],
+                                (char *) string.start, string.size,
+                                0, 0, captures, ncaptures);
+
+                if (ret >= 0) {
+                    return njs_regexp_exec_result(vm, regexp, string.start,
+                                                  captures, utf8);
+                }
+
+                if (nxt_slow_path(ret != PCRE_ERROR_NOMATCH)) {
+                    /* TODO: exception */
+                    return NXT_ERROR;
+                }
+            }
+
+            regexp->last_index = 0;
+            vm->retval = njs_value_null;
+
+            return NXT_OK;
+        }
+    }
+
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_regexp_exec_result(njs_vm_t *vm, njs_regexp_t *regexp, u_char *string,
+    int *captures, nxt_uint_t utf8)
+{
+    u_char              *start;
+    int32_t             size, length;
+    nxt_uint_t           i, n;
+    njs_ret_t           ret;
+    njs_array_t         *array;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    static const njs_value_t  njs_string_index = njs_string("index");
+    static const njs_value_t  njs_string_input = njs_string("input");
+
+    array = njs_array_alloc(vm, regexp->pattern->ncaptures, 0);
+    if (nxt_slow_path(array == NULL)) {
+        return NXT_ERROR;
+    }
+
+    for (i = 0; i < regexp->pattern->ncaptures; i++) {
+        n = 2 * i;
+
+        if (captures[n] != -1) {
+            start = &string[captures[n]];
+            size = captures[n + 1] - captures[n];
+
+            switch (utf8) {
+            case 0:
+                length = 0;
+                break;
+            case 1:
+                length = size;
+                break;
+            default:
+                length = nxt_utf8_length(start, size);
+                break;
+            }
+
+            ret = njs_string_create(vm, &array->start[i], start, size, length);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NXT_ERROR;
+            }
+
+        } else {
+            array->start[i] = njs_value_void;
+        }
+    }
+
+    prop = njs_object_prop_alloc(vm, &njs_string_index);
+    if (nxt_slow_path(prop == NULL)) {
+        return NXT_ERROR;
+    }
+
+    /* TODO: Non UTF-8 position */
+
+    njs_number_set(&prop->value, regexp->last_index + captures[0]);
+
+    if (regexp->pattern->global) {
+        regexp->last_index += captures[1];
+    }
+
+    lhq.key_hash = NJS_INDEX_HASH;
+    lhq.key.len = sizeof("index") - 1;
+    lhq.key.data = (u_char *) "index";
+    lhq.replace = 0;
+    lhq.value = prop;
+    lhq.pool = vm->mem_cache_pool;
+    lhq.proto = &njs_object_hash_proto;
+
+    ret = nxt_lvlhsh_insert(&array->object.hash, &lhq);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        /* Only NXT_ERROR can be returned here. */
+        return ret;
+    }
+
+    prop = njs_object_prop_alloc(vm, &njs_string_input);
+    if (nxt_slow_path(prop == NULL)) {
+        return NXT_ERROR;
+    }
+
+    njs_string_copy(&prop->value, &regexp->string);
+
+    lhq.key_hash = NJS_INPUT_HASH;
+    lhq.key.len = sizeof("input") - 1;
+    lhq.key.data = (u_char *) "input";
+    lhq.value = prop;
+
+    ret = nxt_lvlhsh_insert(&array->object.hash, &lhq);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        /* Only NXT_ERROR can be returned here. */
+        return ret;
+    }
+
+    vm->retval.data.u.array = array;
+    vm->retval.type = NJS_ARRAY;
+    vm->retval.data.truth = 1;
+
+    return NXT_OK;
+}
+
+
+static const njs_object_prop_t  njs_regexp_function_properties[] =
+{
+    { njs_string("RegExp"),
+      njs_string("name"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_value(NJS_NUMBER, 1, 2.0),
+      njs_string("length"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_prototype),
+      njs_string("prototype"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_regexp_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_regexp_function_properties,
+                                  nxt_nitems(njs_regexp_function_properties));
+}
+
+
+static const njs_object_prop_t  njs_regexp_prototype_properties[] =
+{
+    { njs_getter(njs_regexp_prototype_last_index),
+      njs_string("lastIndex"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_regexp_prototype_ignore_case),
+      njs_string("ignoreCase"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_regexp_prototype_global),
+      njs_string("global"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_regexp_prototype_multiline),
+      njs_string("multiline"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_regexp_prototype_source),
+      njs_string("source"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_native_function(njs_regexp_prototype_test, 0),
+      njs_string("test"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_regexp_prototype_exec, 0),
+      njs_string("exec"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_regexp_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_regexp_prototype_properties,
+                                  nxt_nitems(njs_regexp_prototype_properties));
+}
diff --git a/njs/njs_regexp.h b/njs/njs_regexp.h
new file mode 100644 (file)
index 0000000..8ca7134
--- /dev/null
@@ -0,0 +1,43 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_REGEXP_H_INCLUDED_
+#define _NJS_REGEXP_H_INCLUDED_
+
+
+typedef enum {
+    NJS_REGEXP_IGNORE_CASE = 1,
+    NJS_REGEXP_GLOBAL      = 2,
+    NJS_REGEXP_MULTILINE   = 4,
+} njs_regexp_flags_t;
+
+
+struct njs_regexp_s {
+    /* Must be aligned to njs_value_t. */
+    njs_object_t          object;
+
+    uint32_t              last_index;
+
+    njs_regexp_pattern_t  *pattern;
+
+    /*
+     * This string value can be not aligned since
+     * it never used in nJSVM operations.
+     */
+    njs_value_t           string;
+};
+
+
+njs_regexp_t *njs_regexp_alloc(njs_vm_t *vm, njs_regexp_pattern_t *pattern);
+njs_regexp_pattern_t *njs_regexp_pattern_create(njs_vm_t *vm,
+    nxt_str_t *source, njs_regexp_flags_t flags);
+njs_ret_t njs_regexp_function(njs_vm_t *vm, njs_param_t *param);
+njs_ret_t njs_regexp_prototype_exec(njs_vm_t *vm, njs_param_t *param);
+nxt_int_t njs_regexp_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+nxt_int_t njs_regexp_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+#endif /* _NJS_REGEXP_H_INCLUDED_ */
diff --git a/njs/njs_regexp_pattern.h b/njs/njs_regexp_pattern.h
new file mode 100644 (file)
index 0000000..9ecded9
--- /dev/null
@@ -0,0 +1,32 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_REGEXP_PATTERN_H_INCLUDED_
+#define _NJS_REGEXP_PATTERN_H_INCLUDED_
+
+#include <pcre.h>
+
+
+struct njs_regexp_pattern_s {
+    pcre                  *code[2];
+    pcre_extra            *extra[2];
+    u_char                *source;
+
+#if (NXT_64BIT)
+    uint32_t              ncaptures;
+    uint8_t               global;       /* 1 bit */
+    uint8_t               ignore_case;  /* 1 bit */
+    uint8_t               multiline;    /* 1 bit */
+#else
+    uint16_t              ncaptures;
+    uint8_t               global;       /* 1 bit */
+    uint8_t               ignore_case:1;
+    uint8_t               multiline:1;
+#endif
+};
+
+
+#endif /* _NJS_REGEXP_PATTERN_H_INCLUDED_ */
diff --git a/njs/njs_shared.c b/njs/njs_shared.c
new file mode 100644 (file)
index 0000000..6a929c1
--- /dev/null
@@ -0,0 +1,189 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <string.h>
+
+
+typedef nxt_int_t (*njs_shared_hash_t) (njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+
+/* STUB */
+static nxt_int_t
+njs_stub_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return NXT_OK;
+}
+static njs_ret_t
+njs_stub_function(njs_vm_t *vm, njs_param_t *param)
+{
+    return NXT_ERROR;
+}
+/**/
+
+
+nxt_int_t
+njs_shared_objects_create(njs_vm_t *vm)
+{
+    size_t          size;
+    nxt_int_t       ret;
+    nxt_uint_t      i;
+    njs_object_t    *prototypes;
+    njs_function_t  *functions;
+
+    static const njs_shared_hash_t  prototype_hash[] = {
+        njs_object_prototype_hash,
+        njs_array_prototype_hash,
+        njs_stub_hash,
+        njs_number_prototype_hash,
+        njs_string_prototype_hash,
+        njs_function_prototype_hash,
+        njs_regexp_prototype_hash,
+    };
+
+    static const njs_shared_hash_t  function_hash[] = {
+        njs_object_function_hash,
+        njs_array_function_hash,
+        njs_stub_hash,
+        njs_number_function_hash,
+        njs_string_function_hash,
+        njs_function_function_hash,
+        njs_regexp_function_hash,
+        njs_stub_hash,
+    };
+
+    static const njs_native_t  native_functions[] = {
+        njs_object_function,
+        njs_array_function,
+        njs_stub_function,
+        njs_number_function,
+        njs_string_ctor_function,
+        njs_stub_function,
+        njs_stub_function,
+        njs_stub_function,
+    };
+
+    size = NJS_PROTOTYPE_MAX * sizeof(njs_object_t);
+
+    prototypes = nxt_mem_cache_zalign(vm->mem_cache_pool, sizeof(njs_value_t),
+                                      size);
+    if (nxt_slow_path(prototypes == NULL)) {
+        return NXT_ERROR;
+    }
+
+    vm->shared->prototypes = prototypes;
+
+    for (i = NJS_PROTOTYPE_OBJECT; i < NJS_PROTOTYPE_MAX; i++) {
+        /* TODO: shared hash: prototype & constructor getters, methods */
+
+        ret = prototype_hash[i](vm, &prototypes[i].shared_hash);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
+    size = NJS_FUNCTION_MAX * sizeof(njs_function_t);
+
+    functions = nxt_mem_cache_zalign(vm->mem_cache_pool, sizeof(njs_value_t),
+                                     size);
+    if (nxt_slow_path(functions == NULL)) {
+        return NXT_ERROR;
+    }
+
+    vm->shared->functions = functions;
+
+    for (i = NJS_FUNCTION_OBJECT; i < NJS_FUNCTION_MAX; i++) {
+        functions[i].native = 1;
+        functions[i].args_offset = 1;
+        functions[i].code.native = native_functions[i];
+
+        ret = function_hash[i](vm, &functions[i].object.shared_hash);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+    }
+
+    /* TODO: create function shared hash: prototype+contructor getter */
+
+    return NXT_OK;
+}
+
+
+/*
+ * Object(),
+ * Object.__proto__             -> Function_Prototype,
+ * Object_Prototype.__proto__   -> null,
+ *   the null value is handled by njs_object_prototype_get_prototype(),
+ *
+ * Array(),
+ * Array.__proto__              -> Function_Prototype,
+ * Array_Prototype.__proto__    -> Object_Prototype,
+ *
+ * Function(),
+ * Function.__proto__           -> Function_Prototype,
+ * Function_Prototype.__proto__ -> Object_Prototype,
+ *
+ * [...]
+ *
+ * eval().
+ */
+
+nxt_int_t
+njs_shared_objects_clone(njs_vm_t *vm)
+{
+    size_t          size;
+    nxt_uint_t      i;
+    njs_value_t     *values;
+    njs_object_t    *prototypes;
+    njs_function_t  *functions;
+
+    size = NJS_PROTOTYPE_MAX * sizeof(njs_object_t);
+
+    prototypes = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                     size);
+    if (nxt_slow_path(prototypes == NULL)) {
+        return NXT_ERROR;
+    }
+
+    vm->prototypes = prototypes;
+
+    memcpy(prototypes, vm->shared->prototypes, size);
+
+    for (i = NJS_PROTOTYPE_ARRAY; i < NJS_PROTOTYPE_MAX; i++) {
+        prototypes[i].__proto__ = &prototypes[NJS_PROTOTYPE_OBJECT];
+    }
+
+    size = NJS_FUNCTION_MAX * sizeof(njs_function_t);
+
+    functions = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                    size);
+    if (nxt_slow_path(functions == NULL)) {
+        return NXT_ERROR;
+    }
+
+    vm->functions = functions;
+
+    memcpy(functions, vm->shared->functions, size);
+
+    values = vm->scopes[NJS_SCOPE_GLOBAL];
+
+    for (i = NJS_FUNCTION_OBJECT; i < NJS_FUNCTION_MAX; i++) {
+        values[i].type = NJS_FUNCTION;
+        values[i].data.truth = 1;
+        values[i].data.u.function = &functions[i];
+        functions[i].object.__proto__ = &prototypes[NJS_FUNCTION_FUNCTION];
+    }
+
+    return NXT_OK;
+}
diff --git a/njs/njs_string.c b/njs/njs_string.c
new file mode 100644 (file)
index 0000000..f2a248d
--- /dev/null
@@ -0,0 +1,1602 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_utf8.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_malloc.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_regexp_pattern.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+static nxt_noinline njs_ret_t njs_string_index_of(njs_vm_t *vm, njs_value_t *src,
+    njs_value_t *search_string, size_t index);
+
+
+njs_ret_t
+njs_string_create(njs_vm_t *vm, njs_value_t *value, u_char *start, size_t size,
+    size_t length)
+{
+    u_char        *dst, *src;
+    njs_string_t  *string;
+
+    value->type = NJS_STRING;
+    njs_string_truth(value, size);
+
+    if (size <= NJS_STRING_SHORT) {
+        value->short_string.size = size;
+        value->short_string.length = length;
+
+        dst = value->short_string.start;
+        src = start;
+
+        while (size != 0) {
+            /* The maximum size is just 14 bytes. */
+            nxt_pragma_loop_disable_vectorization;
+
+            *dst++ = *src++;
+            size--;
+        }
+
+    } else {
+        /*
+         * Setting UTF-8 length is not required here, it just allows
+         * to store the constant in whole byte instead of bit twiddling.
+         */
+        value->short_string.size = NJS_STRING_LONG;
+        value->short_string.length = 0;
+        value->data.external0 = 0xff;
+        value->data.string_size = size;
+
+        string = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_string_t));
+        if (nxt_slow_path(string == NULL)) {
+            return NXT_ERROR;
+        }
+
+        value->data.u.string = string;
+
+        string->start = start;
+        string->length = length;
+        string->retain = 1;
+    }
+
+    return NXT_OK;
+}
+
+
+nxt_noinline u_char *
+njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint32_t size,
+    uint32_t length)
+{
+    uint32_t      total;
+    njs_string_t  *string;
+
+    value->type = NJS_STRING;
+    njs_string_truth(value, size);
+
+    if (size <= NJS_STRING_SHORT) {
+        value->short_string.size = size;
+        value->short_string.length = length;
+
+        return value->short_string.start;
+    }
+
+    /*
+     * Setting UTF-8 length is not required here, it just allows
+     * to store the constant in whole byte instead of bit twiddling.
+     */
+    value->short_string.size = NJS_STRING_LONG;
+    value->short_string.length = 0;
+    value->data.external0 = 0;
+    value->data.string_size = size;
+
+    if (size != length && length > NJS_STRING_MAP_OFFSET) {
+        total = nxt_align_size(size, sizeof(uint32_t));
+        total += ((length - 1) / NJS_STRING_MAP_OFFSET) * sizeof(uint32_t);
+
+    } else {
+        total = size;
+    }
+
+    string = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                 sizeof(njs_string_t) + total);
+
+    if (nxt_fast_path(string != NULL)) {
+        value->data.u.string = string;
+
+        string->start = (u_char *) string + sizeof(njs_string_t);
+        string->length = length;
+        string->retain = 1;
+
+        return string->start;
+    }
+
+    return NULL;
+}
+
+
+void
+njs_string_copy(njs_value_t *dst, njs_value_t *src)
+{
+    *dst = *src;
+
+    /* GC: long string retain */
+}
+
+
+/*
+ * njs_string_validate() validates an UTF-8 string, evaluates its length,
+ * sets njs_string_prop_t struct, and initializes offset map if it is required.
+ */
+
+nxt_noinline njs_ret_t
+njs_string_validate(njs_vm_t *vm, njs_string_prop_t *string, njs_value_t *value)
+{
+    u_char     *start;
+    size_t     new_size;
+    ssize_t    size;
+    njs_ret_t  length;
+
+    size = value->short_string.size;
+
+    if (size != NJS_STRING_LONG) {
+        string->start = value->short_string.start;
+        length = value->short_string.length;
+
+        if (length == 0 && length != size) {
+            length = nxt_utf8_length(value->short_string.start, size);
+
+            if (nxt_slow_path(length < 0)) {
+                /* Invalid UTF-8 string. */
+                return length;
+            }
+
+            value->short_string.length = length;
+        }
+
+    } else {
+        string->start = value->data.u.string->start;
+        size = value->data.string_size;
+        length = value->data.u.string->length;
+
+        if (length == 0 && length != size) {
+            length = nxt_utf8_length(string->start, size);
+
+            if (length != size) {
+                if (nxt_slow_path(length < 0)) {
+                    /* Invalid UTF-8 string. */
+                    return length;
+                }
+
+                if (length > NJS_STRING_MAP_OFFSET) {
+                    /*
+                     * Reallocate the long string with offset map
+                     * after the string.
+                     */
+                    new_size = nxt_align_size(size, sizeof(uint32_t));
+                    new_size += ((length - 1) / NJS_STRING_MAP_OFFSET)
+                                * sizeof(uint32_t);
+
+                    start = nxt_mem_cache_alloc(vm->mem_cache_pool, new_size);
+                    if (nxt_slow_path(start == NULL)) {
+                        return NXT_ERROR;
+                    }
+
+                    memcpy(start, string->start, size);
+                    string->start = start;
+                    value->data.u.string->start = start;
+
+                    njs_string_offset_map_init(start, size);
+                }
+            }
+
+            value->data.u.string->length = length;
+        }
+    }
+
+    string->size = size;
+    string->length = length;
+
+    return length;
+}
+
+
+nxt_noinline size_t
+njs_string_prop(njs_string_prop_t *string, njs_value_t *value)
+{
+    size_t     size;
+    uintptr_t  length;
+
+    size = value->short_string.size;
+
+    if (size != NJS_STRING_LONG) {
+        string->start = value->short_string.start;
+        length = value->short_string.length;
+
+    } else {
+        string->start = value->data.u.string->start;
+        size = value->data.string_size;
+        length = value->data.u.string->length;
+    }
+
+    string->size = size;
+    string->length = length;
+
+    return (length == 0) ? size : length;
+}
+
+
+njs_ret_t
+njs_string_ctor_function(njs_vm_t *vm, njs_param_t *param)
+{
+    njs_object_t       *object;
+    const njs_value_t  *value;
+
+    if (param->nargs == 0) {
+        value = &njs_string_empty;
+
+    } else {
+        /* TODO: to_string. */
+        value = &param->args[0];
+    }
+
+    if (vm->frame->ctor) {
+        /* value->type is the same as prototype offset. */
+        object = njs_object_value_alloc(vm, value, value->type);
+        if (nxt_slow_path(object == NULL)) {
+            return NXT_ERROR;
+        }
+
+        vm->retval.data.u.object = object;
+        vm->retval.type = NJS_OBJECT_STRING;
+        vm->retval.data.truth = 1;
+
+    } else {
+        vm->retval = *value;
+    }
+
+    return NXT_OK;
+}
+
+
+static const njs_object_prop_t  njs_string_function_properties[] =
+{
+    { njs_string("String"),
+      njs_string("name"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_value(NJS_NUMBER, 1, 1.0),
+      njs_string("length"),
+      NJS_PROPERTY, 0, 0, 0, },
+
+    { njs_getter(njs_object_prototype_create_prototype),
+      njs_string("prototype"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_string_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_string_function_properties,
+                                  nxt_nitems(njs_string_function_properties));
+}
+
+
+static njs_ret_t
+njs_string_prototype_get_prototype(njs_vm_t *vm, njs_value_t *value)
+{
+    vm->retval.type = NJS_OBJECT;
+    vm->retval.data.truth = 1;
+    vm->retval.data.u.object = &vm->prototypes[NJS_PROTOTYPE_STRING];
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_length(njs_vm_t *vm, njs_value_t *value)
+{
+    size_t     size;
+    uintptr_t  length;
+
+    length = 0;
+
+    /* TODO: String object. */
+
+    if (njs_is_string(value)) {
+        size = value->short_string.size;
+        length = value->short_string.length;
+
+        if (size == NJS_STRING_LONG) {
+            size = value->data.string_size;
+            length = value->data.u.string->length;
+        }
+
+        length = (length == 0) ? size : length;
+    }
+
+    njs_number_set(&vm->retval, length);
+
+    njs_release(vm, value);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_bytes(njs_vm_t *vm, njs_value_t *value)
+{
+    u_char             *p;
+    uintptr_t          size;
+    const u_char       *s, *end;
+    njs_string_prop_t  string;
+
+    size = njs_string_prop(&string, value);
+
+    p = njs_string_alloc(vm, &vm->retval, size, 0);
+
+    if (nxt_fast_path(p != NULL)) {
+
+        if (string.length == 0) {
+            memcpy(p, string.start, size);
+
+        } else {
+            s = string.start;
+            end = s + string.size;
+
+            while (s < end) {
+                *p++ = (u_char) nxt_utf8_decode(&s, end);
+            }
+        }
+
+        njs_release(vm, value);
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static njs_ret_t
+njs_string_prototype_utf8(njs_vm_t *vm, njs_value_t *value)
+{
+    u_char             *p;
+    ssize_t            length;
+    njs_string_prop_t  string;
+
+    (void) njs_string_prop(&string, value);
+
+    length = nxt_utf8_length(string.start, string.size);
+
+    if (length < 0) {
+        vm->retval = njs_value_null;
+        njs_release(vm, value);
+        return NXT_OK;
+    }
+
+    if ((size_t) length == string.size) {
+        return njs_string_create(vm, &vm->retval, string.start,
+                                 length, length);
+    }
+
+    /* length != string.size */
+
+    p = njs_string_alloc(vm, &vm->retval, string.size, length);
+
+    if (nxt_fast_path(p != NULL)) {
+        memcpy(p, string.start, string.size);
+
+        if (length >= NJS_STRING_MAP_OFFSET) {
+            njs_string_offset_map_init(p, string.size);
+        }
+
+        njs_release(vm, value);
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+nxt_noinline void
+njs_string_offset_map_init(const u_char *start, size_t size)
+{
+    size_t        offset;
+    uint32_t      *map;
+    nxt_uint_t    n;
+    const u_char  *p, *end;
+
+    end = start + size;
+    map = (uint32_t *) nxt_align_ptr(end, sizeof(uint32_t));
+    p = start;
+    n = 0;
+    offset = NJS_STRING_MAP_OFFSET;
+
+    do {
+        if (offset == 0) {
+            map[n++] = p - start;
+            offset = NJS_STRING_MAP_OFFSET;
+        }
+
+        /* The UTF-8 string should be valid since its length is known. */
+        p = nxt_utf8_next(p, end);
+
+        offset--;
+
+    } while (p < end);
+}
+
+
+nxt_bool_t
+njs_string_eq(const njs_value_t *v1, const njs_value_t *v2)
+{
+    size_t        size;
+    const u_char  *start1, *start2;
+
+    size = v1->short_string.size;
+
+    if (size != v2->short_string.size) {
+        return 0;
+    }
+
+    if (size != NJS_STRING_LONG) {
+        start1 = v1->short_string.start;
+        start2 = v2->short_string.start;
+
+    } else {
+        size = v1->data.string_size;
+
+        if (size != v2->data.string_size) {
+            return 0;
+        }
+
+        start1 = v1->data.u.string->start;
+        start2 = v2->data.u.string->start;
+    }
+
+    return (memcmp(start1, start2, size) == 0);
+}
+
+
+nxt_int_t
+njs_string_cmp(const njs_value_t *v1, const njs_value_t *v2)
+{
+    nxt_int_t     ret;
+    size_t        size, size1, size2;
+    const u_char  *start1, *start2;
+
+    size1 = v1->short_string.size;
+
+    if (size1 != NJS_STRING_LONG) {
+        start1 = v1->short_string.start;
+
+    } else {
+        size1 = v1->data.string_size;
+        start1 = v1->data.u.string->start;
+    }
+
+    size2 = v2->short_string.size;
+
+    if (size2 != NJS_STRING_LONG) {
+        start2 = v2->short_string.start;
+
+    } else {
+        size2 = v2->data.string_size;
+        start2 = v2->data.u.string->start;
+    }
+
+    size = nxt_min(size1, size2);
+
+    ret = memcmp(start1, start2, size);
+
+    if (ret != 0) {
+        return ret;
+    }
+
+    return (size1 - size2);
+}
+
+
+njs_ret_t
+njs_string_prototype_concat(njs_vm_t *vm, njs_param_t *param)
+{
+    u_char             *p, *start;
+    size_t             size, length, mask;
+    uintptr_t          nargs;
+    nxt_uint_t          i;
+    njs_ret_t          ret;
+    njs_value_t        *object, *args, *values;
+    njs_string_prop_t  string;
+
+    object = param->object;
+    nargs = param->nargs;
+
+    if (nargs == 0) {
+        njs_string_copy(&vm->retval, object);
+        return NXT_OK;
+    }
+
+    values = alloca((nargs + 1) * sizeof(njs_value_t));
+
+    ret = njs_value_to_string(vm, &values[0], object);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NXT_ERROR;
+    }
+
+    (void) njs_string_prop(&string, &values[0]);
+
+    size = string.size;
+    length = string.length;
+    mask = (length != 0) ? -1 : 0;
+
+    args = param->args;
+
+    for (i = 0; i < nargs; i++) {
+        ret = njs_value_to_string(vm, &values[i + 1], &args[i]);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NXT_ERROR;
+        }
+
+        (void) njs_string_prop(&string, &values[i + 1]);
+
+        size += string.size;
+        length += string.length;
+
+        if (string.length == 0 && string.size != 0) {
+            mask = 0;
+        }
+    }
+
+    length &= mask;
+
+    start = njs_string_alloc(vm, &vm->retval, size, length);
+
+    if (nxt_slow_path(start == NULL)) {
+        return NXT_ERROR;
+    }
+
+    p = start;
+
+    for (i = 0; i <= nargs; i++) {
+        (void) njs_string_prop(&string, &values[i]);
+
+        p = memcpy(p, string.start, string.size);
+        p += string.size;
+    }
+
+    if (length >= NJS_STRING_MAP_OFFSET && size != length) {
+        njs_string_offset_map_init(start, size);
+    }
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_slice(njs_vm_t *vm, njs_param_t *param)
+{
+    ssize_t            start, end;
+    uintptr_t          nargs;
+    njs_ret_t          length, string_length;
+    njs_value_t        *args;
+    njs_string_prop_t  string;
+
+    string_length = njs_string_prop(&string, param->object);
+
+    length = string_length;
+    start = 0;
+    nargs = param->nargs;
+
+    if (nargs != 0) {
+        args = param->args;
+
+        start = njs_value_to_number(&args[0]);
+
+        if (start < 0) {
+            start += length;
+
+            if (start < 0) {
+                start = 0;
+            }
+        }
+
+        if (nargs > 1) {
+            end = njs_value_to_number(&args[1]);
+
+            if (end < 0) {
+                end += length;
+            }
+
+            length = end - start;
+
+            if (length < 0) {
+                start = 0;
+                length = 0;
+            }
+        }
+    }
+
+    return njs_string_slice(vm, &vm->retval, &string, string_length,
+                            start, length);
+}
+
+
+static njs_ret_t
+njs_string_prototype_substring(njs_vm_t *vm, njs_param_t *param)
+{
+    ssize_t            start, end;
+    uintptr_t          nargs;
+    njs_ret_t          length, string_length;
+    njs_value_t        *args;
+    njs_string_prop_t  string;
+
+    string_length = njs_string_prop(&string, param->object);
+
+    length = string_length;
+    start = 0;
+    nargs = param->nargs;
+
+    if (nargs != 0) {
+        args = param->args;
+
+        start = njs_value_to_number(&args[0]);
+
+        if (start < 0) {
+            start = 0;
+        }
+
+        if (nargs > 1) {
+            end = njs_value_to_number(&args[1]);
+
+            if (end < 0) {
+                end = 0;
+            }
+
+            length = end - start;
+
+            if (length < 0) {
+                length = -length;
+                start = end;
+            }
+        }
+    }
+
+    return njs_string_slice(vm, &vm->retval, &string, string_length,
+                            start, length);
+}
+
+
+static njs_ret_t
+njs_string_prototype_substr(njs_vm_t *vm, njs_param_t *param)
+{
+    ssize_t            start;
+    uintptr_t          nargs;
+    njs_ret_t          length, string_length;
+    njs_value_t        *args;
+    njs_string_prop_t  string;
+
+    string_length = njs_string_prop(&string, param->object);
+
+    length = string_length;
+    start = 0;
+    nargs = param->nargs;
+
+    if (nargs != 0) {
+        args = param->args;
+
+        start = njs_value_to_number(&args[0]);
+
+        if (start < 0) {
+
+            start += length;
+            if (start < 0) {
+                start = 0;
+            }
+        }
+
+        if (nargs > 1) {
+            length = njs_value_to_number(&args[1]);
+        }
+    }
+
+    return njs_string_slice(vm, &vm->retval, &string, string_length,
+                            start, length);
+}
+
+
+static njs_ret_t
+njs_string_prototype_char_at(njs_vm_t *vm, njs_param_t *param)
+{
+    ssize_t            start;
+    njs_ret_t          length, string_length;
+    njs_string_prop_t  string;
+
+    string_length = njs_string_prop(&string, param->object);
+
+    start = 0;
+    length = 1;
+
+    if (param->nargs != 0) {
+        start = njs_value_to_number(&param->args[0]);
+
+        if (start < 0) {
+            length = 0;
+        }
+    }
+
+    return njs_string_slice(vm, &vm->retval, &string, string_length,
+                            start, length);
+}
+
+
+nxt_noinline njs_ret_t
+njs_string_slice(njs_vm_t *vm, njs_value_t *dst,
+    const njs_string_prop_t *string, size_t string_length, size_t index,
+    size_t length)
+{
+    u_char        *slice;
+    size_t        size, n;
+    ssize_t       excess;
+    const u_char  *p, *start, *end;
+
+    if (length > 0 && index < string_length) {
+
+        start = string->start;
+        end = start + string->size;
+
+        if (string->size == string_length) {
+            /* Byte or ASCII string. */
+            start += index;
+
+            excess = (start + length) - end;
+            if (excess > 0) {
+                length -= excess;
+            }
+
+            size = length;
+
+            if (string->length == 0) {
+                length = 0;
+            }
+
+        } else {
+            /* UTF-8 string. */
+            start = njs_string_offset(start, end, index);
+
+            /* Evaluate size of the slice in bytes and ajdust length. */
+            p = start;
+            n = length;
+
+            do {
+                p = nxt_utf8_next(p, end);
+                n--;
+            } while (n != 0 && p < end);
+
+            size = p - start;
+            length -= n;
+        }
+
+        if (nxt_fast_path(size != 0)) {
+            slice = njs_string_alloc(vm, &vm->retval, size, length);
+
+            if (nxt_slow_path(slice == NULL)) {
+                return NXT_ERROR;
+            }
+
+            memcpy(slice, start, size);
+
+            if (length >= NJS_STRING_MAP_OFFSET && size != length) {
+                njs_string_offset_map_init(slice, size);
+            }
+
+            return NXT_OK;
+        }
+    }
+
+    vm->retval = njs_string_empty;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_char_code_at(njs_vm_t *vm, njs_param_t *param)
+{
+    double             num;
+    ssize_t            index;
+    uint32_t           code;
+    njs_ret_t          length;
+    const u_char       *start, *end;
+    njs_string_prop_t  string;
+
+    length = njs_string_prop(&string, param->object);
+
+    index = 0;
+
+    if (param->nargs != 0) {
+        index = njs_value_to_number(&param->args[0]);
+
+        if (nxt_slow_path(index < 0 || index >= length)) {
+            num = NJS_NAN;
+            goto done;
+        }
+    }
+
+    if ((uint32_t) length == string.size) {
+        /* Byte or ASCII string. */
+        code = string.start[index];
+
+    } else {
+        /* UTF-8 string. */
+        end = string.start + string.size;
+        start = njs_string_offset(string.start, end, index);
+        code = nxt_utf8_decode(&start, end);
+    }
+
+    num = code;
+
+done:
+
+    njs_number_set(&vm->retval, num);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_index_of(njs_vm_t *vm, njs_param_t *param)
+{
+    ssize_t      start;
+    uintptr_t    nargs;
+    njs_ret_t    index;
+    njs_value_t  *args;
+
+    index = -1;
+    nargs = param->nargs;
+
+    if (nargs != 0) {
+        start = 0;
+        args = param->args;
+
+        if (nargs > 1) {
+            start = njs_value_to_number(&args[1]);
+
+            if (start < 0) {
+                start = 0;
+            }
+        }
+
+        index = njs_string_index_of(vm, param->object, &args[0], start);
+    }
+
+    njs_number_set(&vm->retval, index);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_last_index_of(njs_vm_t *vm, njs_param_t *param)
+{
+    uintptr_t    nargs;
+    njs_ret_t    ret, index, last;
+    njs_value_t  *args;
+
+    index = -1;
+    nargs = param->nargs;
+
+    if (nargs != 0) {
+        last = NJS_STRING_MAX_LENGTH;
+        args = param->args;
+
+        if (nargs > 1) {
+            last = njs_value_to_number(&args[1]);
+
+            if (last < 0) {
+                last = 0;
+            }
+        }
+
+        ret = 0;
+
+        for ( ;; ) {
+            ret = njs_string_index_of(vm, param->object, &args[0], ret);
+
+            if (ret < 0 || ret >= last) {
+                break;
+            }
+
+            index = ret++;
+        }
+    }
+
+    njs_number_set(&vm->retval, index);
+
+    return NXT_OK;
+}
+
+
+static nxt_noinline njs_ret_t
+njs_string_index_of(njs_vm_t *vm, njs_value_t *src, njs_value_t *search_string,
+    size_t index)
+{
+    njs_ret_t          length;
+    const u_char       *p, *end;
+    njs_string_prop_t  string, search;
+
+    (void) njs_string_prop(&search, search_string);
+
+    length = njs_string_prop(&string, src);
+
+    if (index < (size_t) length) {
+
+        p = string.start;
+        end = p + string.size;
+
+        if (string.size == (size_t) length) {
+            /* Byte or ASCII string. */
+            p += index;
+
+        } else {
+            /* UTF-8 string. */
+            p = njs_string_offset(p, end, index);
+        }
+
+        while (p < end) {
+            if (memcmp(p, search.start, search.size) == 0) {
+                return index;
+            }
+
+            index++;
+            p = nxt_utf8_next(p, end);
+        }
+
+    } else if (search.size == 0) {
+        return length;
+    }
+
+    return -1;
+}
+
+
+/*
+ * njs_string_offset() assumes that index is correct
+ * and the optional offset map has been initialized.
+ */
+
+nxt_noinline const u_char *
+njs_string_offset(const u_char *start, const u_char *end, size_t index)
+{
+    uint32_t    *map;
+    nxt_uint_t  skip;
+
+    if (index >= NJS_STRING_MAP_OFFSET) {
+        map = (uint32_t *) nxt_align_ptr(end, sizeof(uint32_t));
+
+        start += map[index / NJS_STRING_MAP_OFFSET - 1];
+    }
+
+    for (skip = index % NJS_STRING_MAP_OFFSET; skip != 0; skip--) {
+        start = nxt_utf8_next(start, end);
+    }
+
+    return start;
+}
+
+
+/*
+ * njs_string_index() assumes that offset is correct
+ * and the optional offset map has been initialized.
+ */
+
+nxt_noinline uint32_t
+njs_string_index(njs_string_prop_t *string, uint32_t offset)
+{
+    uint32_t      *map, last, index;
+    const u_char  *p, *start, *end;
+
+    if (string->size == string->length) {
+        return offset;
+    }
+
+    last = 0;
+    index = 0;
+
+    if (string->length >= NJS_STRING_MAP_OFFSET) {
+
+        end = string->start + string->size;
+        map = (uint32_t *) nxt_align_ptr(end, sizeof(uint32_t));
+
+        while (index + NJS_STRING_MAP_OFFSET < string->length
+               && *map <= offset)
+        {
+            last = *map++;
+            index += NJS_STRING_MAP_OFFSET;
+        }
+    }
+
+    p = string->start + last;
+    start = string->start + offset;
+    end = string->start + string->size;
+
+    while (p < start) {
+        index++;
+        p = nxt_utf8_next(p, end);
+    }
+
+    return index;
+}
+
+
+static njs_ret_t
+njs_string_prototype_search(njs_vm_t *vm, njs_param_t *param)
+{
+    nxt_int_t              index;
+    nxt_uint_t             n;
+    njs_ret_t             ret;
+    njs_string_prop_t     string;
+    njs_regexp_pattern_t  *pattern;
+    int                   captures[3];
+
+    /* TODO: convert object to String. */
+
+    index = 0;
+
+    if (param->nargs != 0) {
+        /*
+         * TODO: convert args[0] to RegExp:
+         *       RegExp    > RegExp
+         *       String    > RegExp
+         *       undefined > //
+         *       otherwise > String > RegExp
+         */
+        pattern = param->args[0].data.u.regexp->pattern;
+
+        index = -1;
+
+        (void) njs_string_prop(&string, param->object);
+
+        n = (string.length != 0 && string.length != string.size);
+
+        if (pattern->code[n] != NULL) {
+            ret = pcre_exec(pattern->code[n], pattern->extra[n],
+                            (char *) string.start, string.size,
+                            0, 0, captures, 3);
+
+            if (ret >= 0) {
+                index = njs_string_index(&string, captures[0]);
+
+            } else if (ret != PCRE_ERROR_NOMATCH) {
+                /* TODO: exception */
+                return NXT_ERROR;
+            }
+        }
+    }
+
+    njs_number_set(&vm->retval, index);
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+njs_string_prototype_match(njs_vm_t *vm, njs_param_t *param)
+{
+    u_char                *start;
+    int32_t               size, length;
+    nxt_uint_t             n, utf8;
+    njs_ret_t             ret;
+    njs_value_t           *args;
+    njs_array_t           *array;
+    njs_regexp_t          *regexp;
+    njs_string_prop_t     string;
+    njs_regexp_pattern_t  *pattern;
+    int                   captures[3];
+
+    /* TODO: empty regexp */
+
+    args = param->args;
+    regexp = args[0].data.u.regexp;
+
+    if (!regexp->pattern->global) {
+        /*
+         * string.match(regexp) is the same as regexp.exec(string)
+         * if the regexp has no global flag.
+         */
+        param->args = param->object;
+        param->object = args;
+        param->nargs = 1;
+
+        return njs_regexp_prototype_exec(vm, param);
+    }
+
+    vm->retval = njs_value_null;
+
+    (void) njs_string_prop(&string, param->object);
+
+    utf8 = 0;
+    n = 0;
+
+    if (string.length != 0) {
+        utf8 = 1;
+        n = 1;
+
+        if (string.length != string.size) {
+            utf8 = 2;
+        }
+    }
+
+    pattern = regexp->pattern;
+
+    if (pattern->code[n] != NULL) {
+        array = NULL;
+
+        if (n != 0) {
+            utf8 = 2;
+        } else if (string.length != 0) {
+            utf8 = 1;
+        } else {
+            utf8 = 1;
+        }
+
+        do {
+            ret = pcre_exec(pattern->code[n], pattern->extra[n],
+                            (char *) string.start, string.size,
+                            0, 0, captures, 3);
+
+            if (ret >= 0) {
+                if (array != NULL) {
+                    if (array->length == array->size) {
+                        ret = njs_array_realloc(vm, array, 0, array->size + 1);
+                        if (nxt_slow_path(ret != NXT_OK)) {
+                            return ret;
+                        }
+                    }
+
+                } else {
+                    array = njs_array_alloc(vm, 0, NJS_ARRAY_SPARE);
+                    if (nxt_slow_path(array == NULL)) {
+                        return NXT_ERROR;
+                    }
+
+                    vm->retval.data.u.array = array;
+                    vm->retval.type = NJS_ARRAY;
+                    vm->retval.data.truth = 1;
+                }
+
+                start = &string.start[captures[0]];
+
+                string.start += captures[1];
+                string.size -= captures[1];
+
+                size = captures[1] - captures[0];
+
+                switch (utf8) {
+                case 0:
+                    length = 0;
+                    break;
+                case 1:
+                    length = size;
+                    break;
+                default:
+                    length = nxt_utf8_length(start, size);
+                    break;
+                }
+
+                ret = njs_string_create(vm, &array->start[array->length],
+                                        start, size, length);
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
+
+                array->length++;
+
+            } else if (ret == PCRE_ERROR_NOMATCH) {
+                break;
+
+            } else {
+                /* TODO: internal error exception */
+                return NXT_ERROR;
+            }
+
+        } while (string.size > 0);
+    }
+
+    regexp->last_index = 0;
+
+    return NXT_OK;
+}
+
+
+njs_ret_t
+njs_value_to_string(njs_vm_t *vm, njs_value_t *dst, const njs_value_t *src)
+{
+    njs_ret_t           ret;
+    njs_param_t         param;
+    njs_object_prop_t   *prop;
+    const njs_value_t   *value;
+    nxt_lvlhsh_query_t  lhq;
+
+    switch (src->type) {
+
+    case NJS_NULL:
+        value = &njs_string_null;
+        break;
+
+    case NJS_VOID:
+        value = &njs_string_void;
+        break;
+
+    case NJS_BOOLEAN:
+        value = njs_is_true(src) ? &njs_string_true : &njs_string_false;
+        break;
+
+    case NJS_NUMBER:
+        return njs_number_to_string(vm, dst, src);
+
+    case NJS_STRING:
+        /* GC: njs_retain(src); */
+        value = src;
+        break;
+
+    case NJS_OBJECT:
+    case NJS_ARRAY:
+    case NJS_FUNCTION:
+    case NJS_REGEXP:
+        lhq.key_hash = NJS_TO_STRING_HASH;
+        lhq.key.len = sizeof("toString") - 1;
+        lhq.key.data = (u_char *) "toString";
+
+        prop = njs_object_property(vm, &src->data.u.array->object, &lhq);
+
+        if (nxt_fast_path(prop != NULL)) {
+            param.object = (njs_value_t *) src;
+            param.args = NULL;
+            param.nargs = 0;
+            param.retval = (njs_index_t) dst;
+
+            ret = njs_function_apply(vm, &prop->value, &param);
+            if (nxt_fast_path(ret == 0)) {
+                *dst = vm->retval;
+                return ret;
+            }
+        }
+
+        return NXT_ERROR;
+
+        /*
+         * TODO:
+         *   function, regexp: find "toString()" in prototype chain:
+         *      function: full function text.
+         *      regex:    full regexp text like "/regexp/gim".
+         */
+
+    case NJS_NATIVE:
+    case NJS_EXTERNAL:
+        value = &njs_string_native;
+        break;
+
+    default:  /* NJS_INVALID */
+        return NXT_ERROR;
+    }
+
+    *dst = *value;
+
+    return NXT_OK;
+}
+
+
+njs_ret_t
+njs_value_to_ext_string(njs_vm_t *vm, nxt_str_t *dst, const njs_value_t *src)
+{
+    u_char       *start;
+    size_t       size;
+    njs_ret_t    ret;
+    njs_value_t  value;
+
+    if (nxt_fast_path(src != NULL)) {
+        ret = njs_value_to_string(vm, &value, src);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            size = value.short_string.size;
+
+            if (size != NJS_STRING_LONG) {
+                start = nxt_mem_cache_alloc(vm->mem_cache_pool, size);
+                if (nxt_slow_path(start == NULL)) {
+                    return NXT_ERROR;
+                }
+
+                memcpy(start, value.short_string.start, size);
+
+            } else {
+                size = value.data.string_size;
+                start = value.data.u.string->start;
+            }
+
+            dst->len = size;
+            dst->data = start;
+        }
+
+        return ret;
+    }
+
+    dst->len = 0;
+    dst->data = NULL;
+
+    return NXT_OK;
+}
+
+
+double
+njs_string_to_number(njs_value_t *value)
+{
+    double        num;
+    size_t        size;
+    nxt_bool_t     minus;
+    const u_char  *p, *end;
+
+    size = value->short_string.size;
+
+    if (size != NJS_STRING_LONG) {
+        p = value->short_string.start;
+
+    } else {
+        size = value->data.string_size;
+        p = value->data.u.string->start;
+    }
+
+    end = p + size;
+
+    while (p < end) {
+        if (*p != ' ' && *p != '\t') {
+            break;
+        }
+
+        p++;
+    }
+
+    if (p == end) {
+        return 0.0;
+    }
+
+    minus = 0;
+
+    if (*p == '+') {
+        p++;
+
+    } else if (*p == '-') {
+        p++;
+        minus = 1;
+    }
+
+    if (*p >= '0' && *p <= '9') {
+        num = njs_number_parse(&p, end);
+
+    } else {
+        return NJS_NAN;
+    }
+
+    while (p < end) {
+        if (*p != ' ' && *p != '\t') {
+            return NJS_NAN;
+        }
+
+        p++;
+    }
+
+    return minus ? -num : num;
+}
+
+
+static const njs_object_prop_t  njs_string_prototype_properties[] =
+{
+    { njs_getter(njs_string_prototype_get_prototype),
+      njs_string("__proto__"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_string_prototype_length),
+      njs_string("length"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_string_prototype_bytes),
+      njs_string("bytes"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_getter(njs_string_prototype_utf8),
+      njs_string("utf8"),
+      NJS_NATIVE_GETTER, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_concat, 0),
+      njs_string("concat"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_slice, 0),
+      njs_string("slice"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_substring, 0),
+      njs_string("substring"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_substr, 0),
+      njs_string("substr"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_char_at, 0),
+      njs_string("charAt"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_char_code_at, 0),
+      njs_string("charCodeAt"),
+      NJS_METHOD, 0, 0, 0, },
+
+    /* ECMAScript 6, codePointAt(). */
+
+    { njs_native_function(njs_string_prototype_char_code_at, 0),
+      njs_string("codePointAt"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_index_of, 0),
+      njs_string("indexOf"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_last_index_of, 0),
+      njs_string("lastIndexOf"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_search, 0),
+      njs_string("search"),
+      NJS_METHOD, 0, 0, 0, },
+
+    { njs_native_function(njs_string_prototype_match, 0),
+      njs_string("match"),
+      NJS_METHOD, 0, 0, 0, },
+};
+
+
+nxt_int_t
+njs_string_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash)
+{
+    return njs_object_hash_create(vm, hash, njs_string_prototype_properties,
+                                  nxt_nitems(njs_string_prototype_properties));
+}
+
+
+static nxt_int_t
+njs_values_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    njs_value_t  *value;
+
+    value = data;
+
+    if (lhq->key.len == sizeof(njs_value_t)
+        && memcmp(lhq->key.data, value, sizeof(njs_value_t)) == 0)
+    {
+        return NXT_OK;
+    }
+
+    if (value->type == NJS_STRING
+        && value->data.string_size == lhq->key.len
+        && memcmp(value->data.u.string->start, lhq->key.data, lhq->key.len)
+           == 0)
+    {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static const nxt_lvlhsh_proto_t  njs_values_hash_proto
+    nxt_aligned(64) =
+{
+    NXT_LVLHSH_DEFAULT,
+    0,
+    njs_values_hash_test,
+    njs_lvlhsh_alloc,
+    njs_lvlhsh_free,
+};
+
+
+njs_index_t
+njs_value_index(njs_vm_t *vm, njs_parser_t *parser, const njs_value_t *src)
+{
+    u_char              *start;
+    uint32_t            value_size, size, length;
+    nxt_int_t           ret;
+    njs_value_t         *value;
+    njs_string_t        *string;
+    nxt_lvlhsh_query_t  lhq;
+
+    if (src->type != NJS_STRING || src->short_string.size != NJS_STRING_LONG) {
+        size = sizeof(njs_value_t);
+        start = (u_char *) src;
+
+    } else {
+        size = src->data.string_size;
+        start = src->data.u.string->start;
+    }
+
+    lhq.key_hash = nxt_djb_hash(start, size);
+    lhq.key.len = size;
+    lhq.key.data = start;
+    lhq.proto = &njs_values_hash_proto;
+
+    if (nxt_lvlhsh_find(&vm->values_hash, &lhq) == NXT_OK) {
+        value = lhq.value;
+
+    } else if (nxt_lvlhsh_find(&parser->values_hash, &lhq) == NXT_OK) {
+        value = lhq.value;
+
+    } else {
+        value_size = 0;
+
+        if (start != (u_char *) src) {
+            /* Long string value is allocated together with string. */
+            value_size = sizeof(njs_value_t) + sizeof(njs_string_t);
+
+            length = src->data.u.string->length;
+
+            if (size != length && length > NJS_STRING_MAP_OFFSET) {
+                size = nxt_align_size(size, sizeof(uint32_t));
+                size += ((length - 1) / NJS_STRING_MAP_OFFSET)
+                        * sizeof(uint32_t);
+            }
+        }
+
+        value = nxt_mem_cache_align(vm->mem_cache_pool, sizeof(njs_value_t),
+                                    value_size + size);
+        if (nxt_slow_path(value == NULL)) {
+            return NJS_INDEX_NONE;
+        }
+
+        *value = *src;
+
+        if (start != (u_char *) src) {
+            string = (njs_string_t *) ((u_char *) value + sizeof(njs_value_t));
+            value->data.u.string = string;
+
+            string->start = (u_char *) string + sizeof(njs_string_t);
+            string->length = src->data.u.string->length;
+            string->retain = 0xffff;
+
+            memcpy(string->start, start, size);
+        }
+
+        lhq.replace = 0;
+        lhq.value = value;
+        lhq.pool = vm->mem_cache_pool;
+
+        ret = nxt_lvlhsh_insert(&parser->values_hash, &lhq);
+
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NJS_INDEX_NONE;
+        }
+    }
+
+    if (start != (u_char *) src) {
+        /*
+         * The source node value must be updated with the shared value
+         * allocated from the permanent memory pool because the node
+         * value can be used as a variable initial value.
+         */
+        *(njs_value_t *) src = *value;
+    }
+
+    return (njs_index_t) value;
+}
diff --git a/njs/njs_string.h b/njs/njs_string.h
new file mode 100644 (file)
index 0000000..e134bda
--- /dev/null
@@ -0,0 +1,108 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_STRING_H_INCLUDED_
+#define _NJS_STRING_H_INCLUDED_
+
+
+/*
+ * nJSVM supports two string variants:
+ *
+ * 1) short strings which size is lesser than 14 bytes, these strings are
+ *    stored inside njs_value_t (see njs_vm.h for details);
+ *
+ * 2) and long strings using additional njs_string_t structure.
+ *    This structure has the start field to support external strings.
+ *    The long strings can have optional UTF-8 offset map.
+ *
+ * The number of the string variants is limited to 2 variants to minimize
+ * overhead of processing string fields.
+ */
+
+/* The maximum signed int32_t. */
+#define NJS_STRING_MAX_LENGTH  0x7fffffff
+
+/*
+ * Should be power of two to use shift and binary and operations instead of
+ * division and remainder operations but no less than 16 because the maximum
+ * length of short string inlined in njs_value_t is less than 16 bytes.
+ */
+#define NJS_STRING_MAP_OFFSET  32
+
+/*
+ * The JavaScript standard states that strings are stored in UTF-16.
+ * nJSVM allows to store any byte sequences in strings.  A size of the
+ * string in bytes is stored in the size field.  If a byte sequence is
+ * valid UTF-8 string then its length is stored in the UTF-8 length field.
+ * Otherwise, the length field is zero.  If a string is UTF-8 string then
+ * string functions work with UTF-8 characters positions and lengths.
+ * Othersise they work with byte positions and lengths.  Using UTF-8
+ * encoding does not allow to get quickly a character at specified position.
+ * To speed up this search a map of offsets is stored after the UTF-8 string.
+ * The map is aligned to uint32_t and contains byte positions of each
+ * NJS_STRING_MAP_OFFSET UTF-8 character except zero position.  The map
+ * can be allocated and updated on demand.  If a string come outside
+ * JavaScript as byte sequnece just to be concatenated or to be used in
+ * regular expressions the offset map is not required.
+ *
+ * The map is not allocated:
+ * 1) if the length is zero hence it is a byte string;
+ * 2) if the size and length are equal so the string contains only ASCII
+ *    characters map is not required;
+ * 3) if the length is less than NJS_STRING_MAP_OFFSET.
+ *
+ * The current implementation does not support Unicode surrogate pairs.
+ * If offset in map points to surrogate pair, it the previous offset
+ * should be used and so on until start of the string.
+ */
+
+struct njs_string_s {
+    u_char    *start;
+    uint32_t  length;   /* Length in UTF-8 characters. */
+    uint32_t  retain;   /* Link counter. */
+};
+
+
+typedef struct {
+    size_t    size;
+    size_t    length;
+    u_char    *start;
+} njs_string_prop_t;
+
+
+u_char *njs_string_alloc(njs_vm_t *vm, njs_value_t *value, uint32_t size,
+    uint32_t length)
+    NXT_MALLOC_LIKE;
+void njs_string_copy(njs_value_t *dst, njs_value_t *src);
+njs_ret_t njs_string_validate(njs_vm_t *vm, njs_string_prop_t *string,
+    njs_value_t *value);
+nxt_noinline size_t njs_string_prop(njs_string_prop_t *string,
+    njs_value_t *value);
+njs_ret_t njs_string_ctor_function(njs_vm_t *vm, njs_param_t *param);
+nxt_int_t njs_string_function_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+void njs_string_offset_map_init(const u_char *start, size_t size);
+nxt_bool_t njs_string_eq(const njs_value_t *val1, const njs_value_t *val2);
+nxt_int_t njs_string_cmp(const njs_value_t *val1, const njs_value_t *val2);
+njs_ret_t njs_string_slice(njs_vm_t *vm, njs_value_t *dst,
+    const njs_string_prop_t *string, size_t string_length, size_t index,
+    size_t length);
+const u_char *njs_string_offset(const u_char *start, const u_char *end,
+    size_t index);
+nxt_noinline uint32_t njs_string_index(njs_string_prop_t *string,
+    uint32_t offset);
+njs_ret_t njs_string_prototype_concat(njs_vm_t *vm, njs_param_t *param);
+njs_ret_t njs_value_to_string(njs_vm_t *vm, njs_value_t *dst,
+    const njs_value_t *src);
+njs_ret_t njs_value_to_ext_string(njs_vm_t *vm, nxt_str_t *dst,
+    const njs_value_t *src);
+double njs_string_to_number(njs_value_t *value);
+nxt_int_t njs_string_prototype_hash(njs_vm_t *vm, nxt_lvlhsh_t *hash);
+
+njs_index_t njs_value_index(njs_vm_t *vm, njs_parser_t *parser,
+    const njs_value_t *src);
+
+
+#endif /* _NJS_STRING_H_INCLUDED_ */
diff --git a/njs/njs_variable.c b/njs/njs_variable.c
new file mode 100644 (file)
index 0000000..0d2c06b
--- /dev/null
@@ -0,0 +1,161 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_utf8.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_regexp.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+static njs_variable_t *njs_variable_alloc(njs_vm_t *vm,
+    njs_parser_t *parser, nxt_str_t *name);
+
+
+static nxt_int_t
+njs_variables_hash_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    njs_variable_t  *var;
+
+    var = data;
+
+    if (lhq->key.len == var->name_len
+        && memcmp(var->name_start, lhq->key.data, lhq->key.len) == 0)
+    {
+        return NXT_OK;
+    }
+
+
+    return NXT_DECLINED;
+}
+
+
+static const nxt_lvlhsh_proto_t  njs_variables_hash_proto
+    nxt_aligned(64) =
+{
+    NXT_LVLHSH_DEFAULT,
+    0,
+    njs_variables_hash_test,
+    njs_lvlhsh_alloc,
+    njs_lvlhsh_free,
+};
+
+
+njs_variable_t *
+njs_parser_variable(njs_vm_t *vm, njs_parser_t *parser, nxt_uint_t *level)
+{
+    nxt_int_t            ret;
+    nxt_uint_t           n;
+    njs_parser_t         *scope;
+    njs_variable_t       *var;
+    nxt_lvlhsh_query_t   lhq;
+
+    level = 0;
+
+    lhq.key_hash = parser->lexer->key_hash;
+    lhq.key = parser->lexer->text;
+    lhq.proto = &njs_variables_hash_proto;
+
+    scope = parser;
+
+    do {
+        var = scope->arguments->start;
+        n = scope->arguments->items;
+
+        while (n != 0) {
+            if (lhq.key.len == var->name_len
+                && memcmp(var->name_start, lhq.key.data, lhq.key.len) == 0)
+            {
+                return var;
+            }
+
+            var++;
+            n--;
+        }
+
+        if (nxt_lvlhsh_find(&scope->variables_hash, &lhq) == NXT_OK) {
+            return lhq.value;
+        }
+
+        scope = scope->parent;
+        level++;
+
+    } while (scope != NULL);
+
+    level = 0;
+
+    if (nxt_lvlhsh_find(&vm->variables_hash, &lhq) == NXT_OK) {
+        return lhq.value;
+    }
+
+    var = njs_variable_alloc(vm, parser, &parser->lexer->text);
+    if (nxt_slow_path(var == NULL)) {
+        return NULL;
+    }
+
+    lhq.replace = 0;
+    lhq.value = var;
+    lhq.pool = vm->mem_cache_pool;
+
+    ret = nxt_lvlhsh_insert(&parser->variables_hash, &lhq);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        return var;
+    }
+
+    return NULL;
+}
+
+
+static njs_variable_t *
+njs_variable_alloc(njs_vm_t *vm, njs_parser_t *parser, nxt_str_t *name)
+{
+    njs_value_t     *value;
+    njs_variable_t  *var;
+
+    var = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_variable_t));
+
+    if (nxt_fast_path(var != NULL)) {
+        var->name_start = nxt_mem_cache_alloc(vm->mem_cache_pool, name->len);
+
+        if (nxt_fast_path(var->name_start != NULL)) {
+
+            memcpy(var->name_start, name->data, name->len);
+            var->name_len = name->len;
+
+            value = nxt_vector_add(parser->scope_values, &njs_array_mem_proto,
+                                  vm->mem_cache_pool);
+            if (nxt_fast_path(value != NULL)) {
+                 *value = njs_value_void;
+                 var->index = njs_parser_index(parser, parser->scope);
+                 return var;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+
+njs_value_t *
+njs_variable_value(njs_parser_t *parser, njs_index_t index)
+{
+    u_char  *scope;
+
+    scope = parser->scope_values->start;
+
+    return (njs_value_t *) (scope + (njs_offset(index) - parser->scope_offset));
+}
diff --git a/njs/njs_variable.h b/njs/njs_variable.h
new file mode 100644 (file)
index 0000000..edb500d
--- /dev/null
@@ -0,0 +1,34 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_VARIABLE_H_INCLUDED_
+#define _NJS_VARIABLE_H_INCLUDED_
+
+
+typedef enum {
+    NJS_VARIABLE_CREATED = 0,
+    NJS_VARIABLE_PENDING,
+    NJS_VARIABLE_USED,
+    NJS_VARIABLE_SET,
+    NJS_VARIABLE_DECLARED,
+} njs_variable_state_t;
+
+
+typedef struct {
+    u_char                *name_start;
+    uint16_t              name_len;
+    njs_variable_state_t  state:8;  /* 3 bits */
+
+    njs_index_t           index;
+} njs_variable_t;
+
+
+njs_variable_t *njs_parser_variable(njs_vm_t *vm, njs_parser_t *parser,
+    nxt_uint_t *level);
+njs_value_t *njs_variable_value(njs_parser_t *parser, njs_index_t index);
+
+
+#endif /* _NJS_VARIABLE_H_INCLUDED_ */
diff --git a/njs/njs_vm.c b/njs/njs_vm.c
new file mode 100644 (file)
index 0000000..b09e3b9
--- /dev/null
@@ -0,0 +1,2732 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_number.h>
+#include <njs_string.h>
+#include <njs_object.h>
+#include <njs_object_hash.h>
+#include <njs_array.h>
+#include <njs_function.h>
+#include <njs_regexp.h>
+#include <njs_extern.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+/* The values must not coincide with NXT_OK, NXT_ERROR, and NXT_DECLINED. */
+#define NJS_PRIMITIVE_VALUE        1
+#define NJS_ARRAY_VALUE            2
+#define NJS_EXTERNAL_VALUE         3
+
+#define NJS_PROPERTY_QUERY_GET     0
+#define NJS_PROPERTY_QUERY_IN      0
+#define NJS_PROPERTY_QUERY_DELETE  1
+#define NJS_PROPERTY_QUERY_SET     2
+
+
+typedef struct {
+    nxt_lvlhsh_query_t             lhq;
+    njs_value_t                    value;
+    njs_object_t                   *prototype;
+    uint8_t                        query;
+    uint8_t                        shared;
+} njs_property_query_t;
+
+
+typedef struct {
+    int32_t                        index;
+    nxt_lvlhsh_each_t              lhe;
+} njs_property_each_t;
+
+
+/*
+ * These functions are forbidden to inline to minimize JavaScript VM
+ * interpreter memory footprint.  The size is less than 8K on AMD64
+ * and should fit in CPU L1 instruction cache.
+ */
+
+static nxt_noinline njs_ret_t njs_property_query(njs_vm_t *vm,
+    njs_property_query_t *pq, njs_value_t *object, njs_value_t *property);
+static njs_ret_t njs_array_property_query(njs_vm_t *vm,
+    njs_property_query_t *pq, njs_value_t *object, int32_t index);
+static njs_ret_t njs_object_property_query(njs_vm_t *vm,
+    njs_property_query_t *pq, njs_value_t *value, njs_object_t *object);
+static njs_ret_t njs_function_private_copy(njs_vm_t *vm,
+    njs_property_query_t *pq);
+static nxt_noinline uint32_t njs_integer_value(double num);
+static nxt_noinline njs_ret_t njs_values_equal(njs_value_t *val1,
+    njs_value_t *val2);
+static nxt_noinline njs_ret_t njs_values_compare(njs_value_t *val1,
+    njs_value_t *val2);
+static nxt_noinline nxt_bool_t njs_values_strict_equal(njs_value_t *val1,
+    njs_value_t *val2);
+void njs_debug(njs_index_t index, njs_value_t *value);
+
+
+const njs_value_t  njs_value_null =         njs_value(NJS_NULL, 0, 0.0);
+const njs_value_t  njs_value_void =         njs_value(NJS_VOID, 0, NJS_NAN);
+const njs_value_t  njs_value_false =        njs_value(NJS_BOOLEAN, 0, 0.0);
+const njs_value_t  njs_value_true =         njs_value(NJS_BOOLEAN, 1, 1.0);
+const njs_value_t  njs_value_zero =         njs_value(NJS_NUMBER, 0, 0.0);
+const njs_value_t  njs_value_nan =          njs_value(NJS_NUMBER, 0, NJS_NAN);
+
+const njs_value_t  njs_string_empty =       njs_string("");
+const njs_value_t  njs_string_comma =       njs_string(",");
+const njs_value_t  njs_string_void =        njs_string("undefined");
+const njs_value_t  njs_string_null =        njs_string("null");
+const njs_value_t  njs_string_boolean =     njs_string("boolean");
+const njs_value_t  njs_string_false =       njs_string("false");
+const njs_value_t  njs_string_true =        njs_string("true");
+const njs_value_t  njs_string_number =      njs_string("number");
+const njs_value_t  njs_string_minus_infinity =
+                                            njs_string("-Infinity");
+const njs_value_t  njs_string_plus_infinity =
+                                            njs_string("Infinity");
+const njs_value_t  njs_string_nan =         njs_string("NaN");
+const njs_value_t  njs_string_string =      njs_string("string");
+const njs_value_t  njs_string_object =      njs_string("object");
+const njs_value_t  njs_string_function =    njs_string("function");
+const njs_value_t  njs_string_native =      njs_string("[native code]");
+const njs_value_t  njs_string_prototype =   njs_string("prototype");
+const njs_value_t  njs_string_constructor = njs_string("constructor");
+
+const njs_value_t  njs_string_object_null = njs_string("[object Null]");
+const njs_value_t  njs_string_object_undefined =
+                                         njs_long_string("[object Undefined]");
+const njs_value_t  njs_string_object_boolean =
+                                         njs_long_string("[object Boolean]");
+const njs_value_t  njs_string_object_number =
+                                         njs_long_string("[object Number]");
+const njs_value_t  njs_string_object_string =
+                                         njs_long_string("[object String]");
+const njs_value_t  njs_string_object_object =
+                                         njs_long_string("[object Object]");
+const njs_value_t  njs_string_object_array = njs_string("[object Array]");
+const njs_value_t  njs_string_object_function =
+                                         njs_long_string("[object Function]");
+const njs_value_t  njs_string_object_regexp =
+                                         njs_long_string("[object RegExp]");
+
+const njs_value_t  njs_exception_syntax_error =    njs_string("SyntaxError");
+const njs_value_t  njs_exception_reference_error = njs_string("ReferenceError");
+const njs_value_t  njs_exception_type_error =      njs_string("TypeError");
+const njs_value_t  njs_exception_range_error =     njs_string("RangeError");
+const njs_value_t  njs_exception_memory_error =    njs_string("MemoryError");
+
+
+/*
+ * The nJSVM is optimized for an ABIs where the first several arguments
+ * are passed in registers (AMD64, ARM32/64): two pointers to the operand
+ * values is passed as arguments although they are not always used.
+ */
+
+nxt_int_t
+njs_vmcode_interpreter(njs_vm_t *vm)
+{
+    u_char                *catch;
+    njs_ret_t             ret;
+    njs_value_t           *retval, *value1, *value2;
+    njs_frame_t           *frame;
+    njs_native_frame_t    *previous;
+    njs_vmcode_generic_t  *vmcode;
+
+    for ( ;; ) {
+
+    again:
+        for ( ;; ) {
+
+            vmcode = (njs_vmcode_generic_t *) vm->current;
+
+            /*
+             * The first operand index is passed as the value2 to
+             *   njs_vmcode_jump(),
+             *   njs_vmcode_if_true_jump(),
+             *   njs_vmcode_if_false_jump(),
+             *   njs_vmcode_validate(),
+             *   njs_vmcode_call(),
+             *   njs_vmcode_return(),
+             *   njs_vmcode_try_start(),
+             *   njs_vmcode_try_next(),
+             *   njs_vmcode_try_end(),
+             *   njs_vmcode_catch().
+             *   njs_vmcode_throw().
+             *   njs_vmcode_stop().
+             */
+            value2 = (njs_value_t *) vmcode->operand1;
+            value1 = NULL;
+
+            switch (vmcode->code.operands) {
+
+            case NJS_VMCODE_3OPERANDS:
+                value2 = njs_vmcode_operand(vm, vmcode->operand3);
+
+            case NJS_VMCODE_2OPERANDS:
+                value1 = njs_vmcode_operand(vm, vmcode->operand2);
+            }
+
+            ret = vmcode->code.operation(vm, value1, value2);
+
+            /*
+             * On success an operation returns size of the bytecode,
+             * a jump offset or zero after the call or return operations.
+             * Jumps can return a negative offset.  Compilers can generate
+             *    (ret < 0 && ret >= NJS_PASS)
+             * as a single unsigned comparision.
+             */
+
+            if (nxt_slow_path(ret < 0 && ret >= NJS_PASS)) {
+                break;
+            }
+
+            vm->current += ret;
+
+            if (vmcode->code.retval) {
+                retval = njs_vmcode_operand(vm, vmcode->operand1);
+                //njs_release(vm, retval);
+                *retval = vm->retval;
+            }
+        }
+
+        switch (ret) {
+
+        case NJS_TRAP_NUMBER:
+            ret = njs_vmcode_trap(vm,
+                             vm->number_trap + sizeof(njs_vmcode_to_number_t),
+                             value1, value1, 0);
+
+            if (nxt_fast_path(ret == NXT_OK)) {
+                goto again;
+            }
+
+            ret = NXT_ERROR;
+            break;
+
+        case NJS_TRAP_LVALUE_NUMBER:
+            ret = njs_vmcode_trap(vm,
+                             vm->number_trap + sizeof(njs_vmcode_to_number_t),
+                             value1, value2, 1);
+
+            if (nxt_fast_path(ret == NXT_OK)) {
+                goto again;
+            }
+
+            ret = NXT_ERROR;
+            break;
+
+        case NJS_TRAP_NUMBERS:
+            ret = njs_vmcode_trap(vm, vm->number_trap, value1, value2, 0);
+
+            if (nxt_fast_path(ret == NXT_OK)) {
+                goto again;
+            }
+
+            ret = NXT_ERROR;
+            break;
+
+        case NJS_TRAP_STRINGS:
+            ret = njs_vmcode_trap(vm, vm->string_trap, value1, value2, 0);
+
+            if (nxt_fast_path(ret == NXT_OK)) {
+                goto again;
+            }
+
+            ret = NXT_ERROR;
+            break;
+        }
+
+        if (ret == NXT_ERROR) {
+
+            for ( ;; ) {
+                frame = (njs_frame_t *) vm->frame;
+                catch = frame->native.u.exception.catch;
+
+                if (catch != NULL) {
+                    vm->current = catch;
+                    goto again;
+                }
+
+                previous = frame->native.previous;
+                if (previous == NULL) {
+                    return ret;
+                }
+
+                vm->frame = previous;
+
+                /* GC: NJS_SCOPE_ARGUMENTS and NJS_SCOPE_LOCAL. */
+
+                vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = previous->arguments;
+                vm->scopes[NJS_SCOPE_LOCAL] = frame->prev_local;
+                vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->prev_arguments;
+
+                if (frame->native.start) {
+                    nxt_mem_cache_free(vm->mem_cache_pool, frame);
+                }
+            }
+        }
+
+        /* NXT_AGAIN, NXT_DONE */
+
+        return ret;
+    }
+}
+
+
+nxt_noinline void
+njs_value_retain(njs_value_t *value)
+{
+    njs_string_t  *string;
+
+    if (value->type == NJS_STRING) {
+
+        if (value->data.external0 != 0xff) {
+            string = value->data.u.string;
+
+            nxt_thread_log_debug("retain:%uxD \"%*s\"", string->retain,
+                                 value->data.string_size, string->start);
+
+            if (string->retain != 0xffff) {
+                string->retain++;
+            }
+        }
+    }
+}
+
+
+nxt_noinline void
+njs_value_release(njs_vm_t *vm, njs_value_t *value)
+{
+    njs_string_t  *string;
+
+    if (value->type == NJS_STRING) {
+
+        if (value->data.external0 != 0xff) {
+            string = value->data.u.string;
+
+            nxt_thread_log_debug("release:%uxD \"%*s\"", string->retain,
+                                 value->data.string_size, string->start);
+
+            if (string->retain != 0xffff) {
+                string->retain--;
+
+#if 0
+                if (string->retain == 0) {
+                    if ((u_char *) string + sizeof(njs_string_t)
+                        != string->start)
+                    {
+                        nxt_memcache_pool_free(vm->mem_cache_pool,
+                                               string->start);
+                    }
+
+                    nxt_memcache_pool_free(vm->mem_cache_pool, string);
+                }
+#endif
+            }
+        }
+    }
+}
+
+
+njs_ret_t
+njs_vmcode_object_create(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    njs_object_t  *object;
+
+    object = njs_object_alloc(vm);
+
+    if (nxt_fast_path(object != NULL)) {
+        vm->retval.data.u.object = object;
+        vm->retval.type = NJS_OBJECT;
+        vm->retval.data.truth = 1;
+
+        return sizeof(njs_vmcode_object_t);
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_array_create(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    uint32_t            size;
+    njs_array_t         *array;
+    njs_value_t         *value;
+    njs_vmcode_array_t  *code;
+
+    code = (njs_vmcode_array_t *) vm->current;
+
+    array = njs_array_alloc(vm, code->length, NJS_ARRAY_SPARE);
+
+    if (nxt_fast_path(array != NULL)) {
+        size = array->size;
+        value = array->start;
+
+        do {
+            njs_set_invalid(value);
+            value++;
+            size--;
+        } while (size != 0);
+
+        vm->retval.data.u.array = array;
+        vm->retval.type = NJS_ARRAY;
+        vm->retval.data.truth = 1;
+
+        return sizeof(njs_vmcode_array_t);
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_function_create(njs_vm_t *vm, njs_value_t *invld1,
+    njs_value_t *invld2)
+{
+    njs_function_t                *func;
+    njs_vmcode_function_create_t  *code;
+
+    func = nxt_mem_cache_zalign(vm->mem_cache_pool, sizeof(njs_value_t),
+                                sizeof(njs_function_t));
+
+    if (nxt_fast_path(func != NULL)) {
+        func->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+        func->args_offset = 1;
+
+        code = (njs_vmcode_function_create_t *) vm->current;
+        func->code.script = code->function;
+        vm->retval.data.u.function = func;
+        vm->retval.type = NJS_FUNCTION;
+        vm->retval.data.truth = 1;
+
+        return sizeof(njs_vmcode_function_create_t);
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_regexp_create(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    njs_regexp_t         *regexp;
+    njs_vmcode_regexp_t  *code;
+
+    code = (njs_vmcode_regexp_t *) vm->current;
+
+    regexp = njs_regexp_alloc(vm, code->pattern);
+
+    if (nxt_fast_path(regexp != NULL)) {
+        vm->retval.data.u.regexp = regexp;
+        vm->retval.type = NJS_REGEXP;
+        vm->retval.data.truth = 1;
+
+        return sizeof(njs_vmcode_regexp_t);
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *property)
+{
+    size_t                length;
+    double                num;
+    int32_t               index;
+    uintptr_t             data;
+    njs_ret_t             ret;
+    njs_value_t           *val;
+    njs_extern_t          *ext;
+    njs_object_prop_t     *prop;
+    njs_string_prop_t     string;
+    const njs_value_t     *retval;
+    njs_property_query_t  pq;
+
+    pq.query = NJS_PROPERTY_QUERY_GET;
+
+    ret = njs_property_query(vm, &pq, object, property);
+
+    retval = &njs_value_void;
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        prop = pq.lhq.value;
+
+        switch (prop->type) {
+
+        case NJS_METHOD:
+            if (pq.shared) {
+                ret = njs_function_private_copy(vm, &pq);
+
+                if (nxt_slow_path(ret != NXT_OK)) {
+                    return ret;
+                }
+
+                prop = pq.lhq.value;
+            }
+
+        case NJS_PROPERTY:
+            retval = &prop->value;
+            break;
+
+        case NJS_NATIVE_GETTER:
+            ret = prop->value.data.u.getter(vm, object);
+
+            if (nxt_fast_path(ret == NXT_OK)) {
+                return sizeof(njs_vmcode_prop_get_t);
+            }
+
+            return ret;
+
+        default:
+            nxt_thread_log_alert("invalid property get type:%d", prop->type);
+
+            return NXT_ERROR;
+        }
+
+    } else if (nxt_fast_path(ret == NJS_ARRAY_VALUE)) {
+
+        val = pq.lhq.value;
+
+        if (njs_is_valid(val)) {
+            retval = val;
+        }
+
+    } else if (nxt_fast_path(ret == NJS_EXTERNAL_VALUE)) {
+
+        ext = object->data.u.external;
+
+        ret = nxt_lvlhsh_find(&ext->hash, &pq.lhq);
+
+        if (ret == NXT_OK) {
+            ext = pq.lhq.value;
+
+            if ((ext->type & NJS_EXTERN_OBJECT) != 0) {
+                retval = &ext->value;
+                goto done;
+            }
+
+            data = ext->data;
+
+        } else {
+            data = (uintptr_t) &pq.lhq.key;
+        }
+
+        vm->retval = njs_value_void;
+
+        ret = ext->get(vm, &vm->retval, vm->external[ext->object], data);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        /* The vm->retval is already retained by ext->get(). */
+
+        return sizeof(njs_vmcode_prop_get_t);
+
+    } else if (nxt_fast_path(ret == NJS_PRIMITIVE_VALUE)) {
+
+        if (njs_is_string(object)) {
+
+            /* string[n]. */
+
+            num = njs_value_to_number(property);
+            index = (int32_t) num;
+
+            if (index >= 0 && index == num) {
+                length = njs_string_prop(&string, object);
+
+                /* A single codepoint string fits in vm->retval cannot fail. */
+                (void) njs_string_slice(vm, &vm->retval, &string, length,
+                                        index, 1);
+
+                if (nxt_fast_path(vm->retval.data.truth != 0)) {
+                    /* Non-empty string. */
+                    return sizeof(njs_vmcode_prop_get_t);
+                }
+            }
+        }
+
+    } else if (ret == NXT_ERROR) {
+        return ret;
+    }
+
+done:
+
+    vm->retval = *retval;
+
+    /* GC: njs_retain(retval) */
+
+    return sizeof(njs_vmcode_prop_get_t);
+}
+
+
+njs_ret_t
+njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *property)
+{
+    nxt_str_t               s;
+    uintptr_t              data;
+    njs_ret_t              ret;
+    njs_value_t            *p, *value;
+    njs_extern_t           *ext;
+    njs_object_prop_t      *prop;
+    njs_property_query_t   pq;
+    njs_vmcode_prop_set_t  *code;
+
+    code = (njs_vmcode_prop_set_t *) vm->current;
+    value = njs_vmcode_operand(vm, code->value);
+
+    pq.query = NJS_PROPERTY_QUERY_SET;
+
+    ret = njs_property_query(vm, &pq, object, property);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+
+        prop = pq.lhq.value;
+
+    } else if (nxt_fast_path(ret == NJS_ARRAY_VALUE)) {
+
+        p = pq.lhq.value;
+        *p = *value;
+
+        return sizeof(njs_vmcode_prop_set_t);
+
+    } else if (nxt_fast_path(ret == NXT_DECLINED)) {
+
+        prop = njs_object_prop_alloc(vm, &pq.value);
+        if (nxt_slow_path(prop == NULL)) {
+            return NXT_ERROR;
+        }
+
+        pq.lhq.replace = 0;
+        pq.lhq.value = prop;
+        pq.lhq.pool = vm->mem_cache_pool;
+
+        ret = nxt_lvlhsh_insert(&object->data.u.object->hash, &pq.lhq);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            /* Only NXT_ERROR can be returned here. */
+            return ret;
+        }
+
+    } else if (nxt_fast_path(ret == NJS_EXTERNAL_VALUE)) {
+
+        ext = object->data.u.external;
+
+        ret = nxt_lvlhsh_find(&ext->hash, &pq.lhq);
+
+        if (ret == NXT_OK) {
+            ext = pq.lhq.value;
+            data = ext->data;
+
+        } else {
+            data = (uintptr_t) &pq.lhq.key;
+        }
+
+        ret = njs_value_to_ext_string(vm, &s, value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        /* TODO retain value if it is string. */
+
+        ret = ext->set(vm, vm->external[ext->object], data, &s);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return ret;
+        }
+
+        return sizeof(njs_vmcode_prop_set_t);
+
+    } else if (ret == NJS_PRIMITIVE_VALUE) {
+        return sizeof(njs_vmcode_prop_set_t);
+
+    } else {
+        return ret;
+    }
+
+    prop->value = *value;
+
+    return sizeof(njs_vmcode_prop_set_t);
+}
+
+
+njs_ret_t
+njs_vmcode_property_in(njs_vm_t *vm, njs_value_t *property, njs_value_t *object)
+{
+    uintptr_t             data;
+    njs_ret_t             ret;
+    njs_value_t           *value;
+    njs_extern_t          *ext;
+    const njs_value_t     *retval;
+    njs_property_query_t  pq;
+
+    retval = &njs_value_false;
+
+    pq.query = NJS_PROPERTY_QUERY_IN;
+
+    ret = njs_property_query(vm, &pq, object, property);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        retval = &njs_value_true;
+        goto done;
+
+    } else if (nxt_fast_path(ret == NJS_ARRAY_VALUE)) {
+        value = pq.lhq.value;
+        retval = njs_is_valid(value) ? &njs_value_true : &njs_value_false;
+        goto done;
+
+    } else if (nxt_fast_path(ret == NJS_EXTERNAL_VALUE)) {
+
+        ext = object->data.u.external;
+
+        ret = nxt_lvlhsh_find(&ext->hash, &pq.lhq);
+
+        if (ret == NXT_OK) {
+            ext = pq.lhq.value;
+
+            if ((ext->type & NJS_EXTERN_OBJECT) != 0) {
+                retval = &njs_value_true;
+                goto done;
+            }
+
+            data = ext->data;
+
+        } else {
+            data = (uintptr_t) &pq.lhq.key;
+        }
+
+        ret = ext->find(vm, vm->external[ext->object], data, 0);
+
+        if (nxt_slow_path(ret == NXT_ERROR)) {
+            return ret;
+        }
+
+        if (ret == NXT_OK) {
+            retval = &njs_value_true;
+        }
+
+        goto done;
+
+    } else if (nxt_fast_path(ret == NXT_DECLINED)) {
+        retval = &njs_value_false;
+        goto done;
+
+    } else if (nxt_fast_path(ret != NXT_ERROR)) {
+        vm->exception = &njs_exception_type_error;
+    }
+
+    return NXT_ERROR;
+
+done:
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_3addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *property)
+{
+    uintptr_t             data;
+    njs_ret_t             ret;
+    njs_value_t           *value;
+    njs_extern_t          *ext;
+    const njs_value_t     *retval;
+    njs_object_prop_t     *prop;
+    njs_property_query_t  pq;
+
+    retval = &njs_value_false;
+
+    pq.query = NJS_PROPERTY_QUERY_DELETE;
+
+    ret = njs_property_query(vm, &pq, object, property);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        prop = pq.lhq.value;
+
+        if (prop->configurable) {
+            pq.lhq.pool = vm->mem_cache_pool;
+
+            (void) nxt_lvlhsh_delete(&object->data.u.object->hash, &pq.lhq);
+
+            njs_release(vm, property);
+
+            retval = &njs_value_true;
+        }
+
+    } else if (nxt_fast_path(ret == NJS_ARRAY_VALUE)) {
+        value = pq.lhq.value;
+        njs_set_invalid(value);
+
+        retval = &njs_value_true;
+
+    } else if (nxt_fast_path(ret == NJS_EXTERNAL_VALUE)) {
+
+        ext = object->data.u.external;
+
+        ret = nxt_lvlhsh_find(&ext->hash, &pq.lhq);
+
+        if (ret == NXT_OK) {
+            ext = pq.lhq.value;
+
+            if ((ext->type & NJS_EXTERN_OBJECT) != 0) {
+                goto done;
+            }
+
+            data = ext->data;
+
+        } else {
+            data = (uintptr_t) &pq.lhq.key;
+        }
+
+        ret = ext->find(vm, vm->external[ext->object], data, 1);
+
+        if (nxt_slow_path(ret == NXT_ERROR)) {
+            return ret;
+        }
+
+        if (ret == NXT_OK) {
+            retval = &njs_value_true;
+        }
+
+    } else if (nxt_slow_path(ret == NXT_ERROR)) {
+        return ret;
+    }
+
+done:
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_3addr_t);
+}
+
+
+static nxt_noinline njs_ret_t
+njs_property_query(njs_vm_t *vm, njs_property_query_t *pq, njs_value_t *object,
+    njs_value_t *property)
+{
+    double        num;
+    int32_t       index;
+    uint32_t      (*hash)(const void *, size_t);
+    njs_ret_t     ret;
+    njs_extern_t  *ext;
+    njs_object_t  *obj;
+
+    hash = nxt_djb_hash;
+
+    switch (object->type) {
+
+    case NJS_NULL:
+    case NJS_VOID:
+        vm->exception = &njs_exception_type_error;
+        return NXT_ERROR;
+
+    case NJS_STRING:
+        if (pq->query == NJS_PROPERTY_QUERY_DELETE) {
+            return NXT_DECLINED;
+        }
+
+        obj = &vm->prototypes[NJS_PROTOTYPE_STRING];
+        break;
+
+    case NJS_ARRAY:
+        if (nxt_fast_path(!njs_is_null_or_void_or_boolean(property))) {
+
+            if (njs_is_number(property)) {
+                num = property->data.u.number;
+
+            } else {
+                num = njs_value_to_number(property);
+            }
+
+            index = (int) num;
+
+            if (nxt_fast_path(index >= 0 && (double) index == num)) {
+                return njs_array_property_query(vm, pq, object, index);
+            }
+        }
+
+        /* Fall through. */
+
+    case NJS_OBJECT:
+    case NJS_FUNCTION:
+    case NJS_REGEXP:
+        obj = object->data.u.object;
+        break;
+
+    case NJS_NATIVE:
+        obj = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+        break;
+
+    case NJS_EXTERNAL:
+        ext = object->data.u.external;
+
+        if (ext->type == NJS_EXTERN_CASELESS_OBJECT) {
+            hash = nxt_djb_hash_lowcase;
+        }
+
+        obj = NULL;
+        break;
+
+    default:
+        return NJS_PRIMITIVE_VALUE;
+    }
+
+    ret = njs_value_to_string(vm, &pq->value, property);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+
+        pq->lhq.key.len = pq->value.short_string.size;
+
+        if (pq->lhq.key.len != NJS_STRING_LONG) {
+            pq->lhq.key.data = pq->value.short_string.start;
+
+        } else {
+            pq->lhq.key.len = pq->value.data.string_size;
+            pq->lhq.key.data = pq->value.data.u.string->start;
+        }
+
+        pq->lhq.key_hash = hash(pq->lhq.key.data, pq->lhq.key.len);
+
+        if (obj == NULL) {
+            pq->lhq.proto = &njs_extern_hash_proto;
+
+            return NJS_EXTERNAL_VALUE;
+        }
+
+        return njs_object_property_query(vm, pq, object, obj);
+    }
+
+    return ret;
+}
+
+
+static njs_ret_t
+njs_array_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+    njs_value_t *object, int32_t index)
+{
+    njs_ret_t    ret;
+    njs_array_t  *array;
+
+    array = object->data.u.array;
+
+    if ((uint32_t) index >= array->length) {
+
+        if (pq->query != NJS_PROPERTY_QUERY_SET) {
+            return NXT_DECLINED;
+        }
+
+        if ((uint32_t) index >= array->size) {
+            ret = njs_array_realloc(vm, array, 0, index);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+        }
+
+        array->length = index + 1;
+    }
+
+    pq->lhq.value = &array->start[index];
+
+    return NJS_ARRAY_VALUE;
+}
+
+
+static njs_ret_t
+njs_object_property_query(njs_vm_t *vm, njs_property_query_t *pq,
+    njs_value_t *value, njs_object_t *object)
+{
+    njs_ret_t          ret;
+    njs_object_prop_t  *prop;
+
+    pq->lhq.proto = &njs_object_hash_proto;
+
+    do {
+        pq->prototype = object;
+
+        ret = nxt_lvlhsh_find(&object->hash, &pq->lhq);
+
+        if (ret == NXT_OK) {
+            prop = pq->lhq.value;
+
+            if (prop->type != NJS_WHITEOUT) {
+                pq->shared = 0;
+                return ret;
+            }
+
+            goto next;
+        }
+
+        if (pq->query > NJS_PROPERTY_QUERY_IN) {
+            /* NXT_DECLINED */
+            return ret;
+        }
+
+        ret = nxt_lvlhsh_find(&object->shared_hash, &pq->lhq);
+
+        if (ret == NXT_OK) {
+            pq->shared = 1;
+
+            if (pq->query == NJS_PROPERTY_QUERY_IN) {
+                prop = pq->lhq.value;
+
+                if (prop->type == NJS_WHITEOUT) {
+                    return NXT_DECLINED;
+                }
+            }
+
+            return ret;
+        }
+
+        if (pq->query > NJS_PROPERTY_QUERY_IN) {
+            /* NXT_DECLINED */
+            return ret;
+        }
+
+    next:
+
+        object = object->__proto__;
+
+    } while (object != NULL);
+
+    if (nxt_fast_path(njs_is_primitive(value))) {
+        return NJS_PRIMITIVE_VALUE;
+    }
+
+    return ret;
+}
+
+
+static njs_ret_t
+njs_function_private_copy(njs_vm_t *vm, njs_property_query_t *pq)
+{
+    njs_function_t     *func;
+    njs_object_prop_t  *prop, *shared;
+
+    prop = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_object_prop_t));
+    if (nxt_slow_path(prop == NULL)) {
+        return NXT_ERROR;
+    }
+
+    shared = pq->lhq.value;
+    *prop = *shared;
+
+    func = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_function_t));
+    if (nxt_slow_path(func == NULL)) {
+        return NXT_ERROR;
+    }
+
+    *func = *prop->value.data.u.function;
+    func->object.__proto__ = &vm->prototypes[NJS_PROTOTYPE_FUNCTION];
+    prop->value.data.u.function = func;
+
+    pq->lhq.replace = 0;
+    pq->lhq.value = prop;
+    pq->lhq.pool = vm->mem_cache_pool;
+
+    return nxt_lvlhsh_insert(&pq->prototype->hash, &pq->lhq);
+}
+
+
+njs_ret_t
+njs_vmcode_property_each_start(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *invld)
+{
+    njs_ret_t                ret;
+    njs_extern_t             *ext;
+    njs_property_each_t      *pe;
+    njs_vmcode_prop_start_t  *code;
+
+    if (njs_is_object(object)) {
+        pe = nxt_mem_cache_alloc(vm->mem_cache_pool,
+                                 sizeof(njs_property_each_t));
+        if (nxt_slow_path(pe == NULL)) {
+            return NXT_ERROR;
+        }
+
+        vm->retval.data.u.data = pe;
+
+        memset(&pe->lhe, 0, sizeof(nxt_lvlhsh_each_t));
+        pe->lhe.proto = &njs_object_hash_proto;
+        pe->index = -1;
+
+        if (njs_is_array(object) && object->data.u.array->size != 0) {
+            pe->index = 0;
+        }
+
+    } else if (njs_is_external(object)) {
+        ext = object->data.u.external;
+
+        if (ext->each_start != NULL) {
+            ret = ext->each_start(vm, vm->external[ext->object], &vm->retval);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+        }
+    }
+
+    code = (njs_vmcode_prop_start_t *) vm->current;
+
+    return code->offset;
+}
+
+
+njs_ret_t
+njs_vmcode_property_each(njs_vm_t *vm, njs_value_t *object, njs_value_t *each)
+{
+    nxt_uint_t               n;
+    njs_ret_t               ret;
+    njs_array_t             *array;
+    njs_extern_t            *ext;
+    njs_object_prop_t       *prop;
+    njs_property_each_t     *pe;
+    njs_vmcode_prop_each_t  *code;
+
+    code = (njs_vmcode_prop_each_t *) vm->current;
+
+    if (njs_is_object(object)) {
+        pe = each->data.u.data;
+
+        if (pe->index >= 0) {
+            array = object->data.u.array;
+
+            while ((uint32_t) pe->index < array->size) {
+                n = pe->index++;
+
+                if (njs_is_valid(&array->start[n])) {
+                    njs_number_set(&vm->retval, n);
+
+                    return code->offset;
+                }
+            }
+
+            pe->index = -1;
+        }
+
+        prop = nxt_lvlhsh_each(&object->data.u.object->hash, &pe->lhe);
+
+        if (prop != NULL) {
+            vm->retval = prop->name;
+
+            return code->offset;
+        }
+
+        nxt_mem_cache_free(vm->mem_cache_pool, pe);
+
+        vm->retval = njs_value_void;
+
+    } else if (njs_is_external(object)) {
+        ext = object->data.u.external;
+
+        if (ext->each != NULL) {
+            ret = ext->each(vm, &vm->retval, vm->external[ext->object], each);
+
+            if (ret == NXT_OK) {
+                return code->offset;
+            }
+
+            if (nxt_slow_path(ret == NXT_ERROR)) {
+                return ret;
+            }
+
+            /* ret == NXT_DONE. */
+        }
+    }
+
+    return sizeof(njs_vmcode_prop_each_t);
+}
+
+
+njs_ret_t
+njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *constructor)
+{
+    nxt_int_t           ret;
+    njs_value_t        *value;
+    njs_object_t       *prototype, *proto;
+    njs_object_prop_t  *prop;
+    const njs_value_t  *retval;
+    nxt_lvlhsh_query_t  lhq;
+
+    /* TODO: test constructor is function or native: TypeError. */
+    /* TODO: test object is object: false. */
+
+    retval = &njs_value_false;
+
+    lhq.key_hash = NJS_PROTOTYPE_HASH;
+    lhq.key.len = sizeof("prototype") - 1;
+    lhq.key.data = (u_char *) "prototype";
+
+    prop = njs_object_property(vm, constructor->data.u.object, &lhq);
+
+    if (prop != NULL) {
+        value = &prop->value;
+
+        if (prop->type == NJS_NATIVE_GETTER) {
+            /* STUB: getter should be called by some njs_object_property() */
+            ret = prop->value.data.u.getter(vm, constructor);
+
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return ret;
+            }
+
+            value = &vm->retval;
+        }
+
+        /* TODO: test prop->value is object. */
+
+        prototype = value->data.u.object;
+        proto = object->data.u.object;
+
+        do {
+            proto = proto->__proto__;
+
+            if (proto == prototype) {
+                retval = &njs_value_true;
+                break;
+            }
+
+        } while (proto != NULL);
+    }
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_instance_of_t);
+}
+
+
+njs_ret_t
+njs_vmcode_increment(njs_vm_t *vm, njs_value_t *value, njs_value_t *lvalue)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        num = value->data.u.number + 1.0;
+
+        njs_release(vm, lvalue);
+
+        njs_number_set(lvalue, num);
+        vm->retval = *lvalue;
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_LVALUE_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_decrement(njs_vm_t *vm, njs_value_t *value, njs_value_t *lvalue)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        num = value->data.u.number - 1.0;
+
+        njs_release(vm, lvalue);
+
+        njs_number_set(lvalue, num);
+        vm->retval = *lvalue;
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_LVALUE_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_post_increment(njs_vm_t *vm, njs_value_t *value, njs_value_t *lvalue)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        num = value->data.u.number;
+
+        njs_release(vm, lvalue);
+
+        njs_number_set(lvalue, num + 1.0);
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_LVALUE_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_post_decrement(njs_vm_t *vm, njs_value_t *value, njs_value_t *lvalue)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        num = value->data.u.number;
+
+        njs_release(vm, lvalue);
+
+        njs_number_set(lvalue, num - 1.0);
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_LVALUE_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    /* ECMAScript 5.1: null, array and regexp are objects. */
+
+    static const njs_value_t  *types[] = {
+        &njs_string_object,
+        &njs_string_void,
+        &njs_string_boolean,
+        &njs_string_number,
+        &njs_string_string,
+        &njs_string_void,
+        &njs_string_void,
+        &njs_string_void,
+
+        &njs_string_object,
+        &njs_string_object,
+        &njs_string_object,
+        &njs_string_object,
+        &njs_string_object,
+        &njs_string_function,
+        &njs_string_object,
+    };
+
+    vm->retval = *types[value->type];
+
+    return sizeof(njs_vmcode_2addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_void(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    vm->retval = njs_value_void;
+
+    return sizeof(njs_vmcode_2addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_delete(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    njs_release(vm, value);
+
+    njs_set_invalid(value);
+
+    vm->retval = njs_value_true;
+
+    return sizeof(njs_vmcode_2addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_unary_plus(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        njs_number_set(&vm->retval, value->data.u.number);
+        return sizeof(njs_vmcode_2addr_t);
+    }
+
+    return NJS_TRAP_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_unary_negation(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        njs_number_set(&vm->retval, - value->data.u.number);
+        return sizeof(njs_vmcode_2addr_t);
+    }
+
+    return NJS_TRAP_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_addition(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    double       num;
+    njs_ret_t    ret;
+    njs_param_t  param;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num = val1->data.u.number + val2->data.u.number;
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    if (nxt_fast_path(njs_is_string(val1) && njs_is_string(val2))) {
+        param.object = val1;
+        param.args = val2;
+        param.retval = 0;
+        param.nargs = 1;
+
+        ret = njs_string_prototype_concat(vm, &param);
+
+        if (nxt_fast_path(ret >= 0)) {
+            return sizeof(njs_vmcode_3addr_t);
+        }
+
+        return ret;
+    }
+
+    return NJS_TRAP_STRINGS;
+}
+
+
+njs_ret_t
+njs_vmcode_substraction(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num = val1->data.u.number - val2->data.u.number;
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_multiplication(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num = val1->data.u.number * val2->data.u.number;
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_division(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num = val1->data.u.number / val2->data.u.number;
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_remainder(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    double  num;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num = fmod(val1->data.u.number, val2->data.u.number);
+        njs_number_set(&vm->retval, num);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_left_shift(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    int32_t   num1;
+    uint32_t  num2;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num1 = njs_integer_value(val1->data.u.number);
+        num2 = njs_integer_value(val2->data.u.number);
+        njs_number_set(&vm->retval, num1 << (num2 & 0x1f));
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_right_shift(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    int32_t   num1;
+    uint32_t  num2;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num1 = njs_integer_value(val1->data.u.number);
+        num2 = njs_integer_value(val2->data.u.number);
+        njs_number_set(&vm->retval, num1 >> (num2 & 0x1f));
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_unsigned_right_shift(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2)
+{
+    int32_t   num2;
+    uint32_t  num1;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num1 = njs_integer_value(val1->data.u.number);
+        num2 = njs_integer_value(val2->data.u.number);
+        njs_number_set(&vm->retval, num1 >> (num2 & 0x1f));
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_logical_not(njs_vm_t *vm, njs_value_t *value, njs_value_t *inlvd)
+{
+    const njs_value_t  *retval;
+
+    if (njs_is_true(value)) {
+        retval = &njs_value_false;
+
+    } else {
+        retval = &njs_value_true;
+    }
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_2addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_logical_and(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    njs_value_t  *retval;
+
+    if (njs_is_true(val1)) {
+        retval = val2;
+
+    } else {
+        retval = val1;
+    }
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_3addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_logical_or(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    njs_value_t  *retval;
+
+    if (njs_is_true(val1)) {
+        retval = val1;
+
+    } else {
+        retval = val2;
+    }
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_3addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_bitwise_not(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    int32_t  num;
+
+    if (nxt_fast_path(njs_is_numeric(value))) {
+        num = njs_integer_value(value->data.u.number);
+        njs_number_set(&vm->retval, ~num);
+
+        return sizeof(njs_vmcode_2addr_t);
+    }
+
+    return NJS_TRAP_NUMBER;
+}
+
+
+njs_ret_t
+njs_vmcode_bitwise_and(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    int32_t  num1, num2;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num1 = njs_integer_value(val1->data.u.number);
+        num2 = njs_integer_value(val2->data.u.number);
+        njs_number_set(&vm->retval, num1 & num2);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_bitwise_xor(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    int32_t  num1, num2;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num1 = njs_integer_value(val1->data.u.number);
+        num2 = njs_integer_value(val2->data.u.number);
+        njs_number_set(&vm->retval, num1 ^ num2);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+njs_ret_t
+njs_vmcode_bitwise_or(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    int32_t  num1, num2;
+
+    if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+        num1 = njs_integer_value(val1->data.u.number);
+        num2 = njs_integer_value(val2->data.u.number);
+        njs_number_set(&vm->retval, num1 | num2);
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+static nxt_noinline uint32_t
+njs_integer_value(double num)
+{
+    int64_t  i64;
+
+    /*
+     * ECMAScript 5.1: integer must be modulo 2^32.
+     * 2^53 is the largest integer number which can be stored in the IEEE-754
+     * format and numbers less than 2^53 can be just converted to int64_t
+     * eliding more expensive fmod() operation.  Then the int64 integer is
+     * truncated to uint32_t.  The NaN can be converted to 0x8000000000000000
+     * and becomes 0 after truncation.  fmod() of the infinity returns NaN.
+     */
+
+    if (num < 0 || num > 9007199254740992.0) {
+        i64 = fmod(num, 4294967296.0);
+
+    } else {
+        i64 = num;
+    }
+
+    return (uint32_t) i64;
+}
+
+
+njs_ret_t
+njs_vmcode_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    njs_ret_t          ret;
+    const njs_value_t  *retval;
+
+    ret = njs_values_equal(val1, val2);
+
+    if (nxt_fast_path(ret >= 0)) {
+
+        retval = (ret != 0) ? &njs_value_true : &njs_value_false;
+        vm->retval = *retval;
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return ret;
+}
+
+
+njs_ret_t
+njs_vmcode_not_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    njs_ret_t          ret;
+    const njs_value_t  *retval;
+
+    ret = njs_values_equal(val1, val2);
+
+    if (nxt_fast_path(ret >= 0)) {
+
+        retval = (ret == 0) ? &njs_value_true : &njs_value_false;
+        vm->retval = *retval;
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return ret;
+}
+
+
+static nxt_noinline njs_ret_t
+njs_values_equal(njs_value_t *val1, njs_value_t *val2)
+{
+    /* Void and null are equal and not comparable with anything else. */
+    if (njs_is_null_or_void(val1)) {
+        return (njs_is_null_or_void(val2));
+    }
+
+    if (njs_is_numeric(val1) && njs_is_numeric(val2)) {
+        /* NaNs and Infinities are handled correctly by comparision. */
+        return (val1->data.u.number == val2->data.u.number);
+    }
+
+    if (val1->type == val2->type) {
+
+        if (njs_is_string(val1)) {
+            return njs_string_eq(val1, val2);
+        }
+
+        return (val1->data.u.object == val2->data.u.object);
+    }
+
+    return NJS_TRAP_NUMBERS;
+}
+
+
+nxt_noinline njs_ret_t
+njs_vmcode_less(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    njs_ret_t          ret;
+    const njs_value_t  *retval;
+
+    ret = njs_values_compare(val1, val2);
+
+    if (nxt_fast_path(ret >= -1)) {
+
+        retval = (ret > 0) ? &njs_value_true : &njs_value_false;
+        vm->retval = *retval;
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return ret;
+}
+
+
+njs_ret_t
+njs_vmcode_greater(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    return njs_vmcode_less(vm, val2, val1);
+}
+
+
+njs_ret_t
+njs_vmcode_less_or_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    return njs_vmcode_greater_or_equal(vm, val2, val1);
+}
+
+
+nxt_noinline njs_ret_t
+njs_vmcode_greater_or_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    njs_ret_t          ret;
+    const njs_value_t  *retval;
+
+    ret = njs_values_compare(val1, val2);
+
+    if (nxt_fast_path(ret >= -1)) {
+
+        retval = (ret == 0) ? &njs_value_true : &njs_value_false;
+        vm->retval = *retval;
+
+        return sizeof(njs_vmcode_3addr_t);
+    }
+
+    return ret;
+}
+
+
+/*
+ * njs_values_compare() returns
+ *   1 if val1 is less than val2,
+ *   0 if val1 is greater than or equal to val2,
+ *  -1 if the values are not comparable,
+ *  or negative trap number if convertion to primitive is required.
+ */
+
+static nxt_noinline njs_ret_t
+njs_values_compare(njs_value_t *val1, njs_value_t *val2)
+{
+    if (nxt_fast_path(njs_is_numeric(val1) || njs_is_numeric(val2))) {
+
+        if (nxt_fast_path(njs_is_numeric(val1) && njs_is_numeric(val2))) {
+
+            /* NaN and void values are not comparable with anything. */
+            if (njs_is_nan(val1->data.u.number)
+                || njs_is_nan(val2->data.u.number))
+            {
+                return -1;
+            }
+
+            /* Infinities are handled correctly by comparision. */
+            return (val1->data.u.number < val2->data.u.number);
+        }
+
+        return NJS_TRAP_NUMBERS;
+    }
+
+    if (nxt_fast_path(njs_is_string(val1) && njs_is_string(val2))) {
+        return (njs_string_cmp(val1, val2) < 0) ? 1 : 0;
+    }
+
+    return NJS_TRAP_STRINGS;
+}
+
+
+njs_ret_t
+njs_vmcode_strict_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    const njs_value_t  *retval;
+
+    if (njs_values_strict_equal(val1, val2)) {
+        retval = &njs_value_true;
+
+    } else {
+        retval = &njs_value_false;
+    }
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_3addr_t);
+}
+
+
+njs_ret_t
+njs_vmcode_strict_not_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2)
+{
+    const njs_value_t  *retval;
+
+    if (njs_values_strict_equal(val1, val2)) {
+        retval = &njs_value_false;
+
+    } else {
+        retval = &njs_value_true;
+    }
+
+    vm->retval = *retval;
+
+    return sizeof(njs_vmcode_3addr_t);
+}
+
+
+static nxt_noinline nxt_bool_t
+njs_values_strict_equal(njs_value_t *val1, njs_value_t *val2)
+{
+    if (val1->type != val2->type) {
+        return 0;
+    }
+
+    if (njs_is_numeric(val1)) {
+        /* NaNs and Infinities are handled correctly by comparision. */
+        return (val1->data.u.number == val2->data.u.number);
+    }
+
+    if (njs_is_string(val1)) {
+        return njs_string_eq(val1, val2);
+    }
+
+    return (val1->data.u.object == val2->data.u.object);
+}
+
+
+njs_ret_t
+njs_vmcode_move(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld)
+{
+    vm->retval = *value;
+
+    njs_retain(value);
+
+    return sizeof(njs_vmcode_move_t);
+}
+
+
+njs_ret_t
+njs_vmcode_validate(njs_vm_t *vm, njs_value_t *invld, njs_value_t *index)
+{
+    njs_value_t  *value;
+
+    value = njs_vmcode_operand(vm, index);
+
+    if (nxt_fast_path(njs_is_valid(value))) {
+        return sizeof(njs_vmcode_validate_t);
+    }
+
+    vm->exception = &njs_exception_reference_error;
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_jump(njs_vm_t *vm, njs_value_t *invld, njs_value_t *offset)
+{
+    return (njs_ret_t) offset;
+}
+
+
+njs_ret_t
+njs_vmcode_if_true_jump(njs_vm_t *vm, njs_value_t *cond, njs_value_t *offset)
+{
+    if (njs_is_true(cond)) {
+        return (njs_ret_t) offset;
+    }
+
+    return sizeof(njs_vmcode_cond_jump_t);
+}
+
+
+njs_ret_t
+njs_vmcode_if_false_jump(njs_vm_t *vm, njs_value_t *cond, njs_value_t *offset)
+{
+    if (njs_is_true(cond)) {
+        return sizeof(njs_vmcode_cond_jump_t);
+    }
+
+    return (njs_ret_t) offset;
+}
+
+
+njs_ret_t
+njs_vmcode_function(njs_vm_t *vm, njs_value_t *name, njs_value_t *invld)
+{
+    njs_ret_t              ret;
+    njs_value_t            value, *this;
+    njs_param_t            param;
+    njs_object_t           *object;
+    njs_function_t         *function;
+    njs_vmcode_function_t  *func;
+
+    if (nxt_fast_path(njs_is_function(name))) {
+
+        func = (njs_vmcode_function_t *) vm->current;
+
+        function = name->data.u.function;
+
+        if (function->native) {
+            this = njs_vmcode_native_frame(vm, &vm->retval, func->code.nargs,
+                                           func->code.ctor);
+            if (nxt_fast_path(this != NULL)) {
+                *this = njs_value_void;
+                vm->retval = *name;
+
+                return sizeof(njs_vmcode_function_t);
+            }
+
+            return NXT_ERROR;
+        }
+
+        if (func->code.ctor) {
+            object = njs_object_alloc(vm);
+
+            if (nxt_slow_path(object == NULL)) {
+                return NXT_ERROR;
+            }
+
+            value.data.u.object = object;
+            value.type = NJS_OBJECT;
+            value.data.truth = 1;
+            param.object = &value;
+
+        } else {
+            param.object = (njs_value_t *) &njs_value_void;
+        }
+
+        param.args = NULL;
+        param.nargs = func->code.nargs;
+
+        ret = njs_vmcode_function_frame(vm, name, &param, func->code.ctor);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            return sizeof(njs_vmcode_function_t);
+        }
+
+    } else {
+        vm->exception = &njs_exception_type_error;
+    }
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_method(njs_vm_t *vm, njs_value_t *object, njs_value_t *name)
+{
+    uintptr_t             nargs;
+    njs_ret_t             ret;
+    njs_value_t           *this;
+    njs_param_t           param;
+    njs_extern_t          *ext;
+    njs_object_prop_t     *prop;
+    njs_vmcode_method_t   *method;
+    njs_property_query_t  pq;
+
+    method = (njs_vmcode_method_t *) vm->current;
+    nargs = method->code.nargs;
+
+    pq.query = NJS_PROPERTY_QUERY_GET;
+
+    switch (njs_property_query(vm, &pq, object, name)) {
+
+    case NXT_OK:
+        prop = pq.lhq.value;
+
+        if (njs_is_function(&prop->value)
+            && !prop->value.data.u.function->native)
+        {
+            param.object = object;
+            param.args = NULL;
+            param.nargs = nargs;
+
+            ret = njs_vmcode_function_frame(vm, &prop->value, &param,
+                                            method->code.ctor);
+
+            if (nxt_fast_path(ret == NXT_OK)) {
+                return sizeof(njs_vmcode_method_t);
+            }
+
+            return ret;
+        }
+
+        vm->retval = prop->value;
+
+        njs_retain(object);
+
+        this = njs_vmcode_native_frame(vm, &vm->retval, nargs,
+                                       method->code.ctor);
+        if (nxt_slow_path(this == NULL)) {
+            return NXT_ERROR;
+        }
+
+        *this = *object;
+
+        return sizeof(njs_vmcode_method_t);
+
+    case NJS_EXTERNAL_VALUE:
+        ext = object->data.u.external;
+
+        ret = nxt_lvlhsh_find(&ext->hash, &pq.lhq);
+
+        if (ret == NXT_OK) {
+            ext = pq.lhq.value;
+
+            if (ext->type == NJS_EXTERN_METHOD) {
+                vm->retval.type = NJS_NATIVE;
+                vm->retval.data.truth = 1;
+                vm->retval.data.string_size = 0;
+                vm->retval.data.u.method = ext->method;
+
+                this = njs_vmcode_native_frame(vm, &vm->retval, nargs,
+                                               method->code.ctor);
+
+                if (nxt_slow_path(this == NULL)) {
+                    return NXT_ERROR;
+                }
+
+                this->data.u.data = vm->external[ext->object];
+
+                return sizeof(njs_vmcode_method_t);
+            }
+        }
+
+        /* Fall through. */
+
+    default:
+        vm->exception = &njs_exception_type_error;
+        return NXT_ERROR;
+    }
+}
+
+
+njs_ret_t
+njs_vmcode_call(njs_vm_t *vm, njs_value_t *func, njs_value_t *retval)
+{
+    njs_ret_t          ret;
+    njs_value_t        *args;
+    njs_param_t        param;
+    njs_native_t       native;
+    njs_function_t     *function;
+    njs_vmcode_call_t  *call;
+
+    call = (njs_vmcode_call_t *) vm->current;
+    vm->current += sizeof(njs_vmcode_call_t);
+
+    if (njs_is_function(func)) {
+        function = func->data.u.function;
+
+        if (!function->native) {
+            (void) njs_function_call(vm, function, (njs_index_t) retval);
+            return 0;
+        }
+
+        native = function->code.native;
+
+    } else {
+        native = func->data.u.method;
+    }
+
+    param.retval = (njs_index_t) retval;
+    param.nargs = call->code.nargs - 1;
+    args = vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS];
+    param.args = args;
+    param.object = args - 1;
+
+    ret = native(vm, &param);
+    /*
+     * A native method can return:
+     *    NXT_OK on method success;
+     *    NJS_PASS by Function.apply() and Function.call();
+     *    NXT_AGAIN to postpone nJSVM processing;
+     *    NXT_ERROR.
+     *
+     * The callee arguments must be preserved for NJS_PASS and NXT_AGAIN cases.
+     */
+    if (ret == NXT_OK) {
+        /*
+         * If a retval is in a callee arguments scope it
+         * must be in the previous callee arguments scope.
+         */
+        vm->frame = vm->frame->previous;
+        vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = vm->frame->arguments;
+
+        retval = njs_vmcode_operand(vm, retval);
+         /*
+          * GC: value external/internal++ depending
+          * on vm->retval and retval type
+          */
+        *retval = vm->retval;
+
+    } else if (ret == NJS_PASS) {
+        ret = 0;
+
+    } else if (ret == NXT_AGAIN) {
+        vm->frame->reentrant = 1;
+    }
+
+    return ret;
+}
+
+
+njs_ret_t
+njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
+{
+    njs_value_t         *value;
+    njs_frame_t         *frame;
+    njs_value_t         *args;
+    njs_native_frame_t  *previous;
+
+    value = njs_vmcode_operand(vm, retval);
+
+    frame = (njs_frame_t *) vm->frame;
+
+    if (frame->native.ctor) {
+        if (njs_is_object(value)) {
+            njs_release(vm, vm->scopes[NJS_SCOPE_ARGUMENTS]);
+
+        } else {
+            value = vm->scopes[NJS_SCOPE_ARGUMENTS];
+        }
+    }
+
+    previous = frame->native.previous;
+    vm->frame = previous;
+
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = previous->arguments;
+    vm->scopes[NJS_SCOPE_LOCAL] = frame->prev_local;
+    args = vm->scopes[NJS_SCOPE_ARGUMENTS];
+    vm->scopes[NJS_SCOPE_ARGUMENTS] = frame->prev_arguments;
+
+    /*
+     * If a retval is in a callee arguments scope it
+     * must be in the previous callee arguments scope.
+     */
+    retval = njs_vmcode_operand(vm, frame->retval);
+
+    /* GC: value external/internal++ depending on value and retval type */
+    *retval = *value;
+
+    vm->current = frame->return_address;
+
+    /* GC: arguments and local. */
+
+    njs_release(vm, &args[0]);
+
+    if (frame->native.start) {
+        nxt_mem_cache_free(vm->mem_cache_pool, frame);
+    }
+
+    return 0;
+}
+
+
+njs_ret_t
+njs_vmcode_stop(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
+{
+    njs_value_t  *value;
+
+    value = njs_vmcode_operand(vm, retval);
+
+    vm->retval = *value;
+
+    return NXT_DONE;
+}
+
+
+/*
+ * njs_vmcode_try_start() is set on the start of a "try" block to create
+ * a "try" block, to set a catch address to the start of a "catch" or
+ * "finally" blocks and to initialize a value to track uncaught exception.
+ */
+
+njs_ret_t
+njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value, njs_value_t *offset)
+{
+    njs_exception_t  *e;
+
+    if (vm->frame->u.exception.catch != NULL) {
+        e = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_exception_t));
+        if (nxt_slow_path(e == NULL)) {
+            return NXT_ERROR;
+        }
+
+        *e = vm->frame->u.exception;
+        vm->frame->u.exception.next = e;
+    }
+
+    vm->frame->u.exception.catch = vm->current + (njs_ret_t) offset;
+
+    njs_set_invalid(value);
+
+    return sizeof(njs_vmcode_try_start_t);
+}
+
+
+/*
+ * njs_vmcode_try_end() is set on the end of a "try" block to remove the block.
+ * It is also set on the end of a "catch" block followed by a "finally" block.
+ */
+
+nxt_noinline njs_ret_t
+njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld, njs_value_t *offset)
+{
+    njs_exception_t  *e;
+
+    e = vm->frame->u.exception.next;
+
+    if (e == NULL) {
+        vm->frame->u.exception.catch = NULL;
+
+    } else {
+        vm->frame->u.exception = *e;
+        nxt_mem_cache_free(vm->mem_cache_pool, e);
+    }
+
+    return (njs_ret_t) offset;
+}
+
+
+njs_ret_t
+njs_vmcode_throw(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
+{
+    njs_value_t  *value;
+
+    value = njs_vmcode_operand(vm, retval);
+
+    vm->retval = *value;
+
+    return NXT_ERROR;
+}
+
+
+/*
+ * njs_vmcode_catch() is set on the start of a "catch" block to store
+ * exception and to remove a "try" block if there is no "finally" block
+ * or to update a catch address to the start of a "finally" block.
+ * njs_vmcode_catch() is set on the start of a "finally" block to store
+ * uncaught exception and to remove a "try" block.
+ */
+
+njs_ret_t
+njs_vmcode_catch(njs_vm_t *vm, njs_value_t *exception, njs_value_t *offset)
+{
+    *exception = vm->retval;
+
+    if ((njs_ret_t) offset == sizeof(njs_vmcode_catch_t)) {
+        return njs_vmcode_try_end(vm, exception, offset);
+    }
+
+    vm->frame->u.exception.catch = vm->current + (njs_ret_t) offset;
+
+    return sizeof(njs_vmcode_catch_t);
+}
+
+
+/*
+ * njs_vmcode_finally() is set on the end of a "finally" block to throw
+ * uncaught exception.
+ */
+
+njs_ret_t
+njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld, njs_value_t *retval)
+{
+    njs_value_t  *value;
+
+    value = njs_vmcode_operand(vm, retval);
+
+    if (!njs_is_valid(value)) {
+        return sizeof(njs_vmcode_finally_t);
+    }
+
+    vm->retval = *value;
+
+    return NXT_ERROR;
+}
+
+
+njs_ret_t
+njs_vmcode_to_number(njs_vm_t *vm, njs_value_t *invld, njs_value_t *narg)
+{
+    double       num;
+    njs_ret_t    ret;
+    njs_value_t  *value;
+
+    value = njs_native_data(vm->frame);
+    value = &value[(uintptr_t) narg + 1];
+
+    ret = njs_value_to_primitive(vm, value, 0);
+
+    if (nxt_fast_path(ret > 0)) {
+
+        if (!njs_is_numeric(value)) {
+            num = NJS_NAN;
+
+            if (njs_is_string(value)) {
+                num = njs_string_to_number(value);
+            }
+
+            njs_number_set(value, num);
+        }
+
+        ret = sizeof(njs_vmcode_to_number_t);
+    }
+
+    return ret;
+}
+
+
+njs_ret_t
+njs_vmcode_to_string(njs_vm_t *vm, njs_value_t *invld, njs_value_t *narg)
+{
+    njs_ret_t          ret;
+    njs_value_t        *value;
+    const njs_value_t  *string;
+
+    value = njs_native_data(vm->frame);
+    value = &value[(uintptr_t) narg + 1];
+
+    ret = njs_value_to_primitive(vm, value, 1);
+
+    if (nxt_fast_path(ret > 0)) {
+
+        if (!njs_is_string(value)) {
+
+            switch (value->type) {
+
+            case NJS_NULL:
+                string = &njs_string_null;
+                break;
+
+            case NJS_VOID:
+                string = &njs_string_void;
+                break;
+
+            case NJS_BOOLEAN:
+                string = njs_is_true(value) ? &njs_string_true:
+                                              &njs_string_false;
+                break;
+
+            case NJS_NUMBER:
+                ret = njs_number_to_string(vm, value, value);
+                if (nxt_fast_path(ret == NXT_OK)) {
+                    goto done;
+                }
+
+            default:
+                return NXT_ERROR;
+            }
+
+            *value = *string;
+        }
+
+    done:
+
+        ret = sizeof(njs_vmcode_to_string_t);
+    }
+
+    return ret;
+}
+
+
+/*
+ * A hint value is 0 for numbers and 1 for strings.  The value chooses method
+ * calls order specified by ECMAScript 5.1: "valueOf", "toString" for numbers
+ * and "toString", "valueOf" for strings.
+ */
+
+nxt_noinline njs_ret_t
+njs_value_to_primitive(njs_vm_t *vm, njs_value_t *value, nxt_uint_t hint)
+{
+    njs_ret_t           ret;
+    njs_param_t         param;
+    njs_value_t         *retval;
+    njs_object_prop_t   *prop;
+    nxt_lvlhsh_query_t  lhq;
+
+    static const uint32_t  hashes[] = {
+        NJS_VALUE_OF_HASH,
+        NJS_TO_STRING_HASH,
+    };
+
+    static const nxt_str_t  names[] = {
+        nxt_string("valueOf"),
+        nxt_string("toString"),
+    };
+
+    if (!njs_is_primitive(value)) {
+        retval = njs_native_data(vm->frame);
+
+        if (!njs_is_valid(retval)) {
+
+            for ( ;; ) {
+                vm->exception = &njs_exception_type_error;
+                ret = NXT_ERROR;
+
+                if (njs_is_object(value) && vm->frame->reentrant < 2) {
+                    hint ^= vm->frame->reentrant++;
+
+                    lhq.key_hash = hashes[hint];
+                    lhq.key = names[hint];
+
+                    prop = njs_object_property(vm, value->data.u.object, &lhq);
+
+                    if (nxt_fast_path(prop != NULL)) {
+                        param.object = value;
+                        param.retval = (njs_index_t) retval;
+                        param.args = NULL;
+                        param.nargs = 0;
+
+                        ret = njs_function_apply(vm, &prop->value, &param);
+
+                        /*
+                         * njs_function_apply() can return
+                         *     NXT_OK, NJS_PASS, NXT_ERROR, NXT_AGAIN.
+                         */
+                        if (nxt_fast_path(ret == NXT_OK)) {
+
+                            if (njs_is_primitive(&vm->retval)) {
+                                retval = &vm->retval;
+                                break;
+                            }
+
+                            continue;
+                        }
+
+                        if (ret == NJS_PASS) {
+                            ret = 0;
+                        }
+                    }
+                }
+
+                return ret;
+            }
+        }
+
+        *value = *retval;
+
+        njs_set_invalid(retval);
+    }
+
+    vm->frame->reentrant = 0;
+
+    return 1;
+}
+
+
+njs_ret_t
+njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1, njs_value_t *invld2)
+{
+    u_char                *restart;
+    njs_ret_t             ret;
+    njs_value_t           *retval, *value1, *value2;
+    njs_native_frame_t    *frame;
+    njs_vmcode_generic_t  *vmcode;
+
+    frame = vm->frame;
+    vm->frame = frame->previous;
+    vm->scopes[NJS_SCOPE_CALLEE_ARGUMENTS] = frame->previous->arguments;
+
+    restart = frame->u.restart;
+    vm->current = restart;
+    vmcode = (njs_vmcode_generic_t *) restart;
+
+    value1 = njs_native_data(frame);
+    value1 = &value1[1];
+
+    if (frame->lvalue) {
+        value2 = value1[1].data.u.value;
+
+    } else {
+        value2 = &value1[1];
+    }
+
+    ret = vmcode->code.operation(vm, value1, value2);
+
+    retval = njs_vmcode_operand(vm, vmcode->operand1);
+
+    //njs_release(vm, retval);
+
+    *retval = vm->retval;
+
+    if (frame->start) {
+        nxt_mem_cache_free(vm->mem_cache_pool, frame);
+    }
+
+    return ret;
+}
+
+
+nxt_noinline void
+njs_number_set(njs_value_t *value, double num)
+{
+    value->data.u.number = num;
+    value->type = NJS_NUMBER;
+    value->data.truth = njs_is_number_true(num);
+}
+
+
+njs_ret_t
+njs_void_set(njs_value_t *value)
+{
+    *value = njs_value_void;
+    return NXT_OK;
+}
+
+
+void *
+njs_value_data(njs_value_t *value)
+{
+    return value->data.u.data;
+}
+
+
+nxt_uint_t
+njs_vm_is_reentrant(njs_vm_t *vm)
+{
+    return vm->frame->reentrant;
+}
+
+
+nxt_int_t
+njs_value_string(njs_vm_t *vm, nxt_str_t *retval, njs_value_t *value,
+    njs_value_t **tmp)
+{
+    size_t       size;
+    nxt_int_t    ret;
+    njs_value_t  *val;
+
+    val = value;
+    *tmp = NULL;
+
+    if (!njs_is_string(val)) {
+        val = nxt_mem_cache_alloc(vm->mem_cache_pool, sizeof(njs_value_t));
+        if (nxt_slow_path(val == NULL)) {
+            return NXT_ERROR;
+        }
+
+        ret = njs_value_to_string(vm, val, value);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            /* TODO: njs_free(vm, val); */
+            return ret;
+        }
+
+        *tmp = val;
+    }
+
+    size = val->short_string.size;
+
+    if (size != NJS_STRING_LONG) {
+        retval->len = size;
+        retval->data = val->short_string.start;
+
+    } else {
+        njs_retain(val);
+        retval->len = val->data.string_size;
+        retval->data = val->data.u.string->start;
+    }
+
+    return NXT_OK;
+}
+
+
+nxt_int_t
+njs_value_string_copy(njs_vm_t *vm, nxt_str_t *retval, njs_value_t *value,
+    uintptr_t *next)
+{
+    uintptr_t    n;
+    njs_array_t  *array;
+
+    switch (value->type) {
+
+    case NJS_STRING:
+        if (*next != 0) {
+            return NXT_DECLINED;
+        }
+
+        *next = 1;
+        break;
+
+    case NJS_ARRAY:
+        array = value->data.u.array;
+
+        do {
+            n = (*next)++;
+
+            if (n == array->length) {
+                return NXT_DECLINED;
+            }
+
+            value = &array->start[n];
+
+        } while (!njs_is_valid(value));
+
+        break;
+
+    default:
+        return NXT_ERROR;
+    }
+
+    return njs_value_to_ext_string(vm, retval, value);
+}
+
+
+void
+njs_debug(njs_index_t index, njs_value_t *value)
+{
+#if (NXT_DEBUG)
+    u_char    *p;
+    uint32_t  len;
+
+    switch (value->type) {
+
+    case NJS_NULL:
+        nxt_thread_log_debug("%p [null]", index);
+        return;
+
+    case NJS_VOID:
+        nxt_thread_log_debug("%p [void]", index);
+        return;
+
+    case NJS_BOOLEAN:
+        nxt_thread_log_debug("%p [%s]", index,
+                             (value->data.u.number == 0.0) ? "false" : "true");
+        return;
+
+    case NJS_NUMBER:
+        nxt_thread_log_debug("%p [%f]", index, value->data.u.number);
+        return;
+
+    case NJS_STRING:
+        len = value->short_string.size;
+        if (len != NJS_STRING_LONG) {
+            p = value->short_string.start;
+
+        } else {
+            len = value->data.string_size;
+            p = value->data.u.string->start;
+        }
+
+        nxt_thread_log_debug("%p [\"%*s\"]", index, len, p);
+        return;
+
+    case NJS_ARRAY:
+        nxt_thread_log_debug("%p [array]", index);
+        return;
+
+    default:
+        nxt_thread_log_debug("%p [invalid]", index);
+        return;
+    }
+#endif
+}
+
+
+void *
+njs_lvlhsh_alloc(void *data, size_t size, nxt_uint_t nalloc)
+{
+    return nxt_mem_cache_align(data, size, size);
+}
+
+
+void
+njs_lvlhsh_free(void *data, void *p, size_t size)
+{
+    nxt_mem_cache_free(data, p);
+}
diff --git a/njs/njs_vm.h b/njs/njs_vm.h
new file mode 100644 (file)
index 0000000..b7262f7
--- /dev/null
@@ -0,0 +1,908 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJS_VM_H_INCLUDED_
+#define _NJS_VM_H_INCLUDED_
+
+
+#define NJS_TRAP_STRINGS        -10
+#define NJS_TRAP_NUMBERS        -11
+#define NJS_TRAP_LVALUE_NUMBER  -12
+#define NJS_TRAP_NUMBER         -13
+
+#define NJS_PASS                -20
+
+
+/* The order of the enum is used in njs_vmcode_typeof() */
+
+typedef enum {
+    NJS_NULL            = 0x00,
+    NJS_VOID            = 0x01,
+
+    /* The order of the above type is used in njs_is_null_or_void(). */
+
+    NJS_BOOLEAN         = 0x02,
+    /*
+     * The order of the above type is used in njs_is_null_or_void_or_boolean().
+     */
+    NJS_NUMBER          = 0x03,
+    /*
+     * The order of the above type is used in njs_is_numeric().
+     * Booleans, null and void values can be used in mathematical operations:
+     *   a numeric value of the true value is one,
+     *   a numeric value of the null and false values is zero,
+     *   a numeric value of the void value is NaN.
+     */
+    NJS_STRING          = 0x04,
+
+    /* The order of the above type is used in njs_is_primitive(). */
+
+    /* The type is native code. */
+    NJS_NATIVE          = 0x05,
+
+    /* The type is external code. */
+    NJS_EXTERNAL        = 0x06,
+
+    /*
+     * A special value type for uninitialized array members.
+     * It is also used to detect variable non-declared explicitly
+     * or implicitly and to throw ReferenceError exception.
+     */
+    NJS_INVALID         = 0x07,
+
+    /*
+     * The object types have the third bit set.  It is used in njs_is_object().
+     */
+    NJS_OBJECT          = 0x08,
+    NJS_ARRAY           = 0x09,
+    NJS_OBJECT_BOOLEAN  = 0x0a,
+    NJS_OBJECT_NUMBER   = 0x0b,
+    NJS_OBJECT_STRING   = 0x0c,
+    NJS_FUNCTION        = 0x0d,
+    NJS_REGEXP          = 0x0e,
+} njs_value_type_t;
+
+
+typedef struct njs_parser_s          njs_parser_t;
+
+typedef njs_ret_t (*njs_getter_t) (njs_vm_t *vm, njs_value_t *obj);
+typedef njs_ret_t (*njs_native_t) (njs_vm_t *vm, njs_param_t *param);
+
+
+typedef struct njs_string_s          njs_string_t;
+typedef struct njs_object_s          njs_object_t;
+typedef struct njs_array_s           njs_array_t;
+typedef struct njs_object_value_s    njs_object_value_t;
+typedef struct njs_function_s        njs_function_t;
+typedef struct njs_regexp_s          njs_regexp_t;
+typedef struct njs_regexp_pattern_s  njs_regexp_pattern_t;
+typedef struct njs_extern_s          njs_extern_t;
+
+
+union njs_value_s {
+    /*
+     * The njs_value_t size is 16 bytes and must be aligned to 16 bytes
+     * to provide 4 bits to encode scope in njs_index_t.  This space is
+     * used to store short strings.  The maximum size of a short string
+     * is 14 (NJS_STRING_SHORT).  If the short_string.size field is 15
+     * (NJS_STRING_LONG) then the size is in the data.string_size field
+     * and the data.u.string field points to a long string.
+     *
+     * The number of the string types is limited to 2 types to minimize
+     * overhead of processing string fields.  It also is possible to add
+     * strings with size from 14 to 254 which size and length are stored in
+     * the string_size and string_length byte wide fields.  This will lessen
+     * the maximum size of short string to 13.
+     */
+    struct {
+        njs_value_type_t          type:8;  /* 4 bits */
+        /*
+         * The truth field is set during value assignment and then can be
+         * quickly tested by logical and conditional operations regardless
+         * of value type.  The truth field coincides with short_string.size
+         * and short_string.length so when string size and length are zero
+         * the string's value is false.
+         */
+        uint8_t                   truth;
+
+        /* 0xff if u.data.string is external string. */
+        uint8_t                   external0;
+        uint8_t                   _spare;
+        /*
+         * A long string size.  It is better to store here a size instead of
+         * an UTF-8 length because the size is known at creation time but the
+         * length may be set later and it should be updated in one shared place.
+         */
+        uint32_t                  string_size;
+
+        union {
+            double                number;
+            njs_string_t          *string;
+            njs_object_t          *object;
+            njs_array_t           *array;
+            njs_object_value_t    *object_value;
+            njs_function_t        *function;
+            njs_regexp_t          *regexp;
+            njs_getter_t          getter;
+            njs_native_t          method;
+            njs_extern_t          *external;
+            njs_value_t           *value;
+            void                  *data;
+        } u;
+    } data;
+
+    struct {
+        njs_value_type_t          type:8;  /* 4 bits */
+
+#define NJS_STRING_SHORT          14
+#define NJS_STRING_LONG           15
+
+        uint8_t                   size:4;
+        uint8_t                   length:4;
+
+        u_char                    start[NJS_STRING_SHORT];
+    } short_string;
+
+    njs_value_type_t              type:8;  /* 4 bits */
+};
+
+
+#define                                                                       \
+njs_value(_type, _truth, _number) {                                           \
+    .data = {                                                                 \
+        .type = _type,                                                        \
+        .truth = _truth,                                                      \
+        .u.number = _number,                                                  \
+    }                                                                         \
+}
+
+
+#define                                                                       \
+njs_string(s) {                                                               \
+    .short_string = {                                                         \
+        .type = NJS_STRING,                                                   \
+        .size = sizeof(s) - 1,                                                \
+        .length = sizeof(s) - 1,                                              \
+        .start = s,                                                           \
+    }                                                                         \
+}
+
+
+/* NJS_STRING_LONG is set for both big and little endian platforms. */
+
+#define                                                                       \
+njs_long_string(s) {                                                          \
+    .data = {                                                                 \
+        .type = NJS_STRING,                                                   \
+        .truth = (NJS_STRING_LONG << 4) | NJS_STRING_LONG,                    \
+        .string_size = sizeof(s) - 1,                                         \
+        .u.string = & (njs_string_t) {                                        \
+            .start = (u_char *) s,                                            \
+            .length = sizeof(s) - 1,                                          \
+        }                                                                     \
+    }                                                                         \
+}
+
+
+#define                                                                       \
+njs_native_function(_function, _local_size) {                                 \
+    .data = {                                                                 \
+        .type = NJS_FUNCTION,                                                 \
+        .truth = 1,                                                           \
+        .string_size = _local_size,                                           \
+        .u.function = & (njs_function_t) {                                    \
+            .native = 1,                                                      \
+            .args_offset = 1,                                                 \
+            .code.native = _function,                                         \
+        }                                                                     \
+    }                                                                         \
+}
+
+
+#define                                                                       \
+njs_getter(_getter)                                                           \
+    { .data = { .type = NJS_NATIVE,                                           \
+                .truth = 1,                                                   \
+                .u = { .getter = _getter }                                    \
+    } }
+
+
+#define                                                                       \
+njs_method(_method, _size)                                                    \
+    { .data = { .type = NJS_NATIVE,                                           \
+                .truth = 1,                                                   \
+                .string_size = _size,                                         \
+                .u = { .method = _method }                                    \
+    } }
+
+
+#include <njs_number.h>
+#include <njs_string.h>
+#include <njs_object.h>
+#include <njs_object_hash.h>
+#include <njs_array.h>
+#include <njs_function.h>
+#include <njs_extern.h>
+
+
+typedef njs_ret_t (*njs_vmcode_operation_t)(njs_vm_t *vm, njs_value_t *value1,
+    njs_value_t *value2);
+
+
+#define                                                                       \
+njs_is_null(value)                                                            \
+    ((value)->type == NJS_NULL)
+
+
+#define                                                                       \
+njs_is_void(value)                                                            \
+    ((value)->type == NJS_VOID)
+
+
+#define                                                                       \
+njs_is_null_or_void(value)                                                    \
+    ((value)->type <= NJS_VOID)
+
+
+#define                                                                       \
+njs_is_boolean(value)                                                         \
+    ((value)->type == NJS_BOOLEAN)
+
+
+#define                                                                       \
+njs_is_null_or_void_or_boolean(value)                                         \
+    ((value)->type <= NJS_BOOLEAN)
+
+
+#define                                                                       \
+njs_is_true(value)                                                            \
+    ((value)->data.truth != 0)
+
+
+#define                                                                       \
+njs_is_number(value)                                                          \
+    ((value)->type == NJS_NUMBER)
+
+
+/* Testing for NaN first generates a better code at least on i386/amd64. */
+
+#define                                                                       \
+njs_is_number_true(num)                                                       \
+    (!njs_is_nan(num) && num != 0)
+
+
+#define                                                                       \
+njs_is_numeric(value)                                                         \
+    ((value)->type <= NJS_NUMBER)
+
+
+#define                                                                       \
+njs_is_string(value)                                                          \
+    ((value)->type == NJS_STRING)
+
+
+/*
+ * The truth field coincides with short_string.size and short_string.length
+ * so when string size and length are zero the string's value is false and
+ * otherwise is true.
+ */
+#define                                                                       \
+njs_string_truth(value, size)
+
+
+#define                                                                       \
+njs_is_primitive(value)                                                       \
+    ((value)->type <= NJS_STRING)
+
+
+#define                                                                       \
+njs_is_object(value)                                                          \
+    (((value)->type & NJS_OBJECT) != 0)
+
+
+#define                                                                       \
+njs_is_array(value)                                                           \
+    ((value)->type == NJS_ARRAY)
+
+
+#define                                                                       \
+njs_is_function(value)                                                        \
+    ((value)->type == NJS_FUNCTION)
+
+
+#define                                                                       \
+njs_is_native(value)                                                          \
+    ((value)->type == NJS_NATIVE)
+
+
+#define                                                                       \
+njs_is_external(value)                                                        \
+    ((value)->type == NJS_EXTERNAL)
+
+
+#define                                                                       \
+njs_is_valid(value)                                                           \
+    ((value)->type != NJS_INVALID)
+
+
+#define                                                                       \
+njs_set_invalid(value)                                                        \
+    (value)->type = NJS_INVALID
+
+
+#define                                                                       \
+njs_retain(value)                                                             \
+    do {                                                                      \
+        if ((value)->data.truth == NJS_STRING_LONG) {                         \
+            njs_value_retain(value);                                          \
+        }                                                                     \
+    } while (0)
+
+
+#define                                                                       \
+njs_release(vm, value)                                                        \
+    do {                                                                      \
+        if ((value)->data.truth == NJS_STRING_LONG) {                         \
+            njs_value_release((vm), (value));                                 \
+        }                                                                     \
+    } while (0)
+
+
+#define NJS_VMCODE_3OPERANDS   0
+#define NJS_VMCODE_2OPERANDS   1
+#define NJS_VMCODE_1OPERAND    2
+#define NJS_VMCODE_NO_OPERAND  3
+
+#define NJS_VMCODE_NO_RETVAL   0
+#define NJS_VMCODE_RETVAL      1
+
+
+typedef struct {
+    njs_vmcode_operation_t     operation;
+    uint8_t                    operands;   /* 2 bits */
+    uint8_t                    retval;     /* 1 bit  */
+    uint8_t                    ctor;       /* 1 bit  */
+#if (NXT_64BIT)
+    uint32_t                   nargs;
+#else
+    uint16_t                   nargs;
+#endif
+} njs_vmcode_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                operand1;
+    njs_index_t                operand2;
+    njs_index_t                operand3;
+} njs_vmcode_generic_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                index;
+} njs_vmcode_1addr_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                dst;
+    njs_index_t                src;
+} njs_vmcode_2addr_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                dst;
+    njs_index_t                src1;
+    njs_index_t                src2;
+} njs_vmcode_3addr_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+} njs_vmcode_stop_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                index;
+} njs_vmcode_validate_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                dst;
+    njs_index_t                src;
+} njs_vmcode_move_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+} njs_vmcode_object_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    uintptr_t                  length;
+} njs_vmcode_array_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    njs_function_script_t      *function;
+} njs_vmcode_function_create_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    njs_regexp_pattern_t       *pattern;
+} njs_vmcode_regexp_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_ret_t                  offset;
+} njs_vmcode_jump_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_ret_t                  offset;
+    njs_index_t                cond;
+} njs_vmcode_cond_jump_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                value;
+    njs_index_t                object;
+    njs_index_t                property;
+} njs_vmcode_prop_get_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                value;
+    njs_index_t                object;
+    njs_index_t                property;
+} njs_vmcode_prop_set_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                each;
+    njs_index_t                object;
+    njs_ret_t                  offset;
+} njs_vmcode_prop_start_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    njs_index_t                object;
+    njs_index_t                each;
+    njs_ret_t                  offset;
+} njs_vmcode_prop_each_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                value;
+    njs_index_t                constructor;
+    njs_index_t                object;
+} njs_vmcode_instance_of_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                function;
+    njs_index_t                name;
+} njs_vmcode_function_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                function;
+    njs_index_t                object;
+    njs_index_t                method;
+} njs_vmcode_method_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+    njs_index_t                function;
+} njs_vmcode_call_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_ret_t                  offset;
+    njs_index_t                value;
+} njs_vmcode_try_start_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_ret_t                  offset;
+    njs_index_t                exception;
+} njs_vmcode_catch_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+} njs_vmcode_throw_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_ret_t                  offset;
+} njs_vmcode_try_end_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    njs_index_t                retval;
+} njs_vmcode_finally_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    uintptr_t                  narg;
+} njs_vmcode_to_string_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    uintptr_t                  narg;
+} njs_vmcode_to_number_t;
+
+
+typedef struct {
+    njs_vmcode_t               code;
+    uintptr_t                  unused;
+} njs_vmcode_restart_t;
+
+
+typedef enum {
+    NJS_SCOPE_ABSOLUTE = 0,
+    NJS_SCOPE_LOCAL,
+    NJS_SCOPE_GLOBAL,
+    NJS_SCOPE_CALLEE_ARGUMENTS,
+    NJS_SCOPE_ARGUMENTS,
+    NJS_SCOPE_CLOSURE,
+    NJS_SCOPE_PARENT_LOCAL,
+    NJS_SCOPE_PARENT_ARGUMENTS,
+    NJS_SCOPE_PARENT_CLOSURE,
+} njs_scope_t;
+
+#define NJS_SCOPES             (NJS_SCOPE_PARENT_CLOSURE + 1)
+
+#define NJS_SCOPE_SHIFT        4
+#define NJS_SCOPE_MASK         ((uintptr_t) ((1 << NJS_SCOPE_SHIFT) - 1))
+
+#define NJS_INDEX_CACHE        NJS_SCOPE_LOCAL
+
+#define NJS_INDEX_NONE         ((njs_index_t) 0)
+#define NJS_INDEX_ERROR        ((njs_index_t) -1)
+#define NJS_INDEX_THIS         ((njs_index_t) (0 | NJS_SCOPE_ARGUMENTS))
+
+
+enum njs_prototypes_e {
+    NJS_PROTOTYPE_OBJECT = 0,
+    NJS_PROTOTYPE_ARRAY,
+    NJS_PROTOTYPE_BOOLEAN,
+    NJS_PROTOTYPE_NUMBER,
+    NJS_PROTOTYPE_STRING,
+    NJS_PROTOTYPE_FUNCTION,
+    NJS_PROTOTYPE_REGEXP,
+#define NJS_PROTOTYPE_MAX  (NJS_PROTOTYPE_REGEXP + 1)
+};
+
+
+enum njs_functions_e {
+    NJS_FUNCTION_OBJECT = NJS_PROTOTYPE_OBJECT,
+    NJS_FUNCTION_ARRAY = NJS_PROTOTYPE_ARRAY,
+    NJS_FUNCTION_BOOLEAN = NJS_PROTOTYPE_BOOLEAN,
+    NJS_FUNCTION_NUMBER = NJS_PROTOTYPE_NUMBER,
+    NJS_FUNCTION_STRING = NJS_PROTOTYPE_STRING,
+    NJS_FUNCTION_FUNCTION = NJS_PROTOTYPE_FUNCTION,
+    NJS_FUNCTION_REGEXP = NJS_PROTOTYPE_REGEXP,
+
+    NJS_FUNCTION_EVAL,
+#define NJS_FUNCTION_MAX  (NJS_FUNCTION_EVAL + 1)
+};
+
+
+#define                                                                       \
+njs_scope_index(value)                                                        \
+    ((njs_index_t) (value << NJS_SCOPE_SHIFT))
+
+#define                                                                       \
+njs_global_scope_index(value)                                                 \
+    ((njs_index_t) ((value << NJS_SCOPE_SHIFT) | NJS_SCOPE_GLOBAL))
+
+
+#define NJS_INDEX_OBJECT         njs_global_scope_index(NJS_FUNCTION_OBJECT)
+#define NJS_INDEX_ARRAY          njs_global_scope_index(NJS_FUNCTION_ARRAY)
+#define NJS_INDEX_BOOLEAN        njs_global_scope_index(NJS_FUNCTION_BOOLEAN)
+#define NJS_INDEX_NUMBER         njs_global_scope_index(NJS_FUNCTION_NUMBER)
+#define NJS_INDEX_STRING         njs_global_scope_index(NJS_FUNCTION_STRING)
+#define NJS_INDEX_FUNCTION       njs_global_scope_index(NJS_FUNCTION_FUNCTION)
+#define NJS_INDEX_REGEXP         njs_global_scope_index(NJS_FUNCTION_REGEXP)
+#define NJS_INDEX_EVAL           njs_global_scope_index(NJS_FUNCTION_EVAL)
+
+#define NJS_INDEX_GLOBAL_OFFSET  njs_scope_index(NJS_FUNCTION_MAX)
+
+
+#define                                                                       \
+njs_offset(index)                                                             \
+    ((uintptr_t) (index) & ~NJS_SCOPE_MASK)
+
+
+#define                                                                       \
+njs_vmcode_operand(vm, index)                                                 \
+    ((njs_value_t *)                                                          \
+     ((u_char *) vm->scopes[(uintptr_t) (index) & NJS_SCOPE_MASK]             \
+      + njs_offset(index)))
+
+
+#define                                                                       \
+njs_index_size(index)                                                         \
+    njs_offset(index)
+
+
+struct njs_vm_s {
+    /* njs_vm_t must be aligned to njs_value_t due to scratch value. */
+    njs_value_t              retval;
+
+    /*
+     * The scratch value is used for lvalue operations on nonexistent
+     * properties of non-object values: "a = 1; a.b++".
+     */
+    njs_value_t              scratch;
+
+    u_char                   *current;
+
+    njs_value_t              *scopes[NJS_SCOPES];
+
+    void                     **external;
+
+    njs_native_frame_t       *frame;
+
+    const njs_value_t        *exception;
+
+    nxt_lvlhsh_t             externals_hash;
+    nxt_lvlhsh_t             variables_hash;
+    nxt_lvlhsh_t             functions_hash;
+    nxt_lvlhsh_t             values_hash;
+
+    njs_object_t             *prototypes;
+    njs_function_t           *functions;
+
+    u_char                   *number_trap;
+    u_char                   *string_trap;
+
+    nxt_mem_cache_pool_t     *mem_cache_pool;
+
+    njs_value_t              *global_scope;
+    size_t                   scope_size;
+
+    njs_vm_shared_t          *shared;
+    njs_parser_t             *parser;
+};
+
+
+struct njs_vm_shared_s {
+    nxt_lvlhsh_t             keywords_hash;
+    nxt_lvlhsh_t             values_hash;
+
+    njs_object_t             *prototypes;
+    njs_function_t           *functions;
+
+    u_char                   *number_trap;
+    u_char                   *string_trap;
+};
+
+
+nxt_int_t njs_vmcode_interpreter(njs_vm_t *vm);
+
+void njs_disassembler(u_char *start, u_char *end, nxt_str_t *text);
+
+void njs_value_retain(njs_value_t *value);
+void njs_value_release(njs_vm_t *vm, njs_value_t *value);
+
+njs_ret_t njs_vmcode_object_create(njs_vm_t *vm, njs_value_t *inlvd1,
+    njs_value_t *inlvd2);
+njs_ret_t njs_vmcode_array_create(njs_vm_t *vm, njs_value_t *inlvd1,
+    njs_value_t *inlvd2);
+njs_ret_t njs_vmcode_function_create(njs_vm_t *vm, njs_value_t *inlvd1,
+    njs_value_t *invld2);
+njs_ret_t njs_vmcode_regexp_create(njs_vm_t *vm, njs_value_t *inlvd1,
+    njs_value_t *invld2);
+
+njs_ret_t njs_vmcode_property_get(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *property);
+njs_ret_t njs_vmcode_property_set(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *property);
+njs_ret_t njs_vmcode_property_in(njs_vm_t *vm, njs_value_t *property,
+    njs_value_t *object);
+njs_ret_t njs_vmcode_property_delete(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *property);
+njs_ret_t njs_vmcode_property_each_start(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *invld);
+njs_ret_t njs_vmcode_property_each(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *each);
+njs_ret_t njs_vmcode_instance_of(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *constructor);
+
+njs_ret_t njs_vmcode_increment(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *lvalue);
+njs_ret_t njs_vmcode_decrement(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *lvalue);
+njs_ret_t njs_vmcode_post_increment(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *lvalue);
+njs_ret_t njs_vmcode_post_decrement(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *lvalue);
+njs_ret_t njs_vmcode_typeof(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *invld);
+njs_ret_t njs_vmcode_void(njs_vm_t *vm, njs_value_t *invld1,
+    njs_value_t *invld2);
+njs_ret_t njs_vmcode_delete(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *invld);
+njs_ret_t njs_vmcode_unary_plus(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *invld);
+njs_ret_t njs_vmcode_unary_negation(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *invld);
+njs_ret_t njs_vmcode_addition(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_substraction(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_multiplication(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_division(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_remainder(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_logical_not(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *inlvd);
+njs_ret_t njs_vmcode_logical_and(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_logical_or(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_bitwise_not(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *inlvd);
+njs_ret_t njs_vmcode_bitwise_and(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_bitwise_xor(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_bitwise_or(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_left_shift(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_right_shift(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_unsigned_right_shift(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_equal(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2);
+njs_ret_t njs_vmcode_not_equal(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_less(njs_vm_t *vm, njs_value_t *val1, njs_value_t *val2);
+njs_ret_t njs_vmcode_greater(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_less_or_equal(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_greater_or_equal(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_strict_equal(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+njs_ret_t njs_vmcode_strict_not_equal(njs_vm_t *vm, njs_value_t *val1,
+    njs_value_t *val2);
+
+njs_ret_t njs_vmcode_move(njs_vm_t *vm, njs_value_t *value, njs_value_t *invld);
+njs_ret_t njs_vmcode_validate(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *index);
+
+njs_ret_t njs_vmcode_jump(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *offset);
+njs_ret_t njs_vmcode_if_true_jump(njs_vm_t *vm, njs_value_t *cond,
+    njs_value_t *offset);
+njs_ret_t njs_vmcode_if_false_jump(njs_vm_t *vm, njs_value_t *cond,
+    njs_value_t *offset);
+
+njs_ret_t njs_vmcode_function(njs_vm_t *vm, njs_value_t *name,
+    njs_value_t *invld);
+njs_ret_t njs_vmcode_method(njs_vm_t *vm, njs_value_t *object,
+    njs_value_t *method);
+njs_ret_t njs_vmcode_call(njs_vm_t *vm, njs_value_t *func, njs_value_t *retval);
+njs_ret_t njs_vmcode_return(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *retval);
+njs_ret_t njs_vmcode_stop(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *retval);
+
+njs_ret_t njs_vmcode_try_start(njs_vm_t *vm, njs_value_t *value,
+    njs_value_t *offset);
+njs_ret_t njs_vmcode_try_end(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *offset);
+njs_ret_t njs_vmcode_throw(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *retval);
+njs_ret_t njs_vmcode_catch(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *exception);
+njs_ret_t njs_vmcode_finally(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *retval);
+
+njs_ret_t njs_vmcode_to_number(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *narg);
+njs_ret_t njs_vmcode_to_string(njs_vm_t *vm, njs_value_t *invld,
+    njs_value_t *narg);
+njs_ret_t njs_value_to_primitive(njs_vm_t *vm, njs_value_t *value,
+    nxt_uint_t hint);
+njs_ret_t njs_vmcode_restart(njs_vm_t *vm, njs_value_t *invld1,
+    njs_value_t *invld2);
+
+nxt_noinline void njs_number_set(njs_value_t *value, double num);
+
+nxt_int_t njs_shared_objects_create(njs_vm_t *vm);
+nxt_int_t njs_shared_objects_clone(njs_vm_t *vm);
+
+
+/* STUB */
+u_char *njs_number_trap_create(njs_vm_t *vm);
+u_char *njs_string_trap_create(njs_vm_t *vm);
+
+
+void *njs_lvlhsh_alloc(void *data, size_t size, nxt_uint_t nalloc);
+void njs_lvlhsh_free(void *data, void *p, size_t size);
+
+
+extern const njs_value_t  njs_value_void;
+extern const njs_value_t  njs_value_null;
+extern const njs_value_t  njs_value_false;
+extern const njs_value_t  njs_value_true;
+extern const njs_value_t  njs_value_zero;
+extern const njs_value_t  njs_value_nan;
+
+extern const njs_value_t  njs_string_empty;
+extern const njs_value_t  njs_string_comma;
+extern const njs_value_t  njs_string_void;
+extern const njs_value_t  njs_string_null;
+extern const njs_value_t  njs_string_false;
+extern const njs_value_t  njs_string_true;
+extern const njs_value_t  njs_string_native;
+extern const njs_value_t  njs_string_minus_infinity;
+extern const njs_value_t  njs_string_plus_infinity;
+extern const njs_value_t  njs_string_nan;
+extern const njs_value_t  njs_string_prototype;
+extern const njs_value_t  njs_string_constructor;
+
+extern const njs_value_t  njs_string_object_null;
+extern const njs_value_t  njs_string_object_undefined;
+extern const njs_value_t  njs_string_object_boolean;
+extern const njs_value_t  njs_string_object_number;
+extern const njs_value_t  njs_string_object_string;
+extern const njs_value_t  njs_string_object_object;
+extern const njs_value_t  njs_string_object_array;
+extern const njs_value_t  njs_string_object_function;
+extern const njs_value_t  njs_string_object_regexp;
+
+extern const njs_value_t  njs_exception_syntax_error;
+extern const njs_value_t  njs_exception_reference_error;
+extern const njs_value_t  njs_exception_type_error;
+extern const njs_value_t  njs_exception_range_error;
+extern const njs_value_t  njs_exception_memory_error;
+
+extern const nxt_mem_proto_t     njs_array_mem_proto;
+extern const nxt_lvlhsh_proto_t  njs_object_hash_proto;
+
+
+#endif /* _NJS_VM_H_INCLUDED_ */
diff --git a/njs/njscript.c b/njs/njscript.c
new file mode 100644 (file)
index 0000000..b8d2c79
--- /dev/null
@@ -0,0 +1,436 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_malloc.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <njs_vm.h>
+#include <njs_number.h>
+#include <njs_string.h>
+#include <njs_object.h>
+#include <njs_object_hash.h>
+#include <njs_array.h>
+#include <njs_function.h>
+#include <njs_regexp.h>
+#include <njs_extern.h>
+#include <njs_variable.h>
+#include <njs_parser.h>
+#include <string.h>
+
+
+static void *
+njs_alloc(void *mem, size_t size)
+{
+    return nxt_malloc(size);
+}
+
+
+static void *
+njs_zalloc(void *mem, size_t size)
+{
+    void  *p;
+
+    p = nxt_malloc(size);
+
+    if (p != NULL) {
+        memset(p, 0, size);
+    }
+
+    return p;
+}
+
+
+static void *
+njs_align(void *mem, size_t alignment, size_t size)
+{
+    return nxt_memalign(alignment, size);
+}
+
+
+static void
+njs_free(void *mem, void *p)
+{
+    nxt_free(p);
+}
+
+
+static const nxt_mem_proto_t  njs_vm_mem_cache_pool_proto = {
+    njs_alloc,
+    njs_zalloc,
+    njs_align,
+    NULL,
+    njs_free,
+    NULL,
+    NULL,
+};
+
+
+static void *
+njs_array_mem_alloc(void *mem, size_t size)
+{
+    return nxt_mem_cache_alloc(mem, size);
+}
+
+
+static void
+njs_array_mem_free(void *mem, void *p)
+{
+    nxt_mem_cache_free(mem, p);
+}
+
+
+const nxt_mem_proto_t  njs_array_mem_proto = {
+    njs_array_mem_alloc,
+    NULL,
+    NULL,
+    NULL,
+    njs_array_mem_free,
+    NULL,
+    NULL,
+};
+
+
+njs_vm_t *
+njs_vm_create(nxt_mem_cache_pool_t *mcp, njs_vm_shared_t **shared,
+    nxt_lvlhsh_t *externals)
+{
+    njs_vm_t  *vm;
+
+    if (mcp == NULL) {
+        mcp = nxt_mem_cache_pool_create(&njs_vm_mem_cache_pool_proto, NULL,
+                                       NULL, 2 * nxt_pagesize(), 128, 512, 16);
+        if (nxt_slow_path(mcp == NULL)) {
+            return NULL;
+        }
+    }
+
+    vm = nxt_mem_cache_zalign(mcp, sizeof(njs_value_t), sizeof(njs_vm_t));
+
+    if (nxt_fast_path(vm != NULL)) {
+        vm->mem_cache_pool = mcp;
+
+        if (shared != NULL) {
+
+            if (*shared == NULL) {
+                *shared = nxt_mem_cache_zalloc(mcp, sizeof(njs_vm_shared_t));
+                if (nxt_slow_path(*shared == NULL)) {
+                    return NULL;
+                }
+            }
+
+            vm->shared = *shared;
+        }
+
+        if (externals != NULL) {
+            vm->externals_hash = *externals;
+        }
+    }
+
+    return vm;
+}
+
+
+void
+njs_vm_destroy(njs_vm_t *vm)
+{
+    nxt_mem_cache_pool_destroy(vm->mem_cache_pool);
+}
+
+
+nxt_int_t
+njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end)
+{
+    nxt_int_t           ret;
+    nxt_lvlhsh_t        keywords_hash;
+    njs_lexer_t        *lexer;
+    njs_parser_t       *parser;
+    njs_parser_node_t  *node;
+
+    parser = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_parser_t));
+    if (nxt_slow_path(parser == NULL)) {
+        return NJS_ERROR;
+    }
+
+    vm->parser = parser;
+
+    lexer = nxt_mem_cache_zalloc(vm->mem_cache_pool, sizeof(njs_lexer_t));
+    if (nxt_slow_path(lexer == NULL)) {
+        return NJS_ERROR;
+    }
+
+    parser->lexer = lexer;
+
+    if (vm->shared != NULL) {
+        keywords_hash = vm->shared->keywords_hash;
+        parser->values_hash = vm->shared->values_hash;
+
+        vm->number_trap = vm->shared->number_trap;
+        vm->string_trap = vm->shared->string_trap;
+
+        /* STUB */
+        if (vm->shared->prototypes == NULL) {
+            ret = njs_shared_objects_create(vm);
+            if (nxt_slow_path(ret != NXT_OK)) {
+                return NJS_ERROR;
+            }
+        }
+
+    } else {
+        nxt_lvlhsh_init(&keywords_hash);
+    }
+
+    if (nxt_lvlhsh_is_empty(&keywords_hash)) {
+
+        ret = njs_lexer_keywords_init(vm->mem_cache_pool, &keywords_hash);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return NJS_ERROR;
+        }
+
+        if (vm->shared != NULL) {
+            vm->shared->keywords_hash = keywords_hash;
+        }
+    }
+
+    if (vm->number_trap == NULL) {
+
+        vm->number_trap = njs_number_trap_create(vm);
+        if (nxt_slow_path(vm->number_trap == NULL)) {
+            return NJS_ERROR;
+        }
+
+        vm->string_trap = njs_string_trap_create(vm);
+        if (nxt_slow_path(vm->string_trap == NULL)) {
+            return NJS_ERROR;
+        }
+
+        if (vm->shared != NULL) {
+            vm->shared->number_trap = vm->number_trap;
+            vm->shared->string_trap = vm->string_trap;
+        }
+    }
+
+    parser->lexer->keywords_hash = keywords_hash;
+
+    parser->lexer->start = *start;
+    parser->lexer->end = end;
+
+    parser->code_size = sizeof(njs_vmcode_stop_t);
+    parser->scope = NJS_SCOPE_GLOBAL;
+    parser->scope_offset = NJS_INDEX_GLOBAL_OFFSET;
+    parser->index[NJS_SCOPE_GLOBAL - NJS_INDEX_CACHE] = NJS_INDEX_GLOBAL_OFFSET;
+
+    parser->scope_values = nxt_vector_create(4, sizeof(njs_value_t),
+                                            &njs_array_mem_proto,
+                                            vm->mem_cache_pool);
+    if (nxt_slow_path(parser->scope_values == NULL)) {
+        return NJS_ERROR;
+    }
+
+    /* Empty vector to minimize tests in njs_parser_variable(). */
+    parser->arguments = nxt_vector_create(0, sizeof(njs_variable_t),
+                                         &njs_array_mem_proto,
+                                         vm->mem_cache_pool);
+    if (nxt_slow_path(parser->arguments == NULL)) {
+        return NJS_TOKEN_ERROR;
+    }
+
+    node = njs_parser(vm, parser);
+    if (nxt_slow_path(node == NULL)) {
+        return NJS_ERROR;
+    }
+
+    *start = parser->lexer->start;
+
+    ret = njs_generate_scope(vm, parser, node, njs_vmcode_stop);
+    if (nxt_slow_path(ret != NXT_OK)) {
+        return NJS_ERROR;
+    }
+
+    vm->current = parser->code_start;
+
+    vm->global_scope = parser->local_scope;
+    vm->scope_size = parser->scope_size;
+
+    vm->variables_hash = parser->variables_hash;
+    vm->values_hash = parser->values_hash;
+
+    if (vm->shared != NULL) {
+        vm->shared->values_hash = parser->values_hash;
+    }
+
+    vm->parser = NULL;
+
+    return NJS_OK;
+}
+
+
+njs_vm_t *
+njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp, void **external)
+{
+    u_char                *values;
+    size_t                size, scope_size;
+    njs_vm_t              *nvm;
+    nxt_int_t             ret;
+    njs_frame_t           *frame;
+    nxt_mem_cache_pool_t  *nmcp;
+
+    nxt_thread_log_debug("CLONE:");
+
+    nmcp = mcp;
+
+    if (nmcp == NULL) {
+        nmcp = nxt_mem_cache_pool_create(&njs_vm_mem_cache_pool_proto, NULL,
+                                        NULL, 2 * nxt_pagesize(), 128, 512, 16);
+        if (nxt_slow_path(nmcp == NULL)) {
+            return NULL;
+        }
+    }
+
+    nvm = nxt_mem_cache_zalloc(nmcp, sizeof(njs_vm_t));
+
+    if (nxt_fast_path(nvm != NULL)) {
+        nvm->mem_cache_pool = nmcp;
+
+        nvm->shared = vm->shared;
+
+        nvm->variables_hash = vm->variables_hash;
+        nvm->values_hash = vm->values_hash;
+
+        nvm->number_trap = vm->number_trap;
+        nvm->string_trap = vm->string_trap;
+
+        nvm->retval = njs_value_void;
+        nvm->current = vm->current;
+        nvm->external = external;
+
+        nvm->global_scope = vm->global_scope;
+        scope_size = vm->scope_size;
+        nvm->scope_size = scope_size;
+        scope_size += NJS_INDEX_GLOBAL_OFFSET;
+
+        size = NJS_GLOBAL_FRAME_SIZE + scope_size + NJS_FRAME_SPARE_SIZE;
+        size = nxt_align_size(size, NJS_FRAME_SPARE_SIZE);
+
+        frame = nxt_mem_cache_align(nmcp, sizeof(njs_value_t), size);
+        if (nxt_slow_path(frame == NULL)) {
+            goto fail;
+        }
+
+        nvm->frame = &frame->native;
+
+        frame->native.previous = NULL;
+        frame->native.arguments = NULL;
+        frame->native.start = 1;
+
+        frame->native.u.exception.next = NULL;
+        frame->native.u.exception.catch = NULL;
+
+        frame->prev_arguments = NULL;
+        frame->local = NULL;
+        frame->closure = NULL;
+
+        frame->native.size = size - (NJS_GLOBAL_FRAME_SIZE + scope_size);
+
+        values = (u_char *) frame + NJS_GLOBAL_FRAME_SIZE;
+
+        frame->native.last = values + scope_size;
+
+        nvm->scopes[NJS_SCOPE_GLOBAL] = (njs_value_t *) values;
+        memcpy(values + NJS_INDEX_GLOBAL_OFFSET, vm->global_scope,
+                   vm->scope_size);
+
+        ret = njs_shared_objects_clone(nvm);
+        if (nxt_slow_path(ret != NXT_OK)) {
+            goto fail;
+        }
+
+        return nvm;
+    }
+
+fail:
+
+    if (mcp == NULL && nmcp != NULL) {
+        nxt_mem_cache_pool_destroy(nmcp);
+    }
+
+    return NULL;
+}
+
+
+nxt_int_t
+njs_vm_run(njs_vm_t *vm)
+{
+    nxt_str_t  s;
+    nxt_int_t  ret;
+
+    nxt_thread_log_debug("RUN:");
+
+    ret = njs_vmcode_interpreter(vm);
+
+    if (nxt_slow_path(ret == NXT_AGAIN)) {
+        nxt_thread_log_debug("VM: AGAIN");
+        return ret;
+    }
+
+    if (nxt_slow_path(ret != NXT_DONE)) {
+        nxt_thread_log_debug("VM: ERROR");
+        return ret;
+    }
+
+    if (vm->retval.type == NJS_NUMBER) {
+        nxt_thread_log_debug("VM: %f", vm->retval.data.u.number);
+
+    } else if (vm->retval.type == NJS_BOOLEAN) {
+        nxt_thread_log_debug("VM: boolean: %d", vm->retval.data.truth);
+
+    } else if (vm->retval.type == NJS_STRING) {
+
+        if (njs_value_to_ext_string(vm, &s, &vm->retval) == NJS_OK) {
+            nxt_thread_log_debug("VM: '%V'", &s);
+        }
+
+    } else if (vm->retval.type == NJS_FUNCTION) {
+        nxt_thread_log_debug("VM: function");
+
+    } else if (vm->retval.type == NJS_NULL) {
+        nxt_thread_log_debug("VM: null");
+
+    } else if (vm->retval.type == NJS_VOID) {
+        nxt_thread_log_debug("VM: void");
+
+    } else {
+        nxt_thread_log_debug("VM: unknown: %d", vm->retval.type);
+    }
+
+    return NJS_OK;
+}
+
+
+void
+njs_vm_return(njs_vm_t *vm, njs_value_t *retval)
+{
+    vm->retval = *retval;
+}
+
+
+nxt_int_t
+njs_vm_retval(njs_vm_t *vm, nxt_str_t *retval)
+{
+    return njs_value_to_ext_string(vm, retval, &vm->retval);
+}
+
+
+nxt_int_t
+njs_vm_exception(njs_vm_t *vm, nxt_str_t *retval)
+{
+    return njs_value_to_ext_string(vm, retval, vm->exception);
+}
diff --git a/njs/njscript.h b/njs/njscript.h
new file mode 100644 (file)
index 0000000..df75e40
--- /dev/null
@@ -0,0 +1,111 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NJSCRIPT_H_INCLUDED_
+#define _NJSCRIPT_H_INCLUDED_
+
+
+typedef intptr_t                    njs_ret_t;
+typedef uintptr_t                   njs_index_t;
+typedef struct njs_vm_s             njs_vm_t;
+typedef union  njs_value_s          njs_value_t;
+typedef struct njs_vm_shared_s      njs_vm_shared_t;
+
+typedef struct {
+    njs_value_t                     *object;
+    njs_value_t                     *args;
+    uintptr_t                       nargs;
+    njs_index_t                     retval;
+} njs_param_t;
+
+
+/* sizeof(njs_value_t) is 16 bytes. */
+#define                                                                       \
+njs_argument(args, n)                                                         \
+    (njs_value_t *) ((u_char *) args + n * 16)
+
+
+typedef njs_ret_t (*njs_extern_get_t)(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data);
+typedef njs_ret_t (*njs_extern_set_t)(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_str_t *value);
+typedef njs_ret_t (*njs_extern_find_t)(njs_vm_t *vm, void *obj, uintptr_t data,
+    nxt_bool_t delete);
+typedef njs_ret_t (*njs_extern_each_start_t)(njs_vm_t *vm, void *obj,
+    void *each);
+typedef njs_ret_t (*njs_extern_each_t)(njs_vm_t *vm, njs_value_t *value,
+    void *obj, void *each);
+typedef njs_ret_t (*njs_extern_method_t)(njs_vm_t *vm, njs_param_t *param);
+
+
+typedef struct njs_external_s       njs_external_t;
+
+struct njs_external_s {
+    nxt_str_t                       name;
+
+#define NJS_EXTERN_PROPERTY         0x00
+#define NJS_EXTERN_METHOD           0x01
+#define NJS_EXTERN_OBJECT           0x80
+#define NJS_EXTERN_CASELESS_OBJECT  0x81
+
+    uintptr_t                       type;
+
+    njs_external_t                  *properties;
+    nxt_uint_t                      nproperties;
+
+    njs_extern_get_t                get;
+    njs_extern_set_t                set;
+    njs_extern_find_t               find;
+
+    njs_extern_each_start_t         each_start;
+    njs_extern_each_t               each;
+
+    njs_extern_method_t             method;
+
+    uintptr_t                       data;
+};
+
+
+#define NJS_OK                      NXT_OK
+#define NJS_ERROR                   NXT_ERROR
+#define NJS_AGAIN                   NXT_AGAIN
+#define NJS_DECLINED                NXT_DECLINED
+#define NJS_DONE                    NXT_DONE
+
+
+NXT_EXPORT nxt_int_t njs_add_external(nxt_lvlhsh_t *hash,
+    nxt_mem_cache_pool_t *mcp, uintptr_t object, njs_external_t *external,
+    nxt_uint_t n);
+
+NXT_EXPORT njs_vm_t *njs_vm_create(nxt_mem_cache_pool_t *mcp,
+    njs_vm_shared_t **shared, nxt_lvlhsh_t *externals);
+NXT_EXPORT void njs_vm_destroy(njs_vm_t *vm);
+
+NXT_EXPORT nxt_int_t njs_vm_compile(njs_vm_t *vm, u_char **start, u_char *end);
+NXT_EXPORT njs_vm_t *njs_vm_clone(njs_vm_t *vm, nxt_mem_cache_pool_t *mcp,
+    void **external);
+NXT_EXPORT nxt_int_t njs_vm_run(njs_vm_t *vm);
+
+NXT_EXPORT void njs_vm_return(njs_vm_t *vm, njs_value_t *retval);
+NXT_EXPORT nxt_int_t njs_vm_retval(njs_vm_t *vm, nxt_str_t *retval);
+NXT_EXPORT nxt_int_t njs_vm_exception(njs_vm_t *vm, nxt_str_t *retval);
+
+NXT_EXPORT njs_ret_t njs_string_create(njs_vm_t *vm, njs_value_t *value,
+    u_char *start, size_t size, size_t length);
+NXT_EXPORT njs_ret_t njs_void_set(njs_value_t *value);
+
+NXT_EXPORT void *njs_value_data(njs_value_t *value);
+NXT_EXPORT nxt_uint_t njs_vm_is_reentrant(njs_vm_t *vm);
+NXT_EXPORT nxt_int_t njs_value_string(njs_vm_t *vm, nxt_str_t *retval,
+    njs_value_t *value, njs_value_t **tmp);
+NXT_EXPORT nxt_int_t njs_value_string_copy(njs_vm_t *vm, nxt_str_t *retval,
+    njs_value_t *value, uintptr_t *next);
+
+NXT_EXPORT njs_value_t *njs_array_add(njs_vm_t *vm, njs_value_t *array,
+    u_char *start, size_t size);
+
+
+#endif /* _NJSCRIPT_H_INCLUDED_ */
diff --git a/njs/test/njs_unit_test.c b/njs/test/njs_unit_test.c
new file mode 100644 (file)
index 0000000..0133016
--- /dev/null
@@ -0,0 +1,2937 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_malloc.h>
+#include <nxt_array.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_mem_cache_pool.h>
+#include <njscript.h>
+#include <string.h>
+#include <stdio.h>
+
+
+#define  NXT_FIBOBENCH  0
+
+
+typedef struct {
+    nxt_str_t  script;
+    nxt_str_t  ret;
+} nxt_jscript_test_t;
+
+
+extern char  **environ;
+
+
+static nxt_jscript_test_t  js_test[] =
+{
+#if (NXT_FIBOBENCH == 1)
+
+    { nxt_string("function fibo(n) {                        \
+                      if (n > 1)                            \
+                          return fibo(n - 1) + fibo(n - 2)  \
+                      return 'α'                            \
+                  }                                         \
+                  fibo(32).length"),
+      nxt_string("3524578") },
+
+#elif (NXT_FIBOBENCH > 1)
+
+    { nxt_string("function fibo(n) {                        \
+                      if (n > 1)                            \
+                          return fibo(n - 1) + fibo(n - 2)  \
+                      return 1                              \
+                  }                                         \
+                  fibo(32)"),
+      nxt_string("3524578") },
+
+#else /* !(NXT_FIBOBENCH) */
+
+    { nxt_string("0 == '000'"),
+      nxt_string("true") },
+
+    { nxt_string("999999999999999999999"),
+      nxt_string("1e+21") },
+
+#if 0
+    { nxt_string("9223372036854775808"),
+      nxt_string("9223372036854775808") },
+
+    { nxt_string("18446744073709551616"),
+      nxt_string("18446744073709552000") },
+
+    { nxt_string("1.7976931348623157E+308"),
+      nxt_string("1.7976931348623157e+308") },
+#endif
+
+#if 0
+    { nxt_string("var a = 'a\\'b'"),
+      nxt_string("a'b") },
+#endif
+
+    { nxt_string("+1"),
+      nxt_string("1") },
+
+    { nxt_string("+1\n"),
+      nxt_string("1") },
+
+#if 0
+    { nxt_string(""),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("\n"),
+      nxt_string("SyntaxError") },
+#endif
+
+    { nxt_string("\n +1"),
+      nxt_string("1") },
+
+    { nxt_string("1 + undefined"),
+      nxt_string("NaN") },
+
+    { nxt_string("1 + ''"),
+      nxt_string("1") },
+
+    { nxt_string("undefined + undefined"),
+      nxt_string("NaN") },
+
+    { nxt_string("1.2 + 5.7"),
+      nxt_string("6.9") },
+
+    { nxt_string("1 + 1 + '2' + 1 + 1"),
+      nxt_string("2211") },
+
+    { nxt_string("1.2 - '5.7'"),
+      nxt_string("-4.5") },
+
+    { nxt_string("1.2 + -'5.7'"),
+      nxt_string("-4.5") },
+
+    { nxt_string("1 + +'3'"),
+      nxt_string("4") },
+
+    { nxt_string("1 - undefined"),
+      nxt_string("NaN") },
+
+    { nxt_string("1 - ''"),
+      nxt_string("1") },
+
+    { nxt_string("undefined - undefined"),
+      nxt_string("NaN") },
+
+    { nxt_string("12 | 6"),
+      nxt_string("14") },
+
+    { nxt_string("12 | 'abc'"),
+      nxt_string("12") },
+
+    { nxt_string("-1 | 0"),
+      nxt_string("-1") },
+
+    { nxt_string("-2147483648 | 0"),
+      nxt_string("-2147483648") },
+
+    { nxt_string("1024.9 | 0"),
+      nxt_string("1024") },
+
+    { nxt_string("-1024.9 | 0"),
+      nxt_string("-1024") },
+
+    { nxt_string("9007199254740991 | 0"),
+      nxt_string("-1") },
+
+    { nxt_string("9007199254740992 | 0"),
+      nxt_string("0") },
+
+    { nxt_string("9007199254740993 | 0"),
+      nxt_string("0") },
+
+#if 0
+    { nxt_string("9223372036854775808 | 0"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("9223372036854777856 | 0"),
+      nxt_string("2048") },
+
+    { nxt_string("-9223372036854777856 | 0"),
+      nxt_string("-2048") },
+
+    { nxt_string("NaN | 0"),
+      nxt_string("0") },
+
+    { nxt_string("-NaN | 0"),
+      nxt_string("0") },
+
+    { nxt_string("Infinity | 0"),
+      nxt_string("0") },
+
+    { nxt_string("-Infinity | 0"),
+      nxt_string("0") },
+
+    { nxt_string("+0 | 0"),
+      nxt_string("0") },
+
+    { nxt_string("-0 | 0"),
+      nxt_string("0") },
+
+    { nxt_string("32.5 << 2.4"),
+      nxt_string("128") },
+
+    { nxt_string("32.5 << 'abc'"),
+      nxt_string("32") },
+
+    { nxt_string("'abc' << 2"),
+      nxt_string("0") },
+
+    { nxt_string("-1 << 0"),
+      nxt_string("-1") },
+
+    { nxt_string("-1 << -1"),
+      nxt_string("-2147483648") },
+
+    { nxt_string("-2147483648 << 0"),
+      nxt_string("-2147483648") },
+
+#if 0
+    { nxt_string("9223372036854775808 << 0"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("9223372036854777856 << 0"),
+      nxt_string("2048") },
+
+    { nxt_string("-9223372036854777856 << 0"),
+      nxt_string("-2048") },
+
+    { nxt_string("NaN << 0"),
+      nxt_string("0") },
+
+    { nxt_string("32.5 >> 2.4"),
+      nxt_string("8") },
+
+    { nxt_string("-1 >> 30"),
+      nxt_string("-1") },
+
+    { nxt_string("'abc' >> 2"),
+      nxt_string("0") },
+
+    { nxt_string("-1 >> 0"),
+      nxt_string("-1") },
+
+    { nxt_string("-1 >> -1"),
+      nxt_string("-1") },
+
+    { nxt_string("-2147483648 >> 0"),
+      nxt_string("-2147483648") },
+
+    { nxt_string("-2147483648 >> -1"),
+      nxt_string("-1") },
+
+#if 0
+    { nxt_string("9223372036854775808 >> 0"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("9223372036854777856 >> 0"),
+      nxt_string("2048") },
+
+    { nxt_string("-9223372036854777856 >> 0"),
+      nxt_string("-2048") },
+
+    { nxt_string("NaN >> 0"),
+      nxt_string("0") },
+
+    { nxt_string("-1 >>> 30"),
+      nxt_string("3") },
+
+    { nxt_string("NaN >>> 1"),
+      nxt_string("0") },
+
+#if 0
+    { nxt_string("9223372036854775808 >>> 1"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("-1 >>> 0"),
+      nxt_string("4294967295") },
+
+    { nxt_string("-1 >>> -1"),
+      nxt_string("1") },
+
+    { nxt_string("-2147483648 >>> 0"),
+      nxt_string("2147483648") },
+
+    { nxt_string("-2147483648 >>> -1"),
+      nxt_string("1") },
+
+#if 0
+    { nxt_string("9223372036854775808 >>> 0"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("9223372036854777856 >>> 0"),
+      nxt_string("2048") },
+
+    { nxt_string("-9223372036854777856 >>> 0"),
+      nxt_string("4294965248") },
+
+    { nxt_string("NaN >>> 0"),
+      nxt_string("0") },
+
+    { nxt_string("!2"),
+      nxt_string("false") },
+
+    { nxt_string("1 || 2"),
+      nxt_string("1") },
+
+    { nxt_string("1 && 2"),
+      nxt_string("2") },
+
+    { nxt_string("a = true; a = -~!a"),
+      nxt_string("1") },
+
+    { nxt_string("12 & 6"),
+      nxt_string("4") },
+
+    { nxt_string("-1 & 65536"),
+      nxt_string("65536") },
+
+    { nxt_string("-2147483648 & 65536"),
+      nxt_string("0") },
+
+#if 0
+    { nxt_string("9223372036854775808 & 65536"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("NaN & 65536"),
+      nxt_string("0") },
+
+    { nxt_string("12 ^ 6"),
+      nxt_string("10") },
+
+    { nxt_string("-1 ^ 65536"),
+      nxt_string("-65537") },
+
+    { nxt_string("-2147483648 ^ 65536"),
+      nxt_string("-2147418112") },
+
+#if 0
+    { nxt_string("9223372036854775808 ^ 65536"),
+      nxt_string("65536") },
+#endif
+
+    { nxt_string("NaN ^ 65536"),
+      nxt_string("65536") },
+
+    { nxt_string("x = '1'; +x + 2"),
+      nxt_string("3") },
+
+    { nxt_string("'3' -+-+-+ '1' + '1' / '3' * '6' + '2'"),
+      nxt_string("42") },
+
+    { nxt_string("'true' == true"),
+      nxt_string("false") },
+
+    { nxt_string("null == false"),
+      nxt_string("false") },
+
+    { nxt_string("!null"),
+      nxt_string("true") },
+
+    { nxt_string("0 === -0"),
+      nxt_string("true") },
+
+    { nxt_string("1/-0"),
+      nxt_string("-Infinity") },
+
+    { nxt_string("1/0 === 1/-0"),
+      nxt_string("false") },
+
+    { nxt_string("1 == true"),
+      nxt_string("true") },
+
+    { nxt_string("NaN === NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN !== NaN"),
+      nxt_string("true") },
+
+    { nxt_string("NaN == NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN != NaN"),
+      nxt_string("true") },
+
+    { nxt_string("NaN == false"),
+      nxt_string("false") },
+
+    { nxt_string("Infinity == Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("-Infinity == -Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("-Infinity < Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("Infinity - Infinity"),
+      nxt_string("NaN") },
+
+    { nxt_string("Infinity - -Infinity"),
+      nxt_string("Infinity") },
+
+    { nxt_string("undefined == 0"),
+      nxt_string("false") },
+
+    { nxt_string("undefined == null"),
+      nxt_string("true") },
+
+    { nxt_string("'1' == 1"),
+      nxt_string("true") },
+
+    { nxt_string("'1a' == '1'"),
+      nxt_string("false") },
+
+    { nxt_string("'abc' == 'abc'"),
+      nxt_string("true") },
+
+    { nxt_string("'abc' < 'abcde'"),
+      nxt_string("true") },
+
+    { nxt_string("0 == ''"),
+      nxt_string("true") },
+
+    { nxt_string("0 == ' '"),
+      nxt_string("true") },
+
+    { nxt_string("0 == '  '"),
+      nxt_string("true") },
+
+    { nxt_string("0 == '0'"),
+      nxt_string("true") },
+
+    { nxt_string("0 == ' 0 '"),
+      nxt_string("true") },
+
+    { nxt_string("0 == '000'"),
+      nxt_string("true") },
+
+    { nxt_string("'0' == ''"),
+      nxt_string("false") },
+
+    { nxt_string("1 < 2"),
+      nxt_string("true") },
+
+    { nxt_string("NaN < NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN > NaN"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < 1"),
+      nxt_string("false") },
+
+    { nxt_string("[] == false"),
+      nxt_string("true") },
+
+    { nxt_string("[0] == false"),
+      nxt_string("true") },
+
+    { nxt_string("[0,0] == false"),
+      nxt_string("false") },
+
+    { nxt_string("({}) == false"),
+      nxt_string("false") },
+
+    { nxt_string("var a = { valueOf: function() { return 5 } }; a == 5"),
+      nxt_string("true") },
+
+    { nxt_string("var a = { valueOf: function() { return '5' } }; a == 5"),
+      nxt_string("true") },
+
+    { nxt_string("var a = { valueOf: function() { return '5' } }; a == '5'"),
+      nxt_string("true") },
+
+    /* Comparisions. */
+
+    { nxt_string("null === null"),
+      nxt_string("true") },
+
+    { nxt_string("null !== null"),
+      nxt_string("false") },
+
+    { nxt_string("null == null"),
+      nxt_string("true") },
+
+    { nxt_string("null != null"),
+      nxt_string("false") },
+
+    { nxt_string("null < null"),
+      nxt_string("false") },
+
+    { nxt_string("null > null"),
+      nxt_string("false") },
+
+    { nxt_string("null <= null"),
+      nxt_string("true") },
+
+    { nxt_string("null >= null"),
+      nxt_string("true") },
+
+    /**/
+
+    { nxt_string("null === undefined"),
+      nxt_string("false") },
+
+    { nxt_string("null !== undefined"),
+      nxt_string("true") },
+
+    { nxt_string("null == undefined"),
+      nxt_string("true") },
+
+    { nxt_string("null != undefined"),
+      nxt_string("false") },
+
+    { nxt_string("null < undefined"),
+      nxt_string("false") },
+
+    { nxt_string("null > undefined"),
+      nxt_string("false") },
+
+    { nxt_string("null <= undefined"),
+      nxt_string("false") },
+
+    { nxt_string("null >= undefined"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("null === false"),
+      nxt_string("false") },
+
+    { nxt_string("null !== false"),
+      nxt_string("true") },
+
+    { nxt_string("null == false"),
+      nxt_string("false") },
+
+    { nxt_string("null != false"),
+      nxt_string("true") },
+
+    { nxt_string("null < false"),
+      nxt_string("false") },
+
+    { nxt_string("null > false"),
+      nxt_string("false") },
+
+    { nxt_string("null <= false"),
+      nxt_string("true") },
+
+    { nxt_string("null >= false"),
+      nxt_string("true") },
+
+    /**/
+
+    { nxt_string("null === true"),
+      nxt_string("false") },
+
+    { nxt_string("null !== true"),
+      nxt_string("true") },
+
+    { nxt_string("null == true"),
+      nxt_string("false") },
+
+    { nxt_string("null != true"),
+      nxt_string("true") },
+
+    { nxt_string("null < true"),
+      nxt_string("true") },
+
+    { nxt_string("null > true"),
+      nxt_string("false") },
+
+    { nxt_string("null <= true"),
+      nxt_string("true") },
+
+    { nxt_string("null >= true"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("Infinity === Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("Infinity !== Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("Infinity == Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("Infinity != Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("Infinity < Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("Infinity > Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("Infinity <= Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("Infinity >= Infinity"),
+      nxt_string("true") },
+
+    /**/
+
+    { nxt_string("-Infinity === Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("-Infinity !== Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("-Infinity == Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("-Infinity != Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("-Infinity < Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("-Infinity > Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("-Infinity <= Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("-Infinity >= Infinity"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("NaN === NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN !== NaN"),
+      nxt_string("true") },
+
+    { nxt_string("NaN == NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN != NaN"),
+      nxt_string("true") },
+
+    { nxt_string("NaN < NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN > NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN >= NaN"),
+      nxt_string("false") },
+
+    { nxt_string("NaN <= NaN"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("null < 0"),
+      nxt_string("false") },
+
+    { nxt_string("null < 1"),
+      nxt_string("true") },
+
+    { nxt_string("null < NaN"),
+      nxt_string("false") },
+
+    { nxt_string("null < -Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("null < Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("null < 'null'"),
+      nxt_string("false") },
+
+    { nxt_string("null < '1'"),
+      nxt_string("true") },
+
+    { nxt_string("null < [1]"),
+      nxt_string("true") },
+
+    { nxt_string("null < ({})"),
+      nxt_string("false") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }; null < a"),
+      nxt_string("true") },
+
+    { nxt_string("var a = { valueOf: function() { return 'null' } }; null < a"),
+      nxt_string("false") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }; null < a"),
+      nxt_string("true") },
+
+    /**/
+
+    { nxt_string("undefined < null"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < undefined"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < false"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < true"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < 0"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < 1"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < NaN"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < -Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < 'undefined'"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < '1'"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < [1]"),
+      nxt_string("false") },
+
+    { nxt_string("undefined < ({})"),
+      nxt_string("false") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } } undefined < a"),
+      nxt_string("false") },
+
+    { nxt_string("var a = { valueOf: function() { return 'undefined' } }      \
+                  undefined < a"),
+      nxt_string("false") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }              \
+                  undefined < a"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("false < 1"),
+      nxt_string("true") },
+
+    { nxt_string("true < 1"),
+      nxt_string("false") },
+
+    { nxt_string("-1 < 1"),
+      nxt_string("true") },
+
+    { nxt_string("-1 < '1'"),
+      nxt_string("true") },
+
+    { nxt_string("NaN < NaN"),
+      nxt_string("false") },
+
+    { nxt_string("-Infinity < Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("Infinity < -Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("1 < 'abc'"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("[] === []"),
+      nxt_string("false") },
+
+    { nxt_string("[] !== []"),
+      nxt_string("true") },
+
+    { nxt_string("[] == []"),
+      nxt_string("false") },
+
+    { nxt_string("[] != []"),
+      nxt_string("true") },
+
+    { nxt_string("[] < []"),
+      nxt_string("false") },
+
+    { nxt_string("[] > []"),
+      nxt_string("false") },
+
+    { nxt_string("[] >= []"),
+      nxt_string("true") },
+
+    { nxt_string("[] <= []"),
+      nxt_string("true") },
+
+    /**/
+
+    { nxt_string("({}) === ({})"),
+      nxt_string("false") },
+
+    { nxt_string("({}) !== ({})"),
+      nxt_string("true") },
+
+    { nxt_string("({}) == ({})"),
+      nxt_string("false") },
+
+    { nxt_string("({}) != ({})"),
+      nxt_string("true") },
+
+    { nxt_string("({}) > ({})"),
+      nxt_string("false") },
+
+    { nxt_string("({}) <= ({})"),
+      nxt_string("true") },
+
+    { nxt_string("({}) >= ({})"),
+      nxt_string("true") },
+
+    /**/
+
+    { nxt_string("[0] == ({})"),
+      nxt_string("false") },
+
+    { nxt_string("[0] != ({})"),
+      nxt_string("true") },
+
+    { nxt_string("[0] <= ({})"),
+      nxt_string("true") },
+
+    { nxt_string("[0] >= ({})"),
+      nxt_string("false") },
+
+    /**/
+
+    { nxt_string("a = 1 ? 2 : 3"),
+      nxt_string("2") },
+
+    { nxt_string("a = 1 ? 2 : 3 ? 4 : 5"),
+      nxt_string("2") },
+
+    { nxt_string("a = 0 ? 2 : 3 ? 4 : 5"),
+      nxt_string("4") },
+
+    { nxt_string("0 ? 2 ? 3 : 4 : 5"),
+      nxt_string("5") },
+
+    { nxt_string("1 ? 2 ? 3 : 4 : 5"),
+      nxt_string("3") },
+
+    { nxt_string("1 ? 0 ? 3 : 4 : 5"),
+      nxt_string("4") },
+
+    { nxt_string("(1 ? 0 : 3) ? 4 : 5"),
+      nxt_string("5") },
+
+    { nxt_string("a = (1 + 2) ? 2 ? 3 + 4 : 5 : 6"),
+      nxt_string("7") },
+
+    { nxt_string("a = (1 ? 2 : 3) + 4"),
+      nxt_string("6") },
+
+    { nxt_string("a = 1 ? b = 2 + 4 : b = 3"),
+      nxt_string("6") },
+
+    /* Increment. */
+
+    { nxt_string("var a = 1;   ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = '1'; ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = [1]; ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = {};  ++a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } };   ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }; ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }; ++a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }; ++a"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   a = ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = '1'; a = ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = [1]; a = ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = {};  a = ++a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }   a = ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } } a = ++a"),
+      nxt_string("2") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } } a = ++a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } } a = ++a"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   var b = ++a; a +' '+ b"),
+      nxt_string("2 2") },
+
+    { nxt_string("var a = '1'; var b = ++a; a +' '+ b"),
+      nxt_string("2 2") },
+
+    { nxt_string("var a = [1]; var b = ++a; a +' '+ b"),
+      nxt_string("2 2") },
+
+    { nxt_string("var a = {};  var b = ++a; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }                \
+                  var b = ++a; a +' '+ b"),
+      nxt_string("2 2") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }              \
+                  var b = ++a; a +' '+ b"),
+      nxt_string("2 2") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }              \
+                  var b = ++a; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }               \
+                  var b = ++a; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    /* Post increment. */
+
+    { nxt_string("var a = 1;   a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = '1'; a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = [1]; a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = {};  a++"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }    a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }  a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }  a++"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }  a++"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   a = a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = '1'; a = a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = [1]; a = a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = {};  a = a++"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }   a = a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } } a = a++"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } } a = a++"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } } a = a++"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   var b = a++; a +' '+ b"),
+      nxt_string("2 1") },
+
+    { nxt_string("var a = '1'; var b = a++; a +' '+ b"),
+      nxt_string("2 1") },
+
+    { nxt_string("var a = [1]; var b = a++; a +' '+ b"),
+      nxt_string("2 1") },
+
+    { nxt_string("var a = {};  var b = a++; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }                \
+                  var b = a++; a +' '+ b"),
+      nxt_string("2 1") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }              \
+                  var b = a++; a +' '+ b"),
+      nxt_string("2 1") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }              \
+                  var b = a++; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }               \
+                  var b = a++; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    /* Decrement. */
+
+    { nxt_string("var a = 1;   --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = '1'; --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = [1]; --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = {};  --a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1} };   --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = { valueOf: function() { return '1'} }; --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = { valueOf: function() { return [1]} }; --a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }; --a"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   a = --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = '1'; a = --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = [1]; a = --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = {};  a = --a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1} }   a = --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = { valueOf: function() { return '1'} } a = --a"),
+      nxt_string("0") },
+
+    { nxt_string("var a = { valueOf: function() { return [1]} } a = --a"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } } a = --a"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   var b = --a; a +' '+ b"),
+      nxt_string("0 0") },
+
+    { nxt_string("var a = '1'; var b = --a; a +' '+ b"),
+      nxt_string("0 0") },
+
+    { nxt_string("var a = [1]; var b = --a; a +' '+ b"),
+      nxt_string("0 0") },
+
+    { nxt_string("var a = {};  var b = --a; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }                \
+                  var b = --a; a +' '+ b"),
+      nxt_string("0 0") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }              \
+                  var b = --a; a +' '+ b"),
+      nxt_string("0 0") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }              \
+                  var b = --a; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }               \
+                  var b = --a; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    /* Post decrement. */
+
+    { nxt_string("var a = 1;   a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = '1'; a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = [1]; a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = {};  a--"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }    a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }  a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }  a--"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }  a--"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   a = a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = '1'; a = a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = [1]; a = a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = {};  a = a--"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }   a = a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } } a = a--"),
+      nxt_string("1") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } } a = a--"),
+      nxt_string("NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } } a = a--"),
+      nxt_string("NaN") },
+
+    /**/
+
+    { nxt_string("var a = 1;   var b = a--; a +' '+ b"),
+      nxt_string("0 1") },
+
+    { nxt_string("var a = '1'; var b = a--; a +' '+ b"),
+      nxt_string("0 1") },
+
+    { nxt_string("var a = [1]; var b = a--; a +' '+ b"),
+      nxt_string("0 1") },
+
+    { nxt_string("var a = {};  var b = a--; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return 1 } }                \
+                  var b = a--; a +' '+ b"),
+      nxt_string("0 1") },
+
+    { nxt_string("var a = { valueOf: function() { return '1' } }              \
+                  var b = a--; a +' '+ b"),
+      nxt_string("0 1") },
+
+    { nxt_string("var a = { valueOf: function() { return [1] } }              \
+                  var b = a--; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    { nxt_string("var a = { valueOf: function() { return {} } }               \
+                  var b = a--; a +' '+ b"),
+      nxt_string("NaN NaN") },
+
+    /**/
+
+    { nxt_string("a = 2; b = ++a + ++a; a + ' ' + b"),
+      nxt_string("4 7") },
+
+    { nxt_string("a = 2; b = a++ + a++; a + ' ' + b"),
+      nxt_string("4 5") },
+
+    { nxt_string("a = b = 7; a +' '+ b"),
+      nxt_string("7 7") },
+
+    { nxt_string("a = b = c = 5; a +' '+ b +' '+ c"),
+      nxt_string("5 5 5") },
+
+    { nxt_string("a = b = (c = 5) + 2; a +' '+ b +' '+ c"),
+      nxt_string("7 7 5") },
+
+    { nxt_string("1, 2 + 5, 3"),
+      nxt_string("3") },
+
+    { nxt_string("a = 1 /* YES */\n b = a + 2 \n \n + 1 \n + 3"),
+      nxt_string("7") },
+
+    { nxt_string("a = 1 // YES \n b = a + 2 \n \n + 1 \n + 3"),
+      nxt_string("7") },
+
+    { nxt_string("a = 0; ++ \n a"),
+      nxt_string("1") },
+
+    { nxt_string("a = 0; a \n ++"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("a = 1 ? 2 \n : 3"),
+      nxt_string("2") },
+
+    { nxt_string("a = 0 / 0; b = 1 / 0; c = -1 / 0; a +' '+ b +' '+ c"),
+      nxt_string("NaN Infinity -Infinity") },
+
+    { nxt_string("a = (b = 7) + 5; var c; a +' '+ b +' '+ c"),
+      nxt_string("12 7 undefined") },
+
+    { nxt_string("var a, b = 1, c; a +' '+ b +' '+ c"),
+      nxt_string("undefined 1 undefined") },
+
+    { nxt_string("var a = 1, b = a + 1; a +' '+ b"),
+      nxt_string("1 2") },
+
+    { nxt_string("a = a = 1"),
+      nxt_string("1") },
+
+    { nxt_string("var a = 1, \n b; a +' '+ b"),
+      nxt_string("1 undefined") },
+
+    { nxt_string("a = b + 1; var b; a +' '+ b"),
+      nxt_string("NaN undefined") },
+
+    { nxt_string("var a += 1"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("var a = a + 1"),
+      nxt_string("NaN") },
+
+    { nxt_string("a = b + 1; var b = 1; a +' '+ b"),
+      nxt_string("NaN 1") },
+
+    { nxt_string("(a) = 1"),
+      nxt_string("1") },
+
+    { nxt_string("a"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a + a"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a = b + 1"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a = a + 1"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a += 1"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a += 1; var a = 2"),
+      nxt_string("2") },
+
+    { nxt_string("var a = 1"),
+      nxt_string("1") },
+
+    { nxt_string("var a = 1; a = (a = 2) + a"),
+      nxt_string("4") },
+
+    { nxt_string("var a = 1; a = a + (a = 2)"),
+      nxt_string("3") },
+
+    { nxt_string("var a = 1; a += (a = 2)"),
+      nxt_string("3") },
+
+    { nxt_string("var a = b = 1; a +' '+ b"),
+      nxt_string("1 1") },
+
+    { nxt_string("var a \n if (!a) a = 3; a"),
+      nxt_string("3") },
+
+    { nxt_string("a = 3; if (true) if (false); else a = 2; a"),
+      nxt_string("2") },
+
+    { nxt_string("for (i = 0; i < 10; i++) { i += 1 } i"),
+      nxt_string("10") },
+
+    /* Factorial. */
+
+    { nxt_string("n = 5; f = 1; while (n--) f *= n + 1; f"),
+      nxt_string("120") },
+
+    { nxt_string("n = 5; f = 1; while (n) { f *= n; n-- } f"),
+      nxt_string("120") },
+
+    /* Fibonacci. */
+
+    { nxt_string("var n = 50, x; \
+                  for(i=0,j=1,k=0; k<n; i=j,j=x,k++ ){ x=i+j } x"),
+      nxt_string("20365011074") },
+
+    { nxt_string("3 + 'abc' + 'def' + null + true + false + undefined"),
+      nxt_string("3abcdefnulltruefalseundefined") },
+
+    { nxt_string("a = 0; do a++; while (a < 5) if (a == 5) a = 7.33 \n\
+                  else a = 8; while (a < 10) a++; a"),
+      nxt_string("10.33") },
+
+    /* typeof. */
+
+    { nxt_string("typeof null"),
+      nxt_string("object") },
+
+    { nxt_string("typeof undefined"),
+      nxt_string("undefined") },
+
+    { nxt_string("typeof false"),
+      nxt_string("boolean") },
+
+    { nxt_string("typeof true"),
+      nxt_string("boolean") },
+
+    { nxt_string("typeof 0"),
+      nxt_string("number") },
+
+    { nxt_string("typeof -1"),
+      nxt_string("number") },
+
+    { nxt_string("typeof Infinity"),
+      nxt_string("number") },
+
+    { nxt_string("typeof NaN"),
+      nxt_string("number") },
+
+    { nxt_string("typeof 'a'"),
+      nxt_string("string") },
+
+    { nxt_string("typeof {}"),
+      nxt_string("object") },
+
+    { nxt_string("typeof Object()"),
+      nxt_string("object") },
+
+    { nxt_string("typeof []"),
+      nxt_string("object") },
+
+    { nxt_string("typeof function(){}"),
+      nxt_string("function") },
+
+    { nxt_string("typeof Object"),
+      nxt_string("function") },
+
+    { nxt_string("typeof /./i"),
+      nxt_string("object") },
+
+    { nxt_string("typeof $r"),
+      nxt_string("undefined") },
+
+    { nxt_string("typeof a"),
+      nxt_string("undefined") },
+
+    { nxt_string("typeof a; a"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("typeof a; a = 1"),
+      nxt_string("1") },
+
+    /**/
+
+    { nxt_string("void 0"),
+      nxt_string("undefined") },
+
+    { nxt_string("undefined = 1"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("var a; b = a; a = 1; a +' '+ b"),
+      nxt_string("1 undefined") },
+
+    { nxt_string("a = 1"),
+      nxt_string("1") },
+
+    { nxt_string("a; a = 1; a"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a = {}; typeof a +' '+ a"),
+      nxt_string("object [object Object]") },
+
+    { nxt_string("a = {}; a.b"),
+      nxt_string("undefined") },
+
+    { nxt_string("a = {}; a.b = 1 + 2; a.b"),
+      nxt_string("3") },
+
+    { nxt_string("a = {}; a['b']"),
+      nxt_string("undefined") },
+
+    { nxt_string("a = {}; a.b.c"),
+      nxt_string("TypeError") },
+
+    { nxt_string("a = {}; a.b = 1; a.b"),
+      nxt_string("1") },
+
+    { nxt_string("a = {}; a.b = 1; a.b += 2"),
+      nxt_string("3") },
+
+    { nxt_string("a = {}; a.b = 1; a.b += a.b"),
+      nxt_string("2") },
+
+    { nxt_string("a = {}; a.b = 1; x = {}; x.b = 3; a.b += (x.b = 2)"),
+      nxt_string("3") },
+
+    { nxt_string("a = {}; a.b = 1; a.b += (a.b = 2)"),
+      nxt_string("3") },
+
+    { nxt_string("a = {}; a.b += 1"),
+      nxt_string("NaN") },
+
+    { nxt_string("a = 1; b = 2; a = b += 1"),
+      nxt_string("3") },
+
+    { nxt_string("a = 1; b = { x:2 }; a = b.x += 1"),
+      nxt_string("3") },
+
+    { nxt_string("a = 1; b = { x:2 }; a = b.x += (a = 1)"),
+      nxt_string("3") },
+
+    { nxt_string("a = 1; a.b++; a.b"),
+      nxt_string("undefined") },
+
+    { nxt_string("a = {}; a.b = {}; a.b.c = 1; a.b['c']"),
+      nxt_string("1") },
+
+    { nxt_string("a = {}; a.b = {}; a.b.c = 1; a['b']['c']"),
+      nxt_string("1") },
+
+    { nxt_string("a = {}; a.b = {}; c = 'd'; a.b.d = 1; a['b'][c]"),
+      nxt_string("1") },
+
+    { nxt_string("a = {}; a.b = 1; c = a.b++; a.b +' '+ c"),
+      nxt_string("2 1") },
+
+    { nxt_string("a = 2; a.b = 1; c = a.b++; a +' '+ a.b +' '+ c"),
+      nxt_string("2 undefined NaN") },
+
+    { nxt_string("x = { a: 1 }; x.a"),
+      nxt_string("1") },
+
+    { nxt_string("a = { x:1 }; b = { y:2 }; a.x = b.y; a.x"),
+      nxt_string("2") },
+
+    { nxt_string("a = { x:1 }; b = { y:2 }; c = a.x = b.y; c"),
+      nxt_string("2") },
+
+    { nxt_string("a = { x:1 }; b = { y:2 }; a.x = b.y"),
+      nxt_string("2") },
+
+    { nxt_string("a = { x:1 }; b = a.x = 1 + 2; a.x +' '+ b"),
+      nxt_string("3 3") },
+
+    { nxt_string("a = { x:1 }; b = { y:2 }; c = {}; c.x = a.x = b.y; c.x"),
+      nxt_string("2") },
+
+    { nxt_string("y = 2; x = { a:1, b: y + 5, c:3 }; x.a +' '+ x.b +' '+ x.c"),
+      nxt_string("1 7 3") },
+
+    { nxt_string("x = { a: 1, b: { a:2, c:5 } }; x.a +' '+ x.b.a +' '+ x.b.c"),
+      nxt_string("1 2 5") },
+
+    { nxt_string("var y = 5; x = { a:y }; x.a"),
+      nxt_string("5") },
+
+    { nxt_string("x = { a: 1, b: x.a }"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("a = { b: 2 }; a.b += 1"),
+      nxt_string("3") },
+
+    { nxt_string("o = {a:1}; c = o; o.a = o = {b:5}; \
+                  o.a +' '+ o.b +' '+ c.a.b"),
+      nxt_string("undefined 5 5") },
+
+    { nxt_string("y = { a: 2 }; x = { a: 1, b: y.a }; x.a +' '+ x.b"),
+      nxt_string("1 2") },
+
+    { nxt_string("y = { a: 1 }; x = { a: y.a++, b: y.a++ }\n\
+                 x.a +' '+ x.b +' '+ y.a"),
+      nxt_string("1 2 3") },
+
+    { nxt_string("var a=''; var o = {a:1, b:2} \
+                  for (p in o) { a += p +':'+ o[p] +',' } a"),
+      nxt_string("a:1,b:2,") },
+
+    { nxt_string("x = { a: 1 }; b = delete x.a; x.a +' '+ b"),
+      nxt_string("undefined true") },
+
+    { nxt_string("delete null"),
+      nxt_string("true") },
+
+    { nxt_string("var a; delete a"),
+      nxt_string("false") },
+
+    { nxt_string("delete undefined"),
+      nxt_string("false") },
+
+    { nxt_string("delete NaN"),
+      nxt_string("false") },
+
+    { nxt_string("delete Infinity"),
+      nxt_string("false") },
+
+    { nxt_string("delete -Infinity"),
+      nxt_string("true") },
+
+    { nxt_string("delete (1/0)"),
+      nxt_string("true") },
+
+    { nxt_string("delete 1"),
+      nxt_string("true") },
+
+    { nxt_string("delete (a = 1); a"),
+      nxt_string("1") },
+
+    { nxt_string("delete a"),
+      nxt_string("true") },
+
+    { nxt_string("a = 1; delete a"),
+      nxt_string("true") },
+
+    { nxt_string("a = 1; delete a; typeof a"),
+      nxt_string("undefined") },
+
+    { nxt_string("a = { x:1 }; ('x' in a) +' '+ (1 in a)"),
+      nxt_string("true false") },
+
+    { nxt_string("a = {}; 1 in a"),
+      nxt_string("false") },
+
+    { nxt_string("a = 1; 1 in a"),
+      nxt_string("TypeError") },
+
+    { nxt_string("a = [ 1, 2, 3 ]; a[0] + a[1] + a[2]"),
+      nxt_string("6") },
+
+    { nxt_string("a = [ 1, 2, 3 ]; a[0] +' '+ a[1] +' '+ a[2] +' '+ a[3]"),
+      nxt_string("1 2 3 undefined") },
+
+    { nxt_string("[] - 2"),
+      nxt_string("-2") },
+
+    { nxt_string("[1] - 2"),
+      nxt_string("-1") },
+
+    { nxt_string("[[1]] - 2"),
+      nxt_string("-1") },
+
+    { nxt_string("[[[1]]] - 2"),
+      nxt_string("-1") },
+
+    { nxt_string("a = []; a - 2"),
+      nxt_string("-2") },
+
+    { nxt_string("a = [1]; a - 2"),
+      nxt_string("-1") },
+
+    { nxt_string("a = []; a[0] = 1; a - 2"),
+      nxt_string("-1") },
+
+    { nxt_string("[] + 2 + 3"),
+      nxt_string("23") },
+
+    { nxt_string("[1] + 2 + 3"),
+      nxt_string("123") },
+
+    { nxt_string("a = []; a + 2 + 3"),
+      nxt_string("23") },
+
+    { nxt_string("a = [1]; a + 2 + 3"),
+      nxt_string("123") },
+
+    { nxt_string("a = [1,2]; i = 0; a[i++] += a[0] = 5 + i; a[0] +' '+ a[1]"),
+      nxt_string("7 2") },
+
+    { nxt_string("a = []; a[0] = 1; a + 2 + 3"),
+      nxt_string("123") },
+
+    { nxt_string("a = []; a['0'] = 1; a + 2 + 3"),
+      nxt_string("123") },
+
+    { nxt_string("a = []; a[2] = 1; a[2]"),
+      nxt_string("1") },
+
+    { nxt_string("a = [1, 2]; 1 in a"),
+      nxt_string("true") },
+
+    { nxt_string("a = [1, 2]; 2 in a"),
+      nxt_string("false") },
+
+    { nxt_string("a = [1, 2]; delete a[0]; 0 in a"),
+      nxt_string("false") },
+
+    { nxt_string("var s = '', a = [5,1,2]; \
+                  a[null] = null; \
+                  a[undefined] = 'defined'; \
+                  a[false] = false; \
+                  a[true] = true; \
+                  a[-0] = 0; \
+                  a[Infinity] = Infinity; \
+                  a[-Infinity] = -Infinity; \
+                  a[NaN] = NaN; \
+                  a[-NaN] = -NaN; \
+                  for (i in a) { s += i +':'+ a[i] +',' } s"),
+      nxt_string("0:0,1:1,2:2,null:null,undefined:defined,false:false,true:true,Infinity:Infinity,-Infinity:-Infinity,NaN:NaN,") },
+
+    { nxt_string("[].length"),
+      nxt_string("0") },
+
+    { nxt_string("[1,2].length"),
+      nxt_string("2") },
+
+    { nxt_string("a = [1,2]; a.length"),
+      nxt_string("2") },
+
+    { nxt_string("a = [1,2,3]; a.join()"),
+      nxt_string("1,2,3") },
+
+    { nxt_string("a = [1,2,3]; a.join(':')"),
+      nxt_string("1:2:3") },
+
+    { nxt_string("a = []; a[5] = 5; a.join()"),
+      nxt_string(",,,,,5") },
+
+    { nxt_string("a = []; a[5] = 5; a +''"),
+      nxt_string(",,,,,5") },
+
+    { nxt_string("a = []; a.concat([]) +''"),
+      nxt_string("") },
+
+    { nxt_string("a = [1,2,3]; a.concat(4, [5, 6, 7], 8) +''"),
+      nxt_string("1,2,3,4,5,6,7,8") },
+
+    { nxt_string("a = []; a[100] = a.length; a[100] +' '+ a.length"),
+      nxt_string("0 101") },
+
+    { nxt_string("a = [1,2]; a[100] = 100; a[100] +' '+ a.length"),
+      nxt_string("100 101") },
+
+    { nxt_string("a = [1,2,3,4,5]; b = a.slice(3); b[0] +' '+ b[1] +' '+ b[2]"),
+      nxt_string("4 5 undefined") },
+
+    { nxt_string("a = [1,2]; a.pop() +' '+ a.length +' '+ a"),
+      nxt_string("2 1 1") },
+
+    { nxt_string("a = [1,2]; len = a.push(3); len +' '+ a.pop() +' '+ a"),
+      nxt_string("3 3 1,2") },
+
+    { nxt_string("a = [1,2]; len = a.push(3,4,5); len +' '+ a.pop() +' '+ a"),
+      nxt_string("5 5 1,2,3,4") },
+
+    { nxt_string("a = [1,2,3]; a.shift() +' '+ a[0] +' '+ a.length"),
+      nxt_string("1 2 2") },
+
+    { nxt_string("a = [1,2]; len = a.unshift(3); len +' '+ a.shift()"),
+      nxt_string("3 3") },
+
+    { nxt_string("a = [1,2]; len = a.unshift(3,4,5); len +' '+ a.shift()"),
+      nxt_string("5 3") },
+
+    { nxt_string("var a = []; var s = { sum: 0 }; \
+                  a.forEach(function(v, i, a) { this.sum += v }, s); s.sum"),
+      nxt_string("0") },
+
+    { nxt_string("var a = new Array(3); var s = { sum: 0 }; \
+                  a.forEach(function(v, i, a) { this.sum += v }, s); s.sum"),
+      nxt_string("0") },
+
+#if 0
+    { nxt_string("var a = [,,,]; var s = { sum: 0 }; \
+                  a.forEach(function(v, i, a) { this.sum += v }, s); s.sum"),
+      nxt_string("0") },
+#endif
+
+    { nxt_string("var a = [1,2,3]; var s = { sum: 0 }; \
+                  a.forEach(function(v, i, a) { this.sum += v }, s); s.sum"),
+      nxt_string("6") },
+
+    { nxt_string("var a = [1,2,3]; \
+                  a.forEach(function(v, i, a) { a[i+3] = a.length }); \
+                  a +''"),
+      nxt_string("1,2,3,3,4,5") },
+
+    { nxt_string("var a = []; \
+                  a.some(function(v, i, a) { return v > 1 })"),
+      nxt_string("false") },
+
+    { nxt_string("var a = [1,2,3]; \
+                  a.some(function(v, i, a) { return v > 1 })"),
+      nxt_string("true") },
+
+    { nxt_string("var a = [1,2,3]; \
+                  a.some(function(v, i, a) { return v > 2 })"),
+      nxt_string("true") },
+
+    { nxt_string("var a = [1,2,3]; \
+                  a.some(function(v, i, a) { return v > 3 })"),
+      nxt_string("false") },
+
+    { nxt_string("var a = []; \
+                  a.every(function(v, i, a) { return v > 1 })"),
+      nxt_string("true") },
+
+    { nxt_string("var a = [3,2,1]; \
+                  a.every(function(v, i, a) { return v > 3 })"),
+      nxt_string("false") },
+
+    { nxt_string("var a = [3,2,1]; \
+                  a.every(function(v, i, a) { return v > 2 })"),
+      nxt_string("false") },
+
+    { nxt_string("var a = [3,2,1]; \
+                  a.every(function(v, i, a) { return v > 0 })"),
+      nxt_string("true") },
+
+    { nxt_string("var a = '0123456789' + '012345'  \
+                  var b = 'abcdefghij' + 'klmnop'  \
+                      a = b"),
+      nxt_string("abcdefghijklmnop") },
+
+    { nxt_string("''.length"),
+      nxt_string("0") },
+
+    { nxt_string("'abc'.length"),
+      nxt_string("3") },
+
+    { nxt_string("'絵文字'.length"),
+      nxt_string("3") },
+
+    { nxt_string("'えもじ'.length"),
+      nxt_string("3") },
+
+    { nxt_string("'囲碁織'.length"),
+      nxt_string("3") },
+
+    { nxt_string("a = 'abc'; a.length"),
+      nxt_string("3") },
+
+    { nxt_string("a = 'abc'; a['length']"),
+      nxt_string("3") },
+
+    { nxt_string("a = 'абв'; a.length"),
+      nxt_string("3") },
+
+    { nxt_string("a = 'abc' + 'абв'; a.length"),
+      nxt_string("6") },
+
+    { nxt_string("a = 'abc' + 1 + 'абв'; a +' '+ a.length"),
+      nxt_string("abc1абв 7") },
+
+    /* TODO: '\u00C2\u00B6'.bytes */
+
+    { nxt_string("a = '\xC3\x82\xC2\xB6'.bytes; u = a.utf8; \
+                  a.length +' '+ a +' '+ u.length +' '+ u"),
+      nxt_string("2 \xC2\xB6 1 \xC2\xB6") },
+
+    { nxt_string("a = 1; a.length"),
+      nxt_string("undefined") },
+
+    { nxt_string("a = 'abc'; a.concat('абв', 123)"),
+      nxt_string("abcабв123") },
+
+    { nxt_string("a = $r.uri; s = a.utf8; s.length +' '+ s"),
+      nxt_string("3 АБВ") },
+
+    { nxt_string("a = $r.uri; a +' '+ a.length +' '+ a"),
+      nxt_string("АБВ 6 АБВ") },
+
+    { nxt_string("$r.uri = 'αβγ'; a = $r.uri; a.length +' '+ a"),
+      nxt_string("6 αβγ") },
+
+    { nxt_string("$r.uri.length +' '+ $r.uri"),
+      nxt_string("6 αβγ") },
+
+    { nxt_string("$r.uri = $r.uri.substr(2); $r.uri.length +' '+ $r.uri"),
+      nxt_string("4 βγ") },
+
+    { nxt_string("a = $r.host; a +' '+ a.length +' '+ a"),
+      nxt_string("АБВГДЕЁЖЗИЙ 22 АБВГДЕЁЖЗИЙ") },
+
+    { nxt_string("a = $r.host; a.substr(2, 2)"),
+      nxt_string("Б") },
+
+    { nxt_string("a = $r.header['User-Agent']; a +' '+ a.length +' '+ a"),
+      nxt_string("User-Agent|АБВ 17 User-Agent|АБВ") },
+
+    { nxt_string("var a=''; \
+                  for (p in $r.header) { a += p +':'+ $r.header[p] +',' }\
+                  a"),
+      nxt_string("01:01|АБВ,02:02|АБВ,03:03|АБВ,") },
+
+#if 1
+    { nxt_string("$r.nonexistent"),
+      nxt_string("undefined") },
+#endif
+
+    { nxt_string("a = 'abcdefgh'; a.substr(3, 15)"),
+      nxt_string("defgh") },
+
+    { nxt_string("'abcdefgh'.substr(3, 15)"),
+      nxt_string("defgh") },
+
+    { nxt_string("'abcdefghijklmno'.substr(3, 4)"),
+      nxt_string("defg") },
+
+    { nxt_string("'abcdefghijklmno'.substr(-3, 2)"),
+      nxt_string("mn") },
+
+    { nxt_string("'abcdefgh'.substr(100, 120)"),
+      nxt_string("") },
+
+    { nxt_string("('abc' + 'defgh').substr(1, 4)"),
+      nxt_string("bcde") },
+
+    { nxt_string("'abcdefghijklmno'.substring(3, 5)"),
+      nxt_string("de") },
+
+    { nxt_string("'abcdefgh'.substring(3)"),
+      nxt_string("defgh") },
+
+    { nxt_string("'abcdefgh'.substring(5, 3)"),
+      nxt_string("de") },
+
+    { nxt_string("'abcdefgh'.substring(100, 120)"),
+      nxt_string("") },
+
+    { nxt_string("'abcdefghijklmno'.slice(NaN, 5)"),
+      nxt_string("abcde") },
+
+    { nxt_string("'abcdefghijklmno'.slice('0', '5')"),
+      nxt_string("abcde") },
+
+    { nxt_string("'abcdefghijklmno'.slice(3, 5)"),
+      nxt_string("de") },
+
+    { nxt_string("'abcdefgh'.slice(3)"),
+      nxt_string("defgh") },
+
+    { nxt_string("'abcdefgh'.slice(3, -2)"),
+      nxt_string("def") },
+
+    { nxt_string("'abcdefgh'.slice(5, 3)"),
+      nxt_string("") },
+
+    { nxt_string("'abcdefgh'.slice(100, 120)"),
+      nxt_string("") },
+
+    { nxt_string("'abc'.charAt(1 + 1)"),
+      nxt_string("c") },
+
+    { nxt_string("'abc'.charAt(3)"),
+      nxt_string("") },
+
+    { nxt_string("'abc'.charCodeAt(1 + 1)"),
+      nxt_string("99") },
+
+    { nxt_string("'abc'.charCodeAt(3)"),
+      nxt_string("NaN") },
+
+    { nxt_string("a = 'abcdef'; a.3"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'abcdef'[3]"),
+      nxt_string("d") },
+
+    { nxt_string("'abcdef'[0]"),
+      nxt_string("a") },
+
+    { nxt_string("'abcdef'[-1]"),
+      nxt_string("undefined") },
+
+    { nxt_string("'abcdef'[NaN]"),
+      nxt_string("undefined") },
+
+    { nxt_string("'abcdef'[3.5]"),
+      nxt_string("undefined") },
+
+    { nxt_string("'abcdef'[8]"),
+      nxt_string("undefined") },
+
+    { nxt_string("a = 'abcdef'; b = 1 + 2; a[b]"),
+      nxt_string("d") },
+
+    { nxt_string("'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'.charCodeAt(5)"),
+      nxt_string("1077") },
+
+    { nxt_string("'12345абвгдеёжзийклмнопрстуфхцчшщъыьэюя'.charCodeAt(35)"),
+      nxt_string("1101") },
+
+    { nxt_string("'12345абвгдеёжзийклмнопрстуфхцчшщъыьэюя'.substring(35)"),
+      nxt_string("эюя") },
+
+    { nxt_string("'abcdef'.substr(-5, 4).substring(3, 1).charAt(1)"),
+      nxt_string("d") },
+
+    { nxt_string("'abcdef'.substr(2, 4).charAt(2)"),
+      nxt_string("e") },
+
+    { nxt_string("a = 'abcdef'.substr(2, 4).charAt(2).length"),
+      nxt_string("1") },
+
+    { nxt_string("a = 'abcdef'.substr(2, 4).charAt(2) + '1234'"),
+      nxt_string("e1234") },
+
+    { nxt_string("a = ('abcdef'.substr(2, 5 * 2 - 6).charAt(2) + '1234') \
+                      .length"),
+      nxt_string("5") },
+
+    { nxt_string("a = 'abcdef'; function f(a) { \
+                  return a.slice(a.indexOf('cd')) } f(a)"),
+      nxt_string("cdef") },
+
+    { nxt_string("a = 'abcdef'; a.slice(a.indexOf('cd'))"),
+      nxt_string("cdef") },
+
+    { nxt_string("'abcdef'.indexOf('de', 2)"),
+      nxt_string("3") },
+
+    { nxt_string("'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'.indexOf('лмно', 10)"),
+      nxt_string("12") },
+
+    { nxt_string("'abcdef'.indexOf('a', 10)"),
+      nxt_string("-1") },
+
+    { nxt_string("'abcdef'.indexOf('q', 0)"),
+      nxt_string("-1") },
+
+    { nxt_string("'abcdef'.indexOf('', 10)"),
+      nxt_string("6") },
+
+    { nxt_string("'abcdef'.indexOf('', 3)"),
+      nxt_string("3") },
+
+    { nxt_string("'abc abc abc abc'.lastIndexOf('abc')"),
+      nxt_string("12") },
+
+    { nxt_string("'abc abc abc abc'.lastIndexOf('abc', 11)"),
+      nxt_string("8") },
+
+    { nxt_string("'abc abc abc abc'.lastIndexOf('abc', 0)"),
+      nxt_string("-1") },
+
+    { nxt_string("'abcdefgh'.search()"),
+      nxt_string("0") },
+
+    { nxt_string("'abcdefgh'.search(/def/)"),
+      nxt_string("3") },
+
+    { nxt_string("''.match(/^$/) +''"),
+      nxt_string("") },
+
+    { nxt_string("''.match(/^$/g) +''"),
+      nxt_string("") },
+
+    { nxt_string("'abcdefgh'.match(/def/) +''"),
+      nxt_string("def") },
+
+    { nxt_string("'abc ABC aBc'.match(/abc/ig) +''"),
+      nxt_string("abc,ABC,aBc") },
+
+    { nxt_string("var q = 1; function x(a, b, c) { q = a } x(5); q"),
+      nxt_string("5") },
+
+    { nxt_string("function x(a) { while (a < 2) a++; return a + 1 } x(1) "),
+      nxt_string("3") },
+
+    /* Recursive factorial. */
+
+    { nxt_string("function f(a) {                       \
+                      if (a > 1)                        \
+                          return a * f(a - 1)           \
+                      return 1                          \
+                  }                                     \
+                  f(10)"),
+      nxt_string("3628800") },
+
+    /* Recursive factorial. */
+
+    { nxt_string("function f(a) { return (a > 1) ? a * f(a - 1) : 1 } f(10)"),
+      nxt_string("3628800") },
+
+    /* Recursive fibonacci. */
+
+    { nxt_string("function fibo(n) {                    \
+                      if (n > 1)                        \
+                          return fibo(n-1) + fibo(n-2)  \
+                       return 1                         \
+                  }                                     \
+                  fibo(10)"),
+      nxt_string("89") },
+
+    { nxt_string("function fibo(n) {                    \
+                      if (n > 1)                        \
+                          return fibo(n-1) + fibo(n-2)  \
+                       return '.'                       \
+                  }                                     \
+                  fibo(10).length"),
+      nxt_string("89") },
+
+    { nxt_string("function fibo(n) {                    \
+                      if (n > 1)                        \
+                          return fibo(n-1) + fibo(n-2)  \
+                       return 1                         \
+                  }                                     \
+                  fibo('10')"),
+      nxt_string("89") },
+
+    { nxt_string("function add(a, b) { return a + b }           \
+                  function mul(a, b) { return a * b }           \
+                  function f(a, b) {                            \
+                      return a + mul(add(1, 2), add(2, 3)) + b  \
+                  }                                             \
+                  f(30, 70)"),
+      nxt_string("115") },
+
+    { nxt_string("function a(x, y) { return x + y }     \
+                  function b(x, y) { return x * y }     \
+                  a(3, b(4, 5))"),
+      nxt_string("23") },
+
+    { nxt_string("function x(n) { return n }; x('12'.substr(1))"),
+      nxt_string("2") },
+
+    { nxt_string("function f(a) { a *= 2 } f(10)"),
+      nxt_string("undefined") },
+
+    { nxt_string("function f() { return 5 } f()"),
+      nxt_string("5") },
+
+    { nxt_string("function f() { return 5 } f(1)"),
+      nxt_string("5") },
+
+    { nxt_string("function f() {} f()"),
+      nxt_string("undefined") },
+
+    { nxt_string("function f(a) { return a + 1 } b = f(2)"),
+      nxt_string("3") },
+
+    { nxt_string("f = function(a) { a *= 2; return a }; f(10)"),
+      nxt_string("20") },
+
+    { nxt_string("var f = function b(a) { a *= 2; return a }; f(10)"),
+      nxt_string("20") },
+
+    { nxt_string("var f = function b(a) { a *= 2; return a }; b(10)"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("var f = function(a) { a *= 2; return a }; f(10)"),
+      nxt_string("20") },
+
+    { nxt_string("f = function b(a) { a *= 2; return a }; b(10)"),
+      nxt_string("ReferenceError") },
+
+    { nxt_string("f = function b(a) { a *= 2; return a }; f(10)"),
+      nxt_string("20") },
+
+    { nxt_string("f = a = function(a) { a *= 2; return a }; f(10)"),
+      nxt_string("20") },
+
+    { nxt_string("f = a = function(a) { a *= 2; return a }; a(10)"),
+      nxt_string("20") },
+
+    { nxt_string("var f = function b(a) { a *= 2; return a } = 5"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("function a() { return { x:2} }; var b = a(); b.x"),
+      nxt_string("2") },
+
+    { nxt_string("a = {}; function f(a) { return a + 1 } a.b = f(2); a.b"),
+      nxt_string("3") },
+
+    { nxt_string("a = (function() { return 1 })(); a"),
+      nxt_string("1") },
+
+    { nxt_string("a = (function(a) { return a + 1 })(2); a"),
+      nxt_string("3") },
+
+    { nxt_string("a = (function(a) { return a + 1 }(2)); a"),
+      nxt_string("3") },
+
+    { nxt_string("a = !function(a) { return a + 1 }(2); a"),
+      nxt_string("false") },
+
+    { nxt_string("a = +function(a) { return a + 1 }(2); a"),
+      nxt_string("3") },
+
+    { nxt_string("a = true && function(a) { return a + 1 }(2); a"),
+      nxt_string("3") },
+
+    { nxt_string("a = 0, function(a) { return a + 1 }(2); a"),
+      nxt_string("0") },
+
+    { nxt_string("var o = { f: function(a) { return a * 2 } }; o.f(5)"),
+      nxt_string("10") },
+
+    { nxt_string("var o = {}; o.f = function(a) { return a * 2 }; o.f(5)"),
+      nxt_string("10") },
+
+    { nxt_string("var o = { x: 1, f: function() { return this.x } } o.f()"),
+      nxt_string("1") },
+
+    { nxt_string("var o = { x: 1, f: function(a) { return this.x += a } } \
+                  o.f(5) +' '+ o.x"),
+      nxt_string("6 6") },
+
+    { nxt_string("var f = function(a) { return 3 } f.call()"),
+      nxt_string("3") },
+
+    { nxt_string("var f = function(a) { return this } f.call(5)"),
+      nxt_string("5") },
+
+    { nxt_string("var f = function(a, b) { return this + a } f.call(5, 1)"),
+      nxt_string("6") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b } \
+                  f.call(5, 1, 2)"),
+      nxt_string("8") },
+
+    { nxt_string("var f = function(a) { return 3 } f.apply()"),
+      nxt_string("3") },
+
+    { nxt_string("var f = function(a) { return this } f.apply(5)"),
+      nxt_string("5") },
+
+    { nxt_string("var f = function(a) { return this + a } f.apply(5, 1)"),
+      nxt_string("TypeError") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b } \
+                  f.apply(5, [1, 2])"),
+      nxt_string("8") },
+
+    { nxt_string("var f = function(a, b) { return this + a + b } \
+                  f.apply(5, [1, 2], 3)"),
+      nxt_string("8") },
+
+    { nxt_string("''.concat.call()"),
+      nxt_string("TypeError") },
+
+    { nxt_string("''.concat.call('a', 'b', 'c')"),
+      nxt_string("abc") },
+
+    { nxt_string("''.concat.call('a')"),
+      nxt_string("a") },
+
+    { nxt_string("''.concat.call('a', [ 'b', 'c' ])"),
+      nxt_string("ab,c") },
+
+    { nxt_string("''.concat.call('a', [ 'b', 'c' ], 'd')"),
+      nxt_string("ab,cd") },
+
+    { nxt_string("''.concat.apply()"),
+      nxt_string("TypeError") },
+
+    { nxt_string("''.concat.apply('a')"),
+      nxt_string("a") },
+
+    { nxt_string("''.concat.apply('a', 'b')"),
+      nxt_string("TypeError") },
+
+    { nxt_string("''.concat.apply('a', [ 'b', 'c' ])"),
+      nxt_string("abc") },
+
+    { nxt_string("''.concat.apply('a', [ 'b', 'c' ], 'd')"),
+      nxt_string("abc") },
+
+    { nxt_string("[].join.call([1,2,3])"),
+      nxt_string("1,2,3") },
+
+    { nxt_string("[].join.call([1,2,3], ':')"),
+      nxt_string("1:2:3") },
+
+    { nxt_string("[].join.call()"),
+      nxt_string("TypeError") },
+
+    { nxt_string("function F(a, b) { this.a = a + b } \
+                  var o = new F(1, 2) \
+                  o.a"),
+      nxt_string("3") },
+
+    { nxt_string("function F(a, b) { this.a = a + b; return { a: 7 } } \
+                  var o = new F(1, 2) \
+                  o.a"),
+      nxt_string("7") },
+
+    { nxt_string("function a() { return function(x) { return x + 1 } } \
+                  b = a(); b(2)"),
+      nxt_string("3") },
+
+    { nxt_string("/^$/.test('')"),
+      nxt_string("true") },
+
+    { nxt_string("var a = /\\d/; a.test('123')"),
+      nxt_string("true") },
+
+    { nxt_string("var a = /\\d/; a.test('abc')"),
+      nxt_string("false") },
+
+    { nxt_string("/\\d/.test('123')"),
+      nxt_string("true") },
+
+    { nxt_string("/\\d/.test('abc')"),
+      nxt_string("false") },
+
+    { nxt_string("/abc/i.test('ABC')"),
+      nxt_string("true") },
+
+    { nxt_string("/абв/i.test('АБВ')"),
+      nxt_string("true") },
+
+    /* TODO: '\u00C2\u00B6".bytes */
+
+    { nxt_string("/\xC2\xB6/.test('\xC3\x82\xC2\xB6'.bytes)"),
+      nxt_string("true") },
+
+    { nxt_string("/\\x80/.test('\x80'.bytes)"),
+      nxt_string("true") },
+
+    { nxt_string("var a = /^$/.exec(''); a.length +' '+ a"),
+      nxt_string("1 ") },
+
+    { nxt_string("var r = /бв/ig; var a = r.exec('АБВ'); r.lastIndex +' '+ a"),
+      nxt_string("3 БВ") },
+
+    { nxt_string("var r = /\\x80/g; r.exec('\x81\x80'.bytes); r.lastIndex"),
+      nxt_string("1") },
+
+    /*
+     * It seems that "/стоп/ig" fails on early PCRE versions.
+     * It fails at least in 8.1 and works at least in 8.31.
+     */
+
+    { nxt_string("var r = /Стоп/ig; \
+                  var a = r.exec('АБВДЕЁЖЗИКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯСТОП'); \
+                  r.lastIndex +' '+ a"),
+      nxt_string("35 СТОП") },
+
+    { nxt_string("var r = /quick\\s(brown).+?(jumps)/ig \
+                 var a = r.exec('The Quick Brown Fox Jumps Over The Lazy Dog') \
+                 a[0] +' '+ a[1] +' '+ a[2] +' '+ a[3] +' '+ \
+                 a.index +' '+ r.lastIndex +' '+ a.input"),
+      nxt_string("Quick Brown Fox Jumps Brown Jumps undefined 4 25 The Quick Brown Fox Jumps Over The Lazy Dog") },
+
+    { nxt_string("var s; var r = /./g; while (s = r.exec('abc')); s"),
+      nxt_string("null") },
+
+    { nxt_string("var r = /LS/i.exec(false); r[0]"),
+      nxt_string("ls") },
+
+    /* Non-standard ECMA-262 features. */
+
+    /* 0x10400 is not a surrogate pair of 0xD801 and 0xDC00. */
+
+    { nxt_string("var chars = '𐐀'; chars.length +' '+ chars.charCodeAt(0)"),
+      nxt_string("1 66560") },
+
+    /* es5id: 6.1, 0x104A0 is not a surrogate pair of 0xD801 and 0xDCA0. */
+
+    { nxt_string("var chars = '𐒠'; chars.length +' '+ chars.charCodeAt(0)"),
+      nxt_string("1 66720") },
+
+    /* Exceptions. */
+
+    { nxt_string("throw null"),
+      nxt_string("") },
+
+    { nxt_string("var a; try { throw null } catch (e) { a = e } a"),
+      nxt_string("null") },
+
+    { nxt_string("try { throw null } catch (e) { throw e }"),
+      nxt_string("") },
+
+    { nxt_string("var a = 0; try { a = 5 } \
+                  catch (e) { a = 9 } finally { a++ } a"),
+      nxt_string("6") },
+
+    { nxt_string("var a = 0; try { throw 3 } \
+                  catch (e) { a = e } finally { a++ } a"),
+      nxt_string("4") },
+
+    { nxt_string("var a = 0; try { throw 3 } \
+                  catch (e) { throw e + 1 } finally { a++ }"),
+      nxt_string("") },
+
+    { nxt_string("var a = 0; try { throw 3 } \
+                  catch (e) { a = e } finally { throw a }"),
+      nxt_string("") },
+
+    { nxt_string("try { throw null } catch (e) { } finally { }"),
+      nxt_string("undefined") },
+
+    { nxt_string("var a = 0; try { throw 3 } \
+                  catch (e) { throw 4 } finally { throw a }"),
+      nxt_string("") },
+
+    { nxt_string("var a = 0; try { a = 5 } finally { a++ } a"),
+      nxt_string("6") },
+
+    { nxt_string("var a = 0; try { throw 5 } finally { a++ }"),
+      nxt_string("") },
+
+    { nxt_string("var a = 0; try { a = 5 } finally { throw 7 }"),
+      nxt_string("") },
+
+    { nxt_string("function f(a) {                    \
+                     if (a > 1) return f(a - 1);     \
+                     throw 9; return a }             \
+                  var a = 0; try { a = f(5); a++ } catch(e) { a = e } a"),
+      nxt_string("9") },
+
+    { nxt_string("var a; try { try { throw 5 } catch (e) { a = e } throw 3 } \
+                         catch(x) { a += x } a"),
+      nxt_string("8") },
+
+    { nxt_string("var o = { valueOf: function() { return '3' } }; --o"),
+      nxt_string("2") },
+
+    { nxt_string("var o = { valueOf: function() { return [3] } }; --o"),
+      nxt_string("NaN") },
+
+    { nxt_string("var o = { valueOf: function() { return '3' } } 10 - o"),
+      nxt_string("7") },
+
+    { nxt_string("var o = { valueOf: function() { return [3] } } 10 - o"),
+      nxt_string("NaN") },
+
+    { nxt_string("var o = { toString: function() { return 'OK' } } 'o:' + o"),
+      nxt_string("o:OK") },
+
+    { nxt_string("var o = { toString: function() { return [1] } } 'o:' + o"),
+      nxt_string("TypeError") },
+
+    { nxt_string("var a = { valueOf: function() { return '3' } }             \
+                  var b = { toString: function() { return 10 - a + 'OK' } }  \
+                  var c = { toString: function() { return b + 'YES' } }      \
+                  'c:' + c"),
+      nxt_string("c:7OKYES") },
+
+    { nxt_string("[1,2,3].valueOf()"),
+      nxt_string("1,2,3") },
+
+    { nxt_string("var o = { valueOf: function() { return 'OK' } } o.valueOf()"),
+      nxt_string("OK") },
+
+    { nxt_string("[].__proto__ === [1,2].__proto__"),
+      nxt_string("true") },
+
+    { nxt_string("/./.__proto__ === /a/.__proto__"),
+      nxt_string("true") },
+
+    { nxt_string("''.__proto__ === 'abc'.__proto__"),
+      nxt_string("true") },
+
+    { nxt_string("[].__proto__.join.call([1,2,3], ':')"),
+      nxt_string("1:2:3") },
+
+    { nxt_string("''.__proto__.concat.call('a', 'b', 'c')"),
+      nxt_string("abc") },
+
+    { nxt_string("/./.__proto__.test.call(/a{2}/, 'aaa')"),
+      nxt_string("true") },
+
+    { nxt_string("({}) instanceof Object"),
+      nxt_string("true") },
+
+    { nxt_string("[] instanceof Array"),
+      nxt_string("true") },
+
+    { nxt_string("[] instanceof Object"),
+      nxt_string("true") },
+
+    { nxt_string("var o = Object(); o"),
+      nxt_string("[object Object]") },
+
+    { nxt_string("var o = new Object(); o"),
+      nxt_string("[object Object]") },
+
+    { nxt_string("var o = new Object(1); o +''"),
+      nxt_string("1") },
+
+    { nxt_string("var o = {}; o === Object(o)"),
+      nxt_string("true") },
+
+    { nxt_string("var o = {}; o === new Object(o)"),
+      nxt_string("true") },
+
+    { nxt_string("Object.name"),
+      nxt_string("Object") },
+
+    { nxt_string("Object.prototype.constructor === Object"),
+      nxt_string("true") },
+
+    { nxt_string("Object.constructor === Function"),
+      nxt_string("true") },
+
+    { nxt_string("var a = Array(3); a +''"),
+      nxt_string(",,") },
+
+    { nxt_string("var a = Array(); a.length"),
+      nxt_string("0") },
+
+    { nxt_string("var a = Array(0); a.length"),
+      nxt_string("0") },
+
+    { nxt_string("var a = Array(true); a +''"),
+      nxt_string("true") },
+
+    { nxt_string("var a = Array(1,'two',3); a +''"),
+      nxt_string("1,two,3") },
+
+    { nxt_string("var a = Array(-1)"),
+      nxt_string("RangeError") },
+
+    { nxt_string("var a = Array(2.5)"),
+      nxt_string("RangeError") },
+
+    { nxt_string("var a = Array(NaN)"),
+      nxt_string("RangeError") },
+
+    { nxt_string("var a = Array(Infinity)"),
+      nxt_string("RangeError") },
+
+    { nxt_string("var a = new Array(3); a +''"),
+      nxt_string(",,") },
+
+    { nxt_string("Array.name"),
+      nxt_string("Array") },
+
+    { nxt_string("Array.length"),
+      nxt_string("1") },
+
+    { nxt_string("Array.prototype.constructor === Array"),
+      nxt_string("true") },
+
+    { nxt_string("Array.constructor === Function"),
+      nxt_string("true") },
+
+    { nxt_string("var a = []; a.join = 'OK'; a +''"),
+      nxt_string("[object Array]") },
+
+    { nxt_string("[].__proto__ === Array.prototype"),
+      nxt_string("true") },
+
+    { nxt_string("[].__proto__.constructor === Array"),
+      nxt_string("true") },
+
+    { nxt_string("[].constructor === Array"),
+      nxt_string("true") },
+
+    /* TODO: Boolean */
+
+    { nxt_string("Number()"),
+      nxt_string("0") },
+
+    { nxt_string("typeof Number(1)"),
+      nxt_string("number") },
+
+    { nxt_string("typeof new Number(1)"),
+      nxt_string("object") },
+
+    { nxt_string("Number.name"),
+      nxt_string("Number") },
+
+    { nxt_string("Number.length"),
+      nxt_string("1") },
+
+    { nxt_string("Number.prototype.constructor === Number"),
+      nxt_string("true") },
+
+    { nxt_string("Number.constructor === Function"),
+      nxt_string("true") },
+
+    { nxt_string("String()"),
+      nxt_string("") },
+
+    { nxt_string("typeof String('abc')"),
+      nxt_string("string") },
+
+    { nxt_string("typeof new String('abc')"),
+      nxt_string("object") },
+
+    { nxt_string("String.name"),
+      nxt_string("String") },
+
+    { nxt_string("String.length"),
+      nxt_string("1") },
+
+    { nxt_string("String.prototype.length"),
+      nxt_string("0") },
+
+    { nxt_string("String.prototype.constructor === String"),
+      nxt_string("true") },
+
+    { nxt_string("String.constructor === Function"),
+      nxt_string("true") },
+
+    { nxt_string("'test'.__proto__ === String.prototype"),
+      nxt_string("true") },
+
+    { nxt_string("'test'.constructor === String"),
+      nxt_string("true") },
+
+    { nxt_string("'test'.constructor.prototype === String.prototype"),
+      nxt_string("true") },
+
+    { nxt_string("Function.name"),
+      nxt_string("Function") },
+
+    { nxt_string("Function.length"),
+      nxt_string("0") },
+
+    { nxt_string("Function.prototype.constructor === Function"),
+      nxt_string("true") },
+
+    { nxt_string("Function.constructor === Function"),
+      nxt_string("true") },
+
+    { nxt_string("RegExp.name"),
+      nxt_string("RegExp") },
+
+    { nxt_string("RegExp.length"),
+      nxt_string("2") },
+
+    { nxt_string("RegExp.prototype.constructor === RegExp"),
+      nxt_string("true") },
+
+    { nxt_string("RegExp.constructor === Function"),
+      nxt_string("true") },
+
+#if 0
+    { nxt_string("Object.prototype.toString.call()"),
+      nxt_string("[object Undefined]") },
+#endif
+
+    { nxt_string("Object.prototype.toString.call(undefined)"),
+      nxt_string("[object Undefined]") },
+
+    { nxt_string("Object.prototype.toString.call(null)"),
+      nxt_string("[object Null]") },
+
+    { nxt_string("Object.prototype.toString.call(true)"),
+      nxt_string("[object Boolean]") },
+
+    { nxt_string("Object.prototype.toString.call(1)"),
+      nxt_string("[object Number]") },
+
+    { nxt_string("Object.prototype.toString.call('')"),
+      nxt_string("[object String]") },
+
+    { nxt_string("Object.prototype.toString.call({})"),
+      nxt_string("[object Object]") },
+
+    { nxt_string("Object.prototype.toString.call([])"),
+      nxt_string("[object Array]") },
+
+    { nxt_string("Object.prototype.toString.call(new Object(true))"),
+      nxt_string("[object Boolean]") },
+
+    { nxt_string("Object.prototype.toString.call(new Number(1))"),
+      nxt_string("[object Number]") },
+
+    { nxt_string("Object.prototype.toString.call(new Object(1))"),
+      nxt_string("[object Number]") },
+
+    { nxt_string("Object.prototype.toString.call(new Object(''))"),
+      nxt_string("[object String]") },
+
+    { nxt_string("Object.prototype.toString.call(function(){})"),
+      nxt_string("[object Function]") },
+
+    { nxt_string("Object.prototype.toString.call(/./)"),
+      nxt_string("[object RegExp]") },
+
+    { nxt_string("var p = { a:5 }; var o = Object.create(p); o.a"),
+      nxt_string("5") },
+
+    { nxt_string("var p = { a:5 }; var o = Object.create(p); \
+                  o.__proto__ === p"),
+      nxt_string("true") },
+
+    { nxt_string("var o = Object.create(Object.prototype); \
+                  o.__proto__ === Object.prototype"),
+      nxt_string("true") },
+
+    { nxt_string("var o = Object.create(null); '__proto__' in o"),
+      nxt_string("false") },
+
+    /*  es5id: 8.2_A1_T1 */
+    /*  es5id: 8.2_A1_T2 */
+
+    { nxt_string("var x = null;"),
+      nxt_string("") },
+
+#if 0
+    /*  es5id: 8.2_A2 */
+
+    { nxt_string("var null;"),
+      nxt_string("SyntaxError") },
+#endif
+
+    /*  es5id: 8.2_A3 */
+
+    { nxt_string("typeof(null) === \"object\""),
+      nxt_string("true") },
+
+#endif  /* NXT_FIBOBENCH */
+
+};
+
+
+typedef struct {
+    nxt_mem_cache_pool_t  *mem_cache_pool;
+    nxt_str_t             uri;
+} nxt_jscript_unit_test_req;
+
+
+static njs_ret_t
+nxt_jscript_unit_test_r_get_uri_external(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data)
+{
+    nxt_jscript_unit_test_req  *r;
+
+    r = (nxt_jscript_unit_test_req *) obj;
+
+    return njs_string_create(vm, value, r->uri.data, r->uri.len, 0);
+}
+
+
+static njs_ret_t
+nxt_jscript_unit_test_r_set_uri_external(njs_vm_t *vm,
+    void *obj, uintptr_t data, nxt_str_t *value)
+{
+    nxt_jscript_unit_test_req  *r;
+
+    r = (nxt_jscript_unit_test_req *) obj;
+    r->uri = *value;
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+nxt_jscript_unit_test_host_external(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data)
+{
+    return njs_string_create(vm, value, (u_char *) "АБВГДЕЁЖЗИЙ", 22, 0);
+}
+
+
+static njs_ret_t
+nxt_jscript_unit_test_header_external(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data)
+{
+    u_char                     *s, *p;
+    uint32_t                   size;
+    nxt_str_t                  *h;
+    nxt_jscript_unit_test_req  *r;
+
+    r = (nxt_jscript_unit_test_req *) obj;
+    h = (nxt_str_t *) data;
+
+    size = 7 + h->len;
+
+    s = nxt_mem_cache_alloc(r->mem_cache_pool, size);
+    if (nxt_slow_path(s == NULL)) {
+        return NXT_ERROR;
+    }
+
+    p = memcpy(s, h->data, h->len);
+    p += h->len;
+    *p++ = '|';
+    memcpy(p, "АБВ", 6);
+
+    return njs_string_create(vm, value, s, size, 0);
+}
+
+
+static njs_ret_t
+nxt_jscript_unit_test_header_each_start_external(njs_vm_t *vm, void *obj,
+    void *each)
+{
+    u_char  *s;
+
+    s = each;
+    s[0] = '0';
+    s[1] = '0';
+
+    return NXT_OK;
+}
+
+
+static njs_ret_t
+nxt_jscript_unit_test_header_each_external(njs_vm_t *vm, njs_value_t *value,
+    void *obj, void *each)
+{
+    u_char  *s;
+
+    s = each;
+    s[1]++;
+
+    if (s[1] == '4') {
+        return NXT_DONE;
+    }
+
+    return njs_string_create(vm, value, s, 2, 0);
+}
+
+
+static njs_ret_t
+nxt_jscript_unit_test_undefined_external(njs_vm_t *vm, njs_value_t *value,
+    void *obj, uintptr_t data)
+{
+    njs_void_set(value);
+
+    return NJS_OK;
+}
+
+
+static njs_external_t  nxt_test_r_external[] = {
+
+    { nxt_string("uri"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      nxt_jscript_unit_test_r_get_uri_external,
+      nxt_jscript_unit_test_r_set_uri_external,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("host"),
+      NJS_EXTERN_PROPERTY,
+      NULL,
+      0,
+      nxt_jscript_unit_test_host_external,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+    { nxt_string("header"),
+      NJS_EXTERN_OBJECT,
+      NULL,
+      0,
+      nxt_jscript_unit_test_header_external,
+      NULL,
+      NULL,
+      nxt_jscript_unit_test_header_each_start_external,
+      nxt_jscript_unit_test_header_each_external,
+      NULL,
+      0 },
+
+};
+
+
+static njs_external_t  nxt_test_external[] = {
+
+    { nxt_string("$r"),
+      NJS_EXTERN_OBJECT,
+      nxt_test_r_external,
+      nxt_nitems(nxt_test_r_external),
+      nxt_jscript_unit_test_undefined_external,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      NULL,
+      0 },
+
+};
+
+
+static nxt_int_t
+nxt_jscript_unit_test_externals(nxt_lvlhsh_t *externals, nxt_mem_cache_pool_t *mcp)
+{
+    nxt_lvlhsh_init(externals);
+
+    return njs_add_external(externals, mcp, 0, nxt_test_external,
+                            nxt_nitems(nxt_test_external));
+}
+
+
+static void *
+njs_alloc(void *mem, size_t size)
+{
+    return nxt_malloc(size);
+}
+
+
+static void *
+njs_zalloc(void *mem, size_t size)
+{
+    void  *p;
+
+    p = nxt_malloc(size);
+
+    if (p != NULL) {
+        memset(p, 0, size);
+    }
+
+    return p;
+}
+
+
+static void *
+njs_align(void *mem, size_t alignment, size_t size)
+{
+    return nxt_memalign(alignment, size);
+}
+
+
+static void
+njs_free(void *mem, void *p)
+{
+    nxt_free(p);
+}
+
+
+static const nxt_mem_proto_t  nxt_jscript_mem_cache_pool_proto = {
+    njs_alloc,
+    njs_zalloc,
+    njs_align,
+    NULL,
+    njs_free,
+    NULL,
+    NULL,
+};
+
+
+static nxt_int_t
+nxt_jscript_unit_test(void)
+{
+    void                      *ext_object;
+    u_char                    *start;
+    njs_vm_t                  *vm, *nvm;
+    nxt_int_t                  ret;
+    nxt_str_t                  s;
+    nxt_bool_t                 ok;
+    nxt_uint_t                 i;
+    nxt_lvlhsh_t               externals;
+    njs_vm_shared_t           *shared;
+    nxt_mem_cache_pool_t       *mcp;
+    nxt_jscript_unit_test_req  r;
+
+    shared = NULL;
+
+    mcp = nxt_mem_cache_pool_create(&nxt_jscript_mem_cache_pool_proto, NULL, NULL,
+                                   2 * nxt_pagesize(), 128, 512, 16);
+    if (nxt_slow_path(mcp == NULL)) {
+        return NXT_ERROR;
+    }
+
+    r.mem_cache_pool = mcp;
+    r.uri.len = 6;
+    r.uri.data = (u_char *) "АБВ";
+
+    ext_object = &r;
+
+    if (nxt_jscript_unit_test_externals(&externals, mcp) != NXT_OK) {
+        return NXT_ERROR;
+    }
+
+    for (i = 0; i < nxt_nitems(js_test); i++) {
+
+#if 1
+        printf("\"%.*s\"\n",
+               (int) js_test[i].script.len, js_test[i].script.data);
+#endif
+
+        vm = njs_vm_create(mcp, &shared, &externals);
+        if (vm == NULL) {
+            return NXT_ERROR;
+        }
+
+        start = js_test[i].script.data;
+
+        ret = njs_vm_compile(vm, &start, start + js_test[i].script.len);
+
+        if (ret == NXT_OK) {
+            nvm = njs_vm_clone(vm, NULL, &ext_object);
+            if (nvm == NULL) {
+                return NXT_ERROR;
+            }
+
+            if (njs_vm_run(nvm) == NXT_OK) {
+                if (njs_vm_retval(nvm, &s) != NXT_OK) {
+                    return NXT_ERROR;
+                }
+
+            } else {
+                njs_vm_exception(nvm, &s);
+            }
+
+        } else {
+            njs_vm_exception(vm, &s);
+        }
+
+        ok = nxt_strstr_eq(&js_test[i].ret, &s);
+
+        if (!ok) {
+            printf("jscript(\"%.*s\") failed: \"%.*s\" vs \"%.*s\"\n",
+                   (int) js_test[i].script.len, js_test[i].script.data,
+                   (int) js_test[i].ret.len, js_test[i].ret.data,
+                   (int) s.len, s.data);
+
+            return NXT_ERROR;
+        }
+    }
+
+    nxt_mem_cache_pool_destroy(mcp);
+
+    printf("jscript unit tests passed\n");
+
+    return NXT_OK;
+}
+
+
+int nxt_cdecl
+main(int argc, char **argv)
+{
+    return nxt_jscript_unit_test();
+}
diff --git a/nxt/Makefile b/nxt/Makefile
new file mode 100644 (file)
index 0000000..19ae248
--- /dev/null
@@ -0,0 +1,118 @@
+
+NXT_LIB =      nxt
+
+
+$(NXT_BUILDDIR)/libnxt.a: \
+       $(NXT_BUILDDIR)/nxt_djb_hash.o \
+       $(NXT_BUILDDIR)/nxt_utf8.o \
+       $(NXT_BUILDDIR)/nxt_array.o \
+       $(NXT_BUILDDIR)/nxt_queue.o \
+       $(NXT_BUILDDIR)/nxt_rbtree.o \
+       $(NXT_BUILDDIR)/nxt_lvlhsh.o \
+       $(NXT_BUILDDIR)/nxt_malloc.o \
+       $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \
+
+       ar -r -c $(NXT_BUILDDIR)/libnxt.a \
+               $(NXT_BUILDDIR)/nxt_djb_hash.o \
+               $(NXT_BUILDDIR)/nxt_utf8.o \
+               $(NXT_BUILDDIR)/nxt_array.o \
+               $(NXT_BUILDDIR)/nxt_rbtree.o \
+               $(NXT_BUILDDIR)/nxt_lvlhsh.o \
+               $(NXT_BUILDDIR)/nxt_malloc.o \
+               $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \
+
+$(NXT_BUILDDIR)/nxt_murmur_hash.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_murmur_hash.h \
+       $(NXT_LIB)/nxt_murmur_hash.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_murmur_hash.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_murmur_hash.c
+
+$(NXT_BUILDDIR)/nxt_djb_hash.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_djb_hash.h \
+       $(NXT_LIB)/nxt_djb_hash.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_djb_hash.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_djb_hash.c
+
+$(NXT_BUILDDIR)/nxt_utf8.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_utf8.h \
+       $(NXT_LIB)/nxt_utf8.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_utf8.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_utf8.c
+
+$(NXT_BUILDDIR)/nxt_array.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_array.h \
+       $(NXT_LIB)/nxt_array.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_array.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_array.c
+
+$(NXT_BUILDDIR)/nxt_queue.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_queue.h \
+       $(NXT_LIB)/nxt_queue.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_queue.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_queue.c
+
+$(NXT_BUILDDIR)/nxt_rbtree.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_rbtree.h \
+       $(NXT_LIB)/nxt_rbtree.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_rbtree.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_rbtree.c
+
+$(NXT_BUILDDIR)/nxt_lvlhsh.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_lvlhsh.h \
+       $(NXT_LIB)/nxt_lvlhsh.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_lvlhsh.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_lvlhsh.c
+
+$(NXT_BUILDDIR)/nxt_malloc.o: \
+       $(NXT_LIB)/nxt_auto_config.h \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_malloc.h \
+       $(NXT_LIB)/nxt_malloc.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_malloc.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_malloc.c
+
+$(NXT_BUILDDIR)/nxt_mem_cache_pool.o: \
+       $(NXT_LIB)/nxt_types.h \
+       $(NXT_LIB)/nxt_clang.h \
+       $(NXT_LIB)/nxt_alignment.h \
+       $(NXT_LIB)/nxt_queue.h \
+       $(NXT_LIB)/nxt_rbtree.h \
+       $(NXT_LIB)/nxt_mem_cache_pool.h \
+       $(NXT_LIB)/nxt_mem_cache_pool.c \
+
+       $(NXT_CC) -c -o $(NXT_BUILDDIR)/nxt_mem_cache_pool.o $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/nxt_mem_cache_pool.c
+
+include $(NXT_LIB)/test/Makefile
diff --git a/nxt/auto/clang b/nxt/auto/clang
new file mode 100644 (file)
index 0000000..6ca47c1
--- /dev/null
@@ -0,0 +1,164 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+$nxt_echo checking for C compiler: $CC
+cat << END >> $NXT_AUTOCONF_ERR
+----------------------------------------
+checking for C compiler: $CC
+END
+
+
+# Allow error exit status.
+set +e
+
+if [ -z `which $CC` ]; then
+    $nxt_echo
+    $nxt_echo $0: error: $CC not found.
+    $nxt_echo
+    exit 1;
+fi
+
+
+if `/bin/sh -c "($CC -v)" 2>&1 | grep "gcc version" >> $NXT_AUTOCONF_ERR 2>&1`
+then
+    NXT_CC_NAME=gcc
+    $nxt_echo " + using GNU C compiler"
+    NXT_CC_VERSION=`/bin/sh -c "($CC -v)" 2>&1 | grep "gcc version" 2>&1`
+    $nxt_echo " + $NXT_CC_VERSION"
+
+else
+if `/bin/sh -c "($CC -v)" 2>&1 | grep "clang version" >> $NXT_AUTOCONF_ERR 2>&1`
+then
+    NXT_CC_NAME=clang
+    $nxt_echo " + using Clang C compiler"
+    NXT_CC_VERSION=`/bin/sh -c "($CC -v)" 2>&1 | grep "clang version" 2>&1`
+    $nxt_echo " + $NXT_CC_VERSION"
+
+else
+if `/bin/sh -c "($CC -v)" 2>&1 \
+                 | grep "Apple LLVM version" >> $NXT_AUTOCONF_ERR 2>&1`
+then
+    NXT_CC_NAME=clang
+    $nxt_echo " + using Clang C compiler"
+    NXT_CC_VERSION=`/bin/sh -c "($CC -v)" 2>&1 | grep "Apple LLVM version" 2>&1`
+    $nxt_echo " + $NXT_CC_VERSION"
+
+else
+if `/bin/sh -c "($CC -V)" 2>&1 | grep "Sun C" >> $NXT_AUTOCONF_ERR 2>&1`
+then
+    NXT_CC_NAME=SunC
+    $nxt_echo " + using Sun C compiler"
+    NXT_CC_VERSION=`/bin/sh -c "($CC -V)" 2>&1 | grep "Sun C" 2>&1`
+    $nxt_echo " + $NXT_CC_VERSION"
+
+fi # SunC
+fi # Apple LLVM clang
+fi # clang
+fi # gcc
+
+
+case $NXT_CC_NAME in
+
+    gcc)
+        nxt_define=NXT_GCC . ${NXT_AUTO}define
+
+        NXT_CFLAGS="$NXT_CFLAGS -pipe"
+        NXT_CFLAGS="$NXT_CFLAGS -fPIC"
+
+        # Do not export symbols except explicitly marked with NXT_EXPORT.
+        NXT_CFLAGS="$NXT_CFLAGS -fvisibility=hidden"
+
+        # c99/gnu99 conflict with Solaris XOPEN.
+        #NXT_CFLAGS="$NXT_CFLAGS -std=gnu99"
+
+        NXT_CFLAGS="$NXT_CFLAGS -O"
+        #NXT_CFLAGS="$NXT_CFLAGS -O0"
+        NXT_CFLAGS="$NXT_CFLAGS -W -Wall -Wextra"
+
+        #NXT_CFLAGS="$NXT_CFLAGS -Wunused-result"
+        NXT_CFLAGS="$NXT_CFLAGS -Wno-unused-parameter"
+        #NXT_CFLAGS="$NXT_CFLAGS -Wshorten-64-to-32"
+        NXT_CFLAGS="$NXT_CFLAGS -Wwrite-strings"
+
+        # -O2 enables -fstrict-aliasing and -fstrict-overflow.
+        #NXT_CFLAGS="$NXT_CFLAGS -O2"
+        #NXT_CFLAGS="$NXT_CFLAGS -Wno-strict-aliasing"
+
+        #NXT_CFLAGS="$NXT_CFLAGS -fomit-frame-pointer"
+        #NXT_CFLAGS="$NXT_CFLAGS -momit-leaf-frame-pointer"
+
+        # -Wstrict-overflow is supported by GCC 4.2+.
+        #NXT_CFLAGS="$NXT_CFLAGS -Wstrict-overflow=5"
+
+        NXT_CFLAGS="$NXT_CFLAGS -Wmissing-prototypes"
+
+        # Stop on warning.
+        NXT_CFLAGS="$NXT_CFLAGS -Werror"
+
+        # Debug.
+        NXT_CFLAGS="$NXT_CFLAGS -g"
+    ;;
+
+    clang)
+        nxt_define=NXT_CLANG . ${NXT_AUTO}define
+
+        NXT_CFLAGS="$NXT_CFLAGS -pipe"
+        NXT_CFLAGS="$NXT_CFLAGS -fPIC"
+
+        # Do not export symbols except explicitly marked with NXT_EXPORT.
+        NXT_CFLAGS="$NXT_CFLAGS -fvisibility=hidden"
+
+        NXT_CFLAGS="$NXT_CFLAGS -O"
+        #NXT_CFLAGS="$NXT_CFLAGS -O0"
+        NXT_CFLAGS="$NXT_CFLAGS -W -Wall -Wextra"
+
+        #NXT_CFLAGS="$NXT_CFLAGS -Wunused-result"
+        NXT_CFLAGS="$NXT_CFLAGS -Wno-unused-parameter"
+        #NXT_CFLAGS="$NXT_CFLAGS -Wshorten-64-to-32"
+        NXT_CFLAGS="$NXT_CFLAGS -Wwrite-strings"
+        #NXT_CFLAGS="$NXT_CFLAGS -O2"
+        #NXT_CFLAGS="$NXT_CFLAGS -fomit-frame-pointer"
+        NXT_CFLAGS="$NXT_CFLAGS -fstrict-aliasing"
+        NXT_CFLAGS="$NXT_CFLAGS -Wstrict-overflow=5"
+
+        NXT_CFLAGS="$NXT_CFLAGS -Wmissing-prototypes"
+
+        # Stop on warning.
+        NXT_CFLAGS="$NXT_CFLAGS -Werror"
+
+        # Debug.
+
+        if [ "$NXT_SYSTEM_PLATFORM" != "powerpc" ]; then
+            # "-g" flag causes the "unknown pseudo-op: `.cfi_sections'"
+            # error on PowerPC Clang.
+            NXT_CFLAGS="$NXT_CFLAGS -g"
+        fi
+    ;;
+
+    SunC)
+        nxt_define=NXT_SUNC . ${NXT_AUTO}define
+
+        NXT_CFLAGS="$NXT_CFLAGS -fPIC"
+        # Optimization.
+        NXT_CFLAGS="$NXT_CFLAGS -O -fast"
+        # Stop on warning.
+        NXT_CFLAGS="$NXT_CFLAGS -errwarn=%all"
+        # Debug.
+        NXT_CFLAGS="$NXT_CFLAGS -g"
+    ;;
+
+    *)
+    ;;
+
+esac
+
+# Stop on error exit status again.
+set -e
+
+cat << END >> $NXT_MAKEFILE_CONF
+
+NXT_CC =       ${CC}
+NXT_CFLAGS =   ${CFLAGS} ${NXT_CFLAGS}
+END
diff --git a/nxt/auto/configure b/nxt/auto/configure
new file mode 100755 (executable)
index 0000000..305d0d5
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+# Disable localized program messages.
+LANG=C
+export LANG
+
+# Stop on error exit status.
+set -e
+# Stop on uninitialized variable.
+set -u
+
+
+# Initialize variables with null values if they are not defined.
+CFLAGS=${CFLAGS=}
+NXT_TEST_CFLAGS=${NXT_TEST_CFLAGS=}
+NXT_TEST_LIBS=${NXT_TEST_LIBS=}
+
+
+# Initialize variables with default if they are not defined.
+CC=${CC:-cc}
+NXT_CFLAGS=${NXT_CFLAGS=}
+NXT_CC_OPT=${NXT_CC_OPT:--O}
+NXT_LD_OPT=${NXT_CC_OPT:--O}
+NXT_AUTO=${NXT_AUTO:-auto/}
+NXT_AUTO_CONFIG_H=nxt_auto_config.h
+NXT_MAKEFILE_CONF=Makefile.conf
+
+NXT_BUILDDIR=${NXT_BUILDDIR:-build}
+NXT_AUTOTEST=$NXT_BUILDDIR/autotest
+NXT_AUTOCONF_ERR=$NXT_BUILDDIR/autoconf.err
+
+test -d $NXT_BUILDDIR || mkdir $NXT_BUILDDIR
+
+> $NXT_AUTOCONF_ERR
+
+cat << END > $NXT_AUTO_CONFIG_H
+
+/* This file is auto-generated by configure */
+
+END
+
+cat << END > $NXT_MAKEFILE_CONF
+
+# This file is auto-generated by configure
+END
+
+
+. ${NXT_AUTO}os
+. ${NXT_AUTO}clang
+. ${NXT_AUTO}memalign
+. ${NXT_AUTO}pcre
diff --git a/nxt/auto/define b/nxt/auto/define
new file mode 100644 (file)
index 0000000..7c318d6
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+cat << END >> $NXT_AUTO_CONFIG_H
+
+#ifndef $nxt_define
+#define $nxt_define  1
+#endif
+
+END
diff --git a/nxt/auto/echo b/nxt/auto/echo
new file mode 100755 (executable)
index 0000000..a66ee6e
--- /dev/null
@@ -0,0 +1,12 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+if [ "x$1" = x-n ]; then
+   shift
+   echo "$*\c"
+
+else
+   echo "$*"
+fi
diff --git a/nxt/auto/feature b/nxt/auto/feature
new file mode 100644 (file)
index 0000000..73886f3
--- /dev/null
@@ -0,0 +1,112 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+$nxt_echo -n "checking for $nxt_feature ..."
+
+cat << END >> $NXT_AUTOCONF_ERR
+----------------------------------------
+checking for $nxt_feature
+END
+
+
+nxt_found=no
+nxt_feature_value=
+nxt_feature_inc_path=
+
+if test -n "$nxt_feature_incs"; then
+    case "$nxt_feature_incs" in
+        -*)
+            nxt_feature_inc_path="$nxt_feature_incs"
+        ;;
+
+        *)
+            for nxt_temp in $nxt_feature_incs; do
+                nxt_feature_inc_path="$nxt_feature_inc_path -I $nxt_temp"
+            done
+        ;;
+    esac
+fi
+
+
+cat << END > $NXT_AUTOTEST.c
+$nxt_feature_test
+END
+
+
+nxt_test="$CC $CFLAGS $NXT_CFLAGS $NXT_CC_OPT $NXT_TEST_CFLAGS \
+          $nxt_feature_inc_path -o $NXT_AUTOTEST $NXT_AUTOTEST.c \
+          $NXT_LD_OPT $NXT_TEST_LIBS $nxt_feature_libs"
+
+# /bin/sh -c "(...)" is to intercept "Killed", "Abort trap",
+# "Segmentation fault", or other shell messages.
+# "|| true" is to bypass "set -e" setting.
+
+/bin/sh -c "($nxt_test || true)" >> $NXT_AUTOCONF_ERR 2>&1
+
+
+if [ -x $NXT_AUTOTEST ]; then
+
+    case "$nxt_feature_run" in
+
+        value)
+            if /bin/sh -c "($NXT_AUTOTEST)" >> $NXT_AUTOCONF_ERR 2>&1; then
+                $nxt_echo >> $NXT_AUTOCONF_ERR
+                nxt_found=yes
+                nxt_feature_value=`$NXT_AUTOTEST`
+                $nxt_echo " $nxt_feature_value"
+                if [ -n "$nxt_feature_name" ]; then
+                    cat << END >> $NXT_AUTO_CONFIG_H
+
+#ifndef $nxt_feature_name
+#define $nxt_feature_name  $nxt_feature_value
+#endif
+
+END
+                fi
+            else
+                $nxt_echo " not found"
+            fi
+        ;;
+
+        yes)
+            if /bin/sh -c "($NXT_AUTOTEST)" >> $NXT_AUTOCONF_ERR 2>&1; then
+                $nxt_echo " found"
+                nxt_found=yes
+                cat << END >> $NXT_AUTO_CONFIG_H
+
+#ifndef $nxt_feature_name
+#define $nxt_feature_name  1
+#endif
+
+END
+            else
+                $nxt_echo " found but is not working"
+            fi
+        ;;
+
+        *)
+            $nxt_echo " found"
+            nxt_found=yes
+            cat << END >> $NXT_AUTO_CONFIG_H
+
+#ifndef $nxt_feature_name
+#define $nxt_feature_name  1
+#endif
+
+END
+        ;;
+    esac
+
+else
+    $nxt_echo " not found"
+
+    $nxt_echo "----------"    >> $NXT_AUTOCONF_ERR
+    cat $NXT_AUTOTEST.c       >> $NXT_AUTOCONF_ERR
+    $nxt_echo "----------"    >> $NXT_AUTOCONF_ERR
+    $nxt_echo $nxt_test       >> $NXT_AUTOCONF_ERR
+    $nxt_echo "----------"    >> $NXT_AUTOCONF_ERR
+fi
+
+rm -rf $NXT_AUTOTEST*
diff --git a/nxt/auto/memalign b/nxt/auto/memalign
new file mode 100644 (file)
index 0000000..82d34d3
--- /dev/null
@@ -0,0 +1,43 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+# Linux glibc 2.1.91, FreeBSD 7.0, Solaris 11,
+# MacOSX 10.6 (Snow Leopard), NetBSD 5.0.
+
+nxt_feature="posix_memalign()"
+nxt_feature_name=NXT_HAVE_POSIX_MEMALIGN
+nxt_feature_run=yes
+nxt_feature_incs=
+nxt_feature_libs=
+nxt_feature_test="#include <stdlib.h>
+
+                 int main() {
+                     void  *p;
+
+                     if (posix_memalign(&p, 4096, 4096) != 0)
+                         return 1;
+                     return 0;
+                 }"
+. ${NXT_AUTO}feature
+
+
+if [ $nxt_found = no ]; then
+
+    # Solaris, HP-UX.
+
+    nxt_feature="memalign()"
+    nxt_feature_name=NXT_HAVE_MEMALIGN
+    nxt_feature_run=yes
+    nxt_feature_incs=
+    nxt_feature_libs=
+    nxt_feature_test="#include <stdlib.h>
+
+                     int main() {
+                         if (memalign(4096, 4096) == NULL)
+                             return 1;
+                         return 0;
+                     }"
+    . ${NXT_AUTO}feature
+fi
diff --git a/nxt/auto/os b/nxt/auto/os
new file mode 100644 (file)
index 0000000..6726f0d
--- /dev/null
@@ -0,0 +1,56 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+NXT_SYSTEM=`uname -s 2>/dev/null`
+
+
+case "$NXT_SYSTEM" in
+
+    Linux)
+        NXT_SYSTEM_VERSION=`uname -r 2>/dev/null`
+        # Linux uname -p can return "unknown".
+        NXT_SYSTEM_PLATFORM=`uname -m 2>/dev/null`
+        nxt_echo=echo
+        CC=${CC:-cc}
+
+        # NAN and INFINITY require _GNU_SOURCE on old Linux.
+        NXT_CFLAGS="$NXT_CFLAGS -D_GNU_SOURCE"
+        ;;
+
+    FreeBSD | NetBSD | OpenBSD)
+        NXT_SYSTEM_VERSION=`uname -r 2>/dev/null`
+        NXT_SYSTEM_PLATFORM=`uname -m 2>/dev/null`
+        nxt_echo=echo
+        CC=${CC:-cc}
+        ;;
+
+    SunOS)
+        nxt_define=NXT_SOLARIS . ${NXT_AUTO}define
+        NXT_SYSTEM_VERSION=`uname -r 2>/dev/null`
+        NXT_SYSTEM_PLATFORM=`uname -p 2>/dev/null`
+        # Solaris /bin/sh and /bin/echo do not support "-n" option.
+        nxt_echo=auto/echo
+        CC=${CC:-gcc}
+        ;;
+
+    Darwin)
+        NXT_SYSTEM_VERSION=`uname -r 2>/dev/null`
+        NXT_SYSTEM_PLATFORM=`uname -m 2>/dev/null`
+        # MacOSX /bin/sh is bash and its embedded "echo" command
+        # does not support "-n" option.
+        nxt_echo=/bin/echo
+        CC=${CC:-cc}
+        ;;
+
+    *)
+        NXT_SYSTEM_VERSION=`uname -r 2>/dev/null`
+        NXT_SYSTEM_PLATFORM=`uname -p 2>/dev/null`
+        nxt_echo=echo
+        CC=${CC:-gcc}
+        ;;
+
+esac
+
+$nxt_echo configuring for $NXT_SYSTEM $NXT_SYSTEM_VERSION $NXT_SYSTEM_PLATFORM
diff --git a/nxt/auto/pcre b/nxt/auto/pcre
new file mode 100644 (file)
index 0000000..ac9a6cc
--- /dev/null
@@ -0,0 +1,47 @@
+
+# Copyright (C) Igor Sysoev
+# Copyright (C) NGINX, Inc.
+
+
+NXT_PCRE_CFLAGS=
+NXT_PCRE_LIB=
+
+nxt_found=no
+
+if /bin/sh -c "(pcre-config --version)" >> $NXT_AUTOCONF_ERR 2>&1; then
+
+    NXT_PCRE_CFLAGS=`pcre-config --cflags`
+    NXT_PCRE_LIB=`pcre-config --libs`
+
+    nxt_feature="PCRE library"
+    nxt_feature_name=NXT_HAVE_PCRE
+    nxt_feature_run=no
+    nxt_feature_incs=$NXT_PCRE_CFLAGS
+    nxt_feature_libs=$NXT_PCRE_LIB
+    nxt_feature_test="#include <pcre.h>
+
+                     int main() {
+                         pcre  *re;
+
+                         re = pcre_compile(NULL, 0, NULL, 0, NULL);
+                         if (re == NULL)
+                             return 1;
+                         return 0;
+                     }"
+    . ${NXT_AUTO}feature
+fi
+
+if [ $nxt_found = no ]; then
+    $nxt_echo
+    $nxt_echo $0: error: no PCRE library found.
+    $nxt_echo
+    exit 1;
+fi
+
+$nxt_echo " + PCRE version: `pcre-config --version`"
+
+cat << END >> $NXT_MAKEFILE_CONF
+
+NXT_PCRE_CFLAGS = ${NXT_PCRE_CFLAGS}
+NXT_PCRE_LIB = ${NXT_PCRE_LIB}
+END
diff --git a/nxt/nxt_alignment.h b/nxt/nxt_alignment.h
new file mode 100644 (file)
index 0000000..547c430
--- /dev/null
@@ -0,0 +1,49 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_ALIGNMENT_H_INCLUDED_
+#define _NXT_ALIGNMENT_H_INCLUDED_
+
+
+#ifndef NXT_MAX_ALIGNMENT
+
+#if (NXT_SOLARIS)
+/* x86_64: 16, i386: 4, sparcv9: 16, sparcv8: 8. */
+#define NXT_MAX_ALIGNMENT  _MAX_ALIGNMENT
+
+#elif (NXT_WINDOWS)
+/* Win64: 16, Win32: 8. */
+#define NXT_MAX_ALIGNMENT  MEMORY_ALLOCATION_ALIGNMENT
+
+#elif (__amd64__)
+#define NXT_MAX_ALIGNMENT  16
+
+#elif (__i386__ || __i386)
+#define NXT_MAX_ALIGNMENT  4
+
+#elif (__arm__)
+#define NXT_MAX_ALIGNMENT  16
+
+#else
+#define NXT_MAX_ALIGNMENT  16
+#endif
+
+#endif
+
+
+#define nxt_align_size(size, a)                                               \
+    (((size) + ((size_t) (a) - 1)) & ~((size_t) (a) - 1))
+
+
+#define nxt_align_ptr(p, a)                                                   \
+    (u_char *) (((uintptr_t) (p) + ((uintptr_t) (a) - 1))                     \
+                 & ~((uintptr_t) (a) - 1))
+
+#define nxt_trunc_ptr(p, a)                                                   \
+    (u_char *) ((uintptr_t) (p) & ~((uintptr_t) (a) - 1))
+
+
+#endif /* _NXT_ALIGNMENT_H_INCLUDED_ */
diff --git a/nxt/nxt_array.c b/nxt/nxt_array.c
new file mode 100644 (file)
index 0000000..7d19acc
--- /dev/null
@@ -0,0 +1,160 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_array.h>
+#include <string.h>
+
+
+nxt_vector_t *
+nxt_vector_create(nxt_uint_t items, size_t item_size,
+    const nxt_mem_proto_t *proto, void *pool)
+{
+    nxt_vector_t  *vector;
+
+    vector = proto->alloc(pool, sizeof(nxt_vector_t) + items * item_size);
+
+    if (nxt_fast_path(vector != NULL)) {
+        vector->start = (char *) vector + sizeof(nxt_vector_t);
+        vector->items = 0;
+        vector->item_size = item_size;
+        vector->avalaible = items;
+        vector->type = NXT_VECTOR_EMBEDDED;
+    }
+
+    return vector;
+}
+
+
+void *
+nxt_vector_init(nxt_vector_t *vector, nxt_uint_t items, size_t item_size,
+    const nxt_mem_proto_t *proto, void *pool)
+{
+    vector->start = proto->alloc(pool, items * item_size);
+
+    if (nxt_fast_path(vector->start != NULL)) {
+        vector->items = 0;
+        vector->item_size = item_size;
+        vector->avalaible = items;
+        vector->type = NXT_VECTOR_INITED;
+    }
+
+    return vector->start;
+}
+
+
+void
+nxt_vector_destroy(nxt_vector_t *vector, const nxt_mem_proto_t *proto,
+    void *pool)
+{
+    switch (vector->type) {
+
+    case NXT_VECTOR_INITED:
+        proto->free(pool, vector->start);
+#if (NXT_DEBUG)
+        vector->start = NULL;
+        vector->items = 0;
+        vector->avalaible = 0;
+#endif
+        break;
+
+    case NXT_VECTOR_DESCRETE:
+        proto->free(pool, vector->start);
+
+        /* Fall through. */
+
+    case NXT_VECTOR_EMBEDDED:
+        proto->free(pool, vector);
+        break;
+    }
+}
+
+
+void *
+nxt_vector_add(nxt_vector_t *vector, const nxt_mem_proto_t *proto, void *pool)
+{
+    void      *item, *start, *old;
+    size_t    size;
+    uint32_t  n;
+
+    n = vector->avalaible;
+
+    if (n == vector->items) {
+
+        if (n < 16) {
+            /* Allocate new vector twice as much as current. */
+            n *= 2;
+
+        } else {
+            /* Allocate new vector half as much as current. */
+            n += n / 2;
+        }
+
+        size = n * vector->item_size;
+
+        start = proto->alloc(pool, size);
+        if (nxt_slow_path(start == NULL)) {
+            return NULL;
+        }
+
+        vector->avalaible = n;
+        old = vector->start;
+        vector->start = start;
+
+        memcpy(start, old, size);
+
+        if (vector->type == NXT_VECTOR_EMBEDDED) {
+            vector->type = NXT_VECTOR_DESCRETE;
+
+        } else {
+            proto->free(pool, old);
+        }
+    }
+
+    item = (char *) vector->start + vector->item_size * vector->items;
+
+    vector->items++;
+
+    return item;
+}
+
+
+void *
+nxt_vector_zero_add(nxt_vector_t *vector, const nxt_mem_proto_t *proto,
+    void *pool)
+{
+    void  *item;
+
+    item = nxt_vector_add(vector, proto, pool);
+
+    if (nxt_fast_path(item != NULL)) {
+        memset(item, 0, vector->item_size);
+    }
+
+    return item;
+}
+
+
+void
+nxt_vector_remove(nxt_vector_t *vector, void *item)
+{
+    u_char    *next, *last, *end;
+    uint32_t  item_size;
+
+    item_size = vector->item_size;
+    end = (u_char *) vector->start + item_size * vector->items;
+    last = end - item_size;
+
+    if (item != last) {
+        next = (u_char *) item + item_size;
+
+        memmove(item, next, end - next);
+    }
+
+    vector->items--;
+}
diff --git a/nxt/nxt_array.h b/nxt/nxt_array.h
new file mode 100644 (file)
index 0000000..f12ce46
--- /dev/null
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_ARRAY_H_INCLUDED_
+#define _NXT_ARRAY_H_INCLUDED_
+
+
+typedef enum {
+    NXT_VECTOR_INITED = 0,
+    NXT_VECTOR_DESCRETE,
+    NXT_VECTOR_EMBEDDED,
+} nxt_vector_type_t;
+
+
+typedef struct {
+    void              *start;
+    /*
+     * A vector can hold no more than 65536 items.
+     * The item size is no more than 64K.
+     */
+    uint16_t          items;
+    uint16_t          avalaible;
+    uint16_t          item_size;
+    nxt_vector_type_t  type:8;
+} nxt_vector_t;
+
+
+NXT_EXPORT nxt_vector_t *nxt_vector_create(nxt_uint_t items, size_t item_size,
+    const nxt_mem_proto_t *proto, void *pool);
+NXT_EXPORT void *nxt_vector_init(nxt_vector_t *vector, nxt_uint_t items,
+    size_t item_size, const nxt_mem_proto_t *proto, void *pool);
+NXT_EXPORT void nxt_vector_destroy(nxt_vector_t *vector,
+    const nxt_mem_proto_t *proto, void *pool);
+NXT_EXPORT void *nxt_vector_add(nxt_vector_t *vector,
+    const nxt_mem_proto_t *proto, void *pool);
+NXT_EXPORT void *nxt_vector_zero_add(nxt_vector_t *vector,
+    const nxt_mem_proto_t *proto, void *pool);
+NXT_EXPORT void nxt_vector_remove(nxt_vector_t *vector, void *item);
+
+
+#define nxt_vector_last(vector)                                               \
+    ((void *)                                                                 \
+        ((char *) (vector)->start                                             \
+                      + (vector)->item_size * ((vector)->items - 1)))
+
+
+#define nxt_vector_reset(vector)                                              \
+    (vector)->items = 0;
+
+
+#define nxt_vector_is_empty(vector)                                           \
+    ((vector)->items == 0)
+
+
+nxt_inline void *
+nxt_vector_remove_last(nxt_vector_t *vector)
+{
+    vector->items--;
+    return (char *) vector->start + vector->item_size * vector->items;
+}
+
+
+#endif /* _NXT_ARRAY_H_INCLUDED_ */
diff --git a/nxt/nxt_clang.h b/nxt/nxt_clang.h
new file mode 100644 (file)
index 0000000..d504a86
--- /dev/null
@@ -0,0 +1,87 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_CLANG_H_INCLUDED_
+#define _NXT_CLANG_H_INCLUDED_
+
+
+#include <stddef.h>       /* offsetof(). */
+#include <unistd.h>       /* NULL. */
+
+
+#define nxt_inline         static inline __attribute__((always_inline))
+#define nxt_noinline       __attribute__((noinline))
+#define nxt_cdecl
+
+
+#define nxt_container_of(p, type, field)                                      \
+    (type *) ((u_char *) (p) - offsetof(type, field))
+
+#define nxt_nitems(x)                                                         \
+    (sizeof(x) / sizeof((x)[0]))
+
+
+
+#if (NXT_HAVE_BUILTIN_EXPECT)
+#define nxt_fast_path(x)   __builtin_expect((long) (x), 1)
+#define nxt_slow_path(x)   __builtin_expect((long) (x), 0)
+
+#else
+#define nxt_fast_path(x)   (x)
+#define nxt_slow_path(x)   (x)
+#endif
+
+
+#if (NXT_HAVE_BUILTIN_UNREACHABLE)
+#define nxt_unreachable()  __builtin_unreachable()
+
+#else
+#define nxt_unreachable()
+#endif
+
+
+#if (NXT_HAVE_BUILTIN_PREFETCH)
+#define nxt_prefetch(a)    __builtin_prefetch(a)
+
+#else
+#define nxt_prefetch(a)
+#endif
+
+
+#if (NXT_HAVE_GCC_ATTRIBUTE_VISIBILITY)
+#define NXT_EXPORT         __attribute__((visibility("default")))
+
+#else
+#define NXT_EXPORT
+#endif
+
+
+#if (NXT_HAVE_GCC_ATTRIBUTE_MALLOC)
+#define NXT_MALLOC_LIKE    __attribute__((__malloc__))
+
+#else
+#define NXT_MALLOC_LIKE
+#endif
+
+
+#if (NXT_HAVE_GCC_ATTRIBUTE_ALIGNED)
+#define nxt_aligned(x)     __attribute__((aligned(x)))
+
+#else
+#define nxt_aligned(x)
+#endif
+
+
+#if (NXT_CLANG)
+/* Any __asm__ directive disables loop vectorization in GCC and Clang. */
+#define nxt_pragma_loop_disable_vectorization  __asm__("")
+
+#else
+#define nxt_pragma_loop_disable_vectorization
+#endif
+
+
+#endif /* _NXT_CLANG_H_INCLUDED_ */
diff --git a/nxt/nxt_djb_hash.c b/nxt/nxt_djb_hash.c
new file mode 100644 (file)
index 0000000..0541094
--- /dev/null
@@ -0,0 +1,48 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_djb_hash.h>
+
+
+uint32_t
+nxt_djb_hash(const void *data, size_t len)
+{
+    uint32_t      hash;
+    const u_char  *p;
+
+    p = data;
+    hash = NXT_DJB_HASH_INIT;
+
+    while (len != 0) {
+        hash = nxt_djb_hash_add(hash, *p++);
+        len--;
+    }
+
+    return hash;
+}
+
+
+uint32_t
+nxt_djb_hash_lowcase(const void *data, size_t len)
+{
+    u_char        c;
+    uint32_t      hash;
+    const u_char  *p;
+
+    p = data;
+    hash = NXT_DJB_HASH_INIT;
+
+    while (len != 0) {
+        c = *p++;
+        hash = nxt_djb_hash_add(hash, nxt_lowcase(c));
+        len--;
+    }
+
+    return hash;
+}
diff --git a/nxt/nxt_djb_hash.h b/nxt/nxt_djb_hash.h
new file mode 100644 (file)
index 0000000..712f8a4
--- /dev/null
@@ -0,0 +1,25 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_DJB_HASH_H_INCLUDED_
+#define _NXT_DJB_HASH_H_INCLUDED_
+
+
+/* A fast and simple hash function by Daniel J. Bernstein. */
+
+
+NXT_EXPORT uint32_t nxt_djb_hash(const void *data, size_t len);
+NXT_EXPORT uint32_t nxt_djb_hash_lowcase(const void *data, size_t len);
+
+
+#define NXT_DJB_HASH_INIT  5381
+
+
+#define nxt_djb_hash_add(hash, val)                                            \
+    ((uint32_t) ((((hash) << 5) + (hash)) ^ (uint32_t) (val)))
+
+
+#endif /* _NXT_DJB_HASH_H_INCLUDED_ */
diff --git a/nxt/nxt_lvlhsh.c b/nxt/nxt_lvlhsh.c
new file mode 100644 (file)
index 0000000..2842b92
--- /dev/null
@@ -0,0 +1,857 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_lvlhsh.h>
+#include <string.h>
+
+
+/*
+ * The level hash consists of hierarchical levels of arrays of pointers.
+ * The pointers may point to another level, a bucket, or NULL.
+ * The levels and buckets must be allocated in manner alike posix_memalign()
+ * to bookkeep additional information in pointer low bits.
+ *
+ * A level is an array of pointers.  Its size is a power of 2.  Levels
+ * may be different sizes, but on the same level the sizes are the same.
+ * Level sizes are specified by number of bits per level in lvlhsh->shift
+ * array.  A hash may have up to 7 levels.  There are two predefined
+ * shift arrays given by the first two shift array values:
+ *
+ * 1) [0, 0]:  [4, 4, 4, 4, 4, 4, 4] on a 64-bit platform or
+ *             [5, 5, 5, 5, 5, 5, 0] on a 32-bit platform,
+ *    so default size of levels is 128 bytes.
+ *
+ * 2) [0, 10]: [10, 4, 4, 4, 4, 4, 0] on a 64-bit platform or
+ *             [10, 5, 5, 5, 5, 0, 0] on a 32-bit platform,
+ *    so default size of levels is 128 bytes on all levels except
+ *    the first level.  The first level is 8K or 4K on 64-bit or 32-bit
+ *    platforms respectively.
+ *
+ * All buckets in a hash are the same size which is a power of 2.
+ * A bucket contains several entries stored and tested sequentially.
+ * The bucket size should be one or two CPU cache line size, a minimum
+ * allowed size is 32 bytes.  A default 128-byte bucket contains 10 64-bit
+ * entries or 15 32-bit entries.  Each entry consists of pointer to value
+ * data and 32-bit key.  If an entry value pointer is NULL, the entry is free.
+ * On a 64-bit platform entry value pointers are no aligned, therefore they
+ * are accessed as two 32-bit integers.  The rest trailing space in a bucket
+ * is used as pointer to next bucket and this pointer is always aligned.
+ * Although the level hash allows to store a lot of values in a bucket chain,
+ * this is non optimal way.  The large data set should be stored using
+ * several levels.
+ */
+
+#define nxt_lvlhsh_is_bucket(p)                                                \
+    ((uintptr_t) (p) & 1)
+
+
+#define nxt_lvlhsh_count_inc(n)                                                \
+    n = (void *) ((uintptr_t) (n) + 2)
+
+
+#define nxt_lvlhsh_count_dec(n)                                                \
+    n = (void *) ((uintptr_t) (n) - 2)
+
+
+#define nxt_lvlhsh_level_size(proto, nlvl)                                     \
+    ((uintptr_t) 1 << proto->shift[nlvl])
+
+
+#define nxt_lvlhsh_level(lvl, mask)                                            \
+    (void **) ((uintptr_t) lvl & (~mask << 2))
+
+
+#define nxt_lvlhsh_level_entries(lvl, mask)                                    \
+    ((uintptr_t) lvl & (mask << 1))
+
+
+#define nxt_lvlhsh_store_bucket(slot, bkt)                                     \
+    slot = (void **) ((uintptr_t) bkt | 2 | 1)
+
+
+#define nxt_lvlhsh_bucket_size(proto)                                          \
+    proto->bucket_size
+
+
+#define nxt_lvlhsh_bucket(proto, bkt)                                          \
+    (uint32_t *) ((uintptr_t) bkt & ~(uintptr_t) proto->bucket_mask)
+
+
+#define nxt_lvlhsh_bucket_entries(proto, bkt)                                  \
+    (((uintptr_t) bkt & (uintptr_t) proto->bucket_mask) >> 1)
+
+
+#define nxt_lvlhsh_bucket_end(proto, bkt)                                      \
+    &bkt[proto->bucket_end]
+
+
+#define nxt_lvlhsh_free_entry(e)                                               \
+    (!(nxt_lvlhsh_valid_entry(e)))
+
+
+#define nxt_lvlhsh_next_bucket(proto, bkt)                                     \
+    ((void **) &bkt[proto->bucket_end])
+
+#if (NXT_64BIT)
+
+#define nxt_lvlhsh_valid_entry(e)                                              \
+    (((e)[0] | (e)[1]) != 0)
+
+
+#define nxt_lvlhsh_entry_value(e)                                              \
+    (void *) (((uintptr_t) (e)[1] << 32) + (e)[0])
+
+
+#define nxt_lvlhsh_set_entry_value(e, n)                                       \
+    (e)[0] = (uint32_t)  (uintptr_t) n;                                       \
+    (e)[1] = (uint32_t) ((uintptr_t) n >> 32)
+
+
+#define nxt_lvlhsh_entry_key(e)                                                \
+    (e)[2]
+
+
+#define nxt_lvlhsh_set_entry_key(e, n)                                         \
+    (e)[2] = n
+
+#else
+
+#define nxt_lvlhsh_valid_entry(e)                                              \
+    ((e)[0] != 0)
+
+
+#define nxt_lvlhsh_entry_value(e)                                              \
+    (void *) (e)[0]
+
+
+#define nxt_lvlhsh_set_entry_value(e, n)                                       \
+    (e)[0] = (uint32_t) n
+
+
+#define nxt_lvlhsh_entry_key(e)                                                \
+    (e)[1]
+
+
+#define nxt_lvlhsh_set_entry_key(e, n)                                         \
+    (e)[1] = n
+
+#endif
+
+
+#define NXT_LVLHSH_BUCKET_DONE  ((void *) -1)
+
+
+static nxt_int_t nxt_lvlhsh_level_find(nxt_lvlhsh_query_t *lhq, void **lvl,
+    uint32_t key, nxt_uint_t nlvl);
+static nxt_int_t nxt_lvlhsh_bucket_find(nxt_lvlhsh_query_t *lhq, void **bkt);
+static nxt_int_t nxt_lvlhsh_new_bucket(nxt_lvlhsh_query_t *lhq, void **slot);
+static nxt_int_t nxt_lvlhsh_level_insert(nxt_lvlhsh_query_t *lhq,
+    void **slot, uint32_t key, nxt_uint_t nlvl);
+static nxt_int_t nxt_lvlhsh_bucket_insert(nxt_lvlhsh_query_t *lhq,
+    void **slot, uint32_t key, nxt_int_t nlvl);
+static nxt_int_t nxt_lvlhsh_convert_bucket_to_level(nxt_lvlhsh_query_t *lhq,
+    void **slot, nxt_uint_t nlvl, uint32_t *bucket);
+static nxt_int_t nxt_lvlhsh_level_convertion_insert(nxt_lvlhsh_query_t *lhq,
+    void **parent, uint32_t key, nxt_uint_t nlvl);
+static nxt_int_t nxt_lvlhsh_bucket_convertion_insert(nxt_lvlhsh_query_t *lhq,
+    void **slot, uint32_t key, nxt_int_t nlvl);
+static nxt_int_t nxt_lvlhsh_free_level(nxt_lvlhsh_query_t *lhq, void **level,
+    nxt_uint_t size);
+static nxt_int_t nxt_lvlhsh_level_delete(nxt_lvlhsh_query_t *lhq, void **slot,
+    uint32_t key, nxt_uint_t nlvl);
+static nxt_int_t nxt_lvlhsh_bucket_delete(nxt_lvlhsh_query_t *lhq, void **bkt);
+static void *nxt_lvlhsh_level_each(nxt_lvlhsh_each_t *lhe, void **level,
+    nxt_uint_t nlvl, nxt_uint_t shift);
+static void *nxt_lvlhsh_bucket_each(nxt_lvlhsh_each_t *lhe);
+
+
+nxt_int_t
+nxt_lvlhsh_find(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq)
+{
+    void  *slot;
+
+    slot = lh->slot;
+
+    if (nxt_fast_path(slot != NULL)) {
+
+        if (nxt_lvlhsh_is_bucket(slot)) {
+            return nxt_lvlhsh_bucket_find(lhq, slot);
+        }
+
+        return nxt_lvlhsh_level_find(lhq, slot, lhq->key_hash, 0);
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_level_find(nxt_lvlhsh_query_t *lhq, void **lvl, uint32_t key,
+    nxt_uint_t nlvl)
+{
+    void        **slot;
+    uintptr_t   mask;
+    nxt_uint_t  shift;
+
+    shift = lhq->proto->shift[nlvl];
+    mask = ((uintptr_t) 1 << shift) - 1;
+
+    lvl = nxt_lvlhsh_level(lvl, mask);
+    slot = lvl[key & mask];
+
+    if (slot != NULL) {
+
+        if (nxt_lvlhsh_is_bucket(slot)) {
+            return nxt_lvlhsh_bucket_find(lhq, slot);
+        }
+
+        return nxt_lvlhsh_level_find(lhq, slot, key >> shift, nlvl + 1);
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_bucket_find(nxt_lvlhsh_query_t *lhq, void **bkt)
+{
+    void        *value;
+    uint32_t    *bucket, *e;
+    nxt_uint_t  n;
+
+    do {
+        bucket = nxt_lvlhsh_bucket(lhq->proto, bkt);
+        n = nxt_lvlhsh_bucket_entries(lhq->proto, bkt);
+        e = bucket;
+
+        do {
+            if (nxt_lvlhsh_valid_entry(e)) {
+                n--;
+
+                if (nxt_lvlhsh_entry_key(e) == lhq->key_hash) {
+
+                    value = nxt_lvlhsh_entry_value(e);
+
+                    if (lhq->proto->test(lhq, value) == NXT_OK) {
+                        lhq->value = value;
+
+                        return NXT_OK;
+                    }
+                }
+            }
+
+            e += NXT_LVLHSH_ENTRY_SIZE;
+
+        } while (n != 0);
+
+        bkt = *nxt_lvlhsh_next_bucket(lhq->proto, bucket);
+
+    } while (bkt != NULL);
+
+    return NXT_DECLINED;
+}
+
+
+nxt_int_t
+nxt_lvlhsh_insert(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq)
+{
+    uint32_t  key;
+
+    if (nxt_fast_path(lh->slot != NULL)) {
+
+        key = lhq->key_hash;
+
+        if (nxt_lvlhsh_is_bucket(lh->slot)) {
+            return nxt_lvlhsh_bucket_insert(lhq, &lh->slot, key, -1);
+        }
+
+        return nxt_lvlhsh_level_insert(lhq, &lh->slot, key, 0);
+    }
+
+    return nxt_lvlhsh_new_bucket(lhq, &lh->slot);
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_new_bucket(nxt_lvlhsh_query_t *lhq, void **slot)
+{
+    uint32_t  *bucket;
+
+    bucket = lhq->proto->alloc(lhq->pool, nxt_lvlhsh_bucket_size(lhq->proto),
+                               lhq->proto->nalloc);
+
+    if (nxt_fast_path(bucket != NULL)) {
+
+        nxt_lvlhsh_set_entry_value(bucket, lhq->value);
+        nxt_lvlhsh_set_entry_key(bucket, lhq->key_hash);
+
+        *nxt_lvlhsh_next_bucket(lhq->proto, bucket) = NULL;
+
+        nxt_lvlhsh_store_bucket(*slot, bucket);
+
+        return NXT_OK;
+    }
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_level_insert(nxt_lvlhsh_query_t *lhq, void **parent, uint32_t key,
+    nxt_uint_t nlvl)
+{
+    void        **slot, **lvl;
+    nxt_int_t   ret;
+    uintptr_t   mask;
+    nxt_uint_t  shift;
+
+    shift = lhq->proto->shift[nlvl];
+    mask = ((uintptr_t) 1 << shift) - 1;
+
+    lvl = nxt_lvlhsh_level(*parent, mask);
+    slot = &lvl[key & mask];
+
+    if (*slot != NULL) {
+        key >>= shift;
+
+        if (nxt_lvlhsh_is_bucket(*slot)) {
+            return nxt_lvlhsh_bucket_insert(lhq, slot, key, nlvl);
+        }
+
+        return nxt_lvlhsh_level_insert(lhq, slot, key, nlvl + 1);
+    }
+
+    ret = nxt_lvlhsh_new_bucket(lhq, slot);
+
+    if (nxt_fast_path(ret == NXT_OK)) {
+        nxt_lvlhsh_count_inc(*parent);
+    }
+
+    return ret;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_bucket_insert(nxt_lvlhsh_query_t *lhq, void **slot, uint32_t key,
+    nxt_int_t nlvl)
+{
+    void                      **bkt, **vacant_bucket, *value;
+    uint32_t                  *bucket, *e, *vacant_entry;
+    nxt_int_t                 ret;
+    uintptr_t                 n;
+    const void                *new_value;
+    const nxt_lvlhsh_proto_t  *proto;
+
+    bkt = slot;
+    vacant_entry = NULL;
+    vacant_bucket = NULL;
+    proto = lhq->proto;
+
+    /* Search for duplicate entry in bucket chain. */
+
+    do {
+        bucket = nxt_lvlhsh_bucket(proto, *bkt);
+        n = nxt_lvlhsh_bucket_entries(proto, *bkt);
+        e = bucket;
+
+        do {
+            if (nxt_lvlhsh_valid_entry(e)) {
+
+                if (nxt_lvlhsh_entry_key(e) == lhq->key_hash) {
+
+                    value = nxt_lvlhsh_entry_value(e);
+
+                    if (proto->test(lhq, value) == NXT_OK) {
+
+                        new_value = lhq->value;
+                        lhq->value = value;
+
+                        if (lhq->replace) {
+                            nxt_lvlhsh_set_entry_value(e, new_value);
+
+                            return NXT_OK;
+                        }
+
+                        return NXT_DECLINED;
+                    }
+                }
+
+                n--;
+
+            } else {
+                /*
+                 * Save a hole vacant position in bucket
+                 * and continue to search for duplicate entry.
+                 */
+                if (vacant_entry == NULL) {
+                    vacant_entry = e;
+                    vacant_bucket = bkt;
+                }
+            }
+
+            e += NXT_LVLHSH_ENTRY_SIZE;
+
+        } while (n != 0);
+
+        if (e < nxt_lvlhsh_bucket_end(proto, bucket)) {
+            /*
+             * Save a vacant position on incomplete bucket's end
+             * and continue to search for duplicate entry.
+             */
+            if (vacant_entry == NULL) {
+                vacant_entry = e;
+                vacant_bucket = bkt;
+            }
+        }
+
+        bkt = nxt_lvlhsh_next_bucket(proto, bucket);
+
+    } while (*bkt != NULL);
+
+    if (vacant_entry != NULL) {
+        nxt_lvlhsh_set_entry_value(vacant_entry, lhq->value);
+        nxt_lvlhsh_set_entry_key(vacant_entry, lhq->key_hash);
+        nxt_lvlhsh_count_inc(*vacant_bucket);
+
+        return NXT_OK;
+    }
+
+    /* All buckets are full. */
+
+    nlvl++;
+
+    if (nxt_fast_path(proto->shift[nlvl] != 0)) {
+
+        ret = nxt_lvlhsh_convert_bucket_to_level(lhq, slot, nlvl, bucket);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            return nxt_lvlhsh_level_insert(lhq, slot, key, nlvl);
+        }
+
+        return ret;
+    }
+
+    /* The last allowed level, only buckets may be allocated here. */
+
+    return nxt_lvlhsh_new_bucket(lhq, bkt);
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_convert_bucket_to_level(nxt_lvlhsh_query_t *lhq, void **slot,
+    nxt_uint_t nlvl, uint32_t *bucket)
+{
+    void                      *lvl, **level;
+    uint32_t                  *e, *end, key;
+    nxt_int_t                 ret;
+    nxt_uint_t                i, shift, size;
+    nxt_lvlhsh_query_t        q;
+    const nxt_lvlhsh_proto_t  *proto;
+
+    proto = lhq->proto;
+    size = nxt_lvlhsh_level_size(proto, nlvl);
+
+    lvl = proto->alloc(lhq->pool, size * (sizeof(void *)), proto->nalloc);
+
+    if (nxt_slow_path(lvl == NULL)) {
+        return NXT_ERROR;
+    }
+
+    memset(lvl, 0, size * (sizeof(void *)));
+
+    level = lvl;
+    shift = 0;
+
+    for (i = 0; i < nlvl; i++) {
+        /*
+         * Using SIMD operations in this trivial loop with maximum
+         * 8 iterations may increase code size by 170 bytes.
+         */
+        nxt_pragma_loop_disable_vectorization;
+
+        shift += proto->shift[i];
+    }
+
+    end = nxt_lvlhsh_bucket_end(proto, bucket);
+
+    for (e = bucket; e < end; e += NXT_LVLHSH_ENTRY_SIZE) {
+
+        q.proto = proto;
+        q.pool = lhq->pool;
+        q.value = nxt_lvlhsh_entry_value(e);
+        key = nxt_lvlhsh_entry_key(e);
+        q.key_hash = key;
+
+        ret = nxt_lvlhsh_level_convertion_insert(&q, &lvl, key >> shift, nlvl);
+
+        if (nxt_slow_path(ret != NXT_OK)) {
+            return nxt_lvlhsh_free_level(lhq, level, size);
+        }
+    }
+
+    *slot = lvl;
+
+    proto->free(lhq->pool, bucket, nxt_lvlhsh_bucket_size(proto));
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_level_convertion_insert(nxt_lvlhsh_query_t *lhq, void **parent,
+    uint32_t key, nxt_uint_t nlvl)
+{
+    void        **slot, **lvl;
+    nxt_int_t   ret;
+    uintptr_t   mask;
+    nxt_uint_t  shift;
+
+    shift = lhq->proto->shift[nlvl];
+    mask = ((uintptr_t) 1 << shift) - 1;
+
+    lvl = nxt_lvlhsh_level(*parent, mask);
+    slot = &lvl[key & mask];
+
+    if (*slot == NULL) {
+        ret = nxt_lvlhsh_new_bucket(lhq, slot);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            nxt_lvlhsh_count_inc(*parent);
+        }
+
+        return ret;
+    }
+
+    /* Only backets can be here. */
+
+    return nxt_lvlhsh_bucket_convertion_insert(lhq, slot, key >> shift, nlvl);
+}
+
+
+/*
+ * The special bucket insertion procedure is required because during
+ * convertion lhq->key contains garbage values and the test function
+ * cannot be called.  Besides, the procedure can be simpler because
+ * a new entry is inserted just after occupied entries.
+ */
+
+static nxt_int_t
+nxt_lvlhsh_bucket_convertion_insert(nxt_lvlhsh_query_t *lhq, void **slot,
+    uint32_t key, nxt_int_t nlvl)
+{
+    void                      **bkt;
+    uint32_t                  *bucket, *e;
+    nxt_int_t                 ret;
+    uintptr_t                 n;
+    const nxt_lvlhsh_proto_t  *proto;
+
+    bkt = slot;
+    proto = lhq->proto;
+
+    do {
+        bucket = nxt_lvlhsh_bucket(proto, *bkt);
+        n = nxt_lvlhsh_bucket_entries(proto, *bkt);
+        e = bucket + n * NXT_LVLHSH_ENTRY_SIZE;
+
+        if (nxt_fast_path(e < nxt_lvlhsh_bucket_end(proto, bucket))) {
+
+            nxt_lvlhsh_set_entry_value(e, lhq->value);
+            nxt_lvlhsh_set_entry_key(e, lhq->key_hash);
+            nxt_lvlhsh_count_inc(*bkt);
+
+            return NXT_OK;
+        }
+
+        bkt = nxt_lvlhsh_next_bucket(proto, bucket);
+
+    } while (*bkt != NULL);
+
+    /* All buckets are full. */
+
+    nlvl++;
+
+    if (nxt_fast_path(proto->shift[nlvl] != 0)) {
+
+        ret = nxt_lvlhsh_convert_bucket_to_level(lhq, slot, nlvl, bucket);
+
+        if (nxt_fast_path(ret == NXT_OK)) {
+            return nxt_lvlhsh_level_insert(lhq, slot, key, nlvl);
+        }
+
+        return ret;
+    }
+
+    /* The last allowed level, only buckets may be allocated here. */
+
+    return nxt_lvlhsh_new_bucket(lhq, bkt);
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_free_level(nxt_lvlhsh_query_t *lhq, void **level, nxt_uint_t size)
+{
+    size_t                    bsize;
+    nxt_uint_t                i;
+    const nxt_lvlhsh_proto_t  *proto;
+
+    proto = lhq->proto;
+    bsize = nxt_lvlhsh_bucket_size(proto);
+
+    for (i = 0; i < size; i++) {
+
+        if (level[i] != NULL) {
+            /*
+             * Chained buckets are not possible here, since even
+             * in the worst case one bucket cannot be converted
+             * in two chained buckets but remains the same bucket.
+             */
+            proto->free(lhq->pool, nxt_lvlhsh_bucket(proto, level[i]), bsize);
+        }
+    }
+
+    proto->free(lhq->pool, level, size * (sizeof(void *)));
+
+    return NXT_ERROR;
+}
+
+
+nxt_int_t
+nxt_lvlhsh_delete(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq)
+{
+    if (nxt_fast_path(lh->slot != NULL)) {
+
+        if (nxt_lvlhsh_is_bucket(lh->slot)) {
+            return nxt_lvlhsh_bucket_delete(lhq, &lh->slot);
+        }
+
+        return nxt_lvlhsh_level_delete(lhq, &lh->slot, lhq->key_hash, 0);
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_level_delete(nxt_lvlhsh_query_t *lhq, void **parent, uint32_t key,
+    nxt_uint_t nlvl)
+{
+    size_t      size;
+    void        **slot, **lvl;
+    uintptr_t   mask;
+    nxt_int_t   ret;
+    nxt_uint_t  shift;
+
+    shift = lhq->proto->shift[nlvl];
+    mask = ((uintptr_t) 1 << shift) - 1;
+
+    lvl = nxt_lvlhsh_level(*parent, mask);
+    slot = &lvl[key & mask];
+
+    if (*slot != NULL) {
+
+        if (nxt_lvlhsh_is_bucket(*slot)) {
+            ret = nxt_lvlhsh_bucket_delete(lhq, slot);
+
+        } else {
+            key >>= shift;
+            ret = nxt_lvlhsh_level_delete(lhq, slot, key, nlvl + 1);
+        }
+
+        if (*slot == NULL) {
+            nxt_lvlhsh_count_dec(*parent);
+
+            if (nxt_lvlhsh_level_entries(*parent, mask) == 0) {
+                *parent = NULL;
+                size = nxt_lvlhsh_level_size(lhq->proto, nlvl);
+                lhq->proto->free(lhq->pool, lvl, size * sizeof(void *));
+            }
+        }
+
+        return ret;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static nxt_int_t
+nxt_lvlhsh_bucket_delete(nxt_lvlhsh_query_t *lhq, void **bkt)
+{
+    void                      *value;
+    size_t                    size;
+    uint32_t                  *bucket, *e;
+    uintptr_t                 n;
+    const nxt_lvlhsh_proto_t  *proto;
+
+    proto = lhq->proto;
+
+    do {
+        bucket = nxt_lvlhsh_bucket(proto, *bkt);
+        n = nxt_lvlhsh_bucket_entries(proto, *bkt);
+        e = bucket;
+
+        do {
+            if (nxt_lvlhsh_valid_entry(e)) {
+
+                if (nxt_lvlhsh_entry_key(e) == lhq->key_hash) {
+
+                    value = nxt_lvlhsh_entry_value(e);
+
+                    if (proto->test(lhq, value) == NXT_OK) {
+
+                        if (nxt_lvlhsh_bucket_entries(proto, *bkt) == 1) {
+                            *bkt = *nxt_lvlhsh_next_bucket(proto, bucket);
+                            size = nxt_lvlhsh_bucket_size(proto);
+                            proto->free(lhq->pool, bucket, size);
+
+                        } else {
+                            nxt_lvlhsh_count_dec(*bkt);
+                            nxt_lvlhsh_set_entry_value(e, NULL);
+                        }
+
+                        lhq->value = value;
+
+                        return NXT_OK;
+                    }
+                }
+
+                n--;
+            }
+
+            e += NXT_LVLHSH_ENTRY_SIZE;
+
+        } while (n != 0);
+
+        bkt = nxt_lvlhsh_next_bucket(proto, bucket);
+
+    } while (*bkt != NULL);
+
+    return NXT_DECLINED;
+}
+
+
+void *
+nxt_lvlhsh_each(nxt_lvlhsh_t *lh, nxt_lvlhsh_each_t *lhe)
+{
+    void  **slot;
+
+    if (lhe->bucket == NXT_LVLHSH_BUCKET_DONE) {
+        slot = lh->slot;
+
+        if (nxt_lvlhsh_is_bucket(slot)) {
+            return NULL;
+        }
+
+    } else {
+        if (nxt_slow_path(lhe->bucket == NULL)) {
+
+            /* The first iteration only. */
+
+            slot = lh->slot;
+
+            if (slot == NULL) {
+                return NULL;
+            }
+
+            if (!nxt_lvlhsh_is_bucket(slot)) {
+                goto level;
+            }
+
+            lhe->bucket = nxt_lvlhsh_bucket(lhe->proto, slot);
+            lhe->entries = nxt_lvlhsh_bucket_entries(lhe->proto, slot);
+        }
+
+        return nxt_lvlhsh_bucket_each(lhe);
+    }
+
+level:
+
+    return nxt_lvlhsh_level_each(lhe, slot, 0, 0);
+}
+
+
+static void *
+nxt_lvlhsh_level_each(nxt_lvlhsh_each_t *lhe, void **level, nxt_uint_t nlvl,
+    nxt_uint_t shift)
+{
+    void        **slot, *value;
+    uintptr_t   mask;
+    nxt_uint_t  n, level_shift;
+
+    level_shift = lhe->proto->shift[nlvl];
+    mask = ((uintptr_t) 1 << level_shift) - 1;
+
+    level = nxt_lvlhsh_level(level, mask);
+
+    do {
+        n = (lhe->current >> shift) & mask;
+        slot = level[n];
+
+        if (slot != NULL) {
+            if (nxt_lvlhsh_is_bucket(slot)) {
+
+                if (lhe->bucket != NXT_LVLHSH_BUCKET_DONE) {
+
+                    lhe->bucket = nxt_lvlhsh_bucket(lhe->proto, slot);
+                    lhe->entries = nxt_lvlhsh_bucket_entries(lhe->proto, slot);
+                    lhe->entry = 0;
+
+                    return nxt_lvlhsh_bucket_each(lhe);
+                }
+
+                lhe->bucket = NULL;
+
+            } else {
+                value = nxt_lvlhsh_level_each(lhe, slot, nlvl + 1,
+                                              shift + level_shift);
+                if (value != NULL) {
+                    return value;
+                }
+            }
+        }
+
+        lhe->current &= ~(mask << shift);
+        n = ((n + 1) & mask) << shift;
+        lhe->current |= n;
+
+    } while (n != 0);
+
+    return NULL;
+}
+
+
+static nxt_noinline void *
+nxt_lvlhsh_bucket_each(nxt_lvlhsh_each_t *lhe)
+{
+    void      *value, **next;
+    uint32_t  *bucket;
+
+    /* At least one valid entry must present here. */
+    do {
+        bucket = &lhe->bucket[lhe->entry];
+        lhe->entry += NXT_LVLHSH_ENTRY_SIZE;
+
+    } while (nxt_lvlhsh_free_entry(bucket));
+
+    value = nxt_lvlhsh_entry_value(bucket);
+
+    lhe->entries--;
+
+    if (lhe->entries == 0) {
+        next = *nxt_lvlhsh_next_bucket(lhe->proto, lhe->bucket);
+
+        lhe->bucket = (next == NULL) ? NXT_LVLHSH_BUCKET_DONE:
+                                       nxt_lvlhsh_bucket(lhe->proto, next);
+
+        lhe->entries = nxt_lvlhsh_bucket_entries(lhe->proto, next);
+        lhe->entry = 0;
+    }
+
+    return value;
+}
diff --git a/nxt/nxt_lvlhsh.h b/nxt/nxt_lvlhsh.h
new file mode 100644 (file)
index 0000000..08c2429
--- /dev/null
@@ -0,0 +1,193 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_LVLHSH_H_INCLUDED_
+#define _NXT_LVLHSH_H_INCLUDED_
+
+
+typedef struct nxt_lvlhsh_query_s  nxt_lvlhsh_query_t;
+
+typedef nxt_int_t (*nxt_lvlhsh_test_t)(nxt_lvlhsh_query_t *lhq, void *data);
+typedef void *(*nxt_lvlhsh_alloc_t)(void *ctx, size_t size, nxt_uint_t nalloc);
+typedef void (*nxt_lvlhsh_free_t)(void *ctx, void *p, size_t size);
+
+
+#if (NXT_64BIT)
+
+#define NXT_LVLHSH_DEFAULT_BUCKET_SIZE  128
+#define NXT_LVLHSH_ENTRY_SIZE           3
+#define NXT_LVLHSH_BATCH_ALLOC          16
+
+/* 3 is shift of 64-bit pointer. */
+#define NXT_LVLHSH_MEMALIGN_SHIFT       (NXT_MAX_MEMALIGN_SHIFT - 3)
+
+#else
+
+#define NXT_LVLHSH_DEFAULT_BUCKET_SIZE  64
+#define NXT_LVLHSH_ENTRY_SIZE           2
+#define NXT_LVLHSH_BATCH_ALLOC          8
+
+/* 2 is shift of 32-bit pointer. */
+#define NXT_LVLHSH_MEMALIGN_SHIFT       (NXT_MAX_MEMALIGN_SHIFT - 2)
+
+#endif
+
+
+#if (NXT_LVLHSH_MEMALIGN_SHIFT < 10)
+#define NXT_LVLHSH_MAX_MEMALIGN_SHIFT   NXT_LVLHSH_MEMALIGN_SHIFT
+#else
+#define NXT_LVLHSH_MAX_MEMALIGN_SHIFT   10
+#endif
+
+
+#define NXT_LVLHSH_BUCKET_END(bucket_size)                                    \
+    (((bucket_size) - sizeof(void *))                                         \
+        / (NXT_LVLHSH_ENTRY_SIZE * sizeof(uint32_t))                          \
+     * NXT_LVLHSH_ENTRY_SIZE)
+
+
+#define NXT_LVLHSH_BUCKET_SIZE(bucket_size)                                   \
+    NXT_LVLHSH_BUCKET_END(bucket_size), bucket_size, (bucket_size - 1)
+
+
+#define NXT_LVLHSH_DEFAULT                                                    \
+    NXT_LVLHSH_BUCKET_SIZE(NXT_LVLHSH_DEFAULT_BUCKET_SIZE),                   \
+    { 4, 4, 4, 4, 4, 4, 4, 0 }
+
+
+#define NXT_LVLHSH_LARGE_SLAB                                                 \
+    NXT_LVLHSH_BUCKET_SIZE(NXT_LVLHSH_DEFAULT_BUCKET_SIZE),                   \
+    { 10, 4, 4, 4, 4, 4, 4, 0 }
+
+
+#define NXT_LVLHSH_LARGE_MEMALIGN                                             \
+    NXT_LVLHSH_BUCKET_SIZE(NXT_LVLHSH_DEFAULT_BUCKET_SIZE),                   \
+    { NXT_LVLHSH_MAX_MEMALIGN_SHIFT, 4, 4, 4, 4, 0, 0, 0 }
+
+
+typedef struct {
+    uint32_t                 bucket_end;
+    uint32_t                 bucket_size;
+    uint32_t                 bucket_mask;
+    uint8_t                  shift[8];
+    uint32_t                 nalloc;
+
+    nxt_lvlhsh_test_t         test;
+    nxt_lvlhsh_alloc_t        alloc;
+    nxt_lvlhsh_free_t         free;
+} nxt_lvlhsh_proto_t;
+
+
+typedef struct {
+    nxt_lvlhsh_test_t         test;
+    nxt_lvlhsh_alloc_t        alloc;
+    nxt_lvlhsh_free_t         free;
+
+    /* The maximum allowed aligned shift. */
+    uint32_t                 max_shift;
+    uint32_t                 nalloc;
+} nxt_lvlhsh_ctx_t;
+
+
+typedef struct {
+    void                      *slot;
+} nxt_lvlhsh_t;
+
+
+struct nxt_lvlhsh_query_s {
+    uint32_t                 key_hash;
+    nxt_str_t                 key;
+#if 0
+    uint32_t                 key_length;
+    void                     *key_start;
+#endif
+
+    uint8_t                  replace;     /* 1 bit */
+    void                     *value;
+
+    const nxt_lvlhsh_proto_t  *proto;
+    void                     *pool;
+
+    /* Opaque data passed for the test function. */
+    void                     *data;
+};
+
+
+#define                                                                       \
+nxt_lvlhsh_is_empty(lh)                                                       \
+    ((lh)->slot == NULL)
+
+
+#define                                                                       \
+nxt_lvlhsh_init(lh)                                                           \
+    (lh)->slot = NULL
+
+/*
+ * nxt_lvlhsh_find() finds a hash element.  If the element has been
+ * found then it is stored in the lhq->value and nxt_lvlhsh_find()
+ * returns NXT_OK.  Otherwise NXT_DECLINED is returned.
+ *
+ * The required nxt_lvlhsh_query_t fields: key_hash, key, proto.
+ */
+NXT_EXPORT nxt_int_t nxt_lvlhsh_find(nxt_lvlhsh_t *lh, nxt_lvlhsh_query_t *lhq);
+
+/*
+ * nxt_lvlhsh_insert() adds a hash element.  If the element already
+ * presents in lvlhsh and the lhq->replace flag is zero, then lhq->value
+ * is updated with the old element and NXT_DECLINED is returned.
+ * If the element already presents in lvlhsh and the lhq->replace flag
+ * is non-zero, then the old element is replaced with the new element.
+ * lhq->value is updated with the old element, and NXT_OK is returned.
+ * If the element is not present in lvlhsh, then it is inserted and
+ * NXT_OK is returned.  The lhq->value is not changed.
+ * On memory allocation failure NXT_ERROR is returned.
+ *
+ * The required nxt_lvlhsh_query_t fields: key_hash, key, proto, replace, value.
+ * The optional nxt_lvlhsh_query_t fields: pool.
+ */
+NXT_EXPORT nxt_int_t nxt_lvlhsh_insert(nxt_lvlhsh_t *lh,
+    nxt_lvlhsh_query_t *lhq);
+
+/*
+ * nxt_lvlhsh_delete() deletes a hash element.  If the element has been
+ * found then it is removed from lvlhsh and is stored in the lhq->value,
+ * and NXT_OK is returned.  Otherwise NXT_DECLINED is returned.
+ *
+ * The required nxt_lvlhsh_query_t fields: key_hash, key, proto.
+ * The optional nxt_lvlhsh_query_t fields: pool.
+ */
+NXT_EXPORT nxt_int_t nxt_lvlhsh_delete(nxt_lvlhsh_t *lh,
+    nxt_lvlhsh_query_t *lhq);
+
+
+typedef struct {
+    const nxt_lvlhsh_proto_t  *proto;
+
+    /*
+     * Fields to store current bucket entry position.  They cannot be
+     * combined in a single bucket pointer with number of entries in low
+     * bits, because entry positions are not aligned.  A current level is
+     * stored as key bit path from the root.
+     */
+    uint32_t                 *bucket;
+    uint32_t                 current;
+    uint32_t                 entry;
+    uint32_t                 entries;
+} nxt_lvlhsh_each_t;
+
+
+NXT_EXPORT void *nxt_lvlhsh_each(nxt_lvlhsh_t *lh, nxt_lvlhsh_each_t *le);
+
+
+NXT_EXPORT void *nxt_lvlhsh_alloc(void *data, size_t size, nxt_uint_t nalloc);
+NXT_EXPORT void nxt_lvlhsh_free(void *data, void *p, size_t size);
+
+NXT_EXPORT void *nxt_lvlhsh_pool_alloc(void *ctx, size_t size,
+    nxt_uint_t nalloc);
+NXT_EXPORT void nxt_lvlhsh_pool_free(void *ctx, void *p, size_t size);
+
+
+#endif /* _NXT_LVLHSH_H_INCLUDED_ */
diff --git a/nxt/nxt_malloc.c b/nxt/nxt_malloc.c
new file mode 100644 (file)
index 0000000..3285fd5
--- /dev/null
@@ -0,0 +1,52 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_auto_config.h>
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_malloc.h>
+
+
+#if (NXT_HAVE_POSIX_MEMALIGN)
+
+/*
+ * posix_memalign() presents in Linux glibc 2.1.91, FreeBSD 7.0,
+ * Solaris 11, MacOSX 10.6 (Snow Leopard), NetBSD 5.0, OpenBSD 4.8.
+ */
+
+void *
+nxt_memalign(size_t alignment, size_t size)
+{
+    int   err;
+    void  *p;
+
+    err = posix_memalign(&p, alignment, size);
+
+    if (nxt_fast_path(err == 0)) {
+        return p;
+    }
+
+    // STUB
+    //nxt_errno_set(err);
+
+    return NULL;
+}
+
+#elif (NXT_HAVE_MEMALIGN)
+
+/* memalign() presents in Solaris, HP-UX. */
+
+void *
+nxt_memalign(size_t alignment, size_t size)
+{
+    return memalign(alignment, size);
+}
+
+#else
+
+#error no memalign() implementation.
+
+#endif
diff --git a/nxt/nxt_malloc.h b/nxt/nxt_malloc.h
new file mode 100644 (file)
index 0000000..586274c
--- /dev/null
@@ -0,0 +1,28 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_MALLOC_H_INCLUDED_
+#define _NXT_MALLOC_H_INCLUDED_
+
+#include <stdlib.h>
+
+/*
+ * alloca() is defined in stdlib.h in Linux, FreeBSD and MacOSX
+ * and in alloca.h in Linux, Solaris and MacOSX.
+ */
+#if (NXT_SOLARIS)
+#include <alloca.h>
+#endif
+
+
+#define nxt_malloc(size)   malloc(size)
+#define nxt_free(p)        free(p)
+
+
+NXT_EXPORT void *nxt_memalign(size_t alignment, size_t size);
+
+
+#endif /* _NXT_MALLOC_H_INCLUDED_ */
diff --git a/nxt/nxt_mem_cache_pool.c b/nxt/nxt_mem_cache_pool.c
new file mode 100644 (file)
index 0000000..6aa573e
--- /dev/null
@@ -0,0 +1,792 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_alignment.h>
+#include <nxt_stub.h>
+#include <nxt_queue.h>
+#include <nxt_rbtree.h>
+#include <nxt_mem_cache_pool.h>
+#include <string.h>
+
+
+/*
+ * A memory cache pool allocates memory in clusters of specified size and
+ * aligned to page_alignment.  A cluster is divided on pages of specified
+ * size.  Page size must be a power of 2.  A page can be used entirely or
+ * can be divided on chunks of equal size.  Chunk size must be a power of 2.
+ * A cluster can contains pages with different chunk sizes.  Cluster size
+ * must be multiple of page size and may be not a power of 2.  Allocations
+ * greater than page are allocated outside clusters.  Start addresses and
+ * sizes of clusters and large allocations are stored in rbtree to find
+ * them on free operations.  The rbtree nodes are sorted by start addresses.
+ */
+
+
+typedef struct nxt_mem_cache_page_s  nxt_mem_cache_page_t;
+
+struct nxt_mem_cache_page_s {
+    /* Chunk bitmap.  There can be no more than 32 chunks in a page. */
+    uint8_t                     map[4];
+
+    /* Number of free chunks of a chunked page. */
+    uint8_t                     chunks;
+
+    /*
+     * Size of chunks or page shifted by pool->chunk_size_shift.
+     * Zero means that page is free.
+     */
+    uint8_t                     size;
+
+    /*
+     * Page number in page cluster.
+     * There can be no more than 65536 pages in a cluster.
+     */
+    uint16_t                    number;
+
+    /*
+     * Used to link pages with free chunks in pool chunk slot list
+     * or to link free pages in clusters.
+     */
+    nxt_queue_link_t             link;
+};
+
+
+typedef struct {
+    NXT_RBTREE_NODE              (node);
+    uint8_t                     type;
+    uint32_t                    size;
+
+    u_char                      *start;
+    nxt_mem_cache_page_t         pages[];
+} nxt_mem_cache_block_t;
+
+
+typedef struct {
+    nxt_queue_t                  pages;
+#if (NXT_64BIT)
+    uint32_t                    size;
+    uint32_t                    chunks;
+#else
+    uint16_t                    size;
+    uint16_t                    chunks;
+#endif
+} nxt_mem_cache_slot_t;
+
+
+struct nxt_mem_cache_pool_s {
+    /* rbtree of nxt_mem_cache_block_t. */
+    nxt_rbtree_t                 blocks;
+
+    nxt_queue_t                  free_pages;
+
+    uint8_t                     chunk_size_shift;
+    uint8_t                     page_size_shift;
+    uint32_t                    page_size;
+    uint32_t                    page_alignment;
+    uint32_t                    cluster_size;
+
+    const nxt_mem_proto_t        *proto;
+    void                        *mem;
+    void                        *trace;
+
+    nxt_mem_cache_slot_t         slots[];
+};
+
+
+/* A cluster cache block. */
+#define NXT_MEM_CACHE_CLUSTER_BLOCK   0
+
+/* A discrete cache block of large allocation. */
+#define NXT_MEM_CACHE_DISCRETE_BLOCK  1
+/*
+ * An embedded cache block allocated together with large allocation
+ * just after the allocation.
+ */
+#define NXT_MEM_CACHE_EMBEDDED_BLOCK  2
+
+
+#define nxt_mem_cache_chunk_is_free(map, chunk)                                \
+    ((map[chunk / 8] & (0x80 >> (chunk & 7))) == 0)
+
+
+#define nxt_mem_cache_chunk_set_free(map, chunk)                               \
+    map[chunk / 8] &= ~(0x80 >> (chunk & 7))
+
+
+#define nxt_mem_cache_free_junk(p, size)                                       \
+    memset((p), 0x5A, size)
+
+
+static nxt_uint_t nxt_mem_cache_shift(nxt_uint_t n);
+static void *nxt_mem_cache_alloc_small(nxt_mem_cache_pool_t *pool, size_t size);
+static nxt_uint_t nxt_mem_cache_alloc_chunk(u_char *map, nxt_uint_t size);
+static nxt_mem_cache_page_t *
+    nxt_mem_cache_alloc_page(nxt_mem_cache_pool_t *pool);
+static nxt_mem_cache_block_t *
+    nxt_mem_cache_alloc_cluster(nxt_mem_cache_pool_t *pool);
+static void *nxt_mem_cache_alloc_large(nxt_mem_cache_pool_t *pool,
+    size_t alignment, size_t size);
+static nxt_int_t nxt_mem_cache_rbtree_compare(nxt_rbtree_node_t *node1,
+    nxt_rbtree_node_t *node2);
+static nxt_mem_cache_block_t *nxt_mem_cache_find_block(nxt_rbtree_t *tree,
+    u_char *p);
+static const char *nxt_mem_cache_chunk_free(nxt_mem_cache_pool_t *pool,
+    nxt_mem_cache_block_t *cluster, u_char *p);
+
+
+nxt_mem_cache_pool_t *
+nxt_mem_cache_pool_create(const nxt_mem_proto_t *proto, void *mem,
+    void *trace, size_t cluster_size, size_t page_alignment, size_t page_size,
+    size_t min_chunk_size)
+{
+    /* Alignment and sizes must be a power of 2. */
+
+    if (nxt_slow_path((page_alignment & (page_alignment - 1)) != 0
+                     || (page_size & (page_size - 1)) != 0
+                     || (min_chunk_size & (min_chunk_size - 1)) != 0))
+    {
+        return NULL;
+    }
+
+    page_alignment = nxt_max(page_alignment, NXT_MAX_ALIGNMENT);
+
+    if (nxt_slow_path(page_size < 64
+                     || page_size < page_alignment
+                     || page_size < min_chunk_size
+                     || min_chunk_size * 32 < page_size
+                     || cluster_size < page_size
+                     || cluster_size % page_size != 0))
+    {
+        return NULL;
+    }
+
+    return nxt_mem_cache_pool_fast_create(proto, mem, trace,
+                                         cluster_size, page_alignment,
+                                         page_size, min_chunk_size);
+}
+
+
+nxt_mem_cache_pool_t *
+nxt_mem_cache_pool_fast_create(const nxt_mem_proto_t *proto, void *mem,
+    void *trace, size_t cluster_size, size_t page_alignment, size_t page_size,
+    size_t min_chunk_size)
+{
+    nxt_uint_t            slots, chunk_size;
+    nxt_mem_cache_slot_t  *slot;
+    nxt_mem_cache_pool_t  *pool;
+
+    slots = 0;
+    chunk_size = page_size;
+
+    do {
+        slots++;
+        chunk_size /= 2;
+    } while (chunk_size > min_chunk_size);
+
+    pool = proto->zalloc(mem, sizeof(nxt_mem_cache_pool_t)
+                              + slots * sizeof(nxt_mem_cache_slot_t));
+
+    if (nxt_fast_path(pool != NULL)) {
+
+        pool->proto = proto;
+        pool->mem = mem;
+        pool->trace = trace;
+
+        pool->page_size = page_size;
+        pool->page_alignment = nxt_max(page_alignment, NXT_MAX_ALIGNMENT);
+        pool->cluster_size = cluster_size;
+
+        slot = pool->slots;
+
+        do {
+            nxt_queue_init(&slot->pages);
+
+            slot->size = chunk_size;
+            /* slot->chunks should be one less than actual number of chunks. */
+            slot->chunks = (page_size / chunk_size) - 1;
+
+            slot++;
+            chunk_size *= 2;
+        } while (chunk_size < page_size);
+
+        pool->chunk_size_shift = nxt_mem_cache_shift(min_chunk_size);
+        pool->page_size_shift = nxt_mem_cache_shift(page_size);
+
+        nxt_rbtree_init(&pool->blocks, nxt_mem_cache_rbtree_compare);
+
+        nxt_queue_init(&pool->free_pages);
+    }
+
+    return pool;
+}
+
+
+static nxt_uint_t
+nxt_mem_cache_shift(nxt_uint_t n)
+{
+    nxt_uint_t  shift;
+
+    shift = 0;
+    n /= 2;
+
+    do {
+        shift++;
+        n /= 2;
+    } while (n != 0);
+
+    return shift;
+}
+
+
+nxt_bool_t
+nxt_mem_cache_pool_is_empty(nxt_mem_cache_pool_t *pool)
+{
+    return (nxt_rbtree_is_empty(&pool->blocks)
+            && nxt_queue_is_empty(&pool->free_pages));
+}
+
+
+void
+nxt_mem_cache_pool_destroy(nxt_mem_cache_pool_t *pool)
+{
+    void                  *p;
+    nxt_rbtree_node_t      *node, *next;
+    nxt_mem_cache_block_t  *block;
+
+    for (node = nxt_rbtree_min(&pool->blocks);
+         nxt_rbtree_is_there_successor(&pool->blocks, node);
+         node = next)
+    {
+        next = nxt_rbtree_node_successor(&pool->blocks, node);
+
+        block = (nxt_mem_cache_block_t *) node;
+
+        nxt_rbtree_delete(&pool->blocks, &block->node);
+
+        p = block->start;
+
+        if (block->type != NXT_MEM_CACHE_EMBEDDED_BLOCK) {
+            pool->proto->free(pool->mem, block);
+        }
+
+        pool->proto->free(pool->mem, p);
+    }
+
+    pool->proto->free(pool->mem, pool);
+}
+
+
+nxt_inline u_char *
+nxt_mem_cache_page_addr(nxt_mem_cache_pool_t *pool, nxt_mem_cache_page_t *page)
+{
+    nxt_mem_cache_block_t  *block;
+
+    block = (nxt_mem_cache_block_t *)
+                ((u_char *) page - page->number * sizeof(nxt_mem_cache_page_t)
+                 - offsetof(nxt_mem_cache_block_t, pages));
+
+    return block->start + (page->number << pool->page_size_shift);
+}
+
+
+void *
+nxt_mem_cache_alloc(nxt_mem_cache_pool_t *pool, size_t size)
+{
+    if (pool->proto->trace != NULL) {
+        pool->proto->trace(pool->trace, "mem cache alloc: %zd", size);
+    }
+
+    if (size <= pool->page_size) {
+        return nxt_mem_cache_alloc_small(pool, size);
+    }
+
+    return nxt_mem_cache_alloc_large(pool, NXT_MAX_ALIGNMENT, size);
+}
+
+
+void *
+nxt_mem_cache_zalloc(nxt_mem_cache_pool_t *pool, size_t size)
+{
+    void  *p;
+
+    p = nxt_mem_cache_alloc(pool, size);
+
+    if (nxt_fast_path(p != NULL)) {
+        memset(p, 0, size);
+    }
+
+    return p;
+}
+
+
+void *
+nxt_mem_cache_align(nxt_mem_cache_pool_t *pool, size_t alignment, size_t size)
+{
+    if (pool->proto->trace != NULL) {
+        pool->proto->trace(pool->trace,
+                           "mem cache align: @%zd:%zd", alignment, size);
+    }
+
+    /* Alignment must be a power of 2. */
+
+    if (nxt_fast_path((alignment - 1) & alignment) == 0) {
+
+        if (size <= pool->page_size && alignment <= pool->page_alignment) {
+            size = nxt_max(size, alignment);
+
+            if (size <= pool->page_size) {
+                return nxt_mem_cache_alloc_small(pool, size);
+            }
+        }
+
+        return nxt_mem_cache_alloc_large(pool, alignment, size);
+    }
+
+    return NULL;
+}
+
+
+void *
+nxt_mem_cache_zalign(nxt_mem_cache_pool_t *pool, size_t alignment, size_t size)
+{
+    void  *p;
+
+    p = nxt_mem_cache_align(pool, alignment, size);
+
+    if (nxt_fast_path(p != NULL)) {
+        memset(p, 0, size);
+    }
+
+    return p;
+}
+
+
+static void *
+nxt_mem_cache_alloc_small(nxt_mem_cache_pool_t *pool, size_t size)
+{
+    u_char                *p;
+    nxt_queue_link_t      *link;
+    nxt_mem_cache_page_t  *page;
+    nxt_mem_cache_slot_t  *slot;
+
+    p = NULL;
+
+    if (size <= pool->page_size / 2) {
+
+        /* Find a slot with appropriate chunk size. */
+        for (slot = pool->slots; slot->size < size; slot++) { /* void */ }
+
+        size = slot->size;
+
+        if (nxt_fast_path(!nxt_queue_is_empty(&slot->pages))) {
+
+            link = nxt_queue_first(&slot->pages);
+            page = nxt_queue_link_data(link, nxt_mem_cache_page_t, link);
+
+            p = nxt_mem_cache_page_addr(pool, page);
+            p += nxt_mem_cache_alloc_chunk(page->map, size);
+
+            page->chunks--;
+
+            if (page->chunks == 0) {
+                /*
+                 * Remove full page from the pool chunk slot list
+                 * of pages with free chunks.
+                 */
+                nxt_queue_remove(&page->link);
+            }
+
+        } else {
+            page = nxt_mem_cache_alloc_page(pool);
+
+            if (nxt_fast_path(page != NULL)) {
+
+                nxt_queue_insert_head(&slot->pages, &page->link);
+
+                /* Mark the first chunk as busy. */
+                page->map[0] = 0x80;
+                page->map[1] = 0;
+                page->map[2] = 0;
+                page->map[3] = 0;
+
+                /* slot->chunks are already one less. */
+                page->chunks = slot->chunks;
+                page->size = size >> pool->chunk_size_shift;
+
+                p = nxt_mem_cache_page_addr(pool, page);
+            }
+        }
+
+    } else {
+        page = nxt_mem_cache_alloc_page(pool);
+
+        if (nxt_fast_path(page != NULL)) {
+            page->size = pool->page_size >> pool->chunk_size_shift;
+
+            p = nxt_mem_cache_page_addr(pool, page);
+        }
+
+#if (NXT_DEBUG)
+        size = pool->page_size;
+#endif
+    }
+
+    if (pool->proto->trace != NULL) {
+        pool->proto->trace(pool->trace, "mem cache chunk:%uz alloc: %p",
+                           size, p);
+    }
+
+    return p;
+}
+
+
+static nxt_uint_t
+nxt_mem_cache_alloc_chunk(uint8_t *map, nxt_uint_t size)
+{
+    uint8_t     mask;
+    nxt_uint_t  n, offset;
+
+    offset = 0;
+    n = 0;
+
+    /* The page must have at least one free chunk. */
+
+    for ( ;; ) {
+        if (map[n] != 0xff) {
+
+            mask = 0x80;
+
+            do {
+                if ((map[n] & mask) == 0) {
+                    /* A free chunk is found. */
+                    map[n] |= mask;
+                    return offset;
+                }
+
+                offset += size;
+                mask >>= 1;
+
+            } while (mask != 0);
+
+        } else {
+            /* Fast-forward: all 8 chunks are occupied. */
+            offset += size * 8;
+        }
+
+        n++;
+    }
+}
+
+
+static nxt_mem_cache_page_t *
+nxt_mem_cache_alloc_page(nxt_mem_cache_pool_t *pool)
+{
+    nxt_queue_link_t       *link;
+    nxt_mem_cache_page_t   *page;
+    nxt_mem_cache_block_t  *cluster;
+
+    if (nxt_queue_is_empty(&pool->free_pages)) {
+        cluster = nxt_mem_cache_alloc_cluster(pool);
+        if (nxt_slow_path(cluster == NULL)) {
+            return NULL;
+        }
+    }
+
+    link = nxt_queue_first(&pool->free_pages);
+    nxt_queue_remove(link);
+
+    page = nxt_queue_link_data(link, nxt_mem_cache_page_t, link);
+
+    return page;
+}
+
+
+static nxt_mem_cache_block_t *
+nxt_mem_cache_alloc_cluster(nxt_mem_cache_pool_t *pool)
+{
+    nxt_uint_t             n;
+    nxt_mem_cache_block_t  *cluster;
+
+    n = pool->cluster_size >> pool->page_size_shift;
+
+    cluster = pool->proto->zalloc(pool->mem, sizeof(nxt_mem_cache_block_t)
+                                             + n * sizeof(nxt_mem_cache_page_t));
+
+    if (nxt_slow_path(cluster == NULL)) {
+        return NULL;
+    }
+
+    /* NXT_MEM_CACHE_CLUSTER_BLOCK type is zero. */
+
+    cluster->size = pool->cluster_size;
+
+    cluster->start = pool->proto->align(pool->mem, pool->page_alignment,
+                                        pool->cluster_size);
+    if (nxt_slow_path(cluster->start == NULL)) {
+        pool->proto->free(pool->mem, cluster);
+        return NULL;
+    }
+
+    n--;
+    cluster->pages[n].number = n;
+    nxt_queue_insert_head(&pool->free_pages, &cluster->pages[n].link);
+
+    while (n != 0) {
+        n--;
+        cluster->pages[n].number = n;
+        nxt_queue_insert_before(&cluster->pages[n + 1].link,
+                               &cluster->pages[n].link);
+    }
+
+    nxt_rbtree_insert(&pool->blocks, &cluster->node);
+
+    return cluster;
+}
+
+
+static void *
+nxt_mem_cache_alloc_large(nxt_mem_cache_pool_t *pool, size_t alignment,
+    size_t size)
+{
+    u_char                 *p;
+    size_t                 aligned_size;
+    uint8_t                type;
+    nxt_mem_cache_block_t  *block;
+
+    if (nxt_slow_path((size - 1) & size) != 0) {
+        aligned_size = nxt_align_size(size, sizeof(uintptr_t));
+
+        p = pool->proto->align(pool->mem, alignment,
+                               aligned_size + sizeof(nxt_mem_cache_block_t));
+
+        if (nxt_slow_path(p == NULL)) {
+            return NULL;
+        }
+
+        block = (nxt_mem_cache_block_t *) (p + aligned_size);
+        type = NXT_MEM_CACHE_EMBEDDED_BLOCK;
+
+    } else {
+        block = pool->proto->alloc(pool->mem, sizeof(nxt_mem_cache_block_t));
+
+        if (nxt_slow_path(block == NULL)) {
+            pool->proto->free(pool->mem, block);
+            return NULL;
+        }
+
+        p = pool->proto->align(pool->mem, alignment, size);
+        if (nxt_slow_path(p == NULL)) {
+            return NULL;
+        }
+
+        type = NXT_MEM_CACHE_DISCRETE_BLOCK;
+    }
+
+    block->type = type;
+    block->size = size;
+    block->start = p;
+
+    nxt_rbtree_insert(&pool->blocks, &block->node);
+
+    return p;
+}
+
+
+static nxt_int_t
+nxt_mem_cache_rbtree_compare(nxt_rbtree_node_t *node1, nxt_rbtree_node_t *node2)
+{
+    nxt_mem_cache_block_t  *block1, *block2;
+
+    block1 = (nxt_mem_cache_block_t *) node1;
+    block2 = (nxt_mem_cache_block_t *) node2;
+
+    return (uintptr_t) block1->start - (uintptr_t) block2->start;
+}
+
+
+void
+nxt_mem_cache_free(nxt_mem_cache_pool_t *pool, void *p)
+{
+    const char            *err;
+    nxt_mem_cache_block_t  *block;
+
+    if (pool->proto->trace != NULL) {
+        pool->proto->trace(pool->trace, "mem cache free %p", p);
+    }
+
+    block = nxt_mem_cache_find_block(&pool->blocks, p);
+
+    if (nxt_fast_path(block != NULL)) {
+
+        if (block->type == NXT_MEM_CACHE_CLUSTER_BLOCK) {
+            err = nxt_mem_cache_chunk_free(pool, block, p);
+
+            if (nxt_fast_path(err == NULL)) {
+                return;
+            }
+
+        } else if (nxt_fast_path(p == block->start)) {
+            nxt_rbtree_delete(&pool->blocks, &block->node);
+
+            if (block->type == NXT_MEM_CACHE_DISCRETE_BLOCK) {
+                pool->proto->free(pool->mem, block);
+            }
+
+            pool->proto->free(pool->mem, p);
+
+            return;
+
+        } else {
+            err = "freed pointer points to middle of block: %p";
+        }
+
+    } else {
+        err = "freed pointer is out of pool: %p";
+    }
+
+    if (pool->proto->alert != NULL) {
+        pool->proto->alert(pool->trace, err, p);
+    }
+}
+
+
+static nxt_mem_cache_block_t *
+nxt_mem_cache_find_block(nxt_rbtree_t *tree, u_char *p)
+{
+    nxt_rbtree_node_t      *node, *sentinel;
+    nxt_mem_cache_block_t  *block;
+
+    node = nxt_rbtree_root(tree);
+    sentinel = nxt_rbtree_sentinel(tree);
+
+    while (node != sentinel) {
+
+        block = (nxt_mem_cache_block_t *) node;
+
+        if (p < block->start) {
+            node = node->left;
+
+        } else if (p >= block->start + block->size) {
+            node = node->right;
+
+        } else {
+            return block;
+        }
+    }
+
+    return NULL;
+}
+
+
+static const char *
+nxt_mem_cache_chunk_free(nxt_mem_cache_pool_t *pool,
+    nxt_mem_cache_block_t *cluster, u_char *p)
+{
+    u_char                *start;
+    uintptr_t             offset;
+    nxt_uint_t            n, size, chunk;
+    nxt_mem_cache_page_t  *page;
+    nxt_mem_cache_slot_t  *slot;
+
+    n = (p - cluster->start) >> pool->page_size_shift;
+    start = cluster->start + (n << pool->page_size_shift);
+
+    page = &cluster->pages[n];
+
+    if (page->size == 0) {
+        return "freed pointer points to already free page: %p";
+    }
+
+    size = page->size << pool->chunk_size_shift;
+
+    if (size != pool->page_size) {
+
+        offset = (uintptr_t) (p - start) & (pool->page_size - 1);
+        chunk = offset / size;
+
+        if (nxt_slow_path(offset != chunk * size)) {
+            return "freed pointer points to wrong chunk: %p";
+        }
+
+        if (nxt_slow_path(nxt_mem_cache_chunk_is_free(page->map, chunk))) {
+            return "freed pointer points to already free chunk: %p";
+        }
+
+        nxt_mem_cache_chunk_set_free(page->map, chunk);
+
+        /* Find a slot with appropriate chunk size. */
+        for (slot = pool->slots; slot->size < size; slot++) { /* void */ }
+
+        if (page->chunks != slot->chunks) {
+            page->chunks++;
+
+            if (page->chunks == 1) {
+                /*
+                 * Add the page to the head of pool chunk slot list
+                 * of pages with free chunks.
+                 */
+                nxt_queue_insert_head(&slot->pages, &page->link);
+            }
+
+            nxt_mem_cache_free_junk(p, size);
+
+            return NULL;
+
+        } else {
+            /*
+             * All chunks are free, remove the page from pool chunk slot
+             * list of pages with free chunks.
+             */
+            nxt_queue_remove(&page->link);
+        }
+
+    } else if (nxt_slow_path(p != start)) {
+        return "invalid pointer to chunk: %p";
+    }
+
+    /* Add the free page to the pool's free pages tree. */
+
+    page->size = 0;
+    nxt_queue_insert_head(&pool->free_pages, &page->link);
+
+    nxt_mem_cache_free_junk(p, size);
+
+    /* Test if all pages in the cluster are free. */
+
+    page = cluster->pages;
+    n = pool->cluster_size >> pool->page_size_shift;
+
+    do {
+         if (page->size != 0) {
+             return NULL;
+         }
+
+         page++;
+         n--;
+    } while (n != 0);
+
+    /* Free cluster. */
+
+    page = cluster->pages;
+    n = pool->cluster_size >> pool->page_size_shift;
+
+    do {
+         nxt_queue_remove(&page->link);
+         page++;
+         n--;
+    } while (n != 0);
+
+    nxt_rbtree_delete(&pool->blocks, &cluster->node);
+
+    p = cluster->start;
+
+    pool->proto->free(pool->mem, cluster);
+    pool->proto->free(pool->mem, p);
+
+    return NULL;
+}
diff --git a/nxt/nxt_mem_cache_pool.h b/nxt/nxt_mem_cache_pool.h
new file mode 100644 (file)
index 0000000..67f380e
--- /dev/null
@@ -0,0 +1,40 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_MEM_CACHE_POOL_H_INCLUDED_
+#define _NXT_MEM_CACHE_POOL_H_INCLUDED_
+
+
+typedef struct nxt_mem_cache_pool_s  nxt_mem_cache_pool_t;
+
+
+NXT_EXPORT nxt_mem_cache_pool_t *
+    nxt_mem_cache_pool_create(const nxt_mem_proto_t *proto, void *mem,
+    void *trace, size_t cluster_size, size_t page_alignment, size_t page_size,
+    size_t min_chunk_size)
+    NXT_MALLOC_LIKE;
+NXT_EXPORT nxt_mem_cache_pool_t *
+    nxt_mem_cache_pool_fast_create(const nxt_mem_proto_t *proto, void *mem,
+    void *trace, size_t cluster_size, size_t page_alignment, size_t page_size,
+    size_t min_chunk_size)
+    NXT_MALLOC_LIKE;
+NXT_EXPORT nxt_bool_t nxt_mem_cache_pool_is_empty(nxt_mem_cache_pool_t *pool);
+NXT_EXPORT void nxt_mem_cache_pool_destroy(nxt_mem_cache_pool_t *pool);
+
+NXT_EXPORT void *nxt_mem_cache_alloc(nxt_mem_cache_pool_t *pool, size_t size)
+    NXT_MALLOC_LIKE;
+NXT_EXPORT void *nxt_mem_cache_zalloc(nxt_mem_cache_pool_t *pool, size_t size)
+    NXT_MALLOC_LIKE;
+NXT_EXPORT void *nxt_mem_cache_align(nxt_mem_cache_pool_t *pool,
+    size_t alignment, size_t size)
+    NXT_MALLOC_LIKE;
+NXT_EXPORT void *nxt_mem_cache_zalign(nxt_mem_cache_pool_t *pool,
+    size_t alignment, size_t size)
+    NXT_MALLOC_LIKE;
+NXT_EXPORT void nxt_mem_cache_free(nxt_mem_cache_pool_t *pool, void *p);
+
+
+#endif /* _NXT_MEM_CACHE_POOL_H_INCLUDED_ */
diff --git a/nxt/nxt_murmur_hash.c b/nxt/nxt_murmur_hash.c
new file mode 100644 (file)
index 0000000..9f78a15
--- /dev/null
@@ -0,0 +1,86 @@
+
+/*
+ * The code is based on the code by Austin Appleby,
+ * released to the public domain.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_murmur_hash.h>
+
+
+uint32_t
+nxt_murmur_hash2(const void *data, size_t len)
+{
+    uint32_t        h, k;
+    const u_char    *p;
+    const uint32_t  m = 0x5bd1e995;
+
+    p = data;
+    h = 0 ^ (uint32_t) len;
+
+    while (len >= 4) {
+        k  = p[0];
+        k |= p[1] << 8;
+        k |= p[2] << 16;
+        k |= p[3] << 24;
+
+        k *= m;
+        k ^= k >> 24;
+        k *= m;
+
+        h *= m;
+        h ^= k;
+
+        p += 4;
+        len -= 4;
+    }
+
+    switch (len) {
+    case 3:
+        h ^= p[2] << 16;
+    case 2:
+        h ^= p[1] << 8;
+    case 1:
+        h ^= p[0];
+        h *= m;
+    }
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    return h;
+}
+
+
+/* The MurmurHash2 for fixed 4 byte length. */
+
+uint32_t
+nxt_murmur_hash2_uint32(const void *data)
+{
+    uint32_t        h, k;
+    const u_char    *p;
+    const uint32_t  m = 0x5bd1e995;
+
+    p = data;
+
+    k  = p[0];
+    k |= p[1] << 8;
+    k |= p[2] << 16;
+    k |= p[3] << 24;
+
+    k *= m;
+    k ^= k >> 24;
+    k *= m;
+
+    h = 0 ^ 4;
+    h *= m;
+    h ^= k;
+
+    h ^= h >> 13;
+    h *= m;
+    h ^= h >> 15;
+
+    return h;
+}
diff --git a/nxt/nxt_murmur_hash.h b/nxt/nxt_murmur_hash.h
new file mode 100644 (file)
index 0000000..289dc5b
--- /dev/null
@@ -0,0 +1,15 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_MURMUR_HASH_H_INCLUDED_
+#define _NXT_MURMUR_HASH_H_INCLUDED_
+
+
+NXT_EXPORT uint32_t nxt_murmur_hash2(const void *data, size_t len);
+NXT_EXPORT uint32_t nxt_murmur_hash2_uint32(const void *data);
+
+
+#endif /* _NXT_MURMUR_HASH_H_INCLUDED_ */
diff --git a/nxt/nxt_queue.c b/nxt/nxt_queue.c
new file mode 100644 (file)
index 0000000..7c1e2c1
--- /dev/null
@@ -0,0 +1,86 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_queue.h>
+
+
+/*
+ * Find the middle queue element if the queue has odd number of elements,
+ * or the first element of the queue's second part otherwise.
+ */
+
+nxt_queue_link_t *
+nxt_queue_middle(nxt_queue_t *queue)
+{
+    nxt_queue_link_t  *middle, *next;
+
+    middle = nxt_queue_first(queue);
+
+    if (middle == nxt_queue_last(queue)) {
+        return middle;
+    }
+
+    next = middle;
+
+    for ( ;; ) {
+        middle = nxt_queue_next(middle);
+
+        next = nxt_queue_next(next);
+
+        if (next == nxt_queue_last(queue)) {
+            return middle;
+        }
+
+        next = nxt_queue_next(next);
+
+        if (next == nxt_queue_last(queue)) {
+            return middle;
+        }
+    }
+}
+
+
+/*
+ * nxt_queue_sort() provides a stable sort because it uses the insertion
+ * sort algorithm.  Its worst and average computational complexity is O^2.
+ */
+
+void
+nxt_queue_sort(nxt_queue_t *queue,
+    nxt_int_t (*compare)(const void *data, const nxt_queue_link_t *,
+    const nxt_queue_link_t *), const void *data)
+{
+    nxt_queue_link_t  *link, *prev, *next;
+
+    link = nxt_queue_first(queue);
+
+    if (link == nxt_queue_last(queue)) {
+        return;
+    }
+
+    for (link = nxt_queue_next(link);
+         link != nxt_queue_tail(queue);
+         link = next)
+    {
+        prev = nxt_queue_prev(link);
+        next = nxt_queue_next(link);
+
+        nxt_queue_remove(link);
+
+        do {
+            if (compare(data, prev, link) <= 0) {
+                break;
+            }
+
+            prev = nxt_queue_prev(prev);
+
+        } while (prev != nxt_queue_head(queue));
+
+        nxt_queue_insert_after(prev, link);
+    }
+}
diff --git a/nxt/nxt_queue.h b/nxt/nxt_queue.h
new file mode 100644 (file)
index 0000000..3a8a5ba
--- /dev/null
@@ -0,0 +1,199 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_QUEUE_H_INCLUDED_
+#define _NXT_QUEUE_H_INCLUDED_
+
+
+typedef struct nxt_queue_link_s  nxt_queue_link_t;
+
+struct nxt_queue_link_s {
+    nxt_queue_link_t  *prev;
+    nxt_queue_link_t  *next;
+};
+
+
+typedef struct {
+    nxt_queue_link_t  head;
+} nxt_queue_t;
+
+
+#define nxt_queue_init(queue)                                                 \
+    do {                                                                      \
+        (queue)->head.prev = &(queue)->head;                                  \
+        (queue)->head.next = &(queue)->head;                                  \
+    } while (0)
+
+
+#define nxt_queue_sentinel(link)                                              \
+    do {                                                                      \
+        (link)->prev = (link);                                                \
+        (link)->next = (link);                                                \
+    } while (0)
+
+
+/*
+ * Short-circuit a queue link to itself to allow once remove safely it
+ * using nxt_queue_remove().
+ */
+
+#define nxt_queue_self(link)                                                  \
+    nxt_queue_sentinel(link)
+
+
+#define nxt_queue_is_empty(queue)                                             \
+    (&(queue)->head == (queue)->head.prev)
+
+/*
+ * A loop to iterate all queue links starting from head:
+ *
+ *      nxt_queue_link_t  link;
+ *  } nxt_type_t  *tp;
+ *
+ *
+ *  for (lnk = nxt_queue_first(queue);
+ *       lnk != nxt_queue_tail(queue);
+ *       lnk = nxt_queue_next(lnk))
+ *  {
+ *      tp = nxt_queue_link_data(lnk, nxt_type_t, link);
+ *
+ * or starting from tail:
+ *
+ *  for (lnk = nxt_queue_last(queue);
+ *       lnk != nxt_queue_head(queue);
+ *       lnk = nxt_queue_next(lnk))
+ *  {
+ *      tp = nxt_queue_link_data(lnk, nxt_type_t, link);
+ */
+
+#define nxt_queue_first(queue)                                                \
+    (queue)->head.next
+
+
+#define nxt_queue_last(queue)                                                 \
+    (queue)->head.prev
+
+
+#define nxt_queue_head(queue)                                                 \
+    (&(queue)->head)
+
+
+#define nxt_queue_tail(queue)                                                 \
+    (&(queue)->head)
+
+
+#define nxt_queue_next(link)                                                  \
+    (link)->next
+
+
+#define nxt_queue_prev(link)                                                  \
+    (link)->prev
+
+
+#define nxt_queue_insert_head(queue, link)                                    \
+    do {                                                                      \
+        (link)->next = (queue)->head.next;                                    \
+        (link)->next->prev = (link);                                          \
+        (link)->prev = &(queue)->head;                                        \
+        (queue)->head.next = (link);                                          \
+    } while (0)
+
+
+#define nxt_queue_insert_tail(queue, link)                                    \
+    do {                                                                      \
+        (link)->prev = (queue)->head.prev;                                    \
+        (link)->prev->next = (link);                                          \
+        (link)->next = &(queue)->head;                                        \
+        (queue)->head.prev = (link);                                          \
+    } while (0)
+
+
+#define nxt_queue_insert_after(target, link)                                  \
+    do {                                                                      \
+        (link)->next = (target)->next;                                        \
+        (link)->next->prev = (link);                                          \
+        (link)->prev = (target);                                              \
+        (target)->next = (link);                                              \
+    } while (0)
+
+
+#define nxt_queue_insert_before(target, link)                                 \
+    do {                                                                      \
+        (link)->next = (target);                                              \
+        (link)->prev = (target)->prev;                                        \
+        (target)->prev = (link);                                              \
+        (link)->prev->next = (link);                                          \
+    } while (0)
+
+
+#if (NXT_DEBUG)
+
+#define nxt_queue_remove(link)                                                \
+    do {                                                                      \
+        (link)->next->prev = (link)->prev;                                    \
+        (link)->prev->next = (link)->next;                                    \
+        (link)->prev = NULL;                                                  \
+        (link)->next = NULL;                                                  \
+    } while (0)
+
+#else
+
+#define nxt_queue_remove(link)                                                \
+    do {                                                                      \
+        (link)->next->prev = (link)->prev;                                    \
+        (link)->prev->next = (link)->next;                                    \
+    } while (0)
+
+#endif
+
+
+/*
+ * Split the queue "queue" starting at the element "link",
+ * the "tail" is the new tail queue.
+ */
+
+#define nxt_queue_split(queue, link, tail)                                    \
+    do {                                                                      \
+        (tail)->head.prev = (queue)->head.prev;                               \
+        (tail)->head.prev->next = &(tail)->head;                              \
+        (tail)->head.next = (link);                                           \
+        (queue)->head.prev = (link)->prev;                                    \
+        (queue)->head.prev->next = &(queue)->head;                            \
+        (link)->prev = &(tail)->head;                                         \
+    } while (0)
+
+
+/* Truncate the queue "queue" starting at element "link". */
+
+#define nxt_queue_truncate(queue, link)                                       \
+    do {                                                                      \
+        (queue)->head.prev = (link)->prev;                                    \
+        (queue)->head.prev->next = &(queue)->head;                            \
+    } while (0)
+
+
+/* Add the queue "tail" to the queue "queue". */
+
+#define nxt_queue_add(queue, tail)                                            \
+    do {                                                                      \
+        (queue)->head.prev->next = (tail)->head.next;                         \
+        (tail)->head.next->prev = (queue)->head.prev;                         \
+        (queue)->head.prev = (tail)->head.prev;                               \
+        (queue)->head.prev->next = &(queue)->head;                            \
+    } while (0)
+
+
+#define nxt_queue_link_data(lnk, type, link)                                  \
+    nxt_container_of(lnk, type, link)
+
+
+NXT_EXPORT nxt_queue_link_t *nxt_queue_middle(nxt_queue_t *queue);
+NXT_EXPORT void nxt_queue_sort(nxt_queue_t *queue,
+    nxt_int_t (*compare)(const void *, const nxt_queue_link_t *,
+    const nxt_queue_link_t *), const void *data);
+
+
+#endif /* _NXT_QUEUE_H_INCLUDED_ */
diff --git a/nxt/nxt_rbtree.c b/nxt/nxt_rbtree.c
new file mode 100644 (file)
index 0000000..805efa2
--- /dev/null
@@ -0,0 +1,496 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_rbtree.h>
+
+
+/*
+ * The red-black tree code is based on the algorithm described in
+ * the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
+ */
+
+
+static void nxt_rbtree_insert_fixup(nxt_rbtree_node_t *node);
+static void nxt_rbtree_delete_fixup(nxt_rbtree_t *tree, nxt_rbtree_node_t *node);
+nxt_inline void nxt_rbtree_left_rotate(nxt_rbtree_node_t *node);
+nxt_inline void nxt_rbtree_right_rotate(nxt_rbtree_node_t *node);
+nxt_inline void nxt_rbtree_parent_relink(nxt_rbtree_node_t *subst,
+    nxt_rbtree_node_t *node);
+
+
+#define NXT_RBTREE_BLACK  0
+#define NXT_RBTREE_RED    1
+
+
+#define nxt_rbtree_set_callback_type(tree, type)                               \
+    (tree)->sentinel.spare = type
+
+#define nxt_rbtree_has_insertion_callback(tree)                                \
+    ((tree)->sentinel.spare != 0)
+
+#define nxt_rbtree_comparison_callback(tree)                                   \
+    ((nxt_rbtree_compare_t) (tree)->sentinel.right)
+
+
+void
+nxt_rbtree_init(nxt_rbtree_t *tree, nxt_rbtree_compare_t compare)
+{
+    /*
+     * The sentinel is used as a leaf node sentinel and as a tree root
+     * sentinel: it is a parent of a root node and the root node is
+     * the left child of the sentinel.  Combining two sentinels in one
+     * entry and the fact that the sentinel's left child is a root node
+     * simplifies nxt_rbtree_node_successor() and eliminates explicit
+     * root node test before or inside nxt_rbtree_min().
+     */
+
+    /* The root is empty. */
+    tree->sentinel.left = &tree->sentinel;
+
+    /*
+     * The sentinel's right child is never used so
+     * or comparison callback can be safely stored here.
+     */
+    tree->sentinel.right = (void *) compare;
+
+    /* The root and leaf sentinel must be black. */
+    tree->sentinel.color = NXT_RBTREE_BLACK;
+}
+
+
+void
+nxt_rbtree_insert(nxt_rbtree_t *tree, nxt_rbtree_part_t *part)
+{
+    nxt_rbtree_node_t     *node, *new_node, *sentinel, **child;
+    nxt_rbtree_compare_t  compare;
+
+    new_node = (nxt_rbtree_node_t *) part;
+
+    node = nxt_rbtree_root(tree);
+    sentinel = nxt_rbtree_sentinel(tree);
+
+    new_node->left = sentinel;
+    new_node->right = sentinel;
+    new_node->color = NXT_RBTREE_RED;
+
+    compare = (nxt_rbtree_compare_t) tree->sentinel.right;
+    child = &nxt_rbtree_root(tree);
+
+    while (*child != sentinel) {
+        node = *child;
+
+        nxt_prefetch(node->left);
+        nxt_prefetch(node->right);
+
+        child = (compare(new_node, node) < 0) ? &node->left : &node->right;
+    }
+
+    *child = new_node;
+    new_node->parent = node;
+
+    nxt_rbtree_insert_fixup(new_node);
+
+    node = nxt_rbtree_root(tree);
+    node->color = NXT_RBTREE_BLACK;
+}
+
+
+static void
+nxt_rbtree_insert_fixup(nxt_rbtree_node_t *node)
+{
+    nxt_rbtree_node_t  *parent, *grandparent, *uncle;
+
+    /*
+     * Prefetching parent nodes does not help here because they are
+     * already traversed during insertion.
+     */
+
+    for ( ;; ) {
+        parent = node->parent;
+
+        /*
+         * Testing whether a node is a tree root is not required here since
+         * a root node's parent is the sentinel and it is always black.
+         */
+        if (parent->color == NXT_RBTREE_BLACK) {
+            return;
+        }
+
+        grandparent = parent->parent;
+
+        if (parent == grandparent->left) {
+            uncle = grandparent->right;
+
+            if (uncle->color == NXT_RBTREE_BLACK) {
+
+                if (node == parent->right) {
+                    node = parent;
+                    nxt_rbtree_left_rotate(node);
+                }
+
+                parent = node->parent;
+                parent->color = NXT_RBTREE_BLACK;
+
+                grandparent = parent->parent;
+                grandparent->color = NXT_RBTREE_RED;
+                nxt_rbtree_right_rotate(grandparent);
+
+                continue;
+            }
+
+        } else {
+            uncle = grandparent->left;
+
+            if (uncle->color == NXT_RBTREE_BLACK) {
+
+                if (node == parent->left) {
+                    node = parent;
+                    nxt_rbtree_right_rotate(node);
+                }
+
+                parent = node->parent;
+                parent->color = NXT_RBTREE_BLACK;
+
+                grandparent = parent->parent;
+                grandparent->color = NXT_RBTREE_RED;
+                nxt_rbtree_left_rotate(grandparent);
+
+                continue;
+            }
+        }
+
+        uncle->color = NXT_RBTREE_BLACK;
+        parent->color = NXT_RBTREE_BLACK;
+        grandparent->color = NXT_RBTREE_RED;
+
+        node = grandparent;
+    }
+}
+
+
+nxt_rbtree_node_t *
+nxt_rbtree_find(nxt_rbtree_t *tree, nxt_rbtree_part_t *part)
+{
+    nxt_int_t             n;
+    nxt_rbtree_node_t     *node, *next, *sentinel;
+    nxt_rbtree_compare_t  compare;
+
+    node = (nxt_rbtree_node_t *) part;
+
+    next = nxt_rbtree_root(tree);
+    sentinel = nxt_rbtree_sentinel(tree);
+    compare = nxt_rbtree_comparison_callback(tree);
+
+    while (next != sentinel) {
+        nxt_prefetch(next->left);
+        nxt_prefetch(next->right);
+
+        n = compare(node, next);
+
+        if (n < 0) {
+            next = next->left;
+
+        } else if (n > 0) {
+            next = next->right;
+
+        } else {
+            return next;
+        }
+    }
+
+    return NULL;
+}
+
+
+nxt_rbtree_node_t *
+nxt_rbtree_find_less_or_equal(nxt_rbtree_t *tree, nxt_rbtree_part_t *part)
+{
+    nxt_int_t             n;
+    nxt_rbtree_node_t     *node, *retval, *next, *sentinel;
+    nxt_rbtree_compare_t  compare;
+
+    node = (nxt_rbtree_node_t *) part;
+
+    retval = NULL;
+    next = nxt_rbtree_root(tree);
+    sentinel = nxt_rbtree_sentinel(tree);
+    compare = nxt_rbtree_comparison_callback(tree);
+
+    while (next != sentinel) {
+        nxt_prefetch(next->left);
+        nxt_prefetch(next->right);
+
+        n = compare(node, next);
+
+        if (n < 0) {
+            next = next->left;
+
+        } else if (n > 0) {
+            retval = next;
+            next = next->right;
+
+        } else {
+            /* Exact match. */
+            return next;
+        }
+    }
+
+    return retval;
+}
+
+
+nxt_rbtree_node_t *
+nxt_rbtree_find_greater_or_equal(nxt_rbtree_t *tree, nxt_rbtree_part_t *part)
+{
+    nxt_int_t             n;
+    nxt_rbtree_node_t     *node, *retval, *next, *sentinel;
+    nxt_rbtree_compare_t  compare;
+
+    node = (nxt_rbtree_node_t *) part;
+
+    retval = NULL;
+    next = nxt_rbtree_root(tree);
+    sentinel = nxt_rbtree_sentinel(tree);
+    compare = nxt_rbtree_comparison_callback(tree);
+
+    while (next != sentinel) {
+        nxt_prefetch(next->left);
+        nxt_prefetch(next->right);
+
+        n = compare(node, next);
+
+        if (n < 0) {
+            retval = next;
+            next = next->left;
+
+        } else if (n > 0) {
+            next = next->right;
+
+        } else {
+            /* Exact match. */
+            return next;
+        }
+    }
+
+    return retval;
+}
+
+
+void
+nxt_rbtree_delete(nxt_rbtree_t *tree, nxt_rbtree_part_t *part)
+{
+    nxt_uint_t         color;
+    nxt_rbtree_node_t  *node, *sentinel, *subst, *child;
+
+    node = (nxt_rbtree_node_t *) part;
+
+    subst = node;
+    sentinel = nxt_rbtree_sentinel(tree);
+
+    if (node->left == sentinel) {
+        child = node->right;
+
+    } else if (node->right == sentinel) {
+        child = node->left;
+
+    } else {
+        subst = nxt_rbtree_branch_min(tree, node->right);
+        child = subst->right;
+    }
+
+    nxt_rbtree_parent_relink(child, subst);
+
+    color = subst->color;
+
+    if (subst != node) {
+        /* Move the subst node to the deleted node position in the tree. */
+
+        subst->color = node->color;
+
+        subst->left = node->left;
+        subst->left->parent = subst;
+
+        subst->right = node->right;
+        subst->right->parent = subst;
+
+        nxt_rbtree_parent_relink(subst, node);
+    }
+
+#if (NXT_DEBUG)
+    node->left = NULL;
+    node->right = NULL;
+    node->parent = NULL;
+#endif
+
+    if (color == NXT_RBTREE_BLACK) {
+        nxt_rbtree_delete_fixup(tree, child);
+    }
+}
+
+
+static void
+nxt_rbtree_delete_fixup(nxt_rbtree_t *tree, nxt_rbtree_node_t *node)
+{
+    nxt_rbtree_node_t  *parent, *sibling;
+
+    while (node != nxt_rbtree_root(tree) && node->color == NXT_RBTREE_BLACK) {
+        /*
+         * Prefetching parent nodes does not help here according
+         * to microbenchmarks.
+         */
+
+        parent = node->parent;
+
+        if (node == parent->left) {
+            sibling = parent->right;
+
+            if (sibling->color != NXT_RBTREE_BLACK) {
+
+                sibling->color = NXT_RBTREE_BLACK;
+                parent->color = NXT_RBTREE_RED;
+
+                nxt_rbtree_left_rotate(parent);
+
+                sibling = parent->right;
+            }
+
+            if (sibling->right->color == NXT_RBTREE_BLACK) {
+
+                sibling->color = NXT_RBTREE_RED;
+
+                if (sibling->left->color == NXT_RBTREE_BLACK) {
+                    node = parent;
+                    continue;
+                }
+
+                sibling->left->color = NXT_RBTREE_BLACK;
+
+                nxt_rbtree_right_rotate(sibling);
+                /*
+                 * If the node is the leaf sentinel then the right
+                 * rotate above changes its parent so a sibling below
+                 * becames the leaf sentinel as well and this causes
+                 * segmentation fault.  This is the reason why usual
+                 * red-black tree implementations with a leaf sentinel
+                 * which does not require to test leaf nodes at all
+                 * nevertheless test the leaf sentinel in the left and
+                 * right rotate procedures.  Since according to the
+                 * algorithm node->parent must not be changed by both
+                 * the left and right rotates above, it can be cached
+                 * in a local variable.  This not only eliminates the
+                 * sentinel test in nxt_rbtree_parent_relink() but also
+                 * decreases the code size because C forces to reload
+                 * non-restrict pointers.
+                 */
+                sibling = parent->right;
+            }
+
+            sibling->color = parent->color;
+            parent->color = NXT_RBTREE_BLACK;
+            sibling->right->color = NXT_RBTREE_BLACK;
+
+            nxt_rbtree_left_rotate(parent);
+
+            break;
+
+        } else {
+            sibling = parent->left;
+
+            if (sibling->color != NXT_RBTREE_BLACK) {
+
+                sibling->color = NXT_RBTREE_BLACK;
+                parent->color = NXT_RBTREE_RED;
+
+                nxt_rbtree_right_rotate(parent);
+
+                sibling = parent->left;
+            }
+
+            if (sibling->left->color == NXT_RBTREE_BLACK) {
+
+                sibling->color = NXT_RBTREE_RED;
+
+                if (sibling->right->color == NXT_RBTREE_BLACK) {
+                    node = parent;
+                    continue;
+                }
+
+                sibling->right->color = NXT_RBTREE_BLACK;
+
+                nxt_rbtree_left_rotate(sibling);
+
+                /* See the comment in the symmetric branch above. */
+                sibling = parent->left;
+            }
+
+            sibling->color = parent->color;
+            parent->color = NXT_RBTREE_BLACK;
+            sibling->left->color = NXT_RBTREE_BLACK;
+
+            nxt_rbtree_right_rotate(parent);
+
+            break;
+        }
+    }
+
+    node->color = NXT_RBTREE_BLACK;
+}
+
+
+nxt_inline void
+nxt_rbtree_left_rotate(nxt_rbtree_node_t *node)
+{
+    nxt_rbtree_node_t  *child;
+
+    child = node->right;
+    node->right = child->left;
+    child->left->parent = node;
+    child->left = node;
+
+    nxt_rbtree_parent_relink(child, node);
+
+    node->parent = child;
+}
+
+
+nxt_inline void
+nxt_rbtree_right_rotate(nxt_rbtree_node_t *node)
+{
+    nxt_rbtree_node_t  *child;
+
+    child = node->left;
+    node->left = child->right;
+    child->right->parent = node;
+    child->right = node;
+
+    nxt_rbtree_parent_relink(child, node);
+
+    node->parent = child;
+}
+
+
+/* Relink a parent from the node to the subst node. */
+
+nxt_inline void
+nxt_rbtree_parent_relink(nxt_rbtree_node_t *subst, nxt_rbtree_node_t *node)
+{
+    nxt_rbtree_node_t  *parent, **link;
+
+    parent = node->parent;
+    /*
+     * The leaf sentinel's parent can be safely changed here.
+     * See the comment in nxt_rbtree_delete_fixup() for details.
+     */
+    subst->parent = parent;
+    /*
+     * If the node's parent is the root sentinel it is safely changed
+     * because the root sentinel's left child is the tree root.
+     */
+    link = (node == parent->left) ? &parent->left : &parent->right;
+    *link = subst;
+}
diff --git a/nxt/nxt_rbtree.h b/nxt/nxt_rbtree.h
new file mode 100644 (file)
index 0000000..13e10d1
--- /dev/null
@@ -0,0 +1,115 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_RBTREE_H_INCLUDED_
+#define _NXT_RBTREE_H_INCLUDED_
+
+
+typedef struct nxt_rbtree_node_s  nxt_rbtree_node_t;
+
+struct nxt_rbtree_node_s {
+    nxt_rbtree_node_t         *left;
+    nxt_rbtree_node_t         *right;
+    nxt_rbtree_node_t         *parent;
+
+    uint8_t                  color;
+};
+
+
+typedef struct {
+    nxt_rbtree_node_t         *left;
+    nxt_rbtree_node_t         *right;
+    nxt_rbtree_node_t         *parent;
+} nxt_rbtree_part_t;
+
+
+#define NXT_RBTREE_NODE(node)                                                 \
+    nxt_rbtree_part_t         node;                                           \
+    uint8_t                  node##_color
+
+
+#define NXT_RBTREE_NODE_INIT  { NULL, NULL, NULL }, 0
+
+
+typedef struct {
+    nxt_rbtree_node_t         sentinel;
+} nxt_rbtree_t;
+
+
+typedef nxt_int_t (*nxt_rbtree_compare_t)(nxt_rbtree_node_t *node1,
+    nxt_rbtree_node_t *node2);
+
+
+#define nxt_rbtree_root(tree)                                                 \
+    ((tree)->sentinel.left)
+
+
+#define nxt_rbtree_sentinel(tree)                                             \
+    (&(tree)->sentinel)
+
+
+#define nxt_rbtree_is_empty(tree)                                             \
+    (nxt_rbtree_root(tree) == nxt_rbtree_sentinel(tree))
+
+
+#define nxt_rbtree_min(tree)                                                  \
+    nxt_rbtree_branch_min(tree, &(tree)->sentinel)
+
+
+nxt_inline nxt_rbtree_node_t *
+nxt_rbtree_branch_min(nxt_rbtree_t *tree, nxt_rbtree_node_t *node)
+{
+    while (node->left != nxt_rbtree_sentinel(tree)) {
+        node = node->left;
+    }
+
+    return node;
+}
+
+
+#define nxt_rbtree_is_there_successor(tree, node)                             \
+    ((node) != nxt_rbtree_sentinel(tree))
+
+
+nxt_inline nxt_rbtree_node_t *
+nxt_rbtree_node_successor(nxt_rbtree_t *tree, nxt_rbtree_node_t *node)
+{
+    nxt_rbtree_node_t  *parent;
+
+    if (node->right != nxt_rbtree_sentinel(tree)) {
+        return nxt_rbtree_branch_min(tree, node->right);
+    }
+
+    for ( ;; ) {
+        parent = node->parent;
+
+        /*
+         * Explicit test for a root node is not required here, because
+         * the root node is always the left child of the sentinel.
+         */
+        if (node == parent->left) {
+            return parent;
+        }
+
+        node = parent;
+    }
+}
+
+
+NXT_EXPORT void nxt_rbtree_init(nxt_rbtree_t *tree,
+    nxt_rbtree_compare_t compare);
+NXT_EXPORT void nxt_rbtree_insert(nxt_rbtree_t *tree, nxt_rbtree_part_t *node);
+NXT_EXPORT nxt_rbtree_node_t *nxt_rbtree_find(nxt_rbtree_t *tree,
+    nxt_rbtree_part_t *node);
+NXT_EXPORT nxt_rbtree_node_t *nxt_rbtree_find_less_or_equal(nxt_rbtree_t *tree,
+    nxt_rbtree_part_t *node);
+NXT_EXPORT nxt_rbtree_node_t
+    *nxt_rbtree_find_greater_or_equal(nxt_rbtree_t *tree,
+    nxt_rbtree_part_t *node);
+NXT_EXPORT void nxt_rbtree_delete(nxt_rbtree_t *tree, nxt_rbtree_part_t *node);
+
+
+#endif /* _NXT_RBTREE_H_INCLUDED_ */
diff --git a/nxt/nxt_stub.h b/nxt/nxt_stub.h
new file mode 100644 (file)
index 0000000..138cd04
--- /dev/null
@@ -0,0 +1,66 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_STUB_H_INCLUDED_
+#define _NXT_STUB_H_INCLUDED_
+
+
+#define nxt_max(val1, val2)                                                   \
+    ((val1 < val2) ? (val2) : (val1))
+
+#define nxt_min(val1, val2)                                                   \
+    ((val1 < val2) ? (val1) : (val2))
+
+#define nxt_lowcase(c)                                                        \
+    (u_char) ((c >= 'A' && c <= 'Z') ? c | 0x20 : c)
+
+#define nxt_strstr_eq(s1, s2)                                                 \
+    (((s1)->len == (s2)->len)                                                 \
+      && (memcmp((s1)->data, (s2)->data, (s1)->len) == 0))
+
+
+#define NXT_OK             0
+#define NXT_ERROR          (-1)
+#define NXT_AGAIN          (-2)
+#define NXT_DECLINED       (-3)
+#define NXT_DONE           (-4)
+
+
+typedef struct {
+    void           *(*alloc)(void *mem, size_t size);
+    void           *(*zalloc)(void *mem, size_t size);
+    void           *(*align)(void *mem, size_t alignment, size_t size);
+    void           *(*zalign)(void *mem, size_t alignment, size_t size);
+    void           (*free)(void *mem, void *p);
+    void           (*alert)(void *trace, const char *fmt, ...);
+    void nxt_cdecl  (*trace)(void *trace, const char *fmt, ...);
+} nxt_mem_proto_t;
+
+
+typedef struct {
+    size_t         len;
+    u_char         *data;
+} nxt_str_t;
+
+
+#define nxt_string(str)       { sizeof(str) - 1, (u_char *) str }
+#define nxt_string_zero(str)  { sizeof(str), (u_char *) str }
+#define nxt_null_string       { 0, NULL }
+
+
+#define nxt_thread_log_alert(...)
+#define nxt_thread_log_error(...)
+#define nxt_log_error(...)
+#define nxt_thread_log_debug(...)
+#define nxt_number_parse(a, b)      1
+
+#define NXT_DOUBLE_LEN   1024
+
+#include <unistd.h>
+#define nxt_pagesize()      getpagesize()
+
+
+#endif /* _NXT_STUB_H_INCLUDED_ */
diff --git a/nxt/nxt_types.h b/nxt/nxt_types.h
new file mode 100644 (file)
index 0000000..acf1328
--- /dev/null
@@ -0,0 +1,93 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_TYPES_H_INCLUDED_
+#define _NXT_TYPES_H_INCLUDED_
+
+
+/*
+ * off_t is 32 bit on Linux, Solaris and HP-UX by default.
+ * Must be before <sys/types.h>.
+ */
+#define _FILE_OFFSET_BITS  64
+
+/* u_char, u_int, int8_t, int32_t, int64_t, size_t, off_t. */
+#include <sys/types.h>
+#include <inttypes.h>
+
+
+#if (__LP64__)
+#define NXT_64BIT       1
+#define NXT_PTR_SIZE    8
+#else
+#define NXT_64BIT       0
+#define NXT_PTR_SIZE    4
+#endif
+
+
+/*
+ * nxt_int_t corresponds to the most efficient integer type, an architecture
+ * word.  It is usually the long type, however on Win64 the long is int32_t,
+ * so pointer size suits better.  nxt_int_t must be no less than int32_t.
+ */
+
+#if (__amd64__)
+/*
+ * AMD64 64-bit multiplication and division operations are slower and 64-bit
+ * instructions are longer.
+ */
+#define NXT_INT_T_SIZE  4
+typedef int            nxt_int_t;
+typedef u_int          nxt_uint_t;
+
+#else
+#define NXT_INT_T_SIZE  NXT_PTR_SIZE
+typedef intptr_t       nxt_int_t;
+typedef uintptr_t      nxt_uint_t;
+#endif
+
+
+typedef nxt_uint_t      nxt_bool_t;
+
+
+/*
+ * nxt_off_t corresponds to OS's off_t, a file offset type.  Although Linux,
+ * Solaris, and HP-UX define both off_t and off64_t, setting _FILE_OFFSET_BITS
+ * to 64 defines off_t as off64_t.
+ */
+#if (NXT_WINDOWS)
+/* Windows defines off_t as a 32-bit "long". */
+typedef __int64        nxt_off_t;
+
+#else
+typedef off_t          nxt_off_t;
+#endif
+
+
+/*
+ * nxt_time_t corresponds to OS's time_t, time in seconds.  nxt_time_t is
+ * a signed integer.  OS's time_t may be an integer or real-floating type,
+ * though it is usually a signed 32-bit or 64-bit integer depending on
+ * platform bits length.  There are however exceptions, e.g., time_t is:
+ *   32-bit on 64-bit NetBSD prior to 6.0 version;
+ *   64-bit on 32-bit NetBSD 6.0;
+ *   32-bit on 64-bit OpenBSD;
+ *   64-bit in Linux x32 ABI;
+ *   64-bit in 32-bit Visual Studio C++ 2005.
+ *
+ * Besides, QNX defines time_t as uint32_t.
+ */
+#if (NXT_QNX)
+/* Y2038 fix: "typedef int64_t  nxt_time_t". */
+typedef int32_t        nxt_time_t;
+
+#else
+/* Y2038, if time_t is 32-bit integer. */
+typedef time_t         nxt_time_t;
+#endif
+
+
+#endif /* _NXT_TYPES_H_INCLUDED_ */
diff --git a/nxt/nxt_unicode_lowcase.h b/nxt/nxt_unicode_lowcase.h
new file mode 100644 (file)
index 0000000..c868ad1
--- /dev/null
@@ -0,0 +1,1043 @@
+
+/*
+ * 26 128-bytes blocks, 521 pointers.
+ * 14920 bytes on 32-bit platforms, 17000 bytes on 64-bit platforms.
+ */
+
+#define NXT_UNICODE_MAX_LOWCASE  0x10427
+
+#define NXT_UNICODE_BLOCK_SIZE   128
+
+
+static const uint32_t  nxt_unicode_block_000[128]  nxt_aligned(64) = {
+    0x00000, 0x00001, 0x00002, 0x00003, 0x00004, 0x00005, 0x00006, 0x00007,
+    0x00008, 0x00009, 0x0000a, 0x0000b, 0x0000c, 0x0000d, 0x0000e, 0x0000f,
+    0x00010, 0x00011, 0x00012, 0x00013, 0x00014, 0x00015, 0x00016, 0x00017,
+    0x00018, 0x00019, 0x0001a, 0x0001b, 0x0001c, 0x0001d, 0x0001e, 0x0001f,
+    0x00020, 0x00021, 0x00022, 0x00023, 0x00024, 0x00025, 0x00026, 0x00027,
+    0x00028, 0x00029, 0x0002a, 0x0002b, 0x0002c, 0x0002d, 0x0002e, 0x0002f,
+    0x00030, 0x00031, 0x00032, 0x00033, 0x00034, 0x00035, 0x00036, 0x00037,
+    0x00038, 0x00039, 0x0003a, 0x0003b, 0x0003c, 0x0003d, 0x0003e, 0x0003f,
+    0x00040, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067,
+    0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f,
+    0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077,
+    0x00078, 0x00079, 0x0007a, 0x0005b, 0x0005c, 0x0005d, 0x0005e, 0x0005f,
+    0x00060, 0x00061, 0x00062, 0x00063, 0x00064, 0x00065, 0x00066, 0x00067,
+    0x00068, 0x00069, 0x0006a, 0x0006b, 0x0006c, 0x0006d, 0x0006e, 0x0006f,
+    0x00070, 0x00071, 0x00072, 0x00073, 0x00074, 0x00075, 0x00076, 0x00077,
+    0x00078, 0x00079, 0x0007a, 0x0007b, 0x0007c, 0x0007d, 0x0007e, 0x0007f,
+};
+
+
+static const uint32_t  nxt_unicode_block_001[128]  nxt_aligned(64) = {
+    0x00080, 0x00081, 0x00082, 0x00083, 0x00084, 0x00085, 0x00086, 0x00087,
+    0x00088, 0x00089, 0x0008a, 0x0008b, 0x0008c, 0x0008d, 0x0008e, 0x0008f,
+    0x00090, 0x00091, 0x00092, 0x00093, 0x00094, 0x00095, 0x00096, 0x00097,
+    0x00098, 0x00099, 0x0009a, 0x0009b, 0x0009c, 0x0009d, 0x0009e, 0x0009f,
+    0x000a0, 0x000a1, 0x000a2, 0x000a3, 0x000a4, 0x000a5, 0x000a6, 0x000a7,
+    0x000a8, 0x000a9, 0x000aa, 0x000ab, 0x000ac, 0x000ad, 0x000ae, 0x000af,
+    0x000b0, 0x000b1, 0x000b2, 0x000b3, 0x000b4, 0x003bc, 0x000b6, 0x000b7,
+    0x000b8, 0x000b9, 0x000ba, 0x000bb, 0x000bc, 0x000bd, 0x000be, 0x000bf,
+    0x000e0, 0x000e1, 0x000e2, 0x000e3, 0x000e4, 0x000e5, 0x000e6, 0x000e7,
+    0x000e8, 0x000e9, 0x000ea, 0x000eb, 0x000ec, 0x000ed, 0x000ee, 0x000ef,
+    0x000f0, 0x000f1, 0x000f2, 0x000f3, 0x000f4, 0x000f5, 0x000f6, 0x000d7,
+    0x000f8, 0x000f9, 0x000fa, 0x000fb, 0x000fc, 0x000fd, 0x000fe, 0x000df,
+    0x000e0, 0x000e1, 0x000e2, 0x000e3, 0x000e4, 0x000e5, 0x000e6, 0x000e7,
+    0x000e8, 0x000e9, 0x000ea, 0x000eb, 0x000ec, 0x000ed, 0x000ee, 0x000ef,
+    0x000f0, 0x000f1, 0x000f2, 0x000f3, 0x000f4, 0x000f5, 0x000f6, 0x000f7,
+    0x000f8, 0x000f9, 0x000fa, 0x000fb, 0x000fc, 0x000fd, 0x000fe, 0x000ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_002[128]  nxt_aligned(64) = {
+    0x00101, 0x00101, 0x00103, 0x00103, 0x00105, 0x00105, 0x00107, 0x00107,
+    0x00109, 0x00109, 0x0010b, 0x0010b, 0x0010d, 0x0010d, 0x0010f, 0x0010f,
+    0x00111, 0x00111, 0x00113, 0x00113, 0x00115, 0x00115, 0x00117, 0x00117,
+    0x00119, 0x00119, 0x0011b, 0x0011b, 0x0011d, 0x0011d, 0x0011f, 0x0011f,
+    0x00121, 0x00121, 0x00123, 0x00123, 0x00125, 0x00125, 0x00127, 0x00127,
+    0x00129, 0x00129, 0x0012b, 0x0012b, 0x0012d, 0x0012d, 0x0012f, 0x0012f,
+    0x00130, 0x00131, 0x00133, 0x00133, 0x00135, 0x00135, 0x00137, 0x00137,
+    0x00138, 0x0013a, 0x0013a, 0x0013c, 0x0013c, 0x0013e, 0x0013e, 0x00140,
+    0x00140, 0x00142, 0x00142, 0x00144, 0x00144, 0x00146, 0x00146, 0x00148,
+    0x00148, 0x00149, 0x0014b, 0x0014b, 0x0014d, 0x0014d, 0x0014f, 0x0014f,
+    0x00151, 0x00151, 0x00153, 0x00153, 0x00155, 0x00155, 0x00157, 0x00157,
+    0x00159, 0x00159, 0x0015b, 0x0015b, 0x0015d, 0x0015d, 0x0015f, 0x0015f,
+    0x00161, 0x00161, 0x00163, 0x00163, 0x00165, 0x00165, 0x00167, 0x00167,
+    0x00169, 0x00169, 0x0016b, 0x0016b, 0x0016d, 0x0016d, 0x0016f, 0x0016f,
+    0x00171, 0x00171, 0x00173, 0x00173, 0x00175, 0x00175, 0x00177, 0x00177,
+    0x000ff, 0x0017a, 0x0017a, 0x0017c, 0x0017c, 0x0017e, 0x0017e, 0x00073,
+};
+
+
+static const uint32_t  nxt_unicode_block_003[128]  nxt_aligned(64) = {
+    0x00180, 0x00253, 0x00183, 0x00183, 0x00185, 0x00185, 0x00254, 0x00188,
+    0x00188, 0x00256, 0x00257, 0x0018c, 0x0018c, 0x0018d, 0x001dd, 0x00259,
+    0x0025b, 0x00192, 0x00192, 0x00260, 0x00263, 0x00195, 0x00269, 0x00268,
+    0x00199, 0x00199, 0x0019a, 0x0019b, 0x0026f, 0x00272, 0x0019e, 0x00275,
+    0x001a1, 0x001a1, 0x001a3, 0x001a3, 0x001a5, 0x001a5, 0x00280, 0x001a8,
+    0x001a8, 0x00283, 0x001aa, 0x001ab, 0x001ad, 0x001ad, 0x00288, 0x001b0,
+    0x001b0, 0x0028a, 0x0028b, 0x001b4, 0x001b4, 0x001b6, 0x001b6, 0x00292,
+    0x001b9, 0x001b9, 0x001ba, 0x001bb, 0x001bd, 0x001bd, 0x001be, 0x001bf,
+    0x001c0, 0x001c1, 0x001c2, 0x001c3, 0x001c6, 0x001c6, 0x001c6, 0x001c9,
+    0x001c9, 0x001c9, 0x001cc, 0x001cc, 0x001cc, 0x001ce, 0x001ce, 0x001d0,
+    0x001d0, 0x001d2, 0x001d2, 0x001d4, 0x001d4, 0x001d6, 0x001d6, 0x001d8,
+    0x001d8, 0x001da, 0x001da, 0x001dc, 0x001dc, 0x001dd, 0x001df, 0x001df,
+    0x001e1, 0x001e1, 0x001e3, 0x001e3, 0x001e5, 0x001e5, 0x001e7, 0x001e7,
+    0x001e9, 0x001e9, 0x001eb, 0x001eb, 0x001ed, 0x001ed, 0x001ef, 0x001ef,
+    0x001f0, 0x001f3, 0x001f3, 0x001f3, 0x001f5, 0x001f5, 0x00195, 0x001bf,
+    0x001f9, 0x001f9, 0x001fb, 0x001fb, 0x001fd, 0x001fd, 0x001ff, 0x001ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_004[128]  nxt_aligned(64) = {
+    0x00201, 0x00201, 0x00203, 0x00203, 0x00205, 0x00205, 0x00207, 0x00207,
+    0x00209, 0x00209, 0x0020b, 0x0020b, 0x0020d, 0x0020d, 0x0020f, 0x0020f,
+    0x00211, 0x00211, 0x00213, 0x00213, 0x00215, 0x00215, 0x00217, 0x00217,
+    0x00219, 0x00219, 0x0021b, 0x0021b, 0x0021d, 0x0021d, 0x0021f, 0x0021f,
+    0x0019e, 0x00221, 0x00223, 0x00223, 0x00225, 0x00225, 0x00227, 0x00227,
+    0x00229, 0x00229, 0x0022b, 0x0022b, 0x0022d, 0x0022d, 0x0022f, 0x0022f,
+    0x00231, 0x00231, 0x00233, 0x00233, 0x00234, 0x00235, 0x00236, 0x00237,
+    0x00238, 0x00239, 0x02c65, 0x0023c, 0x0023c, 0x0019a, 0x02c66, 0x0023f,
+    0x00240, 0x00242, 0x00242, 0x00180, 0x00289, 0x0028c, 0x00247, 0x00247,
+    0x00249, 0x00249, 0x0024b, 0x0024b, 0x0024d, 0x0024d, 0x0024f, 0x0024f,
+    0x00250, 0x00251, 0x00252, 0x00253, 0x00254, 0x00255, 0x00256, 0x00257,
+    0x00258, 0x00259, 0x0025a, 0x0025b, 0x0025c, 0x0025d, 0x0025e, 0x0025f,
+    0x00260, 0x00261, 0x00262, 0x00263, 0x00264, 0x00265, 0x00266, 0x00267,
+    0x00268, 0x00269, 0x0026a, 0x0026b, 0x0026c, 0x0026d, 0x0026e, 0x0026f,
+    0x00270, 0x00271, 0x00272, 0x00273, 0x00274, 0x00275, 0x00276, 0x00277,
+    0x00278, 0x00279, 0x0027a, 0x0027b, 0x0027c, 0x0027d, 0x0027e, 0x0027f,
+};
+
+
+static const uint32_t  nxt_unicode_block_006[128]  nxt_aligned(64) = {
+    0x00300, 0x00301, 0x00302, 0x00303, 0x00304, 0x00305, 0x00306, 0x00307,
+    0x00308, 0x00309, 0x0030a, 0x0030b, 0x0030c, 0x0030d, 0x0030e, 0x0030f,
+    0x00310, 0x00311, 0x00312, 0x00313, 0x00314, 0x00315, 0x00316, 0x00317,
+    0x00318, 0x00319, 0x0031a, 0x0031b, 0x0031c, 0x0031d, 0x0031e, 0x0031f,
+    0x00320, 0x00321, 0x00322, 0x00323, 0x00324, 0x00325, 0x00326, 0x00327,
+    0x00328, 0x00329, 0x0032a, 0x0032b, 0x0032c, 0x0032d, 0x0032e, 0x0032f,
+    0x00330, 0x00331, 0x00332, 0x00333, 0x00334, 0x00335, 0x00336, 0x00337,
+    0x00338, 0x00339, 0x0033a, 0x0033b, 0x0033c, 0x0033d, 0x0033e, 0x0033f,
+    0x00340, 0x00341, 0x00342, 0x00343, 0x00344, 0x003b9, 0x00346, 0x00347,
+    0x00348, 0x00349, 0x0034a, 0x0034b, 0x0034c, 0x0034d, 0x0034e, 0x0034f,
+    0x00350, 0x00351, 0x00352, 0x00353, 0x00354, 0x00355, 0x00356, 0x00357,
+    0x00358, 0x00359, 0x0035a, 0x0035b, 0x0035c, 0x0035d, 0x0035e, 0x0035f,
+    0x00360, 0x00361, 0x00362, 0x00363, 0x00364, 0x00365, 0x00366, 0x00367,
+    0x00368, 0x00369, 0x0036a, 0x0036b, 0x0036c, 0x0036d, 0x0036e, 0x0036f,
+    0x00371, 0x00371, 0x00373, 0x00373, 0x00374, 0x00375, 0x00377, 0x00377,
+    0x00378, 0x00379, 0x0037a, 0x0037b, 0x0037c, 0x0037d, 0x0037e, 0x0037f,
+};
+
+
+static const uint32_t  nxt_unicode_block_007[128]  nxt_aligned(64) = {
+    0x00380, 0x00381, 0x00382, 0x00383, 0x00384, 0x00385, 0x003ac, 0x00387,
+    0x003ad, 0x003ae, 0x003af, 0x0038b, 0x003cc, 0x0038d, 0x003cd, 0x003ce,
+    0x00390, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7,
+    0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf,
+    0x003c0, 0x003c1, 0x003a2, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7,
+    0x003c8, 0x003c9, 0x003ca, 0x003cb, 0x003ac, 0x003ad, 0x003ae, 0x003af,
+    0x003b0, 0x003b1, 0x003b2, 0x003b3, 0x003b4, 0x003b5, 0x003b6, 0x003b7,
+    0x003b8, 0x003b9, 0x003ba, 0x003bb, 0x003bc, 0x003bd, 0x003be, 0x003bf,
+    0x003c0, 0x003c1, 0x003c3, 0x003c3, 0x003c4, 0x003c5, 0x003c6, 0x003c7,
+    0x003c8, 0x003c9, 0x003ca, 0x003cb, 0x003cc, 0x003cd, 0x003ce, 0x003d7,
+    0x003b2, 0x003b8, 0x003d2, 0x003d3, 0x003d4, 0x003c6, 0x003c0, 0x003d7,
+    0x003d9, 0x003d9, 0x003db, 0x003db, 0x003dd, 0x003dd, 0x003df, 0x003df,
+    0x003e1, 0x003e1, 0x003e3, 0x003e3, 0x003e5, 0x003e5, 0x003e7, 0x003e7,
+    0x003e9, 0x003e9, 0x003eb, 0x003eb, 0x003ed, 0x003ed, 0x003ef, 0x003ef,
+    0x003ba, 0x003c1, 0x003f2, 0x003f3, 0x003b8, 0x003b5, 0x003f6, 0x003f8,
+    0x003f8, 0x003f2, 0x003fb, 0x003fb, 0x003fc, 0x0037b, 0x0037c, 0x0037d,
+};
+
+
+static const uint32_t  nxt_unicode_block_008[128]  nxt_aligned(64) = {
+    0x00450, 0x00451, 0x00452, 0x00453, 0x00454, 0x00455, 0x00456, 0x00457,
+    0x00458, 0x00459, 0x0045a, 0x0045b, 0x0045c, 0x0045d, 0x0045e, 0x0045f,
+    0x00430, 0x00431, 0x00432, 0x00433, 0x00434, 0x00435, 0x00436, 0x00437,
+    0x00438, 0x00439, 0x0043a, 0x0043b, 0x0043c, 0x0043d, 0x0043e, 0x0043f,
+    0x00440, 0x00441, 0x00442, 0x00443, 0x00444, 0x00445, 0x00446, 0x00447,
+    0x00448, 0x00449, 0x0044a, 0x0044b, 0x0044c, 0x0044d, 0x0044e, 0x0044f,
+    0x00430, 0x00431, 0x00432, 0x00433, 0x00434, 0x00435, 0x00436, 0x00437,
+    0x00438, 0x00439, 0x0043a, 0x0043b, 0x0043c, 0x0043d, 0x0043e, 0x0043f,
+    0x00440, 0x00441, 0x00442, 0x00443, 0x00444, 0x00445, 0x00446, 0x00447,
+    0x00448, 0x00449, 0x0044a, 0x0044b, 0x0044c, 0x0044d, 0x0044e, 0x0044f,
+    0x00450, 0x00451, 0x00452, 0x00453, 0x00454, 0x00455, 0x00456, 0x00457,
+    0x00458, 0x00459, 0x0045a, 0x0045b, 0x0045c, 0x0045d, 0x0045e, 0x0045f,
+    0x00461, 0x00461, 0x00463, 0x00463, 0x00465, 0x00465, 0x00467, 0x00467,
+    0x00469, 0x00469, 0x0046b, 0x0046b, 0x0046d, 0x0046d, 0x0046f, 0x0046f,
+    0x00471, 0x00471, 0x00473, 0x00473, 0x00475, 0x00475, 0x00477, 0x00477,
+    0x00479, 0x00479, 0x0047b, 0x0047b, 0x0047d, 0x0047d, 0x0047f, 0x0047f,
+};
+
+
+static const uint32_t  nxt_unicode_block_009[128]  nxt_aligned(64) = {
+    0x00481, 0x00481, 0x00482, 0x00483, 0x00484, 0x00485, 0x00486, 0x00487,
+    0x00488, 0x00489, 0x0048b, 0x0048b, 0x0048d, 0x0048d, 0x0048f, 0x0048f,
+    0x00491, 0x00491, 0x00493, 0x00493, 0x00495, 0x00495, 0x00497, 0x00497,
+    0x00499, 0x00499, 0x0049b, 0x0049b, 0x0049d, 0x0049d, 0x0049f, 0x0049f,
+    0x004a1, 0x004a1, 0x004a3, 0x004a3, 0x004a5, 0x004a5, 0x004a7, 0x004a7,
+    0x004a9, 0x004a9, 0x004ab, 0x004ab, 0x004ad, 0x004ad, 0x004af, 0x004af,
+    0x004b1, 0x004b1, 0x004b3, 0x004b3, 0x004b5, 0x004b5, 0x004b7, 0x004b7,
+    0x004b9, 0x004b9, 0x004bb, 0x004bb, 0x004bd, 0x004bd, 0x004bf, 0x004bf,
+    0x004cf, 0x004c2, 0x004c2, 0x004c4, 0x004c4, 0x004c6, 0x004c6, 0x004c8,
+    0x004c8, 0x004ca, 0x004ca, 0x004cc, 0x004cc, 0x004ce, 0x004ce, 0x004cf,
+    0x004d1, 0x004d1, 0x004d3, 0x004d3, 0x004d5, 0x004d5, 0x004d7, 0x004d7,
+    0x004d9, 0x004d9, 0x004db, 0x004db, 0x004dd, 0x004dd, 0x004df, 0x004df,
+    0x004e1, 0x004e1, 0x004e3, 0x004e3, 0x004e5, 0x004e5, 0x004e7, 0x004e7,
+    0x004e9, 0x004e9, 0x004eb, 0x004eb, 0x004ed, 0x004ed, 0x004ef, 0x004ef,
+    0x004f1, 0x004f1, 0x004f3, 0x004f3, 0x004f5, 0x004f5, 0x004f7, 0x004f7,
+    0x004f9, 0x004f9, 0x004fb, 0x004fb, 0x004fd, 0x004fd, 0x004ff, 0x004ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_00a[128]  nxt_aligned(64) = {
+    0x00501, 0x00501, 0x00503, 0x00503, 0x00505, 0x00505, 0x00507, 0x00507,
+    0x00509, 0x00509, 0x0050b, 0x0050b, 0x0050d, 0x0050d, 0x0050f, 0x0050f,
+    0x00511, 0x00511, 0x00513, 0x00513, 0x00515, 0x00515, 0x00517, 0x00517,
+    0x00519, 0x00519, 0x0051b, 0x0051b, 0x0051d, 0x0051d, 0x0051f, 0x0051f,
+    0x00521, 0x00521, 0x00523, 0x00523, 0x00525, 0x00525, 0x00527, 0x00527,
+    0x00528, 0x00529, 0x0052a, 0x0052b, 0x0052c, 0x0052d, 0x0052e, 0x0052f,
+    0x00530, 0x00561, 0x00562, 0x00563, 0x00564, 0x00565, 0x00566, 0x00567,
+    0x00568, 0x00569, 0x0056a, 0x0056b, 0x0056c, 0x0056d, 0x0056e, 0x0056f,
+    0x00570, 0x00571, 0x00572, 0x00573, 0x00574, 0x00575, 0x00576, 0x00577,
+    0x00578, 0x00579, 0x0057a, 0x0057b, 0x0057c, 0x0057d, 0x0057e, 0x0057f,
+    0x00580, 0x00581, 0x00582, 0x00583, 0x00584, 0x00585, 0x00586, 0x00557,
+    0x00558, 0x00559, 0x0055a, 0x0055b, 0x0055c, 0x0055d, 0x0055e, 0x0055f,
+    0x00560, 0x00561, 0x00562, 0x00563, 0x00564, 0x00565, 0x00566, 0x00567,
+    0x00568, 0x00569, 0x0056a, 0x0056b, 0x0056c, 0x0056d, 0x0056e, 0x0056f,
+    0x00570, 0x00571, 0x00572, 0x00573, 0x00574, 0x00575, 0x00576, 0x00577,
+    0x00578, 0x00579, 0x0057a, 0x0057b, 0x0057c, 0x0057d, 0x0057e, 0x0057f,
+};
+
+
+static const uint32_t  nxt_unicode_block_021[128]  nxt_aligned(64) = {
+    0x01080, 0x01081, 0x01082, 0x01083, 0x01084, 0x01085, 0x01086, 0x01087,
+    0x01088, 0x01089, 0x0108a, 0x0108b, 0x0108c, 0x0108d, 0x0108e, 0x0108f,
+    0x01090, 0x01091, 0x01092, 0x01093, 0x01094, 0x01095, 0x01096, 0x01097,
+    0x01098, 0x01099, 0x0109a, 0x0109b, 0x0109c, 0x0109d, 0x0109e, 0x0109f,
+    0x02d00, 0x02d01, 0x02d02, 0x02d03, 0x02d04, 0x02d05, 0x02d06, 0x02d07,
+    0x02d08, 0x02d09, 0x02d0a, 0x02d0b, 0x02d0c, 0x02d0d, 0x02d0e, 0x02d0f,
+    0x02d10, 0x02d11, 0x02d12, 0x02d13, 0x02d14, 0x02d15, 0x02d16, 0x02d17,
+    0x02d18, 0x02d19, 0x02d1a, 0x02d1b, 0x02d1c, 0x02d1d, 0x02d1e, 0x02d1f,
+    0x02d20, 0x02d21, 0x02d22, 0x02d23, 0x02d24, 0x02d25, 0x010c6, 0x02d27,
+    0x010c8, 0x010c9, 0x010ca, 0x010cb, 0x010cc, 0x02d2d, 0x010ce, 0x010cf,
+    0x010d0, 0x010d1, 0x010d2, 0x010d3, 0x010d4, 0x010d5, 0x010d6, 0x010d7,
+    0x010d8, 0x010d9, 0x010da, 0x010db, 0x010dc, 0x010dd, 0x010de, 0x010df,
+    0x010e0, 0x010e1, 0x010e2, 0x010e3, 0x010e4, 0x010e5, 0x010e6, 0x010e7,
+    0x010e8, 0x010e9, 0x010ea, 0x010eb, 0x010ec, 0x010ed, 0x010ee, 0x010ef,
+    0x010f0, 0x010f1, 0x010f2, 0x010f3, 0x010f4, 0x010f5, 0x010f6, 0x010f7,
+    0x010f8, 0x010f9, 0x010fa, 0x010fb, 0x010fc, 0x010fd, 0x010fe, 0x010ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_03c[128]  nxt_aligned(64) = {
+    0x01e01, 0x01e01, 0x01e03, 0x01e03, 0x01e05, 0x01e05, 0x01e07, 0x01e07,
+    0x01e09, 0x01e09, 0x01e0b, 0x01e0b, 0x01e0d, 0x01e0d, 0x01e0f, 0x01e0f,
+    0x01e11, 0x01e11, 0x01e13, 0x01e13, 0x01e15, 0x01e15, 0x01e17, 0x01e17,
+    0x01e19, 0x01e19, 0x01e1b, 0x01e1b, 0x01e1d, 0x01e1d, 0x01e1f, 0x01e1f,
+    0x01e21, 0x01e21, 0x01e23, 0x01e23, 0x01e25, 0x01e25, 0x01e27, 0x01e27,
+    0x01e29, 0x01e29, 0x01e2b, 0x01e2b, 0x01e2d, 0x01e2d, 0x01e2f, 0x01e2f,
+    0x01e31, 0x01e31, 0x01e33, 0x01e33, 0x01e35, 0x01e35, 0x01e37, 0x01e37,
+    0x01e39, 0x01e39, 0x01e3b, 0x01e3b, 0x01e3d, 0x01e3d, 0x01e3f, 0x01e3f,
+    0x01e41, 0x01e41, 0x01e43, 0x01e43, 0x01e45, 0x01e45, 0x01e47, 0x01e47,
+    0x01e49, 0x01e49, 0x01e4b, 0x01e4b, 0x01e4d, 0x01e4d, 0x01e4f, 0x01e4f,
+    0x01e51, 0x01e51, 0x01e53, 0x01e53, 0x01e55, 0x01e55, 0x01e57, 0x01e57,
+    0x01e59, 0x01e59, 0x01e5b, 0x01e5b, 0x01e5d, 0x01e5d, 0x01e5f, 0x01e5f,
+    0x01e61, 0x01e61, 0x01e63, 0x01e63, 0x01e65, 0x01e65, 0x01e67, 0x01e67,
+    0x01e69, 0x01e69, 0x01e6b, 0x01e6b, 0x01e6d, 0x01e6d, 0x01e6f, 0x01e6f,
+    0x01e71, 0x01e71, 0x01e73, 0x01e73, 0x01e75, 0x01e75, 0x01e77, 0x01e77,
+    0x01e79, 0x01e79, 0x01e7b, 0x01e7b, 0x01e7d, 0x01e7d, 0x01e7f, 0x01e7f,
+};
+
+
+static const uint32_t  nxt_unicode_block_03d[128]  nxt_aligned(64) = {
+    0x01e81, 0x01e81, 0x01e83, 0x01e83, 0x01e85, 0x01e85, 0x01e87, 0x01e87,
+    0x01e89, 0x01e89, 0x01e8b, 0x01e8b, 0x01e8d, 0x01e8d, 0x01e8f, 0x01e8f,
+    0x01e91, 0x01e91, 0x01e93, 0x01e93, 0x01e95, 0x01e95, 0x01e96, 0x01e97,
+    0x01e98, 0x01e99, 0x01e9a, 0x01e61, 0x01e9c, 0x01e9d, 0x000df, 0x01e9f,
+    0x01ea1, 0x01ea1, 0x01ea3, 0x01ea3, 0x01ea5, 0x01ea5, 0x01ea7, 0x01ea7,
+    0x01ea9, 0x01ea9, 0x01eab, 0x01eab, 0x01ead, 0x01ead, 0x01eaf, 0x01eaf,
+    0x01eb1, 0x01eb1, 0x01eb3, 0x01eb3, 0x01eb5, 0x01eb5, 0x01eb7, 0x01eb7,
+    0x01eb9, 0x01eb9, 0x01ebb, 0x01ebb, 0x01ebd, 0x01ebd, 0x01ebf, 0x01ebf,
+    0x01ec1, 0x01ec1, 0x01ec3, 0x01ec3, 0x01ec5, 0x01ec5, 0x01ec7, 0x01ec7,
+    0x01ec9, 0x01ec9, 0x01ecb, 0x01ecb, 0x01ecd, 0x01ecd, 0x01ecf, 0x01ecf,
+    0x01ed1, 0x01ed1, 0x01ed3, 0x01ed3, 0x01ed5, 0x01ed5, 0x01ed7, 0x01ed7,
+    0x01ed9, 0x01ed9, 0x01edb, 0x01edb, 0x01edd, 0x01edd, 0x01edf, 0x01edf,
+    0x01ee1, 0x01ee1, 0x01ee3, 0x01ee3, 0x01ee5, 0x01ee5, 0x01ee7, 0x01ee7,
+    0x01ee9, 0x01ee9, 0x01eeb, 0x01eeb, 0x01eed, 0x01eed, 0x01eef, 0x01eef,
+    0x01ef1, 0x01ef1, 0x01ef3, 0x01ef3, 0x01ef5, 0x01ef5, 0x01ef7, 0x01ef7,
+    0x01ef9, 0x01ef9, 0x01efb, 0x01efb, 0x01efd, 0x01efd, 0x01eff, 0x01eff,
+};
+
+
+static const uint32_t  nxt_unicode_block_03e[128]  nxt_aligned(64) = {
+    0x01f00, 0x01f01, 0x01f02, 0x01f03, 0x01f04, 0x01f05, 0x01f06, 0x01f07,
+    0x01f00, 0x01f01, 0x01f02, 0x01f03, 0x01f04, 0x01f05, 0x01f06, 0x01f07,
+    0x01f10, 0x01f11, 0x01f12, 0x01f13, 0x01f14, 0x01f15, 0x01f16, 0x01f17,
+    0x01f10, 0x01f11, 0x01f12, 0x01f13, 0x01f14, 0x01f15, 0x01f1e, 0x01f1f,
+    0x01f20, 0x01f21, 0x01f22, 0x01f23, 0x01f24, 0x01f25, 0x01f26, 0x01f27,
+    0x01f20, 0x01f21, 0x01f22, 0x01f23, 0x01f24, 0x01f25, 0x01f26, 0x01f27,
+    0x01f30, 0x01f31, 0x01f32, 0x01f33, 0x01f34, 0x01f35, 0x01f36, 0x01f37,
+    0x01f30, 0x01f31, 0x01f32, 0x01f33, 0x01f34, 0x01f35, 0x01f36, 0x01f37,
+    0x01f40, 0x01f41, 0x01f42, 0x01f43, 0x01f44, 0x01f45, 0x01f46, 0x01f47,
+    0x01f40, 0x01f41, 0x01f42, 0x01f43, 0x01f44, 0x01f45, 0x01f4e, 0x01f4f,
+    0x01f50, 0x01f51, 0x01f52, 0x01f53, 0x01f54, 0x01f55, 0x01f56, 0x01f57,
+    0x01f58, 0x01f51, 0x01f5a, 0x01f53, 0x01f5c, 0x01f55, 0x01f5e, 0x01f57,
+    0x01f60, 0x01f61, 0x01f62, 0x01f63, 0x01f64, 0x01f65, 0x01f66, 0x01f67,
+    0x01f60, 0x01f61, 0x01f62, 0x01f63, 0x01f64, 0x01f65, 0x01f66, 0x01f67,
+    0x01f70, 0x01f71, 0x01f72, 0x01f73, 0x01f74, 0x01f75, 0x01f76, 0x01f77,
+    0x01f78, 0x01f79, 0x01f7a, 0x01f7b, 0x01f7c, 0x01f7d, 0x01f7e, 0x01f7f,
+};
+
+
+static const uint32_t  nxt_unicode_block_03f[128]  nxt_aligned(64) = {
+    0x01f80, 0x01f81, 0x01f82, 0x01f83, 0x01f84, 0x01f85, 0x01f86, 0x01f87,
+    0x01f80, 0x01f81, 0x01f82, 0x01f83, 0x01f84, 0x01f85, 0x01f86, 0x01f87,
+    0x01f90, 0x01f91, 0x01f92, 0x01f93, 0x01f94, 0x01f95, 0x01f96, 0x01f97,
+    0x01f90, 0x01f91, 0x01f92, 0x01f93, 0x01f94, 0x01f95, 0x01f96, 0x01f97,
+    0x01fa0, 0x01fa1, 0x01fa2, 0x01fa3, 0x01fa4, 0x01fa5, 0x01fa6, 0x01fa7,
+    0x01fa0, 0x01fa1, 0x01fa2, 0x01fa3, 0x01fa4, 0x01fa5, 0x01fa6, 0x01fa7,
+    0x01fb0, 0x01fb1, 0x01fb2, 0x01fb3, 0x01fb4, 0x01fb5, 0x01fb6, 0x01fb7,
+    0x01fb0, 0x01fb1, 0x01f70, 0x01f71, 0x01fb3, 0x01fbd, 0x003b9, 0x01fbf,
+    0x01fc0, 0x01fc1, 0x01fc2, 0x01fc3, 0x01fc4, 0x01fc5, 0x01fc6, 0x01fc7,
+    0x01f72, 0x01f73, 0x01f74, 0x01f75, 0x01fc3, 0x01fcd, 0x01fce, 0x01fcf,
+    0x01fd0, 0x01fd1, 0x01fd2, 0x01fd3, 0x01fd4, 0x01fd5, 0x01fd6, 0x01fd7,
+    0x01fd0, 0x01fd1, 0x01f76, 0x01f77, 0x01fdc, 0x01fdd, 0x01fde, 0x01fdf,
+    0x01fe0, 0x01fe1, 0x01fe2, 0x01fe3, 0x01fe4, 0x01fe5, 0x01fe6, 0x01fe7,
+    0x01fe0, 0x01fe1, 0x01f7a, 0x01f7b, 0x01fe5, 0x01fed, 0x01fee, 0x01fef,
+    0x01ff0, 0x01ff1, 0x01ff2, 0x01ff3, 0x01ff4, 0x01ff5, 0x01ff6, 0x01ff7,
+    0x01f78, 0x01f79, 0x01f7c, 0x01f7d, 0x01ff3, 0x01ffd, 0x01ffe, 0x01fff,
+};
+
+
+static const uint32_t  nxt_unicode_block_042[128]  nxt_aligned(64) = {
+    0x02100, 0x02101, 0x02102, 0x02103, 0x02104, 0x02105, 0x02106, 0x02107,
+    0x02108, 0x02109, 0x0210a, 0x0210b, 0x0210c, 0x0210d, 0x0210e, 0x0210f,
+    0x02110, 0x02111, 0x02112, 0x02113, 0x02114, 0x02115, 0x02116, 0x02117,
+    0x02118, 0x02119, 0x0211a, 0x0211b, 0x0211c, 0x0211d, 0x0211e, 0x0211f,
+    0x02120, 0x02121, 0x02122, 0x02123, 0x02124, 0x02125, 0x003c9, 0x02127,
+    0x02128, 0x02129, 0x0006b, 0x000e5, 0x0212c, 0x0212d, 0x0212e, 0x0212f,
+    0x02130, 0x02131, 0x0214e, 0x02133, 0x02134, 0x02135, 0x02136, 0x02137,
+    0x02138, 0x02139, 0x0213a, 0x0213b, 0x0213c, 0x0213d, 0x0213e, 0x0213f,
+    0x02140, 0x02141, 0x02142, 0x02143, 0x02144, 0x02145, 0x02146, 0x02147,
+    0x02148, 0x02149, 0x0214a, 0x0214b, 0x0214c, 0x0214d, 0x0214e, 0x0214f,
+    0x02150, 0x02151, 0x02152, 0x02153, 0x02154, 0x02155, 0x02156, 0x02157,
+    0x02158, 0x02159, 0x0215a, 0x0215b, 0x0215c, 0x0215d, 0x0215e, 0x0215f,
+    0x02170, 0x02171, 0x02172, 0x02173, 0x02174, 0x02175, 0x02176, 0x02177,
+    0x02178, 0x02179, 0x0217a, 0x0217b, 0x0217c, 0x0217d, 0x0217e, 0x0217f,
+    0x02170, 0x02171, 0x02172, 0x02173, 0x02174, 0x02175, 0x02176, 0x02177,
+    0x02178, 0x02179, 0x0217a, 0x0217b, 0x0217c, 0x0217d, 0x0217e, 0x0217f,
+};
+
+
+static const uint32_t  nxt_unicode_block_043[128]  nxt_aligned(64) = {
+    0x02180, 0x02181, 0x02182, 0x02184, 0x02184, 0x02185, 0x02186, 0x02187,
+    0x02188, 0x02189, 0x0218a, 0x0218b, 0x0218c, 0x0218d, 0x0218e, 0x0218f,
+    0x02190, 0x02191, 0x02192, 0x02193, 0x02194, 0x02195, 0x02196, 0x02197,
+    0x02198, 0x02199, 0x0219a, 0x0219b, 0x0219c, 0x0219d, 0x0219e, 0x0219f,
+    0x021a0, 0x021a1, 0x021a2, 0x021a3, 0x021a4, 0x021a5, 0x021a6, 0x021a7,
+    0x021a8, 0x021a9, 0x021aa, 0x021ab, 0x021ac, 0x021ad, 0x021ae, 0x021af,
+    0x021b0, 0x021b1, 0x021b2, 0x021b3, 0x021b4, 0x021b5, 0x021b6, 0x021b7,
+    0x021b8, 0x021b9, 0x021ba, 0x021bb, 0x021bc, 0x021bd, 0x021be, 0x021bf,
+    0x021c0, 0x021c1, 0x021c2, 0x021c3, 0x021c4, 0x021c5, 0x021c6, 0x021c7,
+    0x021c8, 0x021c9, 0x021ca, 0x021cb, 0x021cc, 0x021cd, 0x021ce, 0x021cf,
+    0x021d0, 0x021d1, 0x021d2, 0x021d3, 0x021d4, 0x021d5, 0x021d6, 0x021d7,
+    0x021d8, 0x021d9, 0x021da, 0x021db, 0x021dc, 0x021dd, 0x021de, 0x021df,
+    0x021e0, 0x021e1, 0x021e2, 0x021e3, 0x021e4, 0x021e5, 0x021e6, 0x021e7,
+    0x021e8, 0x021e9, 0x021ea, 0x021eb, 0x021ec, 0x021ed, 0x021ee, 0x021ef,
+    0x021f0, 0x021f1, 0x021f2, 0x021f3, 0x021f4, 0x021f5, 0x021f6, 0x021f7,
+    0x021f8, 0x021f9, 0x021fa, 0x021fb, 0x021fc, 0x021fd, 0x021fe, 0x021ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_049[128]  nxt_aligned(64) = {
+    0x02480, 0x02481, 0x02482, 0x02483, 0x02484, 0x02485, 0x02486, 0x02487,
+    0x02488, 0x02489, 0x0248a, 0x0248b, 0x0248c, 0x0248d, 0x0248e, 0x0248f,
+    0x02490, 0x02491, 0x02492, 0x02493, 0x02494, 0x02495, 0x02496, 0x02497,
+    0x02498, 0x02499, 0x0249a, 0x0249b, 0x0249c, 0x0249d, 0x0249e, 0x0249f,
+    0x024a0, 0x024a1, 0x024a2, 0x024a3, 0x024a4, 0x024a5, 0x024a6, 0x024a7,
+    0x024a8, 0x024a9, 0x024aa, 0x024ab, 0x024ac, 0x024ad, 0x024ae, 0x024af,
+    0x024b0, 0x024b1, 0x024b2, 0x024b3, 0x024b4, 0x024b5, 0x024d0, 0x024d1,
+    0x024d2, 0x024d3, 0x024d4, 0x024d5, 0x024d6, 0x024d7, 0x024d8, 0x024d9,
+    0x024da, 0x024db, 0x024dc, 0x024dd, 0x024de, 0x024df, 0x024e0, 0x024e1,
+    0x024e2, 0x024e3, 0x024e4, 0x024e5, 0x024e6, 0x024e7, 0x024e8, 0x024e9,
+    0x024d0, 0x024d1, 0x024d2, 0x024d3, 0x024d4, 0x024d5, 0x024d6, 0x024d7,
+    0x024d8, 0x024d9, 0x024da, 0x024db, 0x024dc, 0x024dd, 0x024de, 0x024df,
+    0x024e0, 0x024e1, 0x024e2, 0x024e3, 0x024e4, 0x024e5, 0x024e6, 0x024e7,
+    0x024e8, 0x024e9, 0x024ea, 0x024eb, 0x024ec, 0x024ed, 0x024ee, 0x024ef,
+    0x024f0, 0x024f1, 0x024f2, 0x024f3, 0x024f4, 0x024f5, 0x024f6, 0x024f7,
+    0x024f8, 0x024f9, 0x024fa, 0x024fb, 0x024fc, 0x024fd, 0x024fe, 0x024ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_058[128]  nxt_aligned(64) = {
+    0x02c30, 0x02c31, 0x02c32, 0x02c33, 0x02c34, 0x02c35, 0x02c36, 0x02c37,
+    0x02c38, 0x02c39, 0x02c3a, 0x02c3b, 0x02c3c, 0x02c3d, 0x02c3e, 0x02c3f,
+    0x02c40, 0x02c41, 0x02c42, 0x02c43, 0x02c44, 0x02c45, 0x02c46, 0x02c47,
+    0x02c48, 0x02c49, 0x02c4a, 0x02c4b, 0x02c4c, 0x02c4d, 0x02c4e, 0x02c4f,
+    0x02c50, 0x02c51, 0x02c52, 0x02c53, 0x02c54, 0x02c55, 0x02c56, 0x02c57,
+    0x02c58, 0x02c59, 0x02c5a, 0x02c5b, 0x02c5c, 0x02c5d, 0x02c5e, 0x02c2f,
+    0x02c30, 0x02c31, 0x02c32, 0x02c33, 0x02c34, 0x02c35, 0x02c36, 0x02c37,
+    0x02c38, 0x02c39, 0x02c3a, 0x02c3b, 0x02c3c, 0x02c3d, 0x02c3e, 0x02c3f,
+    0x02c40, 0x02c41, 0x02c42, 0x02c43, 0x02c44, 0x02c45, 0x02c46, 0x02c47,
+    0x02c48, 0x02c49, 0x02c4a, 0x02c4b, 0x02c4c, 0x02c4d, 0x02c4e, 0x02c4f,
+    0x02c50, 0x02c51, 0x02c52, 0x02c53, 0x02c54, 0x02c55, 0x02c56, 0x02c57,
+    0x02c58, 0x02c59, 0x02c5a, 0x02c5b, 0x02c5c, 0x02c5d, 0x02c5e, 0x02c5f,
+    0x02c61, 0x02c61, 0x0026b, 0x01d7d, 0x0027d, 0x02c65, 0x02c66, 0x02c68,
+    0x02c68, 0x02c6a, 0x02c6a, 0x02c6c, 0x02c6c, 0x00251, 0x00271, 0x00250,
+    0x00252, 0x02c71, 0x02c73, 0x02c73, 0x02c74, 0x02c76, 0x02c76, 0x02c77,
+    0x02c78, 0x02c79, 0x02c7a, 0x02c7b, 0x02c7c, 0x02c7d, 0x0023f, 0x00240,
+};
+
+
+static const uint32_t  nxt_unicode_block_059[128]  nxt_aligned(64) = {
+    0x02c81, 0x02c81, 0x02c83, 0x02c83, 0x02c85, 0x02c85, 0x02c87, 0x02c87,
+    0x02c89, 0x02c89, 0x02c8b, 0x02c8b, 0x02c8d, 0x02c8d, 0x02c8f, 0x02c8f,
+    0x02c91, 0x02c91, 0x02c93, 0x02c93, 0x02c95, 0x02c95, 0x02c97, 0x02c97,
+    0x02c99, 0x02c99, 0x02c9b, 0x02c9b, 0x02c9d, 0x02c9d, 0x02c9f, 0x02c9f,
+    0x02ca1, 0x02ca1, 0x02ca3, 0x02ca3, 0x02ca5, 0x02ca5, 0x02ca7, 0x02ca7,
+    0x02ca9, 0x02ca9, 0x02cab, 0x02cab, 0x02cad, 0x02cad, 0x02caf, 0x02caf,
+    0x02cb1, 0x02cb1, 0x02cb3, 0x02cb3, 0x02cb5, 0x02cb5, 0x02cb7, 0x02cb7,
+    0x02cb9, 0x02cb9, 0x02cbb, 0x02cbb, 0x02cbd, 0x02cbd, 0x02cbf, 0x02cbf,
+    0x02cc1, 0x02cc1, 0x02cc3, 0x02cc3, 0x02cc5, 0x02cc5, 0x02cc7, 0x02cc7,
+    0x02cc9, 0x02cc9, 0x02ccb, 0x02ccb, 0x02ccd, 0x02ccd, 0x02ccf, 0x02ccf,
+    0x02cd1, 0x02cd1, 0x02cd3, 0x02cd3, 0x02cd5, 0x02cd5, 0x02cd7, 0x02cd7,
+    0x02cd9, 0x02cd9, 0x02cdb, 0x02cdb, 0x02cdd, 0x02cdd, 0x02cdf, 0x02cdf,
+    0x02ce1, 0x02ce1, 0x02ce3, 0x02ce3, 0x02ce4, 0x02ce5, 0x02ce6, 0x02ce7,
+    0x02ce8, 0x02ce9, 0x02cea, 0x02cec, 0x02cec, 0x02cee, 0x02cee, 0x02cef,
+    0x02cf0, 0x02cf1, 0x02cf3, 0x02cf3, 0x02cf4, 0x02cf5, 0x02cf6, 0x02cf7,
+    0x02cf8, 0x02cf9, 0x02cfa, 0x02cfb, 0x02cfc, 0x02cfd, 0x02cfe, 0x02cff,
+};
+
+
+static const uint32_t  nxt_unicode_block_14c[128]  nxt_aligned(64) = {
+    0x0a600, 0x0a601, 0x0a602, 0x0a603, 0x0a604, 0x0a605, 0x0a606, 0x0a607,
+    0x0a608, 0x0a609, 0x0a60a, 0x0a60b, 0x0a60c, 0x0a60d, 0x0a60e, 0x0a60f,
+    0x0a610, 0x0a611, 0x0a612, 0x0a613, 0x0a614, 0x0a615, 0x0a616, 0x0a617,
+    0x0a618, 0x0a619, 0x0a61a, 0x0a61b, 0x0a61c, 0x0a61d, 0x0a61e, 0x0a61f,
+    0x0a620, 0x0a621, 0x0a622, 0x0a623, 0x0a624, 0x0a625, 0x0a626, 0x0a627,
+    0x0a628, 0x0a629, 0x0a62a, 0x0a62b, 0x0a62c, 0x0a62d, 0x0a62e, 0x0a62f,
+    0x0a630, 0x0a631, 0x0a632, 0x0a633, 0x0a634, 0x0a635, 0x0a636, 0x0a637,
+    0x0a638, 0x0a639, 0x0a63a, 0x0a63b, 0x0a63c, 0x0a63d, 0x0a63e, 0x0a63f,
+    0x0a641, 0x0a641, 0x0a643, 0x0a643, 0x0a645, 0x0a645, 0x0a647, 0x0a647,
+    0x0a649, 0x0a649, 0x0a64b, 0x0a64b, 0x0a64d, 0x0a64d, 0x0a64f, 0x0a64f,
+    0x0a651, 0x0a651, 0x0a653, 0x0a653, 0x0a655, 0x0a655, 0x0a657, 0x0a657,
+    0x0a659, 0x0a659, 0x0a65b, 0x0a65b, 0x0a65d, 0x0a65d, 0x0a65f, 0x0a65f,
+    0x0a661, 0x0a661, 0x0a663, 0x0a663, 0x0a665, 0x0a665, 0x0a667, 0x0a667,
+    0x0a669, 0x0a669, 0x0a66b, 0x0a66b, 0x0a66d, 0x0a66d, 0x0a66e, 0x0a66f,
+    0x0a670, 0x0a671, 0x0a672, 0x0a673, 0x0a674, 0x0a675, 0x0a676, 0x0a677,
+    0x0a678, 0x0a679, 0x0a67a, 0x0a67b, 0x0a67c, 0x0a67d, 0x0a67e, 0x0a67f,
+};
+
+
+static const uint32_t  nxt_unicode_block_14d[128]  nxt_aligned(64) = {
+    0x0a681, 0x0a681, 0x0a683, 0x0a683, 0x0a685, 0x0a685, 0x0a687, 0x0a687,
+    0x0a689, 0x0a689, 0x0a68b, 0x0a68b, 0x0a68d, 0x0a68d, 0x0a68f, 0x0a68f,
+    0x0a691, 0x0a691, 0x0a693, 0x0a693, 0x0a695, 0x0a695, 0x0a697, 0x0a697,
+    0x0a698, 0x0a699, 0x0a69a, 0x0a69b, 0x0a69c, 0x0a69d, 0x0a69e, 0x0a69f,
+    0x0a6a0, 0x0a6a1, 0x0a6a2, 0x0a6a3, 0x0a6a4, 0x0a6a5, 0x0a6a6, 0x0a6a7,
+    0x0a6a8, 0x0a6a9, 0x0a6aa, 0x0a6ab, 0x0a6ac, 0x0a6ad, 0x0a6ae, 0x0a6af,
+    0x0a6b0, 0x0a6b1, 0x0a6b2, 0x0a6b3, 0x0a6b4, 0x0a6b5, 0x0a6b6, 0x0a6b7,
+    0x0a6b8, 0x0a6b9, 0x0a6ba, 0x0a6bb, 0x0a6bc, 0x0a6bd, 0x0a6be, 0x0a6bf,
+    0x0a6c0, 0x0a6c1, 0x0a6c2, 0x0a6c3, 0x0a6c4, 0x0a6c5, 0x0a6c6, 0x0a6c7,
+    0x0a6c8, 0x0a6c9, 0x0a6ca, 0x0a6cb, 0x0a6cc, 0x0a6cd, 0x0a6ce, 0x0a6cf,
+    0x0a6d0, 0x0a6d1, 0x0a6d2, 0x0a6d3, 0x0a6d4, 0x0a6d5, 0x0a6d6, 0x0a6d7,
+    0x0a6d8, 0x0a6d9, 0x0a6da, 0x0a6db, 0x0a6dc, 0x0a6dd, 0x0a6de, 0x0a6df,
+    0x0a6e0, 0x0a6e1, 0x0a6e2, 0x0a6e3, 0x0a6e4, 0x0a6e5, 0x0a6e6, 0x0a6e7,
+    0x0a6e8, 0x0a6e9, 0x0a6ea, 0x0a6eb, 0x0a6ec, 0x0a6ed, 0x0a6ee, 0x0a6ef,
+    0x0a6f0, 0x0a6f1, 0x0a6f2, 0x0a6f3, 0x0a6f4, 0x0a6f5, 0x0a6f6, 0x0a6f7,
+    0x0a6f8, 0x0a6f9, 0x0a6fa, 0x0a6fb, 0x0a6fc, 0x0a6fd, 0x0a6fe, 0x0a6ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_14e[128]  nxt_aligned(64) = {
+    0x0a700, 0x0a701, 0x0a702, 0x0a703, 0x0a704, 0x0a705, 0x0a706, 0x0a707,
+    0x0a708, 0x0a709, 0x0a70a, 0x0a70b, 0x0a70c, 0x0a70d, 0x0a70e, 0x0a70f,
+    0x0a710, 0x0a711, 0x0a712, 0x0a713, 0x0a714, 0x0a715, 0x0a716, 0x0a717,
+    0x0a718, 0x0a719, 0x0a71a, 0x0a71b, 0x0a71c, 0x0a71d, 0x0a71e, 0x0a71f,
+    0x0a720, 0x0a721, 0x0a723, 0x0a723, 0x0a725, 0x0a725, 0x0a727, 0x0a727,
+    0x0a729, 0x0a729, 0x0a72b, 0x0a72b, 0x0a72d, 0x0a72d, 0x0a72f, 0x0a72f,
+    0x0a730, 0x0a731, 0x0a733, 0x0a733, 0x0a735, 0x0a735, 0x0a737, 0x0a737,
+    0x0a739, 0x0a739, 0x0a73b, 0x0a73b, 0x0a73d, 0x0a73d, 0x0a73f, 0x0a73f,
+    0x0a741, 0x0a741, 0x0a743, 0x0a743, 0x0a745, 0x0a745, 0x0a747, 0x0a747,
+    0x0a749, 0x0a749, 0x0a74b, 0x0a74b, 0x0a74d, 0x0a74d, 0x0a74f, 0x0a74f,
+    0x0a751, 0x0a751, 0x0a753, 0x0a753, 0x0a755, 0x0a755, 0x0a757, 0x0a757,
+    0x0a759, 0x0a759, 0x0a75b, 0x0a75b, 0x0a75d, 0x0a75d, 0x0a75f, 0x0a75f,
+    0x0a761, 0x0a761, 0x0a763, 0x0a763, 0x0a765, 0x0a765, 0x0a767, 0x0a767,
+    0x0a769, 0x0a769, 0x0a76b, 0x0a76b, 0x0a76d, 0x0a76d, 0x0a76f, 0x0a76f,
+    0x0a770, 0x0a771, 0x0a772, 0x0a773, 0x0a774, 0x0a775, 0x0a776, 0x0a777,
+    0x0a778, 0x0a77a, 0x0a77a, 0x0a77c, 0x0a77c, 0x01d79, 0x0a77f, 0x0a77f,
+};
+
+
+static const uint32_t  nxt_unicode_block_14f[128]  nxt_aligned(64) = {
+    0x0a781, 0x0a781, 0x0a783, 0x0a783, 0x0a785, 0x0a785, 0x0a787, 0x0a787,
+    0x0a788, 0x0a789, 0x0a78a, 0x0a78c, 0x0a78c, 0x00265, 0x0a78e, 0x0a78f,
+    0x0a791, 0x0a791, 0x0a793, 0x0a793, 0x0a794, 0x0a795, 0x0a796, 0x0a797,
+    0x0a798, 0x0a799, 0x0a79a, 0x0a79b, 0x0a79c, 0x0a79d, 0x0a79e, 0x0a79f,
+    0x0a7a1, 0x0a7a1, 0x0a7a3, 0x0a7a3, 0x0a7a5, 0x0a7a5, 0x0a7a7, 0x0a7a7,
+    0x0a7a9, 0x0a7a9, 0x00266, 0x0a7ab, 0x0a7ac, 0x0a7ad, 0x0a7ae, 0x0a7af,
+    0x0a7b0, 0x0a7b1, 0x0a7b2, 0x0a7b3, 0x0a7b4, 0x0a7b5, 0x0a7b6, 0x0a7b7,
+    0x0a7b8, 0x0a7b9, 0x0a7ba, 0x0a7bb, 0x0a7bc, 0x0a7bd, 0x0a7be, 0x0a7bf,
+    0x0a7c0, 0x0a7c1, 0x0a7c2, 0x0a7c3, 0x0a7c4, 0x0a7c5, 0x0a7c6, 0x0a7c7,
+    0x0a7c8, 0x0a7c9, 0x0a7ca, 0x0a7cb, 0x0a7cc, 0x0a7cd, 0x0a7ce, 0x0a7cf,
+    0x0a7d0, 0x0a7d1, 0x0a7d2, 0x0a7d3, 0x0a7d4, 0x0a7d5, 0x0a7d6, 0x0a7d7,
+    0x0a7d8, 0x0a7d9, 0x0a7da, 0x0a7db, 0x0a7dc, 0x0a7dd, 0x0a7de, 0x0a7df,
+    0x0a7e0, 0x0a7e1, 0x0a7e2, 0x0a7e3, 0x0a7e4, 0x0a7e5, 0x0a7e6, 0x0a7e7,
+    0x0a7e8, 0x0a7e9, 0x0a7ea, 0x0a7eb, 0x0a7ec, 0x0a7ed, 0x0a7ee, 0x0a7ef,
+    0x0a7f0, 0x0a7f1, 0x0a7f2, 0x0a7f3, 0x0a7f4, 0x0a7f5, 0x0a7f6, 0x0a7f7,
+    0x0a7f8, 0x0a7f9, 0x0a7fa, 0x0a7fb, 0x0a7fc, 0x0a7fd, 0x0a7fe, 0x0a7ff,
+};
+
+
+static const uint32_t  nxt_unicode_block_1fe[128]  nxt_aligned(64) = {
+    0x0ff00, 0x0ff01, 0x0ff02, 0x0ff03, 0x0ff04, 0x0ff05, 0x0ff06, 0x0ff07,
+    0x0ff08, 0x0ff09, 0x0ff0a, 0x0ff0b, 0x0ff0c, 0x0ff0d, 0x0ff0e, 0x0ff0f,
+    0x0ff10, 0x0ff11, 0x0ff12, 0x0ff13, 0x0ff14, 0x0ff15, 0x0ff16, 0x0ff17,
+    0x0ff18, 0x0ff19, 0x0ff1a, 0x0ff1b, 0x0ff1c, 0x0ff1d, 0x0ff1e, 0x0ff1f,
+    0x0ff20, 0x0ff41, 0x0ff42, 0x0ff43, 0x0ff44, 0x0ff45, 0x0ff46, 0x0ff47,
+    0x0ff48, 0x0ff49, 0x0ff4a, 0x0ff4b, 0x0ff4c, 0x0ff4d, 0x0ff4e, 0x0ff4f,
+    0x0ff50, 0x0ff51, 0x0ff52, 0x0ff53, 0x0ff54, 0x0ff55, 0x0ff56, 0x0ff57,
+    0x0ff58, 0x0ff59, 0x0ff5a, 0x0ff3b, 0x0ff3c, 0x0ff3d, 0x0ff3e, 0x0ff3f,
+    0x0ff40, 0x0ff41, 0x0ff42, 0x0ff43, 0x0ff44, 0x0ff45, 0x0ff46, 0x0ff47,
+    0x0ff48, 0x0ff49, 0x0ff4a, 0x0ff4b, 0x0ff4c, 0x0ff4d, 0x0ff4e, 0x0ff4f,
+    0x0ff50, 0x0ff51, 0x0ff52, 0x0ff53, 0x0ff54, 0x0ff55, 0x0ff56, 0x0ff57,
+    0x0ff58, 0x0ff59, 0x0ff5a, 0x0ff5b, 0x0ff5c, 0x0ff5d, 0x0ff5e, 0x0ff5f,
+    0x0ff60, 0x0ff61, 0x0ff62, 0x0ff63, 0x0ff64, 0x0ff65, 0x0ff66, 0x0ff67,
+    0x0ff68, 0x0ff69, 0x0ff6a, 0x0ff6b, 0x0ff6c, 0x0ff6d, 0x0ff6e, 0x0ff6f,
+    0x0ff70, 0x0ff71, 0x0ff72, 0x0ff73, 0x0ff74, 0x0ff75, 0x0ff76, 0x0ff77,
+    0x0ff78, 0x0ff79, 0x0ff7a, 0x0ff7b, 0x0ff7c, 0x0ff7d, 0x0ff7e, 0x0ff7f,
+};
+
+
+static const uint32_t  nxt_unicode_block_208[40]  nxt_aligned(64) = {
+    0x10428, 0x10429, 0x1042a, 0x1042b, 0x1042c, 0x1042d, 0x1042e, 0x1042f,
+    0x10430, 0x10431, 0x10432, 0x10433, 0x10434, 0x10435, 0x10436, 0x10437,
+    0x10438, 0x10439, 0x1043a, 0x1043b, 0x1043c, 0x1043d, 0x1043e, 0x1043f,
+    0x10440, 0x10441, 0x10442, 0x10443, 0x10444, 0x10445, 0x10446, 0x10447,
+    0x10448, 0x10449, 0x1044a, 0x1044b, 0x1044c, 0x1044d, 0x1044e, 0x1044f,
+};
+
+
+static const uint32_t  *nxt_unicode_blocks[]  nxt_aligned(64) = {
+    nxt_unicode_block_000,
+    nxt_unicode_block_001,
+    nxt_unicode_block_002,
+    nxt_unicode_block_003,
+    nxt_unicode_block_004,
+    NULL,
+    nxt_unicode_block_006,
+    nxt_unicode_block_007,
+    nxt_unicode_block_008,
+    nxt_unicode_block_009,
+    nxt_unicode_block_00a,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_021,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_03c,
+    nxt_unicode_block_03d,
+    nxt_unicode_block_03e,
+    nxt_unicode_block_03f,
+    NULL,
+    NULL,
+    nxt_unicode_block_042,
+    nxt_unicode_block_043,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_049,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_058,
+    nxt_unicode_block_059,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_14c,
+    nxt_unicode_block_14d,
+    nxt_unicode_block_14e,
+    nxt_unicode_block_14f,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_1fe,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    NULL,
+    nxt_unicode_block_208,
+};
diff --git a/nxt/nxt_unicode_lowcase.pl b/nxt/nxt_unicode_lowcase.pl
new file mode 100755 (executable)
index 0000000..aca17fc
--- /dev/null
@@ -0,0 +1,88 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+# BLOCK_SIZE should be 128, 256, 512, etc.  The value 128 provides
+# the minimum memory footprint for both 32-bit and 64-bit platforms.
+use constant BLOCK_SIZE => 128;
+
+my %lowcase;
+my %blocks;
+my $max_block = 0;
+my $max_lowcase = 0;
+
+while (<>) {
+    if (/^(\w+); (C|S); (\w+);/) {
+        my ($symbol, $folding) = (hex $1, hex $3);
+        $lowcase{$symbol} = $folding;
+        $blocks{int($symbol / BLOCK_SIZE)} = 1;
+
+        if ($max_lowcase < $symbol) {
+            $max_lowcase = $symbol;
+        }
+    }
+}
+
+
+my $last_block_size = $max_lowcase % BLOCK_SIZE + 1;
+
+
+for my $block (sort { $a <=> $b } keys %blocks) {
+   if ($max_block < $block) {
+       $max_block = $block;
+   }
+}
+
+
+my $blocks = scalar keys %blocks;
+
+printf("\n/*\n" .
+       " * %d %s-bytes blocks, %d pointers.\n" .
+       " * %d bytes on 32-bit platforms, %d bytes on 64-bit platforms.\n" .
+       " */\n\n",
+       $blocks, BLOCK_SIZE, $max_block + 1,
+       ($blocks - 1) * BLOCK_SIZE * 4 + $last_block_size + $max_block * 4,
+       ($blocks - 1) * BLOCK_SIZE * 4 + $last_block_size + $max_block * 8);
+
+printf("#define NXT_UNICODE_MAX_LOWCASE  0x%05x\n\n", $max_lowcase);
+printf("#define NXT_UNICODE_BLOCK_SIZE   %d\n\n\n", BLOCK_SIZE);
+
+
+for my $block (sort { $a <=> $b } keys %blocks) {
+    my $block_size = ($block != $max_block) ? BLOCK_SIZE : $last_block_size;
+
+    print "static const uint32_t  ";
+    printf("nxt_unicode_block_%03x[%d]  nxt_aligned(64) = {",
+           $block, $block_size);
+
+    for my $c (0 .. $block_size - 1) {
+        printf "\n   " if $c % 8 == 0;
+
+        my $n = $block * BLOCK_SIZE + $c;
+
+        if (exists $lowcase{$n}) {
+            printf(" 0x%05x,", $lowcase{$n});
+
+        } else {
+            #print " .......,";
+            printf(" 0x%05x,", $n);
+        }
+    }
+
+    print "\n};\n\n\n";
+}
+
+
+print "static const uint32_t  *nxt_unicode_blocks[]  nxt_aligned(64) = {\n";
+
+for my $block (0 .. $max_block) {
+    if (exists($blocks{$block})) {
+        printf("    nxt_unicode_block_%03x,\n", $block);
+
+    } else {
+        print  "    NULL,\n";
+    }
+}
+
+print "};\n";
diff --git a/nxt/nxt_utf8.c b/nxt/nxt_utf8.c
new file mode 100644 (file)
index 0000000..73f356e
--- /dev/null
@@ -0,0 +1,270 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_utf8.h>
+
+/*
+ * The nxt_unicode_lowcase.h file is the auto-generated file from
+ * the CaseFolding-6.3.0.txt file provided by Unicode, Inc.:
+ *
+ *   ./nxt_unicode_lowcase.pl CaseFolding-6.3.0.txt
+ *
+ * This file should be copied to system specific nxt_unicode_SYSTEM_lowcase.h
+ * file and utf8_file_name_test should be built with this file.
+ * Then a correct system specific file should be generated:
+ *
+ *   ./build/utf8_file_name_test | ./nxt_unicode_lowcase.pl
+ *
+ * Only common and simple case foldings are supported.  Full case foldings
+ * is not supported.  Combined characters are also not supported.
+ */
+
+#include <nxt_unicode_lowcase.h>
+
+
+u_char *
+nxt_utf8_encode(u_char *p, uint32_t u)
+{
+    if (u < 0x80) {
+        *p++ = (u_char) (u & 0xFF);
+        return p;
+    }
+
+    if (u < 0x0800) {
+        *p++ = (u_char) (( u >> 6)          | 0xC0);
+        *p++ = (u_char) (( u        & 0x3f) | 0x80);
+        return p;
+    }
+
+    if (u < 0x10000) {
+        *p++ = (u_char) ( (u >> 12)         | 0xE0);
+        *p++ = (u_char) (((u >>  6) & 0x3F) | 0x80);
+        *p++ = (u_char) (( u        & 0x3F) | 0x80);
+        return p;
+    }
+
+    if (u < 0x110000) {
+        *p++ = (u_char) ( (u >> 18)         | 0xF0);
+        *p++ = (u_char) (((u >> 12) & 0x3F) | 0x80);
+        *p++ = (u_char) (((u >>  6) & 0x3F) | 0x80);
+        *p++ = (u_char) (( u        & 0x3F) | 0x80);
+        return p;
+    }
+
+    return NULL;
+}
+
+
+/*
+ * nxt_utf8_decode() decodes UTF-8 sequences and returns a valid
+ * character 0x00 - 0x10FFFF, or 0xFFFFFFFF for invalid or overlong
+ * UTF-8 sequence.
+ */
+
+uint32_t
+nxt_utf8_decode(const u_char **start, const u_char *end)
+{
+    uint32_t  u;
+
+    u = (uint32_t) **start;
+
+    if (u < 0x80) {
+        (*start)++;
+        return u;
+    }
+
+    return nxt_utf8_decode2(start, end);
+}
+
+
+/*
+ * nxt_utf8_decode2() decodes two and more bytes UTF-8 sequences only
+ * and returns a valid character 0x80 - 0x10FFFF, OR 0xFFFFFFFF for
+ * invalid or overlong UTF-8 sequence.
+ */
+
+uint32_t
+nxt_utf8_decode2(const u_char **start, const u_char *end)
+{
+    u_char        c;
+    size_t        n;
+    uint32_t      u, overlong;
+    const u_char  *p;
+
+    p = *start;
+    u = (uint32_t) *p;
+
+    if (u >= 0xE0) {
+
+        if (u >= 0xF0) {
+
+            if (nxt_slow_path(u > 0xF4)) {
+                /*
+                 * The maximum valid Unicode character is 0x10FFFF
+                 * which is encoded as 0xF4 0x8F 0xBF 0xBF.
+                 */
+                return 0xFFFFFFFF;
+            }
+
+            u &= 0x07;
+            overlong = 0x00FFFF;
+            n = 3;
+
+        } else {
+            u &= 0x0F;
+            overlong = 0x07FF;
+            n = 2;
+        }
+
+    } else if (u >= 0xC2) {
+
+        /* 0x80 is encoded as 0xC2 0x80. */
+
+        u &= 0x1F;
+        overlong = 0x007F;
+        n = 1;
+
+    } else {
+        /* u <= 0xC2 */
+        return 0xFFFFFFFF;
+    }
+
+    p++;
+
+    if (nxt_fast_path(p + n <= end)) {
+
+        do {
+            c = *p++;
+            /*
+             * The byte must in the 0x80 - 0xBF range.
+             * Values below 0x80 become >= 0x80.
+             */
+            c = c - 0x80;
+
+            if (nxt_slow_path(c > 0x3F)) {
+                return 0xFFFFFFFF;
+            }
+
+            u = (u << 6) | c;
+            n--;
+
+        } while (n != 0);
+
+        if (overlong < u && u < 0x110000) {
+            *start = p;
+            return u;
+        }
+    }
+
+    return 0xFFFFFFFF;
+}
+
+
+/*
+ * nxt_utf8_casecmp() tests only up to the minimum of given lengths, but
+ * requires lengths of both strings because otherwise nxt_utf8_decode2()
+ * may fail due to incomplete sequence.
+ */
+
+nxt_int_t
+nxt_utf8_casecmp(const u_char *start1, const u_char *start2, size_t len1,
+    size_t len2)
+{
+    int32_t       n;
+    uint32_t      u1, u2;
+    const u_char  *end1, *end2;
+
+    end1 = start1 + len1;
+    end2 = start2 + len2;
+
+    while (start1 < end1 && start2 < end2) {
+
+        u1 = nxt_utf8_lowcase(&start1, end1);
+
+        u2 = nxt_utf8_lowcase(&start2, end2);
+
+        if (nxt_slow_path((u1 | u2) == 0xFFFFFFFF)) {
+            return NXT_UTF8_SORT_INVALID;
+        }
+
+        n = u1 - u2;
+
+        if (n != 0) {
+            return (nxt_int_t) n;
+        }
+    }
+
+    return 0;
+}
+
+
+uint32_t
+nxt_utf8_lowcase(const u_char **start, const u_char *end)
+{
+    uint32_t        u;
+    const uint32_t  *block;
+
+    u = (uint32_t) **start;
+
+    if (nxt_fast_path(u < 0x80)) {
+        (*start)++;
+
+        return nxt_unicode_block_000[u];
+    }
+
+    u = nxt_utf8_decode2(start, end);
+
+    if (u <= NXT_UNICODE_MAX_LOWCASE) {
+        block = nxt_unicode_blocks[u / NXT_UNICODE_BLOCK_SIZE];
+
+        if (block != NULL) {
+            return block[u % NXT_UNICODE_BLOCK_SIZE];
+        }
+    }
+
+    return u;
+}
+
+
+ssize_t
+nxt_utf8_length(const u_char *p, size_t len)
+{
+    ssize_t       length;
+    const u_char  *end;
+
+    length = 0;
+
+    end = p + len;
+
+    while (p < end) {
+        if (nxt_slow_path(nxt_utf8_decode(&p, end) == 0xffffffff)) {
+            return -1;
+        }
+
+        length++;
+    }
+
+    return length;
+}
+
+
+nxt_bool_t
+nxt_utf8_is_valid(const u_char *p, size_t len)
+{
+    const u_char  *end;
+
+    end = p + len;
+
+    while (p < end) {
+        if (nxt_slow_path(nxt_utf8_decode(&p, end) == 0xffffffff)) {
+            return 0;
+        }
+    }
+
+    return 1;
+}
diff --git a/nxt/nxt_utf8.h b/nxt/nxt_utf8.h
new file mode 100644 (file)
index 0000000..13f42e1
--- /dev/null
@@ -0,0 +1,60 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#ifndef _NXT_UTF8_H_INCLUDED_
+#define _NXT_UTF8_H_INCLUDED_
+
+
+/*
+ * Since the maximum valid Unicode character is 0x0010FFFF, the maximum
+ * difference between Unicode characters is lesser 0x0010FFFF and
+ * 0x0EEE0EEE can be used as value to indicate UTF-8 encoding error.
+ */
+#define NXT_UTF8_SORT_INVALID  0x0EEE0EEE
+
+
+NXT_EXPORT u_char *nxt_utf8_encode(u_char *p, uint32_t u);
+NXT_EXPORT uint32_t nxt_utf8_decode(const u_char **start, const u_char *end);
+NXT_EXPORT uint32_t nxt_utf8_decode2(const u_char **start, const u_char *end);
+NXT_EXPORT nxt_int_t nxt_utf8_casecmp(const u_char *start1,
+    const u_char *start2, size_t len1, size_t len2);
+NXT_EXPORT uint32_t nxt_utf8_lowcase(const u_char **start, const u_char *end);
+NXT_EXPORT ssize_t nxt_utf8_length(const u_char *p, size_t len);
+NXT_EXPORT nxt_bool_t nxt_utf8_is_valid(const u_char *p, size_t len);
+
+
+/* nxt_utf8_next() expects a valid UTF-8 string. */
+
+nxt_inline const u_char *
+nxt_utf8_next(const u_char *p, const u_char *end)
+{
+    u_char  c;
+
+    c = *p++;
+
+    if ((c & 0x80) != 0) {
+
+        do {
+            /*
+             * The first UTF-8 byte is either 0xxxxxxx or 11xxxxxx.
+             * The next UTF-8 bytes are 10xxxxxx.
+             */
+            c = *p;
+
+            if ((c & 0xC0) != 0x80) {
+                return p;
+            }
+
+            p++;
+
+        } while (p < end);
+    }
+
+    return p;
+}
+
+
+#endif /* _NXT_UTF8_H_INCLUDED_ */
diff --git a/nxt/test/Makefile b/nxt/test/Makefile
new file mode 100644 (file)
index 0000000..2a085b1
--- /dev/null
@@ -0,0 +1,45 @@
+
+lib_test: \
+       $(NXT_BUILDDIR)/rbtree_unit_test \
+       $(NXT_BUILDDIR)/lvlhsh_unit_test \
+       $(NXT_BUILDDIR)/utf8_unit_test \
+
+       $(NXT_BUILDDIR)/rbtree_unit_test
+       $(NXT_BUILDDIR)/lvlhsh_unit_test
+       $(NXT_BUILDDIR)/utf8_unit_test
+
+$(NXT_BUILDDIR)/utf8_unit_test: \
+       $(NXT_BUILDDIR)/nxt_utf8.o \
+       $(NXT_LIB)/test/utf8_unit_test.c \
+
+       $(NXT_CC) -o $(NXT_BUILDDIR)/utf8_unit_test $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/test/utf8_unit_test.c \
+               $(NXT_BUILDDIR)/nxt_utf8.o
+
+$(NXT_BUILDDIR)/rbtree_unit_test: \
+       $(NXT_BUILDDIR)/nxt_rbtree.o \
+       $(NXT_BUILDDIR)/nxt_murmur_hash.o \
+       $(NXT_LIB)/test/rbtree_unit_test.c \
+
+       $(NXT_CC) -o $(NXT_BUILDDIR)/rbtree_unit_test $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/test/rbtree_unit_test.c \
+               $(NXT_BUILDDIR)/nxt_rbtree.o \
+               $(NXT_BUILDDIR)/nxt_murmur_hash.o
+
+$(NXT_BUILDDIR)/lvlhsh_unit_test: \
+       $(NXT_BUILDDIR)/nxt_lvlhsh.o \
+       $(NXT_BUILDDIR)/nxt_murmur_hash.o \
+       $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \
+       $(NXT_BUILDDIR)/nxt_malloc.o \
+       $(NXT_LIB)/test/lvlhsh_unit_test.c \
+
+       $(NXT_CC) -o $(NXT_BUILDDIR)/lvlhsh_unit_test $(NXT_CFLAGS) \
+               -I$(NXT_LIB) \
+               $(NXT_LIB)/test/lvlhsh_unit_test.c \
+               $(NXT_BUILDDIR)/nxt_lvlhsh.o \
+               $(NXT_BUILDDIR)/nxt_rbtree.o \
+               $(NXT_BUILDDIR)/nxt_murmur_hash.o \
+               $(NXT_BUILDDIR)/nxt_mem_cache_pool.o \
+               $(NXT_BUILDDIR)/nxt_malloc.o
diff --git a/nxt/test/lvlhsh_unit_test.c b/nxt/test/lvlhsh_unit_test.c
new file mode 100644 (file)
index 0000000..cda0243
--- /dev/null
@@ -0,0 +1,278 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_malloc.h>
+#include <nxt_lvlhsh.h>
+#include <nxt_murmur_hash.h>
+#include <nxt_mem_cache_pool.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+
+static nxt_int_t
+lvlhsh_unit_test_key_test(nxt_lvlhsh_query_t *lhq, void *data)
+{
+    if (*(uintptr_t *) lhq->key.data == (uintptr_t) data) {
+        return NXT_OK;
+    }
+
+    return NXT_DECLINED;
+}
+
+
+static void *
+lvlhsh_unit_test_pool_alloc(void *pool, size_t size, nxt_uint_t nalloc)
+{
+    return nxt_mem_cache_align(pool, size, size);
+}
+
+
+static void
+lvlhsh_unit_test_pool_free(void *pool, void *p, size_t size)
+{
+    nxt_mem_cache_free(pool, p);
+}
+
+
+static const nxt_lvlhsh_proto_t  lvlhsh_proto  nxt_aligned(64) = {
+    NXT_LVLHSH_LARGE_SLAB,
+    0,
+    lvlhsh_unit_test_key_test,
+    lvlhsh_unit_test_pool_alloc,
+    lvlhsh_unit_test_pool_free,
+};
+
+
+static nxt_int_t
+lvlhsh_unit_test_add(nxt_lvlhsh_t *lh, const nxt_lvlhsh_proto_t *proto,
+    void *pool, uintptr_t key)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = key;
+    lhq.replace = 0;
+    lhq.key.len = sizeof(uintptr_t);
+    lhq.key.data = (u_char *) &key;
+    lhq.value = (void *) key;
+    lhq.proto = proto;
+    lhq.pool = pool;
+
+    switch (nxt_lvlhsh_insert(lh, &lhq)) {
+
+    case NXT_OK:
+        return NXT_OK;
+
+    case NXT_DECLINED:
+        printf("lvlhsh unit test failed: key %08lX is already in hash\n",
+               (long) key);
+
+    default:
+        return NXT_ERROR;
+    }
+}
+
+
+static nxt_int_t
+lvlhsh_unit_test_get(nxt_lvlhsh_t *lh, const nxt_lvlhsh_proto_t *proto,
+    uintptr_t key)
+{
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = key;
+    lhq.key.len = sizeof(uintptr_t);
+    lhq.key.data =  (u_char *) &key;
+    lhq.proto = proto;
+
+    if (nxt_lvlhsh_find(lh, &lhq) == NXT_OK) {
+
+        if (key == (uintptr_t) lhq.value) {
+            return NXT_OK;
+        }
+    }
+
+    printf("lvlhsh unit test failed: key %08lX not found in hash\n",
+           (long) key);
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+lvlhsh_unit_test_delete(nxt_lvlhsh_t *lh, const nxt_lvlhsh_proto_t *proto,
+    void *pool, uintptr_t key)
+{
+    nxt_int_t           ret;
+    nxt_lvlhsh_query_t  lhq;
+
+    lhq.key_hash = key;
+    lhq.key.len = sizeof(uintptr_t);
+    lhq.key.data = (u_char *) &key;
+    lhq.proto = proto;
+    lhq.pool = pool;
+
+    ret = nxt_lvlhsh_delete(lh, &lhq);
+
+    if (ret != NXT_OK) {
+        printf("lvlhsh unit test failed: key %08lX not found in hash\n",
+               (long) key);
+    }
+
+    return ret;
+}
+
+
+static void *
+lvlhsh_malloc(void *mem, size_t size)
+{
+    return nxt_malloc(size);
+}
+
+
+static void *
+lvlhsh_zalloc(void *mem, size_t size)
+{
+    void  *p;
+
+    p = nxt_malloc(size);
+
+    if (p != NULL) {
+        memset(p, 0, size);
+    }
+
+    return p;
+}
+
+
+static void *
+lvlhsh_align(void *mem, size_t alignment, size_t size)
+{
+    return nxt_memalign(alignment, size);
+}
+
+
+static void
+lvlhsh_free(void *mem, void *p)
+{
+    nxt_free(p);
+}
+
+
+static void
+lvlhsh_alert(void *mem, const char *fmt, ...)
+{
+    int      n;
+    va_list  args;
+    char     buf[1024];
+
+    va_start(args, fmt);
+    n = vsnprintf(buf, sizeof(buf), fmt, args);
+    va_end(args);
+
+    (void) printf("alert: \"%.*s\"\n", n, buf);
+}
+
+
+static const nxt_mem_proto_t  mem_cache_pool_proto = {
+    lvlhsh_malloc,
+    lvlhsh_zalloc,
+    lvlhsh_align,
+    NULL,
+    lvlhsh_free,
+    lvlhsh_alert,
+    NULL,
+};
+
+
+static nxt_int_t
+lvlhsh_unit_test(nxt_uint_t n)
+{
+    uintptr_t            key;
+    nxt_uint_t            i;
+    nxt_lvlhsh_t          lh;
+    nxt_lvlhsh_each_t     lhe;
+    nxt_mem_cache_pool_t  *pool;
+
+    const size_t         min_chunk_size = 32;
+    const size_t         page_size = 1024;
+    const size_t         page_alignment = 128;
+    const size_t         cluster_size = 4096;
+
+    pool = nxt_mem_cache_pool_create(&mem_cache_pool_proto, NULL, NULL,
+                                    cluster_size, page_alignment,
+                                    page_size, min_chunk_size);
+    if (pool == NULL) {
+        return NXT_ERROR;
+    }
+
+    printf("lvlhsh unit test started: %ld items\n", (long) n);
+
+    memset(&lh, 0, sizeof(nxt_lvlhsh_t));
+
+    key = 0;
+    for (i = 0; i < n; i++) {
+        key = nxt_murmur_hash2(&key, sizeof(uint32_t));
+
+        if (lvlhsh_unit_test_add(&lh, &lvlhsh_proto, pool, key) != NXT_OK) {
+            printf("lvlhsh add unit test failed at %ld\n", (long) i);
+            return NXT_ERROR;
+        }
+    }
+
+    key = 0;
+    for (i = 0; i < n; i++) {
+        key = nxt_murmur_hash2(&key, sizeof(uint32_t));
+
+        if (lvlhsh_unit_test_get(&lh, &lvlhsh_proto, key) != NXT_OK) {
+            return NXT_ERROR;
+        }
+    }
+
+    memset(&lhe, 0, sizeof(nxt_lvlhsh_each_t));
+    lhe.proto = &lvlhsh_proto;
+
+    for (i = 0; i < n + 1; i++) {
+        if (nxt_lvlhsh_each(&lh, &lhe) == NULL) {
+            break;
+        }
+    }
+
+    if (i != n) {
+        printf("lvlhsh each unit test failed at %ld of %ld\n",
+                (long) i, (long) n);
+        return NXT_ERROR;
+    }
+
+    key = 0;
+    for (i = 0; i < n; i++) {
+        key = nxt_murmur_hash2(&key, sizeof(uint32_t));
+
+        if (lvlhsh_unit_test_delete(&lh, &lvlhsh_proto, pool, key) != NXT_OK) {
+            return NXT_ERROR;
+        }
+    }
+
+    if (!nxt_mem_cache_pool_is_empty(pool)) {
+        printf("mem cache pool is not empty\n");
+        return NXT_ERROR;
+    }
+
+    nxt_mem_cache_pool_destroy(pool);
+
+    printf("lvlhsh unit test passed\n");
+
+    return NXT_OK;
+}
+
+
+int
+main(void)
+{
+     return lvlhsh_unit_test(1000 * 1000);
+}
diff --git a/nxt/test/rbtree_unit_test.c b/nxt/test/rbtree_unit_test.c
new file mode 100644 (file)
index 0000000..1b7c544
--- /dev/null
@@ -0,0 +1,193 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_rbtree.h>
+#include <nxt_murmur_hash.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+typedef struct {
+    NXT_RBTREE_NODE  (node);
+    uint32_t        key;
+} nxt_rbtree_test_t;
+
+
+static nxt_int_t rbtree_unit_test_comparison(nxt_rbtree_node_t *node1,
+    nxt_rbtree_node_t *node2);
+static nxt_int_t rbtree_unit_test_compare(uint32_t key1, uint32_t key2);
+static int nxt_cdecl rbtree_unit_test_sort_cmp(const void *one,
+    const void *two);
+
+
+static nxt_int_t
+rbtree_unit_test(nxt_uint_t n)
+{
+    void              *mark;
+    uint32_t          key, *keys;
+    nxt_uint_t         i;
+    nxt_rbtree_t       tree;
+    nxt_rbtree_node_t  *node;
+    nxt_rbtree_test_t  *items, *item;
+
+    printf("rbtree unit test started: %ld nodes\n", (long) n);
+
+    nxt_rbtree_init(&tree, rbtree_unit_test_comparison);
+
+    mark = tree.sentinel.right;
+
+    items = malloc(n * sizeof(nxt_rbtree_test_t));
+    if (items == NULL) {
+        return NXT_ERROR;
+    }
+
+    keys = malloc(n * sizeof(uint32_t));
+    if (keys == NULL) {
+        free(keys);
+        return NXT_ERROR;
+    }
+
+    key = 0;
+
+    for (i = 0; i < n; i++) {
+        key = nxt_murmur_hash2(&key, sizeof(uint32_t));
+        keys[i] = key;
+        items[i].key = key;
+    }
+
+    qsort(keys, n, sizeof(uint32_t), rbtree_unit_test_sort_cmp);
+
+    for (i = 0; i < n; i++) {
+        nxt_rbtree_insert(&tree, &items[i].node);
+    }
+
+    for (i = 0; i < n; i++) {
+        node = nxt_rbtree_find(&tree, &items[i].node);
+
+        if (node != (nxt_rbtree_node_t *) &items[i].node) {
+            printf("rbtree unit test failed: %08X not found\n", items[i].key);
+            goto fail;
+        }
+    }
+
+    i = 0;
+    node = nxt_rbtree_min(&tree);
+
+    while (nxt_rbtree_is_there_successor(&tree, node)) {
+
+        item = (nxt_rbtree_test_t *) node;
+
+        if (keys[i] != item->key) {
+            printf("rbtree unit test failed: %ld: %08X %08X\n",
+                   (long) i, keys[i], item->key);
+            goto fail;
+        }
+
+        i++;
+        node = nxt_rbtree_node_successor(&tree, node);
+    }
+
+    if (i != n) {
+        printf("rbtree unit test failed: %ld\n", (long) i);
+        goto fail;
+    }
+
+    for (i = 0; i < n; i++) {
+        nxt_rbtree_delete(&tree, &items[i].node);
+        memset(&items[i], 0xA5, sizeof(nxt_rbtree_test_t));
+    }
+
+    if (!nxt_rbtree_is_empty(&tree)) {
+        printf("rbtree unit test failed: tree is not empty\n");
+        goto fail;
+    }
+
+    /* Check that the sentinel callback was not modified. */
+
+    if (mark != tree.sentinel.right) {
+        printf("rbtree sentinel unit test failed\n");
+        goto fail;
+    }
+
+    free(keys);
+    free(items);
+
+    printf("rbtree unit test passed\n");
+
+    return NXT_OK;
+
+fail:
+
+    free(keys);
+    free(items);
+
+    return NXT_ERROR;
+}
+
+
+static nxt_int_t
+rbtree_unit_test_comparison(nxt_rbtree_node_t *node1,
+    nxt_rbtree_node_t *node2)
+{
+    nxt_rbtree_test_t  *item1, *item2;
+
+    item1 = (nxt_rbtree_test_t *) node1;
+    item2 = (nxt_rbtree_test_t *) node2;
+
+    return rbtree_unit_test_compare(item1->key, item2->key);
+}
+
+
+/*
+ * Subtraction cannot be used in these comparison functions because
+ * the key values are spread uniform in whole 0 .. 2^32 range but are
+ * not grouped around some value as timeout values are.
+ */
+
+static nxt_int_t
+rbtree_unit_test_compare(uint32_t key1, uint32_t key2)
+{
+    if (key1 < key2) {
+        return -1;
+    }
+
+    if (key1 == key2) {
+        return 0;
+    }
+
+    return 1;
+}
+
+
+static int nxt_cdecl
+rbtree_unit_test_sort_cmp(const void *one, const void *two)
+{
+    const uint32_t  *first, *second;
+
+    first = one;
+    second = two;
+
+    if (*first < *second) {
+        return -1;
+    }
+
+    if (*first == *second) {
+        return 0;
+    }
+
+    return 1;
+}
+
+
+int
+main(void)
+{
+    return rbtree_unit_test(1000 * 1000);
+}
diff --git a/nxt/test/utf8_unit_test.c b/nxt/test/utf8_unit_test.c
new file mode 100644 (file)
index 0000000..702f5d5
--- /dev/null
@@ -0,0 +1,196 @@
+
+/*
+ * Copyright (C) Igor Sysoev
+ * Copyright (C) NGINX, Inc.
+ */
+
+#include <nxt_types.h>
+#include <nxt_clang.h>
+#include <nxt_stub.h>
+#include <nxt_utf8.h>
+#include <stdio.h>
+#include <string.h>
+
+
+#define NXT_UTF8_START_TEST  0xC2
+//#define NXT_UTF8_START_TEST  0
+
+
+static u_char  invalid[] = {
+
+    /* Invalid first byte less than 0xC2. */
+    1, 0x80, 0x00, 0x00, 0x00,
+    1, 0xC0, 0x00, 0x00, 0x00,
+    2, 0xC0, 0x00, 0x00, 0x00,
+    3, 0xC0, 0x00, 0x00, 0x00,
+    4, 0xC0, 0x00, 0x00, 0x00,
+
+    /* Invalid 0x0x110000 value. */
+    4, 0xF4, 0x90, 0x80, 0x80,
+
+    /* Incomplete length. */
+    2, 0xE0, 0xAF, 0xB5, 0x00,
+
+    /* Overlong values. */
+    2, 0xC0, 0x80, 0x00, 0x00,
+    2, 0xC1, 0xB3, 0x00, 0x00,
+    3, 0xE0, 0x80, 0x80, 0x00,
+    3, 0xE0, 0x81, 0xB3, 0x00,
+    3, 0xE0, 0x90, 0x9A, 0x00,
+    4, 0xF0, 0x80, 0x8A, 0x80,
+    4, 0xF0, 0x80, 0x81, 0xB3,
+    4, 0xF0, 0x80, 0xAF, 0xB5,
+};
+
+
+static nxt_int_t
+utf8_overlong(u_char *overlong, size_t len)
+{
+    u_char        *p, utf8[4];
+    size_t        size;
+    uint32_t      u, d;
+    nxt_uint_t     i;
+    const u_char  *pp;
+
+    pp = overlong;
+
+    d = nxt_utf8_decode(&pp, overlong + len);
+
+    len = pp - overlong;
+
+    if (d != 0xFFFFFFFF) {
+        p = nxt_utf8_encode(utf8, d);
+
+        size = (p != NULL) ? p - utf8 : 0;
+
+        if (len != size || memcmp(overlong, utf8, size) != 0) {
+
+            u = 0;
+            for (i = 0; i < len; i++) {
+                u = (u << 8) + overlong[i];
+            }
+
+            printf("nxt_utf8_decode(%05Xd, %zd) failed: %05Xd, %zd\n",
+                   u, len, d, size);
+
+            return NXT_ERROR;
+        }
+    }
+
+    return NXT_OK;
+}
+
+
+static nxt_int_t
+utf8_unit_test(void)
+{
+    u_char        *p, utf8[4];
+    size_t        len;
+    int32_t       n;
+    uint32_t      u, d;
+    nxt_uint_t     i, k, l, m;
+    const u_char  *pp;
+
+    printf("utf8 unit test started\n");
+
+    /* Test valid UTF-8. */
+
+    for (u = 0; u < 0x110000; u++) {
+
+        p = nxt_utf8_encode(utf8, u);
+
+        if (p == NULL) {
+            printf("nxt_utf8_encode(%05Xd) failed\n", u);
+            return NXT_ERROR;
+        }
+
+        pp = utf8;
+
+        d = nxt_utf8_decode(&pp, p);
+
+        if (u != d) {
+            printf("nxt_utf8_decode(%05Xd) failed: %05uxD\n", u, d);
+            return NXT_ERROR;
+        }
+    }
+
+    /* Test some invalid UTF-8. */
+
+    for (i = 0; i < sizeof(invalid); i += 5) {
+
+        len = invalid[i];
+        utf8[0] = invalid[i + 1];
+        utf8[1] = invalid[i + 2];
+        utf8[2] = invalid[i + 3];
+        utf8[3] = invalid[i + 4];
+
+        pp = utf8;
+
+        d = nxt_utf8_decode(&pp, utf8 + len);
+
+        if (d != 0xFFFFFFFF) {
+
+            u = 0;
+            for (i = 0; i < len; i++) {
+                u = (u << 8) + utf8[i];
+            }
+
+            printf("nxt_utf8_decode(%05Xd, %zd) failed: %05Xd\n", u, len, d);
+            return NXT_ERROR;
+        }
+    }
+
+    /* Test all overlong UTF-8. */
+
+    for (i = NXT_UTF8_START_TEST; i < 256; i++) {
+        utf8[0] = i;
+
+        if (utf8_overlong(utf8, 1) != NXT_OK) {
+            return NXT_ERROR;
+        }
+
+        for (k = 0; k < 256; k++) {
+            utf8[1] = k;
+
+            if (utf8_overlong(utf8, 2) != NXT_OK) {
+                return NXT_ERROR;
+            }
+
+            for (l = 0; l < 256; l++) {
+                utf8[2] = l;
+
+                if (utf8_overlong(utf8, 3) != NXT_OK) {
+                    return NXT_ERROR;
+                }
+
+                for (m = 0; m < 256; m++) {
+                    utf8[3] = m;
+
+                    if (utf8_overlong(utf8, 4) != NXT_OK) {
+                        return NXT_ERROR;
+                    }
+                }
+            }
+        }
+    }
+
+    n = nxt_utf8_casecmp((u_char *) "ABC АБВ ΑΒΓ",
+                        (u_char *) "abc абв αβγ",
+                            sizeof("ABC АБВ ΑΒΓ") - 1,
+                            sizeof("abc абв αβγ") - 1);
+
+    if (n != 0) {
+        printf("nxt_utf8_casecmp() failed\n");
+        return NXT_ERROR;
+    }
+
+    printf("utf8 unit test passed\n");
+    return NXT_OK;
+}
+
+
+int
+main(void)
+{
+    return utf8_unit_test();
+}