From: Igor Sysoev Date: Wed, 23 Sep 2015 00:31:27 +0000 (+0300) Subject: Initial import of nJScript. X-Git-Tag: 0.1.0~132 X-Git-Url: http://www.kaiwu.me/postgresql/commit/?a=commitdiff_plain;h=bffbe5e41aa97237e125b5859a8d2613b98af9ef;p=njs.git Initial import of nJScript. --- bffbe5e41aa97237e125b5859a8d2613b98af9ef diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..d1fa27ba --- /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 index 00000000..5d9645f0 --- /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 index 00000000..f8a6e223 --- /dev/null +++ b/README @@ -0,0 +1,118 @@ + +Configure nginx with HTTP JavaScript module using the --add-module option: + + ./configure --add-module=/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 index 00000000..01b07c7a --- /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 index 00000000..a6251000 --- /dev/null +++ b/nginx/config @@ -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 index 00000000..f252ca47 --- /dev/null +++ b/nginx/config.make @@ -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 index 00000000..a888744b --- /dev/null +++ b/nginx/ngx_http_js_module.c @@ -0,0 +1,1205 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) NGINX, Inc. + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +#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 index 00000000..641d012c --- /dev/null +++ b/njs/njs_array.c @@ -0,0 +1,944 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 = ¶m->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 index 00000000..7aa3db7f --- /dev/null +++ b/njs/njs_array.h @@ -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 index 00000000..a2f125bf --- /dev/null +++ b/njs/njs_disassembler.c @@ -0,0 +1,236 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..76d54d0f --- /dev/null +++ b/njs/njs_extern.c @@ -0,0 +1,133 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..fb512b3d --- /dev/null +++ b/njs/njs_extern.h @@ -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 index 00000000..d9f1ad47 --- /dev/null +++ b/njs/njs_function.c @@ -0,0 +1,518 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 = ¶m->args[0]; + p.args = ¶m->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 index 00000000..be856285 --- /dev/null +++ b/njs/njs_function.h @@ -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 index 00000000..e6c3c6e3 --- /dev/null +++ b/njs/njs_generator.c @@ -0,0 +1,2101 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..e5a190bd --- /dev/null +++ b/njs/njs_lexer.c @@ -0,0 +1,708 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..ef6b3e22 --- /dev/null +++ b/njs/njs_lexer_keyword.c @@ -0,0 +1,198 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..f1d7c7a6 --- /dev/null +++ b/njs/njs_nonrecursive_parser.c @@ -0,0 +1,1549 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 = + * "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 index 00000000..8fd60164 --- /dev/null +++ b/njs/njs_number.c @@ -0,0 +1,287 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 = ¶m->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 index 00000000..2e7aa78e --- /dev/null +++ b/njs/njs_number.h @@ -0,0 +1,35 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NJS_NUMBER_H_INCLUDED_ +#define _NJS_NUMBER_H_INCLUDED_ + + +#include + + +#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 index 00000000..800b757c --- /dev/null +++ b/njs/njs_object.c @@ -0,0 +1,593 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(¶m->args[0])) { + + object = njs_object_alloc(vm); + if (nxt_slow_path(object == NULL)) { + return NXT_ERROR; + } + + } else { + value = ¶m->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 index 00000000..eb7f9e06 --- /dev/null +++ b/njs/njs_object.h @@ -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 index 00000000..6a554580 --- /dev/null +++ b/njs/njs_object_hash.h @@ -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 index 00000000..01a27f25 --- /dev/null +++ b/njs/njs_parser.c @@ -0,0 +1,1718 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * 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 index 00000000..ab07e958 --- /dev/null +++ b/njs/njs_parser.h @@ -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 index 00000000..290ede78 --- /dev/null +++ b/njs/njs_parser_expression.c @@ -0,0 +1,1126 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..34465256 --- /dev/null +++ b/njs/njs_regexp.c @@ -0,0 +1,544 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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(®exp->object.hash); + nxt_lvlhsh_init(®exp->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, ®exp->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, ¶m->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, ®exp->string, ¶m->args[0]); + + if (nxt_fast_path(ret == NXT_OK)) { + (void) njs_string_prop(&string, ®exp->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, ®exp->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 index 00000000..8ca7134a --- /dev/null +++ b/njs/njs_regexp.h @@ -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 index 00000000..9ecded92 --- /dev/null +++ b/njs/njs_regexp_pattern.h @@ -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 + + +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 index 00000000..6a929c1a --- /dev/null +++ b/njs/njs_shared.c @@ -0,0 +1,189 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..f2a248dd --- /dev/null +++ b/njs/njs_string.c @@ -0,0 +1,1602 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 = ¶m->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(¶m->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(¶m->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, ¶m); + 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 index 00000000..e134bdaf --- /dev/null +++ b/njs/njs_string.h @@ -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 index 00000000..0d2c06b7 --- /dev/null +++ b/njs/njs_variable.c @@ -0,0 +1,161 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..edb500d9 --- /dev/null +++ b/njs/njs_variable.h @@ -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 index 00000000..b09e3b9b --- /dev/null +++ b/njs/njs_vm.c @@ -0,0 +1,2732 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* 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, ¶m); + + 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, ¶m, 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, ¶m, + 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, ¶m); + /* + * 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, ¶m); + + /* + * 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 index 00000000..b7262f7e --- /dev/null +++ b/njs/njs_vm.h @@ -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 +#include +#include +#include +#include +#include +#include + + +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 index 00000000..b8d2c79a --- /dev/null +++ b/njs/njscript.c @@ -0,0 +1,436 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..df75e40f --- /dev/null +++ b/njs/njscript.h @@ -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 index 00000000..01330163 --- /dev/null +++ b/njs/test/njs_unit_test.c @@ -0,0 +1,2937 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 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 index 00000000..19ae2483 --- /dev/null +++ b/nxt/Makefile @@ -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 index 00000000..6ca47c1c --- /dev/null +++ b/nxt/auto/clang @@ -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 index 00000000..305d0d53 --- /dev/null +++ b/nxt/auto/configure @@ -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 index 00000000..7c318d6f --- /dev/null +++ b/nxt/auto/define @@ -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 index 00000000..a66ee6ec --- /dev/null +++ b/nxt/auto/echo @@ -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 index 00000000..73886f38 --- /dev/null +++ b/nxt/auto/feature @@ -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 index 00000000..82d34d37 --- /dev/null +++ b/nxt/auto/memalign @@ -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 + + 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 + + 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 index 00000000..6726f0db --- /dev/null +++ b/nxt/auto/os @@ -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 index 00000000..ac9a6cc9 --- /dev/null +++ b/nxt/auto/pcre @@ -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 + + 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 index 00000000..547c4304 --- /dev/null +++ b/nxt/nxt_alignment.h @@ -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 index 00000000..7d19acc1 --- /dev/null +++ b/nxt/nxt_array.c @@ -0,0 +1,160 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include + + +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 index 00000000..f12ce469 --- /dev/null +++ b/nxt/nxt_array.h @@ -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 index 00000000..d504a860 --- /dev/null +++ b/nxt/nxt_clang.h @@ -0,0 +1,87 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_CLANG_H_INCLUDED_ +#define _NXT_CLANG_H_INCLUDED_ + + +#include /* offsetof(). */ +#include /* 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 index 00000000..0541094e --- /dev/null +++ b/nxt/nxt_djb_hash.c @@ -0,0 +1,48 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include + + +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 index 00000000..712f8a44 --- /dev/null +++ b/nxt/nxt_djb_hash.h @@ -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 index 00000000..2842b92c --- /dev/null +++ b/nxt/nxt_lvlhsh.c @@ -0,0 +1,857 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include + + +/* + * 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 index 00000000..08c24294 --- /dev/null +++ b/nxt/nxt_lvlhsh.h @@ -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 index 00000000..3285fd5e --- /dev/null +++ b/nxt/nxt_malloc.c @@ -0,0 +1,52 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include + + +#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 index 00000000..586274c1 --- /dev/null +++ b/nxt/nxt_malloc.h @@ -0,0 +1,28 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#ifndef _NXT_MALLOC_H_INCLUDED_ +#define _NXT_MALLOC_H_INCLUDED_ + +#include + +/* + * alloca() is defined in stdlib.h in Linux, FreeBSD and MacOSX + * and in alloca.h in Linux, Solaris and MacOSX. + */ +#if (NXT_SOLARIS) +#include +#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 index 00000000..6aa573e2 --- /dev/null +++ b/nxt/nxt_mem_cache_pool.c @@ -0,0 +1,792 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * 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 index 00000000..67f380e2 --- /dev/null +++ b/nxt/nxt_mem_cache_pool.h @@ -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 index 00000000..9f78a151 --- /dev/null +++ b/nxt/nxt_murmur_hash.c @@ -0,0 +1,86 @@ + +/* + * The code is based on the code by Austin Appleby, + * released to the public domain. + */ + +#include +#include +#include + + +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 index 00000000..289dc5b0 --- /dev/null +++ b/nxt/nxt_murmur_hash.h @@ -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 index 00000000..7c1e2c11 --- /dev/null +++ b/nxt/nxt_queue.c @@ -0,0 +1,86 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include + + +/* + * 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 index 00000000..3a8a5ba9 --- /dev/null +++ b/nxt/nxt_queue.h @@ -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 index 00000000..805efa23 --- /dev/null +++ b/nxt/nxt_rbtree.c @@ -0,0 +1,496 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include + + +/* + * 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 index 00000000..13e10d10 --- /dev/null +++ b/nxt/nxt_rbtree.h @@ -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 index 00000000..138cd04c --- /dev/null +++ b/nxt/nxt_stub.h @@ -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 +#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 index 00000000..acf13281 --- /dev/null +++ b/nxt/nxt_types.h @@ -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 . + */ +#define _FILE_OFFSET_BITS 64 + +/* u_char, u_int, int8_t, int32_t, int64_t, size_t, off_t. */ +#include +#include + + +#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 index 00000000..c868ad19 --- /dev/null +++ b/nxt/nxt_unicode_lowcase.h @@ -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 index 00000000..aca17fcd --- /dev/null +++ b/nxt/nxt_unicode_lowcase.pl @@ -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 index 00000000..73f356e2 --- /dev/null +++ b/nxt/nxt_utf8.c @@ -0,0 +1,270 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include + +/* + * 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 + + +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 index 00000000..13f42e16 --- /dev/null +++ b/nxt/nxt_utf8.h @@ -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 index 00000000..2a085b14 --- /dev/null +++ b/nxt/test/Makefile @@ -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 index 00000000..cda0243d --- /dev/null +++ b/nxt/test/lvlhsh_unit_test.c @@ -0,0 +1,278 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..1b7c544f --- /dev/null +++ b/nxt/test/rbtree_unit_test.c @@ -0,0 +1,193 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + + +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 index 00000000..702f5d56 --- /dev/null +++ b/nxt/test/utf8_unit_test.c @@ -0,0 +1,196 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) NGINX, Inc. + */ + +#include +#include +#include +#include +#include +#include + + +#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(); +}