| |
| /* |
| * Copyright (C) Igor Sysoev |
| * Copyright (C) NGINX, Inc. |
| */ |
| |
| |
| #include <njs_main.h> |
| |
| |
| static njs_int_t njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, |
| njs_scope_t type); |
| static void njs_parser_scope_end(njs_vm_t *vm, njs_parser_t *parser); |
| static njs_token_t njs_parser_statement_chain(njs_vm_t *vm, |
| njs_parser_t *parser, njs_token_t token, njs_bool_t top); |
| 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_statement(njs_vm_t *vm, |
| njs_parser_t *parser); |
| static njs_token_t njs_parser_block(njs_vm_t *vm, |
| njs_parser_t *parser, njs_token_t token); |
| static njs_token_t njs_parser_labelled_statement(njs_vm_t *vm, |
| njs_parser_t *parser); |
| static njs_token_t njs_parser_function_declaration(njs_vm_t *vm, |
| njs_parser_t *parser); |
| static njs_token_t njs_parser_lambda_arguments(njs_vm_t *vm, |
| njs_parser_t *parser, njs_function_lambda_t *lambda, njs_index_t index, |
| njs_token_t token); |
| static njs_token_t njs_parser_lambda_argument(njs_vm_t *vm, |
| njs_parser_t *parser, njs_index_t index); |
| static njs_token_t njs_parser_lambda_body(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token); |
| static njs_parser_node_t *njs_parser_return_set(njs_vm_t *vm, |
| njs_parser_t *parser, njs_parser_node_t *expr); |
| 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, |
| njs_token_t parent, njs_bool_t var_in); |
| static njs_token_t njs_parser_if_statement(njs_vm_t *vm, njs_parser_t *parser); |
| static njs_token_t njs_parser_switch_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_var_in_statement(njs_vm_t *vm, |
| njs_parser_t *parser, njs_parser_node_t *name); |
| static njs_token_t njs_parser_for_in_statement(njs_vm_t *vm, |
| njs_parser_t *parser, njs_str_t *name, njs_token_t token); |
| static njs_token_t njs_parser_brk_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_try_block(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_import_statement(njs_vm_t *vm, |
| njs_parser_t *parser); |
| static njs_token_t njs_parser_export_statement(njs_vm_t *vm, |
| njs_parser_t *parser); |
| static njs_int_t njs_parser_export_sink(njs_vm_t *vm, njs_parser_t *parser); |
| static njs_token_t njs_parser_grouping_expression(njs_vm_t *vm, |
| njs_parser_t *parser); |
| |
| |
| #define njs_parser_chain_current(parser) \ |
| ((parser)->node) |
| |
| #define njs_parser_chain_top(parser) \ |
| ((parser)->scope->top) |
| |
| #define njs_parser_chain_top_set(parser, node) \ |
| (parser)->scope->top = node |
| |
| |
| njs_int_t |
| njs_parser(njs_vm_t *vm, njs_parser_t *parser, njs_parser_t *prev) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_parser_node_t *node; |
| |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_GLOBAL); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_ERROR; |
| } |
| |
| if (prev != NULL) { |
| /* |
| * Copy the global scope variables from the previous |
| * iteration of the accumulative mode. |
| */ |
| ret = njs_variables_copy(vm, &parser->scope->variables, |
| &prev->scope->variables); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return ret; |
| } |
| } |
| |
| token = njs_parser_token(vm, parser); |
| |
| while (token != NJS_TOKEN_END) { |
| |
| token = njs_parser_statement_chain(vm, parser, token, 1); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return NJS_ERROR; |
| } |
| |
| if (token == NJS_TOKEN_CLOSE_BRACE && vm->options.trailer) { |
| parser->lexer->start--; |
| break; |
| } |
| } |
| |
| node = njs_parser_chain_top(parser); |
| |
| if (node == NULL) { |
| /* Empty string, just semicolons or variables declarations. */ |
| |
| node = njs_parser_node_new(vm, parser, 0); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| njs_parser_chain_top_set(parser, node); |
| } |
| |
| node->token = NJS_TOKEN_END; |
| |
| if (njs_slow_path(parser->count != 0)) { |
| njs_internal_error(vm, "parser->count != 0"); |
| return NJS_ERROR; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_int_t |
| njs_parser_scope_begin(njs_vm_t *vm, njs_parser_t *parser, njs_scope_t type) |
| { |
| njs_arr_t *values; |
| njs_uint_t nesting; |
| njs_lexer_t *lexer; |
| njs_parser_scope_t *scope, *parent; |
| |
| nesting = 0; |
| |
| if (type == NJS_SCOPE_FUNCTION) { |
| |
| for (scope = parser->scope; scope != NULL; scope = scope->parent) { |
| |
| if (scope->type == NJS_SCOPE_FUNCTION) { |
| nesting = scope->nesting + 1; |
| |
| if (nesting < NJS_MAX_NESTING) { |
| break; |
| } |
| |
| njs_parser_syntax_error(vm, parser, |
| "The maximum function nesting " |
| "level is \"%d\"", NJS_MAX_NESTING); |
| |
| return NJS_ERROR; |
| } |
| } |
| } |
| |
| scope = njs_mp_zalloc(vm->mem_pool, sizeof(njs_parser_scope_t)); |
| if (njs_slow_path(scope == NULL)) { |
| return NJS_ERROR; |
| } |
| |
| scope->type = type; |
| |
| if (type == NJS_SCOPE_FUNCTION) { |
| scope->next_index[0] = type; |
| scope->next_index[1] = NJS_SCOPE_CLOSURE + nesting |
| + sizeof(njs_value_t); |
| |
| } else { |
| if (type == NJS_SCOPE_GLOBAL) { |
| type += NJS_INDEX_GLOBAL_OFFSET; |
| } |
| |
| scope->next_index[0] = type; |
| scope->next_index[1] = 0; |
| } |
| |
| scope->nesting = nesting; |
| scope->argument_closures = 0; |
| |
| njs_queue_init(&scope->nested); |
| njs_lvlhsh_init(&scope->labels); |
| njs_lvlhsh_init(&scope->variables); |
| njs_lvlhsh_init(&scope->references); |
| |
| values = NULL; |
| |
| if (scope->type < NJS_SCOPE_BLOCK) { |
| values = njs_arr_create(vm->mem_pool, 4, sizeof(njs_value_t)); |
| if (njs_slow_path(values == NULL)) { |
| return NJS_ERROR; |
| } |
| } |
| |
| scope->values[0] = values; |
| scope->values[1] = NULL; |
| |
| lexer = parser->lexer; |
| |
| if (lexer->file.length != 0) { |
| njs_file_basename(&lexer->file, &scope->file); |
| njs_file_dirname(&lexer->file, &scope->cwd); |
| } |
| |
| parent = parser->scope; |
| scope->parent = parent; |
| parser->scope = scope; |
| |
| if (parent != NULL) { |
| njs_queue_insert_tail(&parent->nested, &scope->link); |
| |
| if (nesting == 0) { |
| /* Inherit function nesting in blocks. */ |
| scope->nesting = parent->nesting; |
| } |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| static void |
| njs_parser_scope_end(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_parser_scope_t *scope, *parent; |
| |
| scope = parser->scope; |
| |
| parent = scope->parent; |
| parser->scope = parent; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_statement_chain(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token, njs_bool_t top) |
| { |
| njs_parser_node_t *stmt, *last, *node, *new_node, **child; |
| |
| child = top ? &njs_parser_chain_top(parser) |
| : &njs_parser_chain_current(parser); |
| |
| last = *child; |
| |
| njs_parser_enter(vm, parser); |
| |
| token = njs_parser_statement(vm, parser, token); |
| |
| njs_parser_leave(parser); |
| |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return njs_parser_unexpected_token(vm, parser, token); |
| } |
| |
| if (parser->node == NULL) { |
| /* The statement is empty block or just semicolon. */ |
| return token; |
| } |
| |
| new_node = parser->node; |
| |
| if (new_node->hoist) { |
| child = &njs_parser_chain_top(parser); |
| |
| while (*child != NULL) { |
| node = *child; |
| |
| if (node->hoist) { |
| break; |
| } |
| |
| child = &node->left; |
| } |
| |
| last = *child; |
| } |
| |
| stmt = njs_parser_node_new(vm, parser, NJS_TOKEN_STATEMENT); |
| if (njs_slow_path(stmt == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| stmt->hoist = new_node->hoist; |
| stmt->left = last; |
| stmt->right = new_node; |
| |
| *child = stmt; |
| |
| while (token == NJS_TOKEN_SEMICOLON) { |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| break; |
| } |
| } |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_statement(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token) |
| { |
| size_t offset; |
| |
| parser->node = NULL; |
| |
| switch (token) { |
| |
| case NJS_TOKEN_FUNCTION: |
| return njs_parser_function_declaration(vm, parser); |
| |
| case NJS_TOKEN_IF: |
| return njs_parser_if_statement(vm, parser); |
| |
| case NJS_TOKEN_SWITCH: |
| return njs_parser_switch_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_SEMICOLON: |
| return njs_parser_token(vm, parser); |
| |
| case NJS_TOKEN_OPEN_BRACE: |
| return njs_parser_block_statement(vm, parser); |
| |
| case NJS_TOKEN_CLOSE_BRACE: |
| if (vm->options.trailer) { |
| parser->node = NULL; |
| njs_thread_log_debug("BLOCK END"); |
| return token; |
| } |
| |
| /* Fall through. */ |
| |
| default: |
| |
| switch (token) { |
| case NJS_TOKEN_VAR: |
| token = njs_parser_var_statement(vm, parser, token, 0); |
| break; |
| |
| case NJS_TOKEN_RETURN: |
| token = njs_parser_return_statement(vm, parser); |
| break; |
| |
| case NJS_TOKEN_THROW: |
| token = njs_parser_throw_statement(vm, parser); |
| break; |
| |
| case NJS_TOKEN_CONTINUE: |
| case NJS_TOKEN_BREAK: |
| token = njs_parser_brk_statement(vm, parser, token); |
| break; |
| |
| case NJS_TOKEN_IMPORT: |
| token = njs_parser_import_statement(vm, parser); |
| break; |
| |
| case NJS_TOKEN_EXPORT: |
| token = njs_parser_export_statement(vm, parser); |
| break; |
| |
| case NJS_TOKEN_NAME: |
| offset = 0; |
| if (njs_parser_peek_token(vm, parser, &offset) == NJS_TOKEN_COLON) { |
| return njs_parser_labelled_statement(vm, parser); |
| } |
| |
| /* Fall through. */ |
| |
| default: |
| token = njs_parser_expression(vm, parser, token); |
| break; |
| } |
| |
| if (njs_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(vm, 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_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_parser_node_t *node; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_BLOCK); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parser->node = NULL; |
| |
| while (token != NJS_TOKEN_CLOSE_BRACE) { |
| token = njs_parser_statement_chain(vm, parser, token, 0); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_BLOCK); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = parser->node; |
| node->right = NULL; |
| parser->node = node; |
| |
| njs_parser_scope_end(vm, parser); |
| |
| return njs_parser_token(vm, parser); |
| } |
| |
| |
| static njs_token_t |
| njs_parser_block(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) |
| { |
| if (token == NJS_TOKEN_FUNCTION) { |
| njs_parser_syntax_error(vm, parser, |
| "Functions can only be declared at top level or inside a block"); |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| return njs_parser_statement(vm, parser, token); |
| } |
| |
| |
| static njs_parser_node_t * |
| njs_parser_variable_node(njs_vm_t *vm, njs_parser_t *parser, njs_str_t *name, |
| uint32_t hash, njs_variable_type_t type) |
| { |
| njs_int_t ret; |
| njs_variable_t *var; |
| njs_parser_node_t *node; |
| |
| var = njs_variable_add(vm, parser->scope, name, hash, type); |
| if (njs_slow_path(var == NULL)) { |
| return NULL; |
| } |
| |
| if (njs_is_null(&var->value)) { |
| |
| switch (type) { |
| |
| case NJS_VARIABLE_VAR: |
| njs_set_undefined(&var->value); |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_NAME); |
| if (njs_slow_path(node == NULL)) { |
| return NULL; |
| } |
| |
| ret = njs_variable_reference(vm, parser->scope, node, name, hash, |
| NJS_DECLARATION); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NULL; |
| } |
| |
| return node; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_labelled_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| uint32_t hash; |
| njs_int_t ret; |
| njs_str_t name; |
| njs_token_t token; |
| njs_variable_t *label; |
| |
| name = *njs_parser_text(parser); |
| hash = njs_parser_key_hash(parser); |
| |
| label = njs_label_find(vm, parser->scope, &name, hash); |
| if (njs_slow_path(label != NULL)) { |
| njs_parser_syntax_error(vm, parser, "Label \"%V\" " |
| "has already been declared", &name); |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| label = njs_label_add(vm, parser->scope, &name, hash); |
| if (njs_slow_path(label == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_COLON); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_statement(vm, parser, token); |
| |
| if (njs_fast_path(token > NJS_TOKEN_ILLEGAL)) { |
| |
| if (parser->node != NULL) { |
| /* The statement is not empty block or just semicolon. */ |
| |
| ret = njs_name_copy(vm, &parser->node->name, &name); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| ret = njs_label_remove(vm, parser->scope, &name, hash); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| } |
| } |
| |
| return token; |
| } |
| |
| |
| static njs_function_t * |
| njs_parser_function_alloc(njs_vm_t *vm, njs_parser_t *parser, |
| njs_variable_t *var) |
| { |
| njs_value_t *value; |
| njs_function_t *function; |
| njs_function_lambda_t *lambda; |
| |
| lambda = njs_function_lambda_alloc(vm, 1); |
| if (njs_slow_path(lambda == NULL)) { |
| njs_memory_error(vm); |
| return NULL; |
| } |
| |
| /* TODO: |
| * njs_function_t is used to pass lambda to |
| * njs_generate_function_declaration() and is not actually needed. |
| * real njs_function_t is created by njs_vmcode_function() in runtime. |
| */ |
| |
| function = njs_function_alloc(vm, lambda, NULL, 1); |
| if (njs_slow_path(function == NULL)) { |
| return NULL; |
| } |
| |
| njs_set_function(&var->value, function); |
| |
| if (var->index != NJS_INDEX_NONE |
| && njs_scope_accumulative(vm, parser->scope)) |
| { |
| value = (njs_value_t *) var->index; |
| *value = var->value; |
| } |
| |
| return function; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_function_declaration(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_variable_t *var; |
| njs_function_t *function; |
| njs_parser_node_t *node; |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FUNCTION); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->token_line = njs_parser_token_line(parser); |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_NAME) { |
| if (njs_parser_restricted_identifier(token)) { |
| njs_parser_syntax_error(vm, parser, "Identifier \"%V\" " |
| "is forbidden in function declaration", |
| njs_parser_text(parser)); |
| } |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| var = njs_parser_variable_add(vm, parser, NJS_VARIABLE_FUNCTION); |
| if (njs_slow_path(var == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| ret = njs_parser_variable_reference(vm, parser, node, NJS_DECLARATION); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| parser->node = node; |
| |
| function = njs_parser_function_alloc(vm, parser, var); |
| if (njs_slow_path(function == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_function_lambda(vm, parser, function->u.lambda, token); |
| |
| function->args_count = function->u.lambda->nargs |
| - function->u.lambda->rest_parameters; |
| |
| return token; |
| } |
| |
| |
| njs_token_t |
| njs_parser_function_expression(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_variable_t *var; |
| njs_function_t *function; |
| njs_parser_node_t *node; |
| njs_function_lambda_t *lambda; |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FUNCTION_EXPRESSION); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->token_line = njs_parser_token_line(parser); |
| parser->node = node; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| /* |
| * An optional function expression name is stored |
| * in intermediate shim scope. |
| */ |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_SHIM); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| if (token == NJS_TOKEN_NAME) { |
| var = njs_parser_variable_add(vm, parser, NJS_VARIABLE_SHIM); |
| if (njs_slow_path(var == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| function = njs_parser_function_alloc(vm, parser, var); |
| if (njs_slow_path(function == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| lambda = function->u.lambda; |
| |
| } else { |
| /* Anonymous function. */ |
| lambda = njs_function_lambda_alloc(vm, 1); |
| if (njs_slow_path(lambda == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| } |
| |
| node->u.value.data.u.lambda = lambda; |
| |
| token = njs_parser_function_lambda(vm, parser, lambda, token); |
| |
| njs_parser_scope_end(vm, parser); |
| |
| return token; |
| } |
| |
| |
| njs_token_t |
| njs_parser_function_lambda(njs_vm_t *vm, njs_parser_t *parser, |
| njs_function_lambda_t *lambda, njs_token_t token) |
| { |
| njs_int_t ret; |
| njs_index_t index; |
| |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_FUNCTION); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| index = NJS_SCOPE_ARGUMENTS; |
| |
| /* A "this" reservation. */ |
| index += sizeof(njs_value_t); |
| |
| token = njs_parser_lambda_arguments(vm, parser, lambda, index, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_lambda_body(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| njs_parser_scope_end(vm, parser); |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_lambda_arguments(njs_vm_t *vm, njs_parser_t *parser, |
| njs_function_lambda_t *lambda, njs_index_t index, njs_token_t token) |
| { |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| lambda->nargs = 0; |
| |
| while (token != NJS_TOKEN_CLOSE_PARENTHESIS) { |
| |
| if (njs_slow_path(lambda->rest_parameters)) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| if (njs_slow_path(token == NJS_TOKEN_ELLIPSIS)) { |
| lambda->rest_parameters = 1; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| } |
| |
| if (njs_slow_path(token != NJS_TOKEN_NAME)) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_lambda_argument(vm, parser, index); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token == NJS_TOKEN_COMMA) { |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| } |
| |
| lambda->nargs++; |
| index += sizeof(njs_value_t); |
| } |
| |
| return njs_parser_token(vm, parser); |
| } |
| |
| |
| static njs_token_t |
| njs_parser_lambda_argument(njs_vm_t *vm, njs_parser_t *parser, |
| njs_index_t index) |
| { |
| njs_int_t ret; |
| njs_variable_t *arg; |
| |
| arg = njs_parser_variable_add(vm, parser, NJS_VARIABLE_VAR); |
| if (njs_slow_path(arg == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| if (arg->index > 0) { |
| njs_parser_syntax_error(vm, parser, "Duplicate parameter names"); |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| arg->index = index; |
| |
| ret = njs_name_copy(vm, &arg->name, njs_parser_text(parser)); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| return njs_parser_token(vm, parser); |
| } |
| |
| |
| static njs_token_t |
| njs_parser_lambda_body(njs_vm_t *vm, njs_parser_t *parser, njs_token_t token) |
| { |
| njs_parser_node_t *body, *last, *parent; |
| |
| parent = parser->node; |
| |
| token = njs_parser_lambda_statements(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| last = NULL; |
| body = njs_parser_chain_top(parser); |
| |
| if (body != NULL) { |
| /* Take the last function body statement. */ |
| last = body->right; |
| |
| if (last == NULL) { |
| /* |
| * The last statement is terminated by semicolon. |
| * Take the last statement itself. |
| */ |
| last = body->left; |
| } |
| } |
| |
| if (last == NULL || last->token != NJS_TOKEN_RETURN) { |
| /* |
| * There is no function body or the last function body |
| * body statement is not "return" statement. |
| */ |
| body = njs_parser_return_set(vm, parser, NULL); |
| if (njs_slow_path(body == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| } |
| |
| parent->right = body; |
| |
| parser->node = parent; |
| |
| return token; |
| } |
| |
| |
| static njs_parser_node_t * |
| njs_parser_return_set(njs_vm_t *vm, njs_parser_t *parser, |
| njs_parser_node_t *expr) |
| { |
| njs_parser_node_t *stmt, *node; |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_RETURN); |
| if (njs_slow_path(node == NULL)) { |
| return NULL; |
| } |
| |
| node->right = expr; |
| |
| stmt = njs_parser_node_new(vm, parser, NJS_TOKEN_STATEMENT); |
| if (njs_slow_path(stmt == NULL)) { |
| return NULL; |
| } |
| |
| stmt->left = njs_parser_chain_top(parser); |
| stmt->right = node; |
| |
| njs_parser_chain_top_set(parser, stmt); |
| |
| return stmt; |
| } |
| |
| |
| njs_token_t |
| njs_parser_lambda_statements(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token) |
| { |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_BRACE); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| parser->node = NULL; |
| |
| while (token != NJS_TOKEN_CLOSE_BRACE) { |
| token = njs_parser_statement_chain(vm, parser, token, 1); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| } |
| |
| return njs_parser_token(vm, parser); |
| } |
| |
| |
| 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; |
| njs_parser_scope_t *scope; |
| |
| for (scope = parser->scope; |
| scope != NULL; |
| scope = scope->parent) |
| { |
| if (scope->type == NJS_SCOPE_FUNCTION && !scope->module) { |
| break; |
| } |
| |
| if (scope->type == NJS_SCOPE_GLOBAL) { |
| njs_parser_syntax_error(vm, parser, "Illegal return statement"); |
| |
| return NJS_ERROR; |
| } |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_RETURN); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parser->node = node; |
| |
| token = njs_lexer_token(vm, parser->lexer); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| switch (token) { |
| |
| case NJS_TOKEN_LINE_END: |
| return njs_parser_token(vm, parser); |
| |
| case NJS_TOKEN_SEMICOLON: |
| case NJS_TOKEN_CLOSE_BRACE: |
| case NJS_TOKEN_END: |
| return token; |
| |
| default: |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_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; |
| |
| return token; |
| } |
| } |
| |
| |
| static njs_token_t |
| njs_parser_var_statement(njs_vm_t *vm, njs_parser_t *parser, njs_token_t parent, |
| njs_bool_t var_in) |
| { |
| njs_token_t token; |
| njs_parser_node_t *left, *stmt, *name, *assign, *expr; |
| njs_variable_type_t type; |
| |
| type = NJS_VARIABLE_VAR; |
| |
| parser->node = NULL; |
| left = NULL; |
| |
| do { |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_NAME) { |
| if (njs_parser_restricted_identifier(token)) { |
| njs_parser_syntax_error(vm, parser, "Identifier \"%V\" " |
| "is forbidden in var declaration", |
| njs_parser_text(parser)); |
| } |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| name = njs_parser_variable_node(vm, parser, |
| njs_parser_text(parser), |
| njs_parser_key_hash(parser), |
| type); |
| if (njs_slow_path(name == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (var_in) { |
| if (token == NJS_TOKEN_IN) { |
| return njs_parser_var_in_statement(vm, parser, name); |
| } |
| |
| var_in = 0; |
| } |
| |
| expr = NULL; |
| |
| if (token == NJS_TOKEN_ASSIGNMENT) { |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_assignment_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| expr = parser->node; |
| } |
| |
| assign = njs_parser_node_new(vm, parser, parent); |
| if (njs_slow_path(assign == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| assign->u.operation = NJS_VMCODE_MOVE; |
| assign->left = name; |
| assign->right = expr; |
| |
| stmt = njs_parser_node_new(vm, parser, NJS_TOKEN_STATEMENT); |
| if (njs_slow_path(stmt == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| stmt->left = left; |
| stmt->right = assign; |
| parser->node = stmt; |
| |
| 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; |
| |
| token = njs_parser_grouping_expression(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| cond = parser->node; |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token == NJS_TOKEN_ELSE) { |
| |
| stmt = parser->node; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_BRANCHING); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = stmt; |
| node->right = parser->node; |
| parser->node = node; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_IF); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = cond; |
| node->right = parser->node; |
| parser->node = node; |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_switch_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_token_t token; |
| njs_parser_node_t *node, *swtch, *branch, *dflt, **last; |
| |
| node = NULL; |
| branch = NULL; |
| dflt = NULL; |
| |
| token = njs_parser_grouping_expression(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| swtch = njs_parser_node_new(vm, parser, NJS_TOKEN_SWITCH); |
| if (njs_slow_path(swtch == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| swtch->left = parser->node; |
| last = &swtch->right; |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_BRACE); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| while (token != NJS_TOKEN_CLOSE_BRACE) { |
| |
| if (token == NJS_TOKEN_CASE || token == NJS_TOKEN_DEFAULT) { |
| |
| do { |
| node = njs_parser_node_new(vm, parser, 0); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| if (token == NJS_TOKEN_CASE) { |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node->left = parser->node; |
| |
| branch = njs_parser_node_new(vm, parser, 0); |
| if (njs_slow_path(branch == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| branch->right = node; |
| |
| } else { |
| if (dflt != NULL) { |
| njs_parser_syntax_error(vm, parser, |
| "More than one default clause " |
| "in switch statement"); |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| branch = node; |
| branch->token = NJS_TOKEN_DEFAULT; |
| dflt = branch; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| } |
| |
| *last = branch; |
| last = &branch->left; |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_COLON); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| } while (token == NJS_TOKEN_CASE || token == NJS_TOKEN_DEFAULT); |
| |
| parser->node = NULL; |
| |
| if (token == NJS_TOKEN_CLOSE_BRACE) { |
| break; |
| } |
| |
| } else if (branch == NULL) { |
| /* The first switch statment is not "case/default" keyword. */ |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_statement_chain(vm, parser, token, 0); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node->right = parser->node; |
| } |
| |
| parser->node = swtch; |
| |
| return njs_parser_token(vm, parser); |
| } |
| |
| |
| 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; |
| |
| token = njs_parser_grouping_expression(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| cond = parser->node; |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_WHILE); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = parser->node; |
| node->right = cond; |
| parser->node = node; |
| |
| 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; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| stmt = parser->node; |
| |
| if (njs_slow_path(token != NJS_TOKEN_WHILE)) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_grouping_expression(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_DO); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = stmt; |
| node->right = parser->node; |
| parser->node = node; |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_for_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_str_t name; |
| njs_token_t token; |
| njs_parser_node_t *node, *init, *condition, *update, *cond, *body; |
| |
| init = NULL; |
| condition = NULL; |
| update = NULL; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_SEMICOLON) { |
| |
| if (token == NJS_TOKEN_VAR) { |
| |
| token = njs_parser_var_statement(vm, parser, token, 1); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| init = parser->node; |
| |
| if (init->token == NJS_TOKEN_FOR_IN) { |
| goto done; |
| } |
| |
| } else { |
| |
| name = *njs_parser_text(parser); |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| init = parser->node; |
| |
| if (init->token == NJS_TOKEN_IN) { |
| token = njs_parser_for_in_statement(vm, parser, &name, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| goto done; |
| } |
| } |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_SEMICOLON); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_SEMICOLON) { |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| condition = parser->node; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_SEMICOLON); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_CLOSE_PARENTHESIS) { |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| update = parser->node; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_CLOSE_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FOR); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| cond = njs_parser_node_new(vm, parser, 0); |
| if (njs_slow_path(cond == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| body = njs_parser_node_new(vm, parser, 0); |
| if (njs_slow_path(body == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = init; |
| node->right = cond; |
| |
| cond->left = condition; |
| cond->right = body; |
| |
| body->left = parser->node; |
| body->right = update; |
| |
| parser->node = node; |
| |
| done: |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_var_in_statement(njs_vm_t *vm, njs_parser_t *parser, |
| njs_parser_node_t *name) |
| { |
| njs_token_t token; |
| njs_parser_node_t *node, *foreach; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_IN); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = name; |
| node->right = parser->node; |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_CLOSE_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| foreach = njs_parser_node_new(vm, parser, NJS_TOKEN_FOR_IN); |
| if (njs_slow_path(foreach == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| foreach->left = node; |
| foreach->right = parser->node; |
| |
| parser->node = foreach; |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_for_in_statement(njs_vm_t *vm, njs_parser_t *parser, njs_str_t *name, |
| njs_token_t token) |
| { |
| njs_parser_node_t *node; |
| |
| node = parser->node->left; |
| |
| if (node->token != NJS_TOKEN_NAME) { |
| njs_parser_ref_error(vm, parser, "Invalid left-hand side \"%V\" " |
| "in for-in statement", name); |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FOR_IN); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->left = parser->node; |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_CLOSE_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_block(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node->right = parser->node; |
| parser->node = node; |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_brk_statement(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token) |
| { |
| uint32_t hash; |
| njs_int_t ret; |
| njs_str_t name; |
| njs_parser_node_t *node; |
| |
| node = njs_parser_node_new(vm, parser, token); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->token_line = njs_parser_token_line(parser); |
| parser->node = node; |
| |
| token = njs_lexer_token(vm, parser->lexer); |
| |
| switch (token) { |
| |
| case NJS_TOKEN_LINE_END: |
| return njs_parser_token(vm, parser); |
| |
| case NJS_TOKEN_NAME: |
| name = *njs_parser_text(parser); |
| hash = njs_parser_key_hash(parser); |
| |
| if (njs_label_find(vm, parser->scope, &name, hash) == NULL) { |
| njs_parser_syntax_error(vm, parser, "Undefined label \"%V\"", |
| &name); |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| ret = njs_name_copy(vm, &parser->node->name, &name); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| return njs_parser_token(vm, parser); |
| |
| case NJS_TOKEN_SEMICOLON: |
| case NJS_TOKEN_CLOSE_BRACE: |
| case NJS_TOKEN_END: |
| return token; |
| |
| default: |
| /* TODO: LABEL */ |
| return NJS_TOKEN_ILLEGAL; |
| } |
| } |
| |
| |
| static njs_token_t |
| njs_parser_try_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_parser_node_t *node, *try, *catch; |
| |
| token = njs_parser_try_block(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| try = njs_parser_node_new(vm, parser, NJS_TOKEN_TRY); |
| if (njs_slow_path(try == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| try->left = parser->node; |
| |
| if (token == NJS_TOKEN_CATCH) { |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_NAME) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| catch = njs_parser_node_new(vm, parser, NJS_TOKEN_CATCH); |
| if (njs_slow_path(catch == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| try->right = catch; |
| |
| /* |
| * The "catch" clause creates a block scope for single variable |
| * which receives exception value. |
| */ |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_BLOCK); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node = njs_parser_variable_node(vm, parser, njs_parser_text(parser), |
| njs_parser_key_hash(parser), |
| NJS_VARIABLE_CATCH); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| catch->left = node; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (njs_slow_path(token != NJS_TOKEN_CLOSE_PARENTHESIS)) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_try_block(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| catch->right = parser->node; |
| |
| njs_parser_scope_end(vm, parser); |
| } |
| |
| if (token == NJS_TOKEN_FINALLY) { |
| |
| token = njs_parser_try_block(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FINALLY); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->right = parser->node; |
| |
| if (try->right != NULL) { |
| node->left = try->right; |
| } |
| |
| try->right = node; |
| } |
| |
| if (try->right == NULL) { |
| njs_parser_syntax_error(vm, parser, |
| "Missing catch or finally after try"); |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| parser->node = try; |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_try_block(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_token_t token; |
| njs_parser_node_t *node; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token != NJS_TOKEN_OPEN_BRACE)) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_block_statement(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node = parser->node; |
| |
| if (node != NULL && node->token == NJS_TOKEN_BLOCK) { |
| parser->node = node->left; |
| |
| njs_mp_free(vm->mem_pool, node); |
| } |
| |
| return token; |
| } |
| |
| |
| 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; |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_THROW); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_lexer_token(vm, parser->lexer); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| switch (token) { |
| |
| case NJS_TOKEN_LINE_END: |
| njs_parser_syntax_error(vm, parser, "Illegal newline after throw"); |
| return NJS_TOKEN_ILLEGAL; |
| |
| default: |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node->right = parser->node; |
| parser->node = node; |
| |
| return token; |
| } |
| } |
| |
| |
| static njs_token_t |
| njs_parser_import_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_parser_node_t *name, *import; |
| |
| if (parser->scope->type != NJS_SCOPE_GLOBAL |
| && !parser->scope->module) |
| { |
| njs_parser_syntax_error(vm, parser, "Illegal import statement"); |
| |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_NAME) { |
| njs_parser_syntax_error(vm, parser, |
| "Non-default import is not supported"); |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| name = njs_parser_variable_node(vm, parser, njs_parser_text(parser), |
| njs_parser_key_hash(parser), |
| NJS_VARIABLE_VAR); |
| if (njs_slow_path(name == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_match_name(vm, parser, token, "from"); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token != NJS_TOKEN_STRING) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| ret = njs_parser_module(vm, parser); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| import = njs_parser_node_new(vm, parser, NJS_TOKEN_IMPORT); |
| if (njs_slow_path(import == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| import->left = name; |
| import->right = parser->node; |
| |
| parser->node = import; |
| parser->node->hoist = 1; |
| |
| return njs_parser_token(vm, parser); |
| } |
| |
| |
| njs_token_t |
| njs_parser_module_lambda(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_int_t ret; |
| njs_token_t token; |
| njs_parser_node_t *node, *parent; |
| njs_function_lambda_t *lambda; |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FUNCTION_EXPRESSION); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->token_line = njs_parser_token_line(parser); |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| lambda = njs_function_lambda_alloc(vm, 1); |
| if (njs_slow_path(lambda == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->u.value.data.u.lambda = lambda; |
| parser->node = node; |
| |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_FUNCTION); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parser->scope->module = 1; |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| parent = parser->node; |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_CLOSE_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_lambda_statements(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| ret = njs_parser_export_sink(vm, parser); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parent->right = njs_parser_chain_top(parser); |
| parent->right->token_line = 1; |
| |
| parser->node = parent; |
| |
| njs_parser_scope_end(vm, parser); |
| |
| return token; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_export_statement(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_token_t token; |
| njs_parser_node_t *node; |
| |
| if (!parser->scope->module) { |
| njs_parser_syntax_error(vm, parser, "Illegal export statement"); |
| return NJS_ERROR; |
| } |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_EXPORT); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parser->node = node; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token != NJS_TOKEN_DEFAULT)) { |
| njs_parser_syntax_error(vm, parser, |
| "Non-default export is not supported"); |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| node->right = parser->node; |
| parser->node = node; |
| |
| return token; |
| } |
| |
| |
| static njs_int_t |
| njs_parser_export_sink(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_uint_t n; |
| njs_parser_node_t *node, *prev; |
| |
| n = 0; |
| |
| for (node = njs_parser_chain_top(parser); |
| node != NULL; |
| node = node->left) |
| { |
| if (node->right != NULL |
| && node->right->token == NJS_TOKEN_EXPORT) |
| { |
| n++; |
| } |
| } |
| |
| if (n != 1) { |
| njs_parser_syntax_error(vm, parser, |
| (n == 0) ? "export statement is required" |
| : "Identifier \"default\" has already been declared"); |
| return NJS_ERROR; |
| } |
| |
| node = njs_parser_chain_top(parser); |
| |
| if (node->right && node->right->token == NJS_TOKEN_EXPORT) { |
| return NJS_OK; |
| } |
| |
| prev = njs_parser_chain_top(parser); |
| |
| while (prev->left != NULL) { |
| node = prev->left; |
| |
| if (node->right != NULL |
| && node->right->token == NJS_TOKEN_EXPORT) |
| { |
| prev->left = node->left; |
| break; |
| } |
| |
| prev = prev->left; |
| } |
| |
| node->left = njs_parser_chain_top(parser); |
| njs_parser_chain_top_set(parser, node); |
| |
| return NJS_OK; |
| } |
| |
| |
| static njs_token_t |
| njs_parser_grouping_expression(njs_vm_t *vm, njs_parser_t *parser) |
| { |
| njs_token_t token; |
| |
| token = njs_parser_token(vm, parser); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_OPEN_PARENTHESIS); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| token = njs_parser_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| return njs_parser_match(vm, parser, token, NJS_TOKEN_CLOSE_PARENTHESIS); |
| } |
| |
| |
| njs_int_t |
| njs_parser_match_arrow_expression(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token) |
| { |
| size_t offset; |
| njs_bool_t rest_parameters; |
| |
| if (token != NJS_TOKEN_OPEN_PARENTHESIS && token != NJS_TOKEN_NAME) { |
| return NJS_DECLINED; |
| } |
| |
| offset = 0; |
| |
| if (token == NJS_TOKEN_NAME) { |
| goto arrow; |
| } |
| |
| token = njs_parser_peek_token(vm, parser, &offset); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return NJS_DECLINED; |
| } |
| |
| rest_parameters = 0; |
| |
| while (token != NJS_TOKEN_CLOSE_PARENTHESIS) { |
| |
| if (rest_parameters) { |
| return NJS_DECLINED; |
| } |
| |
| if (njs_slow_path(token == NJS_TOKEN_ELLIPSIS)) { |
| rest_parameters = 1; |
| |
| token = njs_parser_peek_token(vm, parser, &offset); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return NJS_DECLINED; |
| } |
| } |
| |
| if (njs_slow_path(token != NJS_TOKEN_NAME)) { |
| return NJS_DECLINED; |
| } |
| |
| token = njs_parser_peek_token(vm, parser, &offset); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token == NJS_TOKEN_COMMA) { |
| token = njs_parser_peek_token(vm, parser, &offset); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return NJS_DECLINED; |
| } |
| } |
| } |
| |
| arrow: |
| |
| token = njs_parser_peek_token(vm, parser, &offset); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return NJS_DECLINED; |
| } |
| |
| if (parser->lexer->prev_token == NJS_TOKEN_LINE_END) { |
| return NJS_DECLINED; |
| } |
| |
| if (njs_slow_path(token != NJS_TOKEN_ARROW)) { |
| return NJS_DECLINED; |
| } |
| |
| return NJS_OK; |
| } |
| |
| |
| njs_token_t |
| njs_parser_arrow_expression(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token) |
| { |
| njs_int_t ret; |
| njs_index_t index; |
| njs_parser_node_t *node, *body, *parent; |
| njs_function_lambda_t *lambda; |
| |
| node = njs_parser_node_new(vm, parser, NJS_TOKEN_FUNCTION_EXPRESSION); |
| if (njs_slow_path(node == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->token_line = njs_parser_token_line(parser); |
| parser->node = node; |
| |
| lambda = njs_function_lambda_alloc(vm, 0); |
| if (njs_slow_path(lambda == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| node->u.value.data.u.lambda = lambda; |
| |
| ret = njs_parser_scope_begin(vm, parser, NJS_SCOPE_FUNCTION); |
| if (njs_slow_path(ret != NJS_OK)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parser->scope->arrow_function = 1; |
| |
| index = NJS_SCOPE_ARGUMENTS; |
| |
| /* A "this" reservation. */ |
| index += sizeof(njs_value_t); |
| |
| if (token == NJS_TOKEN_OPEN_PARENTHESIS) { |
| token = njs_parser_lambda_arguments(vm, parser, lambda, index, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| } else { |
| token = njs_parser_lambda_argument(vm, parser, index); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| lambda->nargs = 1; |
| } |
| |
| if (parser->lexer->prev_token == NJS_TOKEN_LINE_END) { |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| token = njs_parser_match(vm, parser, token, NJS_TOKEN_ARROW); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| if (token == NJS_TOKEN_OPEN_BRACE) { |
| token = njs_parser_lambda_body(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| } else { |
| parent = parser->node; |
| |
| token = njs_parser_assignment_expression(vm, parser, token); |
| if (njs_slow_path(token <= NJS_TOKEN_ILLEGAL)) { |
| return token; |
| } |
| |
| body = njs_parser_return_set(vm, parser, parser->node); |
| if (njs_slow_path(body == NULL)) { |
| return NJS_TOKEN_ERROR; |
| } |
| |
| parent->right = body; |
| parser->node = parent; |
| } |
| |
| njs_parser_scope_end(vm, parser); |
| |
| return token; |
| } |
| |
| |
| njs_bool_t |
| njs_parser_has_side_effect(njs_parser_node_t *node) |
| { |
| njs_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 (njs_fast_path(!side_effect)) { |
| return njs_parser_has_side_effect(node->right); |
| } |
| |
| return side_effect; |
| } |
| |
| |
| njs_token_t |
| njs_parser_unexpected_token(njs_vm_t *vm, njs_parser_t *parser, |
| njs_token_t token) |
| { |
| if (token != NJS_TOKEN_END) { |
| njs_parser_syntax_error(vm, parser, "Unexpected token \"%V\"", |
| njs_parser_text(parser)); |
| |
| } else { |
| njs_parser_syntax_error(vm, parser, "Unexpected end of input"); |
| } |
| |
| return NJS_TOKEN_ILLEGAL; |
| } |
| |
| |
| u_char * |
| njs_parser_trace_handler(njs_trace_t *trace, njs_trace_data_t *td, |
| u_char *start) |
| { |
| u_char *p; |
| size_t size; |
| njs_vm_t *vm; |
| njs_lexer_t *lexer; |
| njs_parser_t *parser; |
| |
| size = njs_length("InternalError: "); |
| memcpy(start, "InternalError: ", size); |
| p = start + size; |
| |
| vm = trace->data; |
| |
| trace = trace->next; |
| p = trace->handler(trace, td, p); |
| |
| parser = vm->parser; |
| |
| if (parser != NULL && parser->lexer != NULL) { |
| lexer = parser->lexer; |
| |
| if (lexer->file.length != 0) { |
| njs_internal_error(vm, "%s in %V:%uD", start, &lexer->file, |
| njs_parser_token_line(parser)); |
| } else { |
| njs_internal_error(vm, "%s in %uD", start, |
| njs_parser_token_line(parser)); |
| } |
| |
| } else { |
| njs_internal_error(vm, "%s", start); |
| } |
| |
| return p; |
| } |
| |
| |
| static void |
| njs_parser_scope_error(njs_vm_t *vm, njs_parser_scope_t *scope, |
| njs_object_type_t type, uint32_t line, const char *fmt, va_list args) |
| { |
| size_t width; |
| u_char msg[NJS_MAX_ERROR_STR]; |
| u_char *p, *end; |
| njs_str_t *file; |
| |
| file = &scope->file; |
| |
| p = msg; |
| end = msg + NJS_MAX_ERROR_STR; |
| |
| p = njs_vsprintf(p, end, fmt, args); |
| |
| width = njs_length(" in ") + file->length + NJS_INT_T_LEN; |
| |
| if (p > end - width) { |
| p = end - width; |
| } |
| |
| if (file->length != 0 && !vm->options.quiet) { |
| p = njs_sprintf(p, end, " in %V:%uD", file, line); |
| |
| } else { |
| p = njs_sprintf(p, end, " in %uD", line); |
| } |
| |
| njs_error_new(vm, &vm->retval, type, msg, p - msg); |
| } |
| |
| |
| void |
| njs_parser_lexer_error(njs_vm_t *vm, njs_parser_t *parser, |
| njs_object_type_t type, const char *fmt, ...) |
| { |
| va_list args; |
| |
| if (njs_is_error(&vm->retval)) { |
| return; |
| } |
| |
| va_start(args, fmt); |
| njs_parser_scope_error(vm, parser->scope, type, parser->lexer->line, fmt, |
| args); |
| va_end(args); |
| } |
| |
| |
| void |
| njs_parser_node_error(njs_vm_t *vm, njs_parser_node_t *node, |
| njs_object_type_t type, const char *fmt, ...) |
| { |
| va_list args; |
| |
| va_start(args, fmt); |
| njs_parser_scope_error(vm, node->scope, type, node->token_line, fmt, args); |
| va_end(args); |
| } |