]> git.kaiwu.me - njs.git/commitdiff
Support of escape sequences inside strings: "\uXXXX", "\xXX",
authorIgor Sysoev <igor@sysoev.ru>
Wed, 18 Nov 2015 12:45:35 +0000 (15:45 +0300)
committerIgor Sysoev <igor@sysoev.ru>
Wed, 18 Nov 2015 12:45:35 +0000 (15:45 +0300)
"\u{X}, "\r\n", string continuations, etc.

njs/njs_lexer.c
njs/njs_number.c
njs/njs_number.h
njs/njs_parser.c
njs/njs_parser.h
njs/test/njs_unit_test.c
nxt/nxt_utf8.h

index 05dcc1a60c2512c55a0085ceca8eedaa7906a8ee..583a320c757e64dde28a4cea6458d92224dbaa6b 100644 (file)
@@ -472,33 +472,37 @@ njs_lexer_word(njs_lexer_t *lexer, u_char c)
 static njs_token_t
 njs_lexer_string(njs_lexer_t *lexer, u_char quote)
 {
-    u_char  *p, ch;
+    u_char      *p, c;
+    nxt_bool_t  escape;
 
+    escape = 0;
     lexer->text.data = lexer->start;
     p = lexer->start;
 
     while (p < lexer->end) {
 
-        /* TODO: end of line, backslash. */
+        c = *p++;
 
-        ch = *p++;
-
-        if (ch == '\\') {
+        if (c == '\\') {
             if (p == lexer->end) {
                 return NJS_TOKEN_ILLEGAL;
             }
 
-            /* STUB: reallocate. */
-
             p++;
+            escape = 1;
+
             continue;
         }
 
-        if (ch == quote) {
+        if (c == quote) {
             lexer->start = p;
             lexer->text.len = (p - 1) - lexer->text.data;
 
-            return NJS_TOKEN_STRING;
+            if (escape == 0) {
+                return NJS_TOKEN_STRING;
+            }
+
+            return NJS_TOKEN_ESCAPE_STRING;
         }
     }
 
index 09a8432367f0846a4e43269c9c6cd59e83a52c64..4074625fe556c6b3eb56151fc0d2462264e96073 100644 (file)
@@ -110,6 +110,49 @@ njs_number_parse(const u_char **start, const u_char *end)
 }
 
 
+int64_t
+njs_hex_number_parse(u_char *p, u_char *end)
+{
+    int8_t    d;
+    uint32_t  n;
+
+    static const int8_t  hex[256]
+        nxt_aligned(32) =
+    {
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+         0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    };
+
+    n = 0;
+
+    while (p < end) {
+        d = hex[*p++];
+
+        if (nxt_slow_path(d < 0)) {
+            return -1;
+        }
+
+        n = (n << 4) + d;
+    }
+
+    return n;
+}
+
+
 njs_ret_t
 njs_number_to_string(njs_vm_t *vm, njs_value_t *string,
     const njs_value_t *number)
index 2e7aa78e179b881c0653c6cea1c97711570e887e..d59d22843b9a14bb433cde6a60d45bd47be7dfd8 100644 (file)
@@ -25,6 +25,7 @@
 
 double njs_value_to_number(njs_value_t *value);
 double njs_number_parse(const u_char **start, const u_char *end);
+int64_t njs_hex_number_parse(u_char *p, 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);
index 476af93f3c67061d97b281525ecf12c21c412a69..dca82cbc6dd3647cb77d82761388c4c7bd359fe0 100644 (file)
@@ -60,6 +60,8 @@ 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);
+static njs_token_t njs_parser_escape_string_create(njs_vm_t *vm,
+    njs_value_t *value);
 
 
 njs_parser_node_t *
@@ -1373,6 +1375,18 @@ njs_parser_terminal(njs_vm_t *vm, njs_parser_t *parser,
 
         break;
 
+    case NJS_TOKEN_ESCAPE_STRING:
+        node->token = NJS_TOKEN_STRING;
+
+        nxt_thread_log_debug("JS: '%V'", &parser->lexer->text);
+
+        ret = njs_parser_escape_string_create(vm, &node->u.value);
+        if (nxt_slow_path(ret != NJS_TOKEN_STRING)) {
+            return ret;
+        }
+
+        break;
+
     case NJS_TOKEN_NUMBER:
         nxt_thread_log_debug("JS: %f", parser->lexer->number);
 
@@ -1674,6 +1688,168 @@ njs_parser_string_create(njs_vm_t *vm, njs_value_t *value)
 }
 
 
+static njs_token_t
+njs_parser_escape_string_create(njs_vm_t *vm, njs_value_t *value)
+{
+    u_char   c, *p, *start, *dst, *src, *end, *hex_end;
+    size_t   size, length, hex_length, skip;
+    int64_t  u;
+
+    start = NULL;
+    dst = NULL;
+
+    for ( ;; ) {
+        /*
+         * The loop runs twice: at the first step string size and
+         * UTF-8 length are evaluated.  Then the string is allocated
+         * and at the second step string content is copied.
+         */
+        size = 0;
+        length = 0;
+
+        src = vm->parser->lexer->text.data;
+        end = src + vm->parser->lexer->text.len;
+
+        while (src < end) {
+            c = *src++;
+
+            if (c == '\\') {
+                /*
+                 * Testing "src == end" is not required here
+                 * since this has been already tested by lexer.
+                 */
+                c = *src++;
+
+                switch (c) {
+
+                case 'u':
+                    skip = 0;
+                    hex_length = 4;
+
+                    /*
+                     * A character after "u" can be safely tested here
+                     * because there is always a closing quote at the
+                     * end of string: ...\u".
+                     */
+                    if (*src == '{') {
+                        hex_length = 0;
+                        src++;
+
+                        for (p = src; p < end && *p != '}'; p++) {
+                            hex_length++;
+                        }
+
+                        if (hex_length == 0 || hex_length > 6) {
+                            return NJS_TOKEN_ILLEGAL;
+                        }
+
+                        skip = 1;
+                    }
+
+                    goto hex;
+
+                case 'x':
+                    skip = 0;
+                    hex_length = 2;
+                    goto hex;
+
+                case '0':
+                    c = '\0';
+                    break;
+
+                case 'b':
+                    c = '\b';
+                    break;
+
+                case 'f':
+                    c = '\f';
+                    break;
+
+                case 'n':
+                    c = '\n';
+                    break;
+
+                case 'r':
+                    c = '\r';
+                    break;
+
+                case 't':
+                    c = '\t';
+                    break;
+
+                case 'v':
+                    c = '\v';
+                    break;
+
+                case '\r':
+                    /*
+                     * A character after "\r" can be safely tested here
+                     * because there is always a closing quote at the
+                     * end of string: ...\\r".
+                     */
+                    if (*src == '\n') {
+                        src++;
+                    }
+
+                    continue;
+
+                case '\n':
+                    continue;
+
+                default:
+                    break;
+                }
+            }
+
+            size++;
+            length++;
+
+            if (dst != NULL) {
+                *dst++ = c;
+            }
+
+            continue;
+
+        hex:
+
+            hex_end = src + hex_length;
+
+            if (hex_end > end) {
+                return NJS_TOKEN_ILLEGAL;
+            }
+
+            u = njs_hex_number_parse(src, hex_end);
+            if (nxt_slow_path(u < 0)) {
+                return NJS_TOKEN_ILLEGAL;
+            }
+
+            src = hex_end + skip;
+            size += nxt_utf8_size(u);
+            length++;
+
+            if (dst != NULL) {
+                dst = nxt_utf8_encode(dst, (uint32_t) u);
+            }
+        }
+
+        if (start != NULL) {
+            if (length > NJS_STRING_MAP_OFFSET && length != size) {
+                njs_string_offset_map_init(start, size);
+            }
+
+            return NJS_TOKEN_STRING;
+        }
+
+        start = njs_string_alloc(vm, value, size, length);
+        if (nxt_slow_path(start == NULL)) {
+            return NJS_TOKEN_ERROR;
+        }
+
+        dst = start;
+    }
+}
+
+
 njs_index_t
 njs_parser_index(njs_parser_t *parser, uint32_t scope)
 {
index 0faa0589d44c99f0bba006f48c0905c882b76a92..992f30ae6667c07ca747f9a12e8ae021efcea9f3 100644 (file)
@@ -115,6 +115,7 @@ typedef enum {
 
 #define NJS_TOKEN_LAST_CONST      NJS_TOKEN_STRING
 
+    NJS_TOKEN_ESCAPE_STRING,
     NJS_TOKEN_NAME,
 
     NJS_TOKEN_OBJECT_CREATE,
index 7791ca9b9a82d28e399b934fea40d4fc9315956f..eaeafd82f5cfed9e582001f3c969daaac9409893 100644 (file)
@@ -39,11 +39,6 @@ static njs_unit_test_t  njs_test[] =
       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") },
 
@@ -1699,6 +1694,50 @@ static njs_unit_test_t  njs_test[] =
                  "    a = b"),
       nxt_string("abcdefghijklmnop") },
 
+    /* Escape strings. */
+
+    { nxt_string("'\\a \\' \\\" \\\\ \\0 \\b \\f \\n \\r \\t \\v'"),
+      nxt_string("a ' \" \\ \0 \b \f \n \r \t \v") },
+
+    { nxt_string("'a\\\nb'"),
+      nxt_string("ab") },
+
+    { nxt_string("'a\\\rb'"),
+      nxt_string("ab") },
+
+    { nxt_string("'a\\\r\nb'"),
+      nxt_string("ab") },
+
+    { nxt_string("'\\'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\u03B1'"),
+      nxt_string("α") },
+
+    { nxt_string("'\\u'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\u03B'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\u{61}\\u{3B1}\\u{20AC}'"),
+      nxt_string("aα€") },
+
+    { nxt_string("'\\u'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\u{'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\u{}'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\u{1234567}'"),
+      nxt_string("SyntaxError") },
+
+    { nxt_string("'\\x61'"),
+      nxt_string("a") },
+
     { nxt_string("''.length"),
       nxt_string("0") },
 
index 13f42e16ab418693a1252ba064d1713781115ca1..24e53ff5aa379c7b13107d7b67759b0ab411e8d6 100644 (file)
@@ -57,4 +57,8 @@ nxt_utf8_next(const u_char *p, const u_char *end)
 }
 
 
+#define nxt_utf8_size(u)                                                      \
+    ((u < 0x80) ? 1 : ((u < 0x0800) ? 2 : ((u < 0x10000) ? 3 : 4)))
+
+
 #endif /* _NXT_UTF8_H_INCLUDED_ */