mirror of
https://github.com/jerryscript-project/jerryscript.git
synced 2025-12-15 16:29:21 +00:00
Related to #4186. Some notable changes: - The term 'Error' now strictly refers to native Error objects defined in the ECMA standard, which are ordinary objects. All other uses of 'error' or 'error reference' where the term refers to a thrown value is now called 'exception'. - Simplified the naming scheme of many String API functions. These functions will now also take an 'encoding' argument to specify the desired encoding in which to operate. - Removed the substring-copy-to-buffer functions. These functions behaved awkwardly, as they use character index to specify the start/end positions, and were mostly used incorrectly with byte offsets instead. The functionality can still be replicated with other functions if necessary. - String-to-buffer functions will no longer fail if the buffer is not sufficiently large, the string will instead be cropped. - Fixed the usage of the '_sz' prefix in many API functions. The term 'sz' means zero-terminated string in hungarian notation, this was used incorrectly in many cases. - Renamed most of the public API functions to have shorter, more on-point names, rather than the often too long descriptive names. Functions are now also grouped by the type of value they operate on, where this makes sense. JerryScript-DCO-1.0-Signed-off-by: Dániel Bátyai dbatyai@inf.u-szeged.hu
1829 lines
51 KiB
C
1829 lines
51 KiB
C
/* Copyright JS Foundation and other contributors, http://js.foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "ecma-alloc.h"
|
|
#include "ecma-array-object.h"
|
|
#include "ecma-builtin-helpers.h"
|
|
#include "ecma-builtins.h"
|
|
#include "ecma-conversion.h"
|
|
#include "ecma-exceptions.h"
|
|
#include "ecma-function-object.h"
|
|
#include "ecma-gc.h"
|
|
#include "ecma-globals.h"
|
|
#include "ecma-helpers.h"
|
|
#include "ecma-objects-general.h"
|
|
#include "ecma-objects.h"
|
|
|
|
#include "jrt-libc-includes.h"
|
|
#include "jrt.h"
|
|
#include "lit-char-helpers.h"
|
|
#include "lit-globals.h"
|
|
|
|
#if JERRY_BUILTIN_JSON
|
|
|
|
#define ECMA_BUILTINS_INTERNAL
|
|
#include "ecma-builtins-internal.h"
|
|
|
|
/**
|
|
* This object has a custom dispatch function.
|
|
*/
|
|
#define BUILTIN_CUSTOM_DISPATCH
|
|
|
|
/**
|
|
* List of built-in routine identifiers.
|
|
*/
|
|
enum
|
|
{
|
|
ECMA_BUILTIN_JSON_ROUTINE_START = 0,
|
|
ECMA_BUILTIN_JSON_PARSE,
|
|
ECMA_BUILTIN_JSON_STRINGIFY,
|
|
};
|
|
|
|
#define BUILTIN_INC_HEADER_NAME "ecma-builtin-json.inc.h"
|
|
#define BUILTIN_UNDERSCORED_ID json
|
|
#include "ecma-builtin-internal-routines-template.inc.h"
|
|
|
|
/**
|
|
* The number of expected hexidecimal characters in a hex escape sequence
|
|
*/
|
|
#define ECMA_JSON_HEX_ESCAPE_SEQUENCE_LENGTH (4)
|
|
|
|
/** \addtogroup ecma ECMA
|
|
* @{
|
|
*
|
|
* \addtogroup ecmabuiltins
|
|
* @{
|
|
*
|
|
* \addtogroup json ECMA JSON object built-in
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* JSON token type
|
|
*/
|
|
typedef enum
|
|
{
|
|
TOKEN_INVALID, /**< error token */
|
|
TOKEN_END, /**< end of stream reached */
|
|
TOKEN_NUMBER, /**< JSON number */
|
|
TOKEN_STRING, /**< JSON string */
|
|
TOKEN_NULL, /**< JSON null primitive value */
|
|
TOKEN_TRUE, /**< JSON true primitive value */
|
|
TOKEN_FALSE, /**< JSON false primitive value */
|
|
TOKEN_LEFT_BRACE, /**< JSON left brace */
|
|
TOKEN_RIGHT_BRACE, /**< JSON right brace */
|
|
TOKEN_LEFT_SQUARE, /**< JSON left square bracket */
|
|
TOKEN_RIGHT_SQUARE, /**< JSON right square bracket */
|
|
TOKEN_COMMA, /**< JSON comma */
|
|
TOKEN_COLON /**< JSON colon */
|
|
} ecma_json_token_type_t;
|
|
|
|
/**
|
|
* JSON token
|
|
*/
|
|
typedef struct
|
|
{
|
|
ecma_json_token_type_t type; /**< type of the current token */
|
|
const lit_utf8_byte_t *current_p; /**< current position of the string processed by the parser */
|
|
const lit_utf8_byte_t *end_p; /**< end of the string processed by the parser */
|
|
|
|
/**
|
|
* Fields depending on type.
|
|
*/
|
|
union
|
|
{
|
|
ecma_string_t *string_p; /**< when type is string_token it contains the string */
|
|
ecma_number_t number; /**< when type is number_token, it contains the value of the number */
|
|
} u;
|
|
} ecma_json_token_t;
|
|
|
|
/**
|
|
* Parse and extract string token.
|
|
*/
|
|
static void
|
|
ecma_builtin_json_parse_string (ecma_json_token_t *token_p) /**< token argument */
|
|
{
|
|
const lit_utf8_byte_t *current_p = token_p->current_p;
|
|
const lit_utf8_byte_t *end_p = token_p->end_p;
|
|
|
|
ecma_stringbuilder_t result_builder = ecma_stringbuilder_create ();
|
|
const lit_utf8_byte_t *unappended_p = current_p;
|
|
|
|
while (true)
|
|
{
|
|
if (current_p >= end_p || *current_p <= 0x1f)
|
|
{
|
|
goto invalid_string;
|
|
}
|
|
|
|
if (*current_p == LIT_CHAR_DOUBLE_QUOTE)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (*current_p >= LIT_UTF8_4_BYTE_MARKER)
|
|
{
|
|
ecma_stringbuilder_append_raw (&result_builder, unappended_p, (lit_utf8_size_t) (current_p - unappended_p));
|
|
JERRY_ASSERT (current_p + 4 <= end_p);
|
|
|
|
lit_code_point_t cp;
|
|
lit_utf8_size_t read_size = lit_read_code_point_from_utf8 (current_p, 4, &cp);
|
|
JERRY_ASSERT (read_size == 4);
|
|
|
|
ecma_stringbuilder_append_codepoint (&result_builder, cp);
|
|
current_p += 4;
|
|
|
|
unappended_p = current_p;
|
|
continue;
|
|
}
|
|
|
|
if (*current_p == LIT_CHAR_BACKSLASH)
|
|
{
|
|
ecma_stringbuilder_append_raw (&result_builder, unappended_p, (lit_utf8_size_t) (current_p - unappended_p));
|
|
|
|
current_p++;
|
|
|
|
/* If there is an escape sequence but there's no escapable character just return */
|
|
if (current_p >= end_p)
|
|
{
|
|
goto invalid_string;
|
|
}
|
|
|
|
const lit_utf8_byte_t c = *current_p;
|
|
switch (c)
|
|
{
|
|
case LIT_CHAR_DOUBLE_QUOTE:
|
|
case LIT_CHAR_SLASH:
|
|
case LIT_CHAR_BACKSLASH:
|
|
{
|
|
ecma_stringbuilder_append_byte (&result_builder, c);
|
|
current_p++;
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_B:
|
|
{
|
|
ecma_stringbuilder_append_byte (&result_builder, LIT_CHAR_BS);
|
|
current_p++;
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_F:
|
|
{
|
|
ecma_stringbuilder_append_byte (&result_builder, LIT_CHAR_FF);
|
|
current_p++;
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_N:
|
|
{
|
|
ecma_stringbuilder_append_byte (&result_builder, LIT_CHAR_LF);
|
|
current_p++;
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_R:
|
|
{
|
|
ecma_stringbuilder_append_byte (&result_builder, LIT_CHAR_CR);
|
|
current_p++;
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_T:
|
|
{
|
|
ecma_stringbuilder_append_byte (&result_builder, LIT_CHAR_TAB);
|
|
current_p++;
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_U:
|
|
{
|
|
uint32_t hex_value = lit_char_hex_lookup (current_p + 1, end_p, ECMA_JSON_HEX_ESCAPE_SEQUENCE_LENGTH);
|
|
if (hex_value == UINT32_MAX)
|
|
{
|
|
goto invalid_string;
|
|
}
|
|
|
|
ecma_stringbuilder_append_char (&result_builder, (ecma_char_t) hex_value);
|
|
current_p += ECMA_JSON_HEX_ESCAPE_SEQUENCE_LENGTH + 1;
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
goto invalid_string;
|
|
}
|
|
}
|
|
|
|
unappended_p = current_p;
|
|
continue;
|
|
}
|
|
|
|
current_p++;
|
|
}
|
|
|
|
ecma_stringbuilder_append_raw (&result_builder, unappended_p, (lit_utf8_size_t) (current_p - unappended_p));
|
|
token_p->u.string_p = ecma_stringbuilder_finalize (&result_builder);
|
|
token_p->current_p = current_p + 1;
|
|
token_p->type = TOKEN_STRING;
|
|
return;
|
|
|
|
invalid_string:
|
|
ecma_stringbuilder_destroy (&result_builder);
|
|
} /* ecma_builtin_json_parse_string */
|
|
|
|
/**
|
|
* Parse and extract string token.
|
|
*/
|
|
static void
|
|
ecma_builtin_json_parse_number (ecma_json_token_t *token_p) /**< token argument */
|
|
{
|
|
const lit_utf8_byte_t *current_p = token_p->current_p;
|
|
const lit_utf8_byte_t *end_p = token_p->end_p;
|
|
const lit_utf8_byte_t *start_p = current_p;
|
|
|
|
JERRY_ASSERT (current_p < end_p);
|
|
|
|
if (*current_p == LIT_CHAR_MINUS)
|
|
{
|
|
current_p++;
|
|
}
|
|
|
|
if (current_p >= end_p)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (*current_p == LIT_CHAR_0)
|
|
{
|
|
current_p++;
|
|
|
|
if (current_p < end_p && lit_char_is_decimal_digit (*current_p))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (lit_char_is_decimal_digit (*current_p))
|
|
{
|
|
do
|
|
{
|
|
current_p++;
|
|
} while (current_p < end_p && lit_char_is_decimal_digit (*current_p));
|
|
}
|
|
|
|
if (current_p < end_p && *current_p == LIT_CHAR_DOT)
|
|
{
|
|
current_p++;
|
|
|
|
if (current_p >= end_p || !lit_char_is_decimal_digit (*current_p))
|
|
{
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
current_p++;
|
|
} while (current_p < end_p && lit_char_is_decimal_digit (*current_p));
|
|
}
|
|
|
|
if (current_p < end_p && (*current_p == LIT_CHAR_LOWERCASE_E || *current_p == LIT_CHAR_UPPERCASE_E))
|
|
{
|
|
current_p++;
|
|
|
|
if (current_p < end_p && (*current_p == LIT_CHAR_PLUS || *current_p == LIT_CHAR_MINUS))
|
|
{
|
|
current_p++;
|
|
}
|
|
|
|
if (current_p >= end_p || !lit_char_is_decimal_digit (*current_p))
|
|
{
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
current_p++;
|
|
} while (current_p < end_p && lit_char_is_decimal_digit (*current_p));
|
|
}
|
|
|
|
token_p->type = TOKEN_NUMBER;
|
|
token_p->u.number = ecma_utf8_string_to_number (start_p, (lit_utf8_size_t) (current_p - start_p), 0);
|
|
|
|
token_p->current_p = current_p;
|
|
} /* ecma_builtin_json_parse_number */
|
|
|
|
/**
|
|
* Parse next token.
|
|
*
|
|
* The function fills the fields of the ecma_json_token_t
|
|
* argument and advances the string pointer.
|
|
*/
|
|
static void
|
|
ecma_builtin_json_parse_next_token (ecma_json_token_t *token_p, /**< token argument */
|
|
bool parse_string) /**< strings are allowed to parse */
|
|
{
|
|
const lit_utf8_byte_t *current_p = token_p->current_p;
|
|
const lit_utf8_byte_t *end_p = token_p->end_p;
|
|
token_p->type = TOKEN_INVALID;
|
|
|
|
while (current_p < end_p
|
|
&& (*current_p == LIT_CHAR_SP || *current_p == LIT_CHAR_CR || *current_p == LIT_CHAR_LF
|
|
|| *current_p == LIT_CHAR_TAB))
|
|
{
|
|
current_p++;
|
|
}
|
|
|
|
if (current_p == end_p)
|
|
{
|
|
token_p->type = TOKEN_END;
|
|
return;
|
|
}
|
|
|
|
switch (*current_p)
|
|
{
|
|
case LIT_CHAR_LEFT_BRACE:
|
|
{
|
|
token_p->type = TOKEN_LEFT_BRACE;
|
|
token_p->current_p = current_p + 1;
|
|
return;
|
|
}
|
|
case LIT_CHAR_RIGHT_BRACE:
|
|
{
|
|
token_p->type = TOKEN_RIGHT_BRACE;
|
|
token_p->current_p = current_p + 1;
|
|
return;
|
|
}
|
|
case LIT_CHAR_LEFT_SQUARE:
|
|
{
|
|
token_p->type = TOKEN_LEFT_SQUARE;
|
|
token_p->current_p = current_p + 1;
|
|
return;
|
|
}
|
|
case LIT_CHAR_RIGHT_SQUARE:
|
|
{
|
|
token_p->type = TOKEN_RIGHT_SQUARE;
|
|
token_p->current_p = current_p + 1;
|
|
return;
|
|
}
|
|
case LIT_CHAR_COMMA:
|
|
{
|
|
token_p->type = TOKEN_COMMA;
|
|
token_p->current_p = current_p + 1;
|
|
return;
|
|
}
|
|
case LIT_CHAR_COLON:
|
|
{
|
|
token_p->type = TOKEN_COLON;
|
|
token_p->current_p = current_p + 1;
|
|
return;
|
|
}
|
|
case LIT_CHAR_DOUBLE_QUOTE:
|
|
{
|
|
if (parse_string)
|
|
{
|
|
token_p->current_p = current_p + 1;
|
|
ecma_builtin_json_parse_string (token_p);
|
|
}
|
|
return;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_N:
|
|
{
|
|
lit_utf8_size_t size = lit_get_magic_string_size (LIT_MAGIC_STRING_NULL);
|
|
if (current_p + size <= end_p)
|
|
{
|
|
if (!memcmp (lit_get_magic_string_utf8 (LIT_MAGIC_STRING_NULL), current_p, size))
|
|
{
|
|
token_p->type = TOKEN_NULL;
|
|
token_p->current_p = current_p + size;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_T:
|
|
{
|
|
lit_utf8_size_t size = lit_get_magic_string_size (LIT_MAGIC_STRING_TRUE);
|
|
if (current_p + size <= end_p)
|
|
{
|
|
if (!memcmp (lit_get_magic_string_utf8 (LIT_MAGIC_STRING_TRUE), current_p, size))
|
|
{
|
|
token_p->type = TOKEN_TRUE;
|
|
token_p->current_p = current_p + size;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case LIT_CHAR_LOWERCASE_F:
|
|
{
|
|
lit_utf8_size_t size = lit_get_magic_string_size (LIT_MAGIC_STRING_FALSE);
|
|
if (current_p + size <= end_p)
|
|
{
|
|
if (!memcmp (lit_get_magic_string_utf8 (LIT_MAGIC_STRING_FALSE), current_p, size))
|
|
{
|
|
token_p->type = TOKEN_FALSE;
|
|
token_p->current_p = current_p + size;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
if (*current_p == LIT_CHAR_MINUS || lit_char_is_decimal_digit (*current_p))
|
|
{
|
|
token_p->current_p = current_p;
|
|
ecma_builtin_json_parse_number (token_p);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
} /* ecma_builtin_json_parse_next_token */
|
|
|
|
/**
|
|
* Utility for defining properties.
|
|
*
|
|
* It silently ignores all errors.
|
|
*/
|
|
static void
|
|
ecma_builtin_json_define_value_property (ecma_object_t *obj_p, /**< this object */
|
|
ecma_string_t *property_name_p, /**< property name */
|
|
ecma_value_t value) /**< value */
|
|
{
|
|
ecma_value_t completion_value =
|
|
ecma_builtin_helper_def_prop (obj_p, property_name_p, value, ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE);
|
|
|
|
JERRY_ASSERT (ecma_is_value_boolean (completion_value));
|
|
} /* ecma_builtin_json_define_value_property */
|
|
|
|
/**
|
|
* Parse next value.
|
|
*
|
|
* The function fills the fields of the ecma_json_token_t
|
|
* argument and advances the string pointer.
|
|
*
|
|
* @return ecma_value with the property value
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_parse_value (ecma_json_token_t *token_p) /**< token argument */
|
|
{
|
|
switch (token_p->type)
|
|
{
|
|
case TOKEN_NUMBER:
|
|
{
|
|
return ecma_make_number_value (token_p->u.number);
|
|
}
|
|
case TOKEN_STRING:
|
|
{
|
|
return ecma_make_string_value (token_p->u.string_p);
|
|
}
|
|
case TOKEN_NULL:
|
|
{
|
|
return ECMA_VALUE_NULL;
|
|
}
|
|
case TOKEN_TRUE:
|
|
{
|
|
return ECMA_VALUE_TRUE;
|
|
}
|
|
case TOKEN_FALSE:
|
|
{
|
|
return ECMA_VALUE_FALSE;
|
|
}
|
|
case TOKEN_LEFT_BRACE:
|
|
{
|
|
ecma_object_t *object_p = ecma_op_create_object_object_noarg ();
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, true);
|
|
|
|
if (token_p->type == TOKEN_RIGHT_BRACE)
|
|
{
|
|
return ecma_make_object_value (object_p);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
if (token_p->type != TOKEN_STRING)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ecma_string_t *name_p = token_p->u.string_p;
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, false);
|
|
if (token_p->type != TOKEN_COLON)
|
|
{
|
|
ecma_deref_ecma_string (name_p);
|
|
break;
|
|
}
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, true);
|
|
ecma_value_t value = ecma_builtin_json_parse_value (token_p);
|
|
|
|
if (ecma_is_value_empty (value))
|
|
{
|
|
ecma_deref_ecma_string (name_p);
|
|
break;
|
|
}
|
|
|
|
ecma_builtin_json_define_value_property (object_p, name_p, value);
|
|
ecma_deref_ecma_string (name_p);
|
|
ecma_free_value (value);
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, false);
|
|
if (token_p->type == TOKEN_RIGHT_BRACE)
|
|
{
|
|
return ecma_make_object_value (object_p);
|
|
}
|
|
|
|
if (token_p->type != TOKEN_COMMA)
|
|
{
|
|
break;
|
|
}
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, true);
|
|
}
|
|
|
|
/*
|
|
* Parse error occurred.
|
|
*/
|
|
ecma_deref_object (object_p);
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
case TOKEN_LEFT_SQUARE:
|
|
{
|
|
uint32_t length = 0;
|
|
ecma_object_t *array_p = ecma_op_new_array_object (0);
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, true);
|
|
|
|
if (token_p->type == TOKEN_RIGHT_SQUARE)
|
|
{
|
|
return ecma_make_object_value (array_p);
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
ecma_value_t value = ecma_builtin_json_parse_value (token_p);
|
|
|
|
if (ecma_is_value_empty (value))
|
|
{
|
|
JERRY_ASSERT (token_p->type != TOKEN_STRING);
|
|
break;
|
|
}
|
|
|
|
ecma_value_t completion;
|
|
completion = ecma_builtin_helper_def_prop_by_index (array_p,
|
|
length,
|
|
value,
|
|
ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE);
|
|
JERRY_ASSERT (ecma_is_value_true (completion));
|
|
ecma_free_value (value);
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, false);
|
|
|
|
if (token_p->type == TOKEN_RIGHT_SQUARE)
|
|
{
|
|
return ecma_make_object_value (array_p);
|
|
}
|
|
|
|
if (token_p->type != TOKEN_COMMA)
|
|
{
|
|
JERRY_ASSERT (token_p->type != TOKEN_STRING);
|
|
break;
|
|
}
|
|
|
|
ecma_builtin_json_parse_next_token (token_p, true);
|
|
length++;
|
|
}
|
|
|
|
ecma_deref_object (array_p);
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
default:
|
|
{
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
}
|
|
} /* ecma_builtin_json_parse_value */
|
|
|
|
static ecma_value_t ecma_builtin_json_internalize_process_property (ecma_object_t *reviver_p,
|
|
ecma_object_t *object_p,
|
|
ecma_string_t *prop_name);
|
|
|
|
/**
|
|
* Abstract operation InternalizeJSONProperty
|
|
*
|
|
* See also:
|
|
* ECMA-262 v5, 24.3.1.1
|
|
* ECMA-262 v11, 24.5.1.1
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_internalize_property (ecma_object_t *reviver_p, /**< reviver function */
|
|
ecma_object_t *holder_p, /**< holder object */
|
|
ecma_string_t *name_p) /**< property name */
|
|
{
|
|
JERRY_ASSERT (reviver_p);
|
|
JERRY_ASSERT (holder_p);
|
|
JERRY_ASSERT (name_p);
|
|
|
|
/* 1. */
|
|
ecma_value_t value = ecma_op_object_get (holder_p, name_p);
|
|
|
|
/* 2. */
|
|
if (ECMA_IS_VALUE_ERROR (value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/* 3. */
|
|
if (ecma_is_value_object (value))
|
|
{
|
|
/* 3.a */
|
|
ecma_value_t is_array = ecma_is_value_array (value);
|
|
|
|
#if JERRY_ESNEXT
|
|
if (ECMA_IS_VALUE_ERROR (is_array))
|
|
{
|
|
ecma_free_value (value);
|
|
return is_array;
|
|
}
|
|
#endif /* JERRY_ESNEXT */
|
|
|
|
ecma_object_t *object_p = ecma_get_object_from_value (value);
|
|
|
|
/* 3.c */
|
|
if (ecma_is_value_true (is_array))
|
|
{
|
|
/* 3.c.ii */
|
|
ecma_length_t length;
|
|
ecma_value_t to_len = ecma_op_object_get_length (object_p, &length);
|
|
|
|
/* 3.c.iii */
|
|
#if JERRY_BUILTIN_PROXY
|
|
if (ECMA_IS_VALUE_ERROR (to_len))
|
|
{
|
|
ecma_deref_object (object_p);
|
|
return to_len;
|
|
}
|
|
#endif /* JERRY_BUILTIN_PROXY */
|
|
JERRY_ASSERT (ecma_is_value_empty (to_len));
|
|
|
|
/* 3.c.iv */
|
|
for (ecma_length_t i = 0; i < length; i++)
|
|
{
|
|
ecma_string_t *prop_index = ecma_new_ecma_string_from_length (i);
|
|
ecma_value_t result = ecma_builtin_json_internalize_process_property (reviver_p, object_p, prop_index);
|
|
|
|
ecma_deref_ecma_string (prop_index);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (result))
|
|
{
|
|
ecma_deref_object (object_p);
|
|
return result;
|
|
}
|
|
|
|
JERRY_ASSERT (result == ECMA_VALUE_TRUE);
|
|
}
|
|
}
|
|
/* 3.d */
|
|
else
|
|
{
|
|
ecma_collection_t *props_p =
|
|
ecma_op_object_get_enumerable_property_names (object_p, ECMA_ENUMERABLE_PROPERTY_KEYS);
|
|
#if JERRY_ESNEXT
|
|
if (JERRY_UNLIKELY (props_p == NULL))
|
|
{
|
|
ecma_deref_object (object_p);
|
|
return ECMA_VALUE_ERROR;
|
|
}
|
|
#else /* !JERRY_ESNEXT */
|
|
JERRY_ASSERT (props_p != NULL);
|
|
#endif /* JERRY_ESNEXT */
|
|
|
|
ecma_value_t *buffer_p = props_p->buffer_p;
|
|
|
|
/* 3.d.iii */
|
|
for (uint32_t i = 0; i < props_p->item_count; i++)
|
|
{
|
|
ecma_string_t *property_name_p = ecma_get_string_from_value (buffer_p[i]);
|
|
ecma_value_t result = ecma_builtin_json_internalize_process_property (reviver_p, object_p, property_name_p);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (result))
|
|
{
|
|
ecma_collection_free (props_p);
|
|
ecma_deref_object (object_p);
|
|
return result;
|
|
}
|
|
|
|
JERRY_ASSERT (result == ECMA_VALUE_TRUE);
|
|
}
|
|
|
|
ecma_collection_free (props_p);
|
|
}
|
|
}
|
|
|
|
ecma_value_t arguments_list[2];
|
|
arguments_list[0] = ecma_make_string_value (name_p);
|
|
arguments_list[1] = value;
|
|
|
|
/* 4. */
|
|
ecma_value_t ret_value = ecma_op_function_call (reviver_p, ecma_make_object_value (holder_p), arguments_list, 2);
|
|
ecma_free_value (value);
|
|
return ret_value;
|
|
} /* ecma_builtin_json_internalize_property */
|
|
|
|
/**
|
|
* Part of the InternalizeJSONProperty abstract method.
|
|
*
|
|
* See also:
|
|
* ECMA-262 v5, 15.12.2
|
|
* ECMA-262 v11, 24.5.1.1 in step 2
|
|
*
|
|
* @return ECMA_VALUE_TRUE - if no error occurred.
|
|
* error if one of the operation failed.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_internalize_process_property (ecma_object_t *reviver_p, /**< reviver function */
|
|
ecma_object_t *object_p, /**< holder object */
|
|
ecma_string_t *prop_name) /**< property name */
|
|
{
|
|
/* ES11: 2.b.iii.1 / 2.c.ii.1 */
|
|
ecma_value_t new_element = ecma_builtin_json_internalize_property (reviver_p, object_p, prop_name);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (new_element))
|
|
{
|
|
return new_element;
|
|
}
|
|
|
|
/* ES11: 2.b.iii.2 / 2.c.ii.2 */
|
|
if (ecma_is_value_undefined (new_element))
|
|
{
|
|
/* ES11: 2.b.iii.2.a / 2.c.ii.2.a */
|
|
ecma_value_t delete_val = ecma_op_object_delete (object_p, prop_name, false);
|
|
|
|
#if JERRY_ESNEXT
|
|
if (ECMA_IS_VALUE_ERROR (delete_val))
|
|
{
|
|
return delete_val;
|
|
}
|
|
#endif /* JERRY_ESNEXT */
|
|
|
|
JERRY_ASSERT (ecma_is_value_boolean (delete_val));
|
|
}
|
|
else
|
|
{
|
|
/* ES11: 2.b.iii.3.a / 2.c.ii.3.a */
|
|
ecma_value_t def_value =
|
|
ecma_builtin_helper_def_prop (object_p, prop_name, new_element, ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE);
|
|
ecma_free_value (new_element);
|
|
|
|
#if JERRY_ESNEXT
|
|
if (ECMA_IS_VALUE_ERROR (def_value))
|
|
{
|
|
return def_value;
|
|
}
|
|
#endif /* JERRY_ESNEXT */
|
|
|
|
JERRY_ASSERT (ecma_is_value_boolean (def_value));
|
|
}
|
|
|
|
return ECMA_VALUE_TRUE;
|
|
} /* ecma_builtin_json_internalize_process_property */
|
|
|
|
/**
|
|
* Function to set a string token from the given arguments, fills its fields and advances the string pointer.
|
|
*
|
|
* @return ecma_value_t containing an object or an error massage
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
ecma_value_t
|
|
ecma_builtin_json_parse_buffer (const lit_utf8_byte_t *str_start_p, /**< String to parse */
|
|
lit_utf8_size_t string_size) /**< size of the string */
|
|
{
|
|
ecma_json_token_t token;
|
|
token.current_p = str_start_p;
|
|
token.end_p = str_start_p + string_size;
|
|
|
|
ecma_builtin_json_parse_next_token (&token, true);
|
|
ecma_value_t result = ecma_builtin_json_parse_value (&token);
|
|
|
|
if (!ecma_is_value_empty (result))
|
|
{
|
|
ecma_builtin_json_parse_next_token (&token, false);
|
|
if (token.type == TOKEN_END)
|
|
{
|
|
return result;
|
|
}
|
|
|
|
ecma_free_value (result);
|
|
}
|
|
|
|
return ecma_raise_syntax_error (ECMA_ERR_INVALID_JSON_FORMAT);
|
|
} /* ecma_builtin_json_parse_buffer */
|
|
|
|
/**
|
|
* The JSON object's 'parse' routine
|
|
*
|
|
* See also:
|
|
* ECMA-262 v5, 15.12.2
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_parse (ecma_value_t arg1, /**< string argument */
|
|
ecma_value_t arg2) /**< reviver argument */
|
|
{
|
|
ecma_string_t *text_string_p = ecma_op_to_string (arg1);
|
|
|
|
if (JERRY_UNLIKELY (text_string_p == NULL))
|
|
{
|
|
return ECMA_VALUE_ERROR;
|
|
}
|
|
|
|
ECMA_STRING_TO_UTF8_STRING (text_string_p, str_start_p, string_size);
|
|
ecma_value_t result = ecma_builtin_json_parse_buffer (str_start_p, string_size);
|
|
ECMA_FINALIZE_UTF8_STRING (str_start_p, string_size);
|
|
ecma_deref_ecma_string (text_string_p);
|
|
|
|
if (!ECMA_IS_VALUE_ERROR (result) && ecma_op_is_callable (arg2))
|
|
{
|
|
ecma_object_t *object_p = ecma_op_create_object_object_noarg ();
|
|
|
|
ecma_property_value_t *prop_value_p;
|
|
prop_value_p = ecma_create_named_data_property (object_p,
|
|
ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY),
|
|
ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE,
|
|
NULL);
|
|
|
|
ecma_named_data_property_assign_value (object_p, prop_value_p, result);
|
|
|
|
ecma_free_value (result);
|
|
result = ecma_builtin_json_internalize_property (ecma_get_object_from_value (arg2),
|
|
object_p,
|
|
ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY));
|
|
ecma_deref_object (object_p);
|
|
}
|
|
|
|
return result;
|
|
} /* ecma_builtin_json_parse */
|
|
|
|
/**
|
|
* Abstract operation 'QuoteJSONString' defined in 24.3.2.2
|
|
*/
|
|
static void
|
|
ecma_builtin_json_quote (ecma_stringbuilder_t *builder_p, /**< builder for the result */
|
|
ecma_string_t *string_p) /**< string that should be quoted */
|
|
{
|
|
ECMA_STRING_TO_UTF8_STRING (string_p, string_buff, string_buff_size);
|
|
const lit_utf8_byte_t *str_p = string_buff;
|
|
const lit_utf8_byte_t *regular_str_start_p = string_buff;
|
|
const lit_utf8_byte_t *str_end_p = str_p + string_buff_size;
|
|
|
|
ecma_stringbuilder_append_byte (builder_p, LIT_CHAR_DOUBLE_QUOTE);
|
|
|
|
while (str_p < str_end_p)
|
|
{
|
|
ecma_char_t c = lit_cesu8_read_next (&str_p);
|
|
|
|
bool should_escape = false;
|
|
|
|
#if JERRY_ESNEXT
|
|
if (lit_is_code_point_utf16_high_surrogate (c))
|
|
{
|
|
if (str_p < str_end_p)
|
|
{
|
|
const ecma_char_t next_ch = lit_cesu8_peek_next (str_p);
|
|
if (lit_is_code_point_utf16_low_surrogate (next_ch))
|
|
{
|
|
str_p += LIT_UTF8_MAX_BYTES_IN_CODE_UNIT;
|
|
continue;
|
|
}
|
|
should_escape = true;
|
|
}
|
|
else
|
|
{
|
|
should_escape = true;
|
|
}
|
|
}
|
|
else if (lit_is_code_point_utf16_low_surrogate (c))
|
|
{
|
|
should_escape = true;
|
|
}
|
|
#endif /* JERRY_ESNEXT */
|
|
|
|
if (c == LIT_CHAR_BACKSLASH || c == LIT_CHAR_DOUBLE_QUOTE)
|
|
{
|
|
ecma_stringbuilder_append_raw (builder_p,
|
|
regular_str_start_p,
|
|
(lit_utf8_size_t) (str_p - regular_str_start_p - 1));
|
|
regular_str_start_p = str_p;
|
|
ecma_stringbuilder_append_byte (builder_p, LIT_CHAR_BACKSLASH);
|
|
ecma_stringbuilder_append_byte (builder_p, (lit_utf8_byte_t) c);
|
|
}
|
|
else if (c < LIT_CHAR_SP || should_escape)
|
|
{
|
|
/**
|
|
* In ES10 we should escape high or low surrogate characters,
|
|
* so we shouldn't append the unescaped character to the stringbuilder
|
|
*/
|
|
uint8_t offset = should_escape ? LIT_UTF8_MAX_BYTES_IN_CODE_UNIT : 1;
|
|
|
|
ecma_stringbuilder_append_raw (builder_p,
|
|
regular_str_start_p,
|
|
(lit_utf8_size_t) (str_p - regular_str_start_p - offset));
|
|
|
|
regular_str_start_p = str_p;
|
|
|
|
switch (c)
|
|
{
|
|
case LIT_CHAR_BS:
|
|
{
|
|
ecma_stringbuilder_append_raw (builder_p, (lit_utf8_byte_t *) "\\b", 2);
|
|
break;
|
|
}
|
|
case LIT_CHAR_FF:
|
|
{
|
|
ecma_stringbuilder_append_raw (builder_p, (lit_utf8_byte_t *) "\\f", 2);
|
|
break;
|
|
}
|
|
case LIT_CHAR_LF:
|
|
{
|
|
ecma_stringbuilder_append_raw (builder_p, (lit_utf8_byte_t *) "\\n", 2);
|
|
break;
|
|
}
|
|
case LIT_CHAR_CR:
|
|
{
|
|
ecma_stringbuilder_append_raw (builder_p, (lit_utf8_byte_t *) "\\r", 2);
|
|
break;
|
|
}
|
|
case LIT_CHAR_TAB:
|
|
{
|
|
ecma_stringbuilder_append_raw (builder_p, (lit_utf8_byte_t *) "\\t", 2);
|
|
break;
|
|
}
|
|
default: /* Hexadecimal. */
|
|
{
|
|
lit_char_unicode_escape (builder_p, c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ecma_stringbuilder_append_raw (builder_p, regular_str_start_p, (lit_utf8_size_t) (str_end_p - regular_str_start_p));
|
|
ecma_stringbuilder_append_byte (builder_p, LIT_CHAR_DOUBLE_QUOTE);
|
|
|
|
ECMA_FINALIZE_UTF8_STRING (string_buff, string_buff_size);
|
|
} /* ecma_builtin_json_quote */
|
|
|
|
static ecma_value_t ecma_builtin_json_serialize_property (ecma_json_stringify_context_t *context_p,
|
|
ecma_object_t *holder_p,
|
|
ecma_string_t *key_p);
|
|
|
|
/**
|
|
* Abstract operation 'SerializeJSONObject' defined in 24.3.2.3
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_serialize_object (ecma_json_stringify_context_t *context_p, /**< context*/
|
|
ecma_object_t *obj_p) /**< the object*/
|
|
{
|
|
/* 1. */
|
|
if (ecma_json_has_object_in_stack (context_p->occurrence_stack_last_p, obj_p))
|
|
{
|
|
return ecma_raise_type_error (ECMA_ERR_THE_STRUCTURE_IS_CYCLICAL);
|
|
}
|
|
|
|
/* 2. */
|
|
ecma_json_occurrence_stack_item_t stack_item;
|
|
stack_item.next_p = context_p->occurrence_stack_last_p;
|
|
stack_item.object_p = obj_p;
|
|
context_p->occurrence_stack_last_p = &stack_item;
|
|
|
|
/* 3. - 4.*/
|
|
const lit_utf8_size_t stepback_size = ecma_stringbuilder_get_size (&context_p->indent_builder);
|
|
ecma_stringbuilder_append (&context_p->indent_builder, context_p->gap_str_p);
|
|
|
|
const bool has_gap = !ecma_compare_ecma_string_to_magic_id (context_p->gap_str_p, LIT_MAGIC_STRING__EMPTY);
|
|
const lit_utf8_size_t separator_size = ecma_stringbuilder_get_size (&context_p->indent_builder);
|
|
|
|
ecma_collection_t *property_keys_p;
|
|
/* 5. */
|
|
if (context_p->property_list_p != NULL)
|
|
{
|
|
property_keys_p = context_p->property_list_p;
|
|
}
|
|
/* 6. */
|
|
else
|
|
{
|
|
property_keys_p = ecma_op_object_get_enumerable_property_names (obj_p, ECMA_ENUMERABLE_PROPERTY_KEYS);
|
|
|
|
#if JERRY_BUILTIN_PROXY
|
|
if (property_keys_p == NULL)
|
|
{
|
|
return ECMA_VALUE_ERROR;
|
|
}
|
|
#endif /* JERRY_BUILTIN_PROXY */
|
|
}
|
|
|
|
/* 8. */
|
|
ecma_value_t *buffer_p = property_keys_p->buffer_p;
|
|
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_LEFT_BRACE);
|
|
const lit_utf8_size_t left_brace = ecma_stringbuilder_get_size (&context_p->result_builder);
|
|
lit_utf8_size_t last_prop = left_brace;
|
|
ecma_value_t result = ECMA_VALUE_EMPTY;
|
|
|
|
for (uint32_t i = 0; i < property_keys_p->item_count; i++)
|
|
{
|
|
if (has_gap)
|
|
{
|
|
ecma_stringbuilder_append_raw (&context_p->result_builder,
|
|
ecma_stringbuilder_get_data (&context_p->indent_builder),
|
|
separator_size);
|
|
}
|
|
|
|
ecma_string_t *key_p = ecma_get_string_from_value (buffer_p[i]);
|
|
ecma_builtin_json_quote (&context_p->result_builder, key_p);
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_COLON);
|
|
|
|
/* 8.c.iii */
|
|
if (has_gap)
|
|
{
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_SP);
|
|
}
|
|
|
|
result = ecma_builtin_json_serialize_property (context_p, obj_p, key_p);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (result))
|
|
{
|
|
goto cleanup;
|
|
}
|
|
|
|
/* 8.b */
|
|
if (!ecma_is_value_undefined (result))
|
|
{
|
|
/* ecma_builtin_json_serialize_property already appended the result. */
|
|
JERRY_ASSERT (ecma_is_value_empty (result));
|
|
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_COMMA);
|
|
last_prop = ecma_stringbuilder_get_size (&context_p->result_builder);
|
|
}
|
|
else
|
|
{
|
|
/* The property should not be appended, we must backtrack. */
|
|
ecma_stringbuilder_revert (&context_p->result_builder, last_prop);
|
|
}
|
|
}
|
|
|
|
if (last_prop != left_brace)
|
|
{
|
|
/* Remove the last comma. */
|
|
ecma_stringbuilder_revert (&context_p->result_builder, last_prop - 1);
|
|
|
|
if (has_gap)
|
|
{
|
|
/* We appended at least one element, and have a separator, so must append the stepback. */
|
|
ecma_stringbuilder_append_raw (&context_p->result_builder,
|
|
ecma_stringbuilder_get_data (&context_p->indent_builder),
|
|
stepback_size);
|
|
}
|
|
}
|
|
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_RIGHT_BRACE);
|
|
result = ECMA_VALUE_EMPTY;
|
|
|
|
/* 11. */
|
|
context_p->occurrence_stack_last_p = stack_item.next_p;
|
|
|
|
/* 12. */
|
|
ecma_stringbuilder_revert (&context_p->indent_builder, stepback_size);
|
|
|
|
cleanup:
|
|
if (context_p->property_list_p == NULL)
|
|
{
|
|
ecma_collection_free (property_keys_p);
|
|
}
|
|
|
|
return result;
|
|
} /* ecma_builtin_json_serialize_object */
|
|
|
|
/**
|
|
* Abstract operation 'SerializeJSONArray' defined in 24.3.2.4
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_serialize_array (ecma_json_stringify_context_t *context_p, /**< context*/
|
|
ecma_object_t *obj_p) /**< the array object*/
|
|
{
|
|
#ifndef JERRY_NDEBUG
|
|
ecma_value_t obj_value = ecma_make_object_value (obj_p);
|
|
ecma_value_t is_array = ecma_is_value_array (obj_value);
|
|
|
|
JERRY_ASSERT (ecma_is_value_true (is_array));
|
|
#endif /* !JERRY_NDEBUG */
|
|
|
|
/* 1. */
|
|
if (ecma_json_has_object_in_stack (context_p->occurrence_stack_last_p, obj_p))
|
|
{
|
|
return ecma_raise_type_error (ECMA_ERR_THE_STRUCTURE_IS_CYCLICAL);
|
|
}
|
|
|
|
/* 2. */
|
|
ecma_json_occurrence_stack_item_t stack_item;
|
|
stack_item.next_p = context_p->occurrence_stack_last_p;
|
|
stack_item.object_p = obj_p;
|
|
context_p->occurrence_stack_last_p = &stack_item;
|
|
|
|
/* 3. - 4.*/
|
|
const lit_utf8_size_t stepback_size = ecma_stringbuilder_get_size (&context_p->indent_builder);
|
|
ecma_stringbuilder_append (&context_p->indent_builder, context_p->gap_str_p);
|
|
const lit_utf8_size_t separator_size = ecma_stringbuilder_get_size (&context_p->indent_builder);
|
|
|
|
const bool has_gap = !ecma_compare_ecma_string_to_magic_id (context_p->gap_str_p, LIT_MAGIC_STRING__EMPTY);
|
|
|
|
/* 6. */
|
|
ecma_length_t array_length;
|
|
ecma_value_t length_value = ecma_op_object_get_length (obj_p, &array_length);
|
|
|
|
#if JERRY_BUILTIN_PROXY
|
|
if (ECMA_IS_VALUE_ERROR (length_value))
|
|
{
|
|
return length_value;
|
|
}
|
|
#endif /* JERRY_BUILTIN_PROXY */
|
|
JERRY_ASSERT (ecma_is_value_empty (length_value));
|
|
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_LEFT_SQUARE);
|
|
|
|
const lit_utf8_size_t left_square = ecma_stringbuilder_get_size (&context_p->result_builder);
|
|
lit_utf8_size_t last_prop = left_square;
|
|
|
|
/* 8. - 9. */
|
|
for (ecma_length_t index = 0; index < array_length; index++)
|
|
{
|
|
/* 9.a */
|
|
ecma_string_t *index_str_p = ecma_new_ecma_string_from_length (index);
|
|
|
|
if (has_gap)
|
|
{
|
|
ecma_stringbuilder_append_raw (&context_p->result_builder,
|
|
ecma_stringbuilder_get_data (&context_p->indent_builder),
|
|
separator_size);
|
|
}
|
|
|
|
ecma_value_t result = ecma_builtin_json_serialize_property (context_p, obj_p, index_str_p);
|
|
ecma_deref_ecma_string (index_str_p);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (result))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
if (ecma_is_value_undefined (result))
|
|
{
|
|
/* 9.c */
|
|
ecma_stringbuilder_append_magic (&context_p->result_builder, LIT_MAGIC_STRING_NULL);
|
|
}
|
|
else
|
|
{
|
|
JERRY_ASSERT (ecma_is_value_empty (result));
|
|
}
|
|
|
|
last_prop = ecma_stringbuilder_get_size (&context_p->result_builder);
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_COMMA);
|
|
}
|
|
|
|
/* Remove the last comma. */
|
|
ecma_stringbuilder_revert (&context_p->result_builder, last_prop);
|
|
|
|
/* 11.b.iii */
|
|
if (last_prop != left_square && has_gap)
|
|
{
|
|
/* We appended at least one element, and have a separator, so must append the stepback. */
|
|
ecma_stringbuilder_append_raw (&context_p->result_builder,
|
|
ecma_stringbuilder_get_data (&context_p->indent_builder),
|
|
stepback_size);
|
|
}
|
|
|
|
ecma_stringbuilder_append_byte (&context_p->result_builder, LIT_CHAR_RIGHT_SQUARE);
|
|
|
|
/* 12. */
|
|
context_p->occurrence_stack_last_p = stack_item.next_p;
|
|
|
|
/* 13. */
|
|
ecma_stringbuilder_revert (&context_p->indent_builder, stepback_size);
|
|
|
|
return ECMA_VALUE_EMPTY;
|
|
} /* ecma_builtin_json_serialize_array */
|
|
|
|
/**
|
|
* Abstract operation 'SerializeJSONProperty' defined in 24.3.2.1
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_serialize_property (ecma_json_stringify_context_t *context_p, /**< context*/
|
|
ecma_object_t *holder_p, /**< the object*/
|
|
ecma_string_t *key_p) /**< property key*/
|
|
{
|
|
/* 1. */
|
|
ecma_value_t value = ecma_op_object_get (holder_p, key_p);
|
|
|
|
/* 2. */
|
|
if (ECMA_IS_VALUE_ERROR (value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/* 3. */
|
|
if (ecma_is_value_object (value) || ecma_is_value_bigint (value))
|
|
{
|
|
ecma_value_t to_object_value = ecma_op_to_object (value);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (to_object_value))
|
|
{
|
|
ecma_free_value (value);
|
|
return to_object_value;
|
|
}
|
|
|
|
ecma_object_t *value_obj_p = ecma_get_object_from_value (to_object_value);
|
|
ecma_value_t to_json =
|
|
ecma_op_object_get_with_receiver (value_obj_p, ecma_get_magic_string (LIT_MAGIC_STRING_TO_JSON_UL), value);
|
|
|
|
ecma_deref_object (value_obj_p);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (to_json))
|
|
{
|
|
ecma_free_value (value);
|
|
return to_json;
|
|
}
|
|
|
|
/* 3.c */
|
|
if (ecma_op_is_callable (to_json))
|
|
{
|
|
ecma_value_t key_value = ecma_make_string_value (key_p);
|
|
ecma_value_t call_args[] = { key_value };
|
|
ecma_object_t *to_json_obj_p = ecma_get_object_from_value (to_json);
|
|
|
|
ecma_value_t result = ecma_op_function_call (to_json_obj_p, value, call_args, 1);
|
|
ecma_free_value (value);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (result))
|
|
{
|
|
ecma_deref_object (to_json_obj_p);
|
|
return result;
|
|
}
|
|
value = result;
|
|
}
|
|
ecma_free_value (to_json);
|
|
}
|
|
|
|
/* 4. */
|
|
if (context_p->replacer_function_p)
|
|
{
|
|
ecma_value_t holder_value = ecma_make_object_value (holder_p);
|
|
ecma_value_t key_value = ecma_make_string_value (key_p);
|
|
ecma_value_t call_args[] = { key_value, value };
|
|
|
|
ecma_value_t result = ecma_op_function_call (context_p->replacer_function_p, holder_value, call_args, 2);
|
|
ecma_free_value (value);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (result))
|
|
{
|
|
return result;
|
|
}
|
|
|
|
value = result;
|
|
}
|
|
|
|
/* 5. */
|
|
if (ecma_is_value_object (value))
|
|
{
|
|
ecma_object_t *obj_p = ecma_get_object_from_value (value);
|
|
|
|
if (ecma_get_object_base_type (obj_p) == ECMA_OBJECT_BASE_TYPE_CLASS)
|
|
{
|
|
switch (((ecma_extended_object_t *) obj_p)->u.cls.type)
|
|
{
|
|
/* 5.a */
|
|
case ECMA_OBJECT_CLASS_NUMBER:
|
|
{
|
|
ecma_number_t num;
|
|
value = ecma_op_to_number (value, &num);
|
|
ecma_deref_object (obj_p);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (value))
|
|
{
|
|
return value;
|
|
}
|
|
|
|
value = ecma_make_number_value (num);
|
|
break;
|
|
}
|
|
/* 5.b */
|
|
case ECMA_OBJECT_CLASS_STRING:
|
|
{
|
|
ecma_string_t *str_p = ecma_op_to_string (value);
|
|
ecma_deref_object (obj_p);
|
|
|
|
if (JERRY_UNLIKELY (str_p == NULL))
|
|
{
|
|
return ECMA_VALUE_ERROR;
|
|
}
|
|
|
|
value = ecma_make_string_value (str_p);
|
|
break;
|
|
}
|
|
/* 5.c */
|
|
case ECMA_OBJECT_CLASS_BOOLEAN:
|
|
{
|
|
ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) obj_p;
|
|
value = ext_object_p->u.cls.u3.value;
|
|
ecma_deref_object (obj_p);
|
|
break;
|
|
}
|
|
#if JERRY_BUILTIN_BIGINT
|
|
/* 5.d */
|
|
case ECMA_OBJECT_CLASS_BIGINT:
|
|
{
|
|
ecma_extended_object_t *ext_object_p = (ecma_extended_object_t *) obj_p;
|
|
value = ecma_copy_value (ext_object_p->u.cls.u3.value);
|
|
ecma_deref_object (obj_p);
|
|
break;
|
|
}
|
|
#endif /* JERRY_BUILTIN_BIGINT */
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 6. - 8. */
|
|
if (ecma_is_value_null (value))
|
|
{
|
|
ecma_stringbuilder_append_magic (&context_p->result_builder, LIT_MAGIC_STRING_NULL);
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
|
|
if (ecma_is_value_true (value))
|
|
{
|
|
ecma_stringbuilder_append_magic (&context_p->result_builder, LIT_MAGIC_STRING_TRUE);
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
|
|
if (ecma_is_value_false (value))
|
|
{
|
|
ecma_stringbuilder_append_magic (&context_p->result_builder, LIT_MAGIC_STRING_FALSE);
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
|
|
/* 9. */
|
|
if (ecma_is_value_string (value))
|
|
{
|
|
ecma_string_t *value_str_p = ecma_get_string_from_value (value);
|
|
/* Quote will append the result. */
|
|
ecma_builtin_json_quote (&context_p->result_builder, value_str_p);
|
|
ecma_deref_ecma_string (value_str_p);
|
|
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
|
|
/* 10. */
|
|
if (ecma_is_value_number (value))
|
|
{
|
|
ecma_number_t num_value = ecma_get_number_from_value (value);
|
|
|
|
/* 10.a */
|
|
if (!ecma_number_is_nan (num_value) && !ecma_number_is_infinity (num_value))
|
|
{
|
|
ecma_string_t *result_string_p = ecma_op_to_string (value);
|
|
JERRY_ASSERT (result_string_p != NULL);
|
|
|
|
ecma_stringbuilder_append (&context_p->result_builder, result_string_p);
|
|
ecma_deref_ecma_string (result_string_p);
|
|
}
|
|
else
|
|
{
|
|
/* 10.b */
|
|
ecma_stringbuilder_append_magic (&context_p->result_builder, LIT_MAGIC_STRING_NULL);
|
|
}
|
|
|
|
ecma_free_value (value);
|
|
return ECMA_VALUE_EMPTY;
|
|
}
|
|
|
|
#if JERRY_BUILTIN_BIGINT
|
|
if (ecma_is_value_bigint (value))
|
|
{
|
|
ecma_free_value (value);
|
|
return ecma_raise_type_error (ECMA_ERR_BIGINT_SERIALIZED);
|
|
}
|
|
#endif /* JERRY_BUILTIN_BIGINT */
|
|
|
|
/* 11. */
|
|
if (ecma_is_value_object (value) && !ecma_op_is_callable (value))
|
|
{
|
|
ecma_value_t is_array = ecma_is_value_array (value);
|
|
|
|
#if JERRY_BUILTIN_BIGINT
|
|
if (ECMA_IS_VALUE_ERROR (is_array))
|
|
{
|
|
ecma_free_value (value);
|
|
return is_array;
|
|
}
|
|
#endif /* JERRY_BUILTIN_BIGINT */
|
|
|
|
ecma_object_t *obj_p = ecma_get_object_from_value (value);
|
|
|
|
ecma_value_t ret_value;
|
|
/* 10.a */
|
|
if (ecma_is_value_true (is_array))
|
|
{
|
|
ret_value = ecma_builtin_json_serialize_array (context_p, obj_p);
|
|
}
|
|
/* 10.b */
|
|
else
|
|
{
|
|
ret_value = ecma_builtin_json_serialize_object (context_p, obj_p);
|
|
}
|
|
|
|
ecma_deref_object (obj_p);
|
|
return ret_value;
|
|
}
|
|
|
|
/* 12. */
|
|
ecma_free_value (value);
|
|
return ECMA_VALUE_UNDEFINED;
|
|
} /* ecma_builtin_json_serialize_property */
|
|
|
|
/**
|
|
* Helper function to stringify an object in JSON format representing an ecma_value.
|
|
*
|
|
* @return ecma_value_t string created from an abject formating by a given context
|
|
* Returned value must be freed with ecma_free_value.
|
|
*
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_str_helper (ecma_json_stringify_context_t *context_p, /**< context argument */
|
|
const ecma_value_t arg1) /**< object argument */
|
|
{
|
|
ecma_value_t ret_value = ECMA_VALUE_EMPTY;
|
|
ecma_object_t *obj_wrapper_p = ecma_op_create_object_object_noarg ();
|
|
ecma_string_t *empty_str_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY);
|
|
ecma_value_t put_comp_val =
|
|
ecma_builtin_helper_def_prop (obj_wrapper_p, empty_str_p, arg1, ECMA_PROPERTY_CONFIGURABLE_ENUMERABLE_WRITABLE);
|
|
|
|
JERRY_ASSERT (ecma_is_value_true (put_comp_val));
|
|
|
|
context_p->result_builder = ecma_stringbuilder_create ();
|
|
|
|
if (!ecma_compare_ecma_string_to_magic_id (context_p->gap_str_p, LIT_MAGIC_STRING__EMPTY))
|
|
{
|
|
ecma_stringbuilder_append_byte (&context_p->indent_builder, LIT_CHAR_LF);
|
|
}
|
|
|
|
ret_value = ecma_builtin_json_serialize_property (context_p, obj_wrapper_p, empty_str_p);
|
|
ecma_deref_object (obj_wrapper_p);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (ret_value) || ecma_is_value_undefined (ret_value))
|
|
{
|
|
ecma_stringbuilder_destroy (&context_p->result_builder);
|
|
return ret_value;
|
|
}
|
|
|
|
return ecma_make_string_value (ecma_stringbuilder_finalize (&context_p->result_builder));
|
|
} /* ecma_builtin_json_str_helper */
|
|
|
|
/**
|
|
* Function to create a JSON string from a JS value.
|
|
*
|
|
* Note:
|
|
* The returned value must be freed with ecma_free_value.
|
|
*
|
|
* @return - ecma_value_t containing a json string.
|
|
* - Error value in case of any errors.
|
|
*/
|
|
ecma_value_t
|
|
ecma_builtin_json_stringify_no_opts (const ecma_value_t value) /**< value to stringify */
|
|
{
|
|
ecma_json_stringify_context_t context;
|
|
context.occurrence_stack_last_p = NULL;
|
|
context.indent_builder = ecma_stringbuilder_create ();
|
|
context.property_list_p = NULL;
|
|
context.replacer_function_p = NULL;
|
|
context.gap_str_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY);
|
|
|
|
ecma_value_t ret_value = ecma_builtin_json_str_helper (&context, value);
|
|
|
|
ecma_deref_ecma_string (context.gap_str_p);
|
|
ecma_stringbuilder_destroy (&context.indent_builder);
|
|
return ret_value;
|
|
} /* ecma_builtin_json_stringify_no_opts */
|
|
|
|
/**
|
|
* The JSON object's 'stringify' routine
|
|
*
|
|
* See also:
|
|
* ECMA-262 v5, 15.12.3
|
|
* ECMA-262 v11, 24.5.2
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
static ecma_value_t
|
|
ecma_builtin_json_stringify (ecma_value_t arg1, /**< value */
|
|
ecma_value_t arg2, /**< replacer */
|
|
ecma_value_t arg3) /**< space */
|
|
{
|
|
ecma_json_stringify_context_t context;
|
|
context.replacer_function_p = NULL;
|
|
context.property_list_p = NULL;
|
|
|
|
/* 4. */
|
|
if (ecma_is_value_object (arg2))
|
|
{
|
|
ecma_object_t *obj_p = ecma_get_object_from_value (arg2);
|
|
|
|
/* 4.a */
|
|
if (ecma_op_is_callable (arg2))
|
|
{
|
|
context.replacer_function_p = obj_p;
|
|
}
|
|
/* 4.b */
|
|
else
|
|
{
|
|
ecma_value_t is_array = ecma_is_value_array (arg2);
|
|
|
|
#if JERRY_ESNEXT
|
|
if (ECMA_IS_VALUE_ERROR (is_array))
|
|
{
|
|
return is_array;
|
|
}
|
|
#endif /* JERRY_ESNEXT */
|
|
|
|
if (ecma_is_value_true (is_array))
|
|
{
|
|
ecma_length_t array_length;
|
|
ecma_value_t to_len = ecma_op_object_get_length (obj_p, &array_length);
|
|
|
|
#if JERRY_BUILTIN_PROXY
|
|
if (ECMA_IS_VALUE_ERROR (to_len))
|
|
{
|
|
return to_len;
|
|
}
|
|
#endif /* JERRY_BUILTIN_PROXY */
|
|
JERRY_ASSERT (ecma_is_value_empty (to_len));
|
|
|
|
context.property_list_p = ecma_new_collection ();
|
|
|
|
uint32_t index = 0;
|
|
|
|
/* 4.b.iii.5 */
|
|
while (index < array_length)
|
|
{
|
|
ecma_value_t value = ecma_op_object_get_by_index (obj_p, index);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (value))
|
|
{
|
|
ecma_collection_free (context.property_list_p);
|
|
return value;
|
|
}
|
|
|
|
/* 4.b.iii.5.c */
|
|
ecma_value_t item = ECMA_VALUE_UNDEFINED;
|
|
|
|
/* 4.b.iii.5.d */
|
|
if (ecma_is_value_string (value))
|
|
{
|
|
ecma_ref_ecma_string (ecma_get_string_from_value (value));
|
|
item = value;
|
|
}
|
|
/* 4.b.iii.5.e */
|
|
else if (ecma_is_value_number (value))
|
|
{
|
|
ecma_string_t *number_str_p = ecma_op_to_string (value);
|
|
JERRY_ASSERT (number_str_p != NULL);
|
|
item = ecma_make_string_value (number_str_p);
|
|
}
|
|
/* 4.b.iii.5.f */
|
|
else if (ecma_is_value_object (value))
|
|
{
|
|
ecma_object_t *value_obj_p = ecma_get_object_from_value (value);
|
|
|
|
if (ecma_get_object_base_type (value_obj_p) == ECMA_OBJECT_BASE_TYPE_CLASS)
|
|
{
|
|
uint8_t class_type = ((ecma_extended_object_t *) value_obj_p)->u.cls.type;
|
|
|
|
if (class_type == ECMA_OBJECT_CLASS_NUMBER || class_type == ECMA_OBJECT_CLASS_STRING)
|
|
{
|
|
ecma_string_t *str_p = ecma_op_to_string (value);
|
|
|
|
if (JERRY_UNLIKELY (str_p == NULL))
|
|
{
|
|
ecma_collection_free (context.property_list_p);
|
|
ecma_free_value (value);
|
|
return ECMA_VALUE_ERROR;
|
|
}
|
|
|
|
item = ecma_make_string_value (str_p);
|
|
}
|
|
}
|
|
}
|
|
|
|
ecma_free_value (value);
|
|
|
|
/* 4.b.iii.5.g */
|
|
if (!ecma_is_value_undefined (item))
|
|
{
|
|
JERRY_ASSERT (ecma_is_value_string (item));
|
|
ecma_string_t *string_p = ecma_get_string_from_value (item);
|
|
|
|
if (!ecma_collection_has_string_value (context.property_list_p, string_p))
|
|
{
|
|
ecma_collection_push_back (context.property_list_p, item);
|
|
}
|
|
else
|
|
{
|
|
ecma_deref_ecma_string (string_p);
|
|
}
|
|
}
|
|
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ecma_value_t space = ECMA_VALUE_EMPTY;
|
|
|
|
/* 5. */
|
|
if (ecma_is_value_object (arg3))
|
|
{
|
|
ecma_object_t *obj_p = ecma_get_object_from_value (arg3);
|
|
|
|
if (ecma_get_object_base_type (obj_p) == ECMA_OBJECT_BASE_TYPE_CLASS)
|
|
{
|
|
uint8_t class_type = ((ecma_extended_object_t *) obj_p)->u.cls.type;
|
|
|
|
/* 5.a */
|
|
if (class_type == ECMA_OBJECT_CLASS_NUMBER)
|
|
{
|
|
ecma_number_t num;
|
|
ecma_value_t value = ecma_op_to_number (arg3, &num);
|
|
|
|
if (ECMA_IS_VALUE_ERROR (value))
|
|
{
|
|
if (context.property_list_p != NULL)
|
|
{
|
|
ecma_collection_free (context.property_list_p);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
space = ecma_make_number_value (num);
|
|
}
|
|
/* 5.b */
|
|
else if (class_type == ECMA_OBJECT_CLASS_STRING)
|
|
{
|
|
ecma_string_t *value_str_p = ecma_op_to_string (arg3);
|
|
|
|
if (JERRY_UNLIKELY (value_str_p == NULL))
|
|
{
|
|
if (context.property_list_p != NULL)
|
|
{
|
|
ecma_collection_free (context.property_list_p);
|
|
}
|
|
return ECMA_VALUE_ERROR;
|
|
}
|
|
|
|
space = ecma_make_string_value (value_str_p);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (space == ECMA_VALUE_EMPTY)
|
|
{
|
|
space = ecma_copy_value (arg3);
|
|
}
|
|
|
|
/* 6. */
|
|
if (ecma_is_value_number (space))
|
|
{
|
|
/* 6.a */
|
|
ecma_number_t num_of_spaces;
|
|
ecma_op_to_integer (space, &num_of_spaces);
|
|
|
|
num_of_spaces = JERRY_MIN (10, num_of_spaces);
|
|
|
|
/* 6.b */
|
|
if (num_of_spaces < 1)
|
|
{
|
|
context.gap_str_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY);
|
|
}
|
|
else
|
|
{
|
|
JMEM_DEFINE_LOCAL_ARRAY (space_buff, num_of_spaces, char);
|
|
|
|
memset (space_buff, LIT_CHAR_SP, (size_t) num_of_spaces);
|
|
context.gap_str_p =
|
|
ecma_new_ecma_string_from_utf8 ((lit_utf8_byte_t *) space_buff, (lit_utf8_size_t) num_of_spaces);
|
|
|
|
JMEM_FINALIZE_LOCAL_ARRAY (space_buff);
|
|
}
|
|
}
|
|
/* 7. */
|
|
else if (ecma_is_value_string (space))
|
|
{
|
|
ecma_string_t *space_str_p = ecma_get_string_from_value (space);
|
|
lit_utf8_size_t num_of_chars = ecma_string_get_length (space_str_p);
|
|
|
|
if (num_of_chars < 10)
|
|
{
|
|
ecma_ref_ecma_string (space_str_p);
|
|
context.gap_str_p = space_str_p;
|
|
}
|
|
else
|
|
{
|
|
context.gap_str_p = ecma_string_substr (space_str_p, 0, 10);
|
|
}
|
|
}
|
|
/* 8. */
|
|
else
|
|
{
|
|
context.gap_str_p = ecma_get_magic_string (LIT_MAGIC_STRING__EMPTY);
|
|
}
|
|
|
|
ecma_free_value (space);
|
|
|
|
/* 1., 2., 3. */
|
|
context.occurrence_stack_last_p = NULL;
|
|
context.indent_builder = ecma_stringbuilder_create ();
|
|
|
|
/* 9. */
|
|
ecma_value_t ret_value = ecma_builtin_json_str_helper (&context, arg1);
|
|
|
|
ecma_deref_ecma_string (context.gap_str_p);
|
|
ecma_stringbuilder_destroy (&context.indent_builder);
|
|
|
|
if (context.property_list_p != NULL)
|
|
{
|
|
ecma_collection_free (context.property_list_p);
|
|
}
|
|
|
|
return ret_value;
|
|
} /* ecma_builtin_json_stringify */
|
|
|
|
/**
|
|
* Dispatcher of the built-in's routines
|
|
*
|
|
* @return ecma value
|
|
* Returned value must be freed with ecma_free_value.
|
|
*/
|
|
ecma_value_t
|
|
ecma_builtin_json_dispatch_routine (uint8_t builtin_routine_id, /**< built-in wide routine identifier */
|
|
ecma_value_t this_arg, /**< 'this' argument value */
|
|
const ecma_value_t arguments_list_p[], /**< list of arguments
|
|
* passed to routine */
|
|
uint32_t arguments_number) /**< length of arguments' list */
|
|
{
|
|
JERRY_UNUSED_2 (this_arg, arguments_number);
|
|
|
|
switch (builtin_routine_id)
|
|
{
|
|
case ECMA_BUILTIN_JSON_PARSE:
|
|
{
|
|
return ecma_builtin_json_parse (arguments_list_p[0], arguments_list_p[1]);
|
|
}
|
|
case ECMA_BUILTIN_JSON_STRINGIFY:
|
|
{
|
|
return ecma_builtin_json_stringify (arguments_list_p[0], arguments_list_p[1], arguments_list_p[2]);
|
|
}
|
|
default:
|
|
{
|
|
JERRY_UNREACHABLE ();
|
|
}
|
|
}
|
|
} /* ecma_builtin_json_dispatch_routine */
|
|
|
|
/**
|
|
* @}
|
|
* @}
|
|
* @}
|
|
*/
|
|
|
|
#endif /* JERRY_BUILTIN_JSON */
|