/* Part of the EDG Compiler Project, under the Apache License v2.0 with LLVM Exceptions. See https://edgcpp.org/LICENSE.txt for license information. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ /* lexical.c -- Source input and lexical scanning routines. These routines and data structures handle reading of source lines and parsing of them into tokens. */ /* Header files common to all files. */ #include "fe_common.h" #ifdef PCH_PRAGMA_GUARD /* Mark the end of the sequence of headers subject to precompiled header processing. */ #pragma hdrstop #endif /* ifdef PCH_PRAGMA_GUARD */ #include /* Additional header files. */ #include "class_decl.h" #include "decls.h" #include "disambig.h" #include "expr.h" #include "exprutil.h" #include "folding.h" #include "fe_init.h" #include "literals.h" #include "macro.h" #include "overload.h" #include "pch.h" #include "pragma.h" #include "preproc.h" #include "symbol_ref.h" #include "templates.h" #include "ifc_modules.h" #if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY #include "func_def.h" #endif /* INCLUDE_COMMENTS_IN_ASM_FUNC_BODY */ #if MICROSOFT_EXTENSIONS_ALLOWED #include "ms_metadata.h" #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Conditionally open the "edg" namespace. */ BEGIN_EDG_NAMESPACE constexpr sizeof_t token_ctrl_block_batch_size = ((HOST_ALLOCATION_INCREMENT - sizeof(a_mem_block_header)) / sizeof(a_shared_token_ctrl_block)); /* The number of tokens to allocate per batch. This is sized to consume an entire memory block's worth of tokens at a time. Note that for PCH support to function when USE_MMAP_FOR_MEMORY_REGIONS is FALSE, the total allocation size cannot exceed HOST_ALLOCATION_INCREMENT. */ void FE_allocator::reserve_block() /* Allocate a block of address space to token control blocks. */ { an_elem *elem = (an_elem*)alloc_fe(sizeof(an_elem) * token_ctrl_block_batch_size); for (size_t i = token_ctrl_block_batch_size; i != 0; --i) { avail_token_ctrl_blocks->push_back(elem + (i - 1)); } /* for */ #if DEBUG num_shared_token_states_allocated += token_ctrl_block_batch_size; #endif /* DEBUG */ } /* FE_allocator::reserve_block */ /* Declarations relating to expansion of #embed directives. */ struct an_embed_control_block { a_source_position directive_pos; /* The position of the #embed directive. */ #if EXTRA_SOURCE_POSITIONS_IN_IL a_source_position directive_end_pos; /* The ending position of the #embed directive. */ #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ unsigned char *buf; /* If non-null, points to a buffer allocated in general memory containing the contents of the file designated by the current #embed directive; NULL if we are not currently processing a #embed. */ size_t size; /* The allocated size of the current or most recent #embed buffer. */ a_const_char *next_byte; /* When reading_from_buffer is TRUE, points to the next character of buf whose value will be returned by get_token(); NULL if we are not currently processing a #embed. */ a_const_char *last_byte; /* Points to the last byte of buf whose value will be returned by get_token() during #embed processing; NULL if we are not currently processing a #embed. */ char char_following_prefix; /* If a prefix parameter was specified, this is the character following the closing right parenthesis that was replaced by the second character of the terminating LE_END_OF_EMBED_PREFIX lexical escape so it can be replaced once the prefix operand has been traversed. */ char char_following_suffix; /* If a suffix or if_empty parameter was specified, this is the character following the closing right parenthesis that was replaced by the second character of the terminating LE_END_OF_EMBED lexical escape so it can be replaced once that operand has been traversed. */ a_const_char *prefix_loc; /* Points to the first character of the operand of the directive's prefix parameter, if one was specified and the file is not empty, and NULL otherwise. */ a_const_char *after_prefix; /* Points to the closing parenthesis of the directive's prefix parameter, if one was specified and the file is not empty, and NULL otherwise. */ a_const_char *suffix_loc; /* Points to the first character of the operand of the directive's suffix parameter, if one was specified and the file is not empty, or the operand of the if_empty parameter if one was specified and the file is empty. NULL if neither condition applies. */ a_const_char *eol_loc; /* The location of the LE_NEWLINE lexical escape ending the #embed directive. Saved from curr_char_loc on entry and used to restore that value when the #embed expansion is complete. */ a_boolean in_process; /* TRUE if the next get_token() should return data from a #embed directive that is currently being processed. Set to FALSE when the #embed is potentially optimizable to allow the next token to be examined in order to determine if the optimization can actually be applied, after which copy_embed_data_to_il or process_embed_data_as_bytes should be called to reflect the outcome. */ a_boolean reading_from_buffer; /* TRUE if tokens should be taken from the contents of the embed file; FALSE during processing of the prefix, suffix, or if_empty parameter text and if we are not processing a #embed. */ a_boolean comma_is_next; /* While tokens are being taken from the contents of the embed file, a TRUE value indicates that get_token() will return the eok_comma that separates the numeric values of the embed file, and FALSE indicates that get_token() will return the value of *next_byte as a tok_int_constant. */ }; /* an_embed_control_block */ STATIC_THREAD an_embed_control_block embed_control; /* Information controlling the expansion of the current #embed directive, if any. */ /* Macro that returns TRUE if tok is tok_uuid. */ #if MICROSOFT_EXTENSIONS_ALLOWED #define is_microsoft_tok_uuid(tok) ((tok) == tok_uuid) #else /* !MICROSOFT_EXTENSIONS_ALLOWED */ /*lint -emacro(506,is_microsoft_tok_uuid)*/ #define is_microsoft_tok_uuid(tok) (FALSE) #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Return TRUE if tok is a token kind that is a literal constant. */ #define is_literal_constant_token(tok) \ (tok == tok_float_constant || tok == tok_int_constant || \ tok == tok_char_constant || tok == tok_string_literal || \ tok == tok_false || tok == tok_true || \ tok == tok_gen_constant || \ is_microsoft_tok_uuid(tok)) /* Macro that returns TRUE if the curr_token_pragma list should not be processed because we are currently processing tokens as part of a preprocessing operation or are in the process of flushing tokens to discard them. */ #define suppress_pragma_processing \ (fetch_pp_tokens || in_preprocessing_directive || \ curr_lexical_state_stack_entry->flushing_tokens) /* Macro that determines whether any of the initial special case tests at the beginning of get_token need to be done. */ #define recalc_any_initial_get_token_tests_needed() \ (any_initial_get_token_tests_needed = \ (!curr_token_pragmas->is_empty() || \ !cached_token_rescan_stack->is_empty() || \ reusable_cache_stack != NULL) || \ embed_control.in_process) /* Macro to check prevent calling the error checking function unless some error flags have been specified. */ #define check_for_generalized_identifier_errors(options, pos) \ (((options & GID_ERROR_FLAGS) != 0) ? \ f_check_for_generalized_identifier_errors(options, pos) : FALSE) /* Macro to call the routine to check for template declarator errors, but only when the appropriate options have been specified. */ #define check_for_template_declarator_errors(options, pos) \ (((options & \ (GID_IS_TEMPLATE_DECLARATION | GID_IS_TEMPLATE_SPECIALIZATION)) != 0) ? \ f_check_for_template_declarator_errors(options, pos) : FALSE) /* Entry used to represent the table of universal character names that may be used as part of an identifier. A sorted array of these entries is used when checking the validity of a given universal character. */ typedef struct a_UCN_range *a_UCN_range_ptr; typedef struct a_UCN_range { unsigned long start; /* The first character in the range. */ unsigned long end; /* The last character in the range. */ a_byte dialects; /* A bit set indicating the dialects for which this range of characters is accepted. See below for the encoding masks. */ a_byte non_initial_dialects; /* A bit set indicating the dialects for which this range of characters is not permitted as the first character of an identifier. This uses the same encoding as dialects. */ } a_UCN_range; /* Macros giving the encoding used in the dialects and non_initial_dialects fields of the a_UCN_range entry. There is one flag for both C11 and C++11, since the character tables specified in the respective Standards are identical. UAX44 represents the Unicode Standard Annex #44, which was adopted for the C++23 Standard and as a Defect Report against previous C++ Standards via WG21 document P1949R7. C23 also adopted the Unicode properties for identifier characters; see WG14 documents N2836 and N2939. */ #define CPP03 0x1 #define C99 0x2 #define CPP11 0x4 /* Also applies to C11. */ #define UAX44 0x8 #define _ 0x80 /* Unused flag for spacing in the table below. */ /* Sorted table of valid UCN ranges. Note that there is no column in the initializers for non_initial_dialects for CPP03 because that Standard does not specify any restrictions for initial characters. The C99 Standard excludes characters classified as digits, while the C++11 and C11 Standards have explicit ranges of excluded characters. The UAX44 values are based on the XID_Start and XID_Continue Derived Properties in www.unicode.org/Public/UCD/latest/ucd/DerivedCoreProperties.txt for Unicode version 17.0.0, dated 2025-07-30. */ static constexpr a_UCN_range UCN_table[] = { { 0x000a8, 0x000a8, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x000aa, 0x000aa, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x000ad, 0x000ad, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x000af, 0x000af, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x000b2, 0x000b4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x000b5, 0x000b5, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x000b7, 0x000b7, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x000b8, 0x000b9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x000ba, 0x000ba, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x000bc, 0x000be, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x000c0, 0x000d6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x000d8, 0x000f6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x000f8, 0x001f5, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x001f6, 0x001f9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x001fa, 0x00217, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00218, 0x0024f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00250, 0x002a8, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x002a9, 0x002af, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x002b0, 0x002b8, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x002b9, 0x002ba, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x002bb, 0x002bb, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x002bc, 0x002bc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x002bd, 0x002c1, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x002c2, 0x002c5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x002c6, 0x002cf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x002d0, 0x002d1, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x002d2, 0x002df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x002e0, 0x002e4, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x002e5, 0x002eb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x002ec, 0x002ec, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x002ed, 0x002ed, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x002ee, 0x002ee, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x002ef, 0x002ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00300, 0x0036f, _ | _ | CPP11 | UAX44, _ | CPP11 | UAX44 }, { 0x00370, 0x00374, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00375, 0x00375, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00376, 0x00377, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00378, 0x00379, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0037a, 0x0037a, _ | C99 | CPP11 | _ , _ | _ | _ }, { 0x0037b, 0x0037d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0037e, 0x0037e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0037f, 0x0037f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00380, 0x00383, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00384, 0x00384, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x00385, 0x00385, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00386, 0x00386, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00387, 0x00387, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00388, 0x0038a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0038b, 0x0038b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0038c, 0x0038c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0038d, 0x0038d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0038e, 0x003a1, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003a2, 0x003a2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x003a3, 0x003ce, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003cf, 0x003cf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003d0, 0x003d6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003d7, 0x003d9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003da, 0x003da, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003db, 0x003db, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003dc, 0x003dc, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003dd, 0x003dd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003de, 0x003de, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003df, 0x003df, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003e0, 0x003e0, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003e1, 0x003e1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003e2, 0x003f3, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x003f4, 0x003f5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x003f6, 0x003f6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x003f7, 0x00400, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00401, 0x0040c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0040d, 0x0040d, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0040e, 0x0040e, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0040f, 0x0044f, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00450, 0x00450, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00451, 0x0045c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0045d, 0x0045d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0045e, 0x00481, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00482, 0x00482, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00483, 0x00487, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00488, 0x00489, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0048a, 0x0048f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00490, 0x004c4, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x004c5, 0x004c6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x004c7, 0x004c8, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x004c9, 0x004ca, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x004cb, 0x004cc, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x004cd, 0x004cf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x004d0, 0x004eb, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x004ec, 0x004ed, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x004ee, 0x004f5, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x004f6, 0x004f7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x004f8, 0x004f9, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x004fa, 0x0052f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00530, 0x00530, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00531, 0x00556, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00557, 0x00558, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00559, 0x00559, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0055a, 0x0055f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00560, 0x00560, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00561, 0x00587, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00588, 0x00588, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00589, 0x00590, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00591, 0x005af, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005b0, 0x005b9, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005ba, 0x005ba, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005bb, 0x005bd, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005be, 0x005be, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x005bf, 0x005bf, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005c0, 0x005c0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x005c1, 0x005c2, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005c3, 0x005c3, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x005c4, 0x005c5, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005c6, 0x005c6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x005c7, 0x005c7, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x005c8, 0x005cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x005d0, 0x005ea, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x005eb, 0x005ee, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x005ef, 0x005ef, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x005f0, 0x005f2, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x005f3, 0x005f4, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x005f5, 0x0060f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00610, 0x0061a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0061b, 0x0061f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00620, 0x00620, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00621, 0x0063a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0063b, 0x0063f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00640, 0x0064a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0064b, 0x00652, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00653, 0x0065f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00660, 0x00669, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x0066a, 0x0066d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0066e, 0x0066f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00670, 0x00670, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00671, 0x006b7, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x006b8, 0x006b9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x006ba, 0x006be, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x006bf, 0x006bf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x006c0, 0x006ce, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x006cf, 0x006cf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x006d0, 0x006d3, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x006d4, 0x006d4, _ | C99 | CPP11 | _ , _ | _ | _ }, { 0x006d5, 0x006d5, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x006d6, 0x006dc, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x006dd, 0x006de, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x006df, 0x006e4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x006e5, 0x006e6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x006e7, 0x006e7, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x006e8, 0x006e8, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x006e9, 0x006e9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x006ea, 0x006ed, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x006ee, 0x006ef, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x006f0, 0x006f9, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x006fa, 0x006fc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x006fd, 0x006fe, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x006ff, 0x006ff, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00700, 0x0070f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00710, 0x00710, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00711, 0x00711, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00712, 0x0072f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00730, 0x0074a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0074b, 0x0074c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0074d, 0x007a5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x007a6, 0x007b0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x007b1, 0x007b1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x007b2, 0x007bf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x007c0, 0x007c9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x007ca, 0x007ea, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x007eb, 0x007f3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x007f4, 0x007f5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x007f6, 0x007f9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x007fa, 0x007fa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x007fb, 0x007fc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x007fd, 0x007fd, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x007fe, 0x007ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00800, 0x00815, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00816, 0x00819, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0081a, 0x0081a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0081b, 0x00823, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00824, 0x00824, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00825, 0x00827, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00828, 0x00828, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00829, 0x0082d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0082e, 0x0083f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00840, 0x00858, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00859, 0x0085b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0085c, 0x0085f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00860, 0x0086a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0086b, 0x0086f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00870, 0x00887, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00888, 0x00888, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00889, 0x0088f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00890, 0x00896, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00897, 0x0089f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x008a0, 0x008c9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x008ca, 0x008e1, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x008e2, 0x008e2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x008e3, 0x00900, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00901, 0x00903, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00904, 0x00904, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00905, 0x00939, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0093a, 0x0093c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0093d, 0x0093d, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0093e, 0x0094d, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0094e, 0x0094f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00950, 0x00950, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00951, 0x00952, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00953, 0x00957, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00958, 0x00961, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00962, 0x00962, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00963, 0x00963, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00964, 0x00965, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00966, 0x0096f, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00970, 0x00970, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00971, 0x00980, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00981, 0x00983, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00984, 0x00984, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00985, 0x0098c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0098d, 0x0098e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0098f, 0x00990, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00991, 0x00992, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00993, 0x009a8, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009a9, 0x009a9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009aa, 0x009b0, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009b1, 0x009b1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009b2, 0x009b2, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009b3, 0x009b5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009b6, 0x009b9, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009ba, 0x009bb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009bc, 0x009bc, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009bd, 0x009bd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x009be, 0x009c4, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009c5, 0x009c6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009c7, 0x009c8, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009c9, 0x009ca, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009cb, 0x009cd, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009ce, 0x009ce, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x009cf, 0x009d6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009d7, 0x009d7, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009d8, 0x009db, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009dc, 0x009dd, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009de, 0x009de, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009df, 0x009e1, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009e2, 0x009e3, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009e4, 0x009e5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009e6, 0x009ef, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x009f0, 0x009f1, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x009f2, 0x009fb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009fc, 0x009fc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x009fd, 0x009fd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x009fe, 0x009fe, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x009ff, 0x00a00, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a01, 0x00a01, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a02, 0x00a02, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a03, 0x00a03, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a04, 0x00a04, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a05, 0x00a0a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a0b, 0x00a0e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a0f, 0x00a10, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a11, 0x00a12, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a13, 0x00a28, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a29, 0x00a29, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a2a, 0x00a30, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a31, 0x00a31, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a32, 0x00a33, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a34, 0x00a34, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a35, 0x00a36, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a37, 0x00a37, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a38, 0x00a39, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a3a, 0x00a3b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a3c, 0x00a3c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a3d, 0x00a3d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a3e, 0x00a42, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a43, 0x00a46, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a47, 0x00a48, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a49, 0x00a4a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a4b, 0x00a4d, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a4e, 0x00a50, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a51, 0x00a51, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a52, 0x00a58, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a59, 0x00a5c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a5d, 0x00a5d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a5e, 0x00a5e, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a5f, 0x00a65, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a66, 0x00a6f, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00a70, 0x00a71, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a72, 0x00a73, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00a74, 0x00a74, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a75, 0x00a75, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a76, 0x00a80, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a81, 0x00a83, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00a84, 0x00a84, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a85, 0x00a8b, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a8c, 0x00a8c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00a8d, 0x00a8d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a8e, 0x00a8e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a8f, 0x00a91, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00a92, 0x00a92, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00a93, 0x00aa8, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00aa9, 0x00aa9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00aaa, 0x00ab0, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ab1, 0x00ab1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ab2, 0x00ab3, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ab4, 0x00ab4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ab5, 0x00ab9, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00aba, 0x00abb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00abc, 0x00abc, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00abd, 0x00abd, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00abe, 0x00ac5, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ac6, 0x00ac6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ac7, 0x00ac9, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00aca, 0x00aca, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00acb, 0x00acd, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ace, 0x00acf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ad0, 0x00ad0, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ad1, 0x00adf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ae0, 0x00ae0, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ae1, 0x00ae1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00ae2, 0x00ae3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ae4, 0x00ae5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ae6, 0x00aef, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00af0, 0x00af8, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00af9, 0x00af9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00afa, 0x00aff, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b00, 0x00b00, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b01, 0x00b03, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b04, 0x00b04, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b05, 0x00b0c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b0d, 0x00b0e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b0f, 0x00b10, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b11, 0x00b12, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b13, 0x00b28, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b29, 0x00b29, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b2a, 0x00b30, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b31, 0x00b31, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b32, 0x00b33, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b34, 0x00b34, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b35, 0x00b35, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00b36, 0x00b39, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b3a, 0x00b3b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b3c, 0x00b3c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b3d, 0x00b3d, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b3e, 0x00b43, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b44, 0x00b44, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b45, 0x00b46, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b47, 0x00b48, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b49, 0x00b4a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b4b, 0x00b4d, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b4e, 0x00b54, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b55, 0x00b57, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b58, 0x00b5b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b5c, 0x00b5d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b5e, 0x00b5e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b5f, 0x00b61, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b62, 0x00b63, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b64, 0x00b65, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b66, 0x00b6f, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00b70, 0x00b70, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b71, 0x00b71, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00b72, 0x00b81, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b82, 0x00b82, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00b83, 0x00b83, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b84, 0x00b84, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b85, 0x00b8a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b8b, 0x00b8d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b8e, 0x00b90, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b91, 0x00b91, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b92, 0x00b95, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b96, 0x00b98, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b99, 0x00b9a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b9b, 0x00b9b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b9c, 0x00b9c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00b9d, 0x00b9d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00b9e, 0x00b9f, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ba0, 0x00ba2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ba3, 0x00ba4, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ba5, 0x00ba7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ba8, 0x00baa, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00bab, 0x00bad, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00bae, 0x00bb5, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00bb6, 0x00bb6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00bb7, 0x00bb9, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00bba, 0x00bbd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00bbe, 0x00bc2, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00bc3, 0x00bc5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00bc6, 0x00bc8, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00bc9, 0x00bc9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00bca, 0x00bcd, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00bce, 0x00bcf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00bd0, 0x00bd0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00bd1, 0x00bd6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00bd7, 0x00bd7, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00bd8, 0x00be5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00be6, 0x00be6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00be7, 0x00bef, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00bf0, 0x00bff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c00, 0x00c00, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c01, 0x00c03, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c04, 0x00c04, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c05, 0x00c0c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c0d, 0x00c0d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c0e, 0x00c10, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c11, 0x00c11, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c12, 0x00c28, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c29, 0x00c29, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c2a, 0x00c33, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c34, 0x00c34, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00c35, 0x00c39, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c3a, 0x00c3b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c3c, 0x00c3c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c3d, 0x00c3d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00c3e, 0x00c44, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c45, 0x00c45, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c46, 0x00c48, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c49, 0x00c49, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c4a, 0x00c4d, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c4e, 0x00c54, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c55, 0x00c56, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c57, 0x00c57, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c58, 0x00c5a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00c5b, 0x00c5b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c5c, 0x00c5d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00c5e, 0x00c5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c60, 0x00c61, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c62, 0x00c63, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c64, 0x00c65, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c66, 0x00c6f, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00c70, 0x00c7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c80, 0x00c80, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00c81, 0x00c81, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c82, 0x00c83, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00c84, 0x00c84, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c85, 0x00c8c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c8d, 0x00c8d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c8e, 0x00c90, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00c91, 0x00c91, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00c92, 0x00ca8, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ca9, 0x00ca9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00caa, 0x00cb3, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00cb4, 0x00cb4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cb5, 0x00cb9, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00cba, 0x00cbb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cbc, 0x00cbc, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00cbd, 0x00cbd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00cbe, 0x00cc4, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00cc5, 0x00cc5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cc6, 0x00cc8, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00cc9, 0x00cc9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cca, 0x00ccd, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00cce, 0x00cd4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cd5, 0x00cd6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00cd7, 0x00cdb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cdc, 0x00cdd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00cde, 0x00cde, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00cdf, 0x00cdf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ce0, 0x00ce1, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ce2, 0x00ce3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ce4, 0x00ce5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ce6, 0x00cef, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00cf0, 0x00cf0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00cf1, 0x00cf2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00cf3, 0x00cf3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00cf4, 0x00cff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d00, 0x00d01, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d02, 0x00d03, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d04, 0x00d04, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d05, 0x00d0c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00d0d, 0x00d0d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d0e, 0x00d10, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00d11, 0x00d11, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d12, 0x00d28, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00d29, 0x00d29, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d2a, 0x00d39, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00d3a, 0x00d3a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d3b, 0x00d3c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d3d, 0x00d3d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d3e, 0x00d43, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d44, 0x00d44, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d45, 0x00d45, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d46, 0x00d48, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d49, 0x00d49, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d4a, 0x00d4d, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d4e, 0x00d4e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d4f, 0x00d53, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d54, 0x00d56, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d57, 0x00d57, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d58, 0x00d5e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d5f, 0x00d5f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d60, 0x00d61, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00d62, 0x00d63, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d64, 0x00d65, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d66, 0x00d6f, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00d70, 0x00d79, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d7a, 0x00d7f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d80, 0x00d80, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d81, 0x00d83, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00d84, 0x00d84, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d85, 0x00d96, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00d97, 0x00d99, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00d9a, 0x00db1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00db2, 0x00db2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00db3, 0x00dbb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00dbc, 0x00dbc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00dbd, 0x00dbd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00dbe, 0x00dbf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00dc0, 0x00dc6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00dc7, 0x00dc9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00dca, 0x00dca, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00dcb, 0x00dce, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00dcf, 0x00dd4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00dd5, 0x00dd5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00dd6, 0x00dd6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00dd7, 0x00dd7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00dd8, 0x00ddf, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00de0, 0x00de5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00de6, 0x00def, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00df0, 0x00df1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00df2, 0x00df3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00df4, 0x00e00, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00e01, 0x00e30, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e31, 0x00e31, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00e32, 0x00e32, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e33, 0x00e33, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00e34, 0x00e3a, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00e3b, 0x00e3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00e40, 0x00e46, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e47, 0x00e4e, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00e4f, 0x00e4f, CPP03 | C99 | CPP11 | _ , _ | _ | _ }, { 0x00e50, 0x00e59, CPP03 | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00e5a, 0x00e5b, CPP03 | C99 | CPP11 | _ , _ | _ | _ }, { 0x00e5c, 0x00e80, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00e81, 0x00e82, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e83, 0x00e83, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00e84, 0x00e84, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e85, 0x00e85, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00e86, 0x00e86, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00e87, 0x00e88, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e89, 0x00e89, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00e8a, 0x00e8a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e8b, 0x00e8b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00e8c, 0x00e8c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00e8d, 0x00e8d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e8e, 0x00e93, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00e94, 0x00e97, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00e98, 0x00e98, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00e99, 0x00e9f, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ea0, 0x00ea0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00ea1, 0x00ea3, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ea4, 0x00ea4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ea5, 0x00ea5, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ea6, 0x00ea6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ea7, 0x00ea7, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ea8, 0x00ea9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00eaa, 0x00eab, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00eac, 0x00eac, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00ead, 0x00eae, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00eaf, 0x00eaf, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00eb0, 0x00eb0, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00eb1, 0x00eb1, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00eb2, 0x00eb2, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00eb3, 0x00eb3, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00eb4, 0x00eb9, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00eba, 0x00eba, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ebb, 0x00ebc, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ebd, 0x00ebd, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ebe, 0x00ebf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ec0, 0x00ec4, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ec5, 0x00ec5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ec6, 0x00ec6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ec7, 0x00ec7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ec8, 0x00ece, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00ecf, 0x00ecf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00ed0, 0x00ed9, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00eda, 0x00edb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00edc, 0x00edd, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00ede, 0x00edf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00ee0, 0x00eff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f00, 0x00f00, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00f01, 0x00f17, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f18, 0x00f19, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f1a, 0x00f1f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f20, 0x00f29, _ | C99 | CPP11 | UAX44, C99 | _ | UAX44 }, { 0x00f2a, 0x00f33, _ | C99 | CPP11 | _ , C99 | _ | _ }, { 0x00f34, 0x00f34, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f35, 0x00f35, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f36, 0x00f36, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f37, 0x00f37, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f38, 0x00f38, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f39, 0x00f39, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f3a, 0x00f3d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f3e, 0x00f3f, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f40, 0x00f47, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00f48, 0x00f48, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f49, 0x00f69, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00f6a, 0x00f6c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00f6d, 0x00f70, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f71, 0x00f84, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f85, 0x00f85, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f86, 0x00f87, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f88, 0x00f8b, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x00f8c, 0x00f8c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x00f8d, 0x00f8f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f90, 0x00f95, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f96, 0x00f96, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f97, 0x00f97, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00f98, 0x00f98, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00f99, 0x00fad, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fae, 0x00fb0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fb1, 0x00fb7, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fb8, 0x00fb8, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fb9, 0x00fb9, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fba, 0x00fbc, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fbd, 0x00fc5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x00fc6, 0x00fc6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x00fc7, 0x00fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01000, 0x0102a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0102b, 0x0103e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0103f, 0x0103f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01040, 0x01049, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0104a, 0x0104f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01050, 0x01055, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01056, 0x01059, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0105a, 0x0105d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0105e, 0x01060, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01061, 0x01061, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01062, 0x01064, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01065, 0x01066, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01067, 0x0106d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0106e, 0x01070, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01071, 0x01074, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01075, 0x01081, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01082, 0x0108d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0108e, 0x0108e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0108f, 0x0109d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0109e, 0x0109f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x010a0, 0x010c5, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x010c6, 0x010c6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x010c7, 0x010c7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x010c8, 0x010cc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x010cd, 0x010cd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x010ce, 0x010cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x010d0, 0x010f6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x010f7, 0x010fa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x010fb, 0x010fb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x010fc, 0x010ff, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01100, 0x01159, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0115a, 0x01160, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01161, 0x011a2, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x011a3, 0x011a7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x011a8, 0x011f9, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x011fa, 0x01248, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01249, 0x01249, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0124a, 0x0124d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0124e, 0x0124f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01250, 0x01256, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01257, 0x01257, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01258, 0x01258, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01259, 0x01259, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0125a, 0x0125d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0125e, 0x0125f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01260, 0x01288, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01289, 0x01289, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0128a, 0x0128d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0128e, 0x0128f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01290, 0x012b0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x012b1, 0x012b1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x012b2, 0x012b5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x012b6, 0x012b7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x012b8, 0x012be, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x012bf, 0x012bf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x012c0, 0x012c0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x012c1, 0x012c1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x012c2, 0x012c5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x012c6, 0x012c7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x012c8, 0x012d6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x012d7, 0x012d7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x012d8, 0x01310, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01311, 0x01311, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01312, 0x01315, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01316, 0x01317, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01318, 0x0135a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0135b, 0x0135c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0135d, 0x0135f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01360, 0x01368, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01369, 0x01371, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01372, 0x0137f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01380, 0x0138f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01390, 0x0139f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x013a0, 0x013f5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x013f6, 0x013f7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x013f8, 0x013fd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x013fe, 0x01400, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01401, 0x0166c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0166d, 0x0166e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0166f, 0x0167f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01681, 0x0169a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0169b, 0x0169f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x016a0, 0x016ea, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x016eb, 0x016ed, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x016ee, 0x016f8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x016f9, 0x016ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01700, 0x01711, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01712, 0x01715, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01716, 0x0171e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0171f, 0x01731, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01732, 0x01734, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01735, 0x0173f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01740, 0x01751, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01752, 0x01753, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01754, 0x0175f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01760, 0x0176c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0176d, 0x0176d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0176e, 0x01770, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01771, 0x01771, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01772, 0x01773, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01774, 0x0177f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01780, 0x017b3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x017b4, 0x017d3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x017d4, 0x017d6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x017d7, 0x017d7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x017d8, 0x017db, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x017dc, 0x017dc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x017dd, 0x017dd, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x017de, 0x017df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x017e0, 0x017e9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x017ea, 0x0180a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0180b, 0x0180d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0180f, 0x01819, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0181a, 0x0181f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01820, 0x01878, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01879, 0x0187f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01880, 0x018a8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x018a9, 0x018a9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x018aa, 0x018aa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x018ab, 0x018af, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x018b0, 0x018f5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x018f6, 0x018ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01900, 0x0191e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0191f, 0x0191f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01920, 0x0192b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0192c, 0x0192f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01930, 0x0193b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0193c, 0x01945, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01946, 0x0194f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01950, 0x0196d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0196e, 0x0196f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01970, 0x01974, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01975, 0x0197f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01980, 0x019ab, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x019ac, 0x019af, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x019b0, 0x019c9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x019ca, 0x019cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x019d0, 0x019da, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x019db, 0x019ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01a00, 0x01a16, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01a17, 0x01a1b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01a1c, 0x01a1f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01a20, 0x01a54, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01a55, 0x01a5e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01a5f, 0x01a5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01a60, 0x01a7c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01a7d, 0x01a7e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01a7f, 0x01a89, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01a8a, 0x01a8f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01a90, 0x01a99, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01a9a, 0x01aa6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01aa7, 0x01aa7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01aa8, 0x01aaf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01ab0, 0x01abd, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01abe, 0x01abe, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01abf, 0x01add, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01ade, 0x01adf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01ae0, 0x01aeb, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01aec, 0x01aff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01b00, 0x01b04, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01b05, 0x01b33, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01b34, 0x01b44, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01b45, 0x01b4c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01b4d, 0x01b4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01b50, 0x01b59, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01b5a, 0x01b6a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01b6b, 0x01b73, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01b74, 0x01b7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01b80, 0x01b82, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01b83, 0x01ba0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01ba1, 0x01bad, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01bae, 0x01baf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01bb0, 0x01bb9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01bba, 0x01be5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01be6, 0x01bf3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01bf4, 0x01bff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01c00, 0x01c23, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01c24, 0x01c37, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01c38, 0x01c3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01c40, 0x01c49, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01c4a, 0x01c4c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01c4d, 0x01c4f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01c50, 0x01c59, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01c5a, 0x01c7d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01c7e, 0x01c7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01c80, 0x01c8a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01c8b, 0x01c8f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01c90, 0x01cba, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01cbb, 0x01cbc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01cbd, 0x01cbf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01cc0, 0x01ccf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01cd0, 0x01cd2, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01cd3, 0x01cd3, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01cd4, 0x01ce8, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01ce9, 0x01cec, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01ced, 0x01ced, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01cee, 0x01cf3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01cf4, 0x01cf4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01cf5, 0x01cf6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01cf7, 0x01cf9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x01cfa, 0x01cfa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01cfb, 0x01cff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01d00, 0x01dbf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01dc0, 0x01dff, _ | _ | CPP11 | UAX44, _ | CPP11 | UAX44 }, { 0x01e00, 0x01e9a, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01e9b, 0x01e9b, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01e9c, 0x01e9f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01ea0, 0x01ef9, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01efa, 0x01eff, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x01f00, 0x01f15, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f16, 0x01f17, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f18, 0x01f1d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f1e, 0x01f1f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f20, 0x01f45, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f46, 0x01f47, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f48, 0x01f4d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f4e, 0x01f4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f50, 0x01f57, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f58, 0x01f58, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f59, 0x01f59, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f5a, 0x01f5a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f5b, 0x01f5b, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f5c, 0x01f5c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f5d, 0x01f5d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f5e, 0x01f5e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f5f, 0x01f7d, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01f7e, 0x01f7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01f80, 0x01fb4, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fb5, 0x01fb5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fb6, 0x01fbc, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fbd, 0x01fbd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fbe, 0x01fbe, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fbf, 0x01fc1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fc2, 0x01fc4, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fc5, 0x01fc5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fc6, 0x01fcc, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fcd, 0x01fcf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fd0, 0x01fd3, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fd4, 0x01fd5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fd6, 0x01fdb, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fdc, 0x01fdf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01fe0, 0x01fec, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01fed, 0x01ff1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01ff2, 0x01ff4, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01ff5, 0x01ff5, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x01ff6, 0x01ffc, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x01ffd, 0x01fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0200b, 0x0200b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0200c, 0x0200d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0202a, 0x0202e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0203f, 0x02040, _ | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x02054, 0x02054, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x02060, 0x02070, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02071, 0x02071, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02072, 0x0207e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0207f, 0x0207f, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02080, 0x0208f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02090, 0x0209c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0209d, 0x020cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x020d0, 0x020dc, _ | _ | CPP11 | UAX44, _ | CPP11 | UAX44 }, { 0x020dd, 0x020e0, _ | _ | CPP11 | _ , _ | CPP11 | _ }, { 0x020e1, 0x020e1, _ | _ | CPP11 | UAX44, _ | CPP11 | UAX44 }, { 0x020e2, 0x020e4, _ | _ | CPP11 | _ , _ | CPP11 | _ }, { 0x020e5, 0x020f0, _ | _ | CPP11 | UAX44, _ | CPP11 | UAX44 }, { 0x020f1, 0x020ff, _ | _ | CPP11 | _ , _ | CPP11 | _ }, { 0x02100, 0x02101, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02102, 0x02102, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02103, 0x02106, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02107, 0x02107, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02108, 0x02109, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0210a, 0x02113, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02114, 0x02114, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02115, 0x02115, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02116, 0x02117, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02118, 0x0211d, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0211e, 0x02123, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02124, 0x02124, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02125, 0x02125, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02126, 0x02126, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02127, 0x02127, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02128, 0x02128, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02129, 0x02129, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0212a, 0x02131, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02132, 0x02132, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02133, 0x02138, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02139, 0x02139, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0213a, 0x0213b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0213c, 0x0213f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02140, 0x02144, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02145, 0x02149, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0214a, 0x0214d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0214e, 0x0214e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0214f, 0x0215f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02160, 0x02182, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x02183, 0x02188, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02189, 0x0218f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02460, 0x024ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02776, 0x02793, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02c00, 0x02ce4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02ce5, 0x02cea, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02ceb, 0x02cee, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02cef, 0x02cf1, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x02cf2, 0x02cf3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02cf4, 0x02cff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02d00, 0x02d25, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02d26, 0x02d26, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02d27, 0x02d27, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02d28, 0x02d2c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02d2d, 0x02d2d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02d2e, 0x02d2f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02d30, 0x02d67, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02d68, 0x02d6e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02d6f, 0x02d6f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02d70, 0x02d7e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02d7f, 0x02d7f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x02d80, 0x02d96, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02d97, 0x02d9f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02da0, 0x02da6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02da7, 0x02da7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02da8, 0x02dae, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02daf, 0x02daf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02db0, 0x02db6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02db7, 0x02db7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02db8, 0x02dbe, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02dbf, 0x02dbf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02dc0, 0x02dc6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02dc7, 0x02dc7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02dc8, 0x02dce, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02dcf, 0x02dcf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02dd0, 0x02dd6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02dd7, 0x02dd7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02dd8, 0x02dde, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x02ddf, 0x02ddf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x02de0, 0x02dff, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x02e80, 0x02fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03004, 0x03004, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03005, 0x03007, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x03021, 0x03029, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0302a, 0x0302f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x03031, 0x03035, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x03036, 0x03037, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03038, 0x0303c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0303d, 0x03040, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03041, 0x03093, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x03094, 0x03094, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x03095, 0x03096, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x03097, 0x03098, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03099, 0x0309a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0309b, 0x0309c, CPP03 | C99 | CPP11 | _ , _ | _ | _ }, { 0x0309d, 0x0309e, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0309f, 0x0309f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x030a0, 0x030a0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x030a1, 0x030f6, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x030f7, 0x030fa, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x030fb, 0x030fb, CPP03 | C99 | CPP11 | UAX44, _ | _ | UAX44 }, { 0x030fc, 0x030fc, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x030fd, 0x030fe, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x030ff, 0x030ff, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x03100, 0x03104, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03105, 0x0312c, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0312d, 0x0312f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x03130, 0x03130, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03131, 0x0318e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0318f, 0x0319f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x031a0, 0x031bf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x031c0, 0x031ef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x031f0, 0x031ff, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x03200, 0x033ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x03400, 0x04dbf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x04dc0, 0x04dff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x04e00, 0x09fa5, CPP03 | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x09fa6, 0x0a48c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a48d, 0x0a4cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a4d0, 0x0a4fd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a4fe, 0x0a4ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a500, 0x0a60c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a60d, 0x0a60f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a610, 0x0a61f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a620, 0x0a629, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a62a, 0x0a62b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a62c, 0x0a63f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a640, 0x0a66e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a66f, 0x0a66f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a670, 0x0a673, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a674, 0x0a67d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a67e, 0x0a67e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a67f, 0x0a69d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a69e, 0x0a69f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a6a0, 0x0a6ef, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a6f0, 0x0a6f1, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a6f2, 0x0a716, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a717, 0x0a71f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a720, 0x0a721, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a722, 0x0a788, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a789, 0x0a78a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a78b, 0x0a7dc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a7dd, 0x0a7f0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a7f1, 0x0a801, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a802, 0x0a802, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a803, 0x0a805, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a806, 0x0a806, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a807, 0x0a80a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a80b, 0x0a80b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a80c, 0x0a822, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a823, 0x0a827, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a828, 0x0a82b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a82c, 0x0a82c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a82d, 0x0a83f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a840, 0x0a873, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a874, 0x0a87f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a880, 0x0a881, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a882, 0x0a8b3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a8b4, 0x0a8c5, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a8c6, 0x0a8cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a8d0, 0x0a8d9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a8da, 0x0a8df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a8e0, 0x0a8f1, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a8f2, 0x0a8f7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a8f8, 0x0a8fa, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a8fb, 0x0a8fb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a8fc, 0x0a8fc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a8fd, 0x0a8fe, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a8ff, 0x0a909, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a90a, 0x0a925, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a926, 0x0a92d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a92e, 0x0a92f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a930, 0x0a946, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a947, 0x0a953, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a954, 0x0a95f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a960, 0x0a97c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a97d, 0x0a97f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a980, 0x0a983, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a984, 0x0a9b2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a9b3, 0x0a9c0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a9c1, 0x0a9ce, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a9cf, 0x0a9cf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a9d0, 0x0a9d9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a9da, 0x0a9df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0a9e0, 0x0a9e4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a9e5, 0x0a9e5, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a9e6, 0x0a9ef, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a9f0, 0x0a9f9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0a9fa, 0x0a9fe, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0a9ff, 0x0a9ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aa00, 0x0aa28, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aa29, 0x0aa36, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aa37, 0x0aa3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aa40, 0x0aa42, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aa43, 0x0aa43, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aa44, 0x0aa4b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aa4c, 0x0aa4d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aa4e, 0x0aa4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aa50, 0x0aa59, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aa5a, 0x0aa5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aa60, 0x0aa76, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aa77, 0x0aa79, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aa7a, 0x0aa7a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aa7b, 0x0aa7d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aa7e, 0x0aaaf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aab0, 0x0aab0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aab1, 0x0aab1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aab2, 0x0aab4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aab5, 0x0aab6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aab7, 0x0aab8, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aab9, 0x0aabd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aabe, 0x0aabf, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aac0, 0x0aac0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aac1, 0x0aac1, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aac2, 0x0aac2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aac3, 0x0aada, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aadb, 0x0aadd, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aade, 0x0aadf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aae0, 0x0aaea, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aaeb, 0x0aaef, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aaf0, 0x0aaf1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0aaf2, 0x0aaf4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0aaf5, 0x0aaf6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0aaf7, 0x0ab00, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab01, 0x0ab06, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab07, 0x0ab08, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab09, 0x0ab0e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab0f, 0x0ab10, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab11, 0x0ab16, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab17, 0x0ab1f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab20, 0x0ab26, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab27, 0x0ab27, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab28, 0x0ab2e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab2f, 0x0ab2f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab30, 0x0ab5a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab5b, 0x0ab5b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab5c, 0x0ab69, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ab6a, 0x0ab6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ab70, 0x0abe2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0abe3, 0x0abea, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0abeb, 0x0abeb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0abec, 0x0abed, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0abee, 0x0abef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0abf0, 0x0abf9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0abfa, 0x0abff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ac00, 0x0d7a3, _ | C99 | CPP11 | UAX44, _ | _ | _ }, { 0x0d7a4, 0x0d7af, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0d7b0, 0x0d7c6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0d7c7, 0x0d7ca, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0d7cb, 0x0d7fb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0d7fc, 0x0d7ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0f900, 0x0fa2d, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fa2e, 0x0fa6d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fa6e, 0x0fa6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fa70, 0x0fad9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fada, 0x0faff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb00, 0x0fb06, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb07, 0x0fb12, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb13, 0x0fb17, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb18, 0x0fb1c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb1d, 0x0fb1d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb1e, 0x0fb1e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0fb1f, 0x0fb28, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb29, 0x0fb29, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb2a, 0x0fb36, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb37, 0x0fb37, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb38, 0x0fb3c, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb3d, 0x0fb3d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb3e, 0x0fb3e, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb3f, 0x0fb3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb40, 0x0fb41, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb42, 0x0fb42, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb43, 0x0fb44, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fb45, 0x0fb45, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fb46, 0x0fbb1, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fbb2, 0x0fbd2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fbd3, 0x0fc5d, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fc5e, 0x0fc63, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fc64, 0x0fd3d, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fd3e, 0x0fd3f, CPP03 | _ | _ | _ , _ | _ | _ }, { 0x0fd40, 0x0fd4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fd50, 0x0fd8f, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fd90, 0x0fd91, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fd92, 0x0fdc7, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fdc8, 0x0fdcf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fdf0, 0x0fdf9, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fdfa, 0x0fdfb, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fdfc, 0x0fdff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe00, 0x0fe0f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0fe10, 0x0fe1f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe20, 0x0fe2f, _ | _ | CPP11 | UAX44, _ | CPP11 | UAX44 }, { 0x0fe30, 0x0fe32, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe33, 0x0fe34, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0fe35, 0x0fe44, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe47, 0x0fe4c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe4d, 0x0fe4f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0fe50, 0x0fe6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe70, 0x0fe70, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe71, 0x0fe71, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fe72, 0x0fe72, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe73, 0x0fe73, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fe74, 0x0fe74, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe75, 0x0fe75, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe76, 0x0fe76, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe77, 0x0fe77, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fe78, 0x0fe78, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe79, 0x0fe79, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fe7a, 0x0fe7a, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe7b, 0x0fe7b, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fe7c, 0x0fe7c, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe7d, 0x0fe7d, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fe7e, 0x0fe7e, CPP03 | _ | CPP11 | _ , _ | _ | _ }, { 0x0fe7f, 0x0fefc, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0fefd, 0x0ff0f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ff10, 0x0ff19, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0ff1a, 0x0ff20, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ff21, 0x0ff3a, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ff3b, 0x0ff3e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ff3f, 0x0ff3f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0ff40, 0x0ff40, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ff41, 0x0ff5a, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ff5b, 0x0ff64, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ff65, 0x0ff65, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0ff66, 0x0ff9d, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ff9e, 0x0ff9f, CPP03 | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x0ffa0, 0x0ffbe, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ffbf, 0x0ffc1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ffc2, 0x0ffc7, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ffc8, 0x0ffc9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ffca, 0x0ffcf, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ffd0, 0x0ffd1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ffd2, 0x0ffd7, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ffd8, 0x0ffd9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x0ffda, 0x0ffdc, CPP03 | _ | CPP11 | UAX44, _ | _ | _ }, { 0x0ffdd, 0x0fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10000, 0x1000b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1000c, 0x1000c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1000d, 0x10026, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10027, 0x10027, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10028, 0x1003a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1003b, 0x1003b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1003c, 0x1003d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1003e, 0x1003e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1003f, 0x1004d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1004e, 0x1004f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10050, 0x1005d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1005e, 0x1007f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10080, 0x100fa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x100fb, 0x1013f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10140, 0x10174, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10175, 0x101fc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x101fd, 0x101fd, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x101fe, 0x1027f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10280, 0x1029c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1029d, 0x1029f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x102a0, 0x102d0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x102d1, 0x102df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x102e0, 0x102e0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x102e1, 0x102ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10300, 0x1031f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10320, 0x1032c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1032d, 0x1034a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1034b, 0x1034f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10350, 0x10375, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10376, 0x1037a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1037b, 0x1037f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10380, 0x1039d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1039e, 0x1039f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x103a0, 0x103c3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x103c4, 0x103c7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x103c8, 0x103cf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x103d0, 0x103d0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x103d1, 0x103d5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x103d6, 0x103ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10400, 0x1049d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1049e, 0x1049f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x104a0, 0x104a9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x104aa, 0x104af, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x104b0, 0x104d3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x104d4, 0x104d7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x104d8, 0x104fb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x104fc, 0x104ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10500, 0x10527, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10528, 0x1052f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10530, 0x10563, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10564, 0x1056f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10570, 0x1057a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1057b, 0x1057b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1057c, 0x1058a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1058b, 0x1058b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1058c, 0x10592, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10593, 0x10593, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10594, 0x10595, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10596, 0x10596, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10597, 0x105a1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x105a2, 0x105a2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x105a3, 0x105b1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x105b2, 0x105b2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x105b3, 0x105b9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x105ba, 0x105ba, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x105bb, 0x105bc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x105bd, 0x105bf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x105c0, 0x105f3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x105f4, 0x105ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10600, 0x10736, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10737, 0x1073f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10740, 0x10755, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10756, 0x1075f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10760, 0x10767, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10768, 0x1077f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10780, 0x10785, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10786, 0x10786, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10787, 0x107b0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x107b1, 0x107b1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x107b2, 0x107ba, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x107bb, 0x107ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10800, 0x10805, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10806, 0x10807, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10808, 0x10808, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10809, 0x10809, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1080a, 0x10835, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10836, 0x10836, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10837, 0x10838, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10839, 0x1083b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1083c, 0x1083c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1083d, 0x1083e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1083f, 0x10855, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10856, 0x1085f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10860, 0x10876, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10877, 0x1087f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10880, 0x1089e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1089f, 0x108df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x108e0, 0x108f2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x108f3, 0x108f3, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x108f4, 0x108f5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x108f6, 0x108ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10900, 0x10915, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10916, 0x1091f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10920, 0x10939, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1093a, 0x1093f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10940, 0x10959, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1095a, 0x1097f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10980, 0x109b7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x109b8, 0x109bd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x109be, 0x109bf, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x109c0, 0x109ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a00, 0x10a00, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10a01, 0x10a03, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10a04, 0x10a04, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a05, 0x10a06, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10a07, 0x10a0b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a0c, 0x10a0f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10a10, 0x10a13, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10a14, 0x10a14, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a15, 0x10a17, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10a18, 0x10a18, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a19, 0x10a35, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10a36, 0x10a37, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a38, 0x10a3a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10a3b, 0x10a3e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a3f, 0x10a3f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10a40, 0x10a5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a60, 0x10a7c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10a7d, 0x10a7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10a80, 0x10a9c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10a9d, 0x10abf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10ac0, 0x10ac7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10ac8, 0x10ac8, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10ac9, 0x10ae4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10ae5, 0x10ae6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10ae7, 0x10aff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10b00, 0x10b35, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10b36, 0x10b3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10b40, 0x10b55, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10b56, 0x10b5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10b60, 0x10b72, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10b73, 0x10b7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10b80, 0x10b91, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10b92, 0x10bff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10c00, 0x10c48, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10c49, 0x10c7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10c80, 0x10cb2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10cb3, 0x10cbf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10cc0, 0x10cf2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10cf3, 0x10cff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10d00, 0x10d23, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10d24, 0x10d27, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10d28, 0x10d2f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10d30, 0x10d39, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10d3a, 0x10d3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10d40, 0x10d49, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10d4a, 0x10d65, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10d66, 0x10d68, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10d69, 0x10d6d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10d6e, 0x10d6e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10d6f, 0x10d85, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10d86, 0x10e7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10e80, 0x10ea9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10eaa, 0x10eaa, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10eab, 0x10eac, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10ead, 0x10eaf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10eb0, 0x10eb1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10eb2, 0x10ec1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10ec2, 0x10ec7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10ec8, 0x10ef9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10efa, 0x10eff, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10f00, 0x10f1c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10f1d, 0x10f26, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10f27, 0x10f27, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10f28, 0x10f2f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10f30, 0x10f45, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10f46, 0x10f50, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10f51, 0x10f6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10f70, 0x10f81, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10f82, 0x10f85, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x10f86, 0x10faf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10fb0, 0x10fc4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10fc5, 0x10fdf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x10fe0, 0x10ff6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x10ff7, 0x10fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11000, 0x11002, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11003, 0x11037, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11038, 0x11046, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11047, 0x11065, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11066, 0x11070, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11071, 0x11072, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11073, 0x11074, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11075, 0x11075, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11076, 0x1107e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1107f, 0x11082, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11083, 0x110af, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x110b0, 0x110ba, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x110bb, 0x110c1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x110c2, 0x110c2, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x110c3, 0x110cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x110d0, 0x110e8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x110e9, 0x110ef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x110f0, 0x110f9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x110fa, 0x110ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11100, 0x11102, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11103, 0x11126, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11127, 0x11134, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11135, 0x11135, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11136, 0x1113f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11140, 0x11143, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11144, 0x11144, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11145, 0x11146, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11147, 0x11147, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11148, 0x1114f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11150, 0x11172, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11173, 0x11173, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11174, 0x11175, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11176, 0x11176, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11177, 0x1117f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11180, 0x11182, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11183, 0x111b2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x111b3, 0x111c0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x111c1, 0x111c4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x111c5, 0x111c8, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x111c9, 0x111cc, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x111cd, 0x111cd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x111ce, 0x111d9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x111da, 0x111da, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x111db, 0x111db, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x111dc, 0x111dc, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x111dd, 0x111ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11200, 0x11211, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11212, 0x11212, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11213, 0x1122b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1122c, 0x11237, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11238, 0x1123d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1123e, 0x1123e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1123f, 0x11240, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11241, 0x11241, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11242, 0x1127f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11280, 0x11286, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11287, 0x11287, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11288, 0x11288, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11289, 0x11289, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1128a, 0x1128d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1128e, 0x1128e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1128f, 0x1129d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1129e, 0x1129e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1129f, 0x112a8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x112a9, 0x112af, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x112b0, 0x112de, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x112df, 0x112ea, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x112eb, 0x112ef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x112f0, 0x112f9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x112fa, 0x112ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11300, 0x11303, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11304, 0x11304, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11305, 0x1130c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1130d, 0x1130e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1130f, 0x11310, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11311, 0x11312, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11313, 0x11328, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11329, 0x11329, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1132a, 0x11330, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11331, 0x11331, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11332, 0x11333, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11334, 0x11334, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11335, 0x11339, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1133a, 0x1133a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1133b, 0x1133c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1133d, 0x1133d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1133e, 0x11344, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11345, 0x11346, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11347, 0x11348, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11349, 0x1134a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1134b, 0x1134d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1134e, 0x1134f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11350, 0x11350, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11351, 0x11356, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11357, 0x11357, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11358, 0x1135c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1135d, 0x11361, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11362, 0x11363, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11364, 0x11365, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11366, 0x1136c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1136d, 0x1136f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11370, 0x11374, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11375, 0x11379, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11380, 0x11389, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1138a, 0x1138a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1138b, 0x1138b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1138c, 0x1138d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1138e, 0x1138e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1138f, 0x1138f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11390, 0x113b5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x113b6, 0x113b6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x113b7, 0x113b7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x113b8, 0x113c0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113c1, 0x113c1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x113c2, 0x113c2, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113c3, 0x113c4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x113c5, 0x113c5, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113c6, 0x113c6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x113c7, 0x113ca, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113cb, 0x113cb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x113cc, 0x113d0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113d1, 0x113d1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x113d2, 0x113d2, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113d3, 0x113d3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x113d4, 0x113e0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x113e1, 0x113e2, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x113e3, 0x113ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11400, 0x11434, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11435, 0x11446, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11447, 0x1144a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1144b, 0x1144f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11450, 0x11459, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1145a, 0x1145d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1145e, 0x1145e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1145f, 0x11461, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11462, 0x1147f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11480, 0x114af, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x114b0, 0x114c3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x114c4, 0x114c5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x114c6, 0x114c6, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x114c7, 0x114c7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x114c8, 0x114cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x114d0, 0x114d9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x114da, 0x1157f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11580, 0x115ae, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x115af, 0x115b5, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x115b6, 0x115b7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x115b8, 0x115c0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x115c1, 0x115d7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x115d8, 0x115db, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x115dc, 0x115dd, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x115de, 0x115ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11600, 0x1162f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11630, 0x11640, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11641, 0x11643, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11644, 0x11644, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11645, 0x1164f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11650, 0x11659, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1165a, 0x1167f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11680, 0x116aa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x116ab, 0x116b7, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x116b8, 0x116b8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x116b9, 0x116bf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x116c0, 0x116c9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x116ca, 0x116cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x116d0, 0x116e3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x116e4, 0x116ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11700, 0x1171a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1171b, 0x1171c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1171d, 0x1172b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1172c, 0x1172f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11730, 0x11739, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1173a, 0x1173f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11740, 0x11746, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11747, 0x117ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11800, 0x1182b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1182c, 0x1183a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1183b, 0x1189f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x118a0, 0x118df, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x118e0, 0x118e9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x118ea, 0x118fe, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x118ff, 0x11906, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11907, 0x11908, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11909, 0x11909, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1190a, 0x1190b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1190c, 0x11913, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11914, 0x11914, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11915, 0x11916, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11917, 0x11917, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11918, 0x1192f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11930, 0x11935, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11936, 0x11936, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11937, 0x11938, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11939, 0x1193a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1193b, 0x1193e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1193f, 0x1193f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11940, 0x11940, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11941, 0x11941, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11942, 0x11943, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11944, 0x1194f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11950, 0x11959, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1195a, 0x1199f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x119a0, 0x119a7, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x119a8, 0x119a9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x119aa, 0x119d0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x119d1, 0x119d7, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x119d8, 0x119d9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x119da, 0x119e0, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x119e1, 0x119e1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x119e2, 0x119e2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x119e3, 0x119e3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x119e4, 0x119e4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x119e5, 0x119ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11a00, 0x11a00, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11a01, 0x11a0a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11a0b, 0x11a32, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11a33, 0x11a39, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11a3a, 0x11a3a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11a3b, 0x11a3e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11a3f, 0x11a46, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11a47, 0x11a47, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11a48, 0x11a4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11a50, 0x11a50, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11a51, 0x11a5b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11a5c, 0x11a89, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11a8a, 0x11a99, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11a9a, 0x11a9c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11a9d, 0x11a9d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11a9e, 0x11aaf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11ab0, 0x11af8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11af9, 0x11b5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11b60, 0x11b67, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11b68, 0x11bbf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11bc0, 0x11be0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11be1, 0x11bef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11bf0, 0x11bf9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11bfa, 0x11bff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11c00, 0x11c08, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11c09, 0x11c09, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11c0a, 0x11c2e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11c2f, 0x11c36, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11c37, 0x11c37, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11c38, 0x11c3f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11c40, 0x11c40, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11c41, 0x11c4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11c50, 0x11c59, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11c5a, 0x11c71, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11c72, 0x11c8f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11c90, 0x11c91, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11c92, 0x11ca7, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11ca8, 0x11ca8, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11ca9, 0x11cb6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11cb7, 0x11cff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d00, 0x11d06, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d07, 0x11d07, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d08, 0x11d09, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d0a, 0x11d0a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d0b, 0x11d30, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d31, 0x11d36, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d37, 0x11d39, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d3a, 0x11d3a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d3b, 0x11d3b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d3c, 0x11d3d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d3e, 0x11d3e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d3f, 0x11d45, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d46, 0x11d46, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d47, 0x11d47, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d48, 0x11d4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d50, 0x11d59, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d5a, 0x11d5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d60, 0x11d65, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d66, 0x11d66, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d67, 0x11d68, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d69, 0x11d69, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d6a, 0x11d89, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d8a, 0x11d8e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d8f, 0x11d8f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d90, 0x11d91, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d92, 0x11d92, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11d93, 0x11d97, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11d98, 0x11d98, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11d99, 0x11d9f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11da0, 0x11da9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11daa, 0x11daf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11db0, 0x11ddb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11ddc, 0x11ddf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11de0, 0x11de9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11dea, 0x11edf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11ee0, 0x11ef2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11ef3, 0x11ef6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11ef7, 0x11eff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11f00, 0x11f01, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11f02, 0x11f02, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11f03, 0x11f03, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11f04, 0x11f10, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11f11, 0x11f11, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11f12, 0x11f33, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11f34, 0x11f3a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11f3e, 0x11f42, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11f43, 0x11f49, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11f50, 0x11f5a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x11f5b, 0x11faf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x11fb0, 0x11fb0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x11fb1, 0x11fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x12000, 0x12399, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1239a, 0x123ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x12400, 0x1246e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1246f, 0x1247f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x12480, 0x12543, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x12544, 0x12f8f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x12f90, 0x12ff0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x12ff1, 0x12fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x13000, 0x1342f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x13440, 0x13440, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x13441, 0x13446, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x13447, 0x13455, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x13456, 0x1345f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x13460, 0x143fa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x143fb, 0x143ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x14400, 0x14646, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x14647, 0x1600f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16100, 0x1611d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1611e, 0x16139, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1613a, 0x167ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16800, 0x16a38, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16a39, 0x16a3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16a40, 0x16a5e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16a5f, 0x16a5f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16a60, 0x16a69, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16a6a, 0x16a6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16a70, 0x16abe, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16abf, 0x16abf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16ac0, 0x16ac9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16aca, 0x16acf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16ad0, 0x16aed, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16aee, 0x16aef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16af0, 0x16af4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16af5, 0x16aff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16b00, 0x16b2f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16b30, 0x16b36, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16b37, 0x16b3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16b40, 0x16b43, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16b44, 0x16b4f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16b50, 0x16b59, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16b5a, 0x16b62, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16b63, 0x16b77, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16b78, 0x16b7c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16b7d, 0x16b8f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16b90, 0x16d3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16d40, 0x16d6c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16d6d, 0x16d6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16d70, 0x16d79, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16d7a, 0x16e3f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16e40, 0x16e7f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16e80, 0x16e9f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16ea0, 0x16eb8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16eb9, 0x16eba, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16ebb, 0x16ed3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16ed4, 0x16eff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16f00, 0x16f4a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16f4b, 0x16f4e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16f4f, 0x16f4f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16f50, 0x16f50, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16f51, 0x16f87, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16f88, 0x16f8e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16f8f, 0x16f92, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16f93, 0x16f9f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16fa0, 0x16fdf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16fe0, 0x16fe1, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16fe2, 0x16fe2, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16fe3, 0x16fe3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16fe4, 0x16fe4, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16fe5, 0x16fef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x16ff0, 0x16ff1, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x16ff2, 0x16ff6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x16ff7, 0x16fff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x17000, 0x18cd5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x18cd6, 0x18cfe, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x18cff, 0x18d1e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x18d1f, 0x18d7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x18d80, 0x18df2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x18df3, 0x1afef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1aff0, 0x1aff3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1aff4, 0x1aff4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1aff5, 0x1affb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1affc, 0x1affc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1affd, 0x1affe, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1afff, 0x1afff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1b000, 0x1b122, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1b123, 0x1b131, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1b132, 0x1b132, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1b133, 0x1b14f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1b150, 0x1b152, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1b153, 0x1b154, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1b155, 0x1b155, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1b156, 0x1b163, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1b164, 0x1b167, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1b168, 0x1b16f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1b170, 0x1b2fb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1b2fc, 0x1bbff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1bc00, 0x1bc6a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1bc6b, 0x1bc6f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1bc70, 0x1bc7c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1bc7d, 0x1bc7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1bc80, 0x1bc88, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1bc89, 0x1bc8f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1bc90, 0x1bc99, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1bc9a, 0x1bc9c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1bc9d, 0x1bc9e, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1bc9f, 0x1ccef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ccf0, 0x1ccf9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1ccfa, 0x1ceff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1cf00, 0x1cf2d, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1cf2e, 0x1cf2f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1cf30, 0x1cf46, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1cf47, 0x1d164, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d165, 0x1d169, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d16a, 0x1d16c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d16d, 0x1d172, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d173, 0x1d17a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d17b, 0x1d182, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d183, 0x1d184, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d185, 0x1d18b, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d18c, 0x1d1a9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d1aa, 0x1d1ad, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d1ae, 0x1d241, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d242, 0x1d244, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d245, 0x1d3ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d400, 0x1d454, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d455, 0x1d455, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d456, 0x1d49c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d49d, 0x1d49d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d49e, 0x1d49f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4a0, 0x1d4a1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4a2, 0x1d4a2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4a3, 0x1d4a4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4a5, 0x1d4a6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4a7, 0x1d4a8, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4a9, 0x1d4ac, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4ad, 0x1d4ad, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4ae, 0x1d4b9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4ba, 0x1d4ba, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4bb, 0x1d4bb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4bc, 0x1d4bc, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4bd, 0x1d4c3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d4c4, 0x1d4c4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d4c5, 0x1d505, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d506, 0x1d506, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d507, 0x1d50a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d50b, 0x1d50c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d50d, 0x1d514, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d515, 0x1d515, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d516, 0x1d51c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d51d, 0x1d51d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d51e, 0x1d539, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d53a, 0x1d53a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d53b, 0x1d53e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d53f, 0x1d53f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d540, 0x1d544, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d545, 0x1d545, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d546, 0x1d546, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d547, 0x1d549, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d54a, 0x1d550, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d551, 0x1d551, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d552, 0x1d6a5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d6a6, 0x1d6a7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d6a8, 0x1d6c0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d6c1, 0x1d6c1, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d6c2, 0x1d6da, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d6db, 0x1d6db, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d6dc, 0x1d6fa, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d6fb, 0x1d6fb, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d6fc, 0x1d714, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d715, 0x1d715, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d716, 0x1d734, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d735, 0x1d735, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d736, 0x1d74e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d74f, 0x1d74f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d750, 0x1d76e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d76f, 0x1d76f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d770, 0x1d788, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d789, 0x1d789, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d78a, 0x1d7a8, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d7a9, 0x1d7a9, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d7aa, 0x1d7c2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d7c3, 0x1d7c3, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d7c4, 0x1d7cb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1d7cc, 0x1d7cd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1d7ce, 0x1d7ff, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1d800, 0x1d9ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1da00, 0x1da36, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1da37, 0x1da3a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1da3b, 0x1da6c, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1da6d, 0x1da74, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1da75, 0x1da75, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1da76, 0x1da83, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1da84, 0x1da84, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1da85, 0x1da9a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1da9b, 0x1da9f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1daa0, 0x1daa0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1daa1, 0x1daaf, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1dab0, 0x1deff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1df00, 0x1df1e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1df1f, 0x1df24, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1df25, 0x1df2a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1df2b, 0x1dfff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e000, 0x1e006, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e007, 0x1e007, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e008, 0x1e018, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e019, 0x1e01a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e01b, 0x1e021, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e022, 0x1e022, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e023, 0x1e024, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e025, 0x1e025, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e026, 0x1e02a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e02b, 0x1e02f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e030, 0x1e06d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e06e, 0x1e08e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e08f, 0x1e08f, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e090, 0x1e0ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e100, 0x1e12c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e12d, 0x1e12f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e130, 0x1e136, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e137, 0x1e13d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e13e, 0x1e13f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e140, 0x1e149, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e14a, 0x1e14d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e14e, 0x1e14e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e14f, 0x1e28f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e290, 0x1e2ad, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e2ae, 0x1e2ae, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e2af, 0x1e2bf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e2c0, 0x1e2eb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e2ec, 0x1e2f9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e2fa, 0x1e4cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e4d0, 0x1e4eb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e4ec, 0x1e4f9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e4fa, 0x1e5cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e5d0, 0x1e5ed, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e5ee, 0x1e5ef, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e5f0, 0x1e5f0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e5f1, 0x1e5fa, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e5fb, 0x1e6bf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e6c0, 0x1e6de, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e6dF, 0x1e6df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e6e0, 0x1e6e2, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e6e3, 0x1e6e3, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e6e4, 0x1e6e5, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e6e6, 0x1e6e6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e6e7, 0x1e6ed, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e6ee, 0x1e6ef, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e6f0, 0x1e6f4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e6f5, 0x1e6f5, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e6f6, 0x1e6fd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e6fe, 0x1e6ff, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e700, 0x1e7df, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e7e0, 0x1e7e6, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e7e7, 0x1e7e7, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e7e8, 0x1e7eb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e7ec, 0x1e7ec, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e7ed, 0x1e7ee, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e7ef, 0x1e7ef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e7f0, 0x1e7fe, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e7ff, 0x1e7ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e800, 0x1e8c4, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e8c5, 0x1e8cf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e8d0, 0x1e8d6, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e8d7, 0x1e8ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e900, 0x1e943, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e944, 0x1e94a, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e94b, 0x1e94b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1e94c, 0x1e94f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1e950, 0x1e959, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1e95a, 0x1edff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee00, 0x1ee03, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee04, 0x1ee04, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee05, 0x1ee1f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee20, 0x1ee20, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee21, 0x1ee22, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee23, 0x1ee23, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee24, 0x1ee24, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee25, 0x1ee26, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee27, 0x1ee27, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee28, 0x1ee28, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee29, 0x1ee32, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee33, 0x1ee33, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee34, 0x1ee37, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee38, 0x1ee38, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee39, 0x1ee39, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee3a, 0x1ee3a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee3b, 0x1ee3b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee3c, 0x1ee41, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee42, 0x1ee42, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee43, 0x1ee46, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee47, 0x1ee47, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee48, 0x1ee48, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee49, 0x1ee49, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee4a, 0x1ee4a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee4b, 0x1ee4b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee4c, 0x1ee4c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee4d, 0x1ee4f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee50, 0x1ee50, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee51, 0x1ee52, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee53, 0x1ee53, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee54, 0x1ee54, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee55, 0x1ee56, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee57, 0x1ee57, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee58, 0x1ee58, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee59, 0x1ee59, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee5a, 0x1ee5a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee5b, 0x1ee5b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee5c, 0x1ee5c, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee5d, 0x1ee5d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee5e, 0x1ee5e, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee5f, 0x1ee5f, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee60, 0x1ee60, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee61, 0x1ee62, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee63, 0x1ee63, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee64, 0x1ee64, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee65, 0x1ee66, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee67, 0x1ee6a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee6b, 0x1ee6b, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee6c, 0x1ee72, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee73, 0x1ee73, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee74, 0x1ee77, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee78, 0x1ee78, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee79, 0x1ee7c, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee7d, 0x1ee7d, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee7e, 0x1ee7e, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee7f, 0x1ee7f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee80, 0x1ee89, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee8a, 0x1ee8a, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1ee8b, 0x1ee9b, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1ee9c, 0x1eea0, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1eea1, 0x1eea3, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1eea4, 0x1eea4, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1eea5, 0x1eea9, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1eeaa, 0x1eeaa, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1eeab, 0x1eebb, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x1eebc, 0x1fbef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x1fbf0, 0x1fbf9, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0x1fbfa, 0x1fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x20000, 0x2a6df, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x2a6e0, 0x2a6ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x2a700, 0x2b81d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x2b81e, 0x2b81f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x2b820, 0x2cead, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x2ceae, 0x2ceaf, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x2ceb0, 0x2ebe0, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x2ebe1, 0x2ebef, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x2ebf0, 0x2ee5d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x2ee5e, 0x2f7ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x2f800, 0x2fa1d, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x2fa1e, 0x2fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x30000, 0x3134a, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x3134b, 0x3134f, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x31350, 0x33479, _ | _ | CPP11 | UAX44, _ | _ | _ }, { 0x3347a, 0x3fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x40000, 0x4fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x50000, 0x5fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x60000, 0x6fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x70000, 0x7fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x80000, 0x8fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0x90000, 0x9fffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0xa0000, 0xafffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0xb0000, 0xbfffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0xc0000, 0xcfffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0xd0000, 0xdfffd, _ | _ | CPP11 | _ , _ | _ | _ }, { 0xe0000, 0xe00ff, _ | _ | CPP11 | _ , _ | _ | _ }, { 0xe0100, 0xe01ef, _ | _ | CPP11 | UAX44, _ | _ | UAX44 }, { 0xe01f0, 0xefffd, _ | _ | CPP11 | _ , _ | _ | _ } }; #undef _ #if !(CPPCLI_ENABLING_POSSIBLE && EDG_WIN32) static #endif /* !(CPPCLI_ENABLING_POSSIBLE && EDG_WIN32) */ an_error_code is_valid_UCN_identifier_char( unsigned long uchar, a_boolean is_identifier_start); /* Variables pertaining to the input stack (for include files and the primary source file) and the current input file (the top entry on the stack). */ STATIC_THREAD an_input_stack_entry_ptr input_stack; /* Input stack, one entry for each active source file. Entry [0] is for the primary source file. Dynamically allocated, reallocated if necessary; size_input_stack gives the current allocated size. Not per-file. */ STATIC_THREAD int size_input_stack; /* Allocated size of input_stack, in elements not bytes. Not per-file. */ #define INPUT_STACK_INCREMENTAL_ALLOCATION 30 /* The number of elements added to input_stack each time it is reallocated; also the initial allocation. */ STATIC_THREAD int depth_input_stack; /* Depth of the input stack, minus 1. input_stack[depth_input_stack] is the active entry. -1 if the stack is completely empty. */ STATIC_THREAD an_input_stack_entry_ptr base_ise; /* The topmost entry in the input stack that reflects an actual file, as opposed to a cloned entry for tracking the tree structure of #line directives. */ STATIC_THREAD FILE *curr_input_stream; /* The currently active source input stream. */ STATIC_THREAD a_boolean eof_read_on_curr_input_stream; /* TRUE if end of file has been reached on the current source stream. This can indicate the end of an include file or the end of the primary source file. This is an internal variable indicating a physical end of file, and should not be confused with the logical end of file variables like after_end_of_all_source. */ #if UNICODE_SOURCE_SUPPORTED STATIC_THREAD a_getc_source_state curr_file_getc_source_state; /* State indication for getc_source for the current input file. */ #endif /* UNICODE_SOURCE_SUPPORTED */ /* getc-like macro that fetches a character from the current input file. Converts UTF-16 to UTF-8 in configurations that support that. */ #define getc_curr_input_stream() \ (getc_source(curr_input_stream, curr_file_getc_source_state)) /* Variables related to the current source line (see lexical.h): */ STATIC_THREAD a_boolean after_end_of_all_source; /* If TRUE, there is no current logical source line; all source has been read, all files popped off the input stack. The current sequence number indicates a position one line beyond the last line actually read. We are no longer inside any source file; we are past the end of the primary file. */ STATIC_THREAD a_boolean any_tokens_gotten_from_curr_source_line; /* If TRUE, one or more tokens (normal or preprocessor) have been gotten from the current source line. If FALSE, nothing (except possibly white space) has been gotten from that line. This is used in determining whether or not a "#" is the start of a preprocessing directive. */ STATIC_THREAD a_boolean no_modifs_to_curr_source_line; /* TRUE if source_line_modif_list == NULL and orig_line_modif_list == NULL. Used as a speed optimization in deciding whether or not one can use the fast method of converting a line location into a source position. */ STATIC_THREAD a_boolean init_do_not_put_curr_line_in_pp_output; /* The value of do_not_put_curr_line_in_pp_output that applies to any text inserted at the beginning of the current source line. Such text may have to be put out even if the main line is suppressed (e.g., if the inserted text is an inert macro identifier and the new line is a preprocessing directive). */ STATIC_THREAD char curr_raw_listing_line_code; /* When a raw listing file is being generated, this indicates the code for the current line: 'S' for a line skipped by an if-skip, 'N' for a normal line, or '\0' if there is no current line. */ STATIC_THREAD char *raw_listing_buffer; /* Buffer used in writing the macro-expanded versions of source lines to the raw listing file. Space is dynamically allocated, and its upper bound is given by after_end_of_raw_listing_buffer. See lexical_init for the initial allocation. */ #define RAW_LISTING_BUFFER_INITIAL_ALLOCATION 3000 /* Initial allocation size for raw_listing_buffer. Subsequent reallocations will double the amount previously allocated. Should probably match the corresponding constant for curr_source_line. */ STATIC_THREAD char *after_end_of_raw_listing_buffer; /* Address past the last element of raw_listing_buffer, as an aid to checking for overflow, etc. A variable because raw_listing_buffer can be reallocated larger if needed. */ STATIC_THREAD char *loc_in_raw_listing_buffer; /* Current output location in raw_listing_buffer. */ STATIC_THREAD a_boolean must_display_raw_listing_buffer; /* TRUE if raw_listing_buffer contains a line or lines modified in nontrivial ways (comments are considered trivial modifications). If so, it must be displayed. */ STATIC_THREAD a_boolean in_token_insertion_from_string; /* TRUE during the initial scan of tokens that are being inserted from a string. */ STATIC_THREAD a_source_position token_insertion_position; /* The source position to be used for tokens inserted from a string. */ #if GET_DEFINITION_OF_CLASS_NEEDED STATIC_THREAD a_text_buffer_ptr class_def_buffer; /* A buffer used by get_definition_of_class */ #endif /* GET_DEFINITION_OF_CLASS_NEEDED */ #if MICROSOFT_EXTENSIONS_ALLOWED STATIC_THREAD a_text_buffer_ptr metadata_import_buffer; /* Text buffer used by generate_top_level_metadata_code. */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ STATIC_THREAD a_text_buffer_ptr ucn_buffer; /* A text buffer used to hold a temporary copy of identifiers containing universal character names. */ STATIC_THREAD a_text_buffer_ptr suffix_replacement_buffer; /* A text buffer used to store a copy of the file name when doing suffix replacement. */ STATIC_THREAD a_boolean curr_line_began_inside_comment; /* TRUE if the call to read_logical_source_line for for the current line occurred during the scan of a multi-line comment. This is used by gen_pp_line_info to avoid inserting line directives into comments when keep_comments_in_pp_output is TRUE. */ STATIC_THREAD a_boolean pending_nonsplice_backslash; /* TRUE if the current line ended in a backslash followed by whitespace (i.e., not a line splice except in GNU modes) and a warning should be put out. The warning is issued on the next call to read_logical_source_line, to allow it to be suppressed if the non-splice occurs inside a comment. */ STATIC_THREAD sizeof_t offset_of_nonsplice_backslash; /* Offset within curr_source_line of the backslash that resulted in the warning to be issued when pending_nonsplice_backslash is TRUE (invalid if pending_nonsplice_backslash is FALSE). Kept as an offset instead of a pointer in case curr_source_line is reallocated between calls to read_logical_source_line. */ STATIC_THREAD a_text_buffer_ptr ud_lit_suffix_buffer; /* Text buffer used for the ud-suffix of a user-defined-string-literal during concatenation of adjacent string literals. */ /* Hash table used by nested_source_line_modif to find the source line modification associated with the ATTENTION_MARKER at a given location. */ #define SOURCE_LINE_MODIF_HASH_TABLE_SIZE 7993 STATIC_THREAD a_source_line_modif_ptr source_line_modif_hash_table [SOURCE_LINE_MODIF_HASH_TABLE_SIZE]; /* Information about cached tokens, i.e., tokens saved for later rescanning. */ STATIC_THREAD a_token_stack *cached_token_rescan_stack; /* If non-NULL, points to a stack of cached tokens that should be rescanned by get_token before going forward in the input stream. */ static constexpr size_t cached_token_rescan_init_capacity = 20; /* The initial capacity of a new token stack to be added to the cached token rescan stack. */ STATIC_THREAD a_constant_ptr avail_cached_constants; /* List of constant entries (allocated in front end storage) for use with token caching entries, freed and available for reuse. */ /* Information about data structures used for working with persistent token caches. */ STATIC_THREAD a_reusable_cache_entry_ptr avail_reusable_cache_entries; /* List of reusable cache stack entries (allocated in front end storage) freed and available for reuse. */ STATIC_THREAD a_reusable_cache_entry_ptr reusable_cache_stack; /* The stack of reusable caches that are currently active. */ STATIC_THREAD a_stop_token_stack_entry_ptr avail_stop_token_stack_entries; /* List of stop token stack entries that have been freed and are available for reuse. */ STATIC_THREAD a_lexical_state_stack_entry_ptr avail_lexical_state_stack_entries; /* List of lexical state stack entries that have been freed and are available for reuse. */ STATIC_THREAD a_boolean trigraph_diagnostic_issued; /* TRUE if a diagnostic indicating that trigraphs are disabled has already been issued. Actually, it is set to TRUE once trigraph_column has been set. */ STATIC_THREAD a_column_number trigraph_column; /* If a trigraph was encountered when trigraphs are disabled, this is the column in the line of the trigraph so that a diagnostic can be issued. */ STATIC_THREAD a_boolean scanning_microsoft_asm; /* TRUE while scanning a Microsoft asm. */ /* Hash table of history information about include files that have been processed. Used to suppress subsequent inclusions of the same file. */ STATIC_THREAD a_hash_table_ptr include_file_history_hash_table; #if UNIQUE_FILE_IDENTIFIER_AVAILABLE /* Hash table of information about include files that have been processed with a key that is a unique file identifier (e.g., an inode number on Unix-like systems). Used to suppress subsequent inclusions of the same file. */ STATIC_THREAD a_hash_table_ptr unique_file_id_hash_table; #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ /* Hash table of information about include file searches files that have been performed. Used to optimize subsequent searches for the same file. */ STATIC_THREAD a_hash_table_ptr include_search_hash_table; /* Array of identifier lookup options indexed by identifier lookup mode. Used to translate the lookup mode into a set of identifier lookup options. */ static an_id_lookup_options_set idl_options_for_lookup_mode[(int)ilm_last+1]= { /* ilm_normal */ IDL_NO_OPTIONS, /* ilm_tag */ IDL_MUST_BE_TAG, /* ilm_tentative_type */ IDL_TENTATIVE_TYPE_LOOKUP, /* ilm_ctor_initializer_name */ IDL_SKIP_CURR_SCOPE, /* ilm_qualified_ctor_initializer_name */ IDL_SKIP_CURR_SCOPE | IDL_MUST_BE_CLASS, /* ilm_namespace */ IDL_MUST_BE_NAMESPACE, /* ilm_typename */ IDL_TYPENAME_LOOKUP, /* ilm_class */ IDL_MUST_BE_CLASS, /* ilm_template_linkage */ IDL_LINKAGE_LOOKUP | IDL_TREAT_AS_TEMPLATE_ID | IDL_USE_PROTOTYPE_NOT_NONREAL, /* ilm_using_declaration */ IDL_USING_DECLARATION, /* ilm_using_typename */ IDL_USING_DECLARATION | IDL_TYPENAME_LOOKUP, /* ilm_expr */ IDL_IS_EXPR_CONTEXT, /* ilm_declarator */ IDL_IS_DECLARATOR, #if MICROSOFT_EXTENSIONS_ALLOWED /* ilm_static_declarator */ IDL_IS_DECLARATOR | IDL_IS_STATIC_DECL, #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* ilm_template_friend */ IDL_FRIEND_LOOKUP | IDL_TREAT_AS_TEMPLATE_ID | IDL_USE_PROTOTYPE_NOT_NONREAL, /* ilm_template_tag */ IDL_MUST_BE_TAG | IDL_TREAT_AS_TEMPLATE_ID | IDL_USE_PROTOTYPE_NOT_NONREAL, /* ilm_template_template_arg */ IDL_TREAT_AS_TEMPLATE_ID, /* ilm_last */ IDL_NO_OPTIONS }; /* Data structure used to represent a list of file suffixes. */ typedef struct a_file_suffix *a_file_suffix_ptr; typedef struct a_file_suffix { a_file_suffix_ptr next; /* Pointer to the next entry on the list. */ char *suffix; /* Pointer to a null terminated string containing the suffix. It does not contain any delimiter to be used between the filename and the suffix. */ } a_file_suffix; #if INSTANTIATION_BY_IMPLICIT_INCLUSION STATIC_THREAD a_file_suffix_ptr implicit_instantiation_file_suffix_list; #endif /* INSTANTIATION_BY_IMPLICIT_INCLUSION */ STATIC_THREAD a_file_suffix_ptr include_file_suffix_list; /* List of file suffixes used when searching for a header file whose name does not include a suffix. */ STATIC_THREAD a_file_suffix_ptr sun_include_file_suffix_list; /* List of file suffixes used in Sun mode when searching for includes specified with the <...> syntax. */ #if MICROSOFT_EXTENSIONS_ALLOWED /* Entry used to store the canonical form of a whitespace keyword (i.e., the first word followed by a single space followed by the second word). An array of these entries is used to create source line modifications to replace the actual spelling of the keyword in the source, which may have varying amounts and kinds of white space separating the words. */ typedef struct a_whitespace_keyword *a_whitespace_keyword_ptr; typedef struct a_whitespace_keyword { char *text; /* Pointer to the spelling of the keyword. */ char *end_of_insertion; /* Pointer to the LE_END_OF_INSERTION lexical escape that terminates the spelling of the keyword. */ } a_whitespace_keyword; /* Pointer to a table of the canonical spellings of whitespace keywords, indexed by the token kind (offset by tok_first_white_space_token). It is initialized by init_whitespace_keywords. */ STATIC_THREAD a_whitespace_keyword_ptr whitespace_keywords; STATIC_THREAD a_boolean scanning_for_whitespace_keyword; /* TRUE when skip_white_space() is being called to find the second word in a potential whitespace keyword. */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Declarations for handling reports of extended characters that cannot be represented in an ordinary narrow string literal. These cannot be reported on the fly when processing a string literal because string concatenation can change the literal kind. For example, "\U0001F602" is an error by itself, but the sequence "\U0001F602" U"." is not, because the result of string concatenation is equivalent to U"\U0001F602.". To handle such cases, register_char_overflow will be called when an extended character or out-of-range numeric escape appears in an ordinary string literal; concat_string_literals will call clear_char_overflows if the resulting literal kind is not an ordinary narrow string literal, and get_token will call report_char_overflows after doing string concatenation. */ struct a_char_overflow { a_char_overflow *next; /* The next report in the list, or NULL for the last report. */ an_error_severity severity; /* The severity of the diagnostic to be issued. */ an_error_code err_code; /* The diagnostic to report at the given position. */ a_source_position pos; /* The source position at which the character appears. */ }; /* a_char_overflow */ STATIC_THREAD a_char_overflow *pending_overflow_reports; /* A list of entries for which diagnostics may be needed. */ STATIC_THREAD a_char_overflow *last_pending_overflow_report; /* The last item in the list of overflow entries. */ STATIC_THREAD a_char_overflow *available_overflow_reports; /* A list of entries that can be reused. */ void register_char_overflow(an_error_severity severity, an_error_code err_code, a_source_position *pos) /* Add a report with the given code, severity, and source position at the end of the list of pending overflow reports. */ { a_char_overflow *cop; if (available_overflow_reports != NULL) { cop = available_overflow_reports; available_overflow_reports = cop->next; } else { cop = alloc_fe_of_type(a_char_overflow); } /* if */ cop->severity = severity; cop->err_code = err_code; cop->pos = *pos; cop->next = NULL; if (last_pending_overflow_report != NULL) { last_pending_overflow_report->next = cop; } else { pending_overflow_reports = cop; } /* if */ last_pending_overflow_report = cop; } /* register_char_overflow */ void clear_char_overflows(void) /* Empty the list of pending overflow reports, placing any existing reports on the "available" list. */ { if (last_pending_overflow_report != NULL) { last_pending_overflow_report->next = available_overflow_reports; available_overflow_reports = pending_overflow_reports; pending_overflow_reports = NULL; last_pending_overflow_report = NULL; } /* if */ } /* clear_char_overflows */ static inline void report_char_overflows(void) /* Issue diagnostics for each position in the list of pending overflow reports and empty the list. */ { if (pending_overflow_reports != NULL) { for (a_char_overflow *cop = pending_overflow_reports; cop != NULL; cop = cop->next) { pos_diagnostic(cop->severity, cop->err_code, &cop->pos); } /* for */ clear_char_overflows(); } /* if */ } /* report_char_overflows */ #if DEBUG /* Counts of tables allocated, to track total use of memory. */ STATIC_THREAD unsigned long num_orig_line_modifs_allocated, num_source_line_modifs_allocated, num_concatenation_records_allocated, num_cached_constants_allocated, num_file_suffixes_allocated, num_include_file_histories_allocated, num_preinclude_files_allocated, num_include_search_results_allocated, cached_pp_token_string_space, num_stop_token_stack_entries_allocated, num_lexical_state_stack_entries_allocated, num_reusable_cache_entries_allocated, num_compares_in_source_line_modif_hash_table, #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED num_id_representations_allocated, num_spelling_storage_buffers_allocated, spelling_storage_buffer_space, #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ num_lookups_in_source_line_modif_hash_table; #if MICROSOFT_EXTENSIONS_ALLOWED static void db_dump_metadata(a_text_buffer_ptr buffer, sizeof_t limit) /* Utility to display a buffer containing metadata (for which the '\r' character is used in place of newline characters). Limit the output to "limit" characters (plus an ellipsis if the buffer size exceeds "limit") if "limit" is non-zero. Ensures that a '\n' is always the last character written. */ { size_t i; char ch = 0; if (limit == 0 || limit > buffer->size) limit = buffer->size; for (i = 0; i < limit; i++) { ch = buffer->buffer[i]; if (ch == '\r') ch = '\n'; fputc(ch, f_debug); } /* for */ if (limit < buffer->size) { fprintf(f_debug, "...\n"); } else if (ch != '\n') { fputc('\n', f_debug); } /* if */ } /* db_dump_metadata */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ #endif /* DEBUG */ a_boolean same_string_ignoring_underscores(a_const_char *s1, a_const_char *s2) /* Returns TRUE if s1 and s2 are the same string, or if s2 has two leading and trailing underscores, but is otherwise the same string as s1. So, for example, if s1 is "byte", s2 will match if it is either "byte" or "__byte__". */ { sizeof_t length_1, length_2; a_boolean result; /* If the strings are an exact match, return quickly. */ if (strcmp(s1, s2) == 0) { result = TRUE; } else if (s2[0] != '_' || s2[1] != '_') { /* If s2 does not start with two underscores, there is no match. */ result = FALSE; } else { /* Ignore the first two underscores of s2. */ s2 += 2; /* Calculate the length of the remaining strings. */ length_1 = (sizeof_t)strlen(s1); length_2 = (sizeof_t)strlen(s2); /* If the last two characters of s2 are not underscores, there is no match. */ if (length_2 != length_1 + 2 || s2[length_2 - 2] != '_' || s2[length_2 - 1] != '_') { result = FALSE; } else { /* Compare the remainder of the strings. */ result = strncmp(s1, s2, size_t_arg(length_1)) == 0; } /* if */ } /* if */ return result; } /* same_string_ignoring_underscores */ static void unimplemented_keyword_diagnostic(a_symbol_ptr sym) /* Issue a diagnostic on unimplemented keywords. */ { if (sym->variant.keyword.diagnostic_issued_if_used != ec_no_error) { an_error_severity severity; severity = strict_ansi_mode ? strict_ansi_error_severity : es_remark; sym_diagnostic(severity, sym->variant.keyword.diagnostic_issued_if_used, sym); sym->variant.keyword.diagnostic_issued_if_used = ec_no_error; } /* if */ } /* unimplemented_keyword_diagnostic */ static void free_reusable_cache_entry(a_reusable_cache_entry_ptr rsep) /* Free a token cache stack entry, i.e., put it on the avail list to be reused. */ { rsep->token_cache.~a_reusable_token_cache(); rsep->next = avail_reusable_cache_entries; avail_reusable_cache_entries = rsep; } /* free_reusable_cache_entry */ static void pop_reusable_cache_stack(void) /* Restore the cached token rescan list to the state before the current reusable cache was pushed onto the stack. These tokens should be rescanned before we resume use of the next entry on the reusable stack. */ { a_reusable_cache_entry_ptr rcep = reusable_cache_stack; delete_fe(&cached_token_rescan_stack); cached_token_rescan_stack = rcep->previous_token_rescan_stack; reusable_cache_stack = rcep->next; free_reusable_cache_entry(rcep); recalc_any_initial_get_token_tests_needed(); } /* pop_reusable_cache_stack */ void increment_dependent_scans_for_reusable_cache(void) /* Increment the number of token scans depending on the current reusable cache. */ { check_assertion(reusable_cache_stack != NULL); reusable_cache_stack->dependent_scans++; } /* increment_dependent_scans_for_reusable_cache */ void decrement_dependent_scans_for_reusable_cache(void) /* Decrement the number of token scans depending on the current reusable cache. */ { check_assertion(reusable_cache_stack != NULL); check_assertion(reusable_cache_stack->dependent_scans > 0); reusable_cache_stack->dependent_scans--; if (!reusable_cache_stack->has_next_token() && reusable_cache_stack->dependent_scans == 0) { /* Don't pop this entry off the stack if it is currently being used for a variadic template rescan. */ pop_reusable_cache_stack(); } /* if */ } /* decrement_dependent_scans_for_reusable_cache */ static inline a_reusable_cache_entry_ptr alloc_reusable_cache_entry( const a_reusable_token_cache &cache) /* Allocate a reusable cache entry. Reuse a freed entry if possible. */ { a_reusable_cache_entry_ptr rsep; if (avail_reusable_cache_entries != NULL) { /* Reuse a freed entry. */ rsep = avail_reusable_cache_entries; avail_reusable_cache_entries = avail_reusable_cache_entries->next; } else { /* Allocate a new entry. */ rsep = (a_reusable_cache_entry_ptr) alloc_fe(sizeof(a_reusable_cache_entry)); #if DEBUG num_reusable_cache_entries_allocated++; #endif /* DEBUG */ } /* if */ rsep->next = NULL; rsep->previous_token_rescan_stack = NULL; rsep->dependent_scans = 0; rsep->next_token = cache->begin(); new (&rsep->token_cache) a_reusable_token_cache(cache); return rsep; } /* alloc_reusable_cache_entry */ a_constant_ptr alloc_cached_constant() /* Allocate a cached constant entry. Reuse a freed entry if possible. */ { a_constant_ptr cp; if (avail_cached_constants != NULL) { /* Reuse a freed entry. */ cp = avail_cached_constants; avail_cached_constants = avail_cached_constants->next; } else { /* Allocate a new entry. */ cp = (a_constant_ptr)alloc_fe(sizeof(a_constant)); #if DEBUG num_cached_constants_allocated++; #endif /* DEBUG */ } /* if */ return cp; } /* alloc_cached_constant */ void free_cached_token_constant(a_constant_ptr cp) /* Free the given constant used by a cached token, i.e., to put it on the avail list to be reused. */ { if (cp != NULL) { (cp)->next = avail_cached_constants; avail_cached_constants = (cp); } /* if */ } /* free_cached_token_constant */ void terminate_token_cache(a_token_cache *cache) /* Save an end-of-source token on the end of the list of tokens saved in *cache. */ { a_token_sequence_number seq = NO_TOKEN_SEQUENCE_NUMBER; /* Copy the token sequence number from the final token of the cache into the cache terminator. */ if (!cache->is_empty()) { const a_shared_token &last_token = cache->get_last_token(); seq = last_token->get_starting_seq_number(); } /* if */ /* Build an entry for the end-of-source token. */ a_cached_token token(tok_end_of_source, pos_curr_token, seq); /* Add the end-of-source token to the end of the cache. */ cache->append_token(token); } /* terminate_token_cache */ static inline void make_copy_of_pp_token(a_pp_token_descr_ptr pptdp) /* Allocates memory and copies the token string indicated by start_of_curr_token and end_of_curr_token to the newly allocated memory. */ { char *new_start; char *new_end; sizeof_t length; /* Compute the length of the string not including a null terminator that will be added. The terminator is not required for most processing, but it is used by add_token_to_string. */ length = (sizeof_t)(end_of_curr_token - start_of_curr_token) + 1; /* Allocate the string, adding space for the terminator. */ new_start = (char *)alloc_fe(length+1); #if DEBUG /* Track the amount of space used for pp token strings. */ cached_pp_token_string_space += (unsigned long)(length+1); #endif /* DEBUG */ new_end = new_start + length - 1; strncpy(new_start, start_of_curr_token, size_t_arg(length)); /* Add the terminator to the new string. */ new_start[length] = '\0'; pptdp->token_start = new_start; pptdp->token_end = new_end; } /* make_copy_of_pp_token */ static unsigned long hash_string(a_const_char *str) /* Compute a hash value for the string "str". */ { unsigned long value = 0; for (; *str != '\0'; str++) { value = (value << 5) + value + (unsigned long)*str; } /* for */ return value; } /* hash_string */ namespace detail { uintptr_t a_cached_token_base::hash_code() const /* Compute and return a hash code for the token. */ { uintptr_t result; if (this->is_pragma()) { /* Do not hash pragmas. */ result = 0; } else if (this->is_identifier()) { /* Hash the identifier string. */ a_string_view str_view(this->get_locator().symbol_header->identifier); result = hash_ptr(str_view); } else if (this->is_constant()) { /* Get a hash value for the constant. For historic reasons, the hash_constant function does not take a const a_constant. */ a_constant_ptr cp = const_cast(this->get_constant()); result = (uintptr_t)hash_constant(cp); } else if (this->is_asm_string()) { /* Hash the asm string. */ result = hash_string(this->get_asm_string()); } else if (this->is_ud_literal()) { /* Get a hash value for the spelling constant. */ const a_ud_literal_descr *ud_lit_descr = this->get_ud_literal_descr(); result = (uintptr_t)hash_constant(ud_lit_descr->spelling_con); } else if (this->is_unresolved_ud_literal()) { /* Hashing of this token is unexpected. See the description of tok_unresolved_ud_literal for more information. */ unexpected_condition(); } else { /* For other tokens, just use the token kind. */ result = (uintptr_t)this->get_kind(); } /* if */ return result; } /* a_cached_token_base::hash_code */ /* This class is given special permission to mess with the internals of detail::a_cached_token_base for the purposes of optimal construction. By using special access permissions to directly construct the extra token information in the destination object, these functions are able to significantly reduce the instruction count. */ struct a_token_factory { static INLINE a_shared_token copy_curr_token(); static INLINE a_shared_token copy_curr_pragma_as_token(); static INLINE a_shared_token build_constant(a_constant_ptr cp, const a_source_position *pos, a_token_kind kind); static INLINE a_shared_token build_tok_body_replacement( a_token_kind token, a_token_sequence_number start_seq, a_token_sequence_number end_seq, a_symbol_ptr symbol, a_boolean semicolon_inserted, const a_source_position *pos); static INLINE a_shared_token build_tok_removed_expr( a_token_sequence_number start_seq, const a_source_position *pos, a_token_cache_iterator begin, a_token_cache_iterator end); static INLINE a_shared_token build_tok_pragma(a_token_sequence_number seq, const a_source_position *pos); static INLINE a_shared_token build_tok_pp(char *text, a_targ_size_t len, const a_source_position *pos); static INLINE a_shared_token build_tok_identifier( a_const_char *str, a_targ_size_t len, const a_source_position *pos); static INLINE a_shared_token build_tok_ifc_ref( a_token_kind kind, a_lexical_ifc_index_reference ref, const a_source_position *pos); static INLINE a_shared_token build_tok_ud_literal( a_constant_ptr value_constant, a_constant_ptr spelling_constant, a_const_char *suffix, a_type_ptr type, const a_source_position *pos); static INLINE a_shared_token build_tok_resolved_type( a_type_ptr type, const a_source_position *pos); static INLINE a_shared_token build_tok_insert_string(); }; /* a_token_factory */ a_shared_token a_token_factory::copy_curr_token() /* Construct and return a shared token representing the current token. */ { /* Build an entry for the current token itself. */ a_shared_token result = shared_obj( curr_token, pos_curr_token, extra_il_src_pos(end_pos_curr_token), curr_token_sequence_number, last_token_sequence_number_of_token); auto &extra_info = result->extra_info; if (fetch_pp_tokens) { /* The token being saved is a pp token. Save this by copying the token string. */ result->extra_info_kind = teik_pp_token; make_copy_of_pp_token(&extra_info.pp_token_descr); } else if (curr_token == tok_identifier || curr_token == tok_ptr_to_member || curr_token == tok_decltype_construct if_microsoft_extensions(|| curr_token == tok_cli_typeid)) { /* Identifier -- save information about it. The locator is also saved for pointer-to-member and decltype_construct tokens. */ result->extra_info_kind = teik_identifier; new (&extra_info.locator) a_symbol_locator(locator_for_curr_id); } else if (curr_token == tok_microsoft_asm) { result->extra_info_kind = teik_asm_string; extra_info.asm_string = curr_token_asm_string; } else if (is_literal_constant_token(curr_token)) { /* Literal constant -- save the constant's value. */ result->extra_info_kind = teik_constant; extra_info.constant = alloc_cached_constant(); /* Copy the constant. Note that anything pointed to by the constant (e.g., a string) has been allocated in the file scope and doesn't need to be copied. */ copy_constant(&const_for_curr_token, extra_info.constant); } else if (curr_token == tok_pending_ifc_expr || curr_token == tok_ifc_entity_ref || curr_token == tok_ifc_decl_ref || curr_token == tok_ifc_type_ref || curr_token == tok_ifc_param_ref || curr_token == tok_ifc_decl) { /* This token has an associated index. */ result->extra_info_kind = teik_ifc_index; new (&extra_info.ifc_index) a_lexical_ifc_index_reference( ifc_index_for_curr_token); } else if (curr_token == tok_ud_literal) { /* Save the information needed to restore the user-defined literal from the cache. */ result->extra_info_kind = teik_ud_lit; extra_info.ud_lit.value_con = alloc_cached_constant(); copy_constant(&const_for_curr_token, extra_info.ud_lit.value_con); extra_info.ud_lit.spelling_con = alloc_cached_constant(); copy_constant(&const_with_curr_tok_spelling, extra_info.ud_lit.spelling_con); extra_info.ud_lit.op_sym = ud_lit_op_sym_for_curr_token; extra_info.ud_lit.suffix = ud_suffix_from_literal_operator_id( locator_for_curr_id.symbol_header->identifier); extra_info.ud_lit.type = ud_lit_type_for_curr_token; } else if (curr_token == tok_unresolved_ud_literal) { /* Save the information needed to restore the unresolved user-defined literal from the cache. */ result->extra_info_kind = teik_unresolved_ud_lit; extra_info.unresolved_ud_lit.value_con = alloc_cached_constant(); copy_constant(&const_for_curr_token, extra_info.unresolved_ud_lit.value_con); extra_info.unresolved_ud_lit.spelling_con = alloc_cached_constant(); copy_constant(&const_with_curr_tok_spelling, extra_info.unresolved_ud_lit.spelling_con); extra_info.unresolved_ud_lit.curr_id_locator = locator_for_curr_id; } else { /* No extra information needed for this token. */ } /* if */ return result; } /* a_token_factory::copy_curr_token */ a_shared_token a_token_factory::copy_curr_pragma_as_token() /* Construct and return a shared token representing the current token's pragmas. */ { /* Use the token sequence number information for the current token to maintain ordering of the token sequence. */ a_shared_token result = shared_obj( tok_error, (*curr_token_pragmas)[0]->pragma_position, (*curr_token_pragmas)[0]->pragma_position, curr_token_sequence_number, last_token_sequence_number_of_token); check_assertion(!curr_token_pragmas->is_empty()); result->extra_info_kind = teik_pragma; new (&result->extra_info.pragmas) a_pending_pragma_list(*curr_token_pragmas); return result; } /* a_token_factory::copy_curr_pragma_as_token */ a_shared_token a_token_factory::build_constant( a_constant_ptr cp, const a_source_position *pos, a_token_kind kind) /* This function is an implementation detail of build_tok_constant; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token( kind, assign_new_token_sequence_number(), pos); result->extra_info_kind = teik_constant; result->extra_info.constant = alloc_cached_constant(); copy_constant(cp, result->extra_info.constant); return result; } /* a_token_factory::build_constant */ a_shared_token a_token_factory::build_tok_body_replacement( a_token_kind token, a_token_sequence_number start_seq, a_token_sequence_number end_seq, a_symbol_ptr symbol, a_boolean semicolon_inserted, const a_source_position *pos) /* This function is an implementation detail of build_tok_body_replacement; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = shared_obj(token, *pos, *pos, start_seq, end_seq); result->extra_info_kind = teik_extracted_body; result->extra_info.extracted_template.symbol = symbol; result->extra_info.extracted_template.semicolon_inserted = semicolon_inserted; return result; } /* a_token_factory::build_tok_body_replacement */ a_shared_token a_token_factory::build_tok_removed_expr( a_token_sequence_number start_seq, const a_source_position *pos, a_token_cache_iterator begin, a_token_cache_iterator end) /* This function is an implementation detail of build_tok_removed_expr; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token(tok_removed_expr, start_seq, pos); result->extra_info_kind = teik_removed_expr; new (&result->extra_info.removed_expr) a_removed_expr_descr(); if (begin != end) { /* Copy the tokens using a non-destructive move. */ result->extra_info.removed_expr.cache = new_fe(/*reusable=*/TRUE, distance(begin, end)); result->extra_info.removed_expr.cache->move_copy_tokens(begin, end); } /* if */ return result; } /* a_token_factory::build_tok_removed_expr */ a_shared_token a_token_factory::build_tok_pragma( a_token_sequence_number seq, const a_source_position *pos) /* This function is an implementation detail of build_tok_pragma; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token(tok_error, seq, pos); result->extra_info_kind = teik_pragma; new (&result->extra_info.pragmas) a_pending_pragma_list(); return result; } /* a_token_factory::build_tok_pragma */ a_shared_token a_token_factory::build_tok_pp(char *text, a_targ_size_t len, const a_source_position *pos) /* This function is an implementation detail of build_tok_pragma; prefer that function for most use cases and see it for a description of the arguments. */ { check_assertion(text != NULL); a_shared_token result = build_cached_token( tok_identifier, assign_new_token_sequence_number(), pos); result->extra_info_kind = teik_pp_token; result->extra_info.pp_token_descr.token_start = text; result->extra_info.pp_token_descr.token_end = text + len; return result; } /* a_token_factory::build_tok_pp */ a_shared_token a_token_factory::build_tok_identifier( a_const_char *str, a_targ_size_t len, const a_source_position *pos) /* This function is an implementation detail of build_tok_identifier; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token( tok_identifier, assign_new_token_sequence_number(), pos); result->extra_info_kind = teik_identifier; clear_locator(&result->extra_info.locator, pos); (void)find_symbol(str, len, &result->extra_info.locator); return result; } /* a_token_factory::build_tok_identifier */ a_shared_token a_token_factory::build_tok_ifc_ref( a_token_kind kind, a_lexical_ifc_index_reference ref, const a_source_position *pos) /* This function is an implementation detail of build_tok_ifc_ref; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token( kind, assign_new_token_sequence_number(), pos); result->extra_info_kind = teik_ifc_index; result->extra_info.ifc_index = ref; return result; } /* a_token_factory::build_tok_ifc_ref */ a_shared_token a_token_factory::build_tok_ud_literal( a_constant_ptr value_constant, a_constant_ptr spelling_constant, a_const_char *suffix, a_type_ptr type, const a_source_position *pos) /* This function is an implementation detail of build_tok_ud_literal; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token( tok_ud_literal, assign_new_token_sequence_number(), pos); /* Save the current token state and mark this as a tok_unresolved_ud_literal as find_literal_operator can mess with the current lexical state. */ a_tiny_scanning_token_cache cache; a_const_char *save_start_of_curr_token = start_of_curr_token; a_const_char *save_end_of_curr_token = end_of_curr_token; /* Create a new lexical state for the rescan operation. */ push_lexical_state_stack(); cache_curr_token(cache.ptr()); curr_token = tok_unresolved_ud_literal; result->extra_info_kind = teik_ud_lit; result->extra_info.ud_lit.value_con = value_constant; result->extra_info.ud_lit.spelling_con = spelling_constant; result->extra_info.ud_lit.type = type; a_targ_size_t suffix_len = (a_targ_size_t)(strlen(suffix) + 1); char *alloc_suffix = alloc_text_of_string_literal(suffix_len); strcpy(alloc_suffix, suffix); result->extra_info.ud_lit.suffix = alloc_suffix; result->extra_info.ud_lit.op_sym = find_literal_operator( alloc_suffix, suffix_len - 1, const_cast(pos), type, /*from_cache=*/FALSE, (a_diagnostic_ptr)NULL); /* Rescan the original current token, discarding the current token state (which is already copied into the given cache). */ rescan_cached_tokens(cache.ptr(), /*discard=*/TRUE); /* Restore the start and end of the current token. This ensures that this function doesn't have an observable affect on parsing outside of the cached tokens. This must be done after the rescan as the rescan will wipe out the start and end values. */ end_of_curr_token = save_end_of_curr_token; start_of_curr_token = save_start_of_curr_token; /* Return to the original lexical state. */ pop_lexical_state_stack(); return result; } /* a_token_factory::build_tok_ud_literal */ a_shared_token a_token_factory::build_tok_resolved_type( a_type_ptr type, const a_source_position *pos) /* This function is an implementation detail of build_tok_resolved_type; prefer that function for most use cases and see it for a description of the arguments. */ { a_shared_token result = build_cached_token( tok_decltype_construct, assign_new_token_sequence_number(), pos); result->extra_info_kind = teik_identifier; result->extra_info.locator = cleared_locator; result->extra_info.locator.variant.decltype_type = type; result->extra_info.locator.source_position = *pos; return result; } /* a_token_factory::build_tok_resolved_type */ a_shared_token a_token_factory::build_tok_insert_string() /* This function merely constructs a new shared token that is used as a placeholder "dummy entry" with the extra info kind set to teik_insert_string. This function is an implementation detail of push_string_insert_cache_entry; see that function (and its sister function pop_string_insert_cache_entry) for more information. */ { a_shared_token result = shared_obj( tok_error, null_source_position, null_source_position, NO_TOKEN_SEQUENCE_NUMBER, NO_TOKEN_SEQUENCE_NUMBER); result->extra_info_kind = teik_insert_string; return result; } /* a_token_factory::build_tok_insert_string */ } /* namespace detail */ a_shared_token build_cached_token(a_token_kind kind, a_token_sequence_number sequence_number, const a_source_position *position) /* Construct and return a shared token of the specified token kind, with the specified token sequence number and source position. The token kind must be one for which there is no associated extra information. */ { if (position == NULL) { position = &null_source_position; } /* if */ return shared_obj(kind, *position, sequence_number); } /* build_cached_token */ a_shared_token build_tok_constant(a_constant_ptr cp, const a_source_position *pos, /* Defaulted: */ a_token_kind kind) /* Construct and return a shared token representing the given constant at the given position with the given token kind. */ { return detail::a_token_factory::build_constant(cp, pos, kind); } /* build_tok_constant */ a_shared_token build_tok_body_replacement( a_token_kind token, a_token_sequence_number start_seq, a_token_sequence_number end_seq, a_symbol_ptr symbol, a_boolean semicolon_inserted, const a_source_position *pos) /* Construct and return a shared token representing a template body that has been replaced. The token's starting and ending sequence numbers should span the range from the starting token sequence number of the first token that has been replaced to the ending sequence number of the last token that has been replaced. Symbol is for the member associated with this body replacement. semicolon_inserted should be TRUE if the replacement token is a semicolon that was not originally present in the source; otherwise, FALSE. pos is the position of the first token. */ { return detail::a_token_factory::build_tok_body_replacement( token, start_seq, end_seq, symbol, semicolon_inserted, pos); } /* build_tok_body_replacement */ a_shared_token build_tok_removed_expr(a_token_sequence_number start_seq, const a_source_position *pos, a_token_cache_iterator begin, a_token_cache_iterator end) /* Construct and return a shared token representing an expression that will be replaced. start_seq is the token sequence number of the first token that's been replaced (or if no tokens have been replaced, the token number representing the expected location of the expression). pos is the position of the first token. If the given iterators are non-default constructed, the token iterator range [begin, end) will be non-destructively moved into a cache allocated for the token. It is up to the caller to remove the original moved-from tokens from the cache associated with the iterator pair and place the returned token in the appropriate position. */ { return detail::a_token_factory::build_tok_removed_expr(start_seq, pos, begin, end); } /* build_tok_removed_expr */ a_shared_token build_tok_pragma(a_token_sequence_number seq, const a_source_position *pos) /* Construct and return a new shared token representing a pragma with the given sequence number and source position. */ { return detail::a_token_factory::build_tok_pragma(seq, pos); } /* build_tok_pragma */ a_shared_token build_tok_pp(char *text, a_targ_size_t len, const a_source_position *pos) /* Construct and return a new shared token representing a preprocessor token with the given textual representation (of the given number of characters) and source position. */ { return detail::a_token_factory::build_tok_pp(text, len, pos); } /* build_tok_pp */ a_shared_token build_tok_identifier(a_const_char *str, a_targ_size_t len, const a_source_position *pos) /* Construct and return a new shared token representing an identifier token with the given identifier text (of the given number of characters) and source position. */ { return detail::a_token_factory::build_tok_identifier(str, len, pos); } /* build_tok_identifier */ a_shared_token build_tok_ifc_ref(a_token_kind kind, a_lexical_ifc_index_reference ref, const a_source_position *pos) /* Construct and return a new shared token of the given kind representing an IFC index. ref is the lexical representation of the IFC index. source position is the source position that should be used for the token. */ { return detail::a_token_factory::build_tok_ifc_ref(kind, ref, pos); } /* build_tok_ifc_ref */ a_shared_token build_tok_ud_literal(a_constant_ptr str_constant, a_const_char *suffix, const a_source_position *pos) /* This function is a simplification of the build_tok_ud_literal function below. The major difference is that the spelling constant and value constant are created from the given string constant. Additionally, the user-defined literal's type is derived from the type of the token. See the below function for more information about the parameters. */ { a_constant_ptr spelling_constant = alloc_cached_constant(); copy_constant(str_constant, spelling_constant); return build_tok_ud_literal(str_constant, spelling_constant, suffix, str_constant->type, pos); } /* build_tok_ud_literal */ a_shared_token build_tok_ud_literal(a_constant_ptr value_constant, a_constant_ptr spelling_constant, a_const_char *suffix, a_type_ptr type, const a_source_position *pos) /* Construct and return a new shared token representing a user-defined literal token with the given computed constant value, constant representing the spelling of the literal, null-terminated suffix string, literal type, and source position. For a more detailed explanation of these parameters, see the a_ud_literal_descr type definition in lexical.h. */ { return detail::a_token_factory::build_tok_ud_literal(value_constant, spelling_constant, suffix, type, pos); } /* build_tok_ud_literal */ a_shared_token build_tok_resolved_type(a_type_ptr type, const a_source_position *pos) /* Construct and return a new shared token representing the given resolved type to the cache. The type will be internally preserved via a decltype construct token. pos is the position of the token. */ { return detail::a_token_factory::build_tok_resolved_type(type, pos); } /* build_tok_resolved_type */ void cache_curr_token(a_token_cache *cache) /* Save the current token on the end of the list of tokens saved in *cache. This is used to save tokens for later rescanning. */ { /* If there are any pragmas associated with the current token, create a token cache entry to preserve the pragma information before adding the token cache entry for the current token. */ if (!curr_token_pragmas->is_empty() && !suppress_pragma_processing) { cache->append_token(detail::a_token_factory::copy_curr_pragma_as_token()); } /* if */ cache->append_token(detail::a_token_factory::copy_curr_token()); } /* cache_curr_token */ void cache_curr_token_fresh(a_token_cache *cache) /* Cache the current token with a fresh token sequence number to the given token cache. */ { a_token_sequence_number new_tsn = assign_new_token_sequence_number(); Value_saver curr_tsn(&curr_token_sequence_number, new_tsn); Value_saver curr_last_tsn(&last_token_sequence_number_of_token, new_tsn); cache_curr_token(cache); } /* cache_curr_token_fresh */ static a_boolean is_template_reference(a_symbol_header_ptr sym_hdr) /* The current token is "<" and the previous token is an identifier. Look up the identifier as a class name. If it is a class template name, return TRUE, otherwise return FALSE. This routine is used by cache_token_stream and flush_tokens when caching (or flushing) until a matching token is found. Template references must be recognized so that matching tokens within the template reference can be ignored. Template references are more difficult to recognize that other matching token pairs (such as () or []) because not all less-than signs begin template references. */ { a_boolean result = FALSE; a_symbol_ptr sym; a_symbol_locator locator; if (!fetch_pp_tokens && sym_hdr != NULL) { /* This test can only be done when not fetching preprocessing tokens. It is not possible to do the ID lookup in fetch_pp_tokens mode. In some error cases sym_hdr can be NULL; don't attempt to do a lookup in such cases. Create a locator that describes the identifier to be looked up. The caching process only deals with identifiers that have not been coalesced yet, so we don't need to handle qualified name cases. */ locator = cleared_locator; locator.symbol_header = sym_hdr; sym = normal_id_lookup(&locator, IDL_DO_NOT_ADD_TO_NONREAL_CLASS); if (sym != NULL && is_class_template_or_injected_template_symbol(sym)) { result = TRUE; } /* if */ } /* if */ return result; } /* is_template_reference */ static void coalesce_and_scan_concept_args(an_identifier_options_set gid_opts) /* Call is_generalized_identifier_start with the options specified in gid_opts, and if it returns TRUE and the current symbol is a concept template followed by a '<', scan the concept argument list. */ { if (is_generalized_identifier_start(gid_opts) && locator_for_curr_id.has_been_coalesced && locator_for_curr_id.specific_symbol != NULL && symbol_is(locator_for_curr_id.specific_symbol, sk_concept_template)) { (void)get_token(); if (curr_token == tok_lt) { a_symbol_ptr sym = locator_for_curr_id.specific_symbol; a_boolean err = FALSE; a_template_arg_ptr tap; (void)get_token(); tap = scan_concept_arg_list(sym, /*type_constraint=*/FALSE, &err); free_template_arg_list(tap); (void)required_token(tok_gt, ec_exp_gt); } /* if */ } /* if */ } /* coalesce_and_scan_concept_args */ static void get_token_and_coalesce_if_needed(a_boolean coalesce_ids, a_boolean is_expr) /* Get a token and, if coalesce_ids is TRUE, call coalesce_and_scan_concept_args to coalesce any identifier in case it is the beginning of something like a qualified name. */ { (void)get_token(); if (coalesce_ids) { an_identifier_options_set gid_opts = is_expr ? GID_IS_EXPR_CONTEXT : GID_IS_TYPENAME; coalesce_and_scan_concept_args(gid_opts | GID_TEMPLATE_ARGS_OPTIONAL); } /* if */ } /* get_token_and_coalesce_if_needed */ void copy_tokens_from_cache(const a_token_cache *src_cache, a_token_sequence_number first_tsn, a_token_sequence_number last_tsn, a_boolean include_last_token, a_token_cache_ptr dest_cache) /* Copy the tokens from src_cache to dest_cache that are in the range of token sequence numbers from first_tsn to last_tsn. last_tsn is actually the first token sequence number to not be included in the cache. If include_last_token is TRUE, last_tsn + 1 is the first token sequence number to not be included in the cache. */ { a_const_token_cache_iterator before_start_of_copy_it; a_const_token_cache_iterator end_of_copy_it; if (!include_last_token && first_tsn == last_tsn && last_tsn != NO_TOKEN_SEQUENCE_NUMBER) { /* There's nothing to copy. */ } else { a_token_sequence_number last_included_tsn = last_tsn; if (last_tsn != NO_TOKEN_SEQUENCE_NUMBER && !include_last_token) { /* The last token should not be included; find_first_and_last returns iterators representing an inclusive range so the last token sequence number should be reduced by one. */ --last_included_tsn; } /* if */ src_cache->find_first_and_last(first_tsn, last_included_tsn, &before_start_of_copy_it, &end_of_copy_it); /* Copy the specified range of tokens to the destination cache. */ a_const_token_cache_iterator cpy_it = before_start_of_copy_it; a_const_token_cache_iterator cpy_it_end = end_of_copy_it; if (end_of_copy_it != src_cache->end()) { ++cpy_it_end; } /* if */ if (before_start_of_copy_it != cpy_it_end) { ++cpy_it; } /* if */ if (cpy_it != cpy_it_end) { sizeof_t num_elems = distance(cpy_it, cpy_it_end); dest_cache->reserve(dest_cache->length() + num_elems); for (; cpy_it != cpy_it_end; ++cpy_it) { dest_cache->append_token(*cpy_it); } /* for */ } /* if */ } /* if */ } /* copy_tokens_from_cache */ #if !MICROSOFT_EXTENSIONS_ALLOWED static #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ void replace_curr_token(a_token_kind new_token) /* Replace the current token with new_token. */ { curr_token = new_token; /* If tokens are being cached as they are fetched, and the token being updated is at the end of the cache, update the copy in the cache. */ if (curr_lexical_state_stack_entry->cache_tokens != 0) { a_token_cache_ptr curr_cache = curr_lexical_state_cache(); a_token_cache_iterator last_tok_it = curr_cache->get_last_token_iter(); const a_shared_token &last_tok = *last_tok_it; if (last_tok->get_starting_seq_number() == curr_token_sequence_number) { a_cached_token replacement_tok = *last_tok; replacement_tok.set_kind(new_token); *last_tok_it = move_from(&replacement_tok); } /* if */ } /* if */ } /* replace_curr_token */ #if MICROSOFT_EXTENSIONS_ALLOWED a_partial_class_body_ptr cache_partial_class_body(a_type_ptr class_type) /* The current token is the beginning of a partial class body. Cache the partial body's tokens, and return a pointer to an a_partial_class_body representing this partial body. When this function returns, the current token should be the final '}' of the partial class body (or EOF in the case of some syntax errors). The token caching strategy used here is derived from cache_template_declaration. */ { a_class_type_supplement_ptr ctsp = class_type_supp(class_type); a_token_cache_ptr p_cache; a_token_set_array stop_tokens; a_partial_class_body_ptr partial_body = alloc_partial_class_body(); a_partial_class_body_ptr *new_body_place; a_boolean empty_partial_base_list = TRUE; /* Assume. */ /* Set new_body_place to the end of the list. */ for (new_body_place = &ctsp->partial_class_bodies; *new_body_place != NULL; new_body_place = &(*new_body_place)->next) { a_partial_class_body_ptr iter = *new_body_place; if (iter->base_cache != NULL) { /* At least one of the partial bodies has a base list. */ empty_partial_base_list = FALSE; } /* if */ } /* for */ /* Add partial_body to the end of the list. */ *new_body_place = partial_body; /* Set the start position for this partial body. */ partial_body->start_position = pos_curr_token; if (curr_token == tok_colon) { /* This partial body has a base list. Cache the base list. */ a_token_cache_ptr base_cache = new_fe(); clear_token_set_array(stop_tokens); incr_token_set_array_element(stop_tokens, tok_lbrace); incr_token_set_array_element(stop_tokens, tok_semicolon); partial_body->base_cache = base_cache; if (!empty_partial_base_list) { /* If a base list exists on a previously cached partial class body, all subsequent bodies should start with a comma, not a colon. */ replace_curr_token(tok_comma); } /* if */ cache_token_stream(base_cache, stop_tokens); } /* if */ /* Cache tokens comprising the partial class body (between the '{' and '}'). */ partial_body->body_cache = new_fe(); p_cache = partial_body->body_cache; clear_token_set_array(stop_tokens); incr_token_set_array_element(stop_tokens, tok_rbrace); /* Consume (and do not cache) the tok_lbrace. */ (void)get_token(); /* Cache the partial class body tokens. */ cache_token_stream(p_cache, stop_tokens); /* If all went well, we will be on an tok_rbrace. Otherwise, tok_end_of_source. */ partial_body->end_position = pos_curr_token; if (curr_token != tok_end_of_source) { /* Consume (and do not cache) the final tok_rbrace. */ (void)get_token(); } /* if */ return partial_body; } /* cache_partial_class_body */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ static void replace_right_shift_by_two_closing_angle_brackets(void) /* The current token must be a ">>": Replace it with two ">" tokens. */ { a_tiny_scanning_token_cache cache; replace_curr_token(tok_gt); cache_curr_token(cache.ptr()); /* Give the second ">" token a new token sequence number. When the numbers were assigned, a slot was reserved so that this number will be known to be unique. */ curr_token_sequence_number++; last_token_sequence_number_of_token = curr_token_sequence_number; rescan_cached_tokens(cache.ptr()); } /* replace_right_shift_by_two_closing_angle_brackets */ void cache_std_attribute(a_token_cache *cache, a_boolean add_tokens_to_cache) /* Cache past a C++11 standard attribute and, if add_tokens_to_cache is TRUE, and the tokens to cache. A standard attribute has the form: [[ ... ]] The contents of the attribute can contain (...), [...], {...}, and [:...:], including nested versions of each of those. It is important to avoid caching past the end of the attribute in error cases. In valid programs all of the delimiters will be balanced, so for better error recovery, parentheses and braces are mostly ignored. Only zero-level brackets (those not inside parentheses of braces) are tracked. The current token is the first bracket of the start of the attribute. Upon return, the current token is the second bracket of the end of the attribute, which has not yet been cached. */ { int paren_count = 0; int bracket_count = 0; int brace_count = 0; int splice_count = 0; a_token_kind prev_token = tok_error; /* Cache the opening bracket. */ if (add_tokens_to_cache) cache_curr_token(cache); /* Get the second bracket token. */ (void)get_token(); check_assertion(curr_token == tok_lbracket); if (add_tokens_to_cache) cache_curr_token(cache); /* Get the first token of the attribute. */ (void)get_token(); /* Loop until we find "]]", even if that is within parentheses or braces. */ while (!(bracket_count == 0 && prev_token == tok_rbracket && curr_token == tok_rbracket)) { if (curr_token == tok_end_of_source) break; prev_token = curr_token; /* Count paired tokens. */ switch (curr_token) { case tok_lparen: paren_count++; break; case tok_rparen: if (paren_count > 0) paren_count--; break; case tok_lbracket: /* Only zero-level brackets are counted. */ if (paren_count == 0 && brace_count == 0 && splice_count == 0) { bracket_count++; } /* if */ break; case tok_rbracket: if (paren_count == 0 && brace_count == 0 && splice_count == 0) { if (bracket_count > 0) bracket_count--; } /* if */ break; case tok_lbrace: brace_count++; break; case tok_rbrace: if (brace_count > 0) brace_count--; break; case tok_lsplice: splice_count++; break; case tok_rsplice: if (splice_count > 0) splice_count--; break; default: break; } /* switch */ /* None of the conditions was satisfied, so keep going. */ if (add_tokens_to_cache) cache_curr_token(cache); (void)get_token(); } /* while */ } /* cache_std_attribute */ a_boolean cache_token_stream_until_matching_token( a_token_cache *cache, a_cts_flag_set options) /* Given curr_token of '<', '(', '[', or '{', copy tokens into the token cache specified by cache up to but not including the corresponding closing token, '>', ')', ']', or '}', respectively. Return immediately if end of source is reached. (This routine is similar to flush_until_matching_token, but instead of throwing tokens away it adds them to the specified token cache.) Normally returns FALSE. Returns TRUE if it returns without finding the desired token (i.e., on end-of-source or a zero level right brace). options specifies a set of flags that enable certain modes, such as the coalescing of identifiers. See the CTS flag definitions in lexical.h for more information. The CTS_COALESCE_IDS flag is used if identifiers found in the token stream should be coalesced. This should be done when the stop token set includes tokens that can appear in an expression, which means that the caching process must be able to determine whether a "<" starts a template argument list or is just a less-than sign. coalesce_ids must be TRUE if curr_token is tok_lt. When coalesce_ids is FALSE and cache is not NULL, any fetched tokens will be added to cache. */ { a_token_kind closing_token = tok_error; int paren_count = 0, bracket_count = 0, brace_count = 0, splice_count = 0; a_boolean done = FALSE; a_boolean err = FALSE; a_boolean coalesce_ids = (options & CTS_COALESCE_IDS) != 0; a_boolean is_expr = (options & CTS_IS_EXPRESSION) != 0; a_boolean add_tokens_to_cache = !coalesce_ids && cache != NULL; a_boolean stop_on_statement_end = (options & CTS_STOP_ON_STATEMENT_END) != 0; db_enter(4, "cache_token_stream_until_matching_token"); if (curr_token == tok_lt) { /* We rely on coalescing of template-ids in the logic below: because we won't see any nested <...> pairs, we don't have to keep a count of angle brackets as we do with parentheses, brackets, and braces. */ check_assertion(coalesce_ids); } /* if */ /* We don't need to coalesce ids if we are not handling a <...> sequence. */ if (curr_token != tok_lt) coalesce_ids = FALSE; /* Determine the closing token that corresponds to curr_token. */ switch (curr_token) { case tok_lt: closing_token = tok_gt; break; case tok_lparen: closing_token = tok_rparen; break; case tok_lbracket: closing_token = tok_rbracket; break; case tok_lbrace: closing_token = tok_rbrace; break; case tok_lsplice: closing_token = tok_rsplice; break; default: unexpected_condition_str2("cache_token_stream_until_matching_token:", "bad token"); } /* switch */ /* Cache the current token, and advance to its successor. */ if (add_tokens_to_cache) cache_curr_token(cache); get_token_and_coalesce_if_needed(coalesce_ids, is_expr); /* Keep looping through successive tokens until the corresponding closing token is found at level zero (i.e., not within a nesting of parens, brackets, or braces). */ if (curr_token == tok_shift_right && closing_token == tok_gt && right_shift_can_be_angle_brackets) { /* A right shift token may need to be treated as two closing angle brackets. */ replace_right_shift_by_two_closing_angle_brackets(); } /* if */ while (!done && (curr_token != closing_token || paren_count != 0 || bracket_count != 0 || brace_count != 0 || splice_count != 0)) { /* Never scan past a zero level right brace. This prevents caching past the end of a class or function in the event of a mismatched paren or bracket. */ if (curr_token == tok_rbrace && brace_count == 0) { err = TRUE; break; } /* if */ if (std_attribute_tokens_next()) { /* The start of a standard attribute. */ cache_std_attribute(cache, add_tokens_to_cache); #if MICROSOFT_EXTENSIONS_ALLOWED } else if (curr_token == tok_microsoft_Lprefix || curr_token == tok_microsoft_lprefix || curr_token == tok_microsoft_Uprefix || curr_token == tok_microsoft_uprefix) { /* Replace __xPREFIX("...") with the correctly-prefixed string literal (__xPREFIX(__FUNCTION__) and the like will be added to the cache for processing during instantiation). */ (void)set_curr_token_to_microsoft_xprefix_operator_string(); #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if (closing_token == tok_rbrace) { /*lint !e539*/ /* When looking for a right brace, don't consider any other delimiters. Braces can't be nested inside parens, brackets, etc. */ switch (curr_token) { case tok_lbrace: brace_count++; break; case tok_rbrace: if (brace_count > 0) brace_count--; break; default:; } /* switch */ } else if (stop_on_statement_end && brace_count == 0 && (curr_token == tok_semicolon || curr_token == tok_rbrace)) { /* We are in a mode where we want to limit caching in error cases. Stop when we hit a semicolon or unmatched right brace. */ err = TRUE; break; } else { /* Count paired tokens within the skip. */ switch (curr_token) { case tok_lparen: paren_count++; break; case tok_rparen: if (paren_count > 0) paren_count--; break; case tok_lbracket: bracket_count++; break; case tok_rbracket: if (bracket_count > 0) bracket_count--; break; case tok_lbrace: brace_count++; break; case tok_rbrace: if (brace_count > 0) brace_count--; break; case tok_lsplice: splice_count++; break; case tok_rsplice: if (splice_count > 0) splice_count--; break; default:; } /* switch */ } /* if */ /* Always stop the flush on end of source. */ if (curr_token == tok_end_of_source) break; /* None of the conditions was satisfied, so keep going. */ if (add_tokens_to_cache) cache_curr_token(cache); get_token_and_coalesce_if_needed(coalesce_ids, is_expr); if (curr_token == tok_shift_right && closing_token == tok_gt && right_shift_can_be_angle_brackets) { /* A right shift token may need to be treated as two closing angle brackets. */ if (paren_count == 0 && bracket_count == 0 && brace_count == 0 && splice_count == 0) { replace_right_shift_by_two_closing_angle_brackets(); } /* if */ } /* if */ } /* while */ db_exit(); return err; } /* cache_token_stream_until_matching_token */ static a_boolean curr_id_is_concept_name(void) /* The current token is an identifier. Return TRUE if it is a concept name. */ { a_symbol_ptr sym = locator_for_curr_id.specific_symbol; a_boolean result; if (sym == NULL) { sym = normal_id_lookup(&locator_for_curr_id, IDL_TENTATIVE_TYPE_LOOKUP); } /* if */ if (sym == NULL) { result = FALSE; } else { sym = fundamental_symbol_of(sym); result = symbol_is(sym, sk_concept_template); } /* if */ return result; } /* curr_id_is_concept_name */ void cache_token_stream_full(a_token_cache_ptr cache, a_token_set_array stop_tokens, a_cts_flag_set options) /* Copy the current token and succeeding tokens into the token cache specified by cache up to but not including the first token that matches a member of the stop tokens array. Return immediately if end of source is reached. (This routine is similar to flush_tokens, but instead of throwing tokens away it adds them to the specified token cache.) If cache is NULL, the tokens are not added to the cache. This may be the case when the background caching mechanism is being used. options specifies a set of flags that enable certain modes, such as the coalescing of identifiers. See the CTS flag definitions in lexical.h for more information. The CTS_COALESCE_IDS flag is used if identifiers found in the token stream should be coalesced. This should be done when the stop token set includes tokens that can appear in an expression, which means that the caching process must be able to determine whether a "<" starts a template argument list or is just a less-than sign. */ { a_token_sequence_number first_tsn = curr_token_sequence_number; a_token_sequence_number last_tsn; a_boolean save_caching_tokens = caching_tokens; a_boolean prev_token_precedes_angle_bracket_list = FALSE; a_boolean prev_token_was_template = FALSE; a_boolean add_tokens_to_cache; a_boolean coalesce_ids; a_boolean is_expr; db_enter(4, "cache_token_stream_full"); /* Set a flag that indicates that the tokens being scanned are to be cached. */ caching_tokens = TRUE; coalesce_ids = (options & CTS_COALESCE_IDS) != 0; is_expr = (options & CTS_IS_EXPRESSION) != 0; add_tokens_to_cache = !coalesce_ids && cache != NULL; /* Start caching of tokens when we are coalescing ids. This is needed because when coalescing ids not all tokens are fetched directly by this routine. A cache is constructed behind the scenes, and the tokens are extracted from that cache when we reach the end of the tokens to be cached. */ if (coalesce_ids) { begin_caching_fetched_tokens(/*include_curr_token=*/TRUE); /* Attempt to coalesce this token in case it begins an identifier. */ an_identifier_options_set gid_opts = GID_TEMPLATE_ARGS_OPTIONAL | GID_PERMIT_UNKNOWN_QUALIFIER; if (is_expr) gid_opts |= GID_IS_EXPR_CONTEXT; coalesce_and_scan_concept_args(gid_opts); } /* if */ /* Loop through the tokens, beginning with the current token and stopping when a token in the stop token array is found. Whenever a '(', '[', or '{' is encountered, ignore the stop token array until the corresponding ')', ']', or '}' is reached. */ while (stop_tokens[(int)curr_token] == 0) { if (coalesce_ids && (((stop_tokens[(int)tok_gt] > 0 || prev_token_was_template) && (curr_token == tok_dynamic_cast || curr_token == tok_static_cast || curr_token == tok_reinterpret_cast || curr_token == tok_const_cast || prev_token_was_template)) || (curr_token == tok_identifier && curr_id_is_concept_name()))) { /* We're looking for the end of a template argument list and are about to scan over a new-style cast, a concept-id, or we have a "template" keyword that indicates the thing following the identifier is a template argument list. We need to look for the ">" that closes the template argument list or new-style cast type. */ prev_token_precedes_angle_bracket_list = TRUE; prev_token_was_template = FALSE; } else if (curr_token == tok_template) { prev_token_was_template = TRUE; } else { if (std_attribute_tokens_next()) { /* The start of a standard attribute. */ cache_std_attribute(cache, add_tokens_to_cache); } else if (curr_token == tok_lparen || curr_token == tok_lbracket || curr_token == tok_lbrace || curr_token == tok_lsplice || (curr_token == tok_lt && prev_token_precedes_angle_bracket_list)) { a_boolean err; err = cache_token_stream_until_matching_token(cache, options); if (err) break; } /* if */ prev_token_precedes_angle_bracket_list = FALSE; prev_token_was_template = FALSE; } /* if */ /* Stop immediately when end of source is reached. */ if (curr_token == tok_end_of_source) break; /* Add the current token to the cache and advance to its successor. */ if (add_tokens_to_cache) cache_curr_token(cache); get_token_and_coalesce_if_needed(coalesce_ids, is_expr); } /* while */ /* Leave error_position associated with what is now curr_token. */ set_err_pos_to_curr_token(); if (coalesce_ids && cache != NULL) { /* Make a copy of the specified range of tokens from the source cache. */ last_tsn = curr_token_sequence_number; /* Get the tokens from the cache that has been accumulated. */ copy_tokens_from_cache(curr_lexical_state_cache(), first_tsn, last_tsn, curr_token == tok_end_of_source, cache); } /* if */ /* Clear the flag that indicates that the tokens being scanned are to be cached. */ caching_tokens = save_caching_tokens; /* End the caching of tokens when coalescing ids. */ if (coalesce_ids) end_caching_fetched_tokens(); db_exit(); } /* cache_token_stream_full */ void cache_token_stream(a_token_cache *cache, a_token_set_array stop_tokens) /* Interface to cache_token_stream_full that does not cause identifiers to be coalesced. */ { cache_token_stream_full(cache, stop_tokens, CTS_IS_EXPRESSION); } /* cache_token_stream */ void cache_token_stream_coalesce_identifiers(a_token_cache_ptr cache, a_token_set_array stop_tokens) /* Interface to cache_token_stream_full that causes identifiers to be coalesced. */ { cache_token_stream_full(cache, stop_tokens, CTS_COALESCE_IDS | CTS_IS_EXPRESSION); } /* cache_token_stream_coalesce_identifiers */ a_token_kind get_token_to_be_cached(void) /* Interface to get_token that sets the caching_tokens flag. */ { caching_tokens = TRUE; (void)get_token(); caching_tokens = FALSE; return curr_token; } /* get_token_to_be_cached */ static INLINE void push_current_token_to_rescan_stack() /* This function is called to copy the current token state directly onto the cached_token_rescan_stack in an efficient manner. This is typically used to preserve the current token before performing one of several different token cache rescan strategies. */ { a_shared_token pragma_tok; /* Create a copy of the current token state so that it is not lost. The copied token(s) will be pushed onto the current token rescan stack. */ if (!curr_token_pragmas->is_empty() && !suppress_pragma_processing) { /* If there are any pragmas associated with the current token, create a token cache entry to preserve the pragma information before adding the token cache entry for the current token. */ pragma_tok = detail::a_token_factory::copy_curr_pragma_as_token(); } /* if */ cached_token_rescan_stack->emplace_back( detail::a_token_factory::copy_curr_token()); if (pragma_tok.ptr() != NULL) { /* If there is currently a pragma applied to the token, that is pushed last (so that it's processed first). */ cached_token_rescan_stack->emplace_back(move_from(&pragma_tok)); } /* if */ if (!suppress_pragma_processing) { /* Clear the current token's pragmas so that the next get_token call can move off of this token and into the token cache without triggering pragma processing. */ curr_token_pragmas->clear(); } /* if */ } /* push_current_token_to_rescan_stack */ void rescan_cached_tokens(a_token_cache *cache, /* Defaulted: */ a_boolean discard_curr_token) /* Put the tokens saved in *cache onto the rescan list so that they will be re-fetched by get_token. On return, the current token is the first token of the cache. The token that was the current token on entry is placed at the end of the rescan list so that it will be fetched again after the rescanned tokens have been gotten. discard_curr_token is TRUE if the current token should be discarded, FALSE if the token should be retained. If there are no tokens in the cache, nothing is done (except that the current token may be discarded). */ { db_enter(4, "rescan_cached_tokens"); #if DEBUG /* This cache was marked as reusable but is now being destructively rescanned. Update the count of reusable cached tokens. */ if (cache->is_reusable) { /* Reset the flag so that any tokens added to the cache (as is done later in this routine) won't be considered reusable. */ cache->is_reusable = FALSE; } /* if */ #endif /* DEBUG */ if (!cache->is_empty()) { /* Add the current token to the cache, so that it is not lost. */ if (!discard_curr_token) { push_current_token_to_rescan_stack(); } /* if */ /* Push the tokens in the cache onto the top of the rescan stack. */ while (!cache->is_empty()) { cached_token_rescan_stack->emplace_back( move_from(&cache->tokens.back_elem())); cache->tokens.pop_back(); } /* while */ /* Indicate that the special case code at the beginning of get_token is needed to check for cached token rescanning. */ any_initial_get_token_tests_needed = TRUE; /* Fetch the first cached token. */ (void)get_token(); } else { /* The caller expects the current token to be discarded. Skip over it now. */ if (discard_curr_token) (void)get_token(); } /* if */ db_exit(); } /* rescan_cached_tokens */ void rescan_copy_of_cache(a_token_cache *cache) /* This routine copies the tokens from the cache provided by the caller and pushes them to the cached_token_rescan_stack. If the source cache is terminated by a tok_end_of_source, it is excluded from the tokens added to the cached_token_rescan_stack. This routine is used to allow a series of tokens to be cached into a reusable cache and then rescanned as if part of the original source with no need to worry about detection of the tok_end_of_source later. */ { if (!cache->is_empty()) { /* Add the current token to the cache, so that it is not lost. */ push_current_token_to_rescan_stack(); a_reverse_token_cache_iterator it = cache->rbegin(); a_reverse_token_cache_iterator it_end = cache->rend(); /* Do not include the end of source token (if any). */ if ((*it)->is(tok_end_of_source)) { ++it; } /* if */ /* Push the tokens in the cache onto the top of the rescan stack. */ for (; it != it_end; ++it) { cached_token_rescan_stack->push_back(*it); } /* for */ /* Indicate that the special case code at the beginning of get_token is needed to check for cached token rescanning. */ any_initial_get_token_tests_needed = TRUE; /* Fetch the first cached token. */ (void)get_token(); } /* if */ } /* rescan_copy_of_cache */ void rescan_reusable_cache(const a_reusable_token_cache &cache) /* This routine is similar to rescan_cached_tokens except that the token cache provided by the caller is not destroyed while it is scanned. This is used by template processing for instantiation processing. The token cache passed by the caller is put on the top of a stack of reusable caches being scanned. The cached token rescan list pointer is saved in the entry for the reusable cache so that it can be restored when the cache has been exhausted. The current token is cached so that it will be fetched again after the reusable tokens have been rescanned. */ { a_reusable_cache_entry_ptr rcep; db_enter(4, "rescan_reusable_cache"); /* Make sure that this cache is reusable. */ check_assertion(cache.ptr() == NULL || cache->is_reusable); if (!cache.is_empty()) { push_current_token_to_rescan_stack(); /* Create a new reusable cache entry for the new cache and put on the front of the list of active reusable caches. */ rcep = alloc_reusable_cache_entry(cache); rcep->next = reusable_cache_stack; reusable_cache_stack = rcep; /* Save and clear the current value of the regular token rescan list. */ rcep->previous_token_rescan_stack = cached_token_rescan_stack; cached_token_rescan_stack = new_fe( cached_token_rescan_init_capacity); /* Indicate that the special case code at the beginning of get_token is needed to cached token rescanning. */ any_initial_get_token_tests_needed = TRUE; /* Fetch the first cached token. */ (void)get_token(); } /* if */ db_exit(); } /* rescan_reusable_cache */ a_reusable_token_cache get_token_cache_being_scanned() /* Return the reusable token cache that is currently being rescanned. Return a default-constructed reusable token cache if there is no reusable cache being scanned. */ { a_reusable_token_cache result; if (reusable_cache_stack != NULL) { result = reusable_cache_stack->token_cache; } /* if */ return result; } /* get_token_cache_being_scanned */ void split_token_cache(a_token_cache *cache1, a_token_cache *cache2, a_token_sequence_number split_location, a_boolean include_prev_token, ARG_UNUSED a_boolean okay_if_not_found) /* Split cache1 into two pieces. cache1 will contain all the tokens up to the one that precedes the token number specified by split location. cache2 will contain all the tokens that follow. If incldue_prev_token is TRUE, we should include the token before the split location in the tokens that are moved to cache2. okay_if_not_found is TRUE if it is okay if the split location is not found. This suppresses an internal error. */ { a_token_cache_iterator before_first_moved_tok_it; a_token_cache_iterator prev_before_first_moved_tok_it; check_assertion_str2(cache1->is_reusable && cache2->is_reusable, "split_token_cache:", "cache not reusable"); a_token_cache_iterator it = cache1->begin(); a_token_cache_iterator it_end = cache1->end(); for (; it != it_end; ++it) { const a_shared_token &tok = *it; if (tok->get_starting_seq_number() == split_location) { break; } /* if */ if (!tok->is_pragma()) { /* The token sequence looks something like: pragma-n0 token-n1 pragma-n2 token-n3 token-n4 where token-n3 is the split location. We also want to move any pragma that precedes token-n3 to cache 2. When we break out of the loop first_moved_tok_it will point to pragma-n2 and before_first_moved_tok_it will point to token-n1. Save the previous values of these fields. If include_prev_token is TRUE we want to split the cache before pragma-n0. */ prev_before_first_moved_tok_it = before_first_moved_tok_it; before_first_moved_tok_it = it; } /* if */ } /* for */ if (it == it_end && okay_if_not_found) { goto exit; } /* if */ check_assertion_str2(it != it_end, "split_token_cache:", "specified token not found"); if (include_prev_token) { /* The token before the split location should be the first to be put in the new cache. */ before_first_moved_tok_it = prev_before_first_moved_tok_it; } /* if */ /* Move the tokens from cache1 to cache2 (before_first_moved_tok_it, it_end). */ cache2->move_copy_tokens(before_first_moved_tok_it + 1, it_end); cache1->remove_token_range(before_first_moved_tok_it + 1, it_end); /* Add a new terminator to the end of the original. */ terminate_token_cache(cache1); exit:; } /* split_token_cache */ static INLINE void get_token_from_cached_token_rescan_stack( a_boolean *no_tokens_on_stack) /* Remove the token at the top of the cached_token_rescan_stack and establish it as the current token. This routine and get_token_from_reusable_cache_stack are very similar. If a change is made to one routine the other should be checked to see if it needs an equivalent change. If the rescan list contains only pragma entries, it is possible for there to be no actual token. *no_tokens_on_stack is set to TRUE in that case, FALSE otherwise. */ { a_shared_token token; *no_tokens_on_stack = FALSE; for (;;) { /* Pop the top of the stack. */ token = move_from(&cached_token_rescan_stack->back_elem()); cached_token_rescan_stack->pop_back(); /* If it is a special entry indicating a pragma, process it and take another entry. Otherwise, exit the loop. */ if (!token->is_pragma()) { break; } /* if */ if (!curr_lexical_state_stack_entry->flushing_tokens) { /* Set the current token pragma list to point to the pragmas associated with the cached token. */ check_assertion_str(!suppress_pragma_processing, "get_token_from...: pragma found in suppress_pragma mode"); for (a_shared_pending_pragma &spp : *token->get_pragma_list()) { curr_token_pragmas->push_back(spp); } /* for */ } /* if */ if (cached_token_rescan_stack->is_empty()) { *no_tokens_on_stack = TRUE; curr_token = tok_error; goto done; } /* if */ } /* for */ /* When fetch_pp_tokens is FALSE, make sure that the token being retrieved is not a cached pp-token. If it is, flush any cached pp-tokens and issue an error at the point at which we resume scanning normal tokens. It should not be possible to run out of tokens while flushing the pp-tokens. */ if (token->is_pp_token() && !fetch_pp_tokens) { while (!cached_token_rescan_stack->is_empty() && token->is_pp_token()) { token = move_from(&cached_token_rescan_stack->back_elem()); cached_token_rescan_stack->pop_back(); } /* while */ check_assertion_str2(!token->is_pp_token(), "get_token_from_reusable_cache_stack:", "pp-token flush consumed all tokens"); pos_error(ec_end_of_flush, token->get_source_position()); } /* if */ /* Entry is for a token (normal case). */ curr_token = token->get_kind(); pos_curr_token = *token->get_source_position(); #if EXTRA_SOURCE_POSITIONS_IN_IL end_pos_curr_token = *token->get_ending_source_position(); #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ error_position = pos_curr_token; curr_token_sequence_number = token->get_starting_seq_number(); last_token_sequence_number_of_token = token->get_ending_seq_number(); start_of_curr_token = end_of_curr_token = NULL; len_of_curr_token = 0; if (token->is_basic()) { /* This is the most common case, there's nothing special about this token; skip further processing. */ } else if (token->is_pp_token()) { /* This is a cached pp-token. Restore the starting and ending token positions. */ a_pp_token_descr_ptr pp_descr = token->get_pp_token_descr(); start_of_curr_token = pp_descr->token_start; end_of_curr_token = pp_descr->token_end; } else if (token->is_identifier()) { /* For an identifier, restore the locator. */ locator_for_curr_id = token->get_locator(); #if BUILTIN_FUNCTIONS_ENABLED if (locator_for_curr_id.symbol_header != NULL && builtin_needs_to_be_loaded(locator_for_curr_id.symbol_header)) { /* If this cached identifier is for a builtin function that has not yet been loaded (because loading is disabled during caching), load it now. */ (void)load_matching_builtin_function(locator_for_curr_id.symbol_header); } /* if */ #endif /* BUILTIN_FUNCTIONS_ENABLED */ } else if (token->is_asm_string()) { /* For a Microsoft asm token, restore the asm string pointer. */ curr_token_asm_string = token->get_asm_string(); } else if (token->is_constant()) { /* For a literal constant, restore const_for_curr_token. */ copy_constant(token->get_constant(), &const_for_curr_token); } else if (token->is_ud_literal()) { /* Restore const_for_curr_token and const_with_curr_tok_spelling. */ a_ud_literal_descr *ud_descr = token->get_ud_literal_descr(); copy_constant(ud_descr->value_con, &const_for_curr_token); copy_constant(ud_descr->spelling_con, &const_with_curr_tok_spelling); /* Unlike tokens in reusable caches, tokens in cached_token_rescan_stack are intended to be evaluated in the context in which they initially appeared. As a result, the code below differs from the similar code in get_token_from_reusable_cache in that the result of the original lookup for the literal operator symbol is simply reused rather than repeating the lookup in the current context, which in some cases may be (temporarily) different and thus yield a different result. We still set locator_for_curr_id as if the lookup had occurred, however. */ ud_lit_op_sym_for_curr_token = ud_descr->op_sym; make_literal_opname_locator(ud_descr->suffix, strlen(ud_descr->suffix), &locator_for_curr_id, &pos_curr_token); ud_lit_type_for_curr_token = ud_descr->type; } else if (token->is_unresolved_ud_literal()) { /* Restore const_for_curr_token and const_with_curr_tok_spelling. */ an_unresolved_ud_literal_descr *ud_descr = token->get_unresolved_ud_literal_descr(); copy_constant(ud_descr->value_con, &const_for_curr_token); copy_constant(ud_descr->spelling_con, &const_with_curr_tok_spelling); locator_for_curr_id = ud_descr->curr_id_locator; } else if (token->is_ifc_reference()) { ifc_index_for_curr_token = token->get_ifc_index(); } /* if */ done: if (cached_token_rescan_stack->is_empty()) { recalc_any_initial_get_token_tests_needed(); } /* if */ } /* get_token_from_cached_token_rescan_stack */ void update_reusable_cache_rescan_location(a_token_cache_ptr cache, a_token_sequence_number tsn) /* Update the current reusable token cache information to begin rescanning tokens from the token in the given cache starting with the given token sequence number. */ { auto get_tsn = [cache](size_t idx) -> a_token_sequence_number { return (*cache)[idx]->get_starting_seq_number(); }; ptrdiff_t first_idx = bin_search(cache->length(), tsn, get_tsn); if (first_idx != -1) { a_token_cache_iterator it(cache, (int)first_idx); /* Discard any tokens on the non-reusable rescan list. */ cached_token_rescan_stack->clear(); reusable_cache_stack->next_token = it; (void)get_token(); } else { expect_error_str("missing token when attempting to update scan position"); } /* if */ } /* update_reusable_cache_rescan_location */ a_boolean skip_to_token_sequence_number(a_token_cache_ptr cache, a_token_sequence_number tsn) /* This is an interface to update_reusable_cache_rescan_location that verifies that the current reusable cache is the same as "cache", and if so, then calls update_reusable_cache_rescan_location to continue scanning at that location. The cache can be different in some cases, such as member functions of local classes of function templates. This is also to prevent issues when an error causes the current reusable cache to differ from the typically-expected cache. Return TRUE if the skip was performed. */ { a_boolean result = FALSE; a_reusable_token_cache cache_being_scanned = get_token_cache_being_scanned(); if (cache_being_scanned.ptr() == cache) { update_reusable_cache_rescan_location(cache, tsn); result = TRUE; } /* if */ return result; } /* skip_to_token_sequence_number */ static INLINE void get_token_from_reusable_cache_stack(void) /* Fetch a token from the current entry on the reusable cache stack and establish it as the current token. This routine and get_token_from_cached_token_rescan_stack are very similar. If a change is made to one routine the other should be checked to see if it needs an equivalent change. */ { a_token_cache_iterator end_it = reusable_cache_stack->token_cache->end(); an_immutable_cached_token *token; db_enter(4, "get_token_from_reusable_cache_stack"); if (!reusable_cache_stack->has_next_token()) { /* We make sure we don't pop a reusable cache if an error occurs during a rescan. */ check_assertion(reusable_cache_stack->dependent_scans > 0); curr_token = tok_end_of_source; curr_token_sequence_number = NO_TOKEN_SEQUENCE_NUMBER; last_token_sequence_number_of_token = NO_TOKEN_SEQUENCE_NUMBER; goto done; } /* if */ for (;;) { /* Remove the first entry from the list. */ token = (*(reusable_cache_stack->next_token)).ptr(); ++(reusable_cache_stack->next_token); if (reusable_cache_stack->next_token == end_it) { reusable_cache_stack->next_token = a_token_cache_iterator(); } /* if */ /* If it is a special entry indicating a pragma, process it and take another entry. Otherwise, exit the loop. */ if (!token->is_pragma()) { break; } /* if */ if (!curr_lexical_state_stack_entry->flushing_tokens) { /* Set the current token pragma list to point to the pragmas associated with the cached token. */ check_assertion_str(!suppress_pragma_processing, "get_token_from...: pragma found in suppress_pragma mode"); copy_fresh_pragmas_into(curr_token_pragmas, *token->get_pragma_list()); } /* if */ } /* for */ /* When fetch_pp_tokens is FALSE, make sure that the token being retrieved is not a cached pp-token. If it is, flush any cached pp-tokens and issue an error at the point at which we resume scanning normal tokens. It should not be possible to run out of tokens while flushing the pp-tokens. */ if (token->is_pp_token() && !fetch_pp_tokens) { while (reusable_cache_stack->has_next_token() && token->is_pp_token()) { token = (*(reusable_cache_stack->next_token)).ptr(); ++(reusable_cache_stack->next_token); if (reusable_cache_stack->next_token == end_it) { reusable_cache_stack->next_token = a_token_cache_iterator(); } /* if */ } /* while */ check_assertion_str2(!token->is_pp_token(), "get_token_from_reusable_cache_stack:", "pp-token flush consumed all tokens"); pos_error(ec_end_of_flush, token->get_source_position()); } /* if */ /* Entry is for a token (normal case). */ curr_token = token->get_kind(); pos_curr_token = *token->get_source_position(); #if EXTRA_SOURCE_POSITIONS_IN_IL end_pos_curr_token = *token->get_ending_source_position(); #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ error_position = pos_curr_token; curr_token_sequence_number = token->get_starting_seq_number(); last_token_sequence_number_of_token = token->get_ending_seq_number(); start_of_curr_token = end_of_curr_token = NULL; len_of_curr_token = 0; if (token->is_basic()) { /* This is the most common case, there's nothing special about this token; skip further processing. */ } else if (token->is_pp_token()) { /* This is a cached pp-token. Restore the starting and ending token positions. */ const a_pp_token_descr *pp_descr = token->get_pp_token_descr(); start_of_curr_token = pp_descr->token_start; end_of_curr_token = pp_descr->token_end; } else if (token->is_identifier()) { /* For an identifier, restore the locator. */ locator_for_curr_id = token->get_locator(); #if BUILTIN_FUNCTIONS_ENABLED if (locator_for_curr_id.symbol_header != NULL && builtin_needs_to_be_loaded(locator_for_curr_id.symbol_header)) { /* If this cached identifier is for a builtin function that has not yet been loaded (because loading is disabled during caching), load it now. */ (void)load_matching_builtin_function(locator_for_curr_id.symbol_header); } /* if */ #endif /* BUILTIN_FUNCTIONS_ENABLED */ } else if (token->is_asm_string()) { /* For a Microsoft asm token, restore the asm string pointer. */ curr_token_asm_string = const_cast(token->get_asm_string()); } else if (token->is_constant()) { /* For a literal constant, restore const_for_curr_token. */ copy_constant(token->get_constant(), &const_for_curr_token); } else if (token->is_ud_literal()) { /* The value of curr_token is set to indicate to cache_curr_token (if invoked by lookups triggered by find_literal_operator) that the current token state is a partially resolved user-defined literal. */ Value_saver saved_curr_token( &curr_token, /*new_value=*/tok_unresolved_ud_literal); /* Restore const_for_curr_token and const_with_curr_tok_spelling. */ const a_ud_literal_descr *ud_descr = token->get_ud_literal_descr(); copy_constant(ud_descr->value_con, &const_for_curr_token); copy_constant(ud_descr->spelling_con, &const_with_curr_tok_spelling); /* Set up locator_for_curr_id and look up the symbol for the literal operator or literal operator template. (The lookup must be repeated here, rather than using the symbol stored with the token, to handle cases where the result will be different from that of the original lookup, e.g., because of a using-directive in this scope that would not have been parsed before the UDL was cached.) */ ud_lit_op_sym_for_curr_token = find_literal_operator(ud_descr->suffix, strlen(ud_descr->suffix), &pos_curr_token, ud_descr->type, /*from_cache=*/TRUE, (a_diagnostic_ptr)NULL); ud_lit_type_for_curr_token = ud_descr->type; } else if (token->is_unresolved_ud_literal()) { /* This token is not expected to appear in long-lived caches such as those the reusable cache stack pulls from; see the description of tok_unresolved_ud_literal for more information. */ unexpected_condition(); } else if (token->is_ifc_reference()) { ifc_index_for_curr_token = token->get_ifc_index(); } /* if */ /* Check whether we have reached the end of this cache. */ while (!reusable_cache_stack->has_next_token() && reusable_cache_stack->dependent_scans == 0) { /* Don't pop this entry off the stack if it is currently being used for a variadic template rescan. */ pop_reusable_cache_stack(); if (reusable_cache_stack == NULL) break; } /* while */ done: db_exit(); } /* get_token_from_reusable_cache_stack */ /* Macro that is TRUE when tokens are being rescanned from a cache. */ #define rescanning_cached_tokens() \ (!cached_token_rescan_stack->is_empty() || reusable_cache_stack != NULL) a_boolean scanning_from_token_cache(void) /* Return whether tokens are being rescanned from a cache. */ { return rescanning_cached_tokens(); } /* scanning_from_token_cache */ static an_orig_line_modif_ptr add_orig_line_modif( an_orig_line_modif_kind kind, a_const_char *line_loc) /* Allocate an original line modification entry, set its kind to "kind", set its line location to "line_loc", set its fields to default values, add it to the end of the orig_line_modif_list, and return a pointer to it. Entries of this kind indicate the locations of modifications to the original source line because of trigraphs and line splices. */ { an_orig_line_modif_ptr olmp; if (avail_orig_line_modifs != NULL) { /* Reuse a freed entry. */ olmp = avail_orig_line_modifs; avail_orig_line_modifs = avail_orig_line_modifs->next; } else { /* Allocate a new entry. */ olmp = (an_orig_line_modif_ptr)alloc_fe(sizeof(an_orig_line_modif)); #if DEBUG num_orig_line_modifs_allocated++; #endif /* DEBUG */ } /* if */ /* Set the fixed fields. */ olmp->next = NULL; olmp->line_loc = line_loc; olmp->kind = kind; olmp->in_raw_string_literal = FALSE; /* Set the variant fields. */ switch (kind) { case olm_trigraph: case olm_splice_whitespace: olmp->variant.orig_char = ' '; /* To be neat. */ break; case olm_line_splice: case olm_multiline_string_splice: olmp->variant.line_splice_seq_number = 0; /* To be neat. */ break; case olm_null: /* No variant fields. */ break; default: unexpected_condition_str("add_orig_line_modif: bad kind"); } /* switch */ /* Add the entry to the end of the list. */ if (orig_line_modif_list == NULL) { /* This is the first entry on the list. */ orig_line_modif_list = olmp; } else { /* Link the current last entry to this new last entry. */ end_orig_line_modif_list->next = olmp; } /* if */ /* The new entry is the new end of list. */ end_orig_line_modif_list = olmp; no_modifs_to_curr_source_line = FALSE; return(olmp); } /* add_orig_line_modif */ #if FULLY_RESOLVED_MACRO_POSITIONS a_macro_text_map_entry_ptr find_macro_text_map_entry_for_offset( a_macro_text_map_ptr mtmp, sizeof_t offset) /* Return the entry in mtmp that covers the given offset, or NULL if there is no such entry. The last entry in a map is a terminator whose offset is larger than any actual offset in the text buffer; an offset equal to that terminator is treated as belonging to the preceding entry when the terminator has a zero sequence number. */ { a_macro_text_map_entry_ptr entries = mtmp->entries; a_macro_text_map_entry_ptr result = NULL; auto get_start = [entries](size_t idx) { return entries[idx].start_of_region; }; /* Search all but the terminator so that a match always has a succeeding entry for the range comparison, as the terminator's offset is larger than any actual offset. */ ptrdiff_t idx = low_bound(mtmp->num_entries - 1, offset, get_start); if (idx == -1) { /* Every non-terminator entry starts before offset. The last real entry covers the offset if it falls in that entry's range, including the terminator offset when the terminator has a zero sequence number. */ if (mtmp->num_entries >= 2) { a_macro_text_map_entry_ptr last_real = &entries[mtmp->num_entries - 2]; a_macro_text_map_entry_ptr terminator = &entries[mtmp->num_entries - 1]; if (offset >= last_real->start_of_region && (offset < terminator->start_of_region || (offset == terminator->start_of_region && terminator->corresponding_source_pos.seq == 0))) { result = last_real; } /* if */ } /* if */ } else if (entries[idx].start_of_region == offset) { result = &entries[idx]; } else if (idx > 0) { /* start[idx] is greater than offset, so the previous entry covers it. */ result = &entries[idx - 1]; } /* if */ return result; } /* find_macro_text_map_entry_for_offset */ static void get_source_pos_from_macro_text_map( a_source_line_modif_ptr slmp, a_const_char *loc_in_line, a_seq_number *seq, a_column_number *column, a_macro_invocation_record_index *macro_context) /* Find the source position associated with the specified line location (loc_in_line) in the macro text map specified by slmp and return the results in *seq, *column, and *macro_context. */ { a_macro_text_map_ptr mtmp = &slmp->text_map; a_macro_text_map_entry_ptr mtmep; sizeof_t offset = loc_in_line - slmp->inserted_text; /* Find the macro text map entry that covers the specified offset. */ mtmep = find_macro_text_map_entry_for_offset(mtmp, offset); check_assertion_str2(mtmep != NULL, "get_source_pos_from_macro_text_map:", "offset not found"); /* Copy the corresponding source position, with the appropriate offset from the start of the region. */ *seq = mtmep->corresponding_source_pos.seq; if (mtmep->corresponding_source_pos.seq != 0) { /* Apply the offset from the start of the region in the buffer to the column position. */ *column = mtmep->corresponding_source_pos.column + (a_column_number)(offset - mtmep->start_of_region); } else { /* Special positions like predefined macros and command line macros are identified by special values of the column field, which must be maintained verbatim. */ *column = mtmep->corresponding_source_pos.column; } /* if */ #if RECORD_MACRO_INVOCATIONS *macro_context = mtmep->macro_context; #else /* !RECORD_MACRO_INVOCATIONS */ *macro_context = NO_PARENT_MACRO_INVOCATION; #endif /* RECORD_MACRO_INVOCATIONS */ } /* get_source_pos_from_macro_text_map */ #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ void add_concatenation_record(a_concatenation_record_ptr *headp, a_concatenation_record_ptr *tailp, char *line_loc, a_symbol_ptr macro_sym) /* Allocate a concatenation record (or reuse one from the "available" list of freed records), adding it to the end of the list described by headp and tailp, and set its line_loc and macro_sym pointers as specified. */ { a_concatenation_record_ptr crp; if (avail_concatenation_records != NULL) { /* Reuse an existing record from the "available" list. */ crp = avail_concatenation_records; avail_concatenation_records = crp->next; } else { /* Allocate a new record. */ crp = (a_concatenation_record_ptr)alloc_fe(sizeof(a_concatenation_record)); #if DEBUG ++num_concatenation_records_allocated; #endif /* DEBUG */ } /* if */ if (*tailp != NULL) { /* Link this record to the last one in the list. */ (*tailp)->next = crp; } else { /* This is the first record. */ *headp = crp; } /* if */ *tailp = crp; crp->next = NULL; crp->line_loc = line_loc; crp->macro_sym = macro_sym; } /* add_concatenation_record */ static void free_concatenation_record(a_concatenation_record_ptr *crpp) /* Move the concatenation record to which *crpp points to the "available" list and set *crpp to point to the record following the one being freed. */ { a_concatenation_record_ptr crp = *crpp; *crpp = crp->next; crp->next = avail_concatenation_records; avail_concatenation_records = crp; } /* free_concatenation_record */ /* Compute the hash value to be used in source_line_modif_hash_table for the indicated address (often the line_loc field value of a source line modification). */ #define hash_value_for_source_line_modif(loc) \ (((possible_lossy_cast_from_pointer(loc))/HOST_ALIGNMENT_REQUIRED)% \ SOURCE_LINE_MODIF_HASH_TABLE_SIZE) void add_source_line_modif_to_hash_table(a_source_line_modif_ptr slmp) /* Add the indicated source line modification to the hash table used to optimize calls to nested_source_line_modif. */ { /* Don't add if line_loc == NULL. */ if (slmp->line_loc != NULL) { unsigned long hash = hash_value_for_source_line_modif(slmp->line_loc); slmp->next_in_hash_table = source_line_modif_hash_table[hash]; source_line_modif_hash_table[hash] = slmp; } /* if */ } /* add_source_line_modif_to_hash_table */ void rem_source_line_modif_from_hash_table(a_source_line_modif_ptr slmp) /* Remove the indicated source line modification from the hash table used to optimize calls to nested_source_line_modif. */ { /* Don't remove if line_loc == NULL (the entry wasn't in the hash table). */ if (slmp->line_loc != NULL) { unsigned long hash = hash_value_for_source_line_modif(slmp->line_loc); a_source_line_modif_ptr tslmp, pslmp; #if DEBUG num_lookups_in_source_line_modif_hash_table++; #endif /* DEBUG */ for (pslmp = NULL, tslmp = source_line_modif_hash_table[hash]; ; pslmp = tslmp, tslmp = tslmp->next_in_hash_table) { check_assertion_str(tslmp != NULL, "rem_source_line_modif_from_hash_table: not found in hash table"); #if DEBUG num_compares_in_source_line_modif_hash_table++; #endif /* DEBUG */ if (tslmp == slmp) { /* Found the entry. Unlink it from the hash table. */ if (pslmp == NULL) { source_line_modif_hash_table[hash] = tslmp->next_in_hash_table; } else { pslmp->next_in_hash_table = tslmp->next_in_hash_table; } /* if */ break; } /* if */ } /* for */ } /* if */ } /* rem_source_line_modif_from_hash_table */ static void free_orig_line_modif(an_orig_line_modif_ptr *olmp) /* Free the original line modification pointed to by *olmp, and set *olmp to NULL. */ { /* Add the entry to the front of the list of available entries. */ (*olmp)->next = avail_orig_line_modifs; avail_orig_line_modifs = *olmp; *olmp = NULL; } /* free_orig_line_modif */ a_source_line_modif_ptr add_source_line_modif( a_const_char *line_loc, sizeof_t num_chars_to_delete, a_const_char *inserted_text, a_const_char *end_inserted_text) /* Allocate a source line modification entry, put "line_loc", "num_chars_to_delete", "inserted_text", and "end_inserted_text" in its like-named fields, set its other fields to default values, add it to the front of the source_line_modif_list, and return a pointer to it. line_loc is NULL to indicate an insertion before the first character of curr_source_line. Inserted_text and end_inserted_text are passed in as NULL when the caller plans to set those fields explicitly later. Entries of this kind are used to record changes made during preprocessing, such as deletion of comments and expansion of macro invocations. */ { a_source_line_modif_ptr slmp; if (avail_source_line_modifs != NULL) { /* Reuse a freed entry. */ slmp = avail_source_line_modifs; avail_source_line_modifs = avail_source_line_modifs->next; } else { /* Allocate a new entry. */ slmp = (a_source_line_modif_ptr)alloc_fe(sizeof(a_source_line_modif)); #if DEBUG num_source_line_modifs_allocated++; #endif /* DEBUG */ } /* if */ slmp->next = source_line_modif_list; slmp->next_in_hash_table = NULL; slmp->line_loc = line_loc; slmp->parent_modif = NULL; slmp->num_chars_to_delete = num_chars_to_delete; slmp->num_deleted_chars = 0; slmp->is_isolated_text = FALSE; slmp->is_for_comment = FALSE; slmp->parent_modif_determined = FALSE; slmp->being_rescanned_for_token_pasting = FALSE; slmp->locked = FALSE; slmp->contains_saved_macro_argument_text = FALSE; slmp->is_whitespace_kwd = FALSE; slmp->is_raw_or_expanded_arg = FALSE; slmp->is_concat_with_macro_argument = FALSE; slmp->is_concat_with_va_args = FALSE; slmp->has_lparen_from_arg = FALSE; slmp->inserted_text = inserted_text; slmp->end_inserted_text = end_inserted_text; slmp->assoc_macro = (a_symbol_ptr)NULL; /* Give this entry a sequence id indicating the "time" at which it was added. */ slmp->sequence_id = ++sequence_id_for_source_line_modifs; slmp->source_position.seq = 0; slmp->source_position.column = SP_COL_UNKNOWN; slmp->text_from_primary_source_line = NULL; #if FULLY_RESOLVED_MACRO_POSITIONS init_macro_text_map(/*num_entries=*/0, &slmp->text_map, /*resizable=*/FALSE); slmp->num_active_position_trackers = 0; #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ #if RECORD_MACRO_INVOCATIONS slmp->invocation_record = NO_PARENT_MACRO_INVOCATION; slmp->invocation_depth = 0; #endif /* RECORD_MACRO_INVOCATIONS */ slmp->concatenations = NULL; if (line_loc != NULL) { /* Normal case: line_loc points to the point of insertion. Save the original character, and replace it with a marker that will call attention to the source modification. */ slmp->orig_char = (unsigned char)*line_loc; *(char *)line_loc = ATTENTION_MARKER; } else { /* Insertion before the first character of curr_source_line. */ slmp->orig_char = ' '; /* Save a pointer to this entry so that it can be found easily. */ #if CHECKING if (line_start_source_line_modif != NULL && !at_end_of_source_file) { /* There should only be one of these unless we are at the end of a source file. (Advancing to a new source line generally sets this variable to NULL, but at the end of a source file, that action is deferred until the source file stack is popped. The scan for the parenthesis after the name of a function-style macro does not pop the source file stack, however, so if the last line of a file both ends in the name of a function-style macro and began with a re-inserted macro name from the preceding line, the line start modification should simply be replaced -- it is not an error.) */ internal_error( "add_source_line_modif: more than one line_start_source_line_modif"); } /* if */ #endif /* CHECKING */ line_start_source_line_modif = slmp; } /* if */ /* Add the entry to the front of the list. (Adding to the front helps keep those entries most likely to be referenced where they will be found most quickly.) */ source_line_modif_list = slmp; no_modifs_to_curr_source_line = FALSE; /* Add the entry to the hash table used by nested_source_line_modif. */ add_source_line_modif_to_hash_table(slmp); return slmp; } /* add_source_line_modif */ /* Add a source line modification to indicate replacement of num_chars characters starting at line_loc by a space. This is used for deletion of comments. A different space string must be used for each comment in the current line so that one can get back from each one to the right place based only on line position; for this reason, the space/end-insertion string is placed in the a_source_line_modif entry. */ #define replace_source_string_by_space(line_loc, num_chars) \ { a_source_line_modif_ptr rslmp; \ rslmp = add_source_line_modif(line_loc, num_chars, \ (char *)NULL, (char *)NULL); \ *rslmp->inserted_chars = ' '; \ rslmp->inserted_chars[1] = LE_ESCAPE; \ rslmp->inserted_chars[2] = LE_END_OF_INSERTION; \ rslmp->inserted_text = rslmp->inserted_chars; \ rslmp->end_inserted_text = rslmp->inserted_chars+1; \ rslmp->is_for_comment = TRUE; \ } /* replace_source_string_by_space */ void free_source_line_modif(a_source_line_modif_ptr *slmp) /* Free the source line modification entry pointed to by *slmp, and set *slmp to NULL. */ { /* Add the entry to the front of the list of available entries. */ (*slmp)->next = avail_source_line_modifs; avail_source_line_modifs = *slmp; /* Add any leftover concatenation records to the list of available entries. */ while ((*slmp)->concatenations != NULL) { free_concatenation_record(&(*slmp)->concatenations); } /* while */ /* Clear the parameter so that the pointer cannot be inadvertently used again. */ *slmp = NULL; } /* free_source_line_modif */ void rem_source_line_modif(a_source_line_modif_ptr slmp) /* Remove the source line modification pointed to by *slmp from the source_line_modif_list. The entry is not freed. */ { a_source_line_modif_ptr prev_slmp; /* Remove this entry from the source_line_modif_list. */ if (slmp == source_line_modif_list) { /* The entry is first on the list, so removing it is easy. */ source_line_modif_list = slmp->next; } else { /* Find the entry preceding this one, and link around this entry. */ for (prev_slmp = source_line_modif_list; prev_slmp->next != slmp; prev_slmp = prev_slmp->next) {}; prev_slmp->next = slmp->next; } /* if */ slmp->next = NULL; /* If this entry was line_start_source_line_modif (a special entry inserted preceding the first character of curr_source_line), clear that pointer now, because there is no longer such an entry. */ if (line_start_source_line_modif == slmp) { line_start_source_line_modif = NULL; } else if (slmp->line_loc == NULL) { /* An entry that contains saved macro argument text from a previous source line or was originally added as line_start_source_line_modif (identified by orig_char == ' ', which cannot otherwise occur). */ check_assertion_str(slmp->contains_saved_macro_argument_text || slmp->orig_char == ' ', "rem_source_line_modif: line_loc NULL"); } else { /* Restore the original character (thus removing the attention marker character put in when this entry was added). */ *(char *)(slmp->line_loc) = (char)slmp->orig_char; } /* if */ /* Remove the entry from the hash table used by nested_source_line_modif. */ rem_source_line_modif_from_hash_table(slmp); } /* rem_source_line_modif */ a_source_line_modif_ptr assoc_source_line_modif_full( a_const_char *loc_in_line, a_boolean failure_allowed) /* Find the source line modification entry that defines the character location loc_in_line. If failure_allowed is FALSE, the location must be one that appears in a source modification (i.e., the caller probably has had to check already that within_curr_source_line(loc_in_line) == FALSE). */ { a_source_line_modif_ptr slmp, prev_slmp; for (prev_slmp = NULL, slmp = source_line_modif_list; slmp != NULL; prev_slmp = slmp, slmp = slmp->next) { if (ptr_in_range(loc_in_line, slmp->inserted_text, slmp->end_inserted_text+1)) { /* loc_in_line falls within this entry's text. Move the entry to the front of the list to speed up other searches. */ if (prev_slmp != NULL && !slmp->locked) { prev_slmp->next = slmp->next; slmp->next = source_line_modif_list; source_line_modif_list = slmp; } /* if */ break; } /* if */ } /* for */ #if CHECKING if (slmp == NULL && !failure_allowed) { internal_error("assoc_source_line_modif: bad address"); } /* if */ #endif /* CHECKING */ return(slmp); } /* assoc_source_line_modif_full */ a_source_line_modif_ptr f_parent_source_line_modif( a_source_line_modif_ptr slmp) /* Find the parent of the source line modification entry slmp, and return a pointer to it. The parent modification is the modification that inserts the text that slmp modifies. Return NULL if slmp has no parent (i.e., it modifies the primary source line). This routine should not be called directly: call the macro parent_source_line_modif instead, which checks the parent_modif_determined flag to see if the parent is already known. */ { a_source_line_modif_ptr parent_slmp; a_const_char *line_loc; line_loc = slmp->line_loc; if (line_loc == NULL) { /* This is line_start_source_line_modif, the special entry to insert at the beginning of the current source line. Its parent is NULL. Or, an entry that contains saved macro argument text from a previous source line, which also has a null parent. */ parent_slmp = NULL; } else if (within_curr_source_line(line_loc)) { /* slmp modifies the primary source line; it has no parent. */ parent_slmp = NULL; } else { /* slmp modifies the text inserted by some other modification. Find out which. */ parent_slmp = assoc_source_line_modif(line_loc); } /* if */ /* Call a macro to store the value and set the parent_modif_determined flag. */ set_parent_modif(slmp, parent_slmp); return parent_slmp; } /* f_parent_source_line_modif */ a_source_line_modif_ptr nested_source_line_modif(a_const_char *loc_in_line) /* *loc_in_line contains an ATTENTION_MARKER, indicating that the text at that point is altered by a source line modification entry. Find the entry, and return a pointer to it. */ { unsigned long hash = hash_value_for_source_line_modif(loc_in_line); a_source_line_modif_ptr slmp; #if DEBUG num_lookups_in_source_line_modif_hash_table++; #endif /* DEBUG */ /* Find the location in the hash table. */ for (slmp = source_line_modif_hash_table[hash]; ; slmp = slmp->next_in_hash_table) { check_assertion_str(slmp != NULL, "nested_source_line_modif: not found in hash table"); #if DEBUG num_compares_in_source_line_modif_hash_table++; #endif /* DEBUG */ if (slmp->line_loc == loc_in_line) break; } /* for */ return slmp; } /* nested_source_line_modif */ a_boolean has_nested_source_line_modif(a_const_char *loc_in_line) /* *loc_in_line contains an ATTENTION_MARKER, possibly indicating that the text at that point is altered by a source line modification entry. (Some ATTENTION_MARKERs can appear in raw string literals in macro expansions, and those do not indicate the start of a source line modification.) Return TRUE if there is a source line modification associated with the indicated location, FALSE otherwise. */ { unsigned long hash = hash_value_for_source_line_modif(loc_in_line); a_source_line_modif_ptr slmp; #if DEBUG num_lookups_in_source_line_modif_hash_table++; #endif /* DEBUG */ /* Search the hash table for the specified location. */ for (slmp = source_line_modif_hash_table[hash]; slmp != NULL && slmp->line_loc != loc_in_line; slmp = slmp->next_in_hash_table) { #if DEBUG num_compares_in_source_line_modif_hash_table++; #endif /* DEBUG */ } /* for */ return slmp != NULL; } /* has_nested_source_line_modif */ void gen_pp_line_info(ARG_UNUSED char kind, a_boolean next_line) /* Write a line-identification directive to f_pp_output as part of preprocessor output. It should identify the current line, or the line following the current line if next_line is TRUE. The kind character is the third operand: '1' for entry into a file, '2' for exit from a file, and ' ' for anything else. This routine should only be called when generate_pp_output is TRUE. */ { a_line_number eff_line_number; if (gen_line_info_in_pp_output) { /* Put out a line-identifying directive. The form is #line line-number "file-name" or, in pcc mode or GNU mode, # line-number "file-name" kind-codes This is similar to, but is not, a #line directive. The kind-codes are not always wanted (the SUN cc generates them, but not all pcc-based compilers do); the flag GEN_EXTRA_LINE_ID_INFO controls whether or not they are generated. */ if (!pcc_preprocessing_mode && !gnu_mode) { /* ANSI version. */ fputs("#line", f_pp_output); } else { /* pcc version. */ fputc('#', f_pp_output); } /* if */ /* Put out the line number. */ eff_line_number = curr_ise->line_number; if (next_line) { /* Identify the line after the current one. */ eff_line_number++; } else { /* Identify the current line. Note that curr_ise->line_number is the number of the last line read, which will be the number of the last physical line when a logical line is continued using backslashes. In such a case, get the number of the first line. */ eff_line_number -= seq_number_last_read - curr_seq_number; } /* if */ fprintf(f_pp_output, " %lu \"", (unsigned long)eff_line_number); /* Put out the file name. For ANSI/ISO output, add escapes as necessary. */ write_file_name(curr_ise->file_name, f_pp_output, /*process_escapes=*/!pcc_preprocessing_mode, /*escape_nonprintable_chars=*/TRUE); fputc('"', f_pp_output); #if GEN_EXTRA_LINE_ID_INFO if (pcc_preprocessing_mode || gnu_mode) { if (kind != ' ') { fputc(' ', f_pp_output); fputc(kind, f_pp_output); } /* if */ if (gnu_mode && curr_ise->from_system_include_dir) { fprintf(f_pp_output, " 3"); } /* if */ } /* if */ #endif /* GEN_EXTRA_LINE_ID_INFO */ fputc('\n', f_pp_output); (void)fflush(f_pp_output); /* Remember the sequence number associated with the current pp output position. When the current logical source line is continued using backslashes, curr_seq_number gives the sequence number of the first line and seq_number_last_read gives the sequence number of the last line. */ if (next_line) { next_seq_in_pp_output = seq_number_last_read + 1; } else { next_seq_in_pp_output = curr_seq_number; } /* if */ } /* if */ } /* gen_pp_line_info */ /* Execute "statement" if a blank should be put out before the first character of a new token to separate it from the previous token. This is used in producing preprocessing output and raw listing output, in cases where token confusion might result without the blank. token_start is TRUE if this is the start of a new token; if so, ch is the first character of the new token, prev_ch is the previous character, and prev_prev_ch is the character before that. prev_ch and prev_prev_ch are updated on return (always). token_start is reset to FALSE if it was TRUE. In some modes (pcc mode, #include directives for stdarg.h) no blank is ever put out. NOTE: This check needs to be pretty fast. It is only run when preprocessing or raw listing output is being created, and then only when there are expansions. Still, it gets done a lot. If this is the first character following a token mark, see if the previous character (prev_ch) and the current character (ch) could appear next to each other in a token. If so, a blank is put out to separate them. The decision is made conservatively -- if there is any doubt, or if it's too hard to figure out, the blank is put out. Start by getting the lexical category of the two characters. If either is a "singleton", i.e., a character that is always its own token and not part of another, then no space is needed. */ #define token_separator_blank_if_needed(ch, prev_ch, prev_prev_ch, \ token_start, statement) \ { a_byte cat_ch, cat_prev_ch; \ if (token_start) { \ token_start = FALSE; \ if (!no_token_separators_in_this_line_of_pp_output) { \ if ((cat_prev_ch = pp_lexical_category[prev_ch-CHAR_MIN]) == \ PLC_SINGLETON || \ (cat_ch = pp_lexical_category[ch-CHAR_MIN]) == PLC_SINGLETON) { \ /* At least one is a singleton, so no space is needed. */ \ } else if (cat_prev_ch != cat_ch && \ /* The two characters have different categories (i.e., one is \ a character that can appear in identifiers or pp-numbers, \ and the other is not). We probably do not need the space. \ However, check for some bizarre special cases having to do \ with pp-numbers (3.1.8; "e" or "E" followed by "+" or "-" \ can appear in a pp-number, and the same for "+" or "-" \ preceded by "e" or "E" and followed by a digit), prefixed \ string and character literals ("L", "U", "u", or "u8" followed \ by a single or double quote), and user-defined string literal \ suffixes ("x"y). */ \ ((prev_ch != 'e' && prev_ch != 'E') || \ (ch != '+' && ch != '-')) && \ !((prev_ch == '+' || prev_ch == '-') && \ (prev_prev_ch == 'e' || prev_prev_ch == 'E') && \ isdigit((unsigned char)ch)) && \ ((prev_ch != 'L' && prev_ch != 'U' && prev_ch != 'u' && \ !(prev_prev_ch == 'u' && prev_ch == '8')) || \ (ch != '\'' && ch != '"')) && \ (prev_ch != '"' || cat_ch != PLC_ID_OR_NUMBER)) { \ /* No space is needed. */ \ } else { \ /* An extra space to separate tokens is needed. */ \ statement; \ } /* if */ \ } /* if */ \ } /* if */ \ prev_prev_ch = prev_ch; \ prev_ch = ch; \ } /* token_separator_blank_if_needed */ namespace { /* This type is used to store invalid characters and their positions for delayed diagnosis. */ struct a_bad_unicode_char { unsigned long ch; /* The invalid character bytes. */ a_source_position pos; /* The position of the invalid character. */ }; /* a_bad_unicode_char */ } /* namespace */ namespace detail { /* The following specializations provide Is_trivially_copyable and Is_trivially_destructible support for a_bad_unicode_char. */ template<> struct Is_trivially_copyable_edg_impl : Integral_constant { }; /* Is_trivially_copyable_edg_impl */ template<> struct Is_trivially_destructible_edg_impl : Integral_constant { }; /* Is_trivially_destructible_edg_impl */ } /* namespace detail */ void gen_pp_output_for_curr_line(a_const_char *start_loc) /* Write out the portion of the current line beginning with start_loc and up to the first LE_END_OF_LINE, LE_END_OF_EMBED_PREFIX, or LE_END_OF_EMBED lexical escape to f_pp_output (preprocessing output), if necessary. This routine should be called only if generate_pp_output is TRUE. */ { a_const_char *loc_in_line; char ch; a_source_line_modif_ptr slmp; a_source_line_modif_ptr ins_slmp; char prev_ch; char prev_prev_ch; a_boolean token_start; an_orig_line_modif_ptr next_raw_string_modif; Small_dyn_array bad_chars_in_line; #if UNICODE_SOURCE_SUPPORTED /* Determine whether the Unicode encoding of the current file matches the default, which is the format used for the preprocessing output. If not, we'll have to do some processing on the characters being output. Note that we're testing the default macro here, not the variable, because we don't want the type of output to change based on a command-line option. */ a_boolean encoding_change_needed = ((curr_file_unicode_source_kind != usk_none) != (DEFAULT_UNICODE_SOURCE_KIND != usk_none)); #endif /* UNICODE_SOURCE_SUPPORTED */ /* do_not_put_curr_line_in_pp_output is TRUE if there is no current source line (as at the start of source), or if the line should not be put out (for example, because it's a preprocessing directive). */ /* There's a strange special case that happens when some text that should be output is inserted at the front of a line that should not be output, e.g., an inert macro identifier is inserted at the beginning of a line that is a preprocessing directive. The flag init_do_not_put_curr_line_in_pp_output controls output of the inserted text independently. */ if (!do_not_put_curr_line_in_pp_output || embed_control.prefix_loc != NULL || embed_control.suffix_loc != NULL || (line_start_source_line_modif != NULL && !init_do_not_put_curr_line_in_pp_output)) { /* See if the new line immediately follows the line previously written. If not, put out a directive to indicate the new line's position. If the previous output line did not end with a newline, line numbering can't be adjusted at this point. */ if (curr_seq_number != next_seq_in_pp_output && prev_pp_output_line_was_complete && line_start_source_line_modif == NULL) { if ((curr_seq_number <= next_seq_in_pp_output+5 || (curr_line_began_inside_comment && keep_comments_in_pp_output)) && /* Following line is needed for some cases involving reinsertion of a macro id at the beginning of a line. The reinserted id is followed by a newline, which bumps up the next_seq_in_pp_output past curr_seq_number. */ curr_seq_number > next_seq_in_pp_output) { /* Optimization -- For changes of a small number of lines, it is more efficient to put out one or more blank lines to move up to the desired line number. This is smaller in the output file, and also more efficient to process on the receiving end. We also avoid using the line directive if it would go into a comment in the pp output. Note that if line position information is not wanted in the output, the blank lines are not put out, since they serve no real purpose. */ while (curr_seq_number != next_seq_in_pp_output) { if (gen_line_info_in_pp_output) putc('\n', f_pp_output); next_seq_in_pp_output++; } /* while */ } else { /* Larger skip; put out a line-identifying directive. */ gen_pp_line_info(' ', /*next_line=*/FALSE); } /* if */ } /* if */ /* Put out the previous line itself. The text in curr_source_line has trigraphs and line splices processed, but is not correct for preprocessing output if there are in it any comments to be deleted or macros to be expanded or if any of the trigraphs or line splices occurred inside raw string literals. For those cases, the preprocessed line must be constructed by using the information in source_line_modif_list and orig_line_modif_list. */ for (next_raw_string_modif = orig_line_modif_list; next_raw_string_modif != NULL && !next_raw_string_modif->in_raw_string_literal; next_raw_string_modif = next_raw_string_modif->next) {} if (source_line_modif_list == NULL && #if UNICODE_SOURCE_SUPPORTED !encoding_change_needed && #endif /* UNICODE_SOURCE_SUPPORTED */ /* If there might be null (zero) characters in the line, do the expensive processing. */ (!null_chars_allowed_in_source || orig_line_modif_list == NULL || /* coverity[returned_null] */ /* coverity[dereference] */ (strchr(curr_source_line, LE_ESCAPE)[1] == LE_NEWLINE && next_raw_string_modif == NULL))) { /* For the common case, output the line quickly. */ /* We count on the fact that an LE_ESCAPE sequence will end the string. */ #if LE_ESCAPE != 0 #error -- LE_ESCAPE expected to be zero #endif /* LE_ESCAPE != 0 */ if (fputs(start_loc, f_pp_output) == EOF) { /* Error in writing the pp output file. This check is done on most lines and supplements the check done when the file is closed. Checking here is so that a disk full error is caught fairly quickly. */ file_write_error(ec_preprocessing_output, errno); } /* if */ /* The newline at the end of the source line is represented by an LE_ESCAPE/LE_NEWLINE lexical escape sequence, so no newline was printed. Also, when printing a partial line, we need to add a newline before the #embed expansion or before we move to the next line following the #embed. Print one now. */ putc('\n', f_pp_output); next_seq_in_pp_output++; prev_pp_output_line_was_complete = TRUE; } else { /* Construct the preprocessing output from the source line and the list of modifications. Start at the beginning, and scan through characters, processing escapes appropriately. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED int remaining_mbc_len = 0; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* The logic here is very similar to that in gen_expanded_raw_listing_output_for_curr_line. If you change this routine, change the other too. */ if (start_loc == curr_source_line) { set_up_for_walk_of_source_line(loc_in_line, slmp); } else { loc_in_line = start_loc; slmp = assoc_source_line_modif_full(start_loc, /*failure_allowed=*/TRUE); } /* if */ prev_ch = '\n'; prev_prev_ch = '\0'; token_start = FALSE; for (;;) { /* Fetch the next character, stepping into and out of macro expansions. */ ch = *loc_in_line; if (next_raw_string_modif != NULL && next_raw_string_modif->line_loc == loc_in_line) { /* The current character(s) in the source line were changed from their representation in the original physical line, but they occur inside a raw string literal and thus must be restored to their original form. */ switch (next_raw_string_modif->kind) { case olm_trigraph: /* A trigraph was changed to its corresponding single character. */ fprintf(f_pp_output, "?" "?%c", next_raw_string_modif->variant.orig_char); ++loc_in_line; break; case olm_line_splice: /* A backslash followed by a newline was deleted. */ fprintf(f_pp_output, "\\\n"); ++next_seq_in_pp_output; break; case olm_multiline_string_splice: /* A newline was changed into the "\n" escape. */ putc('\n', f_pp_output); ++next_seq_in_pp_output; loc_in_line += 2; break; case olm_null: /* A null character was changed into the LE_NULL lexical escape. */ putc('\0', f_pp_output); loc_in_line += LE_ESCAPE_LEN; break; case olm_splice_whitespace: /* A whitespace character followed a line splice. */ fprintf(f_pp_output, "%c", next_raw_string_modif->variant.orig_char); break; default: unexpected_condition(); } /* switch */ /* Advance to the next modification that occurs inside a raw string literal, if any. */ for (next_raw_string_modif = next_raw_string_modif->next; next_raw_string_modif != NULL && !next_raw_string_modif->in_raw_string_literal; next_raw_string_modif = next_raw_string_modif->next) {} /* Suppress inter-token spaces within raw string literals. */ prev_ch = '\n'; } else if (ch == ATTENTION_MARKER && has_nested_source_line_modif(loc_in_line)) { /* Attention marker. Find the associated source line modification and process it. */ walk_into_insertion(slmp, ins_slmp, loc_in_line); if (loc_in_line[0] == LE_ESCAPE && (loc_in_line[1] == LE_INERT_MACRO || loc_in_line[1] == LE_TEMPORARILY_INERT_MACRO)) { /* If the insertion starts with an inert macro indication, do not consider it the start of a new token. This makes some undefined-behavior token-pasting cases work slightly better in the output. */ } else { token_start = TRUE; } /* if */ } else if (ch == LE_ESCAPE) { /* Lexical escape. */ ch = loc_in_line[1]; if (ch == LE_END_OF_TOKEN) { /* Do not output end-of-token markers. */ token_start = TRUE; loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_INERT_MACRO || ch == LE_TEMPORARILY_INERT_MACRO) { /* Do not output inert-macro markers. */ loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_END_OF_INSERTION) { /* End of a macro expansion. */ /* If we've just finished the inserted text at the start of the source line, and the main part of the line is not supposed to be displayed (see comment above), stop here. */ if (slmp == line_start_source_line_modif && do_not_put_curr_line_in_pp_output) break; /* End of a macro. Pick up after the invocation text. */ check_assertion(slmp != NULL); walk_out_of_insertion(slmp, loc_in_line); token_start = TRUE; } else if (ch == LE_NEWLINE) { /* Newline character. */ putc('\n', f_pp_output); prev_ch = '\n'; /* Count newlines. This is done inside the loop because there are cases where no newline is output (two lines are joined in the output), and cases where one line has two newlines, and we don't want to miscount. */ prev_pp_output_line_was_complete = TRUE; next_seq_in_pp_output++; loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_END_OF_LINE || ch == LE_END_OF_EMBED_PREFIX || ch == LE_END_OF_EMBED) { /* If we are putting out a partial line, add a newline. */ if (ch != LE_END_OF_LINE) { fputc('\n', f_pp_output); prev_pp_output_line_was_complete = TRUE; ++next_seq_in_pp_output; } /* if */ /* End of the output. */ break; } else if (ch == LE_NULL) { /* Null (zero) character in line. */ putc('\0', f_pp_output); prev_ch = '\0'; loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_COMMA_FROM_ARGUMENT || ch == LE_RAW_OR_EXPANDED_ARGUMENT || ch == LE_LPAREN_FROM_ARGUMENT) { /* Do not output comma, parenthesis, or argument markers. */ loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_MICROSOFT_MAGIC_COMMA) { /* A comma that was removed because it appeared before an empty __VA_ARGS__ expansion. Put out nothing and skip over the escape and the associated comma. */ loc_in_line += LE_ESCAPE_LEN + 1; #if !FULLY_RESOLVED_MACRO_POSITIONS } else if (ch == LE_END_OF_TOP_LEVEL_EXPANSION) { /* Do not output end-of-top-level-expansion markers. */ loc_in_line += LE_ESCAPE_LEN; #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ } else if (ch == LE_EMPTY_VARIADIC_MACRO) { /* Do not output empty variadic macro expansion markers. */ loc_in_line += LE_ESCAPE_LEN; } else { unexpected_condition_str( "gen_pp_output_for_curr_line: bad lexical escape"); } /* if */ } else if (ch == '\n') { /* Ordinarily a newline character is interpreted as an ATTENTION_MARKER, but if a raw string literal appears in expanded macro text, it can have embedded newlines that are not ATTENTION_MARKERs. The ATTENTION_MARKER case was handled above, so treat this newline as if it were an LE_NEWLINE lexical escape; see above for details. */ putc('\n', f_pp_output); prev_ch = '\n'; prev_pp_output_line_was_complete = TRUE; ++next_seq_in_pp_output; ++loc_in_line; } else { /* Normal character. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED if (remaining_mbc_len > 0) { /* A character after the first in a multibyte character. No separating blank is possible or desirable. */ remaining_mbc_len--; prev_ch = '\n'; token_start = FALSE; } else if (multibyte_chars_in_source_enabled && ((remaining_mbc_len = lex_mbc_length_simple(loc_in_line) - 1) > 0 #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE /* Take this path if the input character is > 0x7f. This occurs when the input is not Unicode and the character must be converted to Unicode for the output. This test is not needed, but is harmless, when the input is Unicode as any initial character over 0x7f will pass the multibyte test above. */ || (unsigned char)ch > 0x7f #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ )) { /* The character is the start of a multibyte character string or in some modes a single non-Unicode character > 0x7f. We assume no separating blank is needed. */ prev_ch = '\n'; token_start = FALSE; #if UNICODE_SOURCE_SUPPORTED if (encoding_change_needed) { /* The input is a multibyte character (or single character that requires translation) and the output is either UTF-8 or non-Unicode. */ unsigned long wc; (void)mbc_to_wide_char( loc_in_line, &wc, (a_boolean *)NULL, curr_file_unicode_source_kind == usk_none); if (curr_file_unicode_source_kind == usk_none) { /* Convert the wide character to UTF-8. */ unsigned char arr[4]; unsigned utf_len = unicode_to_utf8(wc, arr); for (unsigned i = 0; i < utf_len; i++) { putc(arr[i], f_pp_output); } /* for */ ch = '\0'; /* Suppress output of ch below. */ } else { a_boolean err = FALSE; #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE /* Change a UTF-8 character to a (possibly multibyte) character because we're outputting non-Unicode. Give a warning if the character cannot be converted. */ int mb_len; int i; unsigned char arr[MAX_MULTIBYTE_CHAR_LENGTH]; /* Convert the Unicode character to a multibyte character sequence. "?" will be returned on error. */ mb_len = unicode_to_multibyte_char(wc, arr, &err); for (i = 0; i < mb_len; i++) putc(arr[i], f_pp_output); ch = '\0'; /* Suppress output of ch below. */ #else /* !NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ /* Change a UTF-8 character to a single character because we're outputting non-Unicode. Give a warning if the character does not fit. */ if (wc <= UCHAR_MAX) { /* The code point can be represented in a single character in Latin-1. */ ch = (char)(unsigned char)wc; } else { err = TRUE; ch = '?'; } /* if */ #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ if (err) { /* The code point could not be converted to a suitable representation above. Issue a diagnostic. */ conv_line_loc_to_source_pos(loc_in_line, &error_position); a_bad_unicode_char bad_char{wc, error_position}; bad_chars_in_line.push_back(bad_char); } /* if */ } /* if */ loc_in_line += remaining_mbc_len; remaining_mbc_len = 0; } /* if */ } else if (encoding_change_needed && (unsigned char)ch > 0x7f && curr_file_unicode_source_kind == usk_none) { /* Change a Latin-1 character with value > 0x7f to two bytes of UTF-8. */ unsigned char arr[4]; (void)unicode_to_utf8((unsigned long)(unsigned char)ch, arr); putc(arr[0], f_pp_output); ch = (char)arr[1]; prev_ch = '\n'; token_start = FALSE; #endif /* UNICODE_SOURCE_SUPPORTED */ } else #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Do not insert code here. */ { /* Output a blank to separate the character from the previous character if necessary to prevent tokenizing confusion. */ token_separator_blank_if_needed(ch, prev_ch, prev_prev_ch, token_start, putc(' ', f_pp_output)); } /* if */ /* Output the character. It will be set to '\0' if the needed characters were output above. */ if (ch != '\0') putc(ch, f_pp_output); prev_pp_output_line_was_complete = FALSE; loc_in_line++; } /* if */ } /* for */ } /* if */ /* Make sure this line is written only once. */ init_do_not_put_curr_line_in_pp_output = TRUE; do_not_put_curr_line_in_pp_output = TRUE; (void)fflush(f_pp_output); /* Issue diagnostics after the line is fully printed. */ for (a_bad_unicode_char &bad_ch : bad_chars_in_line) { a_number_buffer num_buff{hex_view_of(bad_ch.ch)}; pos_warning(ec_bad_unicode_char_in_pp_output, &bad_ch.pos, num_buff.as_temp_characters()); } /* for */ } /* if */ } /* gen_pp_output_for_curr_line */ void gen_rlisting_line_info(char kind) /* Generate line information for the raw listing file (which is optionally generated as input to a program that will generate an interspersed listing). kind is '1' when going into an include file, '2' after having left an include file, and ' ' for any other cases (like for the primary file and for #line directives). This routine should only be called when f_raw_listing is non-NULL. The slightly convoluted name of this routine is due to 8-char external uniqueness requirements. Sometimes there's no good way to get a unique name. */ { /* Generate line information for the raw listing file. The form is L line-number "file-name" kind */ fprintf(f_raw_listing, "L %lu \"%s\"", (unsigned long)(curr_ise->line_number+1), curr_ise->file_name); if (kind != ' ') { putc(' ', f_raw_listing); putc(kind, f_raw_listing); } /* if */ putc('\n', f_raw_listing); } /* gen_rlisting_line_info */ static void write_orig_line_piece(a_const_char *loc_in_line, a_const_char *stop_loc) /* Write the characters from *loc_in_line to just before *stop_loc in the current source line to the raw listing file. If stop_loc is NULL, write to the end of the source line. Write the characters in their original source form, i.e., if there are macro expansions replace the attention markers with the original characters. The caller guarantees that no orig_line_modif_list modifications apply to the indicated text. */ { a_const_char *local_stop_loc; a_source_line_modif_ptr slmp; for (;;) { if (source_line_modif_list == NULL) { /* There are no macro modifications, so the text can just be written. */ local_stop_loc = stop_loc; } else if (stop_loc == NULL) { /* Writing to end of line, and there are macro modifications to the current line. See if there are any attention markers in the rest of the line. Note that this scan will stop at the LE_ESCAPE/LE_NEWLINE lexical escape sequence at the end of the line if no attention marker is found. */ local_stop_loc = strchr(loc_in_line, ATTENTION_MARKER); } else { /* Writing part of the line, and there are macro modifications to the current line. See if there are any attention markers in the part of the line we want to write. */ for (local_stop_loc = loc_in_line; local_stop_loc < stop_loc && *local_stop_loc != ATTENTION_MARKER; local_stop_loc++) {} } /* if */ /* Now write whatever is just raw text. */ if (local_stop_loc == NULL) { /* Write to the end of the line. The end of every line will be written by this code, and in most cases the entire line. */ if (fputs(loc_in_line, f_raw_listing) == EOF) { /* Error in writing the raw listing file. This check supplements the check done when the file is closed. Checking here is done so that a disk full error is caught fairly quickly. */ file_write_error(ec_raw_listing, errno); } /* if */ /* Write the final newline (the source line contains an LE_ESCAPE/LE_NEWLINE escape rather than an actual newline character). */ putc('\n', f_raw_listing); } else { /* Write a piece of the line. */ fprintf(f_raw_listing, "%.*s", (int)(local_stop_loc-loc_in_line), loc_in_line); } /* if */ loc_in_line = local_stop_loc; /* If the whole requested piece has now been written out, exit the loop. */ if (loc_in_line == stop_loc) break; /* *loc_in_line must be an attention marker. Find and write the original character for that position. Note that there may be several modifications on that same location, and we have to find the original one. */ check_assertion(*loc_in_line == ATTENTION_MARKER); for (slmp = source_line_modif_list; ; slmp = slmp->next) { check_assertion(slmp != NULL); if (slmp->line_loc == loc_in_line && slmp->orig_char != ATTENTION_MARKER) break; } /* for */ loc_in_line++; if (slmp->orig_char != LE_ESCAPE) { putc(slmp->orig_char, f_raw_listing); } else { /* The original character is an escape. This must be the first character of a newline sequence, when the entire line is deleted. Move past it and let the newline character be put out on the normal exit above. */ check_assertion(*loc_in_line == LE_NEWLINE); loc_in_line += LE_ESCAPE_LEN-1; } /* if */ /* If the whole requested piece has now been written out, exit the loop. */ if (loc_in_line == stop_loc) break; } /* for */ } /* write_orig_line_piece */ /* Clear the raw_listing_buffer. */ #define clear_raw_listing_buffer() \ { loc_in_raw_listing_buffer = raw_listing_buffer; \ must_display_raw_listing_buffer = FALSE; \ } /* clear_raw_listing_buffer */ static void expand_raw_listing_buffer(void) /* We have run into a source line that won't fit in raw_listing_buffer; reallocate raw_listing_buffer to make it bigger. */ { sizeof_t old_size, new_size, loc_offset; char *new_raw_listing_buffer; db_enter(4, "expand_raw_listing_buffer"); old_size = (sizeof_t)(after_end_of_raw_listing_buffer - raw_listing_buffer); /* Increase the size of raw_listing_buffer. */ new_size = old_size * 2; new_raw_listing_buffer = realloc_buffer(raw_listing_buffer, old_size, new_size); loc_offset = (sizeof_t)(loc_in_raw_listing_buffer - raw_listing_buffer); raw_listing_buffer = new_raw_listing_buffer; after_end_of_raw_listing_buffer = raw_listing_buffer + new_size; loc_in_raw_listing_buffer = raw_listing_buffer + loc_offset; db_exit(); } /* expand_raw_listing_buffer */ /* Add a character to the raw listing buffer. Expand the buffer if necessary. */ #define add_char_to_raw_listing_buffer(ch) \ { if (loc_in_raw_listing_buffer == after_end_of_raw_listing_buffer) { \ expand_raw_listing_buffer(); \ } /* if */ \ *loc_in_raw_listing_buffer++ = ch; \ } /* add_char_to_raw_listing_buffer */ void gen_expanded_raw_listing_output_for_curr_line(a_boolean do_inserted_text) /* Generate the raw listing file output for the macro-expanded version of the current source line. This routine should only be called if f_raw_listing != NULL. If do_inserted_text is TRUE, there must be text inserted at the beginning of the current source line, and the expanded information for that inserted text (only) will be output. If do_inserted_text is FALSE, there must be a current source line, and the expanded information for it (and not any inserted text) will be output. This routine shouldn't be called more than once with each value of do_inserted_text for a given line, since it doesn't maintain its own flag indicating that the expanded version has already been put out. See gen_raw_listing_output_for_curr_line and cpp_driver, which control the calls to this routine. */ { a_const_char *loc_in_line; char ch; a_source_line_modif_ptr slmp; a_source_line_modif_ptr ins_slmp; char prev_ch; char prev_prev_ch; a_boolean token_start; /* The output line is generated in raw_listing_buffer first, then written to output. This expensive and unfortunate technique is required because of cases like ? ?=pragma hello / * * / there (Where the / * and * / are really comment delimiters, of course, and the two ?s are not separated by a space.) For that case, we'll get here with "#pragma hello " with no newline, and then later with " there". The whole line is written when the piece with the newline arrives. That's because the raw listing output has to look like N? ?=pragma hello / * N* / there X#pragma hello there (Again, the comment delimiters and ?s have to be imagined altered.) Without this trick, the two parts of the "X" line could not be written next to one another. Another issue: Lines that contain only comment modifications are not written. However, we could get a first segment of a line (not ending in a newline) that contains only comment modifications, and then a second segment (ending in a newline) that contains macro modifications. The entire line (both segments) must be written out, but we can't determine that while processing the first segment, so we have to save it. */ /* One easy speed optimization: If the line has no modifications of any kind (which implies it ends with a newline), and the buffer contains no nontrivial modifications (which implies that there is no previous line segment that would force the printing of this line), just throw the line away and clear the buffer. This deals with a substantial percentage of source lines. */ if (no_modifs_to_curr_source_line && !must_display_raw_listing_buffer) { clear_raw_listing_buffer(); } else { /* The logic here is very similar to that in gen_pp_output_for_curr_line. If you change this routine, change the other too. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED int remaining_mbc_len = 0; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ if (do_inserted_text) { /* Do the text inserted at the front of the source line. */ slmp = line_start_source_line_modif; loc_in_line = slmp->inserted_text; } else { /* Do the source line itself. */ slmp = NULL; loc_in_line = curr_source_line; /* A line containing trigraphs or line splices is considered modified and must be displayed. */ if (orig_line_modif_list != NULL) must_display_raw_listing_buffer = TRUE; } /* if */ prev_ch = '\n'; prev_prev_ch = '\0'; token_start = FALSE; for (;;) { /* Fetch the next character, stepping into and out of macro expansions. */ ch = *loc_in_line; if (ch == ATTENTION_MARKER && has_nested_source_line_modif(loc_in_line)) { /* Attention marker. Find the associated source line modification and process it. */ walk_into_insertion(slmp, ins_slmp, loc_in_line); token_start = TRUE; if (!ins_slmp->is_for_comment) { /* Keep track of whether or not there are noncomment (i.e., macro) modifications in the lines in the buffer. */ must_display_raw_listing_buffer = TRUE; } /* if */ } else if (ch == LE_ESCAPE) { /* Lexical escape. */ ch = loc_in_line[1]; if (ch == LE_END_OF_TOKEN || ch == LE_INERT_MACRO || ch == LE_TEMPORARILY_INERT_MACRO || ch == LE_COMMA_FROM_ARGUMENT || ch == LE_LPAREN_FROM_ARGUMENT || ch == LE_RAW_OR_EXPANDED_ARGUMENT || ch == LE_EMPTY_VARIADIC_MACRO #if !FULLY_RESOLVED_MACRO_POSITIONS || ch == LE_END_OF_TOP_LEVEL_EXPANSION #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ ) { /* Do not output end-of-token, inert-macro, comma, parenthesis, argument, empty variadic expansion, or end-of-top-level-expansion markers. */ token_start = TRUE; loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_END_OF_INSERTION) { /* End of a macro expansion. */ /* If we've just finished the inserted text at the start of the source line, stop. */ if (slmp == line_start_source_line_modif) break; /* End of a macro. Pick up after the invocation text. */ check_assertion(slmp != NULL); /* For Coverity. */ walk_out_of_insertion(slmp, loc_in_line); token_start = TRUE; } else if (ch == LE_NEWLINE) { /* Newline character. */ add_char_to_raw_listing_buffer('\n'); prev_ch = '\n'; /* We have a complete line and we should output it or throw it away now. */ if (must_display_raw_listing_buffer) { *loc_in_raw_listing_buffer = '\0'; putc('X', f_raw_listing); fputs(raw_listing_buffer, f_raw_listing); } /* if */ clear_raw_listing_buffer(); loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_END_OF_LINE) { /* End of the whole line. */ break; } else if (ch == LE_NULL) { /* Null (zero) character in line. Put out a blank. */ add_char_to_raw_listing_buffer(' '); prev_ch = ' '; loc_in_line += LE_ESCAPE_LEN; } else if (ch == LE_MICROSOFT_MAGIC_COMMA) { /* A comma that was suppressed by appearing before an empty __VA_ARGS__ expansion. Put out nothing and skip over the escape and the comma. */ loc_in_line += LE_ESCAPE_LEN + 1; } else { unexpected_condition_str( "gen_expanded_raw_listing_...: bad lexical escape"); } /* if */ } else if (ch == '\n') { /* Ordinarily a newline character is interpreted as an ATTENTION_MARKER, but if a raw string literal appears in expanded macro text, it can have embedded newlines that are not ATTENTION_MARKERs. The ATTENTION_MARKER case was handled above, so treat this newline as if it were an LE_NEWLINE lexical escape; see above for details. */ a_boolean save_must_display = must_display_raw_listing_buffer; add_char_to_raw_listing_buffer('\n'); prev_ch = '\n'; /* We have a complete line and we should output it or throw it away now. */ if (must_display_raw_listing_buffer) { *loc_in_raw_listing_buffer = '\0'; putc('X', f_raw_listing); fputs(raw_listing_buffer, f_raw_listing); } /* if */ clear_raw_listing_buffer(); must_display_raw_listing_buffer = save_must_display; ++loc_in_line; } else { /* Normal character. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED if (remaining_mbc_len > 0) { /* A character after the first in a multibyte character. No separating blank is possible or desirable. */ remaining_mbc_len--; token_start = FALSE; } else if (multibyte_chars_in_source_enabled && (remaining_mbc_len = lex_mbc_length_simple(loc_in_line) - 1) > 0) { /* The character is the start of a multibyte character string. We assume no separating blank is needed. */ token_start = FALSE; } else #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Do not insert code here. */ { /* Output a blank to separate the character from the previous character if necessary to prevent tokenizing confusion. */ token_separator_blank_if_needed(ch, prev_ch, prev_prev_ch, token_start, add_char_to_raw_listing_buffer(' ')); } /* if */ /* Output the character. */ add_char_to_raw_listing_buffer(ch); loc_in_line++; } /* if */ } /* for */ } /* if */ } /* gen_expanded_raw_listing_output_for_curr_line */ static void gen_raw_listing_output_for_curr_line(void) /* Generate raw listing output for the current source line. This routine should only be called when f_raw_listing is non-NULL. */ { an_orig_line_modif_ptr olmp, olmp_next; a_const_char *loc_in_line; /* No output if there is no current line. */ if (curr_raw_listing_line_code != '\0') { /* Write the macro-expanded form of the text inserted at the beginning of the source line if there is any and if there are modifications. */ if (line_start_source_line_modif != NULL) { gen_expanded_raw_listing_output_for_curr_line(/*do_inserted_text=*/TRUE); } /* if */ /* If we are now (at the end of the line) not in an if-skip, force the line to be an "N" (normal) line. This gets #else and #endif lines out as "N" lines rather than "S" (skipped) lines. */ if (!currently_in_pp_if_skip) curr_raw_listing_line_code = 'N'; /* Put out the start of the line. */ putc(curr_raw_listing_line_code, f_raw_listing); /* Reconstruct the original line and output it. */ loc_in_line = curr_source_line; /*lint --e{446,850} olmp modified in loop */ for (olmp = orig_line_modif_list; olmp != NULL; olmp = olmp->next) { /* Process each modification in order. */ /* Write unaffected text that precedes this modification. */ write_orig_line_piece(loc_in_line, olmp->line_loc); switch (olmp->kind) { case olm_trigraph: fprintf(f_raw_listing, "?" "?%c", olmp->variant.orig_char); /* If the trigraph is "? ? /", which turns into "\", and it's at the end of a line, the "\" will indicate a line splice. In that case, the "\" for the line splice should not be put out. (The extra spaces in the trigraph above are to avoid complaints from compilers compiling this comment.) */ olmp_next = olmp->next; if (olmp_next != NULL && olmp_next->kind == olm_line_splice && olmp_next->line_loc == olmp->line_loc) { /* Partially process the line-splice entry. */ olmp = olmp_next; loc_in_line = olmp->line_loc; goto partially_process_line_splice; } /* if */ /* Normal trigraph. */ loc_in_line = olmp->line_loc + 1; break; case olm_multiline_string_splice: /* A line splice induced by a multiline string. Skip the artificial \n and don't add a trailing backslash. */ loc_in_line = olmp->line_loc + 2; goto partially_process_line_splice; case olm_line_splice: loc_in_line = olmp->line_loc; putc('\\', f_raw_listing); partially_process_line_splice: putc('\n', f_raw_listing); putc(curr_raw_listing_line_code, f_raw_listing); break; case olm_null: /* Null (zero) character. Output as a blank. */ putc(' ', f_raw_listing); loc_in_line = olmp->line_loc + LE_ESCAPE_LEN; break; case olm_splice_whitespace: /* A whitespace character following a line splice. */ fprintf(f_raw_listing, "%c", olmp->variant.orig_char); break; default: unexpected_condition_str2("gen_raw_listing_output_for_curr_line:", "bad orig_modif_list entry kind"); } /* switch */ } /* for */ /* Write out the last piece of unaffected text, including the newline. In the most common case (no modifications), this writes the entire line. */ write_orig_line_piece(loc_in_line, (char *)NULL); /* Write the macro-expanded form of the line if there are modifications. */ gen_expanded_raw_listing_output_for_curr_line(/*do_inserted_text=*/FALSE); /* Make sure this line is written only once. */ curr_raw_listing_line_code = '\0'; } /* if */ } /* gen_raw_listing_output_for_curr_line */ void finish_raw_listing_file(void) /* Called on abnormal termination of the compilation to do cleanup on the raw listing file, e.g., force out the last source line. */ { if (f_raw_listing != NULL) { gen_raw_listing_output_for_curr_line(); } /* if */ } /* finish_raw_listing_file */ a_preinclude_file_ptr alloc_preinclude_file(void) /* Allocate a_preinclude_file structure, initialize it, and return a pointer. */ { a_preinclude_file_ptr pfp; pfp = alloc_general_of_type(a_preinclude_file); #if DEBUG num_preinclude_files_allocated++; #endif /* DEBUG */ pfp->next = NULL; pfp->file_name = NULL; return pfp; } /* alloc_preinclude_file */ /* The routines that follow are used to detect idioms used to guard against multiple inclusions of a given file. If such idiom is found, the front end will completely suppress any subsequent attempts to re-include the file. The idioms supported are #ifndef guard code, and "#pragma once" directives. Files using #ifndef guards have the form: ... Optional comments ... #ifndef NAME #define NAME ... Body of include file ... #endif #ifndef and #ifdef forms of this are supported. In addition these forms are accepted, but only if they do not have any embedded comments: #if !defined(NAME) #if defined(NAME) This include guard detection mechanism is implemented using a simple state machine. The state information is recorded in the input stack entry. (*) (*) ---------------> FAIL <------------------- | STATE | | ^ | | level-0 | #else/#elif | | | | | | #endif | START #ifdef INTERMEDIATE------------>ACCEPT STATE----------> STATE ---+ STATE | #ifndef | ^ | | | | | (*) | | | | -------| | | | | | | | | #pragma | once | | v | | #pragma ONCE #pragma | |--------------> STATE <------------------| once ^ | once | | |(*)| ----- In the above diagram, comments do not count as tokens at all, while (*) refers to any token other than those marking the other transitions. In the case of the INTERMEDIATE state (which is what the file is in during the body, if an opening #ifndef has been seen), if a matching #else is seen, we know that subsequent inclusions cannot be suppressed. Once the file has reached the ACCEPT state, if any token is seen, then the file goes into the FAIL state. Also, once the file gets into the ONCE state, it will never move out. Most of this determination is done in get_token(), and the routines that handle the #ifdef, #ifndef and #pragma directives (in preproc.c). The final accept-state check is performed in pop_input_stack(). On subsequent includes, the file history information is checked to determine whether the file contained a multiple inclusion guard. If so, the include is suppressed. */ static an_include_file_history_ptr alloc_include_file_history(void) /* Allocate an_include_file_history structure, initialize it, and return a pointer. */ { an_include_file_history_ptr ifhp; ifhp = (an_include_file_history_ptr) alloc_fe(sizeof(an_include_file_history)); #if DEBUG num_include_file_histories_allocated++; #endif /* DEBUG */ ifhp->full_name = NULL; ifhp->suppress_subsequent_include = FALSE; ifhp->pragma_once = FALSE; ifhp->ifdef_guard = FALSE; ifhp->ifndef_guard = FALSE; ifhp->use_canonical_name = FALSE; ifhp->controlling_macro_name = NULL; #if UNIQUE_FILE_IDENTIFIER_AVAILABLE clear_unique_file_id(&ifhp->unique_id); #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ return ifhp; } /* alloc_include_file_history */ a_hash_value hash_include_file_history(a_void_ptr key) /* Produce a hash value for an include file history entry. The key is a pointer to an include file history entry. */ { an_include_file_history_ptr ifhp = (an_include_file_history_ptr)key; a_const_char *str = ifhp->full_name; a_hash_value value = 0; /* Only hash the characters of the actual file name so that differences in the directory portion won't affect the result (e.g., x.h and ./x.h need to be considered the same). */ str = start_of_file_name(str); for (; *str != '\0'; str++) { /* Convert any uppercase characters to lower for hashing purposes. The actual file name comparison may or may not be case sensitive. */ unsigned char ch = (unsigned char)*str; if (isupper(ch)) { ch = (unsigned char)tolower((int)ch); } /* if */ value = (value * 31) + value + ch; } /* for */ return value; } /* hash_include_file_history */ a_boolean compare_include_file_history(a_void_ptr entry, a_void_ptr key) /* Compare an entry in the include file history hash table with an entry to be found. "entry" and "key" are of type an_include_file_history_ptr. Return TRUE if the key matches the entry. */ { an_include_file_history_ptr ifhp; an_include_file_history_ptr key_ifhp; a_const_char *full_name; a_boolean result; ifhp = (an_include_file_history_ptr)entry; key_ifhp = (an_include_file_history_ptr)key; full_name = key_ifhp->full_name; /* compare_file_names converts the names to canonical form. compare_file_chars does not. */ if (key_ifhp->use_canonical_name) { result = compare_file_names(full_name, ifhp->full_name) == 0; } else { result = compare_file_chars(full_name, ifhp->full_name) == 0; } /* if */ return result; } /* compare_include_file_history */ #if UNIQUE_FILE_IDENTIFIER_AVAILABLE a_hash_value hash_unique_file_id_for_table(a_void_ptr key) /* Produce a hash value for the unique file identifier in an include file history entry. The key is a pointer to an include file history entry. */ { an_include_file_history_ptr ifhp = (an_include_file_history_ptr)key; a_hash_value value = 0; value = hash_unique_file_id(&ifhp->unique_id); return value; } /* hash_unique_file_id_for_table */ a_boolean compare_unique_file_id(a_void_ptr entry, a_void_ptr key) /* Compare an entry in the unique file identifier hash table with an entry to be found. "entry" and "key" are of type an_include_file_history_ptr. Return TRUE if the key matches the entry. */ { an_include_file_history_ptr ifhp; an_include_file_history_ptr key_ifhp; a_boolean result; ifhp = (an_include_file_history_ptr)entry; key_ifhp = (an_include_file_history_ptr)key; result = same_unique_file_ids(&ifhp->unique_id, &key_ifhp->unique_id); return result; } /* compare_unique_file_id */ #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ a_boolean find_include_history(a_const_char *full_name, an_include_file_history_ptr *ifhp_ptr, a_boolean create, a_boolean use_canonical) /* Look for "full_name" in the include file history hash table. If no entry exists and "create" is TRUE, create a new entry. If an entry is found or created, return a pointer to the entry in ifhp_ptr. use_canonical is TRUE if file name comparison should use the canonical form of the file name. Return TRUE if an existing entry was returned. */ { an_include_file_history_ptr *ifhp_in_table; #if UNIQUE_FILE_IDENTIFIER_AVAILABLE an_include_file_history_ptr *ifhp_in_unique_id_table; #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ an_include_file_history_ptr ifhp; an_include_file_history key_ifh; a_boolean found = FALSE; /* Look for an existing entry for this file name in the hash table. */ key_ifh.full_name = full_name; key_ifh.use_canonical_name = use_canonical; ifhp_in_table = (an_include_file_history_ptr*)hash_find( include_file_history_hash_table, (a_void_ptr)&key_ifh, create); ifhp = ifhp_in_table == NULL ? NULL : *ifhp_in_table; #if UNIQUE_FILE_IDENTIFIER_AVAILABLE /* If an entry for the file was not found above, do another search using the file system's unique identifier (e.g., the inode number on Unix-like systems) to search for a previous include. */ if (ifhp == NULL) { get_unique_id_for_file(full_name, &key_ifh.unique_id); if (unique_file_id_hash_table == NULL) { unique_file_id_hash_table = alloc_hash_table( FRONT_END_REGION_NUMBER, (a_hash_table_size)1024, fn_for_function(hash_unique_file_id_for_table), fn_for_function(compare_unique_file_id)); } /* if */ ifhp_in_unique_id_table = (an_include_file_history_ptr*)hash_find( unique_file_id_hash_table, (a_void_ptr)&key_ifh, create); ifhp = ifhp_in_unique_id_table == NULL ? NULL : *ifhp_in_unique_id_table; if (create && ifhp != NULL) { /* The entry was found in the unique ID table but not in the file name hash table. Because "create" is TRUE, a slot in the file name hash table was created. We need to fill that in now because it won't be filled in below (because this lookup succeeded). */ *ifhp_in_table = ifhp; } /* if */ } /* if */ #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ if (ifhp != NULL) { /* An entry was found -- this file has been included before. */ found = TRUE; } else if (create) { /* This file has not been included before. Create a new file history entry. */ ifhp = alloc_include_file_history(); ifhp->full_name = copy_string_to_region(FRONT_END_REGION_NUMBER, full_name); #if UNIQUE_FILE_IDENTIFIER_AVAILABLE ifhp->unique_id = key_ifh.unique_id; /* Update the data pointer in the unique identifier hash table. */ *ifhp_in_unique_id_table = ifhp; #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ /* Update the data pointer in the hash table. */ *ifhp_in_table = ifhp; } /* if */ *ifhp_ptr = ifhp; return found; } /* find_include_history */ a_byte get_ifg_state(void) /* Get the current include file guard state from the current input stack entry. The state is first normalized by taking into account the any_tokens_fetched_from_curr_input_file global variable. */ { /* If we are in the START or ACCEPT state and we have encountered some tokens, move to the fail state. This means there were tokens before the opening #ifndef or after the #endif. */ a_byte state; if (curr_ise == NULL || !curr_ise->is_include_file) { state = IFG_STATE_FAIL; } else { if (curr_ise->ifg_state < IFG_STATE_FAIL && any_tokens_fetched_from_curr_input_file) { curr_ise->ifg_state = IFG_STATE_FAIL; } /* if */ state = curr_ise->ifg_state; } /* if */ return state; } /* get_ifg_state */ void set_ifg_state(a_byte new_state) /* Update the include file guard state. This routine resets the any_tokens_fetched_from_curr_input_file when appropriate. */ { if (curr_ise != NULL && curr_ise->is_include_file) { curr_ise->ifg_state = new_state; if (new_state == IFG_STATE_ACCEPT) { /* We've just seen the closing #endif, there should be no more tokens in this file. */ any_tokens_fetched_from_curr_input_file = FALSE; } /* if */ } /* if */ } /* set_ifg_state */ static a_boolean suppress_subsequent_include( an_include_file_history_ptr ifhp) /* Return TRUE if the specified include file contained include guard code that makes it possible to suppress subsequent re-inclusions. */ { a_boolean result = FALSE; a_symbol_ptr assoc_symbol; a_symbol_locator locator; if (ifhp->pragma_once) { result = TRUE; } else if (!ifhp->suppress_subsequent_include) { /* No need to check further. */ } else if (ifhp->ifdef_guard || ifhp->ifndef_guard) { /* See whether the controlling macro is currently defined. */ a_symbol_header_ptr sym_hdr; locator = cleared_locator; sym_hdr = find_symbol_header( ifhp->controlling_macro_name, (sizeof_t)(strlen(ifhp->controlling_macro_name)), &locator); assoc_symbol = find_defined_macro(sym_hdr); /* If the macro is undefined, then an #ifdef NAME guard would cause the included file to be ignored, so we should return TRUE (meaning it is OK to suppress the inclusion). */ result = assoc_symbol == NULL; /* If this is an #ifndef instead of an #ifdef, negate the current value of result. */ if (ifhp->ifndef_guard) result = !result; } /* if */ return result; } /* suppress_subsequent_include */ a_boolean suppress_subsequent_include_of_file( a_const_char *full_name, an_include_file_history_ptr *ifhp_ptr, a_boolean create, a_boolean use_canonical) /* Determine whether the specified file has already been included, and if so, whether a subsequent include should be suppressed because it will have no effect. If no entry is found, create one if "create" is TRUE. use_canonical is TRUE if file name comparison should use the canonical form of the file name when looking for a previously included file. If an entry is found or created, return the pointer in ifhp_ptr. */ { a_boolean result = FALSE; /* Find an existing include file history record for this file, or create one if none exists. */ (void)find_include_history(full_name, ifhp_ptr, create, use_canonical); if (*ifhp_ptr != NULL) { result = suppress_subsequent_include(*ifhp_ptr); } /* if */ #if DEBUG if (db_flag_is_set("ssiof")) { fprintf(f_debug, "suppress_subsequent_include_of_file: %s: %s\n", full_name, result ? "yes" : "no"); } /* if */ #endif /* DEBUG */ return result; } /* suppress_subsequent_include_of_file */ #if DEBUG static void db_include_guard_info(void) /* Display the include guard information associated with the current input stack entry, for debugging purposes. */ { a_const_char *idemp_name = NULL; a_const_char *idemp_text = NULL; db_enter(5, "db_include_guard_info"); switch (curr_ise->ifg_state) { case IFG_STATE_START: fprintf(f_debug, "Pop: File %s is (essentially) empty\n", curr_ise->file_name); break; case IFG_STATE_FAIL: case IFG_STATE_INTERMED: fprintf(f_debug, "Pop: File %s is not guarded\n", curr_ise->file_name); break; case IFG_STATE_ACCEPT: if (curr_ise->include_history->ifdef_guard) { idemp_name = "#ifdef"; idemp_text = curr_ise->include_history->controlling_macro_name; } else if (curr_ise->include_history->ifndef_guard) { idemp_name = "#ifndef"; idemp_text = curr_ise->include_history->controlling_macro_name; } else { unexpected_condition(); } /* if */ fprintf(f_debug, "Pop: File %s is guarded (kind = %s, macro name = \"%s\")\n", curr_ise->file_name, idemp_name, idemp_text); break; case IFG_STATE_ONCE: if (curr_ise->include_history->pragma_once) { fprintf(f_debug, "Pop: File %s is guarded (kind = #pragma once).\n", curr_ise->file_name); } else { unexpected_condition(); } break; default: unexpected_condition(); } /* switch */ db_exit(); } /* db_include_guard_info */ #endif /* DEBUG */ void check_for_generation_of_pch_on_return_to_primary_file(void) /* Generate a PCH file if we are at the top of the input stack and the flag is set indicating that a PCH file should be generated on return to the primary source file. This routine is called when an include file is popped off of the input stack to check whether a PCH file should be generated upon return to the primary source file. This routine is also called when an include directive is processed but the include is suppressed for some reason. */ { if (depth_input_stack == 0 && generate_pch_on_return_to_primary_source_file) { /* We are returning to the primary source file and the flag is set indicating that a PCH file should be generated at this point. */ generate_pch_on_return_to_primary_source_file = FALSE; generate_precompiled_header(); header_stop_no_longer_pending(); } /* if */ } /* check_for_generation_of_pch_on_return_to_primary_file */ a_boolean processing_primary_source_file(void) /* Return TRUE if the primary source file is the current input stack entry. */ { return depth_input_stack == 0; } /* processing_primary_source_file */ a_symbol_ptr resolve_id_pseudo_token_to_sym() /* Return the symbol corresponding to the resolution of the current resolved identifier pseudo token. If the symbol cannot be resolved, instead return NULL. */ { check_assertion(is_resolved_id_pseudo_token(curr_token)); a_symbol_ptr result = NULL; if (curr_token == tok_ifc_entity_ref) { result = load_tok_ifc_entity_ref(); } else { result = load_tok_ifc_decl_ref(); } /* if */ return result; } /* resolve_id_pseudo_token_to_sym */ static a_boolean no_more_preinclude_files(void) /* Returns TRUE if we are processing the last preinclude file. next_preinclude_file points to the next preinclude file to be processed from the current list (either the preinclude list or the macro preinclude list). It is NULL when the end of either list is reached. push_next_preinclude_file begins processing the preinclude list after the macro preinclude list has been exhausted. */ { return next_preinclude_file == NULL && (!processing_macro_preincludes || preinclude_file_list == NULL); } /* no_more_preinclude_files */ void push_next_preinclude_file(void) /* If there were preinclude files specified, push the next preinclude file onto the input stack. next_preinclude_file is initially set to the list of macro-only include files. When we reach the end of the macro-only preinclude list, we process the normal (non-macro-only) preincludes. */ { a_const_char *file_name; if (next_preinclude_file == NULL && processing_macro_preincludes) { /* The macro-only preinclude list has been completed; switch to the normal preincludes. */ next_preinclude_file = preinclude_file_list; if (cppcx_enabled) { /* Skip over the force include generated for vccorlib.h. */ check_assertion(next_preinclude_file != NULL && strcmp(next_preinclude_file->file_name, "vccorlib.h") == 0); next_preinclude_file = next_preinclude_file->next; } /* if */ processing_macro_preincludes = FALSE; } /* if */ if (next_preinclude_file != NULL) { a_boolean include_was_suppressed; file_name = next_preinclude_file->file_name; if (put_dir_of_each_opened_source_file_on_incl_search_path && compare_dir_names(dir_name_of_primary_source_file, get_working_directory(), /*is_partial_file_name=*/FALSE) != 0 && !microsoft_mode) { /* Update the first entry of the include file search list that contains the directory of the primary source file to refer to the current directory instead. This is not done in Microsoft mode, because the Microsoft compiler uses the directory of the primary source file even when processing preinclude files. This is only done if the current directory is different than the directory of the primary source file. */ change_primary_include_search_dir(get_working_directory()); } /* if */ open_file_and_push_input_stack( strcpy(alloc_primary_file_scope_il( (sizeof_t)(strlen(file_name)+1)), file_name), /*use_search_path=*/TRUE, /*is_include_file=*/TRUE, /*is_system_include=*/FALSE, /*is_preinclude=*/TRUE, /*is_macro_preinclude=*/processing_macro_preincludes, /*is_implicit_include=*/FALSE, /*is_include_next=*/FALSE, /*continue_on_open_failure=*/FALSE, &include_was_suppressed); next_preinclude_file = next_preinclude_file->next; /* Normally the next preinclude file is pushed when the previous one is closed, but if the include was suppressed, push it now. */ if (include_was_suppressed) push_next_preinclude_file(); } else if (preinclude_file_list != NULL || macro_preinclude_file_list != NULL) { if (put_dir_of_each_opened_source_file_on_incl_search_path && !microsoft_mode) { /* We're done processing preinclude files, and there was at least one preinclude file. Reset the search path to refer to the primary source file's directory. */ change_primary_include_search_dir(dir_name_of_primary_source_file); } /* if */ } /* if */ } /* push_next_preinclude_file */ static void display_included_file_name(int depth, a_const_char *file_name) /* When using the option to list the included files, this routine is called to actually output the include file name. depth is the stack depth to be used for indentation purposes. file_name is the name of the file to be displayed. */ { /* Indent the output by the input stack depth. */ int indent = depth - 1; check_assertion(indent >= 0); fprintf(f_error, "%*s%s\n", indent, "", format_file_name(file_name)); } /* display_included_file_name */ void open_file_and_push_input_stack(a_const_char *file_name, a_boolean use_search_path, a_boolean is_include_file, a_boolean is_system_include, a_boolean is_preinclude, a_boolean preinclude_macros, a_boolean is_implicit_include, a_boolean is_include_next, a_boolean continue_on_open_failure, a_boolean *include_was_suppressed) /* Push the indicated file onto the input stack, so that the next time a line is read, it will come from that file. If the file cannot be opened, generate a catastrophic error and do not return, unless continue_on_open_error is TRUE, in which case a discretionary error is issued and processing continues. use_search_path is TRUE if the search path of include directories should be used when trying the open. file_name must be allocated in IL storage. is_include_file is TRUE if the file is being read as the result of a #include directive or a --preinclude command-line-option. It is FALSE for implicitly included files. is_system_include is TRUE for files included with the #include notation and FALSE for all other files. is_preinclude is TRUE for files included via the preinclude or preinclude_macros command-line options (preinclude_macros specifies which). is_implicit_include is TRUE for files included for template implicit inclusion. is_include_next is TRUE if the file is being pushed for an #include_next directive. If *include_was_suppressed is not NULL it is set to TRUE if file was not included because it had been previously included and this include was suppressed, FALSE otherwise. */ { a_const_char *full_file_name; a_const_char *display_name; FILE *input_file; an_include_file_history_ptr ifhp = NULL; a_directory_name_entry_ptr dir_entry; a_boolean file_found; a_boolean suppress_include = FALSE; a_unicode_source_kind unicode_source_kind; db_enter(2, "open_file_and_push_input_stack"); if (include_was_suppressed != NULL) *include_was_suppressed = FALSE; /* coverity[alloc_arg] */ file_found = open_file_for_input(file_name, use_search_path, is_include_file, is_system_include, is_include_next, /*is_implicit_include=*/FALSE, is_preinclude, /*is_embed=*/FALSE, continue_on_open_failure, &full_file_name, &display_name, &input_file, &suppress_include, &unicode_source_kind, &dir_entry); check_assertion(file_found || continue_on_open_failure); if (!file_found) { check_assertion(input_file == NULL); /* For Coverity. */ goto done; } /* if */ if (suppress_include || (is_include_file && suppress_subsequent_include_of_file(full_file_name, &ifhp, /*create=*/TRUE, /*use_canonical=*/TRUE))) { /* This file contains include guard code. An inclusion here would have no effect, so it should be suppressed. When suppress_include is TRUE, the file was not actually opened. In most cases the redundant inclusion is detected during the search process, but in some cases it needs to be checked here too. */ if (!suppress_include) (void)fclose(input_file); if (include_was_suppressed != NULL) *include_was_suppressed = TRUE; #if DEBUG if (debug_level >= 4) { fprintf(f_debug, "open_file_and_push_input_stack: skipping guarded include file %s\n", file_name); } /* if */ #endif /* DEBUG */ /* If generating a list of include files (-H option), put out the file name. */ if (list_included_files) { display_included_file_name(depth_input_stack + 1, display_name); } /* if */ actual_include_was_suppressed = TRUE; goto done; } /* if */ push_input_stack(input_file, file_name, display_name, full_file_name, is_include_file, is_system_include, is_preinclude, preinclude_macros, is_implicit_include, unicode_source_kind, dir_entry, ifhp); done: db_exit(); } /* open_file_and_push_input_stack */ static FILE *try_to_open_source_file( a_const_char *name_to_try, an_open_file_result *open_result, a_unicode_source_kind *unicode_source_kind) /* Try to open the source file specified by name_to_try. *open_result stores information about why the file could not be opened if the open fails. *unicode_source_kind is set to indicate the Unicode encoding form for the file, or usk_none if the file is not Unicode. Return the file pointer if the open succeeds, or NULL otherwise. */ { FILE *new_input_file; /* Open the specified file. The flags specify the kinds of errors that cause the routine to return a NULL file pointer. If a failure type is not listed here, the open routine will issue a catastrophic error for that kind of failure. */ new_input_file = open_source_file_with_error_handling( name_to_try, OFF_OKAY_IF_NOT_FOUND | OFF_OKAY_IF_DIRECTORY | OFF_OKAY_IF_NOT_REGULAR | OFF_OKAY_IF_CANNOT_OPEN, open_result, unicode_source_kind); return new_input_file; } /* try_to_open_source_file */ static a_boolean try_to_open_source_file_if_not_already_included( a_const_char *name_to_try, FILE **new_input_file, a_boolean *suppress_include, an_open_file_result *open_result, a_unicode_source_kind *unicode_source_kind) /* Try to open the source file specified by name_to_try. Before attempting to open the file, check whether an inclusion of the file should be suppressed because the file has already been included. Return TRUE if the file was found (the file was either opened or a previously included file was found). If the file was opened, the file pointer is returned in new_input_file. If the include is to be suppressed because the file was already included, TRUE is returned in suppress_include. *unicode_source_kind is set to indicate the Unicode encoding form for the file, or usk_none if the file is not Unicode. *open_result stores information about why the file could not be opened if the open fails. */ { an_include_file_history_ptr ifhp = NULL; a_boolean found = FALSE; *suppress_include = FALSE; *new_input_file = NULL; *unicode_source_kind = usk_none; if (suppress_subsequent_include_of_file(name_to_try, &ifhp, /*create=*/FALSE, /*use_canonical=*/FALSE)) { /* This include should be suppressed. No further action is needed. */ *suppress_include = TRUE; found = TRUE; } else { /* It was not previously included. Attempt to open the file. */ *new_input_file = try_to_open_source_file(name_to_try, open_result, unicode_source_kind); found = *new_input_file != NULL; } /* if */ return found; } /* try_to_open_source_file_if_not_already_included */ /* Entry used by the include_search_hash_table to record information about attempted include searches. */ typedef struct an_include_search_result *an_include_search_result_ptr; typedef struct an_include_search_result { a_const_char *current_directory; /* The current directory when the search was done. */ a_const_char *dir_name; /* The name of the directory in which the search was done. */ a_const_char *file_name; /* The name of the file sought in the directory. */ a_const_char *result_file; /* The result of the search. If a file was found, this is the file name (including the directory as specified in the "directory" field and any file suffix that may have been added). NULL if no file was found. */ } an_include_search_result; static void clear_include_search_result(an_include_search_result_ptr isrp) /* Initialize the fields of an include search result entry. */ { isrp->current_directory = NULL; isrp->dir_name = NULL; isrp->file_name = NULL; isrp->result_file = NULL; } /* clear_include_search_result */ static an_include_search_result_ptr alloc_include_search_result(void) /* Allocate a new include search result entry, initialize its fields, and return a pointer to it. */ { an_include_search_result_ptr isrp; isrp = alloc_general_of_type(an_include_search_result); #if DEBUG num_include_search_results_allocated++; #endif /* DEBUG */ clear_include_search_result(isrp); return isrp; } /* alloc_include_search_result */ a_hash_value hash_include_search_result(a_void_ptr key) /* Produce a hash value for an include search result entry. The key is an_include_search_result_ptr. */ { a_hash_value value = 0; an_include_search_result_ptr isrp; isrp = (an_include_search_result_ptr)key; value = hash_source_string((a_void_ptr)isrp->dir_name) + hash_source_string((a_void_ptr)isrp->file_name); return value; } /* hash_include_search_result */ a_boolean compare_include_search_result(a_void_ptr entry, a_void_ptr key) /* Compare an entry in the include search result hash table with an entry to be found. "entry" and "key" are of type an_include_search_result_ptr. Return TRUE if the key matches the entry. */ { an_include_search_result_ptr entry_isrp; an_include_search_result_ptr key_isrp; a_boolean result; entry_isrp = (an_include_search_result_ptr)entry; key_isrp = (an_include_search_result_ptr)key; result = (entry_isrp->current_directory == key_isrp->current_directory || strcmp(entry_isrp->current_directory, key_isrp->current_directory) == 0) && (entry_isrp->dir_name == key_isrp->dir_name || strcmp(entry_isrp->dir_name, key_isrp->dir_name) == 0) && (entry_isrp->file_name == key_isrp->file_name || strcmp(entry_isrp->file_name, key_isrp->file_name) == 0); return result; } /* compare_include_search_result */ static an_include_search_result_ptr find_or_create_include_search_result( a_const_char *dir_name, a_const_char *file_name, a_boolean *is_new_entry) /* Look for a record of a previous search for this directory name / file name combination. This returns a pointer to the include search result entry found, or a newly created one if no previous one exists. is_new_entry is set to indicate whether or not the returned entry is a newly created one. */ { an_include_search_result isr; an_include_search_result_ptr *isrp_in_table; an_include_search_result_ptr isrp; *is_new_entry = FALSE; /* Create an include search entry that describes the entry to be found. This is the key used for the hash table lookup. */ clear_include_search_result(&isr); isr.current_directory = get_working_directory(); isr.dir_name = dir_name; isr.file_name = file_name; isrp_in_table = (an_include_search_result_ptr*)hash_find( include_search_hash_table, (a_void_ptr)&isr, /*create=*/TRUE); isrp = *isrp_in_table; #if DEBUG if (db_flag_is_set("ssiof")) { fprintf(f_debug, "find_or_create...: dir=%s file=%s: %s\n", dir_name, file_name, isrp == NULL ? "not found" : "found"); } /* if */ #endif /* DEBUG */ if (isrp == NULL) { /* No previous result was found. Create the entry to be referenced by the hash table. */ isrp = alloc_include_search_result(); *isrp_in_table = isrp; /* Copy the key entry created above into the new entry. */ *isrp = isr; isrp->current_directory = new_copy_of_string(isr.current_directory, General_allocator()); isrp->file_name = new_copy_of_string(file_name, General_allocator()); *is_new_entry = TRUE; } /* if */ return isrp; } /* find_or_create_include_search_result */ static a_boolean search_for_input_file( a_const_char *file_name, a_boolean use_search_path, a_directory_name_entry_ptr search_path, a_file_suffix_ptr suffix_list, a_boolean is_implicit_include, a_boolean is_system_include, a_boolean is_preinclude, a_const_char **name_found, FILE **new_input_file, a_boolean *suppress_include, an_open_file_result *open_result, a_unicode_source_kind *unicode_source_kind, a_directory_name_entry_ptr *dir_entry, a_boolean suppress_diagnostics) /* Look for file_name in the list of directories specified by search path. is_implicit_include is TRUE when searching for a source file for implicit inclusion. When it is TRUE, the file suffix of file_name is replaced with each entry in suffix_list for each directory in search_path. When is_implicit_include is FALSE a suffix_list may still be specified, in which case the suffix list is only used if file_name has no suffix. This is used to supply a default suffix for headers specified without a suffix. The path name of the file found is returned in name_found. *dir_entry is set to point to the directory name entry on the search path in which the file was found, or NULL if the search path was not used. is_system_include is TRUE if the included file name was specified in <...>. is_preinclude is TRUE for files included via the preinclude or preinclude_macros command-line options. Return TRUE if the file was found (the file was either opened or a previously included file was found). If the file was opened, the file pointer is returned in new_input_file. If the include is to be suppressed because the file was already included, TRUE is returned in suppress_include. *unicode_source_kind is set to indicate the Unicode encoding form for the file, or usk_none if the file is not Unicode. *open_result stores information about why the file could not be opened if the open fails. Unless suppress_diagnostics is TRUE, a catastrophe is reported if the file is not found, use_search_path is TRUE, and search_path is empty. */ { a_file_suffix_ptr fsp; a_directory_name_entry_ptr curr_directory_name_entry; a_const_char *name_to_try = ""; a_boolean file_found = FALSE; a_const_char *prev_dir_name = NULL; a_text_buffer_ptr buffer = NULL; a_boolean replace_suffix; an_include_search_result_ptr isrp = NULL; a_const_char *suffix; a_boolean special_sun_include = FALSE; *dir_entry = NULL; *new_input_file = NULL; *suppress_include = FALSE; *unicode_source_kind = usk_none; /* Determine whether we need to do the suffix replacement processing. This is done when is_implicit_include is TRUE or when the file name supplied has no suffix. */ suffix = suffix_of(file_name); replace_suffix = is_implicit_include || *suffix == '\0'; if (!replace_suffix) { /* In Sun mode, includes that use the <...> syntax search for the specified file, and also the file with a special suffix. This code handles the case where the file has a .h suffix. The additional search is done by using a special include file suffix list, and employing that list for .h files (normally a suffix list is only used for unsuffixed files). The special Sun mode processing for unsuffixed files is done by adding an extra entry to the normal include file suffix list during initialization. */ if (sun_mode && is_system_include && strcmp(suffix, ".h") == 0) { special_sun_include = TRUE; replace_suffix = TRUE; suffix_list = sun_include_file_suffix_list; } /* if */ } /* if */ if (!use_search_path || is_preinclude || is_absolute_file_name(file_name)) { /* File name is absolute, so search path is not used. This is also done for preinclude files so that the file will be opened relative to the current directory. */ name_to_try = file_name; *new_input_file = try_to_open_source_file(name_to_try, open_result, unicode_source_kind); file_found = *new_input_file != NULL; /* If the file is not found, continue looking for a preinclude file using the search path. */ use_search_path = use_search_path && is_preinclude; } /* if */ if (file_found || !use_search_path) { /* Don't search any further if the file was found or if we should not use the search path. */ } else if (search_path == NULL && !suppress_diagnostics) { /* No search path, so file can't be found. Issue a catastrophic error. Use special message to make it clearer, since problem may be that there are no -I options on the command line. */ str_catastrophe(ec_empty_include_search_path, file_name); } else { /* Loop through the directory name entries. */ a_const_char *dir_name; for (curr_directory_name_entry = search_path; curr_directory_name_entry != NULL; curr_directory_name_entry = curr_directory_name_entry->next) { if (curr_directory_name_entry->dir_name == prev_dir_name) { /* Two directories with the same name are adjacent in the stack. No need to try to open the same file a second time. */ continue; } /* if */ dir_name = curr_directory_name_entry->dir_name; prev_dir_name = dir_name; isrp = NULL; if (!is_implicit_include && !special_sun_include) { /* See if we have searched for this file before. This is not done when looking for implicit include files because the suffix list used is different in that case. It is also not done for the special Sun include processing because the suffix replacement is not done for all includes (only for the <...> form). */ a_boolean is_new_entry; isrp = find_or_create_include_search_result(dir_name, file_name, &is_new_entry); /* If no result file was found, move to the next entry in the search path. The result_file will be NULL in the case where no previous result was found. Don't move to the next search path entry if we are creating a new entry. */ if (isrp->result_file == NULL) { if (!is_new_entry) continue; } else { /* Attempt to open the file from the previous search. */ name_to_try = isrp->result_file; file_found = try_to_open_source_file_if_not_already_included( name_to_try, new_input_file, suppress_include, open_result, unicode_source_kind); } /* if */ } /* if */ if (!file_found) { /* No file was found in the previous search results, do a search now. */ /* We need to traverse the search path. Merge the current entry in the path with the file name and use that name as the base for replacing the suffixes. */ if (microsoft_mode && microsoft_version >= 1300 && *dir_name == '\0') { /* The Microsoft compiler uses a full path name when looking in the current directory for a file. */ dir_name = get_working_directory(); } /* if */ buffer = combine_dir_and_file_name( dir_name, file_name, (a_text_buffer_ptr)NULL); name_to_try = buffer->buffer; /* Now try to open the modified file. */ if (!replace_suffix) { /* We don't need to replace the suffix. Just try the file/directory combination just constructed. */ file_found = try_to_open_source_file_if_not_already_included( name_to_try, new_input_file, suppress_include, open_result, unicode_source_kind); } else { /* We need to replace the suffix. Go through the list of suffixes. */ if (suffix_replacement_buffer == NULL) { /* Allocate the suffix buffer, it not already allocated. */ suffix_replacement_buffer = alloc_text_buffer(128); } /* if */ /* Save a copy the file name. */ reset_text_buffer(suffix_replacement_buffer); add_to_text_buffer(suffix_replacement_buffer, buffer->buffer, buffer->size); /* Loop through the linked list of suffixes. */ for (fsp = suffix_list; fsp != NULL; fsp = fsp->next) { /* Replace the existing suffix with a new one. */ replace_file_name_suffix(fsp->suffix, buffer); /* Get the current buffer pointer in case it was reallocated. */ name_to_try = buffer->buffer; /* Now try to open the modified file. */ file_found = try_to_open_source_file_if_not_already_included( name_to_try, new_input_file, suppress_include, open_result, unicode_source_kind); if (file_found) break; if (fsp->next != NULL) { /* Copy the original file name back into the buffer. */ reset_text_buffer(buffer); add_to_text_buffer(buffer, suffix_replacement_buffer->buffer, suffix_replacement_buffer->size); } /* if */ } /* for */ } /* if */ } /* if */ if (file_found) { *dir_entry = curr_directory_name_entry; break; } /* if */ } /* for */ } /* if */ if (file_found) { /* If a file was found, return the name in name_found. If the name is currently in the temporary buffer, or if the name is from a stored search result, make a copy and return a pointer to the copy. */ if ((buffer != NULL && name_to_try == buffer->buffer) || (isrp != NULL && name_to_try == isrp->result_file)) { a_const_char *src_name; sizeof_t src_len; if (buffer != NULL && name_to_try == buffer->buffer) { src_name = buffer->buffer; src_len = buffer->size; } else { src_name = isrp->result_file; src_len = strlen(src_name) + 1; } /* if */ name_to_try = alloc_primary_file_scope_il((sizeof_t)src_len); (void)strcpy((char *)name_to_try, src_name); } /* if */ *name_found = name_to_try; if (isrp != NULL && isrp->result_file == NULL) { /* Record the name found in the include search result entry. */ isrp->result_file = (char*)alloc_general((sizeof_t)strlen(name_to_try) + 1); (void)strcpy((char *)isrp->result_file, name_to_try); } /* if */ } else { /* A file was not found. Reset the name_found to make sure it is not used by the caller. */ *name_found = NULL; } /* if */ return file_found; } /* search_for_input_file */ a_const_char *resolve_header(a_const_char *filename, a_boolean is_system_include, a_boolean is_include_next, a_boolean is_embed, a_boolean suppress_diagnostics) /* Determine if filename can be opened as a header or #embed file, depending on the value of is_embed. Return the resolved path to the file, or NULL if it could not be opened. In non-embed cases, if is_include_next is TRUE, the search path is the remainder of the list by which the current file was found; otherwise, the search path is the one appropriate to the value of is_system_include. If suppress_diagnostics is TRUE, suppress diagnostics related to the failure to find the header file. */ { a_boolean found; a_const_char *result; FILE *fp; a_boolean suppress_include; an_open_file_result open_result; a_unicode_source_kind unicode_source_kind; a_directory_name_entry_ptr search_path; a_directory_name_entry_ptr dir_entry; if (is_embed) { search_path = embed_search_path; } else if (is_include_next && curr_ise->dir_entry != NULL) { /* If this is for __has_include_next and the current file was found using a search path, search for the named file using that path, beginning with the directory entry following that of the current file. If the current file was not found using a search path (i.e., it was specified with an absolute path), ignore is_include_next. */ search_path = curr_ise->dir_entry->next; } else { /* Use the full search path selected by is_system_include. */ search_path = is_system_include ? sys_incl_search_path : incl_search_path; } /* if */ found = search_for_input_file(filename, /*use_search_path=*/TRUE, search_path, include_file_suffix_list, /*is_implicit_include=*/FALSE, is_system_include, /*is_preinclude=*/FALSE, &result, &fp, &suppress_include, &open_result, &unicode_source_kind, &dir_entry, suppress_diagnostics); if (fp != NULL) { /* If the file was opened, close it. */ fclose(fp); } /* if */ if (!found) { result = NULL; } /* if */ return result; } /* resolve_header */ a_const_char *resolve_header_in_map(a_const_char *filename, a_const_char *resolved_header, a_boolean is_system_include) /* Determine whether an entry for the header named by filename exists in a header map. Return the resolved path to the module file, or NULL if no mapping exists. The header named by filename has already been resolved, and the resolution is provided in resolved_header. The process of resolving the module file may involve looking up previously unresolved headers in the map to see if they match resolved_header. The search path (and map used) for these headers is the one appropriate to the value of is_system_include. */ { a_path_handle header_file{resolved_header}; a_const_char *module_path; module_path = header_unit_map->get(header_file); if (module_path == NULL) { a_header_unit_map *unresolved_map = is_system_include ? header_unit_angle_map : header_unit_quote_map; for (const auto& entry : *unresolved_map) { a_const_char *resolved_entry; if (!entry.has_value()) continue; resolved_entry = resolve_header(entry.key().ptr, is_system_include, /*is_include_next=*/FALSE, /*is_embed=*/FALSE, /*suppress_diagnostics=*/TRUE); if (header_file == resolved_entry) { if (module_path != NULL) { /* A duplicate entry. Issue a diagnostic and otherwise ignore. */ pos_st_error(ec_multiple_header_map_matches, &error_position, filename); } else { /* The header matches the resolved entry. Add the resolved path to the header unit map for easier future resolution. */ a_path_handle new_header_file{resolved_entry}; /* We should not reach this point if the entry already exists in the header unit map. */ check_assertion(header_unit_map->get(new_header_file) == NULL); module_path = entry.value(); header_unit_map->map(new_header_file, module_path); /* In Microsoft compatibility mode, continue the search to check for duplicate matches. */ if (!microsoft_mode) break; } /* if */ } /* if */ } /* for */ } /* if */ return module_path; } /* resolve_header_in_map */ a_boolean header_can_be_found(a_const_char *filename, a_boolean is_system_include, a_boolean is_include_next) /* Return TRUE if filename can be opened as a header file. If is_include_next is TRUE, the search path is the remainder of the list by which the current file was found; otherwise, the search path is the one appropriate to the value of is_system_include. */ { a_const_char *file_path; file_path = resolve_header(filename, is_system_include, is_include_next, /*is_embed=*/FALSE, /*suppress_diagnostics=*/TRUE); return (file_path != NULL); } /* header_can_be_found */ a_boolean open_file_for_input( a_const_char *file_name, a_boolean use_search_path, a_boolean is_include_file, a_boolean is_system_include, a_boolean is_include_next, ARG_UNUSED a_boolean is_implicit_include, a_boolean is_preinclude, a_boolean is_embed, a_boolean continue_on_open_failure, a_const_char **full_file_name, a_const_char **display_name, FILE **new_input_file, a_boolean *suppress_include, a_unicode_source_kind *unicode_source_kind, a_directory_name_entry_ptr *dir_entry) /* Try to open file_name, and return TRUE if the file was found (the file was either opened or a previously included file was found). If the file was opened, the file pointer is returned in new_input_file. If the include is to be suppressed because the file was already included, TRUE is returned in suppress_include. file_name must be allocated in IL storage. use_search_path is TRUE if the search path of include/embed directories should be used when trying the open. is_system_include is TRUE if the included file name was specified in <...>. If the open is successful, the full name of the file that is opened is returned in *full_file_name, the name intended for use in diagnostics and other output is returned in *display_name. *dir_entry is set to point to the entry on the search path in which the file was found, or NULL if the search path was not used. is_include_next is TRUE if the file is being opened for an #include_next directive. is_implicit_include is TRUE when this routine is used to search for an implicitly included template definition file. When is_implicit_include is used, each suffix in the implicit_instantiation_file_suffix_list is used to search for a template definition file. is_preinclude is TRUE for files included via the preinclude or preinclude_macros command-line options. is_embed is TRUE if the file being opened is for a #embed directive. *unicode_source_kind is set to indicate the Unicode encoding form for the file, or usk_none if the file is not Unicode. When is_implicit_include is FALSE, a catastrophic error is normally issued if a file cannot be opened. But if continue_on_open_failure is TRUE, a discretionary error is issued instead. continue_on_open_failure can only be TRUE when doing preprocessing only. If a file cannot be opened, and a catastrophic error is not issued, FALSE is returned. */ { a_const_char *temp_file_name; a_directory_name_entry_ptr search_path; a_boolean file_found = FALSE; a_boolean input_from_stdin = FALSE; an_open_file_result open_result; db_enter(2, "open_file_for_input"); *dir_entry = NULL; *unicode_source_kind = usk_none; search_path = NULL; clear_open_file_result(&open_result); if (use_search_path) { /* Determine the list of directories to be searched when opening the file. */ if (is_embed) { search_path = embed_search_path; } else if (is_include_next && curr_ise->dir_entry != NULL) { /* For #include_next, start at the search path entry after the one in which the current file was found. If the current file was not found via a search path (i.e., the name was specified using an absolute path name) treat the include_next as a normal include. */ search_path = curr_ise->dir_entry; if (search_path != NULL) search_path = search_path->next; } else if (is_system_include) { search_path = sys_incl_search_path; } else { search_path = incl_search_path; } /* if */ } /* if */ *new_input_file = NULL; *suppress_include = FALSE; *full_file_name = NULL; check_assertion((curr_ise == NULL) == (depth_input_stack == -1)); /* Open the new file. */ if (curr_ise == NULL && strcmp(file_name, FILE_NAME_FOR_STDIN) == 0) { /* Special code for stdin; no open needed. */ temp_file_name = file_name; *new_input_file = stdin; input_from_stdin = TRUE; file_found = TRUE; #if INSTANTIATION_BY_IMPLICIT_INCLUSION } else if (is_implicit_include) { file_found = search_for_input_file(file_name, use_search_path, search_path, implicit_instantiation_file_suffix_list, is_implicit_include, is_system_include, is_preinclude, &temp_file_name, new_input_file, suppress_include, &open_result, unicode_source_kind, dir_entry, /*suppress_diagnostics=*/FALSE); #endif /* INSTANTIATION_BY_IMPLICIT_INCLUSION */ } else { file_found = search_for_input_file(file_name, use_search_path, search_path, include_file_suffix_list, /*is_implicit_include=*/FALSE, is_system_include, is_preinclude, &temp_file_name, new_input_file, suppress_include, &open_result, unicode_source_kind, dir_entry, /*suppress_diagnostics=*/FALSE); if (!file_found) { /* The file could not be opened. This is normally a catastrophic error unless continue_on_open_failure is TRUE. */ if (continue_on_open_failure) { file_open_error(es_discretionary_error, ec_source, file_name, &open_result); } else { file_open_error(is_preinclude ? es_command_line_error : es_catastrophe, is_embed ? ec_embed : ec_source, file_name, &open_result); } /* if */ } /* if */ } /* if */ /* If a file was found, update the full file name and display names. */ if (file_found) { /* The display_name is either the file passed in (for a primary source file) or the name returned by search_for_input_file. */ *display_name = temp_file_name; /* The full_file_name is usually the same as the display_name, but in Microsoft mode (for some Microsoft versions) the full_file_name for primary source files has the current directory included. This is done to facilitate the creating of debug information that is similar to that generated by the Microsoft compiler. */ if (microsoft_mode && !is_include_file && !input_from_stdin && microsoft_version >= 1300 && !is_absolute_file_name(temp_file_name)) { a_text_buffer_ptr buffer; char *temp; /* Create a path name from the current directory and the file name specified on the command line. */ buffer = combine_dir_and_file_name( get_working_directory(), file_name, (a_text_buffer_ptr)NULL); /* Eliminate any relative components of the path name. */ temp = normalize_file_name(buffer->buffer); /* Copy the path name to IL memory. */ *full_file_name = alloc_primary_file_scope_il((sizeof_t)(strlen(temp) + 1)); (void)strcpy((char *)*full_file_name, temp); } else { *full_file_name = temp_file_name; } /* if */ } /* if */ db_exit(); return file_found; } /* open_file_for_input */ static int look_for_file_on_input_stack(a_const_char *file_name) /* Look for the file specified by file_name in the input stack and return the number of times that the file appears there. */ { int times_name_appears; int isnum; times_name_appears = 0; for (isnum = depth_input_stack; isnum >= 0; isnum--) { if (compare_file_names(input_stack[isnum].full_name, file_name) == 0) { times_name_appears++; } /* if */ } /* for */ return times_name_appears; } /* look_for_file_on_input_stack */ void push_input_stack( FILE *new_input_file, a_const_char *name_as_written, a_const_char *display_name, a_const_char *full_file_name, a_boolean is_include_file, a_boolean is_system_include, a_boolean is_preinclude, a_boolean preinclude_macros, a_boolean is_implicit_include, a_unicode_source_kind unicode_source_kind, a_directory_name_entry_ptr dir_entry, an_include_file_history_ptr ifhp) /* Push the indicated file onto the input stack. name_as_written, display_name, and full_file_name are various forms of the file name. is_include_file is TRUE if the file is being read as the result of a #include directive or a --preinclude command-line-option. It is FALSE for implicitly included files. is_system_include is TRUE for files included with the #include notation and FALSE for all other files. is_preinclude is TRUE for files included via the preinclude or preinclude_macros command-line options (preinclude_macros specifies which). is_implicit_include is TRUE for files included for template implicit inclusion. unicode_source_kind indicates the kind of Unicode encoding used by the source file, for configurations with UNICODE_SOURCE_SUPPORTED set to TRUE. dir_entry points to the entry on the search path that was used to find this file. */ { int times_name_appears; a_source_file_ptr parent_file; an_input_stack_entry_ptr prev_ise; a_boolean from_system_include_dir = FALSE; db_enter(2, "push_input_stack"); #if DEBUG if (debug_level >= 2) { fprintf(f_debug, "file_name = %s\n", full_file_name); } /* if */ #endif /* DEBUG */ /* Check for recursion of #includes. This is done by looking through the stack for the file name we just opened. */ times_name_appears = look_for_file_on_input_stack(full_file_name); /* The entry in the input stack has the same file name as the file we just opened. This is okay once (it has to be), but if it happens several times, it probably means recursion. */ if (times_name_appears >= 10 /* Arbitrary, must be > 1 */) { str_catastrophe(ec_include_recursion, full_file_name); } /* if */ /* If preprocessing output is being generated, force out the previous source line before the input stack information is changed. */ if (generate_pp_output) { gen_pp_output_for_curr_line(curr_source_line); } /* if */ /* If a raw listing file is being generated, force out the previous line before the input stack information is changed. */ if (f_raw_listing != NULL) { gen_raw_listing_output_for_curr_line(); } /* if */ /* Reset the per-line state of whether token separators should be emitted in the generated output. */ no_token_separators_in_this_line_of_pp_output = no_token_separators_in_pp_output; /* Check for the need to expand the input stack. */ if (depth_input_stack+1 == size_input_stack) { /* Expand the input stack by reallocating it. */ int new_size = size_input_stack + INPUT_STACK_INCREMENTAL_ALLOCATION; input_stack = (an_input_stack_entry_ptr)realloc_buffer( (char *)input_stack, (sizeof_t)size_input_stack * sizeof(an_input_stack_entry), (sizeof_t)new_size * sizeof(an_input_stack_entry)); size_input_stack = new_size; if (depth_input_stack >= 0) { curr_ise = &input_stack[depth_input_stack]; base_ise = curr_ise; } /* if */ } /* if */ /* If the maximum number of files has already been opened, close the top-most file in the input stack after remembering its current position for later re-opening. This prevents hogging of system resources, and works efficiently for moderate nesting depths. */ if (depth_input_stack >= MAX_INCLUDE_FILES_OPEN_AT_ONCE) { curr_ise->position = ftell(curr_ise->file); (void)fclose(curr_ise->file); curr_ise->file = NULL; } /* if */ prev_ise = curr_ise; if (is_include_file) { /* This file is from a system include directory if it was included by a system header, or if it has a directory entry and that directory is considered to be a system include directory. */ if (prev_ise->from_system_include_dir || (dir_entry != NULL && dir_entry->system_include_dir)) { from_system_include_dir = TRUE; } /* if */ ++include_file_depth; } /* if */ /* Push the new input stack entry. */ curr_ise = &input_stack[++depth_input_stack]; base_ise = curr_ise; curr_ise->file = new_input_file; curr_ise->line_number = 0; curr_ise->position = 0; curr_ise->actual_line = 0; /* Update other variables describing the current state. */ eof_read_on_curr_input_stream = FALSE; curr_input_stream = curr_ise->file; /* Save the "display" form of the name and the full name. */ curr_ise->full_name = full_file_name; curr_ise->file_name = display_name; curr_ise->dir_name = gs_directory_of(full_file_name); curr_ise->dir_entry = dir_entry; curr_ise->is_include_file = is_include_file; curr_ise->from_system_include_dir = from_system_include_dir; curr_ise->nested_inclusion = (times_name_appears != 0); curr_ise->include_history = ifhp; curr_ise->ifg_state = IFG_STATE_START; curr_ise->saved_any_tokens_fetched = any_tokens_fetched_from_curr_input_file; curr_ise->is_preinclude = is_preinclude; curr_ise->do_not_advance_past_end_of_file = preinclude_macros; curr_ise->unicode_source_kind = unicode_source_kind; #if UNICODE_SOURCE_SUPPORTED curr_file_unicode_source_kind = unicode_source_kind; clear_getc_source_state(&curr_file_getc_source_state, unicode_source_kind); if (curr_file_unicode_source_kind != usk_none) { /* Scanning UTF-8 requires that multibyte support be turned on. Once we see a file containing Unicode characters, we never turn the flag back off again. */ multibyte_chars_in_source_enabled = TRUE; } /* if */ #endif /* UNICODE_SOURCE_SUPPORTED */ any_tokens_fetched_from_curr_input_file = FALSE; #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR curr_ise->prev_line_terminator_was_carriage_return = FALSE; #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ curr_ise->cloned_for_line_directive = FALSE; /* Create an intermediate file record describing this file. It is useful later in converting sequence numbers into file name/line information. */ if (depth_input_stack == 0) { if (!is_implicit_include) { parent_file = NULL; } else { /* A file included for template implicit inclusion is put under the primary file. */ parent_file = il_header.primary_source_file; /* after_end_of_all_source was set TRUE when we reached the end of the primary source file. Reset it so that we can continue accepting input from the implicitly included template definition files. */ after_end_of_all_source = FALSE; } /* if */ } else { parent_file = input_stack[depth_input_stack-1].assoc_il_file; } /* if */ if (is_implicit_include) { /* Decrement the sequence number when starting an implicitly included file so that we reuse the old end-of-source position. */ seq_number_last_read--; } /* if */ record_start_of_source_file(parent_file, (a_seq_number)seq_number_last_read+1, (a_line_number)1, display_name, full_file_name, name_as_written, &(curr_ise->assoc_il_file), is_include_file, is_system_include, is_preinclude, preinclude_macros, is_implicit_include, from_system_include_dir, /*is_scanned_from_string=*/FALSE); /* The two il file pointers start out the same. They will be made to point to distinct entries if a #line directive is processed: assoc_il_file will point to the entry for the #line, and assoc_actual_il_file will stay as it is (pointing to the entry for the file actually being read). */ curr_ise->assoc_actual_il_file = curr_ise->assoc_il_file; /* Initialize the source file index used by the diagnostic routines to reread source lines when needed. */ curr_ise->next_index_point = initialize_file_index( curr_ise->assoc_actual_il_file); /* If generating preprocessing output, put out a line-identifying directive for the new file. */ if (generate_pp_output) { /* The entry into the primary source file should not be tagged with "1"; that's the way cpp does it. */ if (depth_input_stack == 0) { gen_pp_line_info(' ', /*next_line=*/TRUE); } else { gen_pp_line_info('1', /*next_line=*/TRUE); } /* if */ } /* if */ /* If generating raw listing output (for input to a program that will generate an interspersed listing), put out a line-information record. */ if (f_raw_listing != NULL) { /* The entry into the primary source file should not be tagged with "1". */ if (depth_input_stack == 0) { gen_rlisting_line_info(' '); } else { gen_rlisting_line_info('1'); } /* if */ } /* if */ /* If generating makefile dependency information (-M option), write a line of the form primaryfile.o: includefile.h Note that the code here is executed also for the primary source file, and we do want that dependency line as well. */ if (list_makefile_dependencies) { /* Note that the output is split over two calls of fprintf because format_file_name returns a pointer to a static text buffer. */ fprintf(f_pp_output, "%s:", format_file_name(object_file_name)); fprintf(f_pp_output, " %s\n", format_file_name(curr_ise->file_name)); /* Force a flush to prevent output ordering inconsistencies. */ (void)fflush(f_pp_output); } /* if */ /* If generating a list of include files (-H option), put out the file name. Do not put out the name of the primary source file. */ if (list_included_files && depth_input_stack != 0) { display_included_file_name(depth_input_stack, curr_ise->file_name); } /* if */ if (!curr_ise->assoc_actual_il_file->top_level_file) { /* Modify the search rules for #include directives found within this source file, so that the directory containing the current include file will be searched first. Note that this is not done for the primary source file. The include search entry for the primary source file is managed by the routines in cmd_line.c. */ push_primary_include_search_dir( curr_ise->dir_name, (a_boolean)curr_ise->from_system_include_dir); } /* if */ if (C_dialect != C_dialect_pcc) { /* If not in pcc mode, keep the base of the preprocessing if stack up to date. Each file's #ifs are kept separate; an #if must be ended in the same file in which it began. */ curr_ise->base_pp_if_stack_depth = base_pp_if_stack_depth = pp_if_stack_depth; } /* if */ #if DEBUG if (db_flag_is_set("incl_search_path")) { fprintf(f_debug, "push_input_stack: search path after pushing %s:\n", full_file_name); db_incl_search_path(); } /* if */ #endif /* DEBUG */ db_exit(); } /* push_input_stack */ void push_cloned_input_stack_entry(void) /* Create a new entry at the top of the input stack with the same content as the previous top of stack entry. This is used for tracking the tree structure of #line directives, where the actual input file does not change. */ { /* Check for the need to expand the input stack. */ if (depth_input_stack + 1 == size_input_stack) { /* Expand the input stack by reallocating it. */ int new_size = size_input_stack + INPUT_STACK_INCREMENTAL_ALLOCATION; sizeof_t base_idx = (sizeof_t)(base_ise - input_stack); input_stack = (an_input_stack_entry_ptr)realloc_buffer( (char *)input_stack, (sizeof_t)size_input_stack * sizeof(an_input_stack_entry), (sizeof_t)new_size * sizeof(an_input_stack_entry)); size_input_stack = new_size; curr_ise = &input_stack[depth_input_stack]; base_ise = &input_stack[base_idx]; } /* if */ /* Push the new entry, copy the contents of the previous top of stack, and identify the new entry as having been cloned. */ curr_ise = &input_stack[++depth_input_stack]; *curr_ise = input_stack[depth_input_stack - 1]; curr_ise->cloned_for_line_directive = TRUE; } /* push_cloned_input_stack_entry */ static a_source_file_ptr find_parent_file_of(a_source_file_ptr root, a_source_file_ptr child) /* Recursively scan the tree of source files rooted in root looking for a file whose last child is child. Return a pointer to that file if found or NULL if it cannot be found. */ { a_source_file_ptr result = NULL; if (root->last_child_file == child) { /* This is the parent. */ result = root; } else { /* Check this file's children. */ a_source_file_ptr sfp; for (sfp = root->first_child_file; result == NULL && sfp != NULL; sfp = sfp->next) { result = find_parent_file_of(sfp, child); } /* for */ } /* if */ return result; } /* if */ a_source_file_ptr clone_current_input_file(a_seq_number seq_number, a_line_number line_number) /* Clone the current input file, setting the clone's first_seq_number and first_line number to seq_number and line_number, respectively; link the clone as the original's sibling; update curr_ise to point to the clone; and return a pointer to the clone. This is used for a #line directive to create a new sequence/line number mapping for the lines following the directive. */ { a_source_file_ptr new_file = alloc_source_file(); a_source_file_ptr old_file = curr_ise->assoc_il_file; a_source_file_ptr parent; #if EXPENSIVE_CHECKING && DEBUG /* Check to make sure we don't attempt to create a sibling from a fully sequenced translation unit. */ check_assertion(!curr_translation_unit->is_fully_sequenced); #endif /* EXPENSIVE_CHECKING && DEBUG */ /* Copy all the data from the cloned input file, updating as necessary. */ *new_file = *old_file; new_file->first_seq_number = seq_number; new_file->last_seq_number = MAX_SEQ_NUMBER; new_file->first_line_number = line_number; new_file->first_child_file = NULL; new_file->last_child_file = NULL; /* Link the clone as the sibling to the current file. */ old_file->next = new_file; if (new_file->next == NULL) { /* Need to find the parent and update its last_child_file. */ if (depth_input_stack > 0) { parent = (curr_ise - 1)->assoc_il_file; } else { parent = il_header.primary_source_file; } /* if */ if (parent->last_child_file != old_file) { a_source_file_ptr sfp; a_boolean top_level = FALSE; /* Need to search the tree for the parent. This should only happen with malformed preprocessor output where the #line directives do not actually reflect a tree resulting from #include directives. */ for (parent = NULL, sfp = il_header.primary_source_file; parent == NULL && !top_level && sfp != NULL; sfp = sfp->next) { if (old_file == sfp) { top_level = TRUE; } else { parent = find_parent_file_of(sfp, old_file); } /* if */ } /* for */ check_assertion(parent != NULL || top_level); } /* if */ if (parent != NULL) { parent->last_child_file = new_file; } /* if */ } /* if */ curr_ise->assoc_il_file = new_file; return new_file; } /* clone_current_input_file */ void pop_input_stack(void) /* Pop the input stack, and correctly prepare for input from the file at the next level down. */ { a_boolean is_end_of_primary_source_file = TRUE; a_byte ifg_state; a_boolean is_end_of_preinclude = curr_ise->is_preinclude; db_enter(2, "pop_input_stack"); #if DEBUG if (debug_level >= 4) { db_include_guard_info(); } /* if */ #endif /* DEBUG */ /* First, discard any entries that were cloned for the purpose of tracking #line directives; the processing below applies to the actual source file. */ while (curr_ise->cloned_for_line_directive) { record_end_of_source_file(curr_ise->assoc_il_file, seq_number_last_read); pop_cloned_input_stack_entry(); } /* while */ /* This is where we do a final check to see if subsequent inclusions of the file can potentially be suppressed. If we are in the "accept" or "once" state, then this file satisfies the criteria, otherwise it does not. Unless the file contained an explicit #pragma once, the final determination of whether an actual subsequent include of this file can be suppressed can only be determined at the point of the include because the controlling macro must be tested at that point. */ ifg_state = get_ifg_state(); if (ifg_state != IFG_STATE_ACCEPT && ifg_state != IFG_STATE_ONCE && ifg_state != IFG_STATE_START) { /* Not a candidate for include suppression. */ } else { /* This file does meet the criteria for suppression of subsequent includes. */ curr_ise->include_history->suppress_subsequent_include = TRUE; } /* Restore the previous value of the any_tokens_fetched flag. */ any_tokens_fetched_from_curr_input_file = curr_ise->saved_any_tokens_fetched; /* Remember the final sequence number in the file, for sequence number mapping purposes. */ record_end_of_source_file(curr_ise->assoc_actual_il_file, seq_number_last_read); /* If a #line is in effect, also enter the final sequence number in the entry for that. */ if (curr_ise->assoc_actual_il_file != curr_ise->assoc_il_file) { record_end_of_source_file(curr_ise->assoc_il_file, seq_number_last_read); } /* if */ #if INSTANTIATION_BY_IMPLICIT_INCLUSION if (depth_input_stack == 0) { /* The depth of the input stack can be zero for two reasons; we may have reached the end of the primary source file or we may have reached the end of a file implicitly included during template instantiation processing. If this is the end of an implicitly included template definition file then update the last sequence number of the primary source file to include the sequence numbers of the statements read from the template definition file. */ if (!curr_ise->assoc_actual_il_file->top_level_file) { record_end_of_source_file(il_header.primary_source_file, seq_number_last_read); is_end_of_primary_source_file = FALSE; } /* if */ } /* if */ #endif /* INSTANTIATION_BY_IMPLICIT_INCLUSION */ /* Close the current input file. */ (void)fclose(curr_input_stream); curr_ise->file = NULL; eof_read_on_curr_input_stream = FALSE; at_end_of_source_file = FALSE; /* If preprocessing output is being generated, force out the previous source line before the input stack information is changed. */ if (generate_pp_output) { gen_pp_output_for_curr_line(curr_source_line); } /* if */ /* If a raw listing file is being generated, force out the previous line before the input stack information is changed. */ if (f_raw_listing != NULL) { gen_raw_listing_output_for_curr_line(); } /* if */ /* Reset the per-line state of whether token separators should be emitted in the generated output. */ no_token_separators_in_this_line_of_pp_output = no_token_separators_in_pp_output; /* Check that all #ifs were closed. In ANSI, this has to happen at the end of each source file. In pcc, this has to happen only at the end of the whole compilation. */ if (depth_input_stack == 0 || C_dialect != C_dialect_pcc) { verify_that_all_pp_ifs_were_closed(); } /* if */ if (curr_ise->is_preinclude && no_more_preinclude_files()) { /* See if the end of the preinclude marks the end of the text that is part of the precompiled header being generated. */ if (header_stop_position_pending && cmp_source_positions(header_stop_source_position, preinclude_source_position) == 0) { generate_pch_on_return_to_primary_source_file = TRUE; } /* if */ } /* if */ /* Pop the input stack. */ if (curr_ise->is_include_file) { --include_file_depth; } /* if */ /* If the stack is empty, there is no current input file any more. This happens at the end of the primary source file. */ if (--depth_input_stack < 0) { curr_ise = NULL; base_ise = NULL; curr_input_stream = NULL; #if UNICODE_SOURCE_SUPPORTED curr_file_unicode_source_kind = usk_none; clear_getc_source_state(&curr_file_getc_source_state, usk_none); #endif /* UNICODE_SOURCE_SUPPORTED */ if (!is_end_of_primary_source_file) { /* When a top-level implicitly included source file is popped, reset the primary include search directory to the directory of the primary source file. */ pop_primary_include_search_dir(dir_name_of_primary_source_file, /*system_include_dir=*/FALSE); } /* if */ } else { a_unicode_source_kind unicode_source_kind; #if INSTANTIATION_BY_IMPLICIT_INCLUSION an_input_stack_entry_ptr prev_ise = curr_ise; #endif /* INSTANTIATION_BY_IMPLICIT_INCLUSION */ curr_ise = &input_stack[depth_input_stack]; base_ise = curr_ise; if (curr_ise->file == NULL) { an_open_file_result open_result; #if DEBUG if (debug_level >= 2) { fprintf(f_debug, "re-opening at level %d, name = \"%s\", pos = %ld\n", depth_input_stack, curr_ise->full_name, curr_ise->position); } /* if */ #endif /* DEBUG */ /* This include file was closed on a push_input_stack to keep down the number of open files. Re-open it and reposition it now. */ curr_ise->file = open_source_file(curr_ise->full_name, &open_result, &unicode_source_kind); if (curr_ise->file == NULL || unicode_source_kind != curr_ise->unicode_source_kind) { /* File could not be re-opened; it was probably deleted since the compilation started or the Unicode encoding changed since it was last opened. */ file_open_error(es_catastrophe, ec_source, curr_ise->full_name, &open_result); } /* if */ if (fseek(curr_ise->file, curr_ise->position, SEEK_SET) != 0) { /* The seek could not be done. Again, this implies some change in the file since last it was opened. */ file_open_error(es_catastrophe, ec_source, curr_ise->full_name, &open_result); } /* if */ } /* if */ curr_input_stream = curr_ise->file; #if UNICODE_SOURCE_SUPPORTED curr_file_unicode_source_kind = curr_ise->unicode_source_kind; clear_getc_source_state(&curr_file_getc_source_state, curr_file_unicode_source_kind); #endif /* UNICODE_SOURCE_SUPPORTED */ /* Indicate that we are resuming the processing of the specified file. */ record_resumption_of_source_file(curr_ise->assoc_il_file, seq_number_last_read + 1, curr_ise->line_number + 1); /* If generating preprocessing output, put out a line-identifying directive for the new file. */ if (generate_pp_output) { gen_pp_line_info('2', /*next_line=*/TRUE); } /* if */ /* If generating raw listing information (for input to a program that will generate an interspersed listing), put out line information. */ if (f_raw_listing != NULL) { gen_rlisting_line_info('2'); } /* if */ /* Modify the search rules for #include directives found within this source file, so that the directory containing the current include file will be searched first. Normally, we pop back to the directory of the current input stack entry, but when popping a preinclude file we must (in some modes) pop back to the current directory. In Microsoft mode, the directory name in the input stack entry may be updated to be the full path name of the primary source file (as opposed to the directory name portion of the primary source file name as specified on the command line). */ { a_const_char *prev_dir_name = curr_ise->dir_name; if (put_dir_of_each_opened_source_file_on_incl_search_path && stack_referenced_include_directories) { if (is_end_of_preinclude && !microsoft_mode && compare_dir_names(dir_name_of_primary_source_file, get_working_directory(), /*is_partial_file_name=*/FALSE) != 0) { prev_dir_name = get_working_directory(); } else if (microsoft_mode && microsoft_version >= 1300 && curr_ise->assoc_actual_il_file->top_level_file) { prev_dir_name = dir_name_of_primary_source_file; } /* if */ } /* if */ pop_primary_include_search_dir( prev_dir_name, (a_boolean)curr_ise->from_system_include_dir); } if (C_dialect != C_dialect_pcc) { /* If not in pcc mode, keep the base of the preprocessing if stack up to date. Each file's #ifs are kept separate; an #if must be ended in the same file in which it began. */ base_pp_if_stack_depth = curr_ise->base_pp_if_stack_depth; } /* if */ #if INSTANTIATION_BY_IMPLICIT_INCLUSION if (list_makefile_dependencies && do_preprocessing_only && prev_ise->is_include_file && !prev_ise->assoc_actual_il_file->included_by_preinclude && !prev_ise->nested_inclusion && !C_mode() && implicit_template_inclusion_mode) { /* When generating makefile dependency information in C++ mode, and if implicit inclusion is enabled, look for a source file related to the current include file and include it if found. This may include some files that would not be included in a full compilation that uses implicit inclusion because the preprocessor cannot determine whether or not the include file defined any templates and, if so, whether the templates were used in a way that requires the related source file to be read. */ a_const_char *full_file_name; a_const_char *display_name; FILE *f_source = NULL; a_directory_name_entry_ptr dir_entry; a_source_file_ptr sfp = prev_ise->assoc_actual_il_file; a_boolean file_found; a_boolean suppress_include; file_found = open_file_for_input( sfp->name_as_written, /*use_search_path=*/TRUE, /*is_include_file=*/TRUE, (a_boolean)sfp->included_by_system_include, /*is_include_next=*/FALSE, /*is_implicit_include=*/TRUE, /*is_preinclude=*/FALSE, /*is_embed=*/FALSE, /*continue_on_open_failure=*/FALSE, &full_file_name, &display_name, &f_source, &suppress_include, &unicode_source_kind, &dir_entry); if (file_found) { if (suppress_include) { /* The search process detected that the file has already been included, so the file was not opened. */ #if DEBUG if (debug_level >= 3) { fprintf(f_debug, "pop_input_stack: skipping include file %s\n", full_file_name); } /* if */ #endif /* DEBUG */ } else { /* A related source file was found. Make sure that the name of the file found is not the same as the file we started with. This could occur if the user included a .c file that contains a template declaration. */ if (compare_file_names(full_file_name, sfp->full_name) == 0) { (void)fclose(f_source); } else { an_include_file_history_ptr ifhp; #if DEBUG if (debug_level >= 3) { fprintf(f_debug, " Including text from '%s'\n", full_file_name); } /* if */ #endif /* DEBUG */ /* Push the new file onto the input stack and scan it. There is no "name as written" so a NULL pointer is passed in. */ if (suppress_subsequent_include_of_file(full_file_name, &ifhp, /*create=*/TRUE, /*use_canonical=*/TRUE) || (implicit_template_inclusion_mode && look_for_file_on_input_stack(full_file_name) > 0)) { /* This file contains include guard code or is already on the input stack more than once. An inclusion of a guarded file would have no effect and so, is suppressed. A file that is already on the stack more than once is probably an include loop caused by looking for a file that can be implicitly included in a context in which no implicit include would actually be done in a real compilation. */ (void)fclose(f_source); #if DEBUG if (debug_level >= 3) { fprintf(f_debug, "pop_input_stack: skipping include file %s\n", full_file_name); } /* if */ #endif /* DEBUG */ } else { push_input_stack(f_source, (char *)NULL, display_name, full_file_name, /*is_include_file=*/FALSE, (a_boolean)sfp->included_by_system_include, /*is_preinclude=*/FALSE, /*preinclude_macros=*/FALSE, /*is_implicit_include=*/TRUE, unicode_source_kind, dir_entry, ifhp); } /* if */ } /* if */ } /* if */ } /* if */ } /* if */ #endif /* INSTANTIATION_BY_IMPLICIT_INCLUSION */ /* Check whether a PCH file should be generated at the end of the execution of this include directive. */ check_for_generation_of_pch_on_return_to_primary_file(); } /* if */ #if MICROSOFT_EXTENSIONS_ALLOWED if (processing_vccorlib_header) { /* If we are processing vccorlib.h, interrupt the feedback loop that pushes a forced include after a pop. Preusing processing will occur before we resume processing of force includes. */ } else #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Do not insert code here. */ if (is_end_of_preinclude) { /* If this is the end of a preincluded file, see if there is another file to be preincluded. */ push_next_preinclude_file(); } /* if */ #if DEBUG if (db_flag_is_set("incl_search_path")) { fprintf(f_debug, "pop_input_stack: include search path after popping:\n"); db_incl_search_path(); } /* if */ #endif /* DEBUG */ db_exit(); } /* pop_input_stack */ void pop_cloned_input_stack_entry(void) /* Pop the top element from the input stack, which must have been added by push_cloned_input_stack_entry. Such entries are used for tracking the tree structure of #line directives. */ { check_assertion(curr_ise != NULL && curr_ise->cloned_for_line_directive); curr_ise = &input_stack[--depth_input_stack]; } /* pop_cloned_input_stack_entry */ static void expand_curr_source_line(void) /* We have run into a source line that won't fit in curr_source_line; reallocate curr_source_line to make it bigger. */ { sizeof_t old_size, new_size; char *new_curr_source_line; db_enter(4, "expand_curr_source_line"); old_size = (sizeof_t)(after_end_of_curr_source_line - curr_source_line); /* Increase the size of curr_source_line. */ new_size = old_size * 2; /* Allocate two more bytes than required: one at the end so that a pointer past the end will not have the same address as a pointer to the next object in memory, and one before the start to make it safe to check the previous byte without an additional test. */ new_curr_source_line = realloc_buffer((char *)curr_source_line-1, (sizeof_t)(old_size+2), (sizeof_t)(new_size+2)); new_curr_source_line += 1; new_curr_source_line[-1] = ' '; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Reallocate the logical character info array to have the same size as the source line. */ logical_char_info = (a_const_char**)realloc_buffer((char*)logical_char_info, (sizeof_t)(old_size * sizeof(char*)), (sizeof_t)(new_size * sizeof(char*))); #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Update any pointers to the old curr_source_line in the curr_source_line data structure. */ (void)adjust_curr_source_line_structure_after_realloc( curr_source_line, after_end_of_curr_source_line, new_curr_source_line, /*adjust_source_line_modifs=*/TRUE); curr_source_line = new_curr_source_line; after_end_of_curr_source_line = curr_source_line + new_size; db_exit(); } /* expand_curr_source_line */ void ensure_min_curr_source_line_length(sizeof_t min_len) /* Make sure the curr source line can hold at least min_len characters. */ { while (min_len > (sizeof_t)(after_end_of_curr_source_line - curr_source_line)) { expand_curr_source_line(); } /* while */ } /* ensure_min_curr_source_line_length */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Macro to set cached_logical_char_info_entries_used. Expands to nothing if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED is FALSE. */ #define set_cached_logical_char_info_entries_used(new_val) \ (cached_logical_char_info_entries_used = (new_val)) #else /* !MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ #define set_cached_logical_char_info_entries_used(new_val) /* nothing */ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* The value of logical_char_info_entries_used is saved at the beginning of the token scanning process so that for tokens that contain multibyte characters we can quickly determine the logical column offset of the start of the token. */ STATIC_THREAD int cached_logical_char_info_entries_used; static int f_logical_column_offset(a_const_char *loc_in_line) /* Compute the logical column offset (the difference between the actual byte number of the source line and the logical column number) for cases where loc_in_line is not after the last entry in the logical column info table. To convert a pointer into curr_source_line into a logical column number, you need to find the entry in the logical_char_info array that contains the largest pointer that is <= the pointer you are looking for. The array index+1 is the adjustment needed to convert to a logical column number. */ { int low = 0; int high = logical_char_info_entries_used; int idx; a_const_char *idx_ptr; if (loc_in_line < logical_char_info[0]) { /* The location precedes the first multibyte character. */ idx = 0; } else if (cached_logical_char_info_entries_used != 0 && (cached_logical_char_info_entries_used == logical_char_info_entries_used || (loc_in_line >= logical_char_info[cached_logical_char_info_entries_used - 1] && loc_in_line < logical_char_info[cached_logical_char_info_entries_used]))) { /* We saved the value of logical_char_info_entries_used at the start of the token and loc_in_line refers to a position after the saved entry but before the one that follows it. Use that value. */ idx = cached_logical_char_info_entries_used; } else { /* Find the entry in the logical_char_info array that is <= loc_in_line and where the next entry is > loc_in_line. This is done using a binary search. */ for (;;) { idx = (low + high) / 2; idx_ptr = logical_char_info[idx]; if (idx_ptr > loc_in_line) { high = idx; } else if (logical_char_info[idx + 1] > loc_in_line) { break; } else { low = idx; } /* if */ } /* for */ /* Add one to convert from an array index to the actual column offset. */ idx++; } /* if */ return idx; } /* f_logical_column_offset */ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Return TRUE if the simple version of the logical column conversion can be used, FALSE otherwise. The simple conversion can be done if there are no logical_char_info entries used, or if loc_in_line is greater than or equal to the last entry in the logical column info array. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED #define simple_logical_column_conversion_possible(loc_in_line) \ (logical_char_info_entries_used == 0 || \ (loc_in_line) >= logical_char_info[logical_char_info_entries_used - 1]) #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Compute the logical column offset (the difference between the actual byte number of the source line and the logical column number). In most cases a simple conversion can be done. For other cases, call a function to do the conversion. To convert a pointer into curr_source_line into a logical column number, you need to find the entry in the logical_char_info array that contains the largest pointer that is <= the pointer you are looking for. The array index+1 is the adjustment needed to convert to a logical column number. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED #define logical_column_offset(loc_in_line) \ (simple_logical_column_conversion_possible(loc_in_line) \ ? logical_char_info_entries_used \ : f_logical_column_offset(loc_in_line)) #else /* !MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ #define logical_column_offset(loc_in_line) (0) /*lint --e(835)*/ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ STATIC_THREAD an_orig_line_modif_ptr last_splice_olmp; /* If non-NULL, points to the orig line modification for the rightmost line splice or multi-line character string line break processed by conv_line_loc_to_source_pos in the current line. */ void conv_line_loc_to_source_pos(a_const_char *loc_in_line, a_source_position *position_var) /* Convert a pointer to somewhere in curr_source_line, macro_buffer, or a string insertion buffer (loc_in_line) into the corresponding source sequence number and column (in position_var). This is used to develop a position for later use in error reporting. This function version is for cases where the processing need not be very fast; macro_line_loc_to_source_pos should be used when speed is critical. */ { a_const_char *adj_loc_in_line; a_source_line_modif_ptr slmp, parent_slmp, orig_slmp; an_orig_line_modif_ptr olmp = orig_line_modif_list; a_const_char *start_of_curr_phys_line = curr_source_line; a_seq_number seq_number = curr_seq_number; int column_adjustment = 0; #if FULLY_RESOLVED_MACRO_POSITIONS a_boolean use_orig_position = FALSE; a_seq_number orig_seq = 0; a_column_number orig_column = 0; a_macro_invocation_record_index macro_context = NO_PARENT_MACRO_INVOCATION; #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ #ifdef __COVERITY__ /* Coverity believes (incorrectly) that the column field can be unset in some cases. */ *position_var = null_source_position; #endif /* ifdef __COVERITY__ */ if (in_token_insertion_from_string) { /* We are processing a token insertion from a string. Just use the position at which the insertion is being done. */ *position_var = token_insertion_position; goto done; } /* if */ orig_slmp = NULL; if (!within_curr_source_line(loc_in_line)) { orig_slmp = assoc_source_line_modif(loc_in_line); if (orig_slmp->is_whitespace_kwd) { /* This source line modification is either the canonical representation of a whitespace keyword or a line-start modification reinserting the first token of a potential whitespace keyword that turned out not to be one. It is not a macro expansion, and thus positions within its text must be calculated differently. */ a_boolean skip_position_calculation = TRUE; if (loc_in_line == orig_slmp->inserted_text) { /* The starting position of the first token was stored in the source line modification when it was created. Use it directly. */ *position_var = orig_slmp->source_position; } else if (loc_in_line == orig_slmp->end_inserted_text - 1) { if (orig_slmp->line_loc == NULL) { /* This is the first token of a failed multi-line whitespace keyword. Because the end of that token was in a line that has been overwritten, the position must be calculated relative to the starting position of the token. */ *position_var = orig_slmp->source_position; position_var->column += (a_column_number)(orig_slmp->end_inserted_text - orig_slmp->inserted_text - 1); #if FULLY_RESOLVED_MACRO_POSITIONS position_var->orig_column = position_var->column; #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ } else { /* This is the canonical representation of a whitespace keyword, so at least the ending position is still in the current source line or parent modification and should be calculated from that character position. */ loc_in_line = loc_of_insert(orig_slmp) + orig_slmp->num_chars_to_delete - 1; orig_slmp = NULL; skip_position_calculation = FALSE; } /* if */ } else { /* Neither the start nor the end of the insertion. */ unexpected_condition(); } /* if */ if (skip_position_calculation) { /* We've already set the position, so no further calculation is needed. Set orig_slmp to NULL so its stored position is not overwritten. */ orig_slmp = NULL; goto have_position; } /* if */ } /* if */ } /* if */ adj_loc_in_line = loc_in_line; if (!within_curr_source_line(adj_loc_in_line)) { /* If loc_in_line is now not in curr_source_line, it must be in a macro expansion or macro argument. Find the location in curr_source_line that begins the macro expansion that ultimately generates adj_loc_in_line. This gives us a source line position we can convert. Remember the innermost modification entry in orig_slmp so the position can be put into it once determined. */ slmp = assoc_source_line_modif(adj_loc_in_line); if (orig_slmp == NULL) { orig_slmp = slmp; } /* if */ #if FULLY_RESOLVED_MACRO_POSITIONS get_source_pos_from_macro_text_map(orig_slmp, loc_in_line, &orig_seq, &orig_column, ¯o_context); use_orig_position = TRUE; #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ for (;;) { /* If a source line modification includes a source position, we are done. */ if (slmp->source_position.seq != 0) { *position_var = slmp->source_position; goto have_position; } /* if */ parent_slmp = parent_source_line_modif(slmp); if (parent_slmp == NULL) break; slmp = parent_slmp; } /* for */ /* The topmost modification is not for a macro, so we will have to work out the position from the line location. */ adj_loc_in_line = loc_of_insert(slmp); } /* if */ /* We now have in adj_loc_in_line a position within curr_source_line, which must be converted to the corresponding source position. */ if (last_splice_olmp != NULL && last_splice_olmp->line_loc <= adj_loc_in_line) { /* We don't need to scan through all the modifications but can start from the nearest preceding line splice. This can save a huge amount of time in pathological cases with thousands of line splices in a single logical line. */ olmp = last_splice_olmp; } /* if */ if (olmp != NULL) { /* There are trigraphs and/or line splices, so the position must be determined by finding where adj_loc_in_line falls relative to those modifications in the line. */ do { if (adj_loc_in_line < olmp->line_loc) { /* This position precedes the current entry, so we now know which physical line adj_loc_in_line is in. */ break; } else if (olmp->kind == olm_line_splice || olmp->kind == olm_multiline_string_splice) { if (*adj_loc_in_line == LE_ESCAPE && adj_loc_in_line[1] == LE_NEWLINE && adj_loc_in_line == olmp->line_loc) { /* In the case that a line splice is followed by the end of the logical source line, use the position of the "\" on the current line as the error position. This is useful when the last line of a file ends with a backslash. */ break; } /* if */ /* Keep track of the current physical line. */ start_of_curr_phys_line = olmp->line_loc; if (olmp->kind == olm_multiline_string_splice) { start_of_curr_phys_line += 2; } /* if */ seq_number = olmp->variant.line_splice_seq_number; column_adjustment = logical_column_offset(olmp->line_loc); if (last_splice_olmp == NULL || last_splice_olmp->line_loc < olmp->line_loc) { /* Remember the rightmost splice as an optimization. */ last_splice_olmp = olmp; } /* if */ } else if (adj_loc_in_line == olmp->line_loc || (olmp->kind == olm_splice_whitespace && last_splice_olmp != NULL && olmp->line_loc == last_splice_olmp->line_loc)) { /* This position matches the position in the current entry, so the position we have is right. */ } else if (olmp->kind == olm_null) { /* Null (zero) character in source line. */ /* Adjust for the fact that the escape sequence is bigger than the original null character. */ column_adjustment += (1 - LE_ESCAPE_LEN); } else { #if CHECKING if (olmp->kind != olm_trigraph) { internal_error( "conv_line_loc_to_source_pos: bad orig line modification"); } /* if */ #endif /* CHECKING */ /* Keep a column adjustment to compensate for trigraphs. */ column_adjustment += 2; } /* if */ } while ((olmp = olmp->next) != NULL); } /* if */ /* Set the source position now that we know what physical line we are in and how many trigraphs appear before adj_loc_in_line on this line. */ position_var->seq = seq_number; position_var->column = (a_column_number)(adj_loc_in_line - start_of_curr_phys_line + column_adjustment + 1 - logical_column_offset(adj_loc_in_line)); have_position: /* Save the position determined in the innermost source line modification that covers this location. That will make succeeding calls of this routine faster. */ if (orig_slmp != NULL) { orig_slmp->source_position = *position_var; #if !FULLY_RESOLVED_MACRO_POSITIONS pos_of_macro_invocation = *position_var; #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ } /* if */ done: #if FULLY_RESOLVED_MACRO_POSITIONS if (use_orig_position) { /* The position is in a macro expansion -- copy the original position, too. */ position_var->orig_seq = orig_seq; position_var->orig_column = orig_column; #if RECORD_MACRO_INVOCATIONS position_var->macro_context = macro_context; #endif /* RECORD_MACRO_INVOCATIONS */ } else if (!in_token_insertion_from_string) { /* The position is in the current source, so the original position is the same as the normal position. */ position_var->orig_seq = position_var->seq; position_var->orig_column = position_var->column; #if RECORD_MACRO_INVOCATIONS position_var->macro_context = NO_PARENT_MACRO_INVOCATION; #endif /* RECORD_MACRO_INVOCATIONS */ } /* if */ #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ return; } /* conv_line_loc_to_source_pos */ #if FULLY_RESOLVED_MACRO_POSITIONS /* Copy the normal position (seq and column) to orig_seq and orig_column, as required for positions in ordinary source text, not part of a macro expansion. */ #define copy_pos_to_orig_pos(position_var) \ (position_var).orig_seq = (position_var).seq; \ (position_var).orig_column = (position_var).column; /* Test for whether the current macro position should be used for the current position (always FALSE for FULLY_RESOLVED_MACRO_POSITIONS). */ /*lint -emacro(506,should_use_pos_of_macro_invocation)*/ #define should_use_pos_of_macro_invocation() FALSE /* Macro to copy pos_of_macro_invocation to the specified position variable (not done for FULLY_RESOLVED_MACRO_POSITIONS). */ #define copy_pos_of_macro_invocation_to(position_var) /* nothing */ #else /* !FULLY_RESOLVED_MACRO_POSITIONS */ #define copy_pos_to_orig_pos(position_var) /* nothing */ /* If a macro expansion is currently being scanned, the position of the outermost macro invocation is in pos_of_macro_invocation, and all positions within the expansion should use that position. */ #define should_use_pos_of_macro_invocation() \ (pos_of_macro_invocation.seq != 0) #define copy_pos_of_macro_invocation_to(position_var) \ ((position_var) = pos_of_macro_invocation) #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ #if RECORD_MACRO_INVOCATIONS /* Set the macro context to NO_PARENT_MACRO_INVOCATION, as required for positions in ordinary source text, not part of a macro expansion. */ #define set_macro_context_to_none(position_var) \ (position_var).macro_context = NO_PARENT_MACRO_INVOCATION; #else /* !RECORD_MACRO_INVOCATIONS */ #define set_macro_context_to_none(position_var) /* nothing */ #endif /* RECORD_MACRO_INVOCATIONS */ static INLINE void macro_line_loc_to_source_pos(a_const_char *loc_in_line, a_source_position &position_var) /* Convert a pointer to somewhere in curr_source_line or macro_buffer (loc_in_line) into the corresponding source sequence number and column (in position_var). This is used to develop a position for later use in error reporting. This macro version is for cases where the processing must be very fast; conv_line_loc_to_source_pos should be used when speed is not so critical. If the position is within curr_source_line (i.e., it's not in a macro expansion), and there is no modification information of any kind, curr_source_line is all one line with no trigraphs to perturb the column numbers. The position can be determined directly. This is the most common case. In configurations where FULLY_RESOLVED_MACRO_POSITIONS is FALSE, positions inside macro expansions are mapped to the source position of the outermost macro invocation; in such cases, that position (kept in the global variable pos_of_macro_invocation) is used directly to avoid the overhead of calling conv_line_loc_to_source_pos. */ { if (no_modifs_to_curr_source_line || (within_curr_source_line(loc_in_line) && orig_line_modif_list == NULL && !in_token_insertion_from_string)) { position_var.seq = curr_seq_number; position_var.column = (a_column_number) ((loc_in_line) - curr_source_line + 1 - logical_column_offset(loc_in_line)); copy_pos_to_orig_pos((position_var)); set_macro_context_to_none((position_var)); } else if (should_use_pos_of_macro_invocation()) { copy_pos_of_macro_invocation_to((position_var)); } else { conv_line_loc_to_source_pos((loc_in_line), &(position_var)); } /* if */ } /* macro_line_loc_to_source_pos */ static void diagnostic_at_line_pos(an_error_severity severity, an_error_code error_code, a_const_char *loc_in_line) /* Record the occurrence of the indicated diagnostic at the indicated character position of the current logical source line. */ { /* Convert the character position into an error position. */ conv_line_loc_to_source_pos(loc_in_line, &error_position); diagnostic(severity, error_code); } /* diagnostic_at_line_pos */ /* Record the occurrence of the indicated error at the indicated character position of the current logical source line. */ #define error_at_line_pos(error_code, loc_in_line) \ diagnostic_at_line_pos(es_error, (error_code), (loc_in_line)) /* Record the occurrence of the indicated warning at the indicated character position of the current logical source line. */ #define warning_at_line_pos(error_code, loc_in_line) \ diagnostic_at_line_pos(es_warning, (error_code), (loc_in_line)) /* Macro to temporarily add newline/end-line to the current contents of the source buffer. Needed when an error is detected while building the source line. Without the newline/end-line, the partial line could not be properly displayed with the error message. Fortunately, the errors that there are occur at the end of lines, so the "partial" line is really the full line. */ #define finish_off_source_line_so_it_can_be_displayed_in_error() \ { *loc_in_line = LE_ESCAPE; loc_in_line[1] = LE_NEWLINE; \ loc_in_line[2] = LE_ESCAPE; loc_in_line[3] = LE_END_OF_LINE; } #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED #if BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR #define MBC_CHECKING_NEEDED_IN_LINE_READING TRUE #else /* !BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR */ #if QUESTION_MARK_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR #define MBC_CHECKING_NEEDED_IN_LINE_READING TRUE #else /* !QUESTION_MARK_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR */ #define MBC_CHECKING_NEEDED_IN_LINE_READING FALSE #endif /* QUESTION_MARK_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR */ #endif /* BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR */ #else /* !MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ #define MBC_CHECKING_NEEDED_IN_LINE_READING FALSE #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ #if MBC_CHECKING_NEEDED_IN_LINE_READING static void find_offset_for_source_line_mbc_including( char *new_char, unsigned long *mbc_offset) /* *mbc_offset is the 0-origined offset to a position in curr_source_line that is on a multibyte character boundary. Advance from that position over whole multibyte character sequences to find the start position of the multibyte character sequence that contains new_char. Return *mbc_offset set to the offset of that start position. An offset is used for mbc_offset, rather than a pointer, to avoid the need to remap the pointer if curr_source_line is resized. */ { unsigned long offset = *mbc_offset; a_const_char *ptr; int numch; #if UNICODE_SOURCE_SUPPORTED /* This routine is not expected to be called for Unicode source. If it were needed, there's a more efficient way of finding the beginning of a character -- just back up while the current character has "10" as the top two bits. */ check_assertion(curr_file_unicode_source_kind == usk_none); #endif /* UNICODE_SOURCE_SUPPORTED */ /* If we're already too far in the line, start over. */ if (curr_source_line+offset > new_char) offset = 0; /* If we're starting at the beginning of the line, make sure any shift states are reset. */ if (offset == 0) mbc_scan_init(); /* Step through the characters of the source line, stepping over multibyte character sequences. */ for (ptr = curr_source_line+offset;; ptr += numch, offset += numch) { numch = lex_mbc_length_simple(ptr); if (ptr + numch > new_char) break; } /* for */ *mbc_offset = offset; } /* find_offset_for_source_line_mbc_including */ #endif /* MBC_CHECKING_NEEDED_IN_LINE_READING */ /* Macro that returns TRUE if "ch" is a carriage return and we are in a mode in which a cr or cr/lf terminator should be accepted. */ #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR #define is_carriage_return_line_terminator(ch) \ ((ch) == '\r' && carriage_return_is_line_terminator) #else /* !ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ /*lint -emacro(506,is_carriage_return_line_terminator)*/ #define is_carriage_return_line_terminator(ch) FALSE #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ /* Macro that discards a newline following a carriage return if we are in a mode in which a cr or cr/lf terminator should be accepted. */ #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR #define discard_newline_after_gnu_cr(ch) \ { if (curr_ise->prev_line_terminator_was_carriage_return) { \ curr_ise->prev_line_terminator_was_carriage_return = FALSE; \ if ((ch) == '\n') { \ (ch) = getc_curr_input_stream(); \ } /* if */ \ } /* if */ \ } #else /* !ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ #define discard_newline_after_gnu_cr(ch) /* nothing */ #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ a_boolean read_logical_source_line(a_boolean do_pop_on_end_of_file, a_boolean extend_current_line) /* Read the next logical source line into curr_source_line and the related variables. A "logical source line" is what results after trigraph characters (see standard, 2.2.1.1) have been replaced, and lines ending in newline-backslash have been spliced with the lines immediately following (see standard, 2.1.1.2). The logical source line thus formed is returned in curr_source_line, terminated with an LE_NEWLINE and an LE_END_OF_LINE lexical escape sequence. The input is read from curr_input_stream. Information that allows the mapping of characters in curr_source_line back to the corresponding source sequence number and column is maintained in orig_line_modif_list. If end of file is not encountered, curr_char_loc is pointed at the first character of the line just read. If the end of the current input file is reached, then: 1) If do_pop_on_end_of_file is TRUE, pop_input_stack is called (perhaps more than once) to get to the next line of input, and that is returned in the usual way. If the end of the primary source file is reached and that file is popped, then after_end_of_all_source is set to TRUE, an empty line (just an LE_END_OF_LINE lexical escape) is placed in curr_source_line, and curr_char_loc is pointed at the line-end escape. 2) If do_pop_on_end_of_file is FALSE, then the current line and the current position within it are left unchanged, and at_end_of_source_file is set to TRUE. This is also done if do_not_advance_past_end_of_file is TRUE in the current input stack entry. The return value from this function is at_end_of_source_file || after_end_of_all_source -- i.e., TRUE if no current source line was read. If extend_current_line is TRUE, read more characters onto the end of the current source line instead of beginning a new line. This is used for the GNU C multiline string extension and for multiline raw string literals in C++11. */ { int ch = 0; char *loc_in_line; a_boolean return_value; uint32_t curr_column; #if MBC_CHECKING_NEEDED_IN_LINE_READING unsigned long mbc_offset = 0; #endif /* MBC_CHECKING_NEEDED_IN_LINE_READING */ int next_ch = 0; a_boolean char_is_trapped = FALSE; an_orig_line_modif_ptr olmp; sizeof_t offset_in_line; a_const_char *after_curr_source_line_minus_term = after_end_of_curr_source_line - 2*LE_ESCAPE_LEN; /* For checking of buffer overflow -- to leave room for the newline and line-end lexical escapes. */ uint32_t white_space_chars_after_backslash = 0; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED && \ BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR char *backslash_loc; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED && ... */ /* This routine handles translation phases 1 (trigraphs, newlines) and 2 (line splices) from the description of translation phases in 2.1.1.2 of the standard. */ #if !FULLY_RESOLVED_MACRO_POSITIONS /* Make sure a macro position is not inadvertently used. */ pos_of_macro_invocation = null_source_position; #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ /* If we are being asked to extend the current line, go straight to the slow loop. */ if (extend_current_line) { /* Do not issue a warning for a backslash followed by whitespace on the line being extended. */ pending_nonsplice_backslash = FALSE; goto entry_for_extend_current_line; } /* if */ /* If the current line ended in a backslash followed by whitespace (i.e., visually but not actually a line splice) that did not appear inside a comment, display a warning before discarding the current line's text. */ if (pending_nonsplice_backslash) { warning_at_line_pos(ec_not_a_line_splice, curr_source_line + offset_of_nonsplice_backslash); pending_nonsplice_backslash = FALSE; } /* if */ /* If the compiler is being run just to produce preprocessing output, and not to compile (i.e., it's supposed to act like cpp), dump the previous line of input (possibly modified since being read in) to the preprocessing output file. */ if (generate_pp_output) { gen_pp_output_for_curr_line(curr_source_line); } /* if */ /* If a raw listing file is being generated, write out the previous line. */ if (f_raw_listing != NULL) { gen_raw_listing_output_for_curr_line(); } /* if */ curr_line_began_inside_comment = FALSE; /* Reset the per-line state of whether token separators should be emitted in the generated output. */ no_token_separators_in_this_line_of_pp_output = no_token_separators_in_pp_output; /* Get the first character of the line, checking for end of file in doing so. If eof_read_on_curr_input_stream is already TRUE, the end of file has already been read (this handles the case where the previous call of this routine read an incomplete last line; this call needs to return the end of file indication). */ if (!eof_read_on_curr_input_stream) { ch = getc_curr_input_stream(); discard_newline_after_gnu_cr(ch); } /* if */ while (eof_read_on_curr_input_stream || is_eof_char(ch)) { /* End of file encountered in the expected way, i.e., before a line has started. */ eof_read_on_curr_input_stream = TRUE; at_end_of_source_file = TRUE; if (!do_pop_on_end_of_file || curr_ise->do_not_advance_past_end_of_file) { /* We're asked not to do the pop, so just return things as they are (at_end_of_source_file is TRUE). */ goto simple_return; } /* if */ /* We are supposed to pop the input stack and attempt again to read the next line. */ pop_input_stack(); if (depth_input_stack < 0) { /* We have popped out of the primary source file; this is the real end of file. */ after_end_of_all_source = TRUE; break; } /* if */ /* Loop to try reading from the file reopened by pop_input_stack. */ ch = getc_curr_input_stream(); discard_newline_after_gnu_cr(ch); } /* while */ /* Either the end of all source, or a real line to read. For the end of source case, a line with just a line-end lexical escape is placed in curr_source_line and the sequence number is incremented to an "after all source" position. */ loc_in_line = (char *)curr_source_line; curr_seq_number = ++seq_number_last_read; /* If there are entries on either of the lists indicating modifications to the current source line, clear those lists now, since they are for the old source line. We also want the lists empty to start building them for the new line. */ if (orig_line_modif_list != NULL) { do { olmp = orig_line_modif_list; orig_line_modif_list = orig_line_modif_list->next; free_orig_line_modif(&olmp); } while (orig_line_modif_list != NULL); end_orig_line_modif_list = NULL; last_splice_olmp = NULL; } /* if */ if (source_line_modif_list != NULL) { a_source_line_modif_ptr slmp, next_slmp; /*lint --e{850} slmp modified in loop */ for (slmp = source_line_modif_list; slmp != NULL; slmp = next_slmp) { next_slmp = slmp->next; /* Don't remove entries that are still needed because they hold saved text for scanned macro arguments. However, if we're outside of any macro invocations those entries can also be freed. */ if (!slmp->contains_saved_macro_argument_text || macro_depth == 0) { rem_source_line_modif(slmp); free_source_line_modif(&slmp); } else { /* We're keeping this entry because it contains macro argument text. If its parent is the primary source line, clear the line_loc pointer because the text referred to is going away. */ if (slmp->line_loc != NULL && within_curr_source_line(slmp->line_loc)) { rem_source_line_modif_from_hash_table(slmp); slmp->line_loc = NULL; slmp->num_chars_to_delete = 0; } /* if */ } /* if */ } /* for */ } /* if */ no_modifs_to_curr_source_line = (source_line_modif_list == NULL); if (after_end_of_all_source) { #if EXPENSIVE_CHECKING && DEBUG curr_translation_unit->is_fully_sequenced = TRUE; #endif /* EXPENSIVE_CHECKING && DEBUG */ /* End of all source. Go end the line with a line-end sequence and return. */ goto return_with_line; } else { /* Not end of file, read the line. */ curr_ise->line_number++; curr_ise->actual_line = ++base_ise->actual_line; /* Check if this line being read is that next needed for the file index table (for the actual source file, excluding any entries that were cloned to track the tree structure of #line directives). Remember that the first character has already been read into ch. */ if (base_ise->actual_line == base_ise->next_index_point) { base_ise->next_index_point = update_file_index( base_ise->assoc_actual_il_file, base_ise->actual_line, ftell(base_ise->file) - 1); } /* if */ /* Read characters until the newline indicating end of line. */ /* Every attempt is made to make this FAST, since every character of the source program passes through this loop. The assumption is that trigraphs will almost never appear, and that line splices will not appear too often (mostly in long macro definitions). In general, we try to do tests that are cheap in the normal case, and we exit out to a general-purpose expensive version of this algorithm on anything out of the ordinary. The exits out are by simple goto, to keep even non-executed code out of the inner loop, to improve pipelining. */ /* Check for an empty line. */ if (ch == '\n') { /* A normal newline line terminator. */ #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR } else if (is_carriage_return_line_terminator(ch)) { /* A carriage return that is treated as a line terminator. */ curr_ise->prev_line_terminator_was_carriage_return = TRUE; #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ } else { /* Use local variables in the inner loop, because some compilers have trouble optimizing this otherwise. */ char *local_loc_in_line = loc_in_line; int local_ch = ch; for (;;) { /* Check for question marks and LE_ESCAPE (i.e., null) characters. The presence of two question marks in a row suggests there may be a trigraph in the line. */ #if !DO_NOT_ASSUME_QUESTION_IS_LARGER_THAN_END_OF_LINE if (local_ch > '?') { /* Common case: For speed. */ } else #endif /* !DO_NOT_ASSUME_QUESTION_IS_LARGER_THAN_END_OF_LINE */ /* Do not insert code here. */ if (local_ch == '?') { /* One "?", check previous character to see if it is also a "?". */ if (local_loc_in_line != curr_source_line && *(local_loc_in_line-1) == '?') { /* On reasonable suspicion of a trigraph, exit to more expensive processing code. Note that this is done before the second "?" is stored, so that we do not ever store more characters than ultimately required, and therefore avoid spurious buffer overflows on trigraphs at the ends of very long lines. */ ch = local_ch; loc_in_line = local_loc_in_line; goto possible_trigraph; } /* if */ } else if (local_ch == '\n' || is_carriage_return_line_terminator(local_ch)) { /* This is never true the first time through the loop (because those are special cases handled above). So this test could be put at the bottom of the inner loop. However, placing it here avoids many cases where local_ch > '?', leading to a net win in performance. */ break; } else if (local_ch == LE_ESCAPE) { /* The zero character is reserved for internal use. */ ch = local_ch; loc_in_line = local_loc_in_line; goto null_character; } /* if */ /* Check that there is still room in the line buffer. We have to leave room for both the final newline and line-end escapes. */ if (local_loc_in_line == after_curr_source_line_minus_term) { /* The line is too long; the buffer must be expanded. Note that after the buffer is expanded we do not return to this loop for the current line; the rest of the line is processed in the more expensive loop. */ ch = local_ch; loc_in_line = local_loc_in_line; goto expand_buffer; } /* if */ /* Put the character into curr_source_line. */ *local_loc_in_line++ = (char)local_ch; /* Get next character, check for end of file without newline. */ if (local_ch = getc_curr_input_stream(), is_eof_char(local_ch)) { ch = local_ch; loc_in_line = local_loc_in_line; goto partial_final_line; } /* if */ } /* for */ ch = local_ch; #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR if (ch == '\r') { curr_ise->prev_line_terminator_was_carriage_return = TRUE; } /* if */ #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ loc_in_line = local_loc_in_line; if (loc_in_line != curr_source_line) { char *cp; #if IGNORE_CARRIAGE_RETURN_IN_SOURCE if (!carriage_return_is_line_terminator) { /* Ignore carriage return right before newline. Ignore several if they are present (there are Microsoft header files that have backslash, carriage return, carriage return at the end of lines, and that backslash has to be taken as a line splice). */ while (*(loc_in_line-1) == '\r') { loc_in_line--; /* Avoid the line splice test if the line is empty except for the carriage return. */ if (loc_in_line == curr_source_line) { goto add_newline_and_line_end_and_return; } /* if */ } /* while */ } /* if */ #endif /* IGNORE_CARRIAGE_RETURN_IN_SOURCE */ /* Find the last non-white-space character on the line to see if this might be (or have been intended to be) a spliced line. */ for (cp = loc_in_line - 1; *cp == ' ' || *cp == '\t' || *cp == '\f' || *cp == VERTICAL_TAB_CHARACTER; cp--) { if (cp == curr_source_line) { /* We fell off the beginning of the line without seeing a "\". Just keep the white-space characters. */ goto add_newline_and_line_end_and_return; } /* if */ } /* for */ if (*cp == '\\') { /* Possible line splice. (In non-GNU modes, it is not a line splice if the line ended with white space, but we check for that case in the splice handling code so we can display a warning message.) */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED && \ BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR backslash_loc = cp; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED && ... */ white_space_chars_after_backslash = (uint32_t)(loc_in_line - cp - 1); goto line_splice; } /* if */ } /* if */ } /* if */ } /* if */ add_newline_and_line_end_and_return: /* Store the final LE_NEWLINE lexical escape sequence. */ *loc_in_line++ = LE_ESCAPE; *loc_in_line++ = LE_NEWLINE; if (white_space_chars_after_backslash != 0) { /* White space followed a backslash. Because trailing white space is generally invisible, that looks like a line splice, so we warn about it to clarify what might otherwise be obscure errors reported on the following line. We will do so on the next call, however, not immediately, to allow the warning to be suppressed if the non-splice backslash occurs within a comment. */ pending_nonsplice_backslash = TRUE; offset_of_nonsplice_backslash = (sizeof_t)(loc_in_line - curr_source_line); offset_of_nonsplice_backslash -= white_space_chars_after_backslash; offset_of_nonsplice_backslash -= LE_ESCAPE_LEN; --offset_of_nonsplice_backslash; } /* if */ return_with_line: /* Store the final LE_END_OF_LINE lexical escape sequence. */ *loc_in_line++ = LE_ESCAPE; *loc_in_line = LE_END_OF_LINE; /* Set the input character position to the start of the line. */ if (!extend_current_line) { curr_char_loc = curr_source_line; logical_char_info_entries_used = 0; any_tokens_gotten_from_curr_source_line = FALSE; last_splice_olmp = NULL; } /* if */ simple_return: /* The return value is TRUE on any end-of-file case. */ return_value = at_end_of_source_file || after_end_of_all_source; if (!extend_current_line) { /* The current line should not be put out as preprocessing output if there isn't a current line, if it's being skipped in an #if or the like, or if it is a line after the first in a preprocessing directive (that happens when there are multi-line comments in a preprocessing directive). The flag will be set later for other preprocessing-related cases. If this is a line after the first in a directive being passed unchanged to preprocessing output, put the line out. */ if (generate_pp_output) { do_not_put_curr_line_in_pp_output = currently_in_pp_if_skip || (in_preprocessing_directive && !pass_pp_directive_to_output); /* Maintain a separate flag for any text inserted at the beginning of the line. */ init_do_not_put_curr_line_in_pp_output = do_not_put_curr_line_in_pp_output; if (return_value) do_not_put_curr_line_in_pp_output = TRUE; } /* if */ if (f_raw_listing != NULL) { /* If a raw listing file is being generated, save the line type: "N" indicating that this is a source line, "S" if this line is part of an #if-skip, or '\0' if there is no source line. */ curr_raw_listing_line_code = return_value ? '\0' : (currently_in_pp_if_skip ? 'S' : 'N'); } /* if */ } /* if */ if (trigraph_column != 0) { /* This line contained the first ignored trigraph. Issue a warning. */ warning_at_line_pos(ec_trigraph_ignored, curr_source_line + trigraph_column - 1); trigraph_column = 0; } /* if */ #if DEBUG if (debug_level >= 1) { /* Display the line just read. */ fprintf(f_debug, "Returning from read_logical_source_line, "); fprintf(f_debug, "return value = %d, ", return_value); if (after_end_of_all_source) { fprintf(f_debug, "\nafter_end_of_all_source = TRUE.\n"); } else { fprintf(f_debug, "seq = %lu\n%s\n", (unsigned long)curr_seq_number, curr_source_line); if (debug_level >= 4) { /* Dump out the modification list, which shows the location of the trigraphs and line splices. */ for (olmp = orig_line_modif_list; olmp != NULL; olmp = olmp->next) { /* Put a caret under the proper character of the source line. */ fprintf(f_debug, "%*c ", (int)(olmp->line_loc-curr_source_line+1), '^'); switch (olmp->kind) { case olm_trigraph: fprintf(f_debug, "trigraph: ?" "?%c\n", olmp->variant.orig_char); break; case olm_line_splice: fprintf(f_debug, "line splice: seq = %lu\n", (unsigned long)olmp->variant.line_splice_seq_number); break; case olm_multiline_string_splice: fprintf(f_debug, "multiline string splice: seq = %lu\n", (unsigned long)olmp->variant.line_splice_seq_number); break; case olm_null: fprintf(f_debug, "null\n"); break; case olm_splice_whitespace: fprintf(f_debug, "splice whitespace: %x\n", olmp->variant.orig_char); break; default: unexpected_condition_str2("read_logical_source_line:", "bad orig_modif_list entry kind (2)"); } /* switch */ } /* for */ } /* if */ } /* if */ } /* if */ #endif /* DEBUG */ return (return_value); expand_buffer: /* The curr_source_line buffer is too small to fit the next character, so go into the expensive loop and expand it. */ curr_column = (uint32_t)(loc_in_line - curr_source_line + 1); goto entry_for_expand_buffer; partial_final_line: /* The final line of a file does not end with a newline. Issue a warning (or an error in strict mode), add a newline and line-end to the line, and return. */ eof_read_on_curr_input_stream = TRUE; finish_off_source_line_so_it_can_be_displayed_in_error(); diagnostic_at_line_pos(strict_ansi_mode ? strict_ansi_error_severity : es_warning, ec_last_line_incomplete, loc_in_line); goto add_newline_and_line_end_and_return; possible_trigraph: /* Two "?"s in a row were detected in the first line. Go into the general-purpose algorithm. Note that we don't actually know that the two "?"s are followed by something that makes them a trigraph, but that should happen seldom enough that, from an efficiency point of view, we don't care. */ curr_column = (uint32_t)(loc_in_line - curr_source_line + 1); goto entry_for_possible_trigraph; null_character: /* There is a null character in the input line. Go into the expensive loop. */ curr_column = (uint32_t)(loc_in_line - curr_source_line + 1); goto entry_for_null_character; line_splice: /* A backslash has been detected at the end of the first line. Go into the general-purpose algorithm. */ curr_column = (uint32_t)(loc_in_line - curr_source_line); goto entry_for_line_splice; /* The general-purpose expensive algorithm. Handles trigraphs, tracks mapping to original source characters. Efficiency is less important in this version. */ line_loop: /* Start of a line after the first, first character already read into ch. */ seq_number_last_read++; curr_ise->line_number++; curr_ise->actual_line = ++base_ise->actual_line; curr_column = 0; /* Check if this line being read is that next needed for the file index table. Remember that the first character has already been read into ch. */ if (base_ise->actual_line == base_ise->next_index_point) { base_ise->next_index_point = update_file_index( base_ise->assoc_actual_il_file, base_ise->actual_line, ftell(base_ise->file) - 1); } /* if */ /* Check for an empty line. */ if (ch == '\n') { /* A normal newline line terminator. */ #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR } else if (is_carriage_return_line_terminator(ch)) { /* A carriage return that is treated as a line terminator. */ curr_ise->prev_line_terminator_was_carriage_return = TRUE; #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ } else { /* Process characters until a newline is read. */ do { /* Process one character (ch). */ curr_column++; /* Check for trigraphs. A trigraph is two "?"s followed by another character. */ if (ch == '?') { /* One "?", check previous character to see if it is also a "?". Note the use of curr_column rather than the start of buffer, since a "?" at the end of the previous line should not be counted as part of a trigraph in this line (trigraphs are handled in translation phase 1, line splices in phase 2). */ if (curr_column != 1 && *(loc_in_line-1) == '?') { entry_for_possible_trigraph: /* Trigraphs are disabled if the C dialect being compiled is pcc, but they are recognized in C++ and ANSI C modes. */ if (C_dialect != C_dialect_pcc #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED #if QUESTION_MARK_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR /* See whether the first question mark is actually a question mark, or a character after the first in a multibyte sequence. */ && (!multibyte_chars_in_source_enabled || #if UNICODE_SOURCE_SUPPORTED curr_file_unicode_source_kind != usk_none || #endif /* UNICODE_SOURCE_SUPPORTED */ (find_offset_for_source_line_mbc_including(loc_in_line-1, &mbc_offset), mbc_offset == (unsigned long)(loc_in_line-1-curr_source_line))) #endif /* QUESTION_MARK_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR */ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ ) { int orig_ch = ch; /* Get the next character, the one following the two "?"s. */ next_ch = getc_curr_input_stream(); /* Check for the possible third characters of trigraphs. If one is found, replace the three characters by the new one. If not, pass the "?" through, and trap the next character for processing on the next time around the loop. */ /* See standard, 2.2.1.1. */ switch (next_ch) { case '=': ch = '#'; break; case '(': ch = '['; break; case '/': ch = '\\'; break; case ')': ch = ']'; break; case '\'': ch = '^'; break; case '<': ch = '{'; break; case '!': ch = '|'; break; case '>': ch = '}'; break; case '-': ch = '~'; break; default: char_is_trapped = TRUE; } /* switch */ if (!char_is_trapped && !trigraphs_allowed) { /* Restore the original value of "ch" as we are actually recognizing the trigraph. */ ch = orig_ch; char_is_trapped = TRUE; if (!trigraph_diagnostic_issued) { /* Note that we want trigraph_column to reflect the position of the initial "?". */ trigraph_column = (a_column_number)(loc_in_line - curr_source_line); trigraph_diagnostic_issued = TRUE; } /* if */ } else if (!char_is_trapped) { /* Trigraph detected. Add a modification entry indicating the position of the trigraph, remove the first "?" from the buffer, then go on to store the remapped character. */ loc_in_line--; curr_column++; olmp = add_orig_line_modif(olm_trigraph, loc_in_line); olmp->variant.orig_char = (unsigned char)next_ch; } /* if */ } /* if */ } /* if */ } else if (ch == LE_ESCAPE) { /* The zero character is reserved for internal use. Replace it by a lexical escape and add an orig line modification for it. The null character will be diagnosed, if appropriate, when it is scanned by skip_white_space or accum_quoted_string. */ entry_for_null_character: if (loc_in_line == after_curr_source_line_minus_term) { /* The line is too long; the buffer must be expanded. */ offset_in_line = (sizeof_t)(loc_in_line - curr_source_line); expand_curr_source_line(); loc_in_line = (char *)curr_source_line + offset_in_line; after_curr_source_line_minus_term = after_end_of_curr_source_line - 2*LE_ESCAPE_LEN; } /* if */ /* Add a modification so that we can get the column offsets right (a single character is replaced by a lexical escape, which is more than one character). */ (void)add_orig_line_modif(olm_null, loc_in_line); /* Put the LE_ESCAPE character into curr_source_line. */ *loc_in_line++ = LE_ESCAPE; /* Fall into the normal code to store the LE_NULL character. */ ch = LE_NULL; } /* if */ /* Check that there is still room in the line buffer. We have to leave room for both the final newline and line-end escapes. */ if (loc_in_line == after_curr_source_line_minus_term) { entry_for_expand_buffer: /* The line is too long; the buffer must be expanded. */ offset_in_line = (sizeof_t)(loc_in_line - curr_source_line); expand_curr_source_line(); loc_in_line = (char *)curr_source_line + offset_in_line; after_curr_source_line_minus_term = after_end_of_curr_source_line - 2*LE_ESCAPE_LEN; } /* if */ /* Put the character into curr_source_line. */ *loc_in_line++ = (char)ch; /* Get next character, check for end of file without newline. */ if (char_is_trapped) { ch = next_ch; char_is_trapped = FALSE; } else { ch = getc_curr_input_stream(); } /* if */ if (is_eof_char(ch)) goto partial_final_line; } while (ch != '\n' && /*lint -e506*/!is_carriage_return_line_terminator(ch)); #if ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR if (ch == '\r') { curr_ise->prev_line_terminator_was_carriage_return = TRUE; } /* if */ #endif /* ACCEPT_GNU_CARRIAGE_RETURN_LINE_TERMINATOR */ if (loc_in_line != curr_source_line) { char *cp; #if IGNORE_CARRIAGE_RETURN_IN_SOURCE if (!carriage_return_is_line_terminator) { /* Ignore carriage return right before newline. Ignore several if they are present (there are Microsoft header files that have this). */ while (*(loc_in_line-1) == '\r') { loc_in_line--; curr_column--; /* Avoid the line splice test if the line is empty except for the carriage return. */ if (curr_column == 0) { goto add_newline_and_line_end_and_return; } /* if */ } /* while */ } /* if */ #endif /* IGNORE_CARRIAGE_RETURN_IN_SOURCE */ /* Find the last non-white-space character on the line to see if this might be (or have been intended to be) a spliced line. */ for (cp = loc_in_line - 1; *cp == ' ' || *cp == '\t' || *cp == '\f' || *cp == VERTICAL_TAB_CHARACTER; cp--) { if ((uint32_t)(loc_in_line - cp) == curr_column) { /* We fell off the beginning of the line without seeing a "\". Just keep the white-space characters. */ goto add_newline_and_line_end_and_return; } /* if */ } /* for */ if (*cp == '\\') { /* Possible line splice. (In non-GNU modes, it is not a line splice if the line ended with white space, but we check for that case below so we can display a warning message.) */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED && \ BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR backslash_loc = cp; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED && ... */ white_space_chars_after_backslash = (uint32_t)(loc_in_line - cp - 1); entry_for_line_splice: #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED #if BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR if (multibyte_chars_in_source_enabled #if UNICODE_SOURCE_SUPPORTED && curr_file_unicode_source_kind == usk_none #endif /* UNICODE_SOURCE_SUPPORTED */ ) { /* See whether the backslash is actually a backslash, or a character after the first in a multibyte sequence. */ find_offset_for_source_line_mbc_including(backslash_loc, &mbc_offset); if (curr_source_line + mbc_offset != backslash_loc) { /* The backslash is not really a backslash. It is followed by a newline, though. */ white_space_chars_after_backslash = 0; goto add_newline_and_line_end_and_return; } /* if */ } /* if */ #endif /* BACKSLASH_CAN_OCCUR_AS_PART_OF_MULTIBYTE_CHAR */ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ if (white_space_chars_after_backslash != 0) { if (!gnu_mode && !cpp23_mode) { /* In non-GNU pre-C++23 modes, white space between a backslash and the end of the line causes the backslash not to be interpreted as a line splice. */ goto add_newline_and_line_end_and_return; } /* if */ /* Some white-space characters occurred between "\" and the newline. Add modification entries for the splice and white-space characters, adjust loc_in_line and curr_column appropriately, and issue a warning. */ finish_off_source_line_so_it_can_be_displayed_in_error(); warning_at_line_pos(ec_white_space_inside_splice, loc_in_line - white_space_chars_after_backslash); loc_in_line -= white_space_chars_after_backslash + 1; olmp = add_orig_line_modif(olm_line_splice, loc_in_line); olmp->variant.line_splice_seq_number = seq_number_last_read+1; for (unsigned i = 0; i < white_space_chars_after_backslash; ++i) { /* All the olm_splice_whitespace entries will be associated with the location of the backslash. */ olmp = add_orig_line_modif(olm_splice_whitespace, loc_in_line); olmp->variant.orig_char = (unsigned char)(loc_in_line[i + 1]); } /* for */ /* coverity[assigned_value] */ curr_column -= white_space_chars_after_backslash; white_space_chars_after_backslash = 0; } else { /* Remove the backslash in the buffer. */ --loc_in_line; /* Add a modification entry recording the position of the line splice. */ olmp = add_orig_line_modif(olm_line_splice, loc_in_line); olmp->variant.line_splice_seq_number = seq_number_last_read+1; } /* if */ /* Begin reading the next line. It is an error if end of file is encountered. */ ch = getc_curr_input_stream(); discard_newline_after_gnu_cr(ch); if (!is_eof_char(ch)) goto line_loop; eof_read_on_curr_input_stream = TRUE; /* Backslash at end of last line in a file -- error. */ finish_off_source_line_so_it_can_be_displayed_in_error(); diagnostic_at_line_pos((microsoft_mode || gnu_mode) ? es_warning : es_error, ec_last_line_backslash, loc_in_line); /* Ignore the backslash, end the logical line at this point. */ } /* if */ } /* if */ } /* if */ goto add_newline_and_line_end_and_return; entry_for_extend_current_line: /* Add more characters to the current source line instead of beginning a new source line. */ if (curr_char_loc > after_curr_source_line_minus_term) { /* The \ n inserted can be very close to the end of the buffer. */ expand_curr_source_line(); after_curr_source_line_minus_term = after_end_of_curr_source_line - 2*LE_ESCAPE_LEN; } /* if */ loc_in_line = (char *)curr_char_loc; if (!eof_read_on_curr_input_stream) { ch = getc_curr_input_stream(); discard_newline_after_gnu_cr(ch); } /* if */ if (!eof_read_on_curr_input_stream && !is_eof_char(ch)) goto line_loop; eof_read_on_curr_input_stream = TRUE; at_end_of_source_file = TRUE; goto add_newline_and_line_end_and_return; } /* read_logical_source_line */ a_boolean f_is_identifier_char(a_const_char *ptr, int *len, a_boolean is_identifier_start) /* ptr points to a character, possibly multibyte, or to a universal character name. Return TRUE if that character or UCN is valid as a character in an identifier (as the first character if is_identifier_start is TRUE, otherwise as a character after the first). If so, also return *len set to the length of the character (possibly >1 for a multibyte character or a UCN). If len == NULL, no length is returned. This routine consults the curr_file_unicode_source_kind global variable, and therefore should be used only within the lexical input routines. */ { a_boolean is_id; int llen = 1; if (*ptr == '\\' && (((ptr[1] == 'u' || ptr[1] == 'U') && universal_character_names_allowed) || (ptr[1] == 'N' && ptr[2] == '{' && named_unicode_chars_allowed))) { /* This is a universal character name or a named Unicode character. Determine the associated code point and see if it is an identifier or identifier-start character. Note that we pass FALSE to the identifier flag parameters of the relevant scanning routine; it is not an error in this routine for the character to fail those tests, and we will do them here directly. */ a_const_char *p = ptr; unsigned long ucn; if (ptr[1] == 'N') { ucn = scan_named_unicode_char(&p, /*is_identifier=*/FALSE, /*is_identifier_start=*/FALSE, /*issue_diagnostics=*/TRUE, /*update_pos_on_error=*/FALSE); if (ucn > MAX_UNICODE_VAL) { /* An error occurred in the named unicode character escape, so it cannot be an identifier character. */ is_id = FALSE; goto end; } /* if */ } else { ucn = scan_universal_character(&p, /*is_identifier=*/FALSE, /*is_identifier_start=*/FALSE, /*issue_diagnostics=*/TRUE); } /* if */ is_id = (is_valid_UCN_identifier_char(ucn, is_identifier_start) == ec_no_error); llen = (int)(p - ptr); } else { #if !MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Simplest case: no multibyte characters of any kind allowed. */ is_id = is_id_char[*ptr-CHAR_MIN] && (!is_identifier_start || !isdigit((unsigned char)*ptr)); #else /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ { unsigned long ch; a_boolean err; #if !UNICODE_SOURCE_SUPPORTED if (!multibyte_chars_in_source_enabled #ifdef char_may_begin_multibyte_sequence || !char_may_begin_multibyte_sequence(*ptr) #endif /* ifdef char_may_begin_multibyte_sequence */ ) { /* Multibyte characters are allowed, but this character isn't one. */ is_id = is_id_char[*ptr-CHAR_MIN] && (!is_identifier_start || !isdigit((unsigned char)*ptr)); } else { /* This might be a multibyte character. */ llen = mbc_to_wide_char(ptr, &ch, &err, /*is_native=*/FALSE); if (err) { is_id = FALSE; } else { #if EDG_MULTIBYTE_CHAR_TEST_MODE /* Consider all multi-byte characters as identifier characters. */ is_id = TRUE; #else /* !EDG_MULTIBYTE_CHAR_TEST_MODE */ #if USE_OWN_SJIS_MULTIBYTE_CHAR_PROCESSING /* We don't have the wide character classification functions if we're using our own SJIS functions. */ is_id = FALSE; #else /* !USE_OWN_SJIS_MULTIBYTE_CHAR_PROCESSING */ /* Use C99 library routines from to classify the character. */ wint_t wc = (wint_t)ch; is_id = iswalpha(wc) || (!is_identifier_start && iswdigit(wc)); #endif /* USE_OWN_SJIS_MULTIBYTE_CHAR_PROCESSING */ #endif /* EDG_MULTIBYTE_CHAR_TEST_MODE */ } /* if */ } /* if */ #else /* UNICODE_SOURCE_SUPPORTED */ /* The multibyte coding selected is UTF-8 (but if curr_file_unicode_source_kind is usk_none, this is a non-Unicode source file). */ ch = (unsigned char)*ptr; if (ch > 0x7f) { if (curr_file_unicode_source_kind != usk_none) { /* Convert the multibyte UTF-8 sequence to a single code point. */ llen = mbc_to_wide_char(ptr, &ch, &err, /*is_native=*/FALSE); if (err) ch = 0; /* Forces FALSE result. */ #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE } else if (!multibyte_chars_in_source_enabled) { /* This is a single character -- use the lookup table. */ is_id = is_id_char[*ptr-CHAR_MIN] && (!is_identifier_start || !isdigit((unsigned char)*ptr)); goto is_id_known; } else { /* Convert the native multibyte sequence to a single Unicode code point. */ wint_t wc; llen = lex_mbc_to_wide_char(ptr, &ch, &err); if (err) ch = 0; /* Forces FALSE result. */ #if EDG_WIN32 /* Note that this code is Windows-specific and must be customized for other platforms. */ wc = (wint_t)ch; is_id = _iswalpha_l(wc, native_multibyte_locale) || (!is_identifier_start && _iswdigit_l(wc, native_multibyte_locale)); goto is_id_known; #else /* !EDG_WIN32 */ #if EDG_NATIVE_MULTIBYTE_TEST_MODE /* Use C99 library routines from to classify the character. */ wc = (wint_t)ch; is_id = iswalpha(wc) || (!is_identifier_start && iswdigit(wc)); goto is_id_known; #else /* !EDG_NATIVE_MULTIBYTE_TEST_MODE */ #error is_identifier_char requires customization on non-Windows \ platforms when using \ NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE. #endif /* EDG_NATIVE_MULTIBYTE_TEST_MODE */ #endif /* EDG_WIN32 */ #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ } /* if */ } /* if */ /* See whether the Unicode code point ch is a valid identifier character. */ if (ch <= UCHAR_MAX) { /* Small value -- use the lookup table. */ is_id = is_id_char_no_mbc[ch] && (!is_identifier_start || !isdigit((unsigned char)ch)); } else if (ch >= 0xd800 && ch <= 0xdfff) { /* Surrogate code points are not allowed. */ is_id = FALSE; } else { /* Do the full lookup for larger values. */ is_id = (is_valid_UCN_identifier_char(ch, is_identifier_start) == ec_no_error); } /* if */ #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE is_id_known:; #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ #endif /* !UNICODE_SOURCE_SUPPORTED */ } #endif /* !MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ } /* if */ if (len != NULL) *len = llen; end: return is_id; } /* f_is_identifier_char */ a_boolean is_identifier_spelling(a_const_char *str, sizeof_t len) /* Return TRUE if the len characters starting at str spell an identifier, i.e., if there is at least one character and each of them is valid at its position in an identifier. Note that keywords and other reserved spellings are identifiers by this definition. */ { a_boolean is_id = len >= 1; for (sizeof_t i = 0; is_id && i < len; /* Advanced below. */) { int numch; is_id = is_identifier_char(str + i, &numch, /*is_identifier_start=*/(i == 0)); if (is_id) { check_assertion(numch > 0); i += (sizeof_t)numch; } /* if */ } /* for */ return is_id; } /* is_identifier_spelling */ an_interpolator_kind interpolator_kind_of_curr_token( a_token_kind *closing_token) /* The current token immediately follows the backslash that introduces an interpolator in a token sequence. Return the kind of interpolator that the current token introduces, or ipk_none if it cannot introduce one. When an interpolator is recognized, *closing_token is set to the kind of the token that terminates its operand list (e.g., tok_rbracket for "\[...]"); otherwise, *closing_token is not modified. */ { an_interpolator_kind kind = ipk_none; switch (curr_token) { case tok_lbracket: kind = ipk_identifier; *closing_token = tok_rbracket; break; case tok_lsplice: kind = ipk_splice; *closing_token = tok_rsplice; break; case tok_lbrace: kind = ipk_tokens; *closing_token = tok_rbrace; break; case tok_identifier: if (locator_for_curr_id.symbol_header->has_intrinsic_name) { a_const_char *id = locator_for_curr_id.symbol_header->identifier; if (strcmp(id, "val") == 0) { kind = ipk_value; *closing_token = tok_rparen; } else if (strcmp(id, "str") == 0) { kind = ipk_string; *closing_token = tok_rparen; } /* if */ } /* if */ break; default: break; } /* switch */ return kind; } /* interpolator_kind_of_curr_token */ a_boolean is_nonstandard_character(char ch) /* Return TRUE if the indicated character is one not required by 5.2.1 of the ISO C standard. */ { a_boolean is_nonstd; switch (ch) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '!': case '"': case '#': case '%': case '&': case'\'': case '(': case ')': case '*': case '+': case ',': case '-': case '.': case '/': case ':': case ';': case '<': case '=': case '>': case '?': case '[': case'\\': case ']': case '^': case '_': case '{': case '|': case '}': case '~': case '\f': case VERTICAL_TAB_CHARACTER: case '\t': case ' ': case '\n': is_nonstd = FALSE; break; case '$': case '@': case '`': is_nonstd = !(c23_mode || cpp26_mode); break; default: is_nonstd = TRUE; break; } /* switch */ return is_nonstd; } /* is_nonstandard_character */ /* Flag that is TRUE while scanning a pcc-mode half-comment (see routine below). */ STATIC_THREAD a_boolean in_pcc_mode_half_comment; static void skip_pcc_mode_half_comment(void) /* In pcc mode, a comment can be begun by "/" followed by "*" as a result of macro expansion. This is a strange sort of comment, however: the preprocessor does not see it as a comment, and the compiler does. That's because in pcc, the preprocessor writes out the characters "/" and "*" and continues processing, and the compiler sees those later as a comment delimiter. This is a strange mode. To implement it, we skip tokens until the closing delimiter. */ { a_boolean saved_fetch_pp_tokens = fetch_pp_tokens; a_source_position comment_start_pos; in_pcc_mode_half_comment = TRUE; /* Determine the start position of the comment in case it's needed for an unclosed-comment error. */ conv_line_loc_to_source_pos(curr_char_loc, &comment_start_pos); /* Advance past the "/" and "*". */ curr_char_loc += 2; /* Flush raw pp tokens until the end of the comment. */ fetch_pp_tokens = TRUE; for (;;) { /* The "*" and "/" must be fetched as tokens, because they may come from macro expansions. */ (void)get_token(); while (curr_token == tok_star) { /* No white space is allowed between the tokens. */ skip_white_space(); (void)get_token(); if (kind_of_white_space_skipped == 0 && *start_of_curr_token == '/') { /* End of comment. Skip the "*" and "/". */ curr_char_loc = start_of_curr_token + 1; goto end_of_comment; } /* if */ } /* while */ if (curr_token == tok_end_of_source) { /* Comment unclosed at end of source. */ if (!building_pch_prefix) { /* Only issue this warning during the real compilation, not during the PCH prefix scan. */ error_position = comment_start_pos; pos_error(ec_comment_unclosed_at_eof, &error_position); } /* if */ /* Consider the comment closed. */ goto end_of_comment; } /* if */ } /* for */ end_of_comment: in_pcc_mode_half_comment = FALSE; fetch_pp_tokens = saved_fetch_pp_tokens; } /* skip_pcc_mode_half_comment */ #if ASM_SUPPORT_NEEDED && !ASM_FUNCTION_ALLOWED static void copy_from_source_to_asm_func_buffer( a_const_char *stop_char, a_const_char *after_comment_stop_char); #endif /* ASM_SUPPORT_NEEDED && !ASM_FUNCTION_ALLOWED */ /* Test whether curr_char_loc is the start of a comment. It is already known that *curr_char_loc == '/'. The test can be tricky if *curr_char_loc and *(curr_char_loc+1) are "/" and "*" but those resulted from pasting of tokens in a macro expansion; that case should not be considered to be the start of a comment. In pcc mode and with the traditional Microsoft preprocessor, the token pasting rules are different, and the start of a comment is assumed. Also tests for "//" in C++ mode. */ #if MICROSOFT_EXTENSIONS_ALLOWED #define or_microsoft_mode_slash_slash() \ || (ms_extensions && !ms_std_preproc && *(curr_char_loc+1) == '/') #else /* !MICROSOFT_EXTENSIONS_ALLOWED */ #define or_microsoft_mode_slash_slash() /* Nothing */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ #define start_of_comment() \ ((*(curr_char_loc+1) == '*' || \ (end_of_line_comments_allowed && *(curr_char_loc+1) == '/')) && \ (within_curr_source_line(curr_char_loc) || \ (pcc_preprocessing_mode && !in_pcc_mode_half_comment && \ *(curr_char_loc+1) == '*') \ or_microsoft_mode_slash_slash() )) #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Create an entry in the logical_char_info array for ptr. */ #define add_logical_char_info_entry(ptr) \ (logical_char_info[logical_char_info_entries_used++] = (ptr)) /* Increment curr_char_loc by a total of len bytes. For each bytes except the first, create an entry in the logical character info table. */ #define incr_char_loc_for_multibyte_char(p_ch, len) \ { \ if (len > 1 && within_curr_source_line(p_ch)) { \ int icclfmc_idx; \ (p_ch) += 1; \ for (icclfmc_idx = 1; icclfmc_idx < (len); icclfmc_idx += 1) { \ add_logical_char_info_entry((p_ch)++); \ } /* for */ \ } else { \ (p_ch) += (len); \ } /* if */ \ } /* incr_char_loc_for_multibyte_char */ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED /* Facilities for detecting and reporting suspicious uses of Unicode bidirectional and zero-width control characters. */ /* A structure for recording unterminated bidirectional control characters, which can be nested. */ typedef struct a_pending_bidi_control *a_pending_bidi_control_ptr; struct a_pending_bidi_control { a_pending_bidi_control_ptr next; /* Points to the control character in whose group the current character appears, or to the next entry on the available list. */ unsigned long start_ch; /* The bidirectional control character to which this entry refers. */ }; /* a_pending_bidi_control */ STATIC_THREAD a_pending_bidi_control_ptr pending_bidi_controls; /* The top of a stack (linked on the "next" field) of not-yet-terminated bidirectional control characters. */ STATIC_THREAD a_pending_bidi_control_ptr avail_pending_bidi_controls; /* List (linked on the "next" field) of bidirectional control character entries that are available for reuse. */ static void push_bidi_control(unsigned long ch) /* Push a new or reused bidirectional control character entry on the stack of pending characters, with ch as the starting character. */ { a_pending_bidi_control_ptr pbcp; if (avail_pending_bidi_controls != NULL) { pbcp = avail_pending_bidi_controls; avail_pending_bidi_controls = pbcp->next; } else { pbcp = alloc_fe_of_type(a_pending_bidi_control); } /* if */ pbcp->start_ch = ch; pbcp->next = pending_bidi_controls; pending_bidi_controls = pbcp; } /* push_bidi_control */ static void pop_bidi_control(a_boolean flush) /* Move one (or, if flush is TRUE, every) bidirectional control character entry from the stack to the list of available entries. */ { a_pending_bidi_control_ptr next; while (pending_bidi_controls != NULL) { next = pending_bidi_controls->next; pending_bidi_controls->next = avail_pending_bidi_controls; avail_pending_bidi_controls = pending_bidi_controls; pending_bidi_controls = next; if (!flush) { break; } /* if */ } /* while */ } /* pop_bidi_control */ static a_boolean check_for_suspicious_control(unsigned long ch, a_boolean flag_zero_width) /* ch is a Unicode code point. If it is a bidirectional control code, return TRUE if it violates the stacking rules for such codes. In addition, if flag_zero_width is TRUE, return TRUE if ch is a zero-width character. Otherwise, return FALSE. */ { a_boolean result = FALSE; switch (ch) { case 0x202a: /* LRE */ case 0x202b: /* RLE */ case 0x202d: /* LRO */ case 0x202e: /* RLO */ case 0x2066: /* LRI */ case 0x2067: /* RLI */ case 0x2068: /* FSI */ /* These codes start regions. */ push_bidi_control(ch); break; case 0x202c: /* PDF */ /* Ends an LRE/REL/LRO/LRO region. */ if (pending_bidi_controls == NULL) { /* The stack is empty and cannot be popped. */ result = TRUE; } else if (pending_bidi_controls->start_ch != 0x202a /* LRE */ && pending_bidi_controls->start_ch != 0x202b /* RLE */ && pending_bidi_controls->start_ch != 0x202d /* LRO */ && pending_bidi_controls->start_ch != 0x202e /* RLO */) { /* The pop does not match the top of the stack. Empty it and flag an error. */ pop_bidi_control(/*flush=*/TRUE); result = TRUE; } else { pop_bidi_control(/*flush=*/FALSE); } /* if */ break; case 0x2069: /* PDI */ /* Ends an LRI/RLI/FSI region. */ if (pending_bidi_controls == NULL) { /* The stack is empty and cannot be popped. */ result = TRUE; } else if (pending_bidi_controls->start_ch != 0x2066 /* LRI */ && pending_bidi_controls->start_ch != 0x2067 /* RLI */ && pending_bidi_controls->start_ch != 0x2068 /* FSI */) { /* The pop does not match the top of the stack. Empty it and flag an error. */ pop_bidi_control(/*flush=*/TRUE); result = TRUE; } else { pop_bidi_control(/*flush=*/FALSE); } /* if */ break; case 0x200b: /* ZWSP */ case 0x200c: /* ZWNJ */ case 0x200d: /* ZWJ */ case 0x2060: /* WJ */ case 0xfeff: /* ZWNBSP */ /* Zero-width (invisible) characters. These can cause strings to unobviously compare unequal. */ if (flag_zero_width) { result = TRUE; } /* if */ break; default: /* No action required. */ break; } /* switch */ return result; } /* check_for_suspicious_control */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ void clear_embed_parse_data(void) /* Set the values of embed_parse_data to default values. */ { embed_parse_data.file_name = NULL; embed_parse_data.prefix_start = NULL; embed_parse_data.after_prefix = NULL; embed_parse_data.suffix_start = NULL; embed_parse_data.after_suffix = NULL; embed_parse_data.if_empty_start = NULL; embed_parse_data.after_if_empty = NULL; #if PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED if (embed_parse_data.directive == NULL) { embed_parse_data.directive = alloc_text_buffer(128); } else { reset_text_buffer(embed_parse_data.directive); } /* if */ #endif /* PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED */ embed_parse_data.limit = (a_host_large_unsigned)-1; embed_parse_data.offset = 0; embed_parse_data.num_prefix_elems = 0; embed_parse_data.num_suffix_elems = 0; embed_parse_data.prefix_is_value_list = TRUE; embed_parse_data.suffix_is_value_list = TRUE; } /* clear_embed_parse_data */ static void clear_embed_control_block(void) /* Set the embed control block values to the default values, i.e., not currently processing a #embed directive. */ { embed_control.directive_pos = null_source_position; #if EXTRA_SOURCE_POSITIONS_IN_IL embed_control.directive_end_pos = null_source_position; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ embed_control.buf = NULL; embed_control.next_byte = NULL; embed_control.last_byte = NULL; embed_control.prefix_loc = NULL; embed_control.after_prefix = NULL; embed_control.suffix_loc = NULL; embed_control.eol_loc = NULL; embed_control.in_process = FALSE; embed_control.reading_from_buffer = FALSE; embed_control.comma_is_next = FALSE; } /* clear_embed_control_block */ static a_boolean copy_embed_parameter_to_result(a_const_char *opnd, a_const_char *after_opnd, a_boolean leading_comma, unsigned char *buf, unsigned num_elems) /* If the embed parameter operand contained between opnd and after_opnd is a comma-separated list of length num_elems, consisting of character literals or integer literals with values that can be represented in an unsigned char, preceded or followed by a comma as indicated by leading_comma, copy the values into buf[0] through buf[num_elems - 1] and return TRUE. Otherwise, return FALSE. (Note that the list of elements was verified to be syntactically correct by parse_embed, but the appearance of object-like macros in the list could result in syntactic variation and/or more elements than were originally counted. This is not an error but simply results in returning FALSE.) */ { a_boolean result = TRUE; a_host_large_unsigned max_val = (unsigned char)((1 << CHAR_BIT) - 1); a_const_char *saved_curr_char_loc = curr_char_loc; a_token_kind saved_curr_token = curr_token; curr_char_loc = opnd; if (leading_comma && get_token() != tok_comma) { result = FALSE; } else { for (unsigned i = 0; i < num_elems; ++i) { a_boolean ovflo; a_host_large_unsigned val; (void)get_token(); if (curr_token != tok_char_constant && curr_token != tok_int_constant) { result = FALSE; goto done; } /* if */ val = unsigned_value_of_integer_constant(&const_for_curr_token, &ovflo); if (ovflo || val > max_val) { result = FALSE; goto done; } /* if */ buf[i] = (unsigned char)val; (void)get_token(); if (curr_token != tok_comma && !(curr_token == tok_rparen && i == num_elems - 1)) { result = FALSE; goto done; } /* if */ } /* for */ if (!leading_comma) { /* If a leading comma is not expected, the list should end with a comma. */ if (curr_token != tok_comma) { result = FALSE; goto done; } /* if */ (void)get_token(); } /* if */ if (start_of_curr_token != after_opnd) { /* There are more values than were expected, presumably due to macro expansion, so the full list will not fit into the space allocated in the buffer. */ result = FALSE; } /* if */ } /* if */ done: curr_char_loc = saved_curr_char_loc; curr_token = saved_curr_token; return result; } /* copy_embed_parameter_to_result */ void insert_embed_contents(a_source_position *pos) /* Set up for inserting the expansion of a #embed directive, appearing in the source at *pos, into the token stream. Information about the directive is in embed_parse_data. */ { a_const_char *full_file_name; a_const_char *display_name; FILE *embed_file; a_boolean suppress_include; a_unicode_source_kind unicode_source_kind; a_directory_name_entry_ptr dir_entry; size_t file_size; embed_control.directive_pos = *pos; #if EXTRA_SOURCE_POSITIONS_IN_IL conv_line_loc_to_source_pos(curr_char_loc, &embed_control.directive_end_pos); #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ (void)open_file_for_input(embed_parse_data.file_name, /*use_search_path=*/TRUE, /*is_include_file=*/FALSE, /*is_system_include=*/FALSE, /*is_include_next=*/FALSE, /*is_implicit_include=*/FALSE, /*is_preinclude=*/FALSE, /*is_embed=*/TRUE, /*continue_on_open_failure=*/FALSE, &full_file_name, &display_name, &embed_file, &suppress_include, &unicode_source_kind, &dir_entry); /* Compute the effective file size for insertion: the actual file size minus the starting offset, or the value of the limit parameter if it is smaller. */ file_size = get_file_size(full_file_name); if (file_size > embed_parse_data.offset) { file_size -= embed_parse_data.offset; } else { file_size = 0; } /* if */ if (embed_parse_data.limit < file_size) { file_size = embed_parse_data.limit; } /* if */ if (file_size != 0) { size_t buffer_size = file_size; unsigned char *loc_file_contents; if (embed_parse_data.prefix_is_value_list && embed_parse_data.suffix_is_value_list) { /* The prefix and suffix parameters, if any, are syntactically suitable for merging with the data from the file, so allow space for them in the buffer. If copy_embed_parameter_to_result determines that the operands are not, in fact, suitable, either because of values outside the range of an unsigned char or because of macro expansion, the extra space will simply be ignored. */ buffer_size += embed_parse_data.num_prefix_elems + embed_parse_data.num_suffix_elems; } /* if */ embed_control.buf = (unsigned char *)alloc_general((sizeof_t)buffer_size); embed_control.size = buffer_size; check_assertion(curr_char_loc[1] == LE_NEWLINE); embed_control.eol_loc = curr_char_loc; if (buffer_size != file_size) { /* The prefix and/or suffix parameters were non-empty and potentially suitable for merging with the file contents. Attempt to copy the operand(s) to the buffer at the beginning or the end. If the copy is successful, set the target of the file read to follow the data from the prefix parameter, if any. Otherwise, just read the file contents into the beginning of the buffer and ignore the extra space at the end. */ a_boolean merged = TRUE; unsigned char *loc_suffix_data = embed_control.buf + embed_parse_data.num_prefix_elems + file_size; if (embed_parse_data.num_suffix_elems != 0 && !copy_embed_parameter_to_result(embed_parse_data.suffix_start, embed_parse_data.after_suffix, /*leading_comma=*/TRUE, loc_suffix_data, embed_parse_data.num_suffix_elems)) { merged = FALSE; } /* if */ if (merged && embed_parse_data.num_prefix_elems != 0 && !copy_embed_parameter_to_result(embed_parse_data.prefix_start, embed_parse_data.after_prefix, /*leading_comma=*/FALSE, embed_control.buf, embed_parse_data.num_prefix_elems)) { merged = FALSE; } /* if */ if (merged) { /* Indicate that the general-case processing of the prefix and suffix parameters is not needed. */ embed_parse_data.prefix_start = NULL; embed_parse_data.after_prefix = NULL; embed_parse_data.suffix_start = NULL; embed_parse_data.after_suffix = NULL; loc_file_contents = embed_control.buf + embed_parse_data.num_prefix_elems; embed_control.last_byte = (a_const_char *)(embed_control.buf + buffer_size - 1); } else { loc_file_contents = embed_control.buf; embed_control.last_byte = (a_const_char *)(embed_control.buf + file_size - 1); } /* if */ } else { loc_file_contents = embed_control.buf; embed_control.last_byte = (a_const_char *)(embed_control.buf + file_size - 1); } /* if */ embed_control.next_byte = (a_const_char *)embed_control.buf; embed_control.comma_is_next = FALSE; if (embed_parse_data.offset != 0) { if (fseek(embed_file, (long int)embed_parse_data.offset, SEEK_SET) != 0) { str_catastrophe(ec_cannot_read_file, embed_parse_data.file_name); } /* if */ } /* if */ if (fread((void *)loc_file_contents, size_t_arg(file_size), 1, embed_file) != 1) { str_catastrophe(ec_cannot_read_file, embed_parse_data.file_name); } /* if */ if (embed_parse_data.prefix_start != NULL) { /* Replace the closing right parenthesis of the prefix operand by an LE_END_OF_EMBED_PREFIX lexical escape and set embed_control.prefix_loc and curr_char_loc to point to its first character. The LE_END_OF_EMBED_PREFIX lexical escape will result in skip_white_space calling gen_pp_output_for_curr_line starting at prefix_start, if preprocessing output is enabled, and initiating the processing of the contents of the embed file. */ ((char *)embed_parse_data.after_prefix)[0] = LE_ESCAPE; embed_control.char_following_prefix = embed_parse_data.after_prefix[1]; ((char *)embed_parse_data.after_prefix)[1] = LE_END_OF_EMBED_PREFIX; embed_control.prefix_loc = embed_parse_data.prefix_start; embed_control.after_prefix = embed_parse_data.after_prefix; curr_char_loc = embed_parse_data.prefix_start; } else { /* get_token() will immediately start returning the contents of the embedded file. */ embed_control.reading_from_buffer = TRUE; } /* if */ if (embed_parse_data.suffix_start != NULL) { /* Replace the closing right parenthesis of the suffix operand by an LE_END_OF_EMBED lexical escape and set embed_control.suffix_loc to its first character. The LE_END_OF_EMBED lexical escape will result in skip_white_space calling gen_pp_output_for_curr_line starting at suffix_loc, if preprocessing output is enabled, and then setting curr_char_loc to the saved end-of-line sequence. */ ((char *)embed_parse_data.after_suffix)[0] = LE_ESCAPE; embed_control.char_following_suffix = embed_parse_data.after_suffix[1]; ((char *)embed_parse_data.after_suffix)[1] = LE_END_OF_EMBED; embed_control.suffix_loc = embed_parse_data.suffix_start; } /* if */ /* Indicate that the special case code at the beginning of get_token is needed to handle the embed file contents and prefix. */ any_initial_get_token_tests_needed = TRUE; embed_control.in_process = TRUE; } else if (embed_parse_data.if_empty_start != NULL) { /* The file is effectively empty, and an if_empty parameter was specified in the directive. Replace the closing right parenthesis of the operand by an LE_END_OF_EMBED lexical escape, and set curr_char_loc to its first character. The LE_END_OF_EMBED lexical escape will result in skip_white_space calling gen_pp_output_for_curr_line starting at suffix_loc, if preprocessing output is enabled, and then setting curr_char_loc to the saved end-of-line sequence. */ embed_control.eol_loc = curr_char_loc; ((char *)embed_parse_data.after_if_empty)[0] = LE_ESCAPE; embed_control.char_following_suffix = embed_parse_data.after_if_empty[1]; ((char *)embed_parse_data.after_if_empty)[1] = LE_END_OF_EMBED; embed_control.suffix_loc = embed_parse_data.if_empty_start; curr_char_loc = embed_parse_data.if_empty_start; embed_control.in_process = TRUE; } /* if */ (void)fclose(embed_file); } /* insert_embed_contents */ static void make_string_constant_for_embed_expansion(a_source_position *pos) /* A potentially-optimizable #embed directive has been encountered at source location *pos. Set up const_for_curr_token as a special ck_string constant to represent the expansion and flag the case for detection by the caller of get_token. */ { a_type_ptr array_type = alloc_type(tk_array); check_assertion(embed_control.buf != NULL && embed_control.size != 0); /* Make an array type of the appropriate size of const unsigned char. */ array_type->variant.array.element_type = make_qualified_type(integer_type(ik_unsigned_char), TQ_CONST); array_type->variant.array.variant.number_of_elements = embed_control.size; set_type_size(array_type); /* Set the current token to tok_string_literal to flag the #embed expansion as optimizable and the position of the current token to the beginning of the directive for use in potential error diagnostics. */ curr_token = tok_string_literal; pos_curr_token = *pos; /* Set up const_for_curr_token to be a string token containing the embed expansion. Initially the value will point to the buffer allocated in general memory for the #embed expansion, but if the optimization applies, a copy will be made in IL memory by copy_embed_data_to_il. */ clear_constant(&const_for_curr_token, ck_string); const_for_curr_token.type = array_type; const_for_curr_token.variant.string.length = embed_control.size; const_for_curr_token.variant.string.value = (a_const_char *)embed_control.buf; const_for_curr_token.variant.string.embed_expansion = TRUE; /* Disable processing of the #embed expansion for now (but leave embed_control untouched) so that the caller of get_token can use next_token() to determine whether to apply the optimization or not. If the optimization cannot be applied, the caller will call process_embed_data_as_bytes to resume processing of the #embed expansion. */ embed_control.in_process = FALSE; } /* make_string_constant_for_embed_expansion */ void copy_embed_data_to_il(a_type_ptr elem_type) /* A potentially-optimizable #embed has just been confirmed as actually optimizable. Copy the data from the embed_control buffer to the IL (as the data for the const_for_curr_token ck_string constant), free the embed data buffer, and clear the embed_control block to allow tokenizing to continue after the #embed directive. In configurations with PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED set to TRUE, also copy the textual form of the #embed directive to the IL and add it to const_for_curr_token. If the specified element type is a signed character type, check the data for any values that cannot be represented as a signed char and issue a warning (or a narrowing error in strict mode) if any are found. */ { check_assertion(const_for_curr_token.kind == ck_string && embed_control.buf != NULL); const_for_curr_token.variant.string.value = alloc_text_of_string_literal(embed_control.size); memcpy((void *)const_for_curr_token.variant.string.value, embed_control.buf, embed_control.size); if (is_character_type(elem_type) && is_signed_integral_type(elem_type)) { /* The array element type is a signed character type. Check the data for any value that cannot be represented as a signed char. */ int max_signed_val = (1 << (CHAR_BIT - 1)) - 1; for (unsigned i = 0; i < const_for_curr_token.variant.string.length; ++i) { if ((unsigned char)const_for_curr_token.variant.string.value[i] > max_signed_val) { pos_diagnostic(strict_ansi_mode ? strict_ansi_discretionary_severity : es_warning, ec_embed_narrowing, &embed_control.directive_pos); break; } /* if */ } /* for */ } /* if */ free_general(embed_control.buf, (sizeof_t)embed_control.size); clear_embed_control_block(); #if PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED const_for_curr_token.variant.string.embed_directive = alloc_il(embed_parse_data.directive->size); memcpy((void *)const_for_curr_token.variant.string.embed_directive, embed_parse_data.directive->buffer, embed_parse_data.directive->size); #endif /* PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED */ } /* copy_embed_data_to_il */ void process_embed_data_as_bytes(void) /* A potentially-optimizable #embed has just been determined not to be optimizable because a call to next_token() found something other than the closing '}'. Resume normal processing of the #embed expansion. */ { a_boolean no_tokens_on_list; embed_control.in_process = TRUE; any_initial_get_token_tests_needed = TRUE; /* The check for the token following the #embed will necessarily have read a new line, so the old eol_loc is now invalid, and that token will have been put on the cached token rescan list. Discard that token and restart at the beginning of the current source line when the embed data is exhausted. */ check_assertion(!cached_token_rescan_stack->is_empty()); get_token_from_cached_token_rescan_stack(&no_tokens_on_list); embed_control.eol_loc = curr_source_line; } /* process_embed_data_as_bytes */ char orig_char_from_embed_directive(a_const_char lex_escape) /* Return the original character from the current #embed directive that was replaced by the second character of the lexical escape marking the end of a prefix, suffix, or if_empty parameter. */ { return (lex_escape == LE_END_OF_EMBED) ? embed_control.char_following_suffix : embed_control.char_following_prefix; } /* orig_char_from_embed_directive */ static void get_token_from_embed_file(void) /* Using the information in embed_control, set curr_token to either tok_int_constant (with const_for_curr_token containing the value of the current byte of the embed file) or tok_comma. When the last byte of the buffer is processed, free the buffer and restore embed_control to indicate that tokens are no longer coming from a #embed directive. */ { if (embed_control.comma_is_next) { curr_token = tok_comma; embed_control.comma_is_next = FALSE; if (generate_pp_output) { fputc(',', f_pp_output); if ((((a_byte *)embed_control.next_byte - (a_byte *)embed_control.buf) % 16) == 0) { fputc('\n', f_pp_output); } /* if */ } /* if */ } else { if (embed_control.next_byte == (a_const_char *)embed_control.buf) { /* Set const_for_curr_token to contain an int value. We only need to do it once and thereafter can just set the actual value, for efficiency. */ clear_constant(&const_for_curr_token, ck_integer); const_for_curr_token.type = integer_type(ik_int); if (embed_control.prefix_loc != NULL) { /* Set the position of the current token to the closing parenthesis of the prefix parameter for better diagnostics, e.g., a missing comma in an initializer list. */ conv_line_loc_to_source_pos(embed_control.after_prefix, &pos_curr_token); } else { pos_curr_token = embed_control.directive_pos; } /* if */ } else { /* If the current token position was set to the end of the prefix parameter, set it back to the directive position now in case it is needed for, e.g., narrowing diagnostics. */ pos_curr_token = embed_control.directive_pos; } /* if */ set_unsigned_integer_value( &const_for_curr_token.variant.integer_value, (a_host_large_unsigned)(unsigned char)*embed_control.next_byte); curr_token = tok_int_constant; if (generate_pp_output) { fprintf(f_pp_output, "%d", (unsigned char)*embed_control.next_byte); } /* if */ if (++embed_control.next_byte > embed_control.last_byte) { if (embed_control.suffix_loc != NULL) { /* Process the suffix parameter operand. */ embed_control.reading_from_buffer = FALSE; curr_char_loc = (a_const_char *)embed_control.suffix_loc; } else { curr_char_loc = embed_control.eol_loc; free_general(embed_control.buf, (sizeof_t)embed_control.size); clear_embed_control_block(); } /* if */ if (generate_pp_output) { fputc('\n', f_pp_output); } /* if */ } else { embed_control.comma_is_next = TRUE; } /* if */ } /* if */ } /* get_token_from_embed_file */ void skip_white_space(void) /* Skip over any white space in the input. White space includes blanks, horizontal tabs, and comments. Ordinarily, newline, vertical tab, and form feed are also considered white space; however, when the global variable in_preprocessing_directive indicates that we are inside such a directive, newline is not white space, and vertical tab and form feed are flagged as errors. See standard sections 2.1.1.2 (translation phases; this routine handles part of phase 3), 3.1, and 3.8. The global variable kind_of_white_space_skipped is set to indicate the kinds of white space that were skipped, if any (this is useful for scanning of #defines, where white space is significant). This routine also handles the special marker characters that appear in source text (end of token, start of expansion, end of expansion). */ { char ch; int kind_skipped; a_const_char *comment_start_loc = NULL; a_const_char *saved_curr_char_loc; a_boolean comment_pos_determined; a_source_position comment_start_pos; a_const_char *delete_from; a_const_char *delete_to; a_source_line_modif_ptr slmp; a_boolean delete_only_for_comment = FALSE; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED a_boolean suspicious_unicode = FALSE; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ /* Macro used later to test if comments must be deleted. Except for the keep_comments_in_pp_output switch, this is basically a time optimization -- we avoid doing the comment deletion if we will not be outputting the modified line text in some way (as preprocessing or raw listing output). */ /* If asm functions are allowed, also delete comments if inside an asm function body. The comments are copied to the asm string before they are deleted. */ #if ASM_SUPPORT_NEEDED #define or_in_asm_function_body() || in_asm_function_body #else /* !ASM_SUPPORT_NEEDED */ #define or_in_asm_function_body() /* Nothing */ #endif /* ASM_SUPPORT_NEEDED */ #define need_to_delete_comment() \ ((((generate_pp_output && !do_not_put_curr_line_in_pp_output) || \ f_raw_listing != NULL) && \ !keep_comments_in_pp_output) \ or_in_asm_function_body()) /* Macro used to check whether the comment start position has been determined and to determine it if not already done. */ /*lint -emacro(835,determine_comment_pos_if_not_yet_done)*/ #define determine_comment_pos_if_not_yet_done() \ { if (!comment_pos_determined) { \ macro_line_loc_to_source_pos(comment_start_loc, comment_start_pos); \ comment_pos_determined = TRUE; \ } /* if */ \ } /* Forget that we know where the current token's characters are. */ start_of_curr_token = NULL; if (preserve_white_space_kind) { /* Do not reset the value of kind_of_white_space_skipped. This is needed by expand_top_level_pcc_macro for a case like A() B() where A and B are macros, "A()" expands to nothing, and B() has a non-empty expansion. In that case, get_token will return the first token of B(), but skip_white_space will be called during its expansion. We must not lose track of the fact that there was a space between A() and B(), in order to prevent incorrectly concatenating the first token of B() with whatever preceded A(). */ kind_skipped = kind_of_white_space_skipped; } else { /* No white space skipped so far. */ kind_skipped = 0; } /* if */ white_space_loop: /* Examine the current character to see if it is white space. Throw away spaces quickly, since they are always white space and there are lots of them (so we want that to be fast). */ if ((ch = *curr_char_loc) == ' ') { kind_skipped |= WHITE_SPACE_OTHER; do {} while ((ch = *(++curr_char_loc)) == ' '); } /* if */ switch (ch) { case LE_ESCAPE: /* Lexical escape. Can be end of line, end of insertion, etc. Second character indicates which. */ ch = curr_char_loc[1]; if (ch == LE_NEWLINE) { /* Newline is white space ordinarily, but a token to be returned if in a preprocessing directive. */ /* Newline is also a token in asm functions. */ if (in_preprocessing_directive || treat_newline_as_token) goto end_skip; /* The newline character is white space, and is being thrown away. */ kind_skipped |= WHITE_SPACE_OTHER; curr_char_loc += LE_ESCAPE_LEN; } else if (ch == LE_END_OF_LINE || ch == LE_END_OF_INSERTION) { /* End of source line or end of macro insertion. */ #if READ_SOURCE_IN_BINARY_MODE_FOR_MSDOS end_of_current_line: #endif /* READ_SOURCE_IN_BINARY_MODE_FOR_MSDOS */ #if MICROSOFT_EXTENSIONS_ALLOWED if (scanning_for_whitespace_keyword && (ch == LE_END_OF_INSERTION || do_preprocessing_only)) { /* A whitespace keyword cannot span the end of a macro insertion. We also terminate the scan for the second word of the whitespace keyword at the end of the line if we are doing preprocessing only. The reason for this is that when the second word is on a different line, the line break is effectively deleted in the preprocessed output, regardless of whether the keyword was completed or not. This is not desirable when the preprocessor is used to manipulate text that is not C or C++ code, and this consideration outweighs the fact that in some pathological cases the results of direct compilation and preprocessing only can be different. */ goto end_skip; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Check to see if the hanging deletion flag is set. */ if ((delete_from = delete_source_from_loc) != NULL) { /* Source from the indicated position to the end of the line or macro is to be deleted. This is typically because a macro invocation argument list is being scanned. Keep the final line-end lexical escape, but delete the newline lexical escape. */ add_deletion_source_line_modif(delete_from, curr_char_loc-delete_from, /*for_comment=*/FALSE, /*raw_or_exp=*/FALSE); /* Clear the flag to be sure it is cleared for error exit cases. It will be set later to the location of further text if there is any. */ delete_source_from_loc = NULL; } /* if */ if (ch == LE_END_OF_LINE) { /* End of the source line. */ end_of_line_escape_offset = (sizeof_t)(curr_char_loc - curr_source_line); /* We have to read a new logical source line now. read_logical_source_line will pop the input stack if end of file is encountered. On the final end of file, TRUE is returned, and we want to exit this routine. If we are already at the final end of file, don't try reading again, just exit. */ if (after_end_of_all_source || read_logical_source_line(/*do_pop_on_end_of_file=*/TRUE, /*extend_current_line=*/FALSE)) { /* End of file, end the white-space skip. */ goto end_skip; } /* if */ /* Not end of file, keep checking for white space in the new line. */ } else { /* End of the expansion text for a macro or a #embed directive. Find the character location of the character following the macro invocation, and continue there. */ slmp = assoc_source_line_modif(curr_char_loc); if (slmp->being_rescanned_for_token_pasting) { /* We're rescanning a macro expansion in order to do old-style token pasting (e.g., in pcc mode). We've reached the end of the top-level macro. See whether we should continue into the surrounding line, which is true if we're inside the argument list for a macro call. */ if (macro_depth <= 1) { /* Do not continue into the primary source line; a tok_end_of_source will be returned eventually. */ goto end_skip; } /* if */ /* Continue into the primary source line. Clear the flag to indicate that we went off the end. */ slmp->being_rescanned_for_token_pasting = FALSE; } else if (slmp->is_isolated_text) { /* The modification entry is marked with is_isolated_text, so stop and return end-of-source. This is used for macro arguments being macro-expanded in isolation from the rest of the file's tokens. */ goto end_skip; } /* if */ /* Normal case; continue with the text following the macro invocation. */ leave_insertion(slmp, curr_char_loc); last_source_line_modif_exited_while_skipping_white_space = slmp; } /* if */ /* If the hanging deletion flag was set, reset it to the new current position. */ if (delete_from != NULL) { delete_source_from_loc = curr_char_loc; } /* if */ } else if (ch == LE_END_OF_TOKEN) { /* Marker put into text by preprocessing of macros, to force the same interpretation of token boundaries as during the macro definition. At this level, should be ignored. Note that kind_skipped is not set, since this is not white space. */ curr_char_loc += LE_ESCAPE_LEN; } else if (ch == LE_INERT_MACRO || ch == LE_TEMPORARILY_INERT_MACRO) { /* Marker put into text to indicate that the following macro name should not be expanded. Return to caller. */ goto end_skip; } else if (ch == LE_NULL) { /* Null (zero) character. */ if (null_chars_allowed_in_source) { warning_at_line_pos(ec_null_char_ignored, curr_char_loc); } else { diagnostic_at_line_pos(es_discretionary_error, ec_invalid_char, curr_char_loc); } /* if */ kind_skipped |= WHITE_SPACE_OTHER; curr_char_loc += LE_ESCAPE_LEN; } else if (ch == LE_COMMA_FROM_ARGUMENT) { /* Flag the following comma token as being from a macro argument. */ comma_is_from_argument = TRUE; curr_char_loc += LE_ESCAPE_LEN; #if !FULLY_RESOLVED_MACRO_POSITIONS } else if (ch == LE_END_OF_TOP_LEVEL_EXPANSION) { /* Clear pos_of_macro_invocation to indicate that source positions must once again be calculated instead of being mapped to the position of the macro name. */ pos_of_macro_invocation = null_source_position; curr_char_loc += LE_ESCAPE_LEN; #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ } else if (ch == LE_RAW_OR_EXPANDED_ARGUMENT) { /* Marker put into text to indicate the start of a special sequence that allows either the raw or expanded form of a macro argument to be used, depending on how it is used in the replacement text. See choose_raw_or_expanded_arg for details. */ choose_raw_or_expanded_arg(); curr_char_loc += LE_ESCAPE_LEN; } else if (ch == LE_MICROSOFT_MAGIC_COMMA) { /* Marker for a comma that appeared before an empty __VA_ARGS__ expansion. In normal text, such a comma is suppressed (see adjust_length_for_magic_arg). In a macro argument list, however, such commas are not suppressed. (Note that the character following the escape may not be a ',' when reading macro arguments: in an empty argument, the ',' may be replaced by an ATTENTION_MARKER for a source line modification during the expanded-argument scan. That is why we continue the scan for white space even in a macro argument list when the comma would not be skipped, so that we will step into the empty modification and hit the end-of-insertion marker.) */ curr_char_loc += LE_ESCAPE_LEN; if (in_macro_arg_list) { /* A comma in a macro argument list. Don't skip it, and set a flag indicating that it is magic. */ if (*curr_char_loc == ',') { comma_is_magic = TRUE; } /* if */ } else { /* Normal text: skip over the comma, treating the escape and comma as white space and effectively removing them from the expansion. */ check_assertion(*curr_char_loc == ','); ++curr_char_loc; kind_skipped |= WHITE_SPACE_OTHER; } /* if */ goto white_space_loop; } else if (ch == LE_EMPTY_VARIADIC_MACRO) { /* Marker for an empty expansion of a variadic macro. This is used to support the Microsoft preprocessor feature of suppressing a preceding comma when such an expansion appears in a macro argument list. */ curr_char_loc += LE_ESCAPE_LEN; empty_variadic_macro_seen = TRUE; } else if (ch == LE_LPAREN_FROM_ARGUMENT) { /* Flag the following left parenthesis token as being from a macro argument. */ lparen_is_from_argument = TRUE; curr_char_loc += LE_ESCAPE_LEN; } else if (ch == LE_END_OF_EMBED_PREFIX) { if (generate_pp_output) { gen_pp_output_for_curr_line(embed_control.prefix_loc); } /* if */ /* Start processing the embed file contents and restore the characters overwritten by the lexical escape. */ embed_control.reading_from_buffer = TRUE; *((char *)curr_char_loc) = ')'; *((char *)curr_char_loc + 1) = embed_control.char_following_prefix; goto end_skip; } else if (ch == LE_END_OF_EMBED) { if (generate_pp_output) { gen_pp_output_for_curr_line(embed_control.suffix_loc); } /* if */ /* Return to normal (non-embed) processing. */ *((char *)curr_char_loc) = ')'; *((char *)curr_char_loc + 1) = embed_control.char_following_suffix; curr_char_loc = embed_control.eol_loc; if (embed_control.buf != NULL) { free_general(embed_control.buf, (sizeof_t)embed_control.size); } /* if */ clear_embed_control_block(); } else { unexpected_condition_str("skip_white_space: bad lexical escape"); } /* if */ goto white_space_loop; case '\f': /* Form feed. */ case '\t': /* Horizontal tab. */ case VERTICAL_TAB_CHARACTER: /* These are white space if not in a preprocessing directive. Inside a preprocessing directive, it is implementation-defined whether or not they are replaced by a space (conceptually), and that's what this implementation does, so they are allowed there as well. */ curr_char_loc++; kind_skipped |= WHITE_SPACE_OTHER; goto white_space_loop; #if IGNORE_CARRIAGE_RETURN_IN_SOURCE case '\r': /* Carriage return is treated as white space, as an extension. In strict mode, this is an error. Note that carriage returns immediately preceding newlines are ignored as line terminators during the reading of the source line and never get here. The strings generated for C++/CLI metadata can include carriage return characters. */ if (!cli_or_cx_enabled || !scanning_generated_code_from_metadata) { diagnostic_at_line_pos(strict_ansi_mode ? strict_ansi_discretionary_severity : es_remark, ec_stray_carriage_return, curr_char_loc); } /* if */ curr_char_loc++; kind_skipped |= WHITE_SPACE_OTHER; goto white_space_loop; #endif /* IGNORE_CARRIAGE_RETURN_IN_SOURCE */ case ATTENTION_MARKER: /* Marker placed into source text to provide a cue to the fact that a source modification (probably a text replacement due to a macro expansion) begins here. Find the appropriate source line modification entry, and begin scanning text in that entry. kind_skipped is not set, since this is not white space. */ saved_curr_char_loc = curr_char_loc; go_into_insertion(slmp, curr_char_loc); if (slmp->is_isolated_text) { /* This is a temporary insertion of the text of a macro argument while it is being macro-expanded. The insertion is placed at the end of the argument, and we've run into it while scanning the argument text. Stop here. */ check_assertion(macro_depth != 0); curr_char_loc = saved_curr_char_loc; goto end_skip; } /* if */ if (delete_source_from_loc != NULL) { /* Source from the indicated position to the current position is to be deleted. Add a deletion source line modification and reset the deletion location to the new current position. (We avoid doing this for the macro argument case because such deletions will be either superseded by the expanded text or removed when the scanning of the macro argument is completed.) */ add_deletion_source_line_modif( delete_source_from_loc, saved_curr_char_loc-delete_source_from_loc, /*for_comment=*/FALSE, /*raw_or_exp=*/FALSE); delete_source_from_loc = curr_char_loc; } /* if */ goto white_space_loop; case '/': /* Possible start of a comment. Check for "*" following the "/". (Or another "/" in C++ mode.) */ if (!start_of_comment()) goto end_skip; /* This is the start of a comment. */ if (pcc_preprocessing_mode && !within_curr_source_line(curr_char_loc) && *(curr_char_loc+1) == '*') { /* We're in pcc mode, and the "/ *" token opening characters came from token pasting inside a macro. They are considered to start a strange sort of comment: it's a comment to the compiler but not a comment to the preprocessor. Therefore, we have to skip tokens until the closing delimiter. This only comes up if the user is taking terrible liberties with macro processing. */ /* If the text we're looking at was generated by combining the text of a top-level macro expansion and the text following it on the primary source line to allow for token pasting (see expand_top_level_pcc_macro), and the "/" is in the part of the text from the primary source line, we have a real comment, not a half comment. */ slmp = assoc_source_line_modif(curr_char_loc); if (slmp->text_from_primary_source_line != NULL && curr_char_loc >= slmp->text_from_primary_source_line) { goto normal_comment; } /* if */ skip_pcc_mode_half_comment(); } else if (*(curr_char_loc+1) == '/') { /* C++ comment -- //. Skip to end of line. */ comment_start_loc = curr_char_loc; /* Do not warn about a trailing backslash followed by whitespace. */ pending_nonsplice_backslash = FALSE; /* Advance past the first "/". */ curr_char_loc++; #if MICROSOFT_EXTENSIONS_ALLOWED if (ms_extensions && !ms_std_preproc) { if (!within_curr_source_line(curr_char_loc)) { /* In Microsoft mode, a // comment delimiter can appear in a macro: #define startcomment() /##/ startcomment() This is ignored */ if (macro_depth > 0) { /* We only want to treat this as the start of a comment if we have finished expanding the macro in which it appears. */ curr_char_loc = comment_start_loc; goto end_skip; } /* if */ /* Work outward to the primary source line. */ do { slmp = assoc_source_line_modif(curr_char_loc); leave_insertion(slmp, curr_char_loc); } while (!within_curr_source_line(curr_char_loc)); if (need_to_delete_comment()) { /* The text of the comment needs to be deleted. */ curr_char_loc = comment_start_loc; do { slmp = assoc_source_line_modif(curr_char_loc); /* Find the end of the insertion. */ while (*curr_char_loc != LE_ESCAPE || curr_char_loc[1] != LE_END_OF_INSERTION) { curr_char_loc++; } /* while */ #if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY /* Before deleting the comment, see if it's part of an asm function body -- if so, make a copy of it. */ if (in_asm_function_body && !in_preprocessing_directive) { copy_from_source_to_asm_func_buffer(comment_start_loc, curr_char_loc); } /* if */ #endif /* INCLUDE_COMMENTS_IN_ASM_FUNC_BODY */ /* Delete the comment entirely, but leave the end-of-insertion escape. */ add_deletion_source_line_modif(comment_start_loc, curr_char_loc-comment_start_loc, /*for_comment=*/TRUE, /*raw_or_exp=*/FALSE); if (slmp->being_rescanned_for_token_pasting) { /* If we are rescanning tokens to do the special token-pasting for pcc mode or Microsoft mode, clear the flag on the top-level macro call to indicate we have continued into the primary source line. */ slmp->being_rescanned_for_token_pasting = FALSE; } /* if */ leave_insertion(slmp, curr_char_loc); comment_start_loc = curr_char_loc; } while (!within_curr_source_line(curr_char_loc)); } /* if */ } /* if */ } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Advance to the end of line. */ /* Note that no special processing is required for multibyte characters because the LE_ESCAPE/LE_NEWLINE line terminator cannot occur in multibyte character sequences. */ while (*curr_char_loc != LE_ESCAPE || curr_char_loc[1] != LE_NEWLINE) curr_char_loc++; #if MICROSOFT_EXTENSIONS_ALLOWED /* With the trick of a // comment inside a macro, it's possible that there are no characters of the comment in the primary source line. */ if (curr_char_loc != comment_start_loc) #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Do not insert code here; this is the "then" of an "if". */ { if (need_to_delete_comment()) { #if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY /* Before deleting the comment, see if it's part of an asm function body -- if so, make a copy of it. */ if (in_asm_function_body && !in_preprocessing_directive) { copy_from_source_to_asm_func_buffer(comment_start_loc, curr_char_loc); } /* if */ #endif /* INCLUDE_COMMENTS_IN_ASM_FUNC_BODY */ /* Delete the comment entirely, but leave the newline escape. */ add_deletion_source_line_modif(comment_start_loc, curr_char_loc-comment_start_loc, /*for_comment=*/TRUE, /*raw_or_exp=*/FALSE); } /* if */ } /* if */ } else { normal_comment: #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED suspicious_unicode = FALSE; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ /* C-style comment. */ /* Save the position of the start of the comment. Do not determine the source position yet (it may be costly, and it's only needed if there is an error). */ comment_start_loc = curr_char_loc; comment_pos_determined = FALSE; /* Advance past the "/" and "*". */ curr_char_loc += 2; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Initialize for scanning multibyte characters in the comment. */ mbc_scan_init_if_multibyte_chars_in_source_enabled(); #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ if (!in_preprocessing_directive && !currently_in_pp_if_skip) { /* Look for the special lint comments "notreached", "argsused", and "varargs" (all of those in caps -- written here in lower case letters because UNIX lint finds the keywords even in the middle of comments). Ignore blanks or tabs before the keyword (lint actually allows any white space, and even other identifiers, before the keyword; this compiler doesn't). */ if ((ch = *curr_char_loc) == ' ' || ch == '\t') { do {} while ((ch = *(++curr_char_loc)) == ' ' || ch == '\t'); } /* if */ if (ch == 'N' && curr_char_loc[1] == 'O' && strncmp(curr_char_loc+2, "TREACHED", 8) == 0 && !isalpha((unsigned char)curr_char_loc[10])) { /* The special lint comment "notreached" (in caps), asserting that the code following is unreachable. */ #if DEBUG if (debug_level >= 3) { fprintf(f_debug, "lint NOTREACHED comment\n"); } /* if */ #endif /* DEBUG */ determine_comment_pos_if_not_yet_done(); /* coverity[uninit_use_in_call] -- believes that comment_start_pos may not be initialized, but that can't be true after the use of determine_comment_pos_if_not_yet_done above. */ (void)add_curr_token_pseudo_pragma ((a_pragma_kind)pk_lint_notreached, &comment_start_pos); curr_char_loc += 10; } else if (ch == 'A' && curr_char_loc[1] == 'R' && strncmp(curr_char_loc+2, "GSUSED", 6) == 0 && !isalpha((unsigned char)curr_char_loc[8])) { /* The special lint comment "argsused" (in caps), asserting that the arguments of the function following are all used (or more accurately, that it's okay that they aren't all used). */ #if DEBUG if (debug_level >= 3) { fprintf(f_debug, "lint ARGSUSED comment\n"); } /* if */ #endif /* DEBUG */ determine_comment_pos_if_not_yet_done(); (void)add_curr_token_pseudo_pragma((a_pragma_kind)pk_lint_argsused, &comment_start_pos); curr_char_loc += 8; } else if (ch == 'V' && curr_char_loc[1] == 'A' && strncmp(curr_char_loc+2, "RARGS", 5) == 0 && !isalpha((unsigned char)curr_char_loc[7])) { /* The special lint comment "varargs" (in caps), asserting that the function following takes a variable number of arguments. If a number follows the keyword, it is the number of arguments that are fixed and should always be present and checked; 0 is assumed if the number is omitted. */ a_lint_varargs_count varargs_count = 0; curr_char_loc += 7; varargs_count = 0; /* Skip any blanks, then scan a decimal number. lint itself only looks at one digit. */ while (*curr_char_loc == ' ') curr_char_loc++; while (isdigit((unsigned char)(ch = *curr_char_loc))) { if (varargs_count > LINT_VARARGS_COUNT_MAX / 10) { varargs_count = 0; break; } /* if */ varargs_count *= 10; if (varargs_count > LINT_VARARGS_COUNT_MAX - (ch - '0')) { varargs_count = 0; break; } /* if */ varargs_count += (a_lint_varargs_count)(ch - '0'); curr_char_loc++; } /* while */ #if DEBUG if (debug_level >= 3) { fprintf(f_debug, "lint VARARGS comment, count = %d\n", varargs_count); } /* if */ #endif /* DEBUG */ determine_comment_pos_if_not_yet_done(); a_shared_pending_pragma spp = add_curr_token_pseudo_pragma( pk_lint_varargs_count, &comment_start_pos); spp->variant.lint_varargs_count = varargs_count; } /* if */ } /* if */ /* Scan to the * / marking the end. This may involve reading extra lines, and it is interesting to note that the newlines passed in those cases do not terminate preprocessing directives. */ /* Again, a speed note: All text inside comments goes through this loop, so it should be very fast. */ for (;;) { a_const_char *p_ch = curr_char_loc; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED a_boolean mbc_enabled = multibyte_chars_in_source_enabled; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ while ((ch = *p_ch) != '*' && ch != LE_ESCAPE) { /* Advance to the next character position. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED if (mbc_enabled && char_may_begin_multibyte_sequence(ch)) { /* Advance to the next character, dealing with multibyte characters. */ int mbc_len; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && curr_file_unicode_source_kind != usk_none) { unsigned long uni_ch; a_boolean uni_err; mbc_len = mbc_to_wide_char(p_ch, &uni_ch, &uni_err, /*is_native=*/FALSE); if (!uni_err && uni_ch > 0x2000 && check_for_suspicious_control(uni_ch, /*flag_zero_width=*/FALSE)) { suspicious_unicode = TRUE; } /* if */ } else #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ /* Do not insert code here. */ mbc_len = f_mbc_length(p_ch, (a_boolean *)NULL, /*is_native=*/FALSE); /* Increment curr_char_loc by mbc_len and create any logical character index entries. */ incr_char_loc_for_multibyte_char(p_ch, mbc_len); } else #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Do not insert code here -- this is the "else" of an "if". */ { /* Advance to the next character without worrying about multibyte characters. */ p_ch += 1; } /* if */ } /* while */ if (ch == '*') { /* Check for possible nested comment, issue a warning. This helps catch unclosed comments. */ if (p_ch[-1] == '/') { if (!building_pch_prefix) { /* Only issue this warning during the real compilation, not during the PCH prefix scan. */ warning_at_line_pos(ec_nested_comment, p_ch-1); } /* if */ } /* if */ ch = p_ch[1]; if (ch == '/') { /* End of comment. */ curr_char_loc = p_ch+2; break; } else { /* Skip the '*' and loop again. */ curr_char_loc = p_ch+1; } /* if */ } else { /* ch == LE_ESCAPE */ ch = p_ch[1]; if (ch == LE_NULL) { /* Null (zero) character in comment. Ignored. */ curr_char_loc = p_ch + LE_ESCAPE_LEN; continue; } /* if */ /* End of a line of the comment. */ check_assertion_str(ch == LE_NEWLINE || ch == LE_END_OF_LINE, "skip_white_space: bad lexical escape in comment"); /* Determine the source position for the start of the comment now, before we lose the current source line. */ determine_comment_pos_if_not_yet_done(); #if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY if (in_asm_function_body && !in_preprocessing_directive) { /* Copy the comment text if it's part of an asm function body. */ if (ch == LE_NEWLINE) { p_ch += LE_ESCAPE_LEN; ch = LE_END_OF_LINE; } /* if */ copy_from_source_to_asm_func_buffer(comment_start_loc, p_ch); } /* if */ #endif /* INCLUDE_COMMENTS_IN_ASM_FUNC_BODY */ /* We are supposed to delete the characters of the source line from delete_source_from_loc on, if it is non-NULL. This would be, for example, because we are scanning a macro invocation. */ delete_from = delete_source_from_loc; if (delete_from != NULL) { /* Reset the "from" position for subsequent lines. */ delete_source_from_loc = curr_source_line; delete_only_for_comment = FALSE; } else if (need_to_delete_comment()) { /* Delete the characters of the comment if writing preprocessor output with the comments deleted. The newline will be kept, and therefore the comment is deleted entirely rather than replaced by one blank -- since there is already white space here in the form of the newline. */ delete_from = comment_start_loc; delete_only_for_comment = TRUE; } /* if */ if (delete_from != NULL) { /* Delete the required characters. Leave the newline and line-end lexical escape sequences. If this comment is part of a preprocessing directive, delete the newline as well so that multi-line directives will become one-line directives. */ if (in_preprocessing_directive && ch == LE_NEWLINE) { delete_to = p_ch+LE_ESCAPE_LEN; } else { delete_to = p_ch; } /* if */ add_deletion_source_line_modif( delete_from, delete_to-delete_from, /*for_comment=*/delete_only_for_comment, /*raw_or_exp=*/FALSE); } /* if */ /* Read a new line. Note the parameter asking that the input stack not be popped, since we need to know about ends of files (it is an error for a comment to be unclosed at the end of the file in which it was opened). If we are processing command-line or predefined macros, we shouldn't attempt to read another line. Do not warn about a backslash followed by whitespace on the line we are leaving. */ pending_nonsplice_backslash = FALSE; curr_char_loc = p_ch; if (curr_cmd_line_or_predef_macro_def != NULL || read_logical_source_line(/*do_pop_on_end_of_file=*/FALSE, /*extend_current_line=*/FALSE)) { /* End of file encountered, unclosed comment. */ if (!building_pch_prefix) { /* Only issue this warning during the real compilation, not during the PCH prefix scan. */ error_position = comment_start_pos; pos_error(ec_comment_unclosed_at_eof, &error_position); } /* if */ /* Consider the comment closed. */ goto end_of_comment; } /* if */ curr_line_began_inside_comment = TRUE; /* Reset the start of comment location for subsequent lines. */ comment_start_loc = curr_source_line; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Initialize for scanning multibyte characters in the comment. */ mbc_scan_init_if_multibyte_chars_in_source_enabled(); #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && curr_file_unicode_source_kind != usk_none) { /* Only the last line of a comment needs to be considered when determining suspicious bidirectional controls. */ suspicious_unicode = FALSE; pop_bidi_control(/*flush=*/TRUE); } /* if */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ } /* if */ } /* for */ if (need_to_delete_comment() && delete_source_from_loc == NULL) { #if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY /* Before deleting the comment, see if it's part of an asm function body -- if so, make a copy of it. */ if (in_asm_function_body && !in_preprocessing_directive) { copy_from_source_to_asm_func_buffer(comment_start_loc, curr_char_loc); } /* if */ #endif /* INCLUDE_COMMENTS_IN_ASM_FUNC_BODY */ /* Delete the characters of the comment if writing preprocessor output with the comments deleted. Under ANSI rules, the comment must be replaced by one space if there is no other white space here (2.1.1.2, phase 3); we replace it with a blank always. Under pcc rules, the comment is always deleted entirely. The deletion here is suppressed if we are deleting everything up to this point anyway (delete_source_from_loc != NULL). */ if (pcc_preprocessing_mode) { /* pcc mode; delete the comment entirely. */ add_deletion_source_line_modif(comment_start_loc, curr_char_loc-comment_start_loc, /*for_comment=*/TRUE, /*raw_or_exp=*/FALSE); } else { /* ANSI mode; replace the comment with a space. */ replace_source_string_by_space(comment_start_loc, (sizeof_t)(curr_char_loc-comment_start_loc)); } /* if */ } /* if */ end_of_comment:; } /* if */ #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && multibyte_chars_in_source_enabled && curr_file_unicode_source_kind != usk_none && (pending_bidi_controls != NULL || suspicious_unicode)) { /* We either had an unterminated bidirectional control or the bidirectional nesting rules were violated. Warn about it and empty the stack in preparation for the next comment or string. */ /* coverity[var_deref_model] */ warning_at_line_pos(ec_suspicious_comment_formatting, comment_start_loc); pop_bidi_control(/*flush=*/TRUE); } /* if */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ /* Remember that a comment was skipped in white space. Note that this must be done after the test of kind_skipped above. */ kind_skipped |= WHITE_SPACE_COMMENTS; goto white_space_loop; #if READ_SOURCE_IN_BINARY_MODE_FOR_MSDOS case CONTROL_Z: if (microsoft_mode && curr_ise != NULL) { /* In this mode, a ctrl-Z (0x1a) appearing in a file where a token start is expected (i.e., not in a comment or string literal and not on the command line or in the predefined macros file) is treated as an end-of-file marker. */ eof_read_on_curr_input_stream = TRUE; ch = LE_END_OF_LINE; goto end_of_current_line; } /* if */ FALLTHROUGH #endif /* READ_SOURCE_IN_BINARY_MODE_FOR_MSDOS */ default: /* For all non-white-space characters, do nothing and return. */ ; } /* switch */ end_skip: /* "Return" the mask of kinds of white space skipped. */ kind_of_white_space_skipped = kind_skipped; #undef determine_comment_pos_if_not_yet_done } /* skip_white_space */ /* Valid characters (depending on the kind of number being scanned) that can follow a digit separator (apostrophe). */ static constexpr a_const_char *dig_or_nondig = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; static constexpr a_const_char *binary_dig = "01"; static constexpr a_const_char *decimal_dig = "0123456789"; static constexpr a_const_char *hex_dig = "0123456789ABCDEFabcdef"; static int bit_precise_literal_suffix_length(a_const_char *suffix) /* Return the length of the bit-precise integer literal suffix starting at suffix, or zero if no such suffix is present. The "wb" portion must be consistently lowercase or uppercase. */ { int result = 0; if (standard_bit_precise_literal_suffix_allowed()) { if ((suffix[0] == 'w' && suffix[1] == 'b') || (suffix[0] == 'W' && suffix[1] == 'B')) { result = 2; } else if ((suffix[0] == 'u' || suffix[0] == 'U') && ((suffix[1] == 'w' && suffix[2] == 'b') || (suffix[1] == 'W' && suffix[2] == 'B'))) { result = 3; } /* if */ } /* if */ if (result == 0 && prefixed_bit_precise_literal_suffix_allowed() && suffix[0] == '_' && suffix[1] == '_') { a_const_char *start = suffix + 2; if ((start[0] == 'w' && start[1] == 'b') || (start[0] == 'W' && start[1] == 'B')) { result = 4; } else if ((start[0] == 'u' || start[0] == 'U') && ((start[1] == 'w' && start[2] == 'b') || (start[1] == 'W' && start[2] == 'B'))) { result = 5; } /* if */ } /* if */ return result; } /* bit_precise_literal_suffix_length */ static a_token_kind scan_number(void) /* Scan a numeric token (integer, fixed-point, or floating constant). Return the kind of token. */ { char ch; enum {k_decimal, k_octal, k_hex, k_binary, #if FIXED_POINT_ALLOWED k_fixed_point, #endif /* FIXED_POINT_ALLOWED */ k_float} kind; a_token_kind ctoken = tok_error; a_boolean err = FALSE; a_const_char *err_pos; an_error_code err_code; a_boolean is_hex_fp_value = FALSE; a_boolean any_hex_digits = FALSE; a_boolean u_suffix_seen = FALSE; a_boolean bit_precise_suffix_seen = FALSE; a_boolean local_allow_hex_fp_constants; int l_suffix_seen = 0; int bit_precise_suffix_len; a_boolean z_suffix_seen = FALSE; a_boolean single_l_confirmed = FALSE; #if FIXED_POINT_ALLOWED a_boolean l_before_u_suffix = FALSE; a_boolean fixed_point_ruled_out = FALSE; #endif /* FIXED_POINT_ALLOWED */ #if C99_IL_EXTENSIONS_SUPPORTED a_boolean imaginary_literal = FALSE; #endif /* C99_IL_EXTENSIONS_SUPPORTED */ a_const_char *first_non_id_char = NULL; a_boolean potential_ud_suffix = FALSE; a_source_position start_pos; a_boolean first_digit_seen = FALSE; a_const_char *possible_start_of_ud_suffix = NULL; a_boolean decimal_point_seen = FALSE; a_const_char *valid_chars = fetch_pp_tokens ? dig_or_nondig : decimal_dig; a_const_char *separator_diag_issued_for = NULL; a_boolean valid_sep; a_boolean prefer_udl_over_imag_suffix = user_defined_literals_enabled && (clang_mode || gnu_version_is(>=80100)); a_boolean tentative_udl_lookup = FALSE; sizeof_t id_len = 0; an_error_severity sev = es_error; a_boolean accept_f_suffix = extended_float_types || (gcc_version_is(>= 70000) || gpp_version_is(>= 130000)); a_boolean accept_bf16_suffix = gnu_version_is(>= 130000); int char_bytes; a_const_char *start_of_invalid_suffix = NULL; /* Macro to skip over an optional C++14 digit separator (apostrophe). Reports a warning if an apostrophe is seen when digit separators are not enabled (to explain the inevitable syntax error that will follow, but suppressed in C modes in which they are not enabled and in skipped conditionally-compiled code, where digit separators are either not expected or aren't considered) and an error if a digit separator appears immediately following a prefix or radix point (as indicated by the value of first_digit_seen) or at the end of the number (as indicated by the next character not being part of valid_chars). N is either 0 or 1, indicating whether the character to be tested is at curr_char_loc or the following position. */ /*lint -emacro(835,skip_digit_separator)*/ #define skip_digit_separator(N) \ if (!currently_in_pp_if_skip && *(curr_char_loc + (N)) == '\'') { \ if (!digit_separators_enabled && (!C_mode() || c23_mode)) { \ warning_at_line_pos(ec_digit_separators_not_enabled, \ curr_char_loc + (N)); \ } else if (digit_separators_enabled) { \ valid_sep = (strchr(valid_chars, *(curr_char_loc + (N) + 1)) != NULL); \ if (!valid_sep && fetch_pp_tokens) { \ /* End the token, i.e., don't skip the apostrophe: the following */ \ /* character does not satisfy the pp-number syntax, but that is */ \ /* not necessarily an error. */ \ } else { \ number_contains_digit_separator = TRUE; \ if ((!first_digit_seen || !valid_sep) && \ separator_diag_issued_for != curr_char_loc + (N)) { \ error_at_line_pos(ec_bad_digit_separator_pos, curr_char_loc + (N)); \ err = TRUE; \ separator_diag_issued_for = curr_char_loc + (N); \ } /* if */ \ /* Skip over the apostrophe. */ \ ++curr_char_loc; \ } /* if */ \ } /* if */ \ } /* if */ macro_line_loc_to_source_pos(curr_char_loc, start_pos); /* Hexadecimal floating point constants are normally controlled by the hex_floating_point_constants_allowed variable, but should also be allowed when scanning C++/CLI metadata. */ local_allow_hex_fp_constants = hex_floating_point_constants_allowed || scanning_generated_code_from_metadata; /* Collect the characters of the constant, and figure out where it ends. In the process, figure out what kind of token it is. This is done according to the syntax for integer constants (C++11 Standard 2.14.2 [lex.icon]) and floating constants (2.14.4 [lex.fcon]), rather than according to the pp-number syntax (2.10 [lex.ppnumber]). */ kind = k_decimal; number_contains_digit_separator = FALSE; if (*curr_char_loc == '0') { /* First digit is a zero. This is probably an octal constant (like 0777) or a hex constant (like 0xfff), but it could also be a solitary 0 (which is octal, though that doesn't matter), or a floating constant that begins with 0. */ if ((ch = *(curr_char_loc+1)) == 'x' || ch == 'X') { /* 0x... or 0X..., hexadecimal. */ kind = k_hex; valid_chars = fetch_pp_tokens ? dig_or_nondig : hex_dig; /* The hex constant stops on a non-hex digit. */ curr_char_loc++; skip_digit_separator(1); while (isxdigit((unsigned char)*(++curr_char_loc))) { any_hex_digits = TRUE; first_digit_seen = TRUE; skip_digit_separator(1); } /* while */ /* Check for floating point. */ if (local_allow_hex_fp_constants || fixed_point_enabled) { /* C99 permits floating point constants specified in hexadecimal. */ if ((ch = *curr_char_loc) == '.') goto float_accum_1; if (ch == 'p' || ch == 'P') goto float_accum_2; } /* if */ /* Check for just "0x" by itself. pcc allows this and interprets it as zero. */ if (curr_char_loc == start_of_curr_token+2 && !fetch_pp_tokens) { if (C_dialect != C_dialect_pcc) { error_at_line_pos(ec_bad_hex_digit, start_of_curr_token); err = TRUE; } else { warning_at_line_pos(ec_bad_hex_digit, start_of_curr_token); } /* if */ } /* if */ } else if (binary_literals_allowed && (ch == 'b' || ch == 'B')) { /* A binary literal. */ a_boolean any_digits = FALSE; kind = k_binary; valid_chars = fetch_pp_tokens ? dig_or_nondig : binary_dig; curr_char_loc++; /* Scan any digits. If a digit is not "0" or "1", an error will be issued later. */ skip_digit_separator(1); while (isdigit((unsigned char)*(++curr_char_loc))) { any_digits = TRUE; first_digit_seen = TRUE; skip_digit_separator(1); } /* while */ if (!any_digits && !fetch_pp_tokens) { /* No digits were found after the "0b" or "0B". */ error_at_line_pos(ec_bad_binary_digit, start_of_curr_token); err = TRUE; } /* if */ #if C99_IL_EXTENSIONS_SUPPORTED } else if (gnu_imaginary_literals_allowed && (ch == 'i' || ch == 'I' || ch == 'j' || ch == 'J')) { /* A GNU imaginary literal 0 (e.g., "0i"). We do not generally support imaginary integer literals, but for "0i" we issue a discretionary error and proceed as if it were a "_Complex double" literal. */ ++curr_char_loc; if (prefer_udl_over_imag_suffix) { /* g++ and clang treat something like 5i as a user-defined literal if a literal operator with that suffix has been declared and as an imaginary literal otherwise. We'll try this as a ud-suffix first and rescan it as an imaginary suffix if the lookup fails. */ potential_ud_suffix = TRUE; tentative_udl_lookup = TRUE; } else { if (!fetch_pp_tokens) { diagnostic_at_line_pos(es_discretionary_error, ec_complex_integral_type, curr_char_loc); } /* if */ goto end_float_accum; } /* if */ #endif /* C99_IL_EXTENSIONS_SUPPORTED */ } else { /* Octal or floating point. Accumulate digits. Digits 8 and 9 are valid in floating point, but in octal they are only valid in pcc mode. That is checked later. For now, the "8" and "9" are accumulated. */ first_digit_seen = TRUE; do { skip_digit_separator(1); } while (isdigit((unsigned char)*(++curr_char_loc))); /* Check for floating point. */ if ((ch = *curr_char_loc) == '.') goto float_accum_1; if (ch == 'e' || ch == 'E') goto float_accum_2; /* Definitely octal. */ kind = k_octal; } /* if */ } else if (*curr_char_loc == '.') { /* Number starting with ".". The character following the "." must be a digit (already checked by get_token). */ if (microsoft_bugs && microsoft_version <= 1200 && isdigit((unsigned char)curr_char_loc[1]) && curr_char_loc[2] == '.') { /* The Microsoft compiler accepts constants like ".1.234". */ curr_char_loc += 2; warning_at_line_pos(ec_extra_chars_on_number, curr_char_loc); } /* if */ goto float_accum_1; } else { /* Number not starting with "0" or ".". Could be a decimal integer or a floating-point number. Accumulate the initial digit sequence. */ first_digit_seen = TRUE; do { skip_digit_separator(1); } while (isdigit((unsigned char)*(++curr_char_loc))); /* A ".", "e", or "E" now indicates a floating-point constant. */ if ((ch = *curr_char_loc) == '.') goto float_accum_1; if (ch == 'e' || ch == 'E') goto float_accum_2; #if C99_IL_EXTENSIONS_SUPPORTED if (gnu_imaginary_literals_allowed && (ch == 'i' || ch == 'I' || ch == 'j' || ch == 'J') && !is_id_char[*(curr_char_loc+1)-CHAR_MIN]) { if (prefer_udl_over_imag_suffix) { /* g++ and clang treat something like 5i as a user-defined literal if a literal operator with that suffix has been declared and as an imaginary literal otherwise. We'll try this as a ud-suffix first and come back here if the lookup fails. */ potential_ud_suffix = TRUE; tentative_udl_lookup = TRUE; } else { int_imaginary_suffix: /* A GNU imaginary literal of integral type (e.g., "12i"). We do not generally support imaginary integer literals, but for decimal integers with an imaginary suffix we issue a discretionary error and proceed as if it were a "_Complex double" literal. curr_char_loc points to the first (or only) character of the suffix. */ char saved_ch; if (!fetch_pp_tokens) { diagnostic_at_line_pos(es_discretionary_error, ec_complex_integral_type, curr_char_loc); } /* if */ /* Include the first character of the suffix in the token and replace it with 'i' (in case the suffix contained integer-suffix characters, e.g., "0ulli") and convert the token to a complex double value. */ end_of_curr_token = curr_char_loc; saved_ch = *end_of_curr_token; *(char *)end_of_curr_token = 'i'; conv_float_literal(kind == k_hex, &err_code, &start_of_curr_token, &sev); ctoken = tok_float_constant; /* Restore the original character in the suffix and include the entire suffix in the token. Set up to continue the scan following the entire suffix. */ *(char *)end_of_curr_token = saved_ch; if (id_len != 0) { end_of_curr_token += id_len - 1; } /* if */ curr_char_loc = end_of_curr_token + 1; goto done; } /* if */ } /* if */ #endif /* C99_IL_EXTENSIONS_SUPPORTED */ /* Definitely a decimal integer. */ kind = k_decimal; } /* if */ /* Integer constant of some kind. Check for an integer suffix: an optional "u/U" and/or an optional "l/L", "ll/LL" (if LONG_LONG_ALLOWED is TRUE), or "z/Z" (if size_suffix_enabled is TRUE). These can appear in either order. */ possible_start_of_ud_suffix = curr_char_loc; for (;; curr_char_loc++) { ch = *curr_char_loc; bit_precise_suffix_len = bit_precise_literal_suffix_length(curr_char_loc); if (!bit_precise_suffix_seen && l_suffix_seen == 0 && !z_suffix_seen && bit_precise_suffix_len != 0) { curr_char_loc += bit_precise_suffix_len - 1; bit_precise_suffix_seen = TRUE; #if FIXED_POINT_ALLOWED fixed_point_ruled_out = TRUE; #endif /* FIXED_POINT_ALLOWED */ } else if ((ch == 'u' || ch == 'U') && !u_suffix_seen && !bit_precise_suffix_seen) { u_suffix_seen = TRUE; if (l_suffix_seen == 1) { /* Don't accept another "l" after "u" ("lul"). */ single_l_confirmed = TRUE; } /* if */ #if FIXED_POINT_ALLOWED l_before_u_suffix = (l_suffix_seen > 0); #endif /* FIXED_POINT_ALLOWED */ } else if ((ch == 'l' || ch == 'L') && !single_l_confirmed && !z_suffix_seen && !bit_precise_suffix_seen && #if LONG_LONG_ALLOWED l_suffix_seen < 2 #else /* !LONG_LONG_ALLOWED */ l_suffix_seen < 1 #endif /* LONG_LONG_ALLOWED */ ) { l_suffix_seen++; } else if (size_suffix_enabled && (ch == 'z' || ch == 'Z') && !z_suffix_seen && l_suffix_seen == 0 && !bit_precise_suffix_seen) { z_suffix_seen = TRUE; if (l_suffix_seen == 1) { /* Dont accept another "l" after "z" ("lzl"). */ single_l_confirmed = TRUE; } /* if */ } else { break; } /* if */ } /* for */ #if LONG_LONG_ALLOWED if (strict_ansi_mode && !long_long_is_standard && !fetch_pp_tokens && l_suffix_seen == 2) { /* "long long" type is nonstandard. */ diagnostic_at_line_pos(strict_ansi_discretionary_severity, ec_nonstd_long_long, start_of_curr_token); } /* if */ #endif /* LONG_LONG_ALLOWED */ if (ms_extensions && !gnu_mode && l_suffix_seen == 0 && (*curr_char_loc == 'i' || *curr_char_loc == 'I') && isdigit((unsigned char)curr_char_loc[1])) { /* The Microsoft compiler allows a suffix like "i32" indicating a 32-bit integer. "ui32" indicates an unsigned 32-bit integer (for that case, the "u" was scanned above). */ do { curr_char_loc++; } while (isdigit((unsigned char)(*curr_char_loc))); #if FIXED_POINT_ALLOWED fixed_point_ruled_out = TRUE; #endif /* FIXED_POINT_ALLOWED */ } /* if */ if (possible_start_of_ud_suffix == curr_char_loc) { /* No numeric suffix was seen. */ possible_start_of_ud_suffix = NULL; } /* if */ goto fixed_point_suffix; float_accum_1: /* At the decimal point in a floating constant. Take whatever digits follow it. The kind variable indicates the kind of digits that are being used (hex or decimal). */ decimal_point_seen = TRUE; first_digit_seen = FALSE; if (kind == k_hex) { skip_digit_separator(1); while (isxdigit((unsigned char)*(++curr_char_loc))) { any_hex_digits = TRUE; first_digit_seen = TRUE; skip_digit_separator(1); } /* while */ if (!any_hex_digits && !fetch_pp_tokens) { /* No hex digits were specified. Something like "0x.". */ error_at_line_pos(ec_bad_float_constant, curr_char_loc); any_hex_digits = TRUE; } /* if */ } else { do { skip_digit_separator(1); first_digit_seen = TRUE; } while (isdigit((unsigned char)*(++curr_char_loc))); if (*curr_char_loc == '.' && kind == k_decimal && !fetch_pp_tokens && clang_mode) { /* In some clang attributes (notably the "availability" attribute), attribute values can take "version numbers" (e.g., 10.12.1). Parse such entities as strings in a tok_clang_version token. Note that other "version number" values (e.g., 10.12 or 10) will be treated as floating-point or integer constants, so attribute parsing needs to be able to handle all of these. */ do { /* No need to skip digit separators here. */ /* Assume version numbers have only decimal digits. */ } while (isdigit((unsigned char)*(++curr_char_loc)) || *curr_char_loc == '.'); end_of_curr_token = curr_char_loc - 1; conv_string_literal(start_of_curr_token, end_of_curr_token + 1, SCLK_ORDINARY_STRING_LITERAL, (unsigned long)(end_of_curr_token - start_of_curr_token + 1), &err_code, &err_pos); ctoken = tok_clang_version; #if DEBUG if (debug_level >= 4) { fprintf(f_debug, "Numeric token = \"%.*s\", kind = clang_version\n", (int)(end_of_curr_token - start_of_curr_token + 1), start_of_curr_token); } /* if */ #endif /* DEBUG */ goto done; } /* if */ } /* if */ /* Check for the presence of an exponent. */ if (kind == k_hex) { if ((ch = *curr_char_loc) != 'p' && ch != 'P') { if (!fetch_pp_tokens) { /* A missing binary suffix -- this is an error. */ error_at_line_pos(ec_bad_float_constant, curr_char_loc); err = TRUE; goto end_float_accum; } else { /* When we encounter this error while fetching pp-tokens, back up one character so that the tests below that look at the next character don't inadvertently run off the end of the constant. */ curr_char_loc--; } /* if */ } /* if */ } else { if ((ch = *curr_char_loc) != 'e' && ch != 'E') goto end_float_accum; } /* if */ float_accum_2: /* At the "e" or "E" indicating the start of the exponent of a floating constant (or the "p" or "P" for a floating point constant specified as a hexadecimal value). Take an optional sign, then decimal digits of the exponent. */ if (kind == k_hex && !any_hex_digits && !fetch_pp_tokens) { /* No hex digits were specified. Something like "0xp0". */ error_at_line_pos(ec_bad_float_constant, curr_char_loc); } /* if */ /* The "e" or "E" might be the start of a ud-suffix if no exponent follows, so record its location. */ possible_start_of_ud_suffix = curr_char_loc; if ((ch = *(curr_char_loc+1)) == '+' || ch == '-') curr_char_loc++; first_digit_seen = FALSE; skip_digit_separator(1); if (!isdigit((unsigned char)*(curr_char_loc+1)) && !fetch_pp_tokens) { /* No digits of the exponent are present. */ if (user_defined_literals_enabled) { /* If user-defined literals are enabled, the "e" or "E" could be a ud-suffix, so we report no error here. */ if (!decimal_point_seen) { /* The only indication that this was a floating-point number was the exponent, but that's potentially a ud-suffix, so treat this as an integer. */ goto fixed_point_suffix; } else { /* Restore the current character to be the sign character, if any, and treat the number before the "e" or "E" as the entire floating-point number. */ curr_char_loc = possible_start_of_ud_suffix; goto end_float_accum; } /* if */ } else if (C_dialect == C_dialect_pcc) { /* pcc treats this as an exponent of zero, so just issue a warning. */ warning_at_line_pos(ec_bad_float_constant, curr_char_loc+1); } else { error_at_line_pos(ec_bad_float_constant, curr_char_loc+1); err = TRUE; } /* if */ } else { /* We have an exponent, so the "e" or "E" is not the start of a ud-suffix. */ possible_start_of_ud_suffix = NULL; } /* if */ do { skip_digit_separator(1); first_digit_seen = TRUE; } while (isdigit((unsigned char)*(++curr_char_loc))); end_float_accum: is_hex_fp_value = kind == k_hex; kind = k_float; /* Check for a final suffix of "f" or "l", in upper or lower case. In GNU configurations, also accept the "i" or "j" suffix that denotes an imaginary value (it can appear before or after the "f" or "l" suffix). */ #if C99_IL_EXTENSIONS_SUPPORTED if (gnu_imaginary_literals_allowed && ((ch = *curr_char_loc) == 'i' || ch == 'I' || ch == 'j' || ch == 'J')) { if (prefer_udl_over_imag_suffix) { /* g++ and clang treat something like 0.5il as a user-defined literal if a literal operator with that suffix has been declared and as an imaginary literal otherwise. We'll try this as a ud-suffix first and come back here with prefer_udl_over_imag_suffix set to FALSE if the lookup fails. */ possible_start_of_ud_suffix = curr_char_loc; potential_ud_suffix = TRUE; tentative_udl_lookup = TRUE; } else { imaginary_literal = TRUE; } /* if */ ++curr_char_loc; #if FIXED_POINT_ALLOWED fixed_point_ruled_out = TRUE; #endif /* FIXED_POINT_ALLOWED */ } /* if */ #endif /* C99_IL_EXTENSIONS_SUPPORTED */ if ((ch = *curr_char_loc) == 'f' || ch == 'F' || ch == 'l' || ch == 'L' || (float80_enabled && (ch == 'w' || ch == 'W')) || (float128_enabled && (ch == 'q' || ch == 'Q')) || ((extended_float_types || accept_bf16_suffix) && (ch == 'b' || ch == 'B'))) { if (possible_start_of_ud_suffix == NULL) { possible_start_of_ud_suffix = curr_char_loc; } /* if */ curr_char_loc++; if (accept_f_suffix || float16_enabled) { a_const_char *p = curr_char_loc; if ((extended_float_types || accept_bf16_suffix) && (ch == 'b' || ch == 'B')) { if (((ch == 'b' && *p == 'f') || (ch == 'B' && *p == 'F')) && p[1] == '1' && p[2] == '6') { /* bf16 */ curr_char_loc += 3; } else { potential_ud_suffix = TRUE; --curr_char_loc; } /* if */ } else if (ch == 'f' || ch == 'F') { if ((*p == '1' && p[1] == '6')) { /* f16 */ if (gpp_version_is(<130000)) { /* Although g++ began accepting the _Float16 type in version 12.1, it did not accept the f16 suffix until version 13.1. */ potential_ud_suffix = TRUE; --curr_char_loc; } else { curr_char_loc += 2; } /* if */ } else if (accept_f_suffix && ((*p == '3' && p[1] == '2') || (*p == '6' && p[1] == '4'))) { if (p[2] == 'x') { /* f32x or f64x */ curr_char_loc += 3; } else { /* f32 or f64 */ curr_char_loc += 2; } /* if */ } else if (accept_f_suffix && *p == '1' && p[1] == '2' && p[2] == '8') { curr_char_loc += 3; } /* if */ } /* if */ } /* if */ #if FIXED_POINT_ALLOWED if (ch == 'l' || ch == 'L') { ++l_suffix_seen; } else { fixed_point_ruled_out = TRUE; } /* if */ #endif /* FIXED_POINT_ALLOWED */ } /* if */ #if C99_IL_EXTENSIONS_SUPPORTED if (gnu_imaginary_literals_allowed && !imaginary_literal && ((ch = *curr_char_loc) == 'i' || ch == 'I' || ch == 'j' || ch == 'J')) { if (prefer_udl_over_imag_suffix) { potential_ud_suffix = TRUE; tentative_udl_lookup = TRUE; } else { imaginary_literal = TRUE; } /* if */ ++curr_char_loc; #if FIXED_POINT_ALLOWED fixed_point_ruled_out = TRUE; #endif /* FIXED_POINT_ALLOWED */ } /* if */ #endif /* C99_IL_EXTENSIONS_SUPPORTED */ fixed_point_suffix: #if FIXED_POINT_ALLOWED if (fixed_point_enabled && !fixed_point_ruled_out) { a_const_char *start_of_fixed_suffix = curr_char_loc; a_boolean h_suffix_seen = FALSE; /* Check for a fixed-point constant suffix, which must be of the following general form ([] = optional, {} = required): [ u | U ] [ h | H | l | L ] { k | K | r | R } - 'u' or 'U' indicates an unsigned type. - 'h' or 'H' indicates "short" precision. - 'l' or 'L' indicates "long" precision. - 'k' or 'K' indicates an _Accum type. - 'r' or 'R' indicates a _Fract type. */ ch = *curr_char_loc; if (!u_suffix_seen && (ch == 'u' || ch == 'U')) { /* The 'u' or 'U' might have appeared while scanning what at first seemed like an integer literal. Don't allow a second one. */ curr_char_loc++; ch = *curr_char_loc; u_suffix_seen = TRUE; l_before_u_suffix = (l_suffix_seen > 0); } /* if */ if (l_suffix_seen == 0 && (ch == 'l' || ch == 'L')) { /* The 'l' or 'L' might have appeared while scanning what at first seemed like an integer or floating-point literal. Don't allow an additional one. */ curr_char_loc++; ch = *curr_char_loc; ++l_suffix_seen; } /* if */ if (l_suffix_seen == 0 && (ch == 'h' || ch == 'H')) { h_suffix_seen = TRUE; curr_char_loc++; ch = *curr_char_loc; } /* if */ if (ch == 'k' || ch == 'K' || ch == 'r' || ch == 'R') { curr_char_loc++; kind = k_fixed_point; } /* if */ if ((kind == k_fixed_point && l_before_u_suffix) && !fetch_pp_tokens) { /* Unlike with integer literals, any 'u'/'U' suffix must precede any 'l'/'L' suffix in fixed-point literals. */ diagnostic_at_line_pos(es_discretionary_error, ec_nonstd_fixed_point_suffix, start_of_curr_token); } /* if */ if (kind == k_float && (u_suffix_seen || h_suffix_seen) && !fetch_pp_tokens) { error_at_line_pos(ec_bad_float_or_fixed_suffix, start_of_curr_token); start_of_invalid_suffix = start_of_fixed_suffix; } /* if */ } /* if */ #endif /* FIXED_POINT_ALLOWED */ /* Here, start_of_curr_token marks the beginning, and curr_char_loc one past the end of the constant. kind is set correctly. The suffix (if any) has been accumulated. */ end_of_curr_token = curr_char_loc - 1; #if DEBUG if (debug_level >= 4) { a_const_char *ks; switch (kind) { case k_decimal: ks = "decimal"; break; case k_octal: ks = "octal"; break; case k_hex: ks = "hex"; break; case k_binary: ks = "binary"; break; #if FIXED_POINT_ALLOWED case k_fixed_point: ks = "fixed-point"; break; #endif /* FIXED_POINT_ALLOWED */ case k_float: ks = "float"; break; default: ks = ""; } /* switch */ fprintf(f_debug, "Numeric token = \"%.*s\", kind = %s\n", (int)(end_of_curr_token - start_of_curr_token + 1), start_of_curr_token, ks); } /* if */ #endif /* DEBUG */ /* See if there are more characters that would have been part of this number had it been scanned as a pp-number. The pp-number syntax is (C++11 Standard 2.10 [lex.ppnumber], C99 Standard 6.4.8): pp-number: digit . digit pp-number digit pp-number identifier-nondigit pp-number e sign pp-number E sign pp-number p sign (C99) pp-number P sign (C99) pp-number . digit is any decimal digit (0-9). identifier-nondigit is an alphabetic, "_", or UCN. */ /* The decision for whether to scan for a pp-number is complex. We never do so in pcc mode, but we always do so in strict and GNU modes. In other modes, we generally do so only when pp-tokens are explicitly requested, i.e., when fetch_pp_tokens is TRUE, for example, when handling preprocessor directives and expanding macros. Unfortunately, however, that flag is always TRUE when producing preprocessing output, regardless of the context, so we check explicitly for those cases when generate_pp_output is TRUE. Further complicating the situation is the fact that the Microsoft compiler behaves differently when compiling and when producing preprocessing output, scanning for pp-numbers in the latter case but not the former, so in Microsoft mode we always scan for pp-numbers when fetch_pp_tokens is TRUE, regardless of the value of generate_pp_output. In addition, if C++11 user-defined literals are enabled, we use the pp-number scan to check for a potential ud-suffix. */ if ((strict_ansi_mode || gnu_mode || (user_defined_literals_enabled #if FIXED_POINT_ALLOWED && kind != k_fixed_point #endif /* FIXED_POINT_ALLOWED */ ) || (fetch_pp_tokens && (!generate_pp_output || microsoft_mode || in_preprocessing_directive || macro_depth > 0))) && C_dialect != C_dialect_pcc) { a_boolean part_of_pp_num; char prev_ch = *(curr_char_loc - 1); first_digit_seen = TRUE; do { if (digit_separators_enabled) { /* We must not invoke skip_digit_separator here when digit_separators_enabled is FALSE because an apostrophe will have terminated the number with a warning, leaving curr_char_loc pointing to the apostrophe, and we do not want to repeat the warning. */ skip_digit_separator(0); } /* if */ ch = *curr_char_loc; part_of_pp_num = FALSE; if (is_identifier_char(curr_char_loc, &char_bytes, curr_char_loc == end_of_curr_token + 1)) { /* An identifier character is part of a pp-number. */ part_of_pp_num = TRUE; if (curr_char_loc == end_of_curr_token + 1) { potential_ud_suffix = TRUE; } /* if */ curr_char_loc += char_bytes; } else { /* This is a non-identifier character. */ if (ch == '.' || isdigit((unsigned char)ch)) { /* A decimal point or digit is part of a pp-number. */ part_of_pp_num = TRUE; } else if (ch == '+' || ch == '-') { /* A sign character is part of a pp-number if it follows an exponent indicator. Note that Microsoft mode has a wrinkle: MSVC accepts a construct like 0xE+0x1 as equivalent to 0xE + 0x1, so if the E or e is part of a hexadecimal literal in Microsoft mode, the sign character terminates the number instead of being part of the pp-number. */ if (((prev_ch == 'e' || prev_ch == 'E') && !(microsoft_mode && kind == k_hex)) || ((local_allow_hex_fp_constants || fixed_point_enabled) && (prev_ch == 'p' || prev_ch == 'P'))) { part_of_pp_num = TRUE; } /* if */ } /* if */ if (part_of_pp_num) { if (first_non_id_char == NULL) { /* Remember the location for possible use in a diagnostic below. */ first_non_id_char = curr_char_loc; } /* if */ ++curr_char_loc; } /* if */ } /* if */ prev_ch = ch; } while (part_of_pp_num); #if DEBUG if (debug_level >= 4) { if (curr_char_loc != (end_of_curr_token + 1)) { fprintf(f_debug, "Extra pp-number text: \"%.*s\"\n", (int)(curr_char_loc - end_of_curr_token - 1), end_of_curr_token+1); } /* if */ } /* if */ #endif /* DEBUG */ } else if (C_dialect == C_dialect_pcc && fetch_pp_tokens) { /* pcc mode has its own variant of this. Consider #define a 7 int i = 1a; The "a" in "1a" should not be treated as a separate token. */ while (is_id_char[(ch = *curr_char_loc)-CHAR_MIN]) { /* Alphanumeric character. Keep accumulating. */ curr_char_loc++; } /* while */ } /* if */ if (fetch_pp_tokens) { /* Raw pp-tokens are wanted, do not convert. Take the whole pp-number. */ end_of_curr_token = curr_char_loc - 1; ctoken = tok_pp_number; } else { /* Preprocessing number is not wanted. */ a_boolean potential_ud_literal = FALSE; if (curr_char_loc != (end_of_curr_token + 1) && !err) { /* Extra characters were seen in the pp-number scan. */ if (user_defined_literals_enabled && potential_ud_suffix #if FIXED_POINT_ALLOWED && kind != k_fixed_point #endif /* FIXED_POINT_ALLOWED */ ) { if (first_non_id_char != NULL) { /* A pp-number must convert to a single token, so if a non-identifier character was part of the pp-number, it does not satisfy the grammar for a user-defined-literal. */ error_at_line_pos(ec_bad_user_defined_suffix, first_non_id_char); err = TRUE; } /* if */ } else { /* The pp-number is not a valid numeric literal. Note that the extra characters just get thrown away. */ error_at_line_pos(ec_extra_chars_on_number, end_of_curr_token+1); err = TRUE; } /* if */ } /* if */ /* Convert the constant. Errors are still possible, since the checking above allows certain cases by. */ convert_literal_value: if (user_defined_literals_enabled && potential_ud_suffix) { potential_ud_literal = TRUE; if (possible_start_of_ud_suffix != NULL) { /* Exclude the characters that were previously thought to be a numeric suffix; they are actually part of the putative ud-suffix instead. */ end_of_curr_token = possible_start_of_ud_suffix - 1; } /* if */ } /* if */ switch (kind) { case k_decimal: conv_integer_literal(10, &err_code, &err_pos, potential_ud_literal); ctoken = tok_int_constant; break; case k_octal: conv_integer_literal(8, &err_code, &err_pos, potential_ud_literal); ctoken = tok_int_constant; break; case k_hex: conv_integer_literal(16, &err_code, &err_pos, potential_ud_literal); ctoken = tok_int_constant; break; case k_binary: conv_integer_literal(2, &err_code, &err_pos, potential_ud_literal); ctoken = tok_int_constant; break; #if FIXED_POINT_ALLOWED case k_fixed_point: conv_fixed_point_literal(is_hex_fp_value, &err_code, &err_pos); ctoken = tok_fixed_point_constant; break; #endif /* FIXED_POINT_ALLOWED */ case k_float: { a_const_char *saved_token_end = end_of_curr_token; if (is_hex_fp_value && !local_allow_hex_fp_constants) { diagnostic_at_line_pos(strict_ansi_error_severity, ec_hex_fp_constant, start_of_curr_token); } /* if */ if (start_of_invalid_suffix != NULL) { /* An invalid floating-point suffix after the literal could cause problems when converting the value, so temporarily exclude it from the current token. */ end_of_curr_token = start_of_invalid_suffix - 1; } /* if */ conv_float_literal(is_hex_fp_value, &err_code, &err_pos, &sev); end_of_curr_token = saved_token_end; ctoken = tok_float_constant; } break; default: unexpected_condition_str("scan_number: bad kind"); } /* switch */ if (user_defined_literals_enabled && !err && potential_ud_suffix && (err_code == ec_no_error || err_code == ec_bad_float_value || err_code == ec_integer_too_large)) { /* A syntactically-correct user-defined literal was seen. */ a_const_char *canonical_id; id_len = (sizeof_t)(curr_char_loc - end_of_curr_token - 1); canonical_id = make_canonical_identifier(end_of_curr_token + 1, &id_len, /*force_ucn=*/FALSE); if (kind == k_float) { /* The numeric part of a user-defined float literal is implicitly a long double, so we need to reconvert it with a synthesized suffix. */ char saved_char = *++end_of_curr_token; *((char *)end_of_curr_token) = 'L'; conv_float_literal(is_hex_fp_value, &err_code, &err_pos, &sev); *((char *)end_of_curr_token--) = saved_char; } /* if */ /* A temporary variable must be used to store the type in case find_literal_operator results in another user defined literal being parsed while this one is still being constructed. The value of curr_token is set to indicate to cache_curr_token (if invoked by lookups triggered by find_literal_operator) that the current token state is a partially resolved user-defined literal. */ a_type_ptr new_ud_lit_type; curr_token = tok_unresolved_ud_literal; if (is_error_constant(&const_for_curr_token)) { /* The literal overflowed/underflowed, which is not an error for raw literal operators and literal operator templates. Synthesize an appropriate type for the lookup. */ new_ud_lit_type = (kind != k_float) ? integer_type((an_integer_kind)ik_unsigned_long_long) : float_type((a_float_kind)fk_long_double); } else { /* Use the actual type of the literal. */ new_ud_lit_type = const_for_curr_token.type; } /* if */ ud_lit_op_sym_for_curr_token = find_literal_operator(canonical_id, id_len, tentative_udl_lookup ? NULL : &start_pos, new_ud_lit_type, /*from_cache=*/FALSE, (a_diagnostic_ptr)NULL); ud_lit_type_for_curr_token = new_ud_lit_type; if (ud_lit_op_sym_for_curr_token == NULL && id_len <= 5 && gnu_imaginary_literals_allowed && prefer_udl_over_imag_suffix) { /* g++ and clang treat something like 0.5il as a user-defined literal if the corresponding literal operator has been declared and an imaginary literal otherwise. With -fms-extensions, clang has a similar treatment for something like UI32 as a UDL or as a Microsoft-style integer type suffix. We've just determined that there's no literal operator for the suffix. Check to see if the canonical id is one of the permitted literal suffixes and, if so, go back and handle the number appropriately. */ a_const_char *rest = NULL; a_boolean is_imaginary_suffix = FALSE; a_boolean is_ms_type_suffix = FALSE; if (clang_mode && ms_extensions && kind != k_float && strchr("IiUu", canonical_id[0]) != NULL) { sizeof_t num_chars; if (canonical_id[0] == 'I' || canonical_id[0] == 'i') { rest = canonical_id + 1; num_chars = id_len - 1; } else if (canonical_id[1] == 'I' || canonical_id[1] == 'i') { rest = canonical_id + 2; num_chars = id_len - 2; } /* if */ is_ms_type_suffix = (rest != NULL && ((num_chars == 1 && rest[0] == '8') || (num_chars == 2 && ((rest[0] == '1' && rest[1] == '6') || (rest[0] == '3' && rest[1] == '2') || (rest[0] == '6' && rest[1] == '4'))))); } /* if */ if (!is_ms_type_suffix) { if (strchr("IiJj", canonical_id[0]) != NULL) { is_imaginary_suffix = TRUE; if (id_len > 1) { rest = canonical_id + 1; } /* if */ } else if (id_len > 1 && strchr("IiJj", canonical_id[id_len - 1]) != NULL) { is_imaginary_suffix = TRUE; rest = canonical_id; } /* if */ } /* if */ if (is_imaginary_suffix && id_len > 1) { if (kind == k_float) { /* Check that the rest of the suffix satisfies the grammar for floating-point-suffix. */ if (id_len == 2) { if (strchr("FfLlWwQq", *rest) == NULL || (!float80_enabled && (*rest == 'W' || *rest == 'w')) || (!float128_enabled && (*rest == 'Q' || *rest == 'q'))) { is_imaginary_suffix = FALSE; } /* if */ } else if (id_len == 4 && (*rest == 'f' || *rest == 'F')) { if (rest[1] == '1' && rest[2] == '6') { /* IF16 */ is_imaginary_suffix = (gcc_version_is(>= 120000) || clang_version_is(>= 150000) || (gpp_version_is(>= 130000) && !strict_gnu)); } else if ((rest[1] == '3' && rest[2] == '2') || (rest[1] == '6' && rest[2] == '4')) { /* IF32, IF64 */ is_imaginary_suffix = (gcc_version_is(>= 70000) || (gpp_version_is(>= 130000) && !strict_gnu)); } else { is_imaginary_suffix = FALSE; } /* if */ } else if (id_len == 5) { if (((rest[0] == 'b' && rest[1] == 'f') || (rest[0] == 'B' && rest[1] == 'F')) && rest[2] == '1' && rest[3] == '6') { /* IBF16 */ is_imaginary_suffix = accept_bf16_suffix; } else if ((rest[0] == 'f' || rest[1] == 'F') && rest[1] == '1' && rest[2] == '2' && rest[3] == '8') { /* IF128 */ is_imaginary_suffix = (float128_enabled && (gcc_version_is(>= 70000) || (gpp_version_is(>= 130000) && !strict_gnu))); } else { is_imaginary_suffix = FALSE; } /* if */ } else { is_imaginary_suffix = FALSE; } /* if */ } else { /* Check that the rest of the suffix satisfies the grammar for integer-suffix. */ switch (*rest) { case 'Z': case 'z': /* "Z" or "ZU" are permitted. */ if (id_len > 3 || (rest[1] != 'U' && rest[1] != 'u')) { is_imaginary_suffix = FALSE; } /* if */ break; case 'U': case 'u': /* "U", "UZ", "UL", or "ULL" are permitted. */ if (id_len >= 3) { if (rest[1] == 'Z' || rest[1] == 'z') { is_imaginary_suffix = (id_len == 3); } else if (rest[1] == 'L' || rest[1] == 'l') { is_imaginary_suffix = (id_len == 3 || rest[2] == rest[1]); } else { is_imaginary_suffix = FALSE; } /* if */ } /* if */ break; case 'L': case 'l': /* "L", "LL", "LU", or "LLU" are permitted. */ if (id_len == 2) { is_imaginary_suffix = TRUE; } else if (id_len == 3) { is_imaginary_suffix = (rest[1] == 'U' || rest[1] == 'u' || rest[1] == rest[0]); } else if (id_len == 4) { is_imaginary_suffix = (rest[1] == rest[0] && (rest[2] == 'U' || rest[2] == 'u')); } else { is_imaginary_suffix = FALSE; } /* if */ break; default: /* Other characters cannot begin an integer-suffix. */ is_imaginary_suffix = FALSE; break; } /* switch */ } /* if */ } /* if */ if (is_imaginary_suffix || is_ms_type_suffix) { /* Rescan the suffix. */ prefer_udl_over_imag_suffix = FALSE; ch = *curr_char_loc; possible_start_of_ud_suffix = NULL; potential_ud_suffix = FALSE; if (is_ms_type_suffix) { /* Include the suffix in the token spelling and convert it to an integer. */ end_of_curr_token = curr_char_loc - 1; goto convert_literal_value; } else if (kind == k_float) { /* Rescan the suffix. */ curr_char_loc = end_of_curr_token + 1; goto end_float_accum; #if C99_IL_EXTENSIONS_SUPPORTED } else { /* Report an error about complex integer values and treat the literal as a complex double. */ curr_char_loc = end_of_curr_token + 1; goto int_imaginary_suffix; #endif /* C99_IL_EXTENSIONS_SUPPORTED */ } /* if */ } /* if */ } /* if */ if (err_code != ec_no_error && (ud_lit_op_sym_for_curr_token != NULL || caching_tokens)) { /* There was an overflow or underflow in the numeric portion of the user-defined literal. Check to see if that's okay or if we should report an error. */ a_symbol_ptr sym = ud_lit_op_sym_for_curr_token; if (caching_tokens || symbol_is(sym, sk_function_template) || (symbol_is(sym, sk_routine) && sym->variant.routine.ptr->is_raw_literal_operator)) { /* An out-of-range literal is okay with a raw literal operator or a literal operator template, as the spelling, not the value, of the literal is used. If we are caching tokens, we can't reliably look up the literal operator, as the tokens will be parsed in a different context, so we ignore the error since the literal might eventually be linked to a raw literal operator or literal operator template; const_for_curr_token will be an error constant, allowing late diagnosis of the error in case an ordinary literal operator is selected, and const_with_curr_tok_spelling will be set appropriately to allow handling the non-error cases. For other kinds of literal operators, as well as when no literal operator is found, report the invalid literal and do not categorize this as a user-defined literal. */ err_code = ec_no_error; } /* if */ } /* if */ if (err_code == ec_no_error) { ctoken = tok_ud_literal; end_of_curr_token = curr_char_loc - 1; } /* if */ } /* if */ /* Check for errors detected. */ if (err_code != ec_no_error) { diagnostic_at_line_pos(sev, err_code, err_pos); } /* if */ } /* if */ done: #if DEBUG if (db_flag_is_set("scan_number")) { db_constant(&const_for_curr_token); if (const_for_curr_token.type != NULL) { fprintf(f_debug, ", type: "); db_type(const_for_curr_token.type); } /* if */ fprintf(f_debug, "\n"); } /* if */ #endif /* DEBUG */ return (ctoken); #undef skip_digit_separator } /* scan_number */ static void scan_boolean_constant(a_token_kind ctoken) /* Scan the "true" and "false" tokens. ctoken is tok_true or tok_false. Creates an integer constant of the appropriate kind (except when scanning pp tokens). */ { if (fetch_pp_tokens) { /* When fetching preprocessing tokens, just return tok_false or tok_true. Nothing else needs to be done here for this case. */ } else { /* Create a boolean constant with the appropriate value. */ clear_constant(&const_for_curr_token, (a_constant_repr_kind)ck_integer); const_for_curr_token.type = bool_type(); /* The integer value is either 1 (true) or 0 (false). */ set_integer_value(&const_for_curr_token.variant.integer_value, (a_host_large_integer)(ctoken == tok_true)); const_for_curr_token.non_arithmetic = TRUE; } /* if */ } /* scan_boolean_constant */ #if !(CPPCLI_ENABLING_POSSIBLE && EDG_WIN32) static #endif /* !(CPPCLI_ENABLING_POSSIBLE && EDG_WIN32) */ an_error_code is_valid_UCN_identifier_char( unsigned long uchar, a_boolean is_identifier_start) /* Determine whether uchar is a value that is designated as a universal character name that may appear in an identifier. If is_identifier_start is TRUE, the specified character must be a valid initial character. If the identifier is invalid, return an error code that gives the reason the identifier character is invalid. */ { an_error_code result = ec_no_error; a_byte dialect; a_UCN_range const *range = NULL; ptrdiff_t idx; auto get_end = [](size_t table_idx) { return UCN_table[table_idx].end; }; if ((!C_mode() || c23_mode) && !old_id_chars) { dialect = UAX44; } else if (cpp11_mode || c11_mode) { dialect = CPP11; } else if (c99_mode) { dialect = C99; } else { dialect = CPP03; } /* if */ /* The table is sorted by increasing range end (and start). The first range whose end is not less than uchar is the candidate containing range. */ idx = low_bound(sizeof(UCN_table) / sizeof(a_UCN_range), uchar, get_end); if (idx != -1 && UCN_table[idx].start <= uchar) { range = &UCN_table[idx]; } /* if */ if (range == NULL || (range->dialects & dialect) == 0) { /* Not a valid identifier character in this dialect. */ result = ec_invalid_identifier_UCN; } else if (is_identifier_start && range->non_initial_dialects & dialect) { /* This is the first character of an identifier and the character is not allowed as an initial identifier character in this dialect. */ result = ec_invalid_identifier_start_UCN; } /* if */ return result; } /* is_valid_UCN_identifier_char */ static void check_for_invalid_cplusplus_ucn(unsigned long ucn, a_const_char **start_pos, a_boolean is_identifier, a_boolean is_identifier_start) /* Determine whether "ucn" is a valid universal character name in C++. Issue a diagnostic if it is not. The rules for C++98/C++03 and for C++11 are different. The values of is_identifier and is_identifier_start reflect the lexical position of the character, i.e., whether the character might start or be part of an identifier. */ { an_error_code err_code = ec_no_error; if (cpp11_mode || uliterals_enabled) { if (ucn >= 0xd800 && ucn <= 0xdfff) { /* Values reserved for ISO 10646 surrogate code points are never allowed. */ err_code = ec_UCN_names_surrogate_code_point; } else if (ucn > 0x10ffff) { /* Not a valid code point. */ err_code = ec_UCN_names_invalid_code_point; } else if (is_identifier) { /* Other restrictions only apply when the UCN is used in an identifier. */ if (ucn <= 255 && !is_nonstandard_character((char)ucn)) { /* A basic character set code. */ err_code = ec_UCN_names_basic_char; } else if (macro_depth == 0) { /* Check whether this is a valid identifier character. (If the character appears in a macro argument, we don't know whether it will end up in an identifier or not in the macro expansion.) */ err_code = is_valid_UCN_identifier_char(ucn, is_identifier_start); } /* if */ } /* if */ } else { if (ucn <= 255 && !is_nonstandard_character((char)ucn)) { /* A UCN cannot be used to name a character in the basic character set. */ err_code = ec_UCN_names_basic_char; } else if (ucn < 0x20 || (ucn >= 0x7f && ucn <= 0x9f)) { /* These characters are disallowed by the standard. */ err_code = ec_invalid_UCN; } else if (is_identifier && macro_depth == 0) { /* Check whether this is a valid identifier character. */ err_code = is_valid_UCN_identifier_char(ucn, is_identifier_start); } /* if */ } /* if */ if (err_code != ec_no_error) { /* Get the source position that corresponds to this character. */ conv_line_loc_to_source_pos(*start_pos, &error_position); diagnostic(strict_ansi_error_severity, err_code); } /* if */ } /* check_for_invalid_cplusplus_ucn */ static void check_for_invalid_c99_ucn(unsigned long ucn, a_const_char **start_pos, a_boolean is_identifier, a_boolean is_identifier_start) /* Determine whether "ucn" is a valid universal character name in C99 (and subsequent versions). Issue a diagnostic if it is not. The values of is_identifier and is_identifier_start reflect the lexical position of the character, i.e., whether the character might start or be part of an identifier. */ { an_error_code err_code = ec_no_error; if (ucn == '$' && is_identifier && allow_dollar_in_id_chars) { /* A "$" specified as a UCN in an identifier when dollar signs are allowed in identifiers. A dollar sign is normally allowed as a UCN in C99 and following modes (including in C23, unlike C++26), but is disallowed when dollar signs are permitted in identifiers. */ err_code = ec_UCN_names_basic_char; } else if (ucn < 0xa0 && ucn != 0x24 && ucn != 0x40 && ucn != 0x60) { /* A UCN cannot name a character less than 0xa0 except for "$" (0x24), "@" (0x40), and "`" (0x60). C23 did not change the status of those three UCNs, unlike C++23, even though it made those characters part of the basic character set. Note that the dollar sign may be prohibited by the test above. */ err_code = ec_UCN_names_basic_char; } else if (ucn >= 0xd800 && ucn <= 0xdfff) { /* A UCN cannot name a character in the range of 0xd800-0xdfff (the ISO 10646 surrogate code points). */ err_code = ec_UCN_names_surrogate_code_point; } else if (ucn > 0x10ffff) { /* Not a valid code point. */ err_code = ec_UCN_names_invalid_code_point; } else if (is_identifier && macro_depth == 0) { /* Check whether this is a valid identifier character. (If the UCN appears in a macro argument, we don't know whether it will end up in an identifier or not in the macro expansion.) */ err_code = is_valid_UCN_identifier_char(ucn, is_identifier_start); } /* if */ if (err_code != ec_no_error) { /* Get the source position that corresponds to this character. */ conv_line_loc_to_source_pos(*start_pos, &error_position); diagnostic(strict_ansi_error_severity, err_code); } /* if */ } /* check_for_invalid_c99_ucn */ unsigned long scan_universal_character(a_const_char **start_pos, a_boolean is_identifier, a_boolean is_identifier_start, a_boolean issue_diagnostics, /* Defaulted: */ a_boolean *malformed_err) /* Scan the universal character name starting at start_pos. The character specified by the universal character name is returned. If is_identifier is TRUE, an error is issued if the character is not one of those designated as a valid identifier character. If is_identifier_start is TRUE, it must be one of those characters that can appear as the first character in an identifier. If issue_diagnostics is TRUE, diagnostic messages are produced if the universal character is improperly formed, or if it names an invalid character. *start_pos is updated by this routine to point to the character after the universal character name. If a malformed escape sequence is detected and malformed_err is not NULL, *malformed_err is set to TRUE in Microsoft mode for non-delimited UCNs and in all modes for delimited UCNs. */ { a_const_char *pos = *start_pos; a_boolean err = FALSE; unsigned long result = 0; int digits; a_boolean is_delimited; if (malformed_err != NULL) { *malformed_err = FALSE; } /* if */ /* The current position must be the start of a universal character name. */ check_assertion_str2(*pos == '\\' && (*(pos+1) == 'u' || *(pos+1) == 'U'), "scan_universal_character:", "curr pos not universal character"); /* Suppress diagnostics if we are skipping over this string because of some kind of preprocessor "if" directive, or when doing the initial PCH prefix scan of a file. */ if (currently_in_pp_if_skip || building_pch_prefix) { issue_diagnostics = FALSE; } /* if */ /* Skip past the "\u" or "\U", and determine whether we are processing a delimited name or a four or eight character name. "\u{" is a delimited name, allowing an arbitrary number of digits, while "\u" without a brace is followed by four hex digits and "\U" is followed by eight hex digits. */ ++pos; digits = *pos++ == 'u' ? 4 : 8; if (digits == 4 && delimited_escape_seqs_allowed && *pos == '{') { is_delimited = TRUE; ++pos; } else { is_delimited = FALSE; } /* if */ /* Scan the digits and calculate the character value. Stop scanning if we encounter an invalid character. */ for (; is_delimited || digits > 0; --digits) { char ch = *pos++; if (!isxdigit((unsigned char)ch)) { if (is_delimited && ch == '}') { if (digits == 4 && issue_diagnostics) { /* An empty sequence. */ conv_line_loc_to_source_pos(pos - 1, &error_position); pos_diagnostic(es_discretionary_error, ec_empty_delimited_escape, &error_position); /* Increment the position so that the backing-up done below will leave *start_pos pointing to the character after the '}'. */ ++pos; } else { /* Normal end of sequence. */ goto check_validity; } /* if */ } else if (issue_diagnostics) { /* Get the source position that corresponds to this character. */ conv_line_loc_to_source_pos(pos-1, &error_position); if (is_delimited) { pos_diagnostic(es_discretionary_error, ec_unterminated_delimited_escape, &error_position); } else { an_error_severity sev; if ((microsoft_mode || clang_mode || gnu_mode) && curr_cmd_line_or_predef_macro_def != NULL && !processing_predefined_macro) { /* All three emulated compilers accept an invalid UCN in a command-line macro definition without error, although clang emits a warning. */ sev = es_warning; } else if ((microsoft_mode && !is_identifier) || macro_depth > 0) { /* MSVC accepts malformed UCNs in literals, but not in identifiers, without complaint. We also want to suppress error reports during macro expansion and only issue an error if the final expanded text contains the UCN. */ sev = es_remark; } else { sev = es_discretionary_error; } /* if */ pos_diagnostic(sev, ec_malformed_universal_character, &error_position); } /* if */ } /* if */ /* Back up one character so that the invalid character will be treated as part of the token that follows. */ --pos; err = TRUE; if ((is_delimited || microsoft_mode) && malformed_err != NULL) { /* MSVC recovers from a malformed non-delimited UCN by ignoring the '\' and treating the remainder of the UCN as ordinary characters. This style of error recovery is indicated to the caller by setting *malformed_err to TRUE. In non-Microsoft modes, recovery for malformed non-delimited UCNs consists of simply taking whatever numeric value has been accumulated and treating that as the code point. */ *malformed_err = TRUE; } /* if */ break; } /* if */ result = (result << 4) | hexvalue((unsigned char)ch); } /* for */ /* Check whether the specified character is a valid universal character. */ check_validity: if (!err && issue_diagnostics) { if (!C_mode()) { check_for_invalid_cplusplus_ucn(result, start_pos, is_identifier, is_identifier_start); } else { check_for_invalid_c99_ucn(result, start_pos, is_identifier, is_identifier_start); } /* if */ } /* if */ *start_pos = pos; return result; } /* scan_universal_character */ /* The following variables declare a finite state machine giving the names and code points of the characters in the Unicode character set. See the file unicode_name_fsm.c for the definition and description of the data. */ extern const unsigned char unicode_name_fsm[]; extern const sizeof_t size_of_unicode_name_fsm; static inline unsigned long val_from_unicode_name_fsm(unsigned long offset) /* Return the numeric value of the three bytes at the given offset in unicode_name_fsm, interpreted as a big-endian 24-bit unsigned integer. */ { unsigned long result = unicode_name_fsm[offset]; result <<= 8; result |= unicode_name_fsm[offset + 1]; result <<= 8; result |= unicode_name_fsm[offset + 2]; return result; } /* val_from_unicode_name_fsm */ unsigned long scan_named_unicode_char(a_const_char **start_pos, a_boolean is_identifier, a_boolean is_identifier_start, a_boolean issue_diagnostics, a_boolean update_pos_on_error) /* Scan a C++23 named Unicode character, i.e., \N{...}, and return the code point corresponding to the name or (unsigned long)-1 if a Unicode character is not found. On input, *start_pos points to the '\'. If a Unicode character is successfully recognized or if update_pos_on_error is TRUE, *start_pos is updated to point to the character following the terminating '}' or to the LE_ESCAPE terminating the line if no '}' is found. If issue_diagnostics is TRUE, an error will be reported if the construct does not name a Unicode character or, if is_identifier or is_identifier_start are TRUE, if the named character is not valid within or starting an identifier, respectively. */ { a_const_char *pos = *start_pos + 3; unsigned long result = (unsigned long)-1; unsigned long state = 0; a_const_char *rbrace_pos = NULL; /* Suppress diagnostics if we are skipping over this construct because of some kind of preprocessor "if" directive, or when doing the initial PCH prefix scan of a file. */ if (currently_in_pp_if_skip || building_pch_prefix) { issue_diagnostics = FALSE; } /* if */ /* Walk through the characters of the Unicode character name and the corresponding states until a name is recognized or an error occurs, beginning with the initial state, located at offset 0. */ while (state < size_of_unicode_name_fsm) { /* Iterate through the transitions from this state, looking for a character that matches the current character in the putative Unicode character name. */ int transition; int num_transitions = unicode_name_fsm[state]; unsigned char name_char = (unsigned char)*pos++; unsigned long trans_offset = state + 1; if ((num_transitions & 0x80) != 0) { /* This is a multi-character state. The "num_transitions" value actually gives the number of characters in the state's sequence. Loop through all but the last, checking against the putative name characters, and then treat the last character as if it were the start of a transition in a single-transition state. */ int num_state_chars = num_transitions & 0x7f; check_assertion(num_state_chars >= 1); num_transitions = 1; for (int i = 0; i < num_state_chars - 1; ++i) { if (name_char != unicode_name_fsm[trans_offset++]) { /* Report an incorrect name. */ transition = 1; goto end_of_state_processing; } /* if */ name_char = (unsigned char)*pos++; } /* for */ } else if ((num_transitions & 0x40) != 0) { /* This is a range state; the number of ranges is given by the low-order six bits of num_transitions. Names in a range consist of a common prefix, terminated by the four- or five-digit hexadecimal value of the character's code point. Extract the hexadecimal value from the putative character name and compare it against each range in the state. If it is a member of one of the ranges, the code point is the extracted hexadecimal value. If the value is not a member of any of the ranges, the putative character name is invalid. */ unsigned long val = 0; a_boolean found_char = FALSE; num_transitions &= 0x3f; transition = 1; /* Extract the value from up to five hexadecimal digits at this point in the name. (pos was already incremented before reaching this state, so we restore it to the beginning of the hexadecimal part of the name.) */ --pos; for (int i = 0; i < 5; ++i) { if (*pos >= '0' && *pos <= '9') { val = (val << 4) + ((unsigned char)*pos - '0'); } else if (*pos >= 'A' && *pos <= 'F') { val = (val << 4) + ((unsigned char)*pos - 'A' + 10); } else { break; } /* if */ ++pos; } /* for */ if (*pos == '}') { /* The name is well-formed. Now check the value against the ranges. */ rbrace_pos = pos; for (int i = 0; !found_char && i < num_transitions; ++i) { unsigned long first = val_from_unicode_name_fsm(trans_offset); unsigned long last = val_from_unicode_name_fsm(trans_offset + 3); trans_offset += 6; if (val >= first && val <= last) { /* This is a valid character name. */ found_char = TRUE; result = val; transition = 0; state = (unsigned long)-1; } /* if */ } /* for */ if (!found_char) { /* The character name is not valid. The error handling code below relies on rbrace_pos being NULL as the criterion for having seen an invalid name. */ rbrace_pos = NULL; } /* if */ } /* if */ num_transitions = 1; goto end_of_state_processing; } /* if */ /* Process the transitions (or the pseudo-transition at the end of a multi-character state) looking for one that matches the current name character. */ for (transition = 0; transition < num_transitions; ++transition, trans_offset += 4) { unsigned char trans_char = unicode_name_fsm[trans_offset]; unsigned long val = val_from_unicode_name_fsm(trans_offset + 1); a_boolean terminal_transition; if ((trans_char & 0x80) != 0) { /* This is a direct-value transition, i.e., instead of a transition to another state that would contain only the terminating character and the code point value, if the current character matches this transition and the next name character is the terminating '}', the value in this transition is the code point itself. */ terminal_transition = TRUE; trans_char &= 0x7f; } else { terminal_transition = FALSE; } /* if */ if (trans_char == name_char) { /* This transition matches the current character of the Unicode character name. */ if (terminal_transition && *pos == '}') { /* We've matched the name, and the transition's value is the code point. */ result = val; rbrace_pos = pos; state = (unsigned long)-1; } else if (trans_char == '}') { /* The transition's value is the code point; pos points after the '}'. */ result = val; rbrace_pos = pos - 1; state = (unsigned long)-1; } else { /* This is a normal transition; the value gives the offset of the next state. */ state = val; } /* if */ /* Having matched the character in the current transition, we're done with this state's transitions. */ break; } /* if */ } /* for */ end_of_state_processing: if (transition >= num_transitions) { /* The current Unicode name character does not match any transition in this state, so the name is not a valid Unicode character name. */ state = (unsigned long)-1; } /* if */ } /* while */ if (rbrace_pos != NULL && issue_diagnostics) { /* Check whether the named character is valid. */ check_for_invalid_cplusplus_ucn(result, start_pos, is_identifier, is_identifier_start); } /* if */ if (rbrace_pos == NULL) { /* We encountered an error. g++ distinguishes between cases where the name is more-or-less well-formed, consisting only of alphabetic and numeric characters, underscore, hyphen, and space, versus other error cases in which an invalid character or the end of the line is found before the closing '}'. The former case elicits an error and the latter, only a warning. clang treats all those cases as errors. Scan for the closing '}' and issue the appropriate diagnostic. */ a_const_char *name_chars = " -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"; --pos; for (;;) { if (*pos == '}') { /* We made it all the way to the end of the name with no invalid characters. */ if (issue_diagnostics) { conv_line_loc_to_source_pos(*start_pos + 3, &error_position); if (pos == *start_pos + 3) { /* Both g++ and clang issue a warning for an empty name. */ pos_diagnostic((gnu_version_is(any_version) || clang_version_is(any_version) ? es_warning : es_discretionary_error), ec_empty_unicode_name, &error_position); } else { /* A more-or-less well-formed name was supplied but not found. */ pos_in_temp_text_buffer = 0; *(char *)pos = '\0'; put_str_to_temp_text_buffer(*start_pos + 3); *(char *)pos = '}'; pos_st_diagnostic(es_discretionary_error, ec_unicode_name_not_found, &error_position, temp_text_buffer); } /* if */ } /* if */ if (update_pos_on_error) { *start_pos = pos + 1; } /* if */ break; } else if (strchr(name_chars, *pos) == NULL) { /* This character cannot appear in a Unicode character name. */ if (issue_diagnostics) { if (*pos == LE_ESCAPE) { conv_line_loc_to_source_pos(*start_pos, &error_position); pos_diagnostic((gnu_version_is(any_version) ? es_warning : es_discretionary_error), ec_unterminated_unicode_name, &error_position); } else { conv_line_loc_to_source_pos(pos, &error_position); pos_diagnostic((gnu_version_is(any_version) ? es_warning : es_discretionary_error), ec_invalid_char_in_unicode_name, &error_position); } /* if */ } /* if */ if (update_pos_on_error) { /* Look for the terminating '}' or the end of the line and update *start_pos accordingly. */ while (*pos != '}' && *pos != LE_ESCAPE) { ++pos; } /* while */ if (*pos == '}') { *start_pos = pos + 1; } else { *start_pos = pos; } /* if */ } /* if */ break; } /* if */ ++pos; } /* for */ } else { *start_pos = rbrace_pos + 1; } /* if */ return result; } /* scan_named_unicode_char */ #if ABI_COMPATIBILITY_VERSION >= 302 static void output_ucn_value(unsigned long ucn_value, char prefix4, char prefix8) /* Add to ucn_buffer a sequence like \uabcd to represent a UCN or multibyte character. ucn_value is the value to be put out. prefix4 and prefix8 are the prefix characters to be used for 4-digit and 8-digit output. */ { size_t ucn_chars = ucn_value > 0xffff ? 8 : 4; char ucn[8] = ""; for (size_t i = ucn_chars; i > 0; i--) { int hex_digit = ucn_value & 0xf; ucn_value = ucn_value >> 4; ucn[i - 1] = "0123456789abcdef"[hex_digit]; } /* for */ add_char_to_text_buffer(ucn_buffer, '\\'); add_char_to_text_buffer(ucn_buffer, ucn_chars == 8 ? prefix8 : prefix4); add_to_text_buffer(ucn_buffer, ucn, ucn_chars); /* Record the fact that an identifier containing a UCN or UCN-like character has been encountered. */ il_header.UCN_identifiers_used = TRUE; } /* output_ucn_value */ char *make_canonical_identifier(a_const_char *identifier, sizeof_t *length, ARG_UNUSED a_boolean force_ucn) /* "identifier" points to the characters of an identifier containing universal character names or multibyte characters. Make a copy of the identifier in which any upper case characters in the UCN are converted to lower case and any multibyte characters are converted to canonical form. If force_ucn is TRUE, UNICODE_SOURCE_SUPPORTED must be TRUE and the result will use UCNs for any extended characters; otherwise, the result depends on the value of IDENTIFIER_STRINGS_ALLOW_MULTIBYTE_CHARS and UNICODE_SOURCE_SUPPORTED. "length" is updated to the actual length of the new identifier. */ { a_const_char *src; a_const_char *end_pos = identifier + *length - 1; #if !UNICODE_SOURCE_SUPPORTED check_assertion(!force_ucn); #endif /* !UNICODE_SOURCE_SUPPORTED */ /* Allocate a text buffer to be used for the copy if one has not yet been created. */ if (ucn_buffer == NULL) ucn_buffer = alloc_text_buffer(128); reset_text_buffer(ucn_buffer); for (src = identifier; src <= end_pos;) { if (*src == '\\' && (*(src+1) == 'u' || *(src+1) == 'U' || (*(src+1) == 'N' && *(src+2) == '{'))) { /* Scan the universal character or named Unicode character. We pass in FALSE for is_identifier, etc., because those are only used when diagnosing errors. */ unsigned long ucn_value; if (src[1] == 'N') { /* An erroneous named Unicode character escape should have been detected during the initial identifier scan and shouldn't reach here, so we assume the return value is a valid identifier character. */ ucn_value = scan_named_unicode_char(&src, /*is_identifier=*/FALSE, /*is_identifier_start=*/FALSE, /*issue_diagnostics=*/FALSE, /*update_pos_on_error=*/TRUE); } else { ucn_value = scan_universal_character(&src, /*is_identifier=*/FALSE, /*is_identifier_start=*/FALSE, /*issue_diagnostics=*/FALSE); } /* if */ #if UNICODE_SOURCE_SUPPORTED && IDENTIFIER_STRINGS_ALLOW_MULTIBYTE_CHARS if (!force_ucn) { /* We can put the UTF-8 for the character directly into the identifier string. */ unsigned char arr[4]; unsigned numch = unicode_to_utf8(ucn_value, arr); for (unsigned i = 0; i < numch; i++) { add_char_to_text_buffer(ucn_buffer, arr[i]); } /* for */ } else { /* Use a canonicalized UCN for the character. */ output_ucn_value(ucn_value, 'u', 'U'); } /* if */ #else /* !(UNICODE_SOURCE_SUPPORTED && ...) */ /* Add the UCN to the buffer as \uxxxx or the like. */ output_ucn_value(ucn_value, 'u', 'U'); #endif /* UNICODE_SOURCE_SUPPORTED && ... */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED } else if ((multibyte_chars_in_source_enabled #ifdef char_may_begin_multibyte_sequence && char_may_begin_multibyte_sequence(*src) #endif /* ifdef char_may_begin_multibyte_sequence */ ) #if UNICODE_SOURCE_SUPPORTED || ((unsigned char)*src) > 0x7f #endif /* UNICODE_SOURCE_SUPPORTED */ ) { /* This character may be a multibyte character or is a single extended character that must be translated to Unicode in the canonical representation. */ unsigned long wc; a_boolean err; /* When native multibyte characters are allowed, lex_mbc_to_wide_char will convert the character to Unicode if the source file is non-Unicode. */ int numch = lex_mbc_to_wide_char(src, &wc, &err); #if UNICODE_SOURCE_SUPPORTED if (err) { /* The scanning of a multibyte character resulted in a value that has no Unicode representation. */ error_at_line_pos(ec_non_unicode_char_in_ident, src); } /* if */ #endif /* UNICODE_SOURCE_SUPPORTED */ #if IDENTIFIER_STRINGS_ALLOW_MULTIBYTE_CHARS #if UNICODE_SOURCE_SUPPORTED if (!force_ucn) { /* Put the UTF-8 version of the character into the identifier. */ unsigned char arr[4]; unsigned utflen = unicode_to_utf8(wc, arr); for (unsigned i = 0; i < utflen; i++) { add_char_to_text_buffer(ucn_buffer, arr[i]); } /* for */ } else { /* Use a UCN for the character. */ output_ucn_value(wc, 'u', 'U'); } /* if */ #else /* !UNICODE_SOURCE_SUPPORTED */ check_assertion(!err); /* Put the multibyte character into the identifier. */ { int i; for (i = 0; i < numch; i++) { add_char_to_text_buffer(ucn_buffer, src[i]); } /* for */ } #endif /* UNICODE_SOURCE_SUPPORTED */ #else /* !IDENTIFIER_STRINGS_ALLOW_MULTIBYTE_CHARS */ /* Normally, characters less than UCHAR_MAX are just added to the buffer. When Unicode is supported, characters above 0x7f may have been translated from the source form to a different value, so output those as escapes. */ #if UNICODE_SOURCE_SUPPORTED if (wc <= 0x7f) #else /* !UNICODE_SOURCE_SUPPORTED */ if (wc <= UCHAR_MAX) #endif /* UNICODE_SOURCE_SUPPORTED */ /* Do not insert code here. */ { add_char_to_text_buffer(ucn_buffer, (char)wc); } else { /* Put an encoding for the character into the buffer. If the multibyte character set is Unicode, use the \u form that UCNs use, since the underlying character set is the same. */ #if UNICODE_SOURCE_SUPPORTED output_ucn_value(wc, 'u', 'U'); #else /* !UNICODE_SOURCE_SUPPORTED */ output_ucn_value(wc, 'm', 'M'); #endif /* UNICODE_SOURCE_SUPPORTED */ } /* if */ #endif /* IDENTIFIER_STRINGS_ALLOW_MULTIBYTE_CHARS */ src += numch; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ } else { /* Simple non-multibyte non-UCN character. */ add_char_to_text_buffer(ucn_buffer, *(src++)); } /* if */ } /* for */ /* Update the length parameter with the length of the new identifier. */ *length = ucn_buffer->size; return ucn_buffer->buffer; } /* make_canonical_identifier */ #else /* !(ABI_COMPATIBILITY_VERSION >= 302) */ #if UNICODE_SOURCE_SUPPORTED #error -- cannot support Unicode source at ABI levels < 302 #endif /* UNICODE_SOURCE_SUPPORTED */ #endif /* ABI_COMPATIBILITY_VERSION >= 302 */ static a_boolean is_closing_raw_string_delimiter( a_const_char *start_of_raw_string_delimiter, int raw_string_delimiter_len, int *olm_adjustment) /* Return TRUE if the string immediately preceding curr_char_loc is the corresponding closing delimiter of the indicated raw string delimiter. Set *olm_adjustment to the number of characters that should be subtracted from the string character count as a result of having reverted trigraphs and null characters in the closing delimiter. */ { a_boolean result = FALSE; an_orig_line_modif_ptr olmp; int right_delim_len = raw_string_delimiter_len; if (start_of_raw_string_delimiter[raw_string_delimiter_len] == '[') { /* The left delimiter was terminated by the parenthesis in the trigraph for '['. As a result, a matching right delimiter will be two characters longer than the left string and the last two characters preceding curr_char_loc must both be '?'. */ right_delim_len += 2; } /* if */ *olm_adjustment = 0; check_assertion(curr_char_loc >= start_of_raw_string_delimiter); if (curr_char_loc - start_of_raw_string_delimiter >= raw_string_delimiter_len + right_delim_len + 2) { /* The total string is long enough to contain both copies of the delimiter and the left and right parentheses. */ a_boolean found_right_paren = (*(curr_char_loc - right_delim_len - 1) == ')'); if (!found_right_paren && orig_line_modif_list != NULL && *(curr_char_loc - right_delim_len - 1) == ']') { /* Check for the obscure case in which the two characters preceding the closing right parenthesis were both '?', which will have been recognized as the trigraph for ']'. */ for (olmp = orig_line_modif_list; olmp != NULL && olmp->line_loc < curr_char_loc - right_delim_len - 1; olmp = olmp->next) {} while (olmp != NULL && olmp->line_loc == curr_char_loc - right_delim_len - 1) { if (olmp->kind == olm_trigraph) { /* The original text had a ')' in the right location. */ found_right_paren = TRUE; } /* if */ olmp = olmp->next; } /* while */ } /* if */ if (found_right_paren) { if (strncmp(start_of_raw_string_delimiter, curr_char_loc - right_delim_len, size_t_arg(raw_string_delimiter_len)) == 0) { /* The strings compare equal. Now we need to make sure that they both have the same set of modifications, if any. */ an_orig_line_modif_ptr left_olmp; an_orig_line_modif_ptr right_olmp; /* Find the first modification, if any, in the left prefix. */ for (left_olmp = orig_line_modif_list; left_olmp != NULL && left_olmp->line_loc < start_of_raw_string_delimiter; left_olmp = left_olmp->next) {} /* Find the first modification, if any, in the right prefix. */ for (right_olmp = left_olmp; right_olmp != NULL && right_olmp->line_loc < curr_char_loc - right_delim_len; right_olmp = right_olmp->next) {} if (left_olmp != NULL && left_olmp->line_loc >= start_of_raw_string_delimiter + raw_string_delimiter_len) { /* No modifications in the left prefix. */ left_olmp = NULL; } /* if */ if (right_olmp != NULL && right_olmp->line_loc >= curr_char_loc) { /* No modifications in the right prefix. */ right_olmp = NULL; } /* if */ if ((left_olmp == NULL) != (right_olmp == NULL)) { /* There are modifications in one but not the other, so the strings do not match. */ } else if (left_olmp == NULL && right_olmp == NULL) { /* There are no modifications in either, so the strings match. */ result = TRUE; } else { /* We have to compare to make sure the strings each have the same modifications in the same location. */ a_boolean modifications_match = TRUE; while (modifications_match && left_olmp->line_loc < start_of_raw_string_delimiter + raw_string_delimiter_len && right_olmp != NULL && right_olmp->line_loc < curr_char_loc) { /* We don't need to check the kinds of the modifications, as mismatched kinds would have failed the string comparison above, just the locations. */ if (left_olmp->line_loc - start_of_raw_string_delimiter != right_olmp->line_loc - (curr_char_loc - right_delim_len)) { modifications_match = FALSE; } /* if */ /* A trigraph was counted as 3 characters in the overall string length, but the delimiter length that will be subtracted was calculated using the substituted single character. Similarly, a null character (which is an error but must be handled for error recovery) was counted as one character but the delimiter length included both characters of the LE_NULL lexical escape. Update *olm_adjustment so the correct length can be calculated. */ *olm_adjustment += (left_olmp->kind == (an_orig_line_modif_kind)olm_null) ? 1 - LE_ESCAPE_LEN : 2; left_olmp = left_olmp->next; right_olmp = right_olmp->next; } /* while */ if (modifications_match) { /* Make sure we traversed all the modifications in each prefix. */ result = (left_olmp->line_loc >= start_of_raw_string_delimiter + raw_string_delimiter_len && (right_olmp == NULL || right_olmp->line_loc >= curr_char_loc)); } /* if */ } /* if */ if (result && raw_string_delimiter_len != right_delim_len) { /* Ensure that the last two characters of the presumed right delimiter are both '?'. */ result = curr_char_loc[-2] == '?' && curr_char_loc[-1] == '?'; /* Add the extra terminator length to the adjustment, too. */ *olm_adjustment += 2; } /* if */ } /* if */ } /* if */ } /* if */ return result; } /* is_closing_raw_string_delimiter */ a_boolean accum_quoted_string( unsigned long *num_chars, a_boolean is_header_name, a_string_or_char_literal_kind literal_kind, char quoting_char, a_const_char *start_of_raw_string_delimiter, int raw_string_delimiter_len, ARG_UNUSED a_const_char *string_start_loc, /* Defaulted: */ an_orig_line_modif_ptr last_olmp) /* Scan a quoted construct, i.e., a character constant or a string literal. literal_kind describes the literal being scanned (string or character, the encoding to be used, and whether a string literal is raw or not). This routine is also used for header names in #include and #line directives (is_header_name is TRUE for the #include case). curr_char_loc is just past the initial quote. Scan to the matching closing quote (indicated by quoting_char), and do not be confused by escaped characters and multibyte character sequences. Increment *num_chars by the number of target code units contained in the string, after escape processing. curr_char_loc and end_of_curr_token are set to point just before the closing quote of the string. If raw_string_delimiter_len is >= 0, the string being scanned is a C++11 raw string, and the terminating '"' must be preceded by the same string (of that length) to which start_of_raw_string_delimiter points, preceded by a right parenthesis; *num_chars will not include the length of this trailing delimiter sequence. If raw_string_delimiter_len is < 0, start_of_raw_string_delimiter is not used. string_start_loc points to the initial character of the literal, including any prefix, for diagnostic purposes (used only for reporting Unicode source vulnerabilities). If it is NULL, curr_char_loc will be used. If last_olmp is not NULL, it points to the last original line modification processed by this routine during a series of calls for multi-line string literals; if NULL (the default value), this routine will scan the list of original line modifications from the beginning to find the first one applicable to the literal. The return value is TRUE if the string was not terminated before the end of the line, FALSE if it was. The caller is responsible for issuing error messages. */ { char ch; unsigned long nchars; a_boolean unterminated = FALSE; an_orig_line_modif_ptr olmp = NULL; int delim_len_adjustment = 0; a_boolean is_raw_string; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED a_boolean suspicious_unicode = FALSE; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ nchars = 0; is_raw_string = (literal_kind & SCLK_RAW_STRING_LITERAL) != 0; check_assertion(is_raw_string == (raw_string_delimiter_len >= 0)); check_assertion(!(is_raw_string && (literal_kind & SCLK_STRING_LITERAL) == 0)); literal_kind = literal_encoding_prefix(literal_kind); #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (string_start_loc == NULL) { string_start_loc = curr_char_loc; } /* if */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ if (is_raw_string && orig_line_modif_list != NULL) { /* This is a raw string literal, potentially including trigraphs and line splices that will be reversed. Set up to account for those changes in the character count. */ for (olmp = (last_olmp != NULL) ? last_olmp->next : orig_line_modif_list; olmp != NULL && olmp->line_loc < curr_char_loc; olmp = olmp->next) { last_olmp = olmp; } /* for */ } /* if */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Initialize for scanning multibyte characters in the string. */ mbc_scan_init_if_multibyte_chars_in_source_enabled(); #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Scan through the characters of the string looking for the closing quoting character. */ while ((ch = *curr_char_loc) != quoting_char || (is_raw_string && !is_closing_raw_string_delimiter(start_of_raw_string_delimiter, raw_string_delimiter_len, &delim_len_adjustment))) { if (ch == '\\' && !is_header_name && !is_raw_string) { /* Backslash, escapes the next character. If followed by "0", "x", or "o{", an octal or hexadecimal value must be scanned. We recognize those digits so we can accurately count characters, but we do not convert them at this point. If followed by "u" or "U", a universal character name will be scanned. */ a_boolean is_delimited = FALSE; a_const_char *escape_start = curr_char_loc; ch = *(++curr_char_loc); if (ch == LE_ESCAPE) { /* Token ends after the "\" -- this is an unclosed string. This can happen because of macro definitions on the command line, e.g., -DX="\ */ /* Also handles \ followed by an LE_ESCAPE/LE_NULL sequence, though not terribly gracefully. */ unterminated = TRUE; goto return_point; } else if (((ch == 'u' || ch == 'U') && universal_character_names_allowed) || (ch == 'N' && curr_char_loc[1] == '{' && named_unicode_chars_allowed)) { /* A universal character name or Unicode named character escape sequence. Skip past the characters that make up the construct. Ignore any errors at this point -- they will be issued when the escape is converted to a character. */ a_boolean malformed_err = FALSE; is_delimited = delimited_escape_seqs_allowed && ch == 'u' && curr_char_loc[1] == '{'; /* Back up one character because the routines expect the opening backslash to be the current character. */ curr_char_loc--; if (ch == 'N') { if (scan_named_unicode_char(&curr_char_loc, /*is_identifier=*/FALSE, /*is_identifier_start=*/FALSE, /*issue_diagnostics=*/FALSE, /*update_pos_on_error=*/FALSE) > MAX_UNICODE_VAL) { /* An error occurred and curr_char_loc was not updated. Treat the '\' as an individual character, not part of an escape sequence. */ ch = '\\'; goto normal_char; } /* if */ } else { (void)scan_universal_character(&curr_char_loc, /*is_identifier=*/FALSE, /*is_identifier_start=*/FALSE, /*issue_diagnostics=*/FALSE, &malformed_err); } /* if */ if (malformed_err) { /* A malformed escape sequence will be treated as an ordinary sequence of characters (skipping the '\') with no special meaning. */ nchars += (unsigned long)(curr_char_loc - escape_start - 1); } else if ((ch == 'U' || (ch == 'u' && is_delimited) || ch == 'N') && (literal_kind == SCLK_CHAR16_T_LITERAL || literal_kind == SCLK_WIDE_LITERAL)) { /* A (potentially) 32-bit code to be stored in a 16-bit character representation, which could result in a surrogate pair. Since we don't know how many characters will be needed for the encoding, assume the longest. */ nchars += MAX_CHAR16_T_ENCODING_LENGTH; } else if (literal_kind == SCLK_ORDINARY_LITERAL || literal_kind == SCLK_UTF8_LITERAL) { /* The character will be translated into a multibyte character set; the actual length will be calculated when the literal is converted. We set 4 as the estimated length because the target encoding may be UTF-8, and the maximum length of a UTF-8 sequence is 4 bytes. An additional check will be made when the literal is converted to ensure that the converted character does not overflow. */ nchars += 4; } else { /* A char32_t literal, which can hold the value in one character, or a wide-character literal, which will truncate to a single character if necessary. */ ++nchars; } /* if */ } else { is_delimited = delimited_escape_seqs_allowed && (ch == 'o' || ch == 'x') && curr_char_loc[1] == '{'; curr_char_loc += is_delimited ? 2 : 1; nchars++; if (isdigit((unsigned char)ch) && ch != '8' && ch != '9') { /* Octal escape, one to three digits. Note that neither ANSI nor pcc allows 8 and 9 as octal digits in this case. Note that there is code in conv_single_char that must match this code.*/ ch = *curr_char_loc; if (isdigit((unsigned char)ch) && ch != '8' && ch != '9') { curr_char_loc++; ch = *curr_char_loc; if (isdigit((unsigned char)ch) && ch != '8' && ch != '9') { curr_char_loc++; } /* if */ } /* if */ } else if (ch == 'o' && is_delimited) { /* A delimited octal escape, which allows unlimited octal digits (but represents only a single character). */ ch = *curr_char_loc; if (ch == '}') { /* An empty sequence. */ diagnostic_at_line_pos(es_discretionary_error, ec_empty_delimited_escape, curr_char_loc); } else { while (isdigit((unsigned char)ch) && ch != '8' && ch != '9') { ch = *++curr_char_loc; } /* while */ if (*curr_char_loc == '}') { ++curr_char_loc; } else { diagnostic_at_line_pos(es_discretionary_error, ec_unterminated_delimited_escape, curr_char_loc); /* Adjust the character count to allow for all the characters of the malformed escape sequence (already incremented by one in anticipation of the expected single character result). */ nchars += (unsigned long)(curr_char_loc - escape_start - 1); } /* if */ } /* if */ } else if (ch == 'x') { /* Hex escape, any number of digits. */ if (is_delimited && *curr_char_loc == '}') { /* An empty sequence. */ diagnostic_at_line_pos(es_discretionary_error, ec_empty_delimited_escape, curr_char_loc); } else { while (isxdigit((unsigned char)*curr_char_loc)) { ++curr_char_loc; } /* while */ if (is_delimited && *curr_char_loc != '}') { diagnostic_at_line_pos(es_discretionary_error, ec_unterminated_delimited_escape, curr_char_loc); /* Adjust the character count to allow for all the characters of the malformed escape sequence (already incremented by one in anticipation of the expected single character result). */ nchars += (unsigned long)(curr_char_loc - escape_start - 1); } /* if */ } /* if */ if (is_delimited && *curr_char_loc == '}') { ++curr_char_loc; } /* if */ } /* if */ } /* if */ } else if (olmp != NULL && olmp->line_loc == curr_char_loc) { /* Trigraphs and line splices inside raw string literals will be reversed, so calculate the length based on the original characters. */ switch (olmp->kind) { case olm_trigraph: /* Two '?'s and another character were replaced by one character. */ nchars += 3; if (olmp->next == NULL || olmp->next->line_loc > curr_char_loc) { /* Advance the current location only if the next modification entry is not at the current location (to allow for cases where the backslash in a line splice is represented by a trigraph, which will result in an olm_trigraph followed by an olm_line_splice at the same location). */ ++curr_char_loc; } /* if */ break; case olm_line_splice: /* A '\' and a newline were replaced by nothing. */ nchars += 2; break; case olm_multiline_string_splice: /* A newline was replaced by '\' and 'n'. */ ++nchars; curr_char_loc += 2; break; case olm_null: /* A zero byte was replaced by an LE_NULL lexical escape. In Microsoft mode, the character is ignored. */ if (!microsoft_mode) { ++nchars; } /* if */ curr_char_loc += LE_ESCAPE_LEN; break; case olm_splice_whitespace: /* The backslash of a line splice was followed by a whitespace character (removed from the logical source line). */ ++nchars; break; default: unexpected_condition(); } /* switch */ last_olmp = olmp; olmp = olmp->next; } else if (ch == LE_ESCAPE) { if (curr_char_loc[1] == LE_NULL) { /* Null (zero) character. */ if (raw_string_delimiter_len >= 0) { /* This must be a raw string inside a macro expansion, and a null character is permitted there. */ check_assertion(!within_curr_source_line(curr_char_loc)); if (!microsoft_mode) { ++nchars; } /* if */ } else if (!null_chars_allowed_in_source) { diagnostic_at_line_pos(es_discretionary_error, ec_invalid_char, curr_char_loc); if (!microsoft_mode) { /* Keep the character in the string, except in Microsoft mode. */ ++nchars; } /* if */ } else if (microsoft_mode) { /* In Microsoft mode, the character is thrown away. */ warning_at_line_pos(ec_null_char_ignored, curr_char_loc); } else { warning_at_line_pos(is_header_name ? ec_null_char_in_header_name: ec_null_char_in_string, curr_char_loc); nchars++; } /* if */ curr_char_loc += LE_ESCAPE_LEN; } else { /* Other lexical escape, e.g., newline. The string is unterminated at end of line or in some other strange way that comes up with preprocessing. */ unterminated = TRUE; goto return_point; } /* if */ } else { normal_char: /* Normal character. */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED if (multibyte_chars_in_source_enabled) { /* Advance to the next character, dealing with multibyte characters. */ a_boolean err = FALSE; int numch; #if EDG_WIN32 || UNICODE_VULNERABILITY_DETECTION_SUPPORTED unsigned long wc; #endif /* EDG_WIN32 || UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && curr_file_unicode_source_kind != usk_none) { numch = mbc_to_wide_char(curr_char_loc, &wc, &err, /*is_native=*/FALSE); if (!err && wc > 0x2000 && check_for_suspicious_control(wc, /*flag_zero_width=*/TRUE)) { suspicious_unicode = TRUE; } /* if */ } else #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ /* Do not insert code here. */ #if !EDG_WIN32 numch = lex_mbc_length(curr_char_loc, &err); #else /* EDG_WIN32 */ /* Windows has a bug that can cause the length returned by lex_mbc_length_simple to differ from what is returned by lex_mbc_to_wide_char. The length computed here must match the value computed by conv_string_literal, so on Windows we use lex_mbc_to_wide_char here. */ numch = lex_mbc_to_wide_char(curr_char_loc, &wc, &err); #endif /* !EDG_WIN32 */ /* Increment curr_char_loc by numch and create any logical character index entries. */ incr_char_loc_for_multibyte_char(curr_char_loc, numch); if (err) { /* conv_string_literal will replace an erroneous multibyte character with a single character. */ ++nchars; } else { switch (literal_kind) { case SCLK_ORDINARY_LITERAL: /* Assume the character will have the same length in the target encoding. */ nchars += (unsigned long)(long)numch; break; case SCLK_UTF8_LITERAL: /* Assume the maximum UTF-8 character length, which will be revised downward if necessary when the character is converted. */ nchars += 4; break; case SCLK_CHAR32_T_LITERAL: /* char32_t should be able to accommodate any character code. */ ++nchars; break; case SCLK_CHAR16_T_LITERAL: case SCLK_WIDE_LITERAL: /* A single char16_t or wchar_t is not assumed to be sufficient for a multibyte input character. Instead, we assume the longest encoding case. */ nchars += MAX_CHAR16_T_ENCODING_LENGTH; break; default: unexpected_condition(); } /* switch */ } /* if */ } else #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ /* Do not insert code here -- this is the "else" of an "if". */ { /* Advance to the next character without worrying about multibyte characters. */ curr_char_loc++; if (literal_kind == SCLK_UTF8_LITERAL) { /* The character will be represented as UTF-8. Determine the number of code units it will occupy. (This conversion assumes that the source is in Latin-1, whose code points are the same as the first 256 code points of Unicode.) */ unsigned char utf8_char[4]; nchars += unicode_to_utf8((unsigned long)ch, utf8_char); } else { /* The character will occupy one code unit of the target string. */ nchars++; } /* if */ } /* if */ } /* if */ } /* while */ return_point: end_of_curr_token = curr_char_loc; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && multibyte_chars_in_source_enabled && curr_file_unicode_source_kind != usk_none) { if (!unterminated && (pending_bidi_controls != NULL || suspicious_unicode)) { /* This is the last (or only) line of the string, and we either had an unterminated bidirectional control or the bidirectional nesting rules were violated. (Only the last line of a multi-line string need be considered for suspicious formatting.) Issue a warning. */ warning_at_line_pos(ec_suspicious_string_formatting, string_start_loc); } /* if */ pop_bidi_control(/*flush=*/TRUE); } /* if */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ if (unterminated) { end_of_curr_token--; *num_chars += nchars; } else if (!is_raw_string) { *num_chars += nchars; } else { *num_chars += nchars; *num_chars -= (unsigned long)raw_string_delimiter_len; *num_chars -= (unsigned long)delim_len_adjustment; *num_chars -= 1; } /* if */ return unterminated; } /* accum_quoted_string */ static a_boolean check_for_ud_suffix(void) /* Upon entry, curr_char_loc points to the character following a character-literal or string-literal. If a ud-suffix for a user-defined-literal is found there, update curr_char_loc to point after the ud-suffix and return TRUE. Otherwise, leave curr_char_loc unchanged and return FALSE. */ { a_const_char *p = curr_char_loc; a_boolean initial_char = TRUE; int char_bytes; a_boolean found_suffix = FALSE; check_assertion(user_defined_literals_enabled); while (is_identifier_char(p, &char_bytes, initial_char)) { p += char_bytes; initial_char = FALSE; } /* while */ if (p != curr_char_loc) { /* Found a ud-suffix. */ found_suffix = TRUE; curr_char_loc = p; } /* if */ return found_suffix; } /* check_for_ud_suffix */ static a_token_kind scan_char_constant(a_string_or_char_literal_kind lit_kind) /* Scan a character constant token described by lit_kind, return the token kind or tok_error. The token can be a normal or wide character constant. */ { a_token_kind ctoken = tok_char_constant; a_character_kind character_kind = (a_character_kind)ck_last; unsigned long num_chars = 0; an_error_code err_code = ec_no_error; a_const_char *err_pos = curr_char_loc; a_source_position start_pos; check_assertion((lit_kind & SCLK_STRING_LITERAL) == 0); macro_line_loc_to_source_pos(curr_char_loc, start_pos); switch (lit_kind) { case SCLK_ORDINARY_LITERAL: character_kind = (a_character_kind)chk_char; break; case SCLK_UTF8_LITERAL: character_kind = char8_t_enabled ? (a_character_kind)chk_char8_t : (a_character_kind)chk_char; break; case SCLK_WIDE_LITERAL: character_kind = (a_character_kind)chk_wchar_t; break; case SCLK_CHAR32_T_LITERAL: character_kind = (a_character_kind)chk_char32_t; break; case SCLK_CHAR16_T_LITERAL: character_kind = (a_character_kind)chk_char16_t; break; default: unexpected_condition(); } /* switch */ curr_char_loc += offset_to_start_of_literal_value(lit_kind); /*lint !e679*/ if (accum_quoted_string(&num_chars, /*is_header_name=*/FALSE, lit_kind, '\'', NULL, -1, err_pos)) { /* Error, character constant is unclosed. */ /* Similar error for other strange cases of incomplete strings, which can come up with preprocessing. */ /* Message is generic -- "Missing closing quote". */ ctoken = tok_error; err_code_for_error_token = ec_unclosed_string; } else { /* Advance past closing quote. */ check_assertion(*curr_char_loc == '\''); curr_char_loc++; /* Character constants may not be zero length. (Except wide character literals in Microsoft mode.) */ if (num_chars == 0) { if (ms_extensions && character_kind == (a_character_kind)chk_wchar_t) { /* Microsoft accepts L'' as a null character constant. (The call to conv_char_literal below will correctly produce a zero value given an empty literal.) */ if (!fetch_pp_tokens) { warning_at_line_pos(ec_empty_wide_character, start_of_curr_token); } /* if */ } else { ctoken = tok_error; err_code_for_error_token = ec_zero_length_string; } /* if */ } /* if */ } /* if */ if (!fetch_pp_tokens) { /* Convert the constant to internal form. */ if (ctoken == tok_error) { ctoken = tok_char_constant; set_error_constant(&const_for_curr_token); err_code = err_code_for_error_token; err_pos = start_of_curr_token; } else { conv_char_literal(num_chars, &err_code, &err_pos); report_char_overflows(); } /* if */ } /* if */ if (err_code == ec_no_error && user_defined_literals_enabled) { /* Check for a user-defined literal. */ a_const_char *id_start = curr_char_loc; if (check_for_ud_suffix()) { /* Found a ud-suffix. */ if (!fetch_pp_tokens) { /* The type and value of the literal are known: set up locator_for_curr_id and ud_lit_op_sym_for_curr_token (they are not needed when fetching pp-tokens). */ if (const_for_curr_token.type == integer_type((an_integer_kind)ik_int)) { /* A multicharacter literal cannot be part of a user-defined literal because it is not permitted to declare a literal operator with int as its parameter type. Note that the ud-suffix is simply discarded -- it does not become part of the current token -- and the token value and kind are left unchanged. */ err_code = ec_multichar_ud_lit; err_pos = start_of_curr_token; } else { sizeof_t id_len = (sizeof_t)(curr_char_loc - id_start); a_const_char *canonical_id = make_canonical_identifier(id_start, &id_len, /*force_ucn=*/FALSE); /* The value of curr_token is set to indicate to cache_curr_token (if invoked by lookups triggered by find_literal_operator) that the current token state is a partially resolved user-defined literal. */ curr_token = tok_unresolved_ud_literal; ud_lit_op_sym_for_curr_token = find_literal_operator(canonical_id, id_len, &start_pos, const_for_curr_token.type, /*from_cache=*/FALSE, (a_diagnostic_ptr)NULL); ud_lit_type_for_curr_token = const_for_curr_token.type; } /* if */ } /* if */ if (err_code == ec_no_error) { ctoken = tok_ud_literal; end_of_curr_token = curr_char_loc - 1; } /* if */ } /* if */ } /* if */ /* Check for errors detected. */ if (err_code != ec_no_error) { error_at_line_pos(err_code, err_pos); } /* if */ return ctoken; } /* scan_char_constant */ static a_boolean scan_multiline_string( unsigned long *num_chars, a_string_or_char_literal_kind literal_kind, a_const_char *string_start_loc, a_const_char *start_of_raw_string_delimiter, int raw_string_delimiter_len) /* Process the second and subsequent lines of a multi-line string. Return TRUE if the string turns out to be well-formed, FALSE otherwise. literal_kind indicates the kind of literal (wide, raw, etc.) being scanned. The number of characters scanned (a conservative estimate in the case of UTF-8, char16_t, and wide literals) is added to *num_chars. string_start_loc is points to the first character of the literal, including any prefix, for diagnostic purposes. If raw_string_delimiter_len is >= 0, the string being scanned is a C++11 raw string, and the terminating '"' must be preceded by the same string (of that length) to which start_of_raw_string_delimiter points, preceded by a right parenthesis; *num_chars will not include the length of this trailing delimiter sequence. If raw_string_delimiter_len is < 0, start_of_raw_string_delimiter is not used. */ { an_orig_line_modif_ptr olmp; an_orig_line_modif_ptr prev_olmp; a_boolean result = FALSE; a_const_char *delim_ptr; a_pointer_registration delim_ptr_reg; a_pointer_registration_ptr save_registered_pointers = registered_pointers; register_pointer_variable(delim_ptr, delim_ptr_reg); delim_ptr = start_of_raw_string_delimiter; while (curr_char_loc[0] == LE_ESCAPE && curr_char_loc[1] == LE_NEWLINE) { /* Inject the characters \ n on top of the NEWLINE escape, and add an entry to the orig_line_modif_list so that this can be undone. */ prev_olmp = end_orig_line_modif_list; olmp = add_orig_line_modif(olm_multiline_string_splice, curr_char_loc); olmp->variant.line_splice_seq_number = seq_number_last_read + 1; *(char *)curr_char_loc++ = '\\'; *(char *)curr_char_loc++ = 'n'; /* Read the next line of the input file, extending the current logical source line. */ if (read_logical_source_line(/*do_pop_on_end_of_file=*/FALSE, /*extend_current_line=*/TRUE)) { /* End of file, report an error. */ break; } /* if */ /* Back up over the \n added above and resume scanning. */ curr_char_loc -= 2; if (!accum_quoted_string(num_chars, /*is_header_name=*/FALSE, literal_kind, '"', delim_ptr, raw_string_delimiter_len, string_start_loc, prev_olmp)) { /* End of string, done. */ result = TRUE; break; } /* if */ } /* while */ registered_pointers = save_registered_pointers; return result; } /* scan_multiline_string */ static a_boolean scan_raw_string_delimiter(void) /* On entry, curr_char_loc is pointing to the first character of the d-char-sequence (delimiter) of a C++11 raw string literal. If a '(' is found within the permitted delimiter length, advance curr_char_loc to point immediately after the '(' that terminates the d-char-sequence and return TRUE; otherwise, issue an error diagnostic, leave curr_char_loc unchanged, and return FALSE. */ { #define MAX_DELIM_LENGTH 16 a_boolean found_end = FALSE; a_const_char *p; a_const_char *max_delim = curr_char_loc + MAX_DELIM_LENGTH; an_orig_line_modif_ptr olmp; int trigraph_len_offset = 0; a_const_char *invalid_char_loc[MAX_DELIM_LENGTH]; int num_invalid_chars_seen = 0; a_byte_boolean invalid_char_seen[CHAR_MAX-CHAR_MIN+1]; /* Find the first original line modification that might be in the delimiter, if any. */ for (olmp = orig_line_modif_list; olmp != NULL && olmp->line_loc < curr_char_loc; olmp = olmp->next) {} /* Scan through the line looking for the end of the delimiter. */ for (p = curr_char_loc; !found_end && p <= max_delim - trigraph_len_offset; ++p) { a_boolean char_is_invalid = FALSE; char invalid_char = '\0'; if (*p == '(') { /* This is the terminator for the delimiter */ found_end = TRUE; } else if (*p == LE_ESCAPE) { /* This can only happen at the end of a line; do not advance further. */ break; } else if (olmp != NULL && olmp->line_loc == p) { /* This character is the result of an original line modification (trigraph or splice replacement). */ if (olmp->kind != olm_trigraph) { /* The non-trigraph modifications imply that a character not allowed in a raw string delimiter appeared in the original source line. Treat it as a backslash. */ char_is_invalid = TRUE; invalid_char = '\\'; } else if (olmp->variant.orig_char == '(') { /* Recognition of the delimiter applies to the unmodified source, so the '(' terminates the delimiter. */ found_end = TRUE; } else { /* The length calculation is based on the original characters, too; this character accounts for three in the original source. */ trigraph_len_offset += 2; olmp = olmp->next; } /* if */ } else if (!is_raw_string_delimiter_char[*p-CHAR_MIN]) { /* The character is not permitted in a d-char-sequence. */ char_is_invalid = TRUE; invalid_char = *p; } /* if */ if (char_is_invalid) { if (num_invalid_chars_seen == 0) { /* This is the first one. Put out only one diagnostic for each invalid code point used in the delimiter -- if the user wrote "@@@$$$" as a delimiter, there's no reason to put out six diagnostics, but it could be friendly to put out one for the first '@' and one for the first '$'. */ int c; for (c = CHAR_MIN; c <= CHAR_MAX; ++c) { invalid_char_seen[c - CHAR_MIN] = FALSE; } } /* if */ if (num_invalid_chars_seen > 0 && *invalid_char_loc[num_invalid_chars_seen - 1] == LE_ESCAPE && p == invalid_char_loc[num_invalid_chars_seen - 1] + 1 /*lint --e(530)*/) { /* Do not report the second character of a lexical escape as a separate invalid character. */ } else if (!invalid_char_seen[invalid_char - CHAR_MIN]) { invalid_char_seen[invalid_char - CHAR_MIN] = TRUE; invalid_char_loc[num_invalid_chars_seen++] = p; } /* if */ } /* if */ } /* for */ if (found_end) { int i; an_error_severity severity = currently_in_pp_if_skip ? es_remark : es_discretionary_error; /* Put out any pending "invalid character" diagnostics. */ for (i = 0; i < num_invalid_chars_seen; ++i) { diagnostic_at_line_pos(severity, ec_bad_raw_string_delim_char, invalid_char_loc[i]); } /* for */ /* Advance curr_char_loc to point after the '(' or '[' that terminates the delimiter. */ curr_char_loc = p; } else { /* Left parenthesis not found. Ignore any pending "invalid character" diagnostics, as the raw string appears to be garbage. */ error_at_line_pos(ec_missing_raw_string_delim_lparen, start_of_curr_token); if (fetch_pp_tokens) { /* Skip over the "R" prefix to avoid cascading messages from macro expansions. */ ++start_of_curr_token; } /* if */ } /* if */ return found_end; #undef MAX_DELIM_LENGTH } /* scan_raw_string_delimiter */ a_token_kind scan_string_literal(a_string_or_char_literal_kind lit_kind) /* Scan a string literal token, described by lit_kind, and return the token kind or tok_error. If fetch_pp_tokens is FALSE, set const_for_curr_token to contain the value of the string. The token can be any kind of string literal. On entry, curr_char_loc points to the first character after the opening quotation mark; on exit, it points after the closing quote. */ { a_token_kind ctoken = tok_string_literal; unsigned long num_chars = 0; an_error_code err_code; a_const_char *err_pos; int raw_string_delimiter_len = -1; a_boolean unterminated; a_const_char *start_of_raw_string_delimiter = NULL; a_pointer_registration start_of_raw_string_delimiter_reg; a_const_char *start_of_string_value; a_pointer_registration start_of_string_value_reg; a_const_char *str_start_loc; a_pointer_registration str_start_loc_reg; a_pointer_registration_ptr save_registered_pointers = registered_pointers; register_pointer_variable(start_of_raw_string_delimiter, start_of_raw_string_delimiter_reg); register_pointer_variable(start_of_string_value, start_of_string_value_reg); register_pointer_variable(str_start_loc, str_start_loc_reg); check_assertion(lit_kind & SCLK_STRING_LITERAL); start_of_string_value = curr_char_loc; str_start_loc = curr_char_loc - offset_to_start_of_literal_value(lit_kind); if (lit_kind & SCLK_RAW_STRING_LITERAL) { /* The literal appears to be a raw string. Scan the delimiter and save its location and length for later use. */ start_of_raw_string_delimiter = curr_char_loc; if (scan_raw_string_delimiter()) { /* curr_char_loc now points immediately following the left parenthesis that terminates the delimiter. Calculate the length of the delimiter (not counting the left parenthesis). */ raw_string_delimiter_len = (int)(curr_char_loc - start_of_raw_string_delimiter - 1); start_of_string_value = curr_char_loc; } else { /* Clear the raw string indicator in lit_kind, since we're treating this as a non-raw string. */ lit_kind &= ~SCLK_RAW_STRING_LITERAL; } /* if */ } /* if */ unterminated = accum_quoted_string(&num_chars, /*is_header_name=*/FALSE, lit_kind, '"', start_of_raw_string_delimiter, raw_string_delimiter_len, str_start_loc); if (unterminated && curr_cmd_line_or_predef_macro_def == NULL && (raw_string_delimiter_len >= 0 #if GNU_EXTENSIONS_ALLOWED || (gnu_mode && gnu_version < 30300) #endif /* GNU_EXTENSIONS_ALLOWED */ )) { /* Raw string literals, as well as gcc and g++ versions prior to 3.3, permit a string literal to extend over multiple lines. */ unterminated = !scan_multiline_string(&num_chars, lit_kind, str_start_loc, start_of_raw_string_delimiter, raw_string_delimiter_len); } /* if */ if (unterminated) { /* Error, string is unclosed. */ /* Similar error for other strange cases of incomplete strings, which can come up with preprocessing. */ ctoken = tok_error; if (raw_string_delimiter_len < 0) { /* Message is generic -- "Missing closing quote". */ err_code_for_error_token = ec_unclosed_string; } else { /* Give specific raw string message. */ err_code_for_error_token = ec_missing_raw_string_delimiter; } /* if */ } else { /* Advance past closing quote. */ check_assertion(*curr_char_loc == '"'); curr_char_loc++; } /* if */ if (!fetch_pp_tokens) { /* Convert the constant to internal form. */ if (ctoken == tok_error) { ctoken = tok_string_literal; set_error_constant(&const_for_curr_token); error_at_line_pos(err_code_for_error_token, start_of_curr_token); } else { int ending_delim_len = raw_string_delimiter_len; if (raw_string_delimiter_len >= 0 && start_of_raw_string_delimiter[raw_string_delimiter_len] == '[') { /* The delimiter includes two '?' characters not included in the length because they were part of a trigraph. */ ending_delim_len += 2; } /* if */ conv_string_literal(start_of_string_value, end_of_curr_token - ending_delim_len - 1, lit_kind, num_chars, &err_code, &err_pos); /* Check for errors detected. */ if (err_code != ec_no_error) { diagnostic_at_line_pos(es_discretionary_error, err_code, err_pos); } /* if */ } /* if */ } else if (start_of_raw_string_delimiter != NULL && orig_line_modif_list != NULL) { /* Mark any original line modifications occurring within a raw string literal. */ an_orig_line_modif_ptr olmp; for (olmp = orig_line_modif_list; olmp != NULL && olmp->line_loc < curr_char_loc; olmp = olmp->next) { if (olmp->line_loc >= start_of_raw_string_delimiter) { olmp->in_raw_string_literal = TRUE; } /* if */ } /* for */ } /* if */ registered_pointers = save_registered_pointers; return ctoken; } /* scan_string_literal */ static a_token_kind scan_header_name(void) /* Scan a header name token, return the token kind or tok_error. */ { a_token_kind ctoken = tok_header_name; unsigned long num_chars = 0; char quoting_char; quoting_char = *curr_char_loc++; /* Angle-bracket includes are closed by a different character than opens them. */ if (quoting_char == '<') quoting_char = '>'; check_assertion(quoting_char == '"' || quoting_char == '>'); if (accum_quoted_string(&num_chars, /*is_header_name=*/TRUE, SCLK_ORDINARY_STRING_LITERAL, quoting_char, NULL, -1, curr_char_loc - 1)) { /* Error, header name is unclosed. */ ctoken = tok_error; err_code_for_error_token = ec_unclosed_string; } else { /* Advance past closing quote. */ check_assertion(*curr_char_loc == quoting_char); curr_char_loc++; } /* else */ if (!fetch_pp_tokens && ctoken == tok_error) { error_at_line_pos(err_code_for_error_token, start_of_curr_token); ctoken = tok_header_name; } /* if */ return ctoken; } /* scan_header_name */ #if ASM_SUPPORT_NEEDED #define ASM_FUNC_BODY_BUFFER_INCREMENTAL_ALLOCATION 1024 /* Initial and incremental allocation size for asm_func_body_buffer. The initial allocation should be such that almost all cases can be accepted (so that the realloc is hardly ever needed). */ static void expand_asm_func_body_buffer(sizeof_t size_needed) /* Expand the asm_func_body_buffer by reallocating it, so that its total size is at least size_needed. Called by ensure_asm_func_body_buffer_space. */ { sizeof_t new_size; new_size = size_asm_func_body_buffer + ASM_FUNC_BODY_BUFFER_INCREMENTAL_ALLOCATION; if (new_size < size_needed) new_size = size_needed; asm_func_body_buffer = realloc_buffer(asm_func_body_buffer, size_asm_func_body_buffer, new_size); size_asm_func_body_buffer = new_size; } /* expand_asm_func_body_buffer */ static void add_to_asm_func_buffer(a_const_char *start_char, sizeof_t len) /* Add len characters to the asm function body buffer, beginning at start_char (a pointer to a piece of text in the source program). */ { /* Ensure that asm_func_body_buffer has enough space left to accommodate "len" bytes. If not, expand asm_func_body_buffer by reallocating it. */ if (size_asm_func_body_buffer - pos_in_asm_func_body_buffer < len) { expand_asm_func_body_buffer((sizeof_t)(pos_in_asm_func_body_buffer + len)); } /* if */ memcpy(&asm_func_body_buffer[pos_in_asm_func_body_buffer], start_char, size_t_arg(len)); pos_in_asm_func_body_buffer += len; } /* add_to_asm_func_buffer */ /* This variable and the EXTERN prev_asm_stop_char remember where the previous copy_from_source_to_asm_func_buffer() left off. They are set by scan_asm_function_body() to start just after the initial left brace. */ STATIC_THREAD a_seq_number prev_seq_number; #if !ASM_FUNCTION_ALLOWED static #endif /* !ASM_FUNCTION_ALLOWED */ void reset_asm_buffer(void) /* Reset the static variables used while building the string representation of an asm function or Microsoft asm block. */ { pos_in_asm_func_body_buffer = 0; prev_asm_stop_char = NULL; prev_seq_number = curr_seq_number; } /* reset_asm_buffer */ #if !ASM_FUNCTION_ALLOWED static #endif /* !ASM_FUNCTION_ALLOWED */ void copy_from_source_to_asm_func_buffer( a_const_char *stop_char, ARG_UNUSED a_const_char *after_comment_stop_char) /* The buffer in which to collect the characters comprising the asm function is asm_func_body_buffer. Append to it all the characters in the source beginning at *prev_asm_stop_char through, but not including, *stop_char. Then, if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY is TRUE and after_comment_stop_char is non-NULL, also append the characters in the comment, through but not including *after_comment_stop_char. */ { a_source_line_modif_ptr slmp; a_const_char *curr_char, *next_char = NULL; sizeof_t len; char ch; a_boolean ends_with_newline = FALSE; if (prev_seq_number != curr_seq_number) { /* We've advanced to a new source line. Update prev_asm_stop_char to point to the start of the new line. */ if (line_start_source_line_modif != NULL) { prev_asm_stop_char = line_start_source_line_modif->inserted_text; } else { prev_asm_stop_char = curr_source_line; } /* if */ prev_seq_number = curr_seq_number; } /* if */ /* curr_char is the pointer into the source line. Its initial value is usually prev_asm_stop_char, which is usually the character following the last character that was copied into the buffer. */ if (prev_asm_stop_char != NULL) { curr_char = prev_asm_stop_char; } else { /* prev_asm_stop_char is NULL, which is the case on the first call to this routine for a given asm function (or following a case in which there is no start_of_curr_token pointer). Use the initial character of the current token. */ curr_char = start_of_curr_token; if (curr_char == NULL) { /* Rare case -- there is no start_of_curr_token pointer. */ curr_char = stop_char; } /* if */ prev_asm_stop_char = curr_char; } /* if */ while (curr_char != stop_char) { switch (*curr_char) { case LE_ESCAPE: ch = curr_char[1]; if (ch == LE_END_OF_TOKEN || ch == LE_INERT_MACRO || ch == LE_TEMPORARILY_INERT_MACRO || ch == LE_NULL || ch == LE_COMMA_FROM_ARGUMENT || ch == LE_LPAREN_FROM_ARGUMENT || ch == LE_RAW_OR_EXPANDED_ARGUMENT || ch == LE_EMPTY_VARIADIC_MACRO #if !FULLY_RESOLVED_MACRO_POSITIONS || ch == LE_END_OF_TOP_LEVEL_EXPANSION #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ ) { /* Marker put into text by preprocessing of macros, to force the same interpretation of token boundaries as during the macro definition. Or, marker that indicates that a macro name should not be expanded, or represents a null (zero) character. Or, marker that indicates that the next comma or left parenthesis token came from a macro argument. Or, marker for the end of a top-level macro invocation. Or, marker for a special sequence that includes both raw and expanded versions of a macro argument. Or, marker for an empty variadic macro expansion. Skip over the escape and don't put it out. */ next_char = curr_char + LE_ESCAPE_LEN; } else if (ch == LE_END_OF_INSERTION) { /* End of the expansion text for a macro. Find the character location of the character following the macro invocation, and continue there. */ slmp = assoc_source_line_modif(curr_char); next_char = curr_char; leave_insertion(slmp, next_char); } else if (ch == LE_NEWLINE) { /* Newline character. */ ends_with_newline = TRUE; next_char = curr_char + LE_ESCAPE_LEN; } else if (ch == LE_MICROSOFT_MAGIC_COMMA) { /* A comma that is suppressed because it appeared preceding an empty __VA_ARGS__ expansion. Skip the escape and the comma. */ next_char = curr_char + LE_ESCAPE_LEN + 1; } else { unexpected_condition_str( "copy_from_source_to_asm_func_buffer: bad lexical escape"); } /* if */ break; case ATTENTION_MARKER: /* Marker placed into source text to provide a cue to the fact that a source modification (probably a text replacement due to a macro expansion) begins here. next_char will be adjusted by the macro to point to the first character in the insertion text. */ next_char = curr_char; go_into_insertion(slmp, next_char); break; default: /* Normal case: bump curr_char and keep looping. */ curr_char++; continue; } /* switch */ /* Falling through to here mean one of the special characters was seen. Copy the characters from prev_asm_stop_char through (but not including) curr_char into the buffer, and then reset prev_asm_stop_char and curr_char to next_char. */ if ((len = (sizeof_t)(curr_char - prev_asm_stop_char)) != 0) { /* Add "len" characters to the buffer, starting at prev_asm_stop_char. */ add_to_asm_func_buffer(prev_asm_stop_char, len); } /* if */ curr_char = prev_asm_stop_char = next_char; if (ends_with_newline) { ends_with_newline = FALSE; len = 1; add_to_asm_func_buffer("\n", len); } /* if */ } /* while */ check_assertion(prev_asm_stop_char != NULL); if (curr_char > prev_asm_stop_char) { /* Copy the characters from prev_asm_stop_char through (but not including) curr_char into the buffer. */ len = (sizeof_t)(curr_char - prev_asm_stop_char); /* Add "len" characters to the buffer, starting at prev_asm_stop_char. */ add_to_asm_func_buffer(prev_asm_stop_char, len); prev_asm_stop_char = curr_char; } /* if */ #if INCLUDE_COMMENTS_IN_ASM_FUNC_BODY if (after_comment_stop_char != NULL) { /* Append text of commentary, too. */ ends_with_newline = FALSE; check_assertion(after_comment_stop_char > prev_asm_stop_char); len = (sizeof_t)(after_comment_stop_char - prev_asm_stop_char); if (len >= LE_ESCAPE_LEN && after_comment_stop_char[-LE_ESCAPE_LEN ] == LE_ESCAPE && after_comment_stop_char[-LE_ESCAPE_LEN+1] == LE_NEWLINE) { /* The comment ends with a newline. Put it out separately below (the lexical escape in the line is not the '\n' character we want in the string). */ ends_with_newline = TRUE; len -= LE_ESCAPE_LEN; } /* if */ /* Add "len" characters to the buffer, starting at prev_asm_stop_char. */ add_to_asm_func_buffer(prev_asm_stop_char, len); if (ends_with_newline) { len = 1; add_to_asm_func_buffer("\n", len); } /* if */ /* Reset prev_asm_stop_char. */ prev_asm_stop_char = after_comment_stop_char; } /* if */ #endif /* INCLUDE_COMMENTS_IN_ASM_FUNC_BODY */ } /* copy_from_source_to_asm_func_buffer */ #endif /* ASM_SUPPORT_NEEDED */ #if MICROSOFT_EXTENSIONS_ALLOWED static void skip_asm_comment(a_boolean include_newline) /* A semicolon in a Microsoft asm begins an asm comment. Process the rest of the tokens on the line specially so that they are not included in brace counts (for example). include_newline is TRUE if the terminating newline should be included in the asm string. */ { while (curr_token != tok_newline && curr_token != tok_end_of_source) { (void)get_token(); if (curr_token != tok_newline || include_newline) { copy_from_source_to_asm_func_buffer(end_of_curr_token + 1, (char *)NULL); } /* if */ } /* while */ } /* skip_asm_comment */ static a_boolean build_microsoft_asm_string(void) /* When an __asm token is encountered in Microsoft mode, the string that follows is immediately scanned and stored in curr_token_asm_string. This value is saved and restored as needed by the token caching mechanism. This routine scans and builds the asm string. When ms_extensions is used in GNU/clang mode, it is possible for __asm to be followed by either the Microsoft or GNU syntax. In such cases, look ahead to see if this is a GNU/clang asm, and if so return FALSE and do no other processing. Return TRUE for a Microsoft __asm. */ { unsigned int nbrace = 0; char *body; a_boolean is_asm_block; a_boolean save_token = FALSE; a_source_position saved_pos_curr_token; a_boolean result = TRUE; if (gnu_mode) { /* In ms_extensions mode, a Microsoft __asm and a GNU __asm are both permitted. */ a_const_char *ch = curr_char_loc; while (*ch == ' ' || *ch == '\t') ch++; if (*ch == '(') { result = FALSE; goto done; } /* if */ } /* if */ /* Set a flag that indicates we are scanning a Microsoft asm. This prevents this routine from being called recursively. */ scanning_microsoft_asm = TRUE; fetch_pp_tokens = TRUE; /* Save the position of this token so that it can be restored later. */ saved_pos_curr_token = pos_curr_token; /* Advance past the __asm token. */ (void)get_token(); /* Initialize variables used for building the string. */ reset_asm_buffer(); /* Initialize global variables used by lexical routines. */ in_asm_function_body = TRUE; treat_newline_as_token = TRUE; is_asm_block = curr_token == tok_lbrace; if (is_asm_block) { /* Loop through the tokens and build the string token by token. */ while (curr_token != tok_end_of_source) { /* Special handling for a left brace embedded within the assembler code: assume it has a matching right brace. */ if (curr_token == tok_lbrace) ++nbrace; /* Copy characters from the source line to the buffer, from last_stop_char through the end of the current token. */ copy_from_source_to_asm_func_buffer(end_of_curr_token + 1, (char *)NULL); /* Stop when a zero-level right brace is reached. Keep track of braces. */ if (curr_token == tok_rbrace && --nbrace == 0) { /* This right brace matches the opening left brace, marking the end of the asm function body. */ break; } /* if */ /* Advance to the next token. */ (void)get_token(); /* If this is the start of an asm comment, process the rest of the line specially. */ if (curr_token == tok_semicolon) { skip_asm_comment(/*include_newline=*/TRUE); } /* if */ } /* while */ } else { /* Not an asm block. Just take tokens up to the end of the line or up to a closing brace. */ #if EXTRA_SOURCE_POSITIONS_IN_IL a_source_position end_pos; end_pos = end_pos_curr_token; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ while (curr_token != tok_end_of_source) { if (curr_token == tok_newline || curr_token == tok_rbrace) { save_token = curr_token == tok_rbrace; #if EXTRA_SOURCE_POSITIONS_IN_IL if (curr_token == tok_newline) { /* Restore the ending position to that of the token before the newline. */ end_pos_curr_token = end_pos; } /* if */ #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ break; } /* if */ #if EXTRA_SOURCE_POSITIONS_IN_IL end_pos = end_pos_curr_token; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ /* Copy characters from the source line to the buffer, from last_stop_char through the end of the current token. */ copy_from_source_to_asm_func_buffer(end_of_curr_token + 1, (char *)NULL); /* If this is the start of an asm comment, process the rest of the line specially. */ if (curr_token == tok_semicolon) { skip_asm_comment(/*include_newline=*/FALSE); } else { /* Advance to the next token. */ (void)get_token(); } /* if */ } /* while */ } /* if */ fetch_pp_tokens = FALSE; in_asm_function_body = FALSE; treat_newline_as_token = FALSE; if (save_token) { /* The current token is not part of the asm string. Cache it so that it can will be rescanned after the tok_microsoft_asm is fetched. */ a_tiny_scanning_token_cache cache; cache_curr_token(cache.ptr()); rescan_cached_tokens(cache.ptr()); } /* if */ /* Allocate a block of the current IL memory region (the one established for the asm function) -- the asm buffer will be copied into it, along with a trailing null character. */ body = alloc_asm_function_body(pos_in_asm_func_body_buffer + 1); (void)memcpy(body, asm_func_body_buffer, size_t_arg(pos_in_asm_func_body_buffer)); /* Add a null terminator. */ body[pos_in_asm_func_body_buffer] = '\0'; curr_token_asm_string = body; curr_token = tok_microsoft_asm; /* Restore the token start position of the __asm token. */ pos_curr_token = saved_pos_curr_token; /* Reset the token start pointer because we may have moved to another source line. */ start_of_curr_token = NULL; #if DEBUG if (debug_level >= 3 || db_flag_is_set("asm_string")) { fprintf(f_debug, "asm string: %s\n", body); } /* if */ #endif /* DEBUG */ scanning_microsoft_asm = FALSE; done: return result; } /* build_microsoft_asm_string */ static a_boolean scan_if_exists_identifier( a_boolean is_if_exists, a_boolean *is_dependent, ARG_UNUSED a_source_position *start_pos) /* Scan the identifier in a Microsoft __if_exists or __if_not_exists directive. "if_if_exists" is TRUE for __if_exists and FALSE for __if_not_exists. Return TRUE if the tokens should be scanned. This is not strictly the same as whether or not the identifier exists because of special handling of prototype instantiations. is_dependent is returned to indicate whether the identifier refers to a template-dependent entity. start_pos is the position of the __if_exists or __if_not_exists token. */ { a_boolean result = FALSE; #if GENERATE_MICROSOFT_IF_EXISTS_ENTRIES a_boolean is_this = FALSE; #endif /* GENERATE_MICROSOFT_IF_EXISTS_ENTRIES */ a_symbol_ptr sym = NULL; check_assertion(depth_scope_stack != NO_SCOPE_DEPTH); *is_dependent = FALSE; if (curr_token == tok_this) { /* "this" keyword. See if we are in a context where the "this" keyword can be used. */ #if GENERATE_MICROSOFT_IF_EXISTS_ENTRIES is_this = TRUE; #endif /* GENERATE_MICROSOFT_IF_EXISTS_ENTRIES */ if (is_template_dependent_context()) { /* Don't attempt to check for validity in dependent contexts. This allows the __if_exist to potentially be kept in the IL below. */ *is_dependent = TRUE; result = TRUE; } else { result = variable_this_exists((a_variable_ptr*)NULL, (a_type_ptr*)NULL); if (!is_if_exists) result = !result; } /* if */ /* Bypass the keyword. */ (void)get_token(); } else if (is_generalized_identifier_start(GID_IN_IF_EXISTS | GID_PERMIT_UNKNOWN_QUALIFIER)) { a_boolean err; sym = coalesce_and_lookup_generalized_identifier( GID_IN_IF_EXISTS | GID_PERMIT_UNKNOWN_QUALIFIER, ilm_normal, &err); /* Determine whether the symbol refers to a dependent entity. If the symbol is dependent, the tokens are always retained during the prototype instantiation. */ if (sym != NULL && is_template_dependent_context()) { if (is_type_symbol(sym)) { *is_dependent = is_template_param_type_symbol(sym); } else if (sym->kind == (a_symbol_kind)sk_constant) { *is_dependent = is_nontype_template_param_symbol(sym); } else if (sym->kind == (a_symbol_kind)sk_class_template) { *is_dependent = is_template_template_param_symbol(sym); } /* if */ } /* if */ if (*is_dependent) { /* The tokens are always retained in prototype instantiations. */ result = TRUE; } else { /* For non-dependent identifiers the tokens are only retained if the condition is TRUE. */ a_boolean exists = FALSE; if (sym == NULL || sym->is_error) { /* The symbol does not exist or is an error symbol (which is considered to not exist). */ } else if (is_template_class_and_not_specific_def_symbol(sym)) { /* For a non-specialized instance of a class template, the symbol is only considered to exist if the type has been partially or fully instantiated, but the reference in the __if_exists does not cause the instance to be partially instantiated. */ exists = TRUE; } else { /* All other symbols are considered to exist. */ exists = TRUE; } /* if */ result = is_if_exists == exists; } /* if */ /* Bypass the identifier. */ (void)get_token(); } else { pos_error(ec_exp_identifier, &error_position); *is_dependent = FALSE; } /* if */ #if GENERATE_MICROSOFT_IF_EXISTS_ENTRIES /* For a dependent identifier, create a source sequence entry to mark the start of the __if_exists. Note that *is_dependent will always be FALSE for the error case above. */ if (*is_dependent && generate_microsoft_if_exists_entries()) { an_ms_if_exists_ptr msiep; char *entity; an_il_entry_kind kind; msiep = alloc_ms_if_exists(); if (is_this) { msiep->is_this = TRUE; } else { entity = il_entry_for_symbol(sym, &kind); msiep->entity.ptr = entity; msiep->entity.kind = kind; if (record_name_references_in_context()) { msiep->name_reference = qualifiable_name_reference( &locator_for_curr_id, (a_source_correspondence*)entity); } /* if */ } /* if */ msiep->position = *start_pos; msiep->is_if_exists = is_if_exists; msiep->pending = TRUE; add_to_ms_if_exists_list(msiep, decl_scope_level); add_to_source_sequence_list((char *)msiep, (an_il_entry_kind)iek_ms_if_exists); } /* if */ #endif /* GENERATE_MICROSOFT_IF_EXISTS_ENTRIES */ return result; } /* scan_if_exists_identifier */ static a_scanning_token_cache cache_if_exists_tokens( ARG_UNUSED a_boolean is_dependent) /* Cache the tokens between the braces of an __if_exists or __if_not_exists directive; return the created cache. */ { a_scanning_token_cache cache; /* Initialize a local stop token set. */ a_token_set_array stop_tokens; clear_token_set_array(stop_tokens); incr_token_set_array_element(stop_tokens, tok_rbrace); cache_token_stream(cache.ptr(), stop_tokens); /* Add a special token to the end of the cache to mark the end of the tokens that come from the __if_exists. */ if (is_dependent) { a_token_kind saved_curr_token = curr_token; curr_token = tok_end_of_if_exists; cache_curr_token(cache.ptr()); curr_token = saved_curr_token; } /* if */ return cache; } /* cache_if_exists_tokens */ static void scan_microsoft_if_exists(a_token_kind ctoken) /* Scan a Microsoft __if_exists or __if_not_exists of the form: __if_exists ( name ) { tokens } "name" is a qualified or unqualified name. The name is looked up, and if found the associated "tokens" are either scanned or discarded (depending on which form of the directive is being used). When the tokens are to be scanned, a cache is created and the tokens are rescanned from the cache. */ { a_boolean keep_tokens; a_boolean is_dependent; a_source_position start_pos; a_pending_pragma_list saved_curr_token_pragmas; /* Clear the curr_token_pragmas list so that it can be restored after the tokens of the __if_exists directive have been scanned. */ saved_curr_token_pragmas = *curr_token_pragmas; curr_token_pragmas = new_fe(); start_pos = pos_curr_token; /* Bypass the directive token. */ (void)get_token(); /* Scan the "(". */ if (curr_token == tok_lparen) { (void)get_token(); } else { pos_error(ec_exp_lparen, &error_position); } /* if */ add_stop_token(tok_rparen); add_stop_token(tok_lbrace); /* Scan the identifier. */ keep_tokens = scan_if_exists_identifier(ctoken == tok_if_exists, &is_dependent, &start_pos); /* Scan the ")". */ (void)required_token(tok_rparen, ec_exp_rparen); remove_stop_token(tok_rparen); remove_stop_token(tok_lbrace); /* Scan the open brace. */ if (curr_token != tok_lbrace) { pos_error(ec_exp_lbrace, &error_position); } else { /* Bypass the open brace of the directive. */ (void)get_token(); } /* if */ /* Cache tokens up to the matching brace. */ a_scanning_token_cache cache = cache_if_exists_tokens(is_dependent); /* If keeping the tokens, rescan the from the cache; otherwise just discard the tokens. */ if (keep_tokens) { /* Rescan the tokens. Discard the current token if it is not tok_end_of_source. */ rescan_cached_tokens(cache.ptr(), curr_token != tok_end_of_source); } else { /* Bypass the closing brace. */ if (curr_token != tok_end_of_source) (void)get_token(); } /* if */ if (is_template_dependent_context() && is_dependent) { /* Record the number of dependent __if_exists (see the comments for the field in a_scope_stack_entry for more details). */ scope_stack[decl_scope_level].pending_dependent_if_exists++; } /* if */ /* Add the saved curr_token_pragmas list to the current list. It will usually be empty, but this is done just in case a new entry was added. */ add_to_curr_token_pragma_list(saved_curr_token_pragmas); #if GENERATE_MICROSOFT_IF_EXISTS_ENTRIES /* Create a pseudo-pragma that indicates that a source sequence entry was created for this __if_exists. This is used to detect cases where an __if_exists appears in an invalid context. */ if (is_dependent && generate_microsoft_if_exists_entries()) { (void)add_curr_token_pseudo_pragma((a_pragma_kind)pk_if_exists, &start_pos); } /* if */ #endif /* GENERATE_MICROSOFT_IF_EXISTS_ENTRIES */ } /* scan_microsoft_if_exists */ #if GENERATE_MICROSOFT_IF_EXISTS_ENTRIES void f_check_for_if_exists_pragmas(void) /* If there are any current token pragmas that are __if_exists pseudo-pragmas process them now. This routine is called in locations in which an __if_exists is permitted. The pragma entry is removed from the list, so that the pragma processing function (if_exists_pragma) will not be called. If a pragma appears in an invalid location, if_exists_pragma is called, resulting in a diagnostic. */ { auto is_if_exists_pragma = [](const a_shared_pending_pragma &spp) -> a_boolean { return spp->descr_ptr->kind == pk_if_exists; }; curr_token_pragmas->remove_if(is_if_exists_pragma); } /* f_check_for_if_exists_pragmas */ void if_exists_pragma(a_pending_pragma_ptr ppp) /* The routine called when a if_exists pseudo pragma is encountered on the current token pragmas list. Such pragmas are processed specially in locations where they are allowed. A diagnostic is issued for any entries that have not been handled. */ { pos_diagnostic(ppp->descr_ptr->error_severity, ec_if_exists_not_allowed, &ppp->pragma_position); } /* if_exists_pragma */ static an_ms_if_exists_ptr last_pending_if_exists(void) /* Return a pointer to the last unclosed __if_exists block, or NULL if there are no such blocks. */ { an_ms_if_exists_ptr msiep; an_ms_if_exists_ptr opening_msiep = NULL; a_scope_stack_entry_ptr ssep; /* Find the __if_exists entry that this brace closes. Note that this finds the last pending entry on the list. */ ssep = &scope_stack[decl_scope_level]; for (msiep = ssep->il_scope != NULL ? ssep->il_scope->ms_if_exists : NULL; msiep != NULL; msiep = msiep->next) { if (msiep->pending) opening_msiep = msiep; } /* for */ return opening_msiep; } /* last_pending_if_exists */ void check_for_unclosed_if_exists_blocks(void) /* Make sure the current scope does not contain any unclosed __if_exists blocks. Issue an error if any are found. */ { an_ms_if_exists_ptr msiep; msiep = last_pending_if_exists(); if (msiep != NULL) { pos_error(ec_if_exists_not_closed, &msiep->position); } /* if */ } /* check_for_unclosed_if_exists_blocks */ #endif /* GENERATE_MICROSOFT_IF_EXISTS_ENTRIES */ static void process_end_of_if_exists(void) /* This routine is called by the lexical routines when a special "end of __if_exists" token is encountered. When GENERATE_MICROSOFT_IF_EXISTS_ENTRIES is TRUE, generate a source sequence entry to mark the end of the __if_exists. In any case, see if pending_if_exists needs to be decremented. */ { #if GENERATE_MICROSOFT_IF_EXISTS_ENTRIES an_ms_if_exists_ptr msiep; an_ms_if_exists_ptr opening_msiep; /* Reset the pending flag of the last pending __if_exists block. */ opening_msiep = last_pending_if_exists(); if (opening_msiep == NULL) { /* No open entry was found. An error will have been issued at the end of the scope containing the unclosed block. */ } else { opening_msiep->pending = FALSE; } /* if */ msiep = alloc_ms_if_exists(); msiep->position = pos_curr_token; add_to_ms_if_exists_list(msiep, decl_scope_level); add_to_source_sequence_list((char *)msiep, (an_il_entry_kind)iek_ms_if_exists); /* Create a pseudo-pragma that indicates that a source sequence entry was created for this __if_exists. */ (void)add_curr_token_pseudo_pragma((a_pragma_kind)pk_if_exists, &pos_curr_token); #endif /* GENERATE_MICROSOFT_IF_EXISTS_ENTRIES */ if (is_template_dependent_context()) { /* Decrement the number of pending __if_exists entries. Don't do so if that would result in the number being negative. This can happen because __if_exists constructs are not required to nest properly relative to scopes. */ if (scope_stack[decl_scope_level].pending_dependent_if_exists > 0) { scope_stack[decl_scope_level].pending_dependent_if_exists--; } /* if */ } /* if */ } /* process_end_of_if_exists */ static a_constant_ptr get_constant_for_ms_string_operand(void) /* The current token is a tok_string_literal. If it is a valid narrow string literal constant, return the constant. Otherwise, return NULL. Issue an error for a wide string literal. This is used for certain Microsoft features such as __identifier. */ { a_constant_ptr cp; cp = &const_for_curr_token; if (is_error_constant(cp)) { /* The string literal was ill-formed. An error will already have been issued. */ cp = NULL; } else if (!is_normal_character_kind(cp->character_kind)) { pos_error(ec_wide_string_not_allowed, &error_position); cp = NULL; } /* if */ return cp; } /* get_constant_for_ms_string_operand */ static void scan_microsoft_identifier_operator(void) /* Scan a Microsoft __identifier operator. The syntax of such an operator is __identifier(keyword-token) The keyword-token is normally a C++ keyword, but can also be a normal identifier. The result of scanning this operator is an identifier token where the identifier name is the character sequence of the keyword-token. If the operator does not contain a valid identifier, the current token is set to tok_error. */ { a_symbol_locator locator; a_boolean err = FALSE; /* Push a new lexical state so that the tokens scanned by this routine will not be cached by the background caching mechanism. */ push_lexical_state_stack(); /* Bypass the operator token. */ (void)get_token(); /* Scan the "(". */ if (curr_token == tok_lparen) { /* Get the keyword-token. */ suppress_keyword_recognition = TRUE; (void)get_token(); suppress_keyword_recognition = FALSE; } else { pos_error(ec_exp_lparen, &error_position); } /* if */ add_stop_token(tok_rparen); add_stop_token(tok_semicolon); if (curr_token == tok_identifier) { /* Save the symbol locator so that it can be restored after the closing parenthesis is scanned. */ locator = locator_for_curr_id; /* Bypass the identifier. */ (void)get_token(); } else if (curr_token == tok_string_literal) { /* The Microsoft compiler gives an error on a string literal, but allows the diagnostic to be suppressed (using a pragma) and the contents of the string literal are used as the identifier. We issue a discretionary error (which will automatically be suppressed in files from system include directories). */ a_constant_ptr cp; if (processing_vccorlib_header) { /* Do not emit this diagnostic while processing vccorlib.h. */ } else if (cli_or_cx_enabled && scanning_generated_code_from_metadata) { /* Suppress this error for generated code from metadata. We use __identifier for template specializations imported from metadata. For example, ref class __identifier("Foo"). */ } else { diagnostic(es_discretionary_error, ec_exp_cpp_keyword); /*lint !e725*/ } /* if */ clear_locator(&locator, &pos_curr_token); cp = get_constant_for_ms_string_operand(); if (cp != NULL) { if (processing_vccorlib_header && strcmp(cp->variant.string.value, "") == 0) { /* The metadata reader generates __identifier(""). Map it back to "Dispose" while processing vccorlib.h. */ (void)find_symbol_header("Dispose", sizeof("Dispose") - 1, &locator); } else { (void)find_symbol_header(cp->variant.string.value, (sizeof_t)cp->variant.string.length - 1, &locator); } /* if */ } else { err = TRUE; } /* if */ /* Bypass the string literal. */ (void)get_token(); } else { pos_error(ec_exp_cpp_keyword, &error_position); err = TRUE; } /* if */ /* Scan the ")". */ (void)required_token_no_advance(tok_rparen, ec_exp_rparen); remove_stop_token(tok_semicolon); remove_stop_token(tok_rparen); /* If a valid identifier was found, return that as the current token; otherwise return tok_error. */ if (err) { curr_token = tok_error; } else { locator_for_curr_id = locator; curr_token = tok_identifier; check_assertion(locator_for_curr_id.symbol_header != NULL); locator_for_curr_id.symbol_header->microsoft_identifier_used = TRUE; } /* if */ /* Pop the lexical state pushed by this routine. */ pop_lexical_state_stack(); } /* scan_microsoft_identifier_operator */ #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE void setlocale_pragma(a_pending_pragma_ptr ppp) /* This routine is called when a Microsoft setlocale pragma is encountered. The form of the pragma is: #pragma setlocale("locale name") The setlocale pragma specifies the encoding that is used for multibyte characters in non-Unicode source files. Multibyte characters are translated from the specified locale into UTF-8 in identifiers and file names, and to Unicode in wide string literals. Multibyte characters in narrow string literals are left unchanged. */ { a_boolean err = TRUE; a_constant_ptr locale_cp = NULL; a_source_position locale_pos; begin_rescan_of_pragma_tokens(ppp); if (required_token(tok_lparen, ec_exp_lparen)) { if (required_token_no_advance(tok_string_literal, ec_exp_string_literal)) { locale_pos = pos_curr_token; locale_cp = get_constant_for_ms_string_operand(); /* Advance past the string literal. */ (void)get_token(); if (required_token(tok_rparen, ec_exp_rparen)) err = FALSE; } /* if */ } /* if */ wrapup_rescan_of_pragma_tokens(err); /* If we did not encounter an error and the constant was acceptable, set the locale to the specified value. */ if (!err && locale_cp != NULL) { if (set_windows_locale(locale_cp->variant.string.value)) { pos_st_error(ec_invalid_locale, &locale_pos, locale_cp->variant.string.value); } /* if */ } /* if */ } /* setlocale_pragma */ #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ static void adjust_pp_int_constant(void) /* The current token is an integer constant scanned within a preprocessing #if expression. It should be made long if it is not already so (or intmax_t/uintmax_t in C99, C++11, and later modes). See C89 Standard, 3.8.1; C99 Standard, 6.10.1; and C++11 Standard, 16.1. */ { an_integer_kind ik; ik = const_for_curr_token.type->variant.integer.int_kind; if (c99_mode || cpp11_mode) { /* In C99 and C++11 modes, use intmax_t for signed types, uintmax_t for unsigned types. */ if (ik == targ_intmax_kind || ik == targ_uintmax_kind) { /* The type is already right, so leave it alone. */ } else { if (!int_kind_is_signed[(int)ik]) { ik = targ_uintmax_kind; } else { ik = targ_intmax_kind; } /* if */ const_for_curr_token.type = integer_type(ik); } /* if */ } else { /* C++03 or C89. Use long or unsigned long. */ if (ik == (an_integer_kind)ik_long || ik == (an_integer_kind)ik_unsigned_long) { /* The type is long, so leave it alone. */ #if LONG_LONG_ALLOWED } else if (ik == (an_integer_kind)ik_long_long || ik == (an_integer_kind)ik_unsigned_long_long) { /* The type is long long, so leave it alone. */ #endif /* LONG_LONG_ALLOWED */ } else { /* The type is smaller than long, so change it. It's changed to unsigned long if the current type is unsigned, otherwise to long. */ if (!int_kind_is_signed[(int)ik]) { ik = (an_integer_kind)ik_unsigned_long; } else { ik = (an_integer_kind)ik_long; } /* if */ const_for_curr_token.type = integer_type(ik); } /* if */ } /* if */ } /* adjust_pp_int_constant */ a_string_or_char_literal_kind scan_encoding_prefix(a_const_char *loc) /* loc points to a character that could be the start of a C++11 or C11 encoding-prefix, i.e., one of 'u', 'U', 'L', or (for C++11 only) 'R'. If it is the start of a valid string literal or the start of a valid character literal, return the literal kind. Otherwise, return SCLK_NOT_A_LITERAL. */ { a_string_or_char_literal_kind kind = SCLK_ORDINARY_LITERAL; /* Set the kind based on the initial character of the putative encoding-prefix and advance p to the next character. */ if (uliterals_enabled && *loc == 'u') { ++loc; if (*loc == '8') { ++loc; kind = SCLK_UTF8_LITERAL; } else { kind = SCLK_CHAR16_T_LITERAL; } /* if */ } else if (uliterals_enabled && *loc == 'U') { ++loc; kind = SCLK_CHAR32_T_LITERAL; } else if (*loc == 'L') { ++loc; kind = SCLK_WIDE_LITERAL; } /* if */ /* Now look for an R, either as the initial character or following an encoding-prefix, indicating that the literal is a C++11 raw string. */ if (raw_string_literals_enabled && *loc == 'R') { ++loc; kind |= SCLK_RAW_STRING_LITERAL; } /* if */ /* Finally, determine if this is a valid literal start. */ if (*loc == '"') { /* A valid string literal. */ kind |= SCLK_STRING_LITERAL; } else if (*loc == '\'' && (kind & SCLK_RAW_STRING_LITERAL) == 0 && (kind != SCLK_UTF8_LITERAL || utf8_char_literals_enabled)) { /* A valid character literal. */ } else { /* Not a valid literal. */ kind = SCLK_NOT_A_LITERAL; } /* if */ return kind; } /* scan_encoding_prefix */ static a_boolean id_is_macro_name(a_const_char *id_ptr, sizeof_t id_len) /* Return TRUE if the string defined by id_ptr and id_len is the name of a macro, FALSE otherwise. */ { a_symbol_locator locator; a_symbol_header_ptr sym_hdr; a_symbol_ptr sym = NULL; sym_hdr = find_symbol_header(id_ptr, id_len, &locator); if (sym_hdr != NULL) { /* Scan the list of symbols with the given spelling, looking for a macro name. */ for (sym = symbol_list_for_file_scope_symbols(sym_hdr); sym != NULL && sym->kind != (a_symbol_kind)sk_macro; sym = sym->next) {} } /* if */ return (sym != NULL); } /* id_is_macro_name */ a_token_kind concat_adjacent_string_literals(a_boolean function_name_case) /* The current token (not in curr_token yet, but in const_for_curr_token) is a string literal (tok_string_literal), and in the current lexical mode normal (not pp) tokens should be fetched, and concatenation of adjacent string literals should be done. Look to see if the next token of input is a string literal, and if so, concatenate it with the current token. Loop to pick up all the adjacent string literals. If function_name_case is TRUE, this is a special call to handle concatenation of strings and function-name keywords like __FUNCTION__; curr_token is already set in that case. Encoding prefixes and user-defined literal suffixes are merged, as appropriate. If one or more of the literals have the same valid user-defined literal suffix, set ud_lit_op_sym_for_curr_token and return tok_ud_literal; otherwise, return tok_string_literal. */ { a_character_kind character_kind; a_string_or_char_literal_kind lit_kind = SCLK_ORDINARY_STRING_LITERAL; a_string_or_char_literal_kind encoding = SCLK_ORDINARY_LITERAL; a_boolean raw_string_seen = FALSE; a_scanning_token_cache cache; a_boolean suffix_mismatch = FALSE; a_token_kind ctoken = tok_string_literal; db_enter(5, "concat_adjacent_string_literals"); /* Start a new lexical state so that only the composite string literal token will be considered part of the current lexical state. */ push_lexical_state_stack(); check_assertion_str(!fetch_pp_tokens && do_string_literal_concatenation, "concat_adjacent_string_literals: bad mode"); /* Start with the character kind of the first literal. If this is a normal char string, the kind of the result may still change if a subsequent literal has a different character kind. */ character_kind = enum_cast( const_for_curr_token.character_kind); if (start_of_curr_token != NULL) { lit_kind = scan_encoding_prefix(start_of_curr_token); if (lit_kind == SCLK_NOT_A_LITERAL) { /* This must be one of the function name identifiers. Treat it as an ordinary character string literal. */ check_assertion(function_name_case); lit_kind = SCLK_ORDINARY_STRING_LITERAL; } /* if */ } else if (constant_is(&const_for_curr_token, ck_string)) { lit_kind = const_for_curr_token.variant.string.literal_kind; } else { check_assertion(function_name_case && constant_is(&const_for_curr_token, ck_template_param)); lit_kind = SCLK_ORDINARY_STRING_LITERAL; } /* if */ encoding = literal_encoding_prefix(lit_kind); raw_string_seen = (lit_kind & SCLK_RAW_STRING_LITERAL) != 0; /* Start a token cache in which we will accumulate all the adjacent string literals. Usually, this will be just a single string literal. */ if (!function_name_case) { /* Set up the first string literal as the current token, so it can be cached. This routine is called from get_token at a point where there is, in effect, no current token, so we're just anticipating the action that would be done on return from get_token here. const_for_curr_token is already set; start_of_curr_token and end_of_curr_token are already set; len_of_curr_token does not need to be set. */ curr_token = tok_string_literal; #if EXTRA_SOURCE_POSITIONS_IN_IL check_assertion(end_of_curr_token != NULL); /* Determine the source position of the end of the string. */ macro_line_loc_to_source_pos(end_of_curr_token, end_pos_curr_token); #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ } /* if */ /* Clear a text buffer to remember any user-defined literal suffixes so they can be processed after the tokens are concatenated. */ if (ud_lit_suffix_buffer == NULL) { ud_lit_suffix_buffer = alloc_text_buffer(128); } /* if */ reset_text_buffer(ud_lit_suffix_buffer); /* Loop as long as the next token is a string literal. */ for (;;) { a_string_or_char_literal_kind next_encoding = SCLK_ORDINARY_LITERAL; a_boolean saw_macro_instead_of_udl_suffix = FALSE; if (user_defined_literals_enabled && !function_name_case) { /* Check for a ud-suffix. (The function name case cannot have a ud-suffix because the token in the source was something like __FUNCTION__, so a suffix would have been scanned as part of the token.) */ a_const_char *id_start = curr_char_loc; if (check_for_ud_suffix()) { /* Found a ud-suffix. */ sizeof_t suffix_len = (sizeof_t)(curr_char_loc - id_start); a_const_char *canonical_id = make_canonical_identifier(id_start, &suffix_len, /* force_ucn=*/FALSE); if (macro_preempts_udl_suffix && id_is_macro_name(canonical_id, suffix_len)) { /* In some programming styles the C99 format macros are placed adjacent to the preceding string with no intervening white space, e.g., "%"PRId64"\n". If so requested, do not treat such a macro as a literal suffix but as a separate token. */ curr_char_loc = id_start; saw_macro_instead_of_udl_suffix = TRUE; } else if (ud_lit_suffix_buffer->size == 0) { /* This is the first one -- copy the identifier. */ add_to_text_buffer(ud_lit_suffix_buffer, canonical_id, suffix_len); add_char_to_text_buffer(ud_lit_suffix_buffer, '\0'); end_of_curr_token = curr_char_loc - 1; } else if (!suffix_mismatch) { /* A suffix has already been seen. Make sure that this one matches the earlier one. */ if (suffix_len == ud_lit_suffix_buffer->size - 1 && memcmp(canonical_id, ud_lit_suffix_buffer->buffer, suffix_len) == 0) { end_of_curr_token = curr_char_loc - 1; } else { conv_line_loc_to_source_pos(id_start, &error_position); str_error(ec_ud_string_suffix_mismatch, ud_lit_suffix_buffer->buffer); suffix_mismatch = TRUE; } /* if */ } /* if */ #if EXTRA_SOURCE_POSITIONS_IN_IL if (!suffix_mismatch) { /* Update the token end position. */ macro_line_loc_to_source_pos(end_of_curr_token, end_pos_curr_token); } /* if */ #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ } /* if */ } /* if */ /* Save the current token (a string literal) by adding it to the token cache. */ if (constant_is(&const_for_curr_token, ck_string)) { const_for_curr_token.variant.string.literal_kind = lit_kind; } /* if */ cache_curr_token(cache.ptr()); /* Clear the current token's pragmas so that they don't get processed by the get_token call below. */ curr_token_pragmas->clear(); /* Scan the next token. Suppress string literal concatenation so that when scanning something like "aaa" "bbb" "ccc" the get_token call at "bbb" does not fetch "bbbccc" (which would cause undesirable behavior in terms of memory use). */ do_string_literal_concatenation = FALSE; (void)get_token(); do_string_literal_concatenation = TRUE; if (function_name_case) { if (token_is_function_name_string_literal(curr_token)) { /* In some modes, function-name keywords like __FUNCTION__ are treated as string literals. The case where these appear as the first literal is handled in expression processing; cases where they appear after a string are handled by calling here. */ set_curr_token_to_function_name_string(/*do_concat=*/FALSE, SCLK_ORDINARY_STRING_LITERAL); #if MICROSOFT_EXTENSIONS_ALLOWED } else if (is_microsoft_string_prefix_operator(curr_token)) { /* Similar handling for the Microsoft __xPREFIX operators. */ (void)set_curr_token_to_microsoft_xprefix_operator_string(); #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* if */ } /* if */ /* End the loop if the new token is not a string literal. */ if (curr_token != tok_string_literal) { if (curr_token == tok_identifier && saw_macro_instead_of_udl_suffix) { warning_at_line_pos(ec_macro_not_udl_suffix, start_of_curr_token); } /* if */ break; } /* if */ if (start_of_curr_token != NULL) { lit_kind = scan_encoding_prefix(start_of_curr_token); if (lit_kind == SCLK_NOT_A_LITERAL) { /* This must be one of the function name identifiers. Treat it as an ordinary character string literal. */ check_assertion(function_name_case); lit_kind = SCLK_ORDINARY_STRING_LITERAL; } /* if */ next_encoding = literal_encoding_prefix(lit_kind); if (lit_kind & SCLK_RAW_STRING_LITERAL) { raw_string_seen = TRUE; } /* if */ } /* if */ if (character_kind != const_for_curr_token.character_kind && !is_error_constant(&const_for_curr_token)) { /* The new string and the old one have different character kinds. In some modes (C99, C++11, and GNU), this may be okay if one of the two kinds is "char" (the concatenation results in the other kind); however, a UTF-8 literal cannot be concatenated with a wide literal. (The check below for SCLK_UTF8_LITERAL is redundant in C++20, since UTF-8 string literals have the distinct character kind chk_char8_t; in earlier dialects, however, the character kind is chk_char and the prefix must be checked explicitly.) In other modes, it is a discretionary error. If two different non-char character types are mixed (e.g., U"A" L"B") a non-discretionary error is issued in all modes. */ an_error_severity sev; if (mixed_string_concat_enabled) { if (character_kind == chk_char && encoding != SCLK_UTF8_LITERAL) { sev = es_none; character_kind = enum_cast( const_for_curr_token.character_kind); } else if (const_for_curr_token.character_kind == chk_char && next_encoding != SCLK_UTF8_LITERAL) { sev = es_none; } else { sev = es_error; } /* if */ } else { if (character_kind != chk_char && const_for_curr_token.character_kind != chk_char) { sev = es_error; } else { sev = es_discretionary_error; } /* if */ if (character_kind == chk_char) { character_kind = enum_cast( const_for_curr_token.character_kind); } /* if */ } /* if */ if (sev != es_none) { diagnostic(sev, ec_mixed_string_concatenation); } /* if */ } /* if */ /* This string literal is okay, and will be added to the concatenation in the token cache. */ if (next_encoding == SCLK_UTF8_LITERAL) { /* Make sure we remember that we're treating the result of this concatenation as a UTF-8 literal. */ encoding = SCLK_UTF8_LITERAL; } /* if */ } /* for */ /* Here, all the adjacent string literals have been captured in a token cache. Concatenate them into a single string literal. */ if (cache->length() > 1) { concat_string_literals(cache.ptr(), character_kind); } /* if */ /* Stick the remaining single string literal back onto the input token stream (ahead of the non-string-literal token that stopped the loop). */ rescan_cached_tokens(cache.ptr()); /* Pop the lexical state pushed by this routine. */ pop_lexical_state_stack(); if (!is_error_constant(&const_for_curr_token)) { if (constant_is(&const_for_curr_token, ck_string)) { const_for_curr_token.variant.string.literal_kind = encoding | SCLK_STRING_LITERAL; } /* if */ if (raw_string_seen && constant_is(&const_for_curr_token, ck_string)) { /* Although whether any of the literals was raw does not affect the final value of the constant, we need to recall whether a raw string was seen in order to exclude something like int operator R"x()x" _foo(const char*); which would otherwise be accepted as a valid literal operator declaration. */ const_for_curr_token.variant.string.literal_kind |= SCLK_RAW_STRING_LITERAL; } /* if */ if (user_defined_literals_enabled && ud_lit_suffix_buffer->size != 0 && !suffix_mismatch) { /* Process the suffix. */ /* The value of curr_token is set to indicate to cache_curr_token (if invoked by lookups triggered by find_literal_operator) that the current token state is a partially resolved user-defined literal. */ curr_token = tok_unresolved_ud_literal; ud_lit_op_sym_for_curr_token = find_literal_operator(ud_lit_suffix_buffer->buffer, ud_lit_suffix_buffer->size - 1, &pos_curr_token, const_for_curr_token.type, /*from_cache=*/FALSE, (a_diagnostic_ptr)NULL); ud_lit_type_for_curr_token = const_for_curr_token.type; ctoken = tok_ud_literal; } /* if */ } /* if */ db_exit(); return ctoken; } /* concat_adjacent_string_literals */ static void check_for_invalid_macro_concatenation(a_boolean end_token_is_valid) /* Issue a diagnostic if the current token indicates that a concatenation operation in a macro expansion ("a ## b") did not result in a single valid token. If end_token_is_valid is TRUE, end_of_curr_token has been set and can be used in the test; otherwise, only start_of_curr_token is valid. */ { a_source_line_modif_ptr slmp = assoc_source_line_modif_full(start_of_curr_token, /*failure_allowed=*/TRUE); a_const_char *end_range; end_range = end_token_is_valid ? end_of_curr_token : start_of_curr_token; if (slmp != NULL) { while (slmp->concatenations != NULL && slmp->concatenations->line_loc <= end_range) { /* There are two cases in which a concatenation can fail to result in a single valid token. The first is when the right operand of the ## begins a new token in its own right; we detect that when the current token starts at the location recorded for a concatenation. The second is when the concatenated token ends before the first token of the right operand did. We can detect this case (when end_token_is_valid is TRUE) because the tokens in the replacement text are terminated by lexical escapes, typically LE_END_OF_TOKEN; if this token is the result of a concatenation but the next character following it is not LE_ESCAPE, the combined token does not encompass the entire first token of the right operand. In this case, the current token might be the result of several concatenations (i.e., several concatenation records might point within the current token), so we only report a diagnostic for the last one; we also suppress the last one if the next concatenation record points to the character following the end of the current token, since the next token will satisfy the first case and result in its own diagnostic. */ if (slmp->concatenations->line_loc == start_of_curr_token || (end_token_is_valid && (slmp->concatenations->next == NULL || (slmp->concatenations->next->line_loc > end_of_curr_token && slmp->concatenations->next->line_loc != end_of_curr_token + 1)) && *(end_of_curr_token + 1) != LE_ESCAPE)) { /* Issue a diagnostic of the appropriate severity. */ pos_stsy_diagnostic(strict_ansi_mode ? strict_ansi_discretionary_severity : es_warning, ec_concat_yields_invalid_token, &pos_curr_token, slmp->concatenations->line_loc, slmp->concatenations->macro_sym); } /* if */ free_concatenation_record(&slmp->concatenations); } /* while */ } /* if */ } /* check_for_invalid_macro_concatenation */ #if MICROSOFT_EXTENSIONS_ALLOWED static a_const_char *check_GUID_hex_digits(a_const_char *str, int ndigits, a_boolean *err) /* As part of checking a GUID string, check for ndigits hexadecimal digits beginning at str. Set *err to TRUE if the digits do not appear. Return str, advanced past the digits that do appear. */ { for (; ndigits > 0; ndigits--, str++) { if (!isxdigit((unsigned char)*str)) { *err = TRUE; break; } /* if */ } /* for */ return str; } /* check_GUID_hex_digits */ static a_const_char *check_GUID_hyphen(a_const_char *str, a_boolean *err) /* As part of checking a GUID string, check that *str is a hyphen character. If not, set *err to TRUE. Return str, advanced past the hyphen if one is present. */ { if (*str != '-') { *err = TRUE; } else { str++; } /* if */ return str; } /* check_GUID_hyphen */ a_boolean is_valid_GUID_string(a_const_char *str, a_targ_size_t length) /* Check the indicated string to see if it is a valid Microsoft GUID string. Such a string must have the form hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh where "h" is a hex digit. length is the length of the string (it is not necessarily null-terminated), or 0 if the length is not known. */ { a_boolean err = FALSE; if (length != 0 && length != 36) { /* A valid GUID string must be 36 characters. */ err = TRUE; } else { str = check_GUID_hex_digits(str, 8, &err); if (!err) str = check_GUID_hyphen(str, &err); if (!err) str = check_GUID_hex_digits(str, 4, &err); if (!err) str = check_GUID_hyphen(str, &err); if (!err) str = check_GUID_hex_digits(str, 4, &err); if (!err) str = check_GUID_hyphen(str, &err); if (!err) str = check_GUID_hex_digits(str, 4, &err); if (!err) str = check_GUID_hyphen(str, &err); if (!err) str = check_GUID_hex_digits(str, 12, &err); } /* if */ return !err; } /* is_valid_GUID_string */ static a_boolean is_uuid_token(void) /* The Microsoft compiler allows an unquoted UUID string to be used in a Microsoft attribute. curr_char_loc points to a character that could be the initial character of a UUID. Return TRUE if the characters make up a valid UUID. */ { a_const_char *ptr = curr_char_loc; a_boolean begins_with_brace; a_boolean valid; /* Pass is_valid_GUID_string the string following an optional brace. */ begins_with_brace = *ptr == '{'; if (begins_with_brace) ptr++; valid = is_valid_GUID_string(ptr, (a_targ_size_t)0); /* If the string began with a brace, make sure it ends with one. */ if (valid && begins_with_brace) valid = ptr[36] == '}'; return valid; } /* is_uuid_token */ static a_token_kind scan_unquoted_uuid(void) /* The Microsoft compiler allows an unquoted UUID string to be used in attributes. For example: [uuid(366AD604-AE26-4F01-A24D-B2E557BB3165)] class A {}; This routine is called after it has been determined that the following characters have been determined to be valid UUID string (possibly enclosed in braces). A string literal constant is created in const_for_curr_token from the characters of the UUID, and a tok_uuid token is returned. If the UUID is followed by additional hexadecimal characters (i.e., characters that look like they should be part of the UUID, those characters are discarded */ { a_const_char *ptr; a_const_char *end_ptr; a_boolean valid = TRUE; a_token_kind result_token; a_targ_size_t uuid_length = 36; ptr = curr_char_loc; /* Include the opening and closing braces in the length, if present. */ if (*ptr == '{') uuid_length += 2; end_ptr = curr_char_loc = ptr + uuid_length; if (*ptr != '{') { /* For a string not surrounded by brackets, if there are hex digits immediately following the UUID (i.e., that look like they were intended to be part of the UUID) discard them and return a tok_error. */ while (isxdigit((unsigned char)*curr_char_loc)) curr_char_loc++; if (end_ptr != curr_char_loc) valid = FALSE; } /* if */ end_of_curr_token = end_ptr - 1; if (!valid) { result_token = tok_error; } else { /* Construct a string literal for the UUID string. */ char *str; result_token = tok_uuid; /* Allocate space for the UUID string (plus a null terminator) and copy the string there. */ str = alloc_text_of_string_literal((sizeof_t)(uuid_length + 1)); (void)memcpy(str, ptr, (sizeof_t)uuid_length); /* Add a null terminator. */ str[uuid_length] = '\0'; clear_constant(&const_for_curr_token, (a_constant_repr_kind)ck_string); const_for_curr_token.type = string_literal_type((a_character_kind)chk_char, uuid_length + 1); const_for_curr_token.variant.string.length = uuid_length + 1; const_for_curr_token.variant.string.value = str; const_for_curr_token.character_kind = (a_character_kind)chk_char; } /* if */ return result_token; } /* scan_unquoted_uuid */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Macro wrapper for check_for_invalid_macro_concatenation that avoids the function call if no check is needed, i.e., calls only if concatenation checking is in effect and the current token is in a source line modification and thus might have been the result of concatenation. */ #define check_for_invalid_macro_concatenation_if_needed(end_token_is_valid) \ { if (check_concatenations && \ !within_curr_source_line(start_of_curr_token)) { \ check_for_invalid_macro_concatenation((end_token_is_valid)); \ } /* if */ \ } /* Macro that determines whether string literal sequence numbers might be needed, and if so, calls a routine to do the assignment. */ #if DO_IL_LOWERING && ASSIGN_STRING_LITERAL_SEQUENCE_NUMBERS #define assign_string_literal_sequence_number() \ if (depth_innermost_function_scope != NO_SCOPE_DEPTH && \ !fetch_pp_tokens && \ scope_stack[depth_innermost_function_scope]. \ assign_string_literal_sequence_numbers) { \ f_assign_string_literal_sequence_number(); \ } /* if */ #else /* !(DO_IL_LOWERING && ASSIGN_STRING_LITERAL_SEQUENCE_NUMBERS) */ #define assign_string_literal_sequence_number() /* nothing */ #endif /* DO_IL_LOWERING && ASSIGN_STRING_LITERAL_SEQUENCE_NUMBERS */ /* Remember that a token has been gotten from the current source line. Remember the start position (sequence number, column) of the token, and also put that into error_position. */ #define remember_token_start() \ { any_tokens_gotten_from_curr_source_line = TRUE; \ macro_line_loc_to_source_pos(start_of_curr_token, pos_curr_token); \ error_position = pos_curr_token; \ } /* remember_token_start */ /* Set start_of_curr_token to the current character location and (when multibyte characters are enabled) save the value of logical_char_info_entries_used to make the byte to logical column translation faster. */ #define record_start_of_curr_token() \ { \ start_of_curr_token = curr_char_loc; \ set_cached_logical_char_info_entries_used(logical_char_info_entries_used); \ } /* record_start_of_curr_token */ static INLINE a_boolean fetching_tokens_from_insert_string() /* Return TRUE if we are getting tokens from an inserted token string. */ { a_boolean result = TRUE; if (cached_token_rescan_stack->is_empty()) { result = FALSE; } else if (!cached_token_rescan_stack->back_elem()->is_insert_string()) { result = FALSE; } /* if */ return result; } /* fetching_tokens_from_insert_string */ /* Macro that is TRUE if digraph tokens should be recognized. */ #define digraphs_allowed() (alternative_tokens_allowed) #if MICROSOFT_EXTENSIONS_ALLOWED void init_whitespace_keywords(void) /* Populate the table of canonical spellings for whitespace keywords. */ { sizeof_t spelling_buffer_size = 0; char *ptr; int tok; /* Allocate the table of whitespace keyword spellings. */ whitespace_keywords = (a_whitespace_keyword_ptr)alloc_fe( sizeof(a_whitespace_keyword) * ((int)tok_last_whitespace_token - (int)tok_first_whitespace_token + 1)); /* Determine out how large a buffer is needed. */ for (tok = (int)tok_first_whitespace_token; tok <= (int)tok_last_whitespace_token; ++tok) { spelling_buffer_size += strlen(token_names[tok]) + LE_ESCAPE_LEN; } /* for */ /* Allocate the buffer */ ptr = alloc_fe(spelling_buffer_size); /* Copy the spellings and populate the whitespace_keyword array. */ for (tok = (int)tok_first_whitespace_token; tok <= (int)tok_last_whitespace_token; ++tok) { sizeof_t len = strlen(token_names[tok]); (void)memcpy(ptr, token_names[tok], size_t_arg(len)); whitespace_keywords[tok - (int)tok_first_whitespace_token].text = ptr; ptr += len; whitespace_keywords[tok - (int)tok_first_whitespace_token]. end_of_insertion = ptr; *ptr++ = LE_ESCAPE; *ptr++ = LE_END_OF_INSERTION; } /* for */ } /* init_whitespace_keywords */ /* Some constants for the "class", "struct" and "each" keyword lengths. */ #define len_of_class (sizeof("class")-1) #define len_of_struct (sizeof("struct")-1) #define len_of_each (sizeof("each")-1) #define len_of_ref (sizeof("ref")-1) #define len_of_new (sizeof("new")-1) static a_token_kind scan_whitespace_keyword(a_token_kind first_word, a_const_char **end_of_token) /* Check to see if first_word (corresponding to the token beginning at start_of_curr_token) together with the next token on the current line form a whitespace token. If so, add a source line modification replacing the actual spelling with the canonical spelling and return the corresponding token kind; otherwise, return the appropriate token kind considering the current token as a standalone token (i.e., tok_for, tok_enum, or tok_identifier). If the second token is on a different line from the first and does not complete a whitespace keyword, a line-start source line modification will be added to restore the first token to the current line. */ { a_token_kind return_token = first_word; a_token_kind next_word = tok_identifier; a_const_char *end_of_word; sizeof_t next_word_len; a_source_position start_pos = pos_curr_token; a_seq_number start_seq_number = curr_seq_number; a_const_char *orig_loc = start_of_curr_token; a_const_char *saved_curr_char_loc = curr_char_loc; a_boolean saved_do_not_advance_past_end_of_file = FALSE; a_whitespace_keyword_ptr kwd; a_source_line_modif_ptr slmp; /* Skip over white space, but not past the end of the current file. */ if (curr_ise != NULL) { saved_do_not_advance_past_end_of_file = curr_ise->do_not_advance_past_end_of_file; curr_ise->do_not_advance_past_end_of_file = TRUE; } /* if */ delete_source_from_loc = orig_loc; scanning_for_whitespace_keyword = TRUE; skip_white_space(); scanning_for_whitespace_keyword = FALSE; if (curr_ise != NULL) { curr_ise->do_not_advance_past_end_of_file = saved_do_not_advance_past_end_of_file; } /* if */ if (curr_seq_number != start_seq_number) { /* We moved to a new source line looking for the second word, so the original pointer to the start of the first word is now invalid; use the start of the current token instead. We also need to delete the leading white space, if any, on the new line. */ orig_loc = curr_char_loc; add_deletion_source_line_modif(delete_source_from_loc, orig_loc - delete_source_from_loc, /*for_comment=*/FALSE, /*raw_or_exp=*/FALSE); } else if (*orig_loc == ATTENTION_MARKER) { /* The call to skip_white_space() added one or more deletions in getting to the next token, including deleting the initial token. (This occurs if the scan entered or left source line modifications.) We need to remove those deletions. */ unsigned long sequence_id; slmp = nested_source_line_modif(orig_loc); sequence_id = slmp->sequence_id; rem_source_line_modif(slmp); free_source_line_modif(&slmp); if (sequence_id != sequence_id_for_source_line_modifs) { /* There were some deletions for white space in addition to the one for the initial token. Scan the list of modifications and remove any that were added after the one that deleted the initial token. */ a_source_line_modif_ptr slmp2; for (slmp = source_line_modif_list; slmp != NULL;) { slmp2 = slmp; slmp = slmp->next; if (slmp2->sequence_id > sequence_id) { /* This modification was added after the one that deleted the initial token. Remove it. */ rem_source_line_modif(slmp2); free_source_line_modif(&slmp2); } /* if */ } /* for */ } /* if */ } /* if */ delete_source_from_loc = NULL; /* Get the length of the next word. */ end_of_word = curr_char_loc; while (is_id_char[(*end_of_word)-CHAR_MIN]) ++end_of_word; next_word_len = (sizeof_t)(end_of_word - curr_char_loc); if (end_of_token != NULL) *end_of_token = end_of_word; /* Determine if first_word and next_word together make a whitespace keyword. */ if (first_word == tok_for) { if (next_word_len == len_of_each && memcmp(curr_char_loc, "each", size_t_arg(len_of_each)) == 0) { check_assertion(ms_extensions || cli_or_cx_enabled); if (for_each_statement_enabled) { /* "for each" statements (the STL and array versions) are available when emulating versions 1400 and later of the Microsoft compiler. */ return_token = tok_for_each; } /* if */ } /* if */ } else if (cppcx_enabled && first_word == tok_prefix_partial) { /* The first word is "partial". Determine if the whitespace keyword is either "partial ref struct" or "partial ref class". */ if (next_word_len == len_of_ref && memcmp(curr_char_loc, "ref", size_t_arg(len_of_ref)) == 0) { /* The second word is "ref", check if the second and third keyword form the whitespace keyword "ref struct" or "ref class". */ start_of_curr_token = orig_loc; curr_char_loc = end_of_word; next_word = scan_whitespace_keyword(tok_prefix_ref, &end_of_word); switch (next_word) { case tok_ref_class: return_token = tok_partial_ref_class; break; case tok_ref_struct: return_token = tok_partial_ref_struct; break; default: return_token = tok_identifier; break; } /* switch */ } else { /* The second word was not "ref"; return "partial" as an identifier. */ return_token = tok_identifier; } /* if */ } else { if (next_word_len == len_of_class && memcmp(curr_char_loc, "class", size_t_arg(len_of_class)) == 0) { next_word = tok_class; } else if (next_word_len == len_of_struct && memcmp(curr_char_loc, "struct", size_t_arg(len_of_struct)) == 0) { next_word = tok_struct; } else if (next_word_len == len_of_new && memcmp(curr_char_loc, "new", size_t_arg(len_of_new)) == 0) { next_word = tok_new; } /* if */ if (first_word == tok_enum) { if (cli_or_cx_enabled || (microsoft_version >= 1700 && !cpp11_mode)) { /* "enum struct" and "enum class" are whitespace keyword tokens in C++/CLI and in recent Microsoft C++ compilers. C++11, however, accepts a similar construct with two keywords instead. */ switch (next_word) { case tok_class: return_token = tok_enum_class; break; case tok_struct: return_token = tok_enum_struct; break; default: return_token = tok_identifier; break; } /* switch */ } /* if */ } else if (cli_or_cx_enabled) { switch (first_word) { case tok_prefix_interface: switch (next_word) { case tok_class: return_token = tok_interface_class; break; case tok_struct: return_token = tok_interface_struct; break; default: return_token = tok_identifier; break; } /* switch */ break; case tok_prefix_ref: switch (next_word) { case tok_new: return_token = cppcx_enabled ? tok_ref_new : tok_identifier; break; case tok_class: return_token = tok_ref_class; break; case tok_struct: return_token = tok_ref_struct; break; default: return_token = tok_identifier; break; } /* switch */ break; case tok_prefix_value: switch (next_word) { case tok_class: return_token = tok_value_class; break; case tok_struct: return_token = tok_value_struct; break; default: return_token = tok_identifier; break; } /* switch */ break; default: unexpected_condition(); break; } /* switch */ } /* if */ } /* if */ if (return_token != first_word && return_token != tok_identifier) { /* A whitespace keyword was detected. */ if (!in_token_insertion_from_string) { /* Add a source line modification to reduce the whitespace keyword into its canonical form. */ check_assertion(return_token >= tok_first_whitespace_token && return_token <= tok_last_whitespace_token); kwd = &whitespace_keywords[(int)return_token - (int)tok_first_whitespace_token]; slmp = add_source_line_modif(orig_loc, (sizeof_t)(end_of_word - orig_loc), kwd->text, kwd->end_of_insertion); adjust_deletion_counts(orig_loc, slmp->num_chars_to_delete); slmp->is_whitespace_kwd = TRUE; slmp->source_position = start_pos; start_of_curr_token = kwd->text; curr_char_loc = kwd->end_of_insertion; } else { /* This is in an insertion from a string. Since it does not come from the source line or from text in the macro buffer, we cannot use a source line modification, so we just take the text in situ as the current token. */ start_of_curr_token = orig_loc; curr_char_loc = end_of_word; } /* if */ len_of_curr_token = (sizeof_t)(curr_char_loc - start_of_curr_token); end_of_curr_token = curr_char_loc - 1; if (cppcx_enabled && return_token == tok_ref_new) { /* In C++/CX mode, "gcnew" is not a keyword; however, "ref new" and "gcnew" have identical semantics so we reuse all tok_gcnew code paths. */ return_token = tok_gcnew; } /* if */ } else if (curr_seq_number != start_seq_number) { /* We fell off the original source line looking for the second word, so we have to restore the first word as a line-start modification. */ if (first_word == tok_for) { /* Map "for" into the range of tokens for which we have replacement text. */ first_word = tok_prefix_for; } else if (first_word == tok_enum) { /* Ditto for "enum". */ first_word = tok_prefix_enum; } /* if */ kwd = &whitespace_keywords[(int)first_word - (int)tok_first_whitespace_token]; slmp = add_source_line_modif((char *) NULL, 0, kwd->text, kwd->end_of_insertion); slmp->is_whitespace_kwd = TRUE; slmp->source_position = start_pos; start_of_curr_token = kwd->text; curr_char_loc = kwd->end_of_insertion; len_of_curr_token = (sizeof_t)(start_of_curr_token - curr_char_loc); end_of_curr_token = curr_char_loc - 1; } else { /* The second word was on the same line, so we just undo the changes to pointers resulting from the skip_white_space call. */ start_of_curr_token = orig_loc; curr_char_loc = saved_curr_char_loc; } /* if */ return return_token; } /* scan_whitespace_keyword */ static a_boolean is_potential_start_of_whitespace_keyword(a_token_kind token) /* Returns TRUE if token is the beginning of a whitespace keyword. */ { a_boolean result; switch(token) { case tok_for: case tok_enum: /* "for each", "enum struct", and "enum class" are recognized both in regular Microsoft modes and in C++/CLI. */ result = TRUE; break; case tok_prefix_interface: case tok_prefix_ref: case tok_prefix_value: /* These can introduce a whitespace keyword only in C++/CLI. */ result = cli_or_cx_enabled; break; case tok_prefix_partial: result = cppcx_enabled; break; default: result = FALSE; break; } /* switch */ return result; } /* is_potential_start_of_whitespace_keyword */ static a_boolean check_for_whitespace_keyword(a_symbol_ptr symbol, a_token_kind *token_kind) /* This function checks if the next two tokens constitute a whitespace keyword, in which case it sets *token_kind to the kind of the whitespace keyword and returns TRUE. On input symbol points to the symbol for the first token. */ { a_boolean is_whitespace_keyword = FALSE; a_token_kind current_token; /* If the current token could begin a whitespace keyword, scan ahead and determine if the next token on the line completes the keyword. */ if ((cli_or_cx_enabled || (ms_extensions && !C_mode())) && !suppress_keyword_recognition) { /* Use of an identifier in a whitespace keyword supersedes any other meaning that has been declared for that identifier. We thus ignore any non-keyword symbols with this spelling. */ while (symbol != NULL && symbol->kind != (a_symbol_kind)sk_keyword) { symbol = symbol->next; } /* while */ if (symbol != NULL) { current_token = (a_token_kind)symbol->variant.keyword.token; if (is_potential_start_of_whitespace_keyword(current_token)) { /* The current token is a valid starting word for a whitespace keyword. */ current_token = scan_whitespace_keyword(current_token, /*end_of_token=*/NULL); if (current_token != tok_identifier && current_token != (a_token_kind)symbol->variant.keyword.token) { /* A whitespace keyword has been found. */ is_whitespace_keyword = TRUE; *token_kind = current_token; } /* if */ } /* if */ } /* if */ } /* if */ return is_whitespace_keyword; } /* check_for_whitespace_keyword */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ static a_boolean is_module_pp_directive(a_token_kind *ctoken) /* Perform special handling for modules-related preprocessing directives "module", "import" and "export". Return TRUE (and set *ctoken) if the input matches one of these directives: pp-module: export module pp-tokens ; new-line opt opt pp-import: export import header-name pp-tokens ; new-line opt opt export import header-name-tokens pp-tokens ; new-line opt opt export import pp-tokens ; new-line opt Note that if "export" matches any of these, the tok_export_keyword (which represents export-keyword in the standard) is returned rather than tok_export (which represents the export keyword in the standard). Note also that when "export" is present, this routine is called twice: initially to determine which type of "export" it is, and a second time to process the "import" or "module" that follows it. */ { a_boolean export_seen = FALSE; a_token_kind token = tok_error, next; a_const_char *saved_curr_char_loc = curr_char_loc, *start_of_args = NULL; a_boolean saved_fetch_pp_tokens = fetch_pp_tokens; a_boolean saved_expand_macros = expand_macros; int saved_logical_char_info_entries_used = logical_char_info_entries_used; a_source_position start_pos; macro_line_loc_to_source_pos(start_of_curr_token, start_pos); /* All processing in this function is as though we're in a preprocessing directive. */ in_preprocessing_directive = TRUE; fetch_pp_tokens = TRUE; expand_macros = FALSE; /* We know the first character is either 'i', 'e', or 'm', and that the caller has verified that these are "import", "export", and "module" respectively, so just use the first character here. */ if (*curr_char_loc == 'e') { /* "export" */ export_seen = TRUE; curr_char_loc += 6; skip_white_space(); start_of_args = curr_char_loc; } /* if */ if (*curr_char_loc == 'i' && (!export_seen || ((strncmp(curr_char_loc+1, "mport", 5) == 0) && !is_identifier_char(&curr_char_loc[6], /*len=*/NULL, /*is_identifier_start=*/FALSE)))) { /* "import" */ curr_char_loc += 6; skip_white_space(); if (!export_seen) start_of_args = curr_char_loc; next = get_token(); if (next == tok_identifier || next == tok_lt || next == tok_colon || next == tok_string_literal) { token = tok_import; } else { /* We're treating this as an identifier rather than a keyword; that might be fine, but issue a warning just to flag it. */ pos_st_warning(ec_identifier_not_keyword, &start_pos, "import"); } /* if */ } else if (*curr_char_loc == 'm' && (!export_seen || ((strncmp(curr_char_loc+1, "odule", 5) == 0) && !is_identifier_char(&curr_char_loc[6], /*len=*/NULL, /*is_identifier_start=*/FALSE)))) { /* Potential module-keyword; look for an identifier or tok_colon or tok_semicolon to make the final determination. */ curr_char_loc += 6; skip_white_space(); if (!export_seen) start_of_args = curr_char_loc; next = get_token(); if (next == tok_identifier || next == tok_colon || next == tok_semicolon) { token = tok_module; if (next == tok_semicolon) { /* Already seen a semicolon; skip directly to check for newline. */ goto check_for_newline; } /* if */ } else { /* We're treating this as an identifier rather than a keyword; that might be fine, but issue a warning just to flag it. */ pos_st_warning(ec_identifier_not_keyword, &start_pos, "module"); } /* if */ } /* if */ if (token != tok_error) { if (!within_curr_source_line(start_of_curr_token)) { /* This appears to be the result of a macro expansion and expanded macros cannot contain preprocessor directives. Give an error to explain that and let the processing continue as though it is not a preprocessing directive. */ pos_error(ec_module_directive_in_macro, &start_pos); token = tok_error; } /* if */ if (token != tok_error) { /* This appears to be a preprocessing directive, but in order to match a pp-import or pp-module directive, there must eventually be a semicolon immediately preceding the first new-line. */ do { skip_white_space(); next = get_token(); if (next == tok_semicolon) { check_for_newline: skip_white_space(); if (get_token() != tok_newline) { /* No newline after first semicolon. */ token = tok_error; } /* if */ break; } /* if */ } while (next != tok_newline && next != tok_end_of_source); if (token == tok_error || next != tok_semicolon) { /* No semicolon found before newline. This isn't ill-formed, but is unlikely to be what the user is trying to do. */ pos_warning(ec_apparent_module_pp_directive, &start_pos); token = tok_error; } /* if */ } /* if */ if (!export_seen && token == tok_module && pp_if_stack_depth != -1) { /* A module directive cannot occur within a #if preprocessor directive.*/ pos_error(ec_module_directive_in_if, &start_pos); token = tok_error; } /* if */ } /* if */ /* Always reset to original value. */ start_of_curr_token = saved_curr_char_loc; if (token == tok_error) { /* Just a regular identifier; restore things that this routine might have changed. */ curr_char_loc = saved_curr_char_loc; } else { /* The input matches the syntax of a preprocessor directive. If "export" was seen on this invocation, return tok_export_keyword, otherwise return tok_import or tok_module as appropriate. */ if (export_seen) { *ctoken = tok_export_keyword; } else { *ctoken = token; } /* if */ /* All tokens here are 6 characters long. */ end_of_curr_token = start_of_curr_token + 5; curr_char_loc = start_of_args; } /* if */ in_preprocessing_directive = FALSE; fetch_pp_tokens = saved_fetch_pp_tokens; expand_macros = saved_expand_macros; logical_char_info_entries_used = saved_logical_char_info_entries_used; return token != tok_error; } /* is_module_pp_directive */ #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED /* Facilities for detecting and reporting confusable identifiers, i.e., distinct identifiers whose graphical representations may be indistinguishable but that are actually spelled using different Unicode characters. */ static a_confusable_map_elem_ptr confusable_char(unsigned long ch) /* If ch is the src_char in an entry in confusable_map, return a pointer to that entry; otherwise, return NULL. */ { int val = (int)ch; auto get_src = [](size_t idx) { return confusable_map[idx].src_char; }; ptrdiff_t idx = bin_search(num_confusable_characters, val, get_src); a_confusable_map_elem_ptr result = NULL; if (idx != -1) { result = &confusable_map[idx]; } /* if */ return result; } /* confusable_char */ /* Facilities providing storage for the spellings of identifiers. The storage is allocated in front end memory and is aggregated into large chunks to reduce the number of allocation calls. */ /* The nominal size of a chunk of memory. If a single spelling exceeds this size, a larger chunk will be allocated to accommodate it. */ #define SPELLING_STORAGE_BUFFER_SIZE 65000 /* The structure of a single chunk of memory, part of a linked list of such chunks. */ typedef struct a_spelling_storage_buffer *a_spelling_storage_buffer_ptr; struct a_spelling_storage_buffer { a_spelling_storage_buffer_ptr next; /* Pointer to the next buffer on the list of all spelling storage buffers. */ sizeof_t allocated_size; /* The capacity of the buffer. Normally, this will be SPELLING_STORAGE_BUFFER_SIZE, but it will be larger if needed to accommodate a single spelling that will not fit into that size. */ sizeof_t next_available; /* The index at which the next spelling will be added to the buffer if there is sufficient room for it. */ char *buffer; /* Storage (allocated in front end memory) for the spellings. */ }; /* a_spelling_storage_buffer */ /* The start and end of the linked list of chunks of memory. */ STATIC_THREAD a_spelling_storage_buffer_ptr spelling_storage_buffer_head; STATIC_THREAD a_spelling_storage_buffer_ptr spelling_storage_buffer_tail; static a_spelling_storage_buffer_ptr alloc_spelling_storage_buffer( sizeof_t size) /* Allocate and initialize a new spelling storage buffer with storage sufficient to hold a string of the indicated size and add it to the list of buffers. */ { a_spelling_storage_buffer_ptr new_ssbp = alloc_fe_of_type(a_spelling_storage_buffer); new_ssbp->next = NULL; new_ssbp->allocated_size = (size > SPELLING_STORAGE_BUFFER_SIZE) ? size : SPELLING_STORAGE_BUFFER_SIZE; #if DEBUG ++num_spelling_storage_buffers_allocated; spelling_storage_buffer_space += (unsigned long)new_ssbp->allocated_size; #endif /* DEBUG */ new_ssbp->next_available = 0; new_ssbp->buffer = alloc_fe(new_ssbp->allocated_size); if (spelling_storage_buffer_tail != NULL) { spelling_storage_buffer_tail->next = new_ssbp; } else { spelling_storage_buffer_head = new_ssbp; } /* if */ spelling_storage_buffer_tail = new_ssbp; return new_ssbp; } /* alloc_spelling_storage_buffer */ static a_const_char *add_spelling_to_buffer(a_const_char *spelling, sizeof_t len) /* Add the indicated spelling (of length len) to a spelling buffer and return a pointer to the start of the spelling in the buffer. */ { a_spelling_storage_buffer_ptr ssbp = spelling_storage_buffer_tail; char *spelling_in_buffer; if (ssbp == NULL || len > ssbp->allocated_size - ssbp->next_available) { ssbp = alloc_spelling_storage_buffer(len); } /* if */ spelling_in_buffer = ssbp->buffer + ssbp->next_available; ssbp->next_available += len; memcpy(spelling_in_buffer, spelling, len); return spelling_in_buffer; } /* add_spelling_to_buffer */ /* A structure recording the representation of a single identifier, intended for insertion into a hash table containing all identifiers in the translation unit. */ typedef struct an_id_representation *an_id_representation_ptr; struct an_id_representation { an_id_representation_ptr next_confusable; /* When confusable identifiers are seen, all their distinct representations are linked via the next_confusable field, with the most recent spelling being the one kept in the hash table. This chain permits each confusable spelling to be reported only on its first occurrence. */ a_const_char *src_spelling; /* The null-terminated UTF-8 representation of the identifier's spelling in the source. */ a_const_char *prototyped_spelling; /* If this identifier contains confusable characters, this is the null-terminated UTF-8 representation of the identifier's spelling, replacing any confusable characters with their prototypes, as described in unicode.org/reports/tr39, "Unicode Security Mechanisms", section 4. If there are no confusable characters in the spelling, this points to the same string as the source spelling. Two confusable identifiers will have the same prototyped spelling and different source spellings. */ a_source_position pos_first_occurrence; /* The position of the first occurrence of the identifier. */ a_hash_value hash_code; /* The hash code for prototyped_spelling, recorded here so that it need be calculated only once, when the identifier is scanned. The hash_id_representation function simply returns this value and does not recompute it. */ }; /* an_id_representation */ STATIC_THREAD a_hash_table_ptr id_representation_map; /* Hash table containing an_id_representation entries for all identifiers encountered in the current translation unit. */ STATIC_THREAD a_text_buffer_ptr src_spelling; /* Text buffer for accumulating the source spelling of an identifier. */ STATIC_THREAD a_text_buffer_ptr prototyped_spelling; /* Text buffer for accumulating the prototyped spelling of an identifier. */ a_hash_value hash_id_representation(a_void_ptr key) /* Return the hash code for a given an_id_representation entry, pointed to by key. For efficiency, the hash value is computed only once and stored in the hash_code field of the entry. */ { return ((an_id_representation_ptr)key)->hash_code; } /* hash_id_representation */ a_boolean id_representations_match(a_void_ptr entry_ptr, a_void_ptr key_ptr) /* Given two an_id_representation entries, entry_ptr and key_ptr, return TRUE if their prototyped spellings are the same (thus representing either the same identifier, if their source spellings are the same, or confusable identifiers if their source spellings differ) and FALSE otherwise. */ { an_id_representation_ptr entry = (an_id_representation_ptr)entry_ptr; an_id_representation_ptr key = (an_id_representation_ptr)key_ptr; return strcmp(entry->prototyped_spelling, key->prototyped_spelling) == 0; } /* id_representations_match */ static void check_for_confusable_id(void) /* This function is called immediately after an identifier is scanned, with the identifier's spelling in the range from start_of_curr_token through end_of_curr_token. It finds or creates an entry in id_representation_map and issues a warning on the first occurrence of an identifier that is confusable with one already in the hash table. */ { an_id_representation curr_id_repr; a_const_char *curr_ch = start_of_curr_token; a_boolean confusable_seen = FALSE; a_hash_data_ptr *hash_data; an_id_representation_ptr new_id_rep; /* Local macro to update the hash code in curr_id_repr with the character ch. This uses the same hashing algorithm found in hash_source_string. */ #define update_hash(ch) \ curr_id_repr.hash_code += \ (a_hash_value)((curr_id_repr.hash_code << 5) + (ch)) check_assertion(multibyte_chars_in_source_enabled); mbc_scan_init(); if (id_representation_map == NULL) { /* Create the hash table and storage for identifiers. */ id_representation_map = alloc_hash_table(FRONT_END_REGION_NUMBER, 65536, fn_for_function(hash_id_representation), fn_for_function(id_representations_match)); src_spelling = alloc_text_buffer(256); prototyped_spelling = alloc_text_buffer(256); } else { reset_text_buffer(src_spelling); reset_text_buffer(prototyped_spelling); } /* if */ /* Create an identifier representation entry for the current identifier. */ curr_id_repr.pos_first_occurrence = pos_curr_token; curr_id_repr.hash_code = 0; while (curr_ch <= end_of_curr_token) { unsigned long ch; a_boolean err; int numch = mbc_to_wide_char(curr_ch, &ch, &err, /*is_native=*/FALSE); if (ch <= 0x7f) { /* An ASCII character, which is a single-byte character in UTF-8. */ add_char_to_text_buffer(src_spelling, (char)ch); add_char_to_text_buffer(prototyped_spelling, (char)ch); update_hash(ch); } else { /* An extended character. Convert it to UTF-8, save it, and see if it is confusable. */ unsigned char arr[4]; unsigned utf_len = unicode_to_utf8(ch, arr); a_confusable_map_elem_ptr cmep = confusable_char(ch); add_to_text_buffer(src_spelling, (a_const_char*)arr, utf_len); if (ch >= 0x200b && ch <= 0x200d) { /* This is a Unicode zero-width character. Since it is invisible, it makes the identifier confusable but should not be added to the prototyped string. */ confusable_seen = TRUE; } else if (cmep == NULL) { /* Not a confusable character. Add the UTF-8 bytes to the prototyped string and update the hash code. */ for (unsigned i = 0; i < utf_len; ++i) { add_char_to_text_buffer(prototyped_spelling, (char)arr[i]); update_hash((a_hash_value)arr[i]); } /* for */ } else { /* This character is confusable with another. Add the prototyped spelling of the character to the prototyped string and update the hash code. */ confusable_seen = TRUE; for (size_t i = 0; i < MAX_PROTOTYPE_LENGTH && cmep->prototype[i] != 0; ++i) { utf_len = unicode_to_utf8((unsigned long)cmep->prototype[i], arr); for (unsigned j = 0; j < utf_len; ++j) { add_char_to_text_buffer(prototyped_spelling, (char)arr[j]); update_hash((a_hash_value)arr[j]); } /* for */ } /* for */ } /* if */ } /* if */ curr_ch += numch; } /* while */ add_char_to_text_buffer(src_spelling, '\0'); curr_id_repr.src_spelling = add_spelling_to_buffer(src_spelling->buffer, src_spelling->size); if (!confusable_seen) { /* The source and prototyped spellings are the same. Set the current identifier representation to use the source spelling for comparison. */ curr_id_repr.prototyped_spelling = curr_id_repr.src_spelling; } else { /* Copy the prototyped spelling into the buffer and set the current identifier representation to use that string for comparison. */ add_char_to_text_buffer(prototyped_spelling, '\0'); curr_id_repr.prototyped_spelling = add_spelling_to_buffer(prototyped_spelling->buffer, prototyped_spelling->size); } /* if */ hash_data = hash_find(id_representation_map, &curr_id_repr, /*create=*/TRUE); if (*hash_data == NULL) { /* This is the first occurrence of this identifier. Update the hash table entry to point to the identifier representation. */ new_id_rep = alloc_fe_of_type(an_id_representation); #if DEBUG ++num_id_representations_allocated; #endif /* DEBUG */ *new_id_rep = curr_id_repr; new_id_rep->next_confusable = NULL; *hash_data = (a_hash_data_ptr)new_id_rep; } else { /* There was at least one other identifier with the same prototyped spelling. We need to find out if this is the same identifier or one that is confusable. Scan through the linked list of confusable representations to see if this identifier is already there. */ an_id_representation_ptr irp; a_boolean id_exists = FALSE; a_const_char *this_cp = curr_id_repr.src_spelling; for (irp = (an_id_representation_ptr)*hash_data; !id_exists && irp != NULL; irp = irp->next_confusable) { id_exists = strcmp(this_cp, irp->src_spelling) == 0; } /* for */ if (!id_exists) { /* This is a new identifier that is confusable with an earlier identifier. Add it to the list of confusables and issue a warning. */ irp = (an_id_representation_ptr)*hash_data; new_id_rep = alloc_fe_of_type(an_id_representation); *new_id_rep = curr_id_repr; new_id_rep->next_confusable = irp; *hash_data = (a_hash_data_ptr)new_id_rep; pos2_st_diagnostic(es_warning, ec_confusable_identifier, &pos_curr_token, &irp->pos_first_occurrence, this_cp); } /* if */ } /* if */ #undef update_hash } /* check_for_confusable_id */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ a_token_kind get_token(void) /* Scan the next token of input, and return its kind. The kind of token is also saved in curr_token. If fetch_pp_tokens is TRUE when this routine is called, a preprocessing token (pp-token) is fetched instead of a regular token. Constants are not converted (and numeric ones are returned as pp-numbers); adjacent string literals are not concatenated; keywords are not recognized; and errors are not issued for malformed tokens. Also, start_of_curr_token and end_of_curr_token will be set to point to the beginning and end of the current token, and len_of_curr_token will be set to its length. If expand_macros is TRUE, preprocessing macros are expanded as they are scanned. The caller sees only the tokens after expansion. If a macro name is preceded by an LE_INERT_MACRO or LE_TEMPORARILY_INERT_MACRO escape sequence, the macro is not expanded and the identifier is returned to the caller with curr_token_is_inert_macro set to TRUE; in addition, if the escape sequence is LE_TEMPORARILY_INERT_MACRO, curr_token_is_temporarily_inert_macro is set to TRUE. When fetch_pp_tokens is FALSE, do_string_literal_concatenation controls whether adjacent string literals are concatenated. If in_preprocessing_directive is TRUE, the definition of white space is changed to that for within preprocessing directives, and newline is returned as a token. Normally, if in_preprocessing_directive is TRUE keywords are not recognized, and "#", and "##" are recognized and returned as tokens. Recognition of keywords is re-enabled when caching_pragma_tokens and recognize_keywords_in_pragma are both TRUE. The "#" and "##" tokens are re-enabled when caching_pragma_tokens is TRUE. If in_pp_if_expression is TRUE (indicating that we are inside a preprocessing #if expression), integer constants will get an implicit "L" suffix, and undefined identifiers will be returned as the integer constant 0L. If exp_header_name is TRUE, then "..." will be scanned as a header name for a #include (tok_header_name). Other tokens will be processed normally. exp_system_header_name is similar, for header names of the form <...>. If exp_digit_sequence is TRUE, then a string of decimal digits will be scanned as a tok_digit_sequence (used in the #line directive). Other tokens will be processed normally. If treat_newline_as_token is TRUE, return tok_newline for ends of lines. If cached_token_rescan_stack is non-empty, it contains a stack of cached tokens which are to be rescanned; the token at the top of that stack is removed from it and returned. Otherwise, if reusable_cache_stack is non-NULL, the top element of that stack points to the next token to be rescanned; that token is then returned and the iterator is advanced without modifying the reusable cache. In the case where an invalid token is scanned, tok_error is returned and err_code_for_error_token is set to indicate a diagnostic that describes the error. Except when fetch_pp_tokens is TRUE, the error will have been issued by get_token. This routine is called an enormous number of times, and therefore has been written to be as fast as possible. Structure has been sacrificed to speed in some cases. */ { a_token_kind ctoken = tok_error; char ch; a_symbol_ptr assoc_symbol; a_symbol_kind id_kind; a_boolean rescan, is_inert_macro = FALSE; a_boolean is_temporarily_inert_macro = FALSE; a_boolean continue_scan; #if MICROSOFT_EXTENSIONS_ALLOWED a_token_kind token_kind; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ a_boolean gotten_from_cache = FALSE; a_symbol_header_ptr sym_hdr; sizeof_t id_length; a_const_char *id_ptr; a_string_or_char_literal_kind lit_kind; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED a_boolean id_contains_ucn = FALSE; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ if (any_initial_get_token_tests_needed && !fetching_tokens_from_insert_string()) { /* Before fetching a new token, do any processing required for pragmas that preceded the current token. Don't do this when fetching preprocessing tokens -- pragmas should only be processed when a "real" token of the source program is fetched. Additionally, when tokens are being recorded for a cache, do not perform any pragma processing. */ if (!suppress_pragma_processing) { if (!caching_tokens && !curr_token_pragmas->is_empty()) { process_curr_token_pragmas(); recalc_any_initial_get_token_tests_needed(); } /* if */ /* Clear any pragmas from the prior token before (potentially) loading a previously cached token. */ curr_token_pragmas->clear(); } /* if */ restart: /* If there are cached tokens to be rescanned, first check the cached_token_rescan_stack and take the first token from the top of the stack (if any); otherwise check the reusable cache stack. */ /* If there are cached tokens to be rescanned, take the first on the list. */ if (!cached_token_rescan_stack->is_empty()) { a_boolean no_token_on_stack; get_token_from_cached_token_rescan_stack(&no_token_on_stack); if (no_token_on_stack) { /* There were only pragma entries on the list. Go back and check for other token sources. */ goto restart; } /* if */ /* Update the ctoken value using the fetched token information. */ ctoken = curr_token; #if MICROSOFT_EXTENSIONS_ALLOWED /* Mark the point at which the end of the __if_exists tokens was encountered. */ if (ctoken == tok_end_of_if_exists) { process_end_of_if_exists(); goto restart; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ gotten_from_cache = TRUE; } else if (reusable_cache_stack != NULL) { /* If there are tokens to be rescanned from the reusable cache stack take the next one on the list. */ get_token_from_reusable_cache_stack(); /* Update the ctoken value using the fetched token information. */ ctoken = curr_token; gotten_from_cache = TRUE; #ifdef __GNUC__ } else { ctoken = tok_error; /* Avoid spurious warning from gcc. */ #endif /* ifdef __GNUC__ */ } /* if */ if (gotten_from_cache) { #if MICROSOFT_EXTENSIONS_ALLOWED if (!caching_tokens && (ctoken == tok_if_exists || ctoken == tok_if_not_exists)) { /* A Microsoft __if_exists or __if_not_exists directive. upon return from this routine the current token will be either the first token of the conditional text or the token following the closing brace of the directive. */ scan_microsoft_if_exists(ctoken); ctoken = curr_token; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ if (ctoken == tok_string_literal) { /* Give this string literal a sequence number, if needed. */ assign_string_literal_sequence_number(); } /* if */ goto return_from_token_scan; } /* if */ if (embed_control.in_process) { /* We are in the expansion of a #embed directive. */ if (!embed_control.reading_from_buffer) { /* We are processing the operand of a prefix, suffix, or if_empty parameter. Let skip_white_space() determine whether we're at the end of that text and, if so, what processing is next. */ skip_white_space(); } /* if */ if (embed_control.in_process && embed_control.reading_from_buffer) { /* We're currently reading the contents of the embed file. */ get_token_from_embed_file(); if (curr_lexical_state_stack_entry->cache_tokens) { assign_curr_token_sequence_number(); ctoken = curr_token; goto return_from_token_scan; } /* if */ goto return_curr_token; } /* if */ } /* if */ } /* if */ /* Clear any pragmas from the prior token before starting a new token. */ if (!suppress_pragma_processing) { curr_token_pragmas->clear(); } /* if */ assign_curr_token_sequence_number(); rescan_token: /* Skip over any initial white space blanks. These are very common, so they're handled inline here. The other potential white space characters (newline, horizontal and vertical tab, form feed, and "/" and "*" indicating the start of a comment) are processed out of the main "switch" statement. */ if ((ch = *curr_char_loc) == ' ') { do {} while ((ch = *(++curr_char_loc)) == ' '); } /* if */ start_of_token_scan: /* Restart here after scanning white space. */ /* Remember the start character position of the token. */ record_start_of_curr_token(); /* Branch to different processing code according to the first character of the token. *curr_char_loc must be used instead of ch because ch is not set when arriving at start_of_token_scan via goto from elsewhere. */ switch (*curr_char_loc) { case LE_ESCAPE: /* Lexical escape. Second character indicates which. */ ch = curr_char_loc[1]; if (ch == LE_END_OF_LINE || ch == LE_END_OF_INSERTION #if !FULLY_RESOLVED_MACRO_POSITIONS || ch == LE_END_OF_TOP_LEVEL_EXPANSION #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ ) { /* End of line or end of macro insertion. Let the white-space routine figure it out. */ skip_white_space(); /* If we are not at end of file after the white-space skip, go scan the next token. */ if (*curr_char_loc != LE_ESCAPE || (curr_char_loc[1] != LE_END_OF_LINE && curr_char_loc[1] != LE_END_OF_INSERTION)) { goto start_of_token_scan; } /* if */ return_end_of_source_token: /* This is the ultimate end of file, or the end of a macro argument string being scanned in isolation from the rest of the source. Return end of file. */ ctoken = tok_end_of_source; record_start_of_curr_token(); /* Remember the character position of the end of the token. */ end_of_curr_token = curr_char_loc + LE_ESCAPE_LEN - 1; /* Determine the source position of the end of source token. */ remember_token_start(); /* If this is the line-end escape at the end of the primary source line, back up the column so it points at the end of the line. */ if (curr_char_loc[1] == LE_END_OF_LINE) { if (curr_char_loc >= curr_source_line+LE_ESCAPE_LEN && curr_char_loc[-LE_ESCAPE_LEN] == LE_ESCAPE) { /* The previous character is an escape sequence, presumably for a newline. */ pos_curr_token.column -= LE_ESCAPE_LEN; } else { pos_curr_token.column -= 1; } /* if */ error_position.column = pos_curr_token.column; #if FULLY_RESOLVED_MACRO_POSITIONS pos_curr_token.orig_column = error_position.orig_column = pos_curr_token.column; #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ } /* if */ /* Go exit with the end-of-source token. */ goto end_of_token_scan_b; } else if (ch == LE_NEWLINE) { /* Newline. Is white space ordinarily, but a token within preprocessing directives and inside an asm function body. */ if (in_preprocessing_directive || treat_newline_as_token) { ctoken = tok_newline; curr_char_loc += LE_ESCAPE_LEN; goto save_end_position; } else { skip_white_space(); goto start_of_token_scan; } /* if */ } else if (ch == LE_END_OF_TOKEN) { /* Marker put into text by preprocessing of macros, to force the same interpretation of token boundaries as during the macro definition. At this level, should be ignored. */ curr_char_loc += LE_ESCAPE_LEN; goto rescan_token; } else if (ch == LE_INERT_MACRO || ch == LE_TEMPORARILY_INERT_MACRO) { /* Marker put into text preceding a macro name to indicate that the macro name should not be expanded. */ curr_char_loc += LE_ESCAPE_LEN; record_start_of_curr_token(); is_inert_macro = TRUE; if (ch == LE_TEMPORARILY_INERT_MACRO) { is_temporarily_inert_macro = TRUE; } /* if */ goto id_scan; } else if (ch == LE_NULL) { /* Null (zero) character. Let the white-space routine figure it out. */ skip_white_space(); goto start_of_token_scan; } else if (ch == LE_COMMA_FROM_ARGUMENT) { /* Marker put into text preceding a comma in a macro argument to prevent it from delimiting macro arguments upon rescan. Process it as white space. */ skip_white_space(); goto start_of_token_scan; } else if (ch == LE_RAW_OR_EXPANDED_ARGUMENT) { /* Marker put into text preceding a special sequence that allows selecting either the raw or expanded version of a macro argument, depending on how it is used in the replacement text. See choose_raw_or_expanded_arg for details. */ skip_white_space(); goto start_of_token_scan; } else if (ch == LE_MICROSOFT_MAGIC_COMMA) { /* A comma that precedes an empty __VA_ARGS__ expansion. The comma may or may not be suppressed, depending on the context; let skip_white_space process it appropriately. */ skip_white_space(); goto start_of_token_scan; } else if (ch == LE_EMPTY_VARIADIC_MACRO) { /* Marker put into text to mark the presence of an empty variadic macro expansion. */ skip_white_space(); goto start_of_token_scan; } else if (ch == LE_LPAREN_FROM_ARGUMENT) { /* Marker put into text preceding a left parenthesis that begins a macro argument. Process it as white space. */ skip_white_space(); goto start_of_token_scan; } else if (ch == LE_END_OF_EMBED_PREFIX || ch == LE_END_OF_EMBED) { /* End of a section of the expansion of a #embed directive. Let skip_white_space() determine how to proceed. */ skip_white_space(); goto start_of_token_scan; } else { unexpected_condition_str("get_token: bad lexical escape"); } /* if */ break; case ' ': case '\t': case '\f': case VERTICAL_TAB_CHARACTER: #if IGNORE_CARRIAGE_RETURN_IN_SOURCE case '\r': #endif /* IGNORE_CARRIAGE_RETURN_IN_SOURCE */ /* Form feed, vertical tab. Usually white space, but implementation- defined when inside a preprocessing directive. Let the white space routine decide. Likewise for carriage return, which is often considered white space as an extension. */ skip_white_space(); goto start_of_token_scan; case ATTENTION_MARKER: /* Marker placed into source text to provide a cue to the fact that a source modification (probably a text replacement due to a macro expansion) begins here. Let the white-space routine handle it. */ skip_white_space(); if (*curr_char_loc == ATTENTION_MARKER) { /* In the case where we run into the temporary insertion for a macro argument while we're scanning the argument, we are supposed to stop and return end-of-source. */ check_assertion(macro_depth != 0); goto return_end_of_source_token; } /* if */ goto start_of_token_scan; case '[': if (curr_char_loc[1] != ':' || !reflection_enabled) { ctoken = tok_lbracket; } else { /* "[:" starts a splice token unless it is followed by exactly one colon. Thus, "[::x" is a bracket followed by "::", but "[:::x" starts a splice of an expression that starts with ":: x". */ if (curr_char_loc[2] == ':' && curr_char_loc[3] != ':') { ctoken = tok_lbracket; } else { ctoken = tok_lsplice; goto two_char_token; } /* if */ } /* if */ break; case ']': ctoken = tok_rbracket; break; case '(': ctoken = tok_lparen; break; case ')': ctoken = tok_rparen; break; case '{': #if MICROSOFT_EXTENSIONS_ALLOWED /* If we are in a Microsoft attribute, check for an unquoted UUID. The UUID string can begin with an optional brace. */ if (in_microsoft_attribute && is_uuid_token()) { ctoken = scan_unquoted_uuid(); goto end_of_token_scan; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ ctoken = tok_lbrace; break; case '}': ctoken = tok_rbrace; break; case ',': ctoken = tok_comma; break; case '~': ctoken = tok_compl; break; case ':': /* In C++, may be "::" or ":>". Beginning with C23, "::" is a separator (used for attribute namespaces). When reflection is enabled, this could also be a splice terminator (":]"). */ if ((ch = *(curr_char_loc+1)) == ':' && (!C_mode() || c23_mode)) { ctoken = tok_colon_colon; goto two_char_token; } else if (ch == '>' && digraphs_allowed()) { ctoken = tok_rbracket; goto two_char_token; } else if (ch == ']' && reflection_enabled) { ctoken = tok_rsplice; goto two_char_token; } /* if */ ctoken = tok_colon; break; case ';': ctoken = tok_semicolon; break; case '?': ctoken = tok_quest_mark; break; case '-': /* One of "--", "->", "-=", or "-". */ /* In C++, "->*" is also a possibility. */ if ((ch = *(curr_char_loc+1)) == '-') { ctoken = tok_minus_minus; goto two_char_token; } else if (ch == '>') { if (C_dialect == C_dialect_cplusplus && *(curr_char_loc+2) == '*') { ctoken = tok_arrow_star; goto three_char_token; } /* if */ ctoken = tok_arrow; goto two_char_token; } else if (ch == '=') { ctoken = tok_minus_assign; goto two_char_token; } /* if */ /* Just plain "-". */ ctoken = tok_minus; break; case '+': /* One of "++", "+=", or "+". */ if ((ch = *(curr_char_loc+1)) == '+') { ctoken = tok_plus_plus; goto two_char_token; } else if (ch == '=') { ctoken = tok_plus_assign; goto two_char_token; } /* if */ /* Just plain "+". */ ctoken = tok_plus; break; case '*': /* One of "*=" or "*". */ if (*(curr_char_loc+1) == '=') { ctoken = tok_times_assign; goto two_char_token; } /* if */ /* Just plain "*". */ ctoken = tok_star; break; case '/': /* One of "/ *" (start of comment), "/=", or "/". */ /* ^--- space here to avoid nested-comment problem. */ if (start_of_comment()) { /* Go skip the comment. */ skip_white_space(); /* If skip_white_space decided this is not a comment, check the normal token possibilities. (It could also be that we've skipped over a comment and we've come upon another "/", but the code here works for that case too.) */ if (*curr_char_loc != '/') goto start_of_token_scan; record_start_of_curr_token(); if (fetch_pp_tokens && curr_char_loc[1] == '/') { /* In some strange Microsoft cases, a "//" in a macro expansion is treated as a comment. Here it ended up being half a comment so pass it through as a two-character error token. */ ctoken = tok_error; goto two_char_token; } /* if */ } /* if */ if (*(curr_char_loc+1) == '=') { ctoken = tok_divide_assign; goto two_char_token; } /* if */ /* Just plain "/". */ ctoken = tok_divide; break; case '&': /* One of "&&", "&=", or "&". */ if ((ch = *(curr_char_loc+1)) == '&') { ctoken = tok_and_and; goto two_char_token; } else if (ch == '=') { ctoken = tok_and_assign; goto two_char_token; } /* if */ /* Just plain "&". */ ctoken = tok_ampersand; break; case '%': /* One of "%=" or "%". Or, in C++, "%>", "%:", "%:%:". */ if (*(curr_char_loc+1) == '=') { ctoken = tok_remainder_assign; goto two_char_token; } else if ((ch = *(curr_char_loc+1)) == ':' && digraphs_allowed()) { if (*(curr_char_loc+2) == ':' && *(curr_char_loc+3) != ':' && !C_mode()) { /* We have a construct like "%::I", which is invalid if we interpret "%:" as a digraph. Issue a warning. */ pos_warning(ec_probable_inadvertent_sharp_digraph, &error_position); } /* if */ goto check_start_of_pp_directive; } else if (ch == '>' && digraphs_allowed()) { ctoken = tok_rbrace; goto two_char_token; } /* if */ /* Just plain "%". */ ctoken = tok_remainder; break; case '<': /* One of "<<", "<<=", "<=", or "<". In C++, "<%" or "<:". If gpp_mode is TRUE, the tokens "?" are also recognized. If exp_system_header_name is TRUE, a header name of the form . */ if (exp_system_header_name) { ctoken = scan_header_name(); goto end_of_token_scan; } else if ((ch = *(curr_char_loc+1)) == '<') { if (*(curr_char_loc+2) == '=') { ctoken = tok_shift_left_assign; goto three_char_token; } else { ctoken = tok_shift_left; goto two_char_token; } /* if */ } else if (ch == '=') { if (*(curr_char_loc+2) == '>' && spaceship_enabled) { ctoken = tok_spaceship; goto three_char_token; } else { ctoken = tok_le; goto two_char_token; } /* if */ } else if (ch == '%' && digraphs_allowed()) { ctoken = tok_lbrace; goto two_char_token; } else if (ch == ':' && digraphs_allowed()) { a_boolean is_digraph = TRUE; if (curr_char_loc[2] == ':' && curr_char_loc[3] != ':' && curr_char_loc[3] != '>') { /* In C++11, the <: digraph is not recognized in cases when the ':' is likely the start of the :: global scope operator, which the preceding condition detects. (The exceptions allow for things like "<:::X" ("[::X") and "<::>" ("[]"), where the digraph is likely intended.) (g++ only implemented this rule in its C++11 mode beginning with version 4.8.0.) */ if (cpp11_mode && !(gnu_mode && !clang_mode && gnu_version < 40800)) { is_digraph = FALSE; } else if (!C_mode()) { /* In other C++ modes, issue a warning that the digraph may not have been intended. */ pos_warning(ec_probable_inadvertent_lbracket_digraph, &error_position); } /* if */ } /* if */ if (is_digraph) { ctoken = tok_lbracket; goto two_char_token; } /* if */ #if GNU_EXTENSIONS_ALLOWED } else if (ch == '?' && gpp_version_is(<40300)) { ctoken = tok_gnu_min; goto two_char_token; #endif /* GNU_EXTENSIONS_ALLOWED */ } /* if */ /* Just plain "<". */ ctoken = tok_lt; break; case '>': /* One of ">>", ">>=", ">=", or ">". */ if ((ch = *(curr_char_loc+1)) == '>') { if (*(curr_char_loc+2) == '=') { ctoken = tok_shift_right_assign; goto three_char_token; } else { ctoken = tok_shift_right; goto two_char_token; } /* if */ } else if (ch == '=') { ctoken = tok_ge; goto two_char_token; #if GNU_EXTENSIONS_ALLOWED } else if (ch == '?' && gpp_version_is(<40300)) { ctoken = tok_gnu_max; goto two_char_token; #endif /* GNU_EXTENSIONS_ALLOWED */ } /* if */ /* Just plain ">". */ ctoken = tok_gt; break; case '=': /* One of "==" or "=". */ if (*(curr_char_loc+1) == '=') { ctoken = tok_eq; goto two_char_token; } /* if */ /* Just plain "=". */ ctoken = tok_assign; break; case '!': /* One of "!=" or "!". */ if (*(curr_char_loc+1) == '=') { ctoken = tok_ne; goto two_char_token; } /* if */ /* Just plain "!". */ ctoken = tok_not; break; case '^': /* One of "^=", "^^", or "^". */ if (*(curr_char_loc+1) == '=') { ctoken = tok_excl_or_assign; goto two_char_token; } else if (*(curr_char_loc+1) == '^' && reflection_enabled) { ctoken = tok_caret_caret; goto two_char_token; } /* if */ /* Just plain "^". */ ctoken = tok_excl_or; break; case '|': /* One of "||", "|=", or "|". */ if ((ch = *(curr_char_loc+1)) == '|') { ctoken = tok_or_or; goto two_char_token; } else if (ch == '=') { ctoken = tok_or_assign; goto two_char_token; } /* if */ /* Just plain "|". */ ctoken = tok_or; break; case '.': /* One of ".", a float constant, or "...". */ /* In C++, ".*" is also a possibility. */ ch = *(curr_char_loc+1); if (isdigit((unsigned char)ch)) { ctoken = scan_number(); goto end_of_token_scan; } else if (ch == '.' && *(curr_char_loc+2) == '.') { ctoken = tok_ellipsis; goto three_char_token; } else if (ch == '*' && C_dialect == C_dialect_cplusplus) { ctoken = tok_period_star; goto two_char_token; } /* if */ /* Just plain ".". */ ctoken = tok_period; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': /* Integer or float constant. If exp_digit_sequence is TRUE, scan digit_sequence instead (used in #line directive). */ if (!exp_digit_sequence) { #if MICROSOFT_EXTENSIONS_ALLOWED /* If we are in a Microsoft attribute, check for an unquoted UUID. */ if (in_microsoft_attribute && is_uuid_token()) { ctoken = scan_unquoted_uuid(); goto end_of_token_scan; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ ch = curr_char_loc[1]; if (char_ends_number[ch-CHAR_MIN]) { /* A single-digit integer. Handle that (frequent) case specially. */ an_integer_kind int_kind; char digit = *curr_char_loc; end_of_curr_token = curr_char_loc; ctoken = tok_int_constant; clear_constant(&const_for_curr_token, (a_constant_repr_kind)ck_integer); int_kind = (in_pp_if_expression && (c99_mode || gnu_mode)) ? targ_intmax_kind : (an_integer_kind)ik_int; const_for_curr_token.type = integer_type(int_kind); if (digit == '0') const_for_curr_token.is_simple_zero = TRUE; set_unsigned_integer_value( &const_for_curr_token.variant.integer_value, (a_host_large_unsigned)(unsigned char)(digit-'0')); curr_char_loc += 1; } else { ctoken = scan_number(); } /* if */ /* Adjust the length of an integer constant in a preprocessing #if expression. */ if (in_pp_if_expression && ctoken == tok_int_constant) { adjust_pp_int_constant(); } /* if */ } else { do {} while (isdigit((unsigned char)*(++curr_char_loc))); end_of_curr_token = curr_char_loc - 1; ctoken = tok_digit_sequence; } /* if */ goto end_of_token_scan; case '$': /* The dollar sign can optionally be accepted as an ID character. If it is to be accepted then we go to the code responsible for scanning identifiers; otherwise it is an unrecognized token. */ if (allow_dollar_in_id_chars) { goto id_scan; } else { goto bad_token; } /* if */ /* This can't fall through into the next case. */ case '\\': /* Either the start of a universal character name or named Unicode character or an invalid token. If the next character is "U" or "u", this is a universal character name; if it's "N" and the character after that is '{', it's a named Unicode character. */ ch = *(curr_char_loc+1); if (((ch == 'U' || ch == 'u') && universal_character_names_allowed) || (ch == 'N' && curr_char_loc[2] == '{' && named_unicode_chars_allowed)) { goto id_scan; } else if (injection_enabled) { /* The backslash token is the interpolator token in token sequences. */ ctoken = tok_backslash; break; } else { goto bad_token; } /* if */ /* This can't fall through into the next case. */ case 'U': case 'u': if (uliterals_enabled) { /* The C committee's TR 19769 (also adopted by C++11 and C11) introduced character and string literals of the forms u'...', U'...', u"...", and U"...". These are similar to wide literals, but potentially involve different character types and encodings. */ goto literal_prefix_scan; } else { goto id_scan; } /* if */ /* This can't fall through into the next case. */ case 'R': if (raw_string_literals_enabled) { /* This might be the start of a raw string literal, e.g., R"xyz(...)xyz". */ goto literal_prefix_scan; } else { goto id_scan; } /* if */ /* This can't fall through into the next case. */ case 'L': /* Probably an identifier, but check for a wide character constant (L'x') or wide string literal (L"xyz") first. */ literal_prefix_scan: lit_kind = scan_encoding_prefix(curr_char_loc); if (lit_kind == SCLK_NOT_A_LITERAL) { goto id_scan; } else if ((lit_kind & SCLK_STRING_LITERAL) == 0) { ctoken = scan_char_constant(lit_kind); goto end_of_token_scan; } else { remember_token_start(); /* Check for invalid concatenation here, as string literal concatenation can destroy the address correspondence needed for the test. */ check_for_invalid_macro_concatenation_if_needed( /*end_token_is_valid=*/FALSE); /* Skip over the prefix, if any, and the leading quote. */ curr_char_loc += /*lint !e679*/ offset_to_start_of_literal_value(lit_kind); ctoken = scan_string_literal(lit_kind); goto concatenate_adjacent_string_literals; } /* if */ /* This can't fall through into the next case. */ case 'i': /* Potentially the beginning of "import" directive. */ case 'e': /* Potentially the beginning of "export" directive. */ case 'm': /* Potentially the beginning of "module" directive. */ /* In C++20, the "import", "export", and "module" strings are considered to introduce preprocessing directives in some circumstances. This code determines whether they do so or should be treated as identifiers. Check for the desired strings before invoking a function call to probe deeper (because this code is frequently executed, e.g., when scanning "int"). */ if (module_keywords_enabled && !in_preprocessing_directive && (curr_token == tok_export_keyword || !any_tokens_gotten_from_curr_source_line) && (((*curr_char_loc == 'i' && strncmp(curr_char_loc+1, "mport", 5) == 0) || (*curr_char_loc == 'e' && strncmp(curr_char_loc+1, "xport", 5) == 0) || (*curr_char_loc == 'm' && strncmp(curr_char_loc+1, "odule", 5) == 0)) && !is_identifier_char(&curr_char_loc[6], /*len=*/NULL, /*is_identifier_start=*/FALSE)) && is_module_pp_directive(&ctoken)) { /* It has been determined that the current line is indeed a preprocessor directive and ctoken has been set accordingly. */ goto end_of_token_scan; } else { /* Scan as an identifier. */ goto id_scan; } /* if */ /* This can't fall through into the next case. */ case 'a': case 'b': case 'c': case 'd': /*above*/ case 'f': case 'g': case 'h': /*above*/ case 'j': case 'k': case 'l': /*above*/ case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': /*above*/ case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': /*above*/ case 'M': case 'N': case 'O': case 'P': case 'Q': /*above*/ case 'S': case 'T': /*above*/ case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': id_scan: /* Identifier (including keywords, macros, etc.). */ #if MICROSOFT_EXTENSIONS_ALLOWED /* If we are in a Microsoft attribute, check for an unquoted UUID. */ if (in_microsoft_attribute && isxdigit((unsigned char)*curr_char_loc) && is_uuid_token()) { /* The Microsoft compiler allows unquoted uuid strings in attributes. */ ctoken = scan_unquoted_uuid(); goto end_of_token_scan; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Find end of identifier. Identifiers can contain alphabetic characters, underscores, and digits after the first character, plus some multibyte characters if those are enabled. */ remember_token_start(); ctoken = tok_identifier; id_contains_ucn_or_multibyte_char = FALSE; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* Initialize for scanning multibyte characters in the string. */ mbc_scan_init_if_multibyte_chars_in_source_enabled(); #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ do { #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED int numch; #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ continue_scan = FALSE; /* Accumulate characters of the identifier after the first. */ #if EXPLICITLY_UNROLL_CRITICAL_LOOPS /* This code is particularly performance sensitive. Most compilers fail to unroll this loop even at high optimization levels, so it pays to unroll the loop manually. (The simple loop may be preferable when an optimizer is profile-guided.) */ for (;;) { if (is_id_char[(ch = curr_char_loc[0])-CHAR_MIN]) { if (is_id_char[(ch = curr_char_loc[1])-CHAR_MIN]) { curr_char_loc += 2; } else { curr_char_loc += 1; } /* if */ } else { break; } /* if */ } /* for */ #else /* !EXPLICITLY_UNROLL_CRITICAL_LOOPS */ while (is_id_char[(ch = *(curr_char_loc))-CHAR_MIN]) { curr_char_loc++; } /* while */ #endif /* EXPLICITLY_UNROLL_CRITICAL_LOOPS */ /* We have just scanned a sequence of "normal" identifier characters. Check whether we are now at a universal character name or named Unicode character. If so, scan the character and check for additional "normal" identifier characters. */ if ((ch = *curr_char_loc) == '\\') { ch = *(curr_char_loc + 1); if (((ch == 'u' || ch == 'U') && universal_character_names_allowed) || (ch == 'N' && curr_char_loc[2] == '{' && named_unicode_chars_allowed)) { a_const_char *ucn_start = curr_char_loc; a_boolean is_identifier_start = curr_char_loc == start_of_curr_token; continue_scan = TRUE; id_contains_ucn_or_multibyte_char = TRUE; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED id_contains_ucn = TRUE; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ if (ch == 'N') { if (scan_named_unicode_char(&curr_char_loc, /*is_identifier=*/TRUE, is_identifier_start, /*issue_diagnostics=*/TRUE, /*update_pos_on_error=*/FALSE) > MAX_UNICODE_VAL) { /* An error occurred. */ if (is_identifier_start) { /* Skip over the '\' and treat the N as starting an identifier. */ curr_char_loc = ucn_start + 1; goto id_scan; } else { /* We've already recognized part of an identifier. Set the end of the token to the character before the '\' and skip over the '\' to prevent it being processed again. */ end_of_curr_token = ucn_start - 1; curr_char_loc = ucn_start + 1; goto end_of_id; } /* if */ } /* if */ } else { a_boolean delimited_err; (void)scan_universal_character(&curr_char_loc, /*is_identifier=*/TRUE, is_identifier_start, /*issue_diagnostics=*/TRUE, &delimited_err); if (delimited_err && is_identifier_start) { /* Skip over the '\' and treat the u/U as starting an identifier. */ curr_char_loc = ucn_start + 1; goto id_scan; } /* if */ } /* if */ } /* if */ #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED } else if (is_identifier_char(curr_char_loc, &numch, /*is_identifier_start=*/FALSE)) { /* A multibyte character that is valid as an identifier character. Note that for an invalid multibyte character is_identifier_char returns FALSE, which makes us drop out of the loop and treat the invalid character as an invalid token. */ continue_scan = TRUE; id_contains_ucn_or_multibyte_char = TRUE; /* Increment curr_char_loc by numch and create any logical character index entries. */ incr_char_loc_for_multibyte_char(curr_char_loc, numch); #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ } /* if */ } while (continue_scan); end_of_curr_token = curr_char_loc - 1; /* Clear the symbol locator for the current identifier. This is done even if the identifier is not looked up in the symbol table. */ end_of_id: clear_locator(&locator_for_curr_id, &pos_curr_token); id_length = (sizeof_t)(end_of_curr_token - start_of_curr_token + 1); id_ptr = start_of_curr_token; #if MICROSOFT_EXTENSIONS_ALLOWED if (cli_or_cx_enabled && scanning_macro_name) { /* The Microsoft compiler in /clr mode allows defining a whitespace token as a macro. Check to see if this is a whitespace token. */ sym_hdr = find_symbol_header(id_ptr, id_length, &locator_for_curr_id); assoc_symbol = symbol_list_for_file_scope_symbols(sym_hdr); if (assoc_symbol != NULL && check_for_whitespace_keyword(assoc_symbol, &token_kind)) { /* This is a whitespace keyword. Return the canonicalized spelling as an identifier. */ ctoken = tok_identifier; } /* if */ } else #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Do not insert code here. */ if ((fetch_pp_tokens || in_preprocessing_directive) && !expand_macros && !caching_pragma_tokens) { /* Raw preprocessing tokens wanted, so do not look up the identifier. */ sym_hdr = NULL; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && multibyte_chars_in_source_enabled #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE /* In this configuration, non-UTF-encoded files can contain native multibyte characters, whose glyphs we cannot reliably compare with those of Unicode code points, so we only process ASCII-only identifiers. (Non-UTF-encoded files with NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE set to FALSE are encoded as Latin-1, so characters > 0x7f have corresponding Unicode code points and can be compared.) */ && (curr_file_unicode_source_kind != usk_none || !id_contains_ucn_or_multibyte_char) #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ ) { check_for_confusable_id(); } /* if */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ } else { /* If variadic macros are allowed, '__VA_ARGS__' should appear only in the replacement list of such macros, and similarly for '__VA_OPT__' when va_opt_enabled is TRUE. */ check_for_reserved_VA_id( (sizeof_t)(end_of_curr_token - start_of_curr_token + 1), start_of_curr_token); /* Look up the identifier in the symbol table. */ if (id_contains_ucn_or_multibyte_char) { /* If the identifier contains a universal character name or a multibyte character, the string must be processed to make it canonical. */ id_ptr = make_canonical_identifier(id_ptr, &id_length, /*force_ucn=*/FALSE); } /* if */ sym_hdr = find_symbol_header(id_ptr, id_length, &locator_for_curr_id); #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED if (check_unicode_security && multibyte_chars_in_source_enabled && #if NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE /* See the comment above regarding the exclusion of non-UTF-encoded files. */ (curr_file_unicode_source_kind != usk_none || !id_contains_ucn_or_multibyte_char) && #endif /* NATIVE_MULTIBYTE_CHARS_SUPPORTED_WITH_UNICODE */ !sym_hdr->id_added_to_map) { /* For speed, we only check for confusable identifiers once and record the fact that the check has been performed using the id_added_to_map flag in the identifier's symbol header. */ check_for_confusable_id(); if (!id_contains_ucn) { /* Exploits involving confusable identifiers rely on the similar graphical appearance of extended characters, so we do not suppress future checks for this identifier if this instance was written using UCNs, which could be used to obfuscate the confusable characters. */ sym_hdr->id_added_to_map = TRUE; } /* if */ } /* if */ #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ #if BUILTIN_FUNCTIONS_ENABLED if (builtin_needs_to_be_loaded(sym_hdr) && !fetch_pp_tokens && !in_preprocessing_directive && !fetching_tokens_from_insert_string()) { /* Load a builtin function once it's referenced (though not during preprocessing nor when creating token caches). */ (void)load_matching_builtin_function(sym_hdr); } /* if */ #endif /* BUILTIN_FUNCTIONS_ENABLED */ assoc_symbol = symbol_list_for_file_scope_symbols(sym_hdr); #if MICROSOFT_EXTENSIONS_ALLOWED /* We must check for white-space keywords before any macro expansion is performed on the current symbol. */ if (assoc_symbol != NULL && check_for_whitespace_keyword(assoc_symbol, &token_kind)) { /* Check the full keyword to see if it's been defined as a macro. If not, we're done. */ id_length = (sizeof_t)(end_of_curr_token - start_of_curr_token + 1); sym_hdr = find_symbol_header(start_of_curr_token, id_length, &locator_for_curr_id); assoc_symbol = symbol_list_for_file_scope_symbols(sym_hdr); if (assoc_symbol == NULL || assoc_symbol->kind == (a_symbol_kind)sk_keyword) { ctoken = token_kind; goto end_id_scan; } /* if */ } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* See if the identifier is a macro or keyword. "Macro" should take precedence over "keyword", but it will naturally, since keywords are entered first and therefore appear at the end of the list. */ while (assoc_symbol != NULL) { if ((id_kind = assoc_symbol->kind) == (a_symbol_kind)sk_macro && !is_inert_macro) { /* Macro to be expanded. */ if (expand_macros) { /* Check for invalid concatenation now, before the macro name can be overwritten (which will cause the "next token" to be at a different address from the saved concatenation point). */ check_for_invalid_macro_concatenation_if_needed( /*end_token_is_valid=*/TRUE); ctoken = macro_invocation(assoc_symbol, &rescan); /* In the usual case, we rescan the expanded form of the macro. */ if (rescan) goto rescan_token; /* Otherwise, macro_invocation has returned a token we can use immediately. If it is an identifier, fall into the remaining processing for non-macro identifiers. If it is a string literal, go look for adjacent string literals with which it might be concatenated. If it is an integer constant in a preprocessing #if expression, adjust its length. Otherwise, just return what we have been given. */ if (ctoken == tok_identifier) { /* Fall into remaining processing for non-macro identifiers. */ } else if (ctoken == tok_string_literal) { goto concatenate_adjacent_string_literals; } else if (in_pp_if_expression && ctoken == tok_int_constant) { adjust_pp_int_constant(); goto end_id_scan; } else { /* Anything else. */ goto end_id_scan; } /* if */ } /* if */ } else if (id_kind == (a_symbol_kind)sk_keyword) { /* Keyword, return the proper token for it. When fetching raw preprocessing tokens, or inside a preprocessing directive, the keywords mean nothing (except the keywords that correspond to operators). An exception is when we are processing a pragma that is explicitly designated as requiring keyword recognition. */ if (!fetch_pp_tokens && !suppress_keyword_recognition && (!in_preprocessing_directive || assoc_symbol->variant.keyword.is_preprocessing_op_or_punc || (caching_pragma_tokens && recognize_keywords_in_pragma) || (in_pp_if_expression && (assoc_symbol->variant.keyword.token == tok_false || assoc_symbol->variant.keyword.token == tok_true #if MICROSOFT_EXTENSIONS_ALLOWED /* MSVC recognizes the __LPREFIX et al. keywords in preprocessor expressions - i.e., it does not give the generic warning it issues for function-call syntax with an unknown name - but issues an error saying that the expression isn't a valid integer constant expression. We emulate that by enabling the keywords and then issuing an error on the operator in scan_microsoft_xprefix_operator. */ || (ms_version_is(>=1300) && (assoc_symbol->variant.keyword.token == tok_microsoft_Lprefix || assoc_symbol->variant.keyword.token == tok_microsoft_lprefix || assoc_symbol->variant.keyword.token == tok_microsoft_Uprefix || assoc_symbol->variant.keyword.token == tok_microsoft_uprefix)) #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ )))) { ctoken = (a_token_kind)assoc_symbol->variant.keyword.token; /* Check for a keyword that is not yet implemented. If one is found, issue a diagnostic and treat the keyword as an identifier. */ if (assoc_symbol->is_invisible) { /* Some keywords (currently, the Sun linker scope specifiers) may be disabled and enabled using pragma directives. */ ctoken = tok_identifier; } else if (ctoken == tok_unimplemented) { unimplemented_keyword_diagnostic(assoc_symbol); ctoken = tok_identifier; } else if (ctoken == tok_false || ctoken == tok_true) { /* A C++ boolean constant. */ scan_boolean_constant(ctoken); goto end_id_scan; } else if (clang_mode && curr_token == tok_struct && token_names[ctoken][0] == '_' && token_names[ctoken][1] == '_' && token_names[ctoken][2] == 'i' && token_names[ctoken][3] == 's' && token_names[ctoken][4] == '_') { /* The clang compiler treats keywords that start with "__is_" context-sensitively once they appear immediately after the "struct" keyword. */ pos_sy_warning(ec_keyword_dropped, &pos_curr_token, assoc_symbol); assoc_symbol->is_invisible = TRUE; ctoken = tok_identifier; } else { #if MICROSOFT_EXTENSIONS_ALLOWED /* The words that can potentially start a white-space keyword are defined as keywords but if we got here they are identifiers in the current context. */ if (cli_or_cx_enabled && (ctoken == tok_prefix_interface || ctoken == tok_prefix_ref || ctoken == tok_prefix_value || (cppcx_enabled && ctoken == tok_prefix_partial))) { ctoken = tok_identifier; } /* if */ if (ms_extensions) { if (ctoken == tok_microsoft_asm && !scanning_microsoft_asm) { if (build_microsoft_asm_string()) { /* If this is a Microsoft asm, build_microsoft_asm_string will have created a string representation of the Microsoft asm and attached it to the current token. */ } else { /* This should be treated as a GNU __asm. */ ctoken = tok_asm; } /* if */ } else if (!caching_tokens && (ctoken == tok_if_exists || ctoken == tok_if_not_exists)) { /* A Microsoft __if_exists or __if_not_exists directive. Upon return from this routine the current token will be either the first token of the conditional text or then token following the closing brace of the directive. */ scan_microsoft_if_exists(ctoken); ctoken = curr_token; goto return_from_token_scan; } else if (ctoken == tok_microsoft_identifier) { /* A Microsoft __identifier operator. Upon return from this routine the current token will be an identifier token for the name found within the parentheses. */ scan_microsoft_identifier_operator(); ctoken = curr_token; } /* if */ } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ goto end_id_scan; } /* if */ } /* if */ } /* if */ assoc_symbol = assoc_symbol->next; } /* while */ /* The identifier is not a macro and not a keyword. If we are in a preprocessing #if expression, replace the identifier with the value 0L. ("true" and "false" are the exception.) */ if (in_pp_if_expression) { ctoken = make_pp_int_constant(0L); const_for_curr_token.from_undefined_preproc_id = TRUE; } /* if */ } /* if */ end_id_scan: /* Use "_b" exit because we may no longer be on the line on which the identifier appears if it is a macro name and the opening parenthesis of the parameter list is on a different line. */ goto end_of_token_scan_b; case '\'': /* Character constant. */ ctoken = scan_char_constant(SCLK_ORDINARY_LITERAL); goto end_of_token_scan; case '"': /* String literal. */ if (exp_header_name) { /* If in a preprocessing directive, and exp_header_name is TRUE, the string should be scanned as a header name (file name on a #include). */ ctoken = scan_header_name(); goto end_of_token_scan; } else { /* Scan as a string literal, not a header name. We could still be in a preprocessing directive, though. */ remember_token_start(); /* Check for invalid concatenation here, as string literal concatenation can destroy the address correspondence needed for the test. */ check_for_invalid_macro_concatenation_if_needed( /*end_token_is_valid=*/FALSE); /* Skip over the leading quote. */ ++curr_char_loc; ctoken = scan_string_literal(SCLK_ORDINARY_STRING_LITERAL); goto concatenate_adjacent_string_literals; } /* if */ /* No break needed, both branches end with a goto. */ case '#': check_start_of_pp_directive: { a_boolean first_char_is_digraph; a_boolean no_chars_before_sharp = (curr_char_loc == curr_source_line); /* As the first token on a line, "#" opens a preprocessing directive. "#" and "##" are also allowed within the body of a #define (for stringizing and pasting). */ first_char_is_digraph = *curr_char_loc == '%'; /* Advance past the initial "#" or "%:". */ curr_char_loc++; if (first_char_is_digraph) curr_char_loc++; #if ATT_PREPROCESSING_EXTENSIONS_ALLOWED /* An AT&T System V release 4 extension uses #name(tokens) in a preprocessing #if to test an #assert predicate name. */ if (in_pp_if_expression && delete_source_from_loc == NULL) { /* Scan the #name(tokens) and create a 1 (TRUE) or 0 (FALSE) constant value accordingly. */ scan_assert_predicate_reference(&rescan); if (rescan) goto rescan_token; ctoken = tok_error; goto end_of_token_scan; } /* if */ #endif /* ATT_PREPROCESSING_EXTENSIONS_ALLOWED */ if (in_preprocessing_directive && !caching_pragma_tokens) { /* We recognize and return these preprocessing tokens even if we do not know that we are in the body of a #define; this helps produce reasonable error messages. Note that this is done even in PCC preprocessing mode even though PCC preprocessors don't generally support these operators. */ if (*curr_char_loc == '#' && !first_char_is_digraph) { ctoken = tok_paste; curr_char_loc++; } else if (*curr_char_loc == '%' && *(curr_char_loc+1) == ':' && first_char_is_digraph) { ctoken = tok_paste; curr_char_loc += 2; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (ms_extensions && *curr_char_loc == '@' && !first_char_is_digraph) { /* In Microsoft mode, a macro definition "#define M(x) #@x" causes "M(a)" to be expanded to 'a'. */ ctoken = tok_charize; curr_char_loc++; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { ctoken = tok_sharp; } /* if */ } else if (!any_tokens_gotten_from_curr_source_line && (!pcc_preprocessing_mode || no_chars_before_sharp) && !in_token_insertion_from_string) { /* A sharp that is the first thing on a line (optionally preceded by white space, except in pcc_preprocessing_mode) -- This is a preprocessing directive. */ if (!currently_in_pp_if_skip) { a_source_position start_of_directive; remember_token_start(); /* For the "#" pseudo-token. */ start_of_directive = pos_curr_token; { #if CHECKING a_shared_token curr_cached_token; if (!cached_token_rescan_stack->is_empty()) { curr_cached_token = cached_token_rescan_stack->back_elem(); } /* if */ #endif /* CHECKING */ pp_directive(); #if CHECKING a_shared_token final_cached_tok; if (!cached_token_rescan_stack->is_empty()) { final_cached_tok = cached_token_rescan_stack->back_elem(); } /* if */ check_assertion_str2(curr_cached_token == final_cached_tok, "get_token: token cache", "affected by preprocessing directive"); #endif /* CHECKING */ } /* After the directive has been processed, go scan another token. */ if (embed_control.in_process && embed_control.reading_from_buffer) { if (next_token_is_start_of_braced_initializer && embed_control.suffix_loc == NULL && !curr_lexical_state_stack_entry->cache_tokens) { /* This is a potentially-optimizable #embed directive. Set up const_for_curr_token as a ck_string constant with the data from the #embed and return a tok_string_literal to the caller to indicate the situation, suspending normal processing of the #embed data to allow examination of the next token following the #embed. */ make_string_constant_for_embed_expansion(&start_of_directive); } else { get_token_from_embed_file(); if (curr_lexical_state_stack_entry->cache_tokens) { assign_curr_token_sequence_number(); ctoken = curr_token; goto return_from_token_scan; } /* if */ } /* if */ goto return_curr_token; } /* if */ skip_white_space(); goto start_of_token_scan; } /* if */ /* Skipping because of an #if or the like, just return this as a token for further checking. */ ctoken = tok_sharp; } else { /* "#" outside of a preprocessing directive, and not at the start of a line; don't know what it means. */ err_code_for_error_token = ec_bad_use_of_sharp; if (!fetch_pp_tokens) { error_at_line_pos(err_code_for_error_token, start_of_curr_token); } /* if */ ctoken = tok_error; } /* if */ } /* When we reach this point, curr_char_loc should have already been advanced past the characters that make up this token (unlike most cases in which curr_char_loc still points to the final character of the token). */ goto save_end_position; /* No break needed. */ default: /* Pick up multibyte characters and things like European accented letters as the start of an identifier. */ if (is_identifier_char(curr_char_loc, (int *)NULL, /*is_identifier_start=*/TRUE)) { goto id_scan; } /* if */ bad_token: /* Something else, an error. */ err_code_for_error_token = ec_bad_token; if (!fetch_pp_tokens) { error_at_line_pos(err_code_for_error_token, start_of_curr_token); } /* if */ ctoken = tok_error; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED /* For a valid multibyte character (but invalid token), advance over all the bytes of the multibyte character. */ if (multibyte_chars_in_source_enabled) { a_boolean err; int numch = lex_mbc_length(curr_char_loc, &err); if (!err) { /* Increment curr_char_loc by numch and create any logical character index entries. */ incr_char_loc_for_multibyte_char(curr_char_loc, numch); goto save_end_position; } /* if */ } #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ } /* switch */ /* Normal assumption on break from switch is that the current character is part of the token, and therefore the current position needs to be incremented. */ curr_char_loc++; save_end_position: /* Remember character position of end of token. It's one before the current position. In cases where that is not right (as when it is necessary to scan white space following the token to check something), one should set end_of_curr_token explicitly and goto end_of_token_scan. */ end_of_curr_token = curr_char_loc - 1; end_of_token_scan: remember_token_start(); end_of_token_scan_b:; /* The "_b" entry here is for cases where something beyond the end of the token has been scanned, and therefore we may be on a new line now. Cases where this is true (perhaps because skip_white_space has been called) should call remember_token_start before scanning the initial token, and then should branch here after all other processing is done. */ /* Save the starting position in const_for_curr_token in case this is a literal token. This is done unconditionally to avoid the overhead of having to test the token kind, which would likely outweigh the cost of the assignment; it is innocuous, since const_for_curr_token is ignored for non-literal token kinds. */ const_for_curr_token.source_corresp.decl_position = pos_curr_token; #if EXTRA_SOURCE_POSITIONS_IN_IL if (start_of_curr_token != NULL) { /* Determine the source position of the end of the token. */ if (ctoken == tok_end_of_source) { /* The end-of-source position must be done specially, because the character position lies outside the usual range, and the pos_curr_token position was also determined specially. */ end_pos_curr_token = pos_curr_token; } else { macro_line_loc_to_source_pos(end_of_curr_token, end_pos_curr_token); } /* if */ /* Save the ending position in const_for_curr_token in case this is a literal token. */ const_for_curr_token.end_position = end_pos_curr_token; } /* if */ #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ return_from_token_scan: if (start_of_curr_token != NULL) { if (ctoken != tok_error) { /* To avoid redundant and spurious errors, only check concatenations involving non-error tokens. For example, the result of concatenating two # characters will be tokenized as two tok_error tokens, not a tok_paste, but should not be diagnosed as an invalid concatenation; some other tok_error tokens will have their own diagnostics. */ check_for_invalid_macro_concatenation_if_needed( /*end_token_is_valid=*/TRUE); } /* if */ len_of_curr_token = (sizeof_t)(end_of_curr_token - start_of_curr_token + 1); } /* if */ curr_token_is_inert_macro = is_inert_macro; curr_token_is_temporarily_inert_macro = is_temporarily_inert_macro; curr_token = ctoken; if (curr_lexical_state_stack_entry->cache_tokens && !curr_lexical_state_stack_entry->suspend_caching_tokens && !scanning_microsoft_asm) { /* A copy of each new token fetched should be saved in a token cache. The token sequence number check is used to prevent a token from being added more than once in cases where tokens are cached and rescanned by the caller for lookahead purposes. Don't include tokens that are part of a Microsoft asm as those will be encoded in the string that is saved as part of the Microsoft asm token. */ if (curr_token_sequence_number > curr_lexical_state_stack_entry->last_tsn_in_cache) { cache_curr_token(curr_lexical_state_cache()); curr_lexical_state_stack_entry->last_tsn_in_cache = curr_token_sequence_number; } /* if */ } /* if */ #if DEBUG if (debug_level >= 3) { /* Write out the current token. */ fprintf(f_debug, "get_token%s: pos = %lu/%2d, %-10s", gotten_from_cache ? " (from cache)" : "", (unsigned long)pos_curr_token.seq, pos_curr_token.column, token_names[(int)curr_token]); if (start_of_curr_token != NULL) { /* Print token string if valid. */ fprintf(f_debug, ", \"%.*s\"", (int)len_of_curr_token, start_of_curr_token); if (debug_level >= 5) { /* Print the length of the token. */ fprintf(f_debug, " (%d bytes)", (int)len_of_curr_token); } /* if */ } /* if */ if (curr_token_is_temporarily_inert_macro) { fprintf(f_debug, " (temporarily inert)"); } else if (curr_token_is_inert_macro) { fprintf(f_debug, " (inert)"); } /* if */ /* Dump constants only if they have been converted. */ if (!fetch_pp_tokens && is_literal_constant_token(curr_token)) { /* Dump value for constant. */ fprintf(f_debug, ", "); db_constant(&const_for_curr_token); } /* if */ (void)fputc('\n', f_debug); } else if (db_flag_is_set("tokens")) { if (start_of_curr_token != NULL) { /* Print token string if valid. */ fprintf(f_debug, "%.*s\n", (int)len_of_curr_token, start_of_curr_token); } /* if */ } /* if */ #endif /* DEBUG */ if (!in_preprocessing_directive) { any_tokens_fetched_from_curr_input_file = TRUE; } /* if */ return_curr_token: return curr_token; two_char_token: /* For two-character tokens, increment the source position appropriately and then rejoin the main code. */ curr_char_loc += 2; goto save_end_position; three_char_token: /* For three-character tokens, increment the source position appropriately and then rejoin the main code. */ curr_char_loc += 3; goto save_end_position; concatenate_adjacent_string_literals: /* Come here after scanning a string literal or wide string literal. If appropriate, string literals following the current one will be concatenated with it. See ANSI C 2.1.1.2, translation phase 6. */ if (fetch_pp_tokens || (in_preprocessing_directive && !caching_pragma_tokens) || !do_string_literal_concatenation) { a_const_char *after_string = curr_char_loc; /* String literal concatenation should not be done in the current mode. */ if (fetch_pp_tokens && ctoken == tok_string_literal && user_defined_literals_enabled && check_for_ud_suffix()) { /* Normally user-defined literals are detected during string literal concatenation, to allow for merging of suffixed and unsuffixed string literals. We still need to detect a user-defined literal when fetching pp-tokens, however, so check for a ud-suffix here. This will produce a tok_ud_literal token, but in this mode there is no need to set locator_for_curr_id and ud_lit_op_sym_for_curr_token. */ a_boolean create_ud_literal = TRUE; if (macro_preempts_udl_suffix) { /* Check to see if the putative literal suffix should be considered a macro instead. */ sizeof_t suffix_len = (sizeof_t)(curr_char_loc - after_string); a_const_char *canonical_id = make_canonical_identifier(after_string, &suffix_len, /*force_ucn=*/FALSE); if (id_is_macro_name(canonical_id, suffix_len)) { create_ud_literal = FALSE; } /* if */ } /* if */ if (create_ud_literal) { /* Turn the current token into a user-defined literal and adjust the end of token pointer. */ ctoken = tok_ud_literal; end_of_curr_token = curr_char_loc - 1; } else { /* Leave the token as a string and restore the current character pointer to what it was before check_for_ud_suffix() changed it. */ curr_char_loc = after_string; } /* if */ } /* if */ goto end_of_token_scan_b; } /* if */ /* Do string literal concatenation. */ check_assertion_str(ctoken == tok_string_literal, "get_token: concatenating string literal, bad token"); ctoken = concat_adjacent_string_literals(/*function_name_case=*/FALSE); /* Give this string literal a sequence number, if needed. */ assign_string_literal_sequence_number(); report_char_overflows(); goto return_from_token_scan; } /* get_token */ a_token_kind get_token_with_colon_separation(a_boolean *seen_tok_colon_colon) /* Like get_token except that tok_colon_colon (i.e., "::") is treated as two separate tok_colon tokens. Used in GNU asm statements where operands, clobbers, and labels are separated by colons, but there may not be any white space between the colons. seen_tok_colon_colon points to a variable that is used to maintain "state" information about whether or not a tok_colon_colon token is currently being processed. *seen_tok_colon_colon should be set to FALSE by the initial caller, and the same address used in subsequent sequential calls (until the colon processing is finished). */ { a_token_kind result; if (C_mode() && !c23_mode) { /* There is no tok_colon_colon in early C modes. */ result = get_token(); } else { if (!*seen_tok_colon_colon) { /* No pending token to process, get the next token. */ result = get_token(); if (result == tok_colon_colon) { /* Split a tok_colon_colon into two tok_colon tokens, returning one now and setting a flag so that the second one will be returned on the next call. */ *seen_tok_colon_colon = TRUE; result = tok_colon; #if EXTRA_SOURCE_POSITIONS_IN_IL /* The start position is the same, but the end position needs adjusting. */ end_pos_curr_token.column--; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ } /* if */ } else { /* The last call to this routine found a tok_colon_colon in the input stream; this call returns the "second" tok_colon token to the caller. */ *seen_tok_colon_colon = FALSE; result = tok_colon; /* Give the second ":" token a new token sequence number. When the numbers were assigned, a slot was reserved so that this number will be known to be unique. */ curr_token_sequence_number++; last_token_sequence_number_of_token = curr_token_sequence_number; /* Update the position information to point to the second character. */ pos_curr_token.column++; #if EXTRA_SOURCE_POSITIONS_IN_IL end_pos_curr_token.column++; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ } /* if */ } /* if */ curr_token = result; check_assertion(result != tok_colon_colon); return result; } /* get_token_with_colon_separation */ a_boolean is_keyword_token(a_token_kind token) /* Return TRUE if token is a token kind associated with a keyword. This is used to determine if a token initially cached as a keyword can be converted back into an identifier. */ { char ch; ch = token_names[(int)token][0]; return (int)token > (int)tok_last_complex_token && is_id_char[ch-CHAR_MIN]; } /* is_keyword_token */ void add_colon_to_tok_colon_if_present(void) /* The current token is tok_colon. If the next character is ':', update the current token to tok_colon_colon (potentially issuing a warning if the new token is the result of macro concatenation). This routine is used in C emulation modes in which "::" is not a token but can be used for attribute namespaces. */ { check_assertion(curr_token == tok_colon); if (*curr_char_loc == ':') { /* Temporarily treat the second ':' as the start of a new token in order to check if there was invalid token concatenation. */ ++start_of_curr_token; check_for_invalid_macro_concatenation(/*end_token_is_valid=*/FALSE); --start_of_curr_token; /* Update the current token values to include the second ':'. */ curr_token = tok_colon_colon; ++end_of_curr_token; ++len_of_curr_token; ++curr_char_loc; } /* if */ } /* add_colon_to_tok_colon_if_present */ static a_stop_token_stack_entry_ptr alloc_stop_token_stack_entry(void) /* Allocate a new stop token stack entry, initialize it, and return a pointer to it. */ { a_stop_token_stack_entry_ptr stsep; if (avail_stop_token_stack_entries != NULL) { /* Reuse an existing entry. */ stsep = avail_stop_token_stack_entries; avail_stop_token_stack_entries = avail_stop_token_stack_entries->next; } else { /* Allocate a new entry. */ stsep = (a_stop_token_stack_entry_ptr) alloc_fe(sizeof(a_stop_token_stack_entry)); #if DEBUG num_stop_token_stack_entries_allocated++; #endif /* DEBUG */ } /* if */ stsep->next = NULL; memzero((char*)stsep->stop_tokens, sizeof(a_token_set_array)); return stsep; } /* alloc_stop_token_stack_entry */ void push_stop_token_stack(void) /* Push a new entry on the stop token entry stack. This is used when a new lexical context is being processed. A pointer to the previous entry is saved for use when the stack is popped. */ { a_stop_token_stack_entry_ptr stsep; stsep = alloc_stop_token_stack_entry(); stsep->next = curr_stop_token_stack_entry; curr_stop_token_stack_entry = stsep; } /* push_stop_token_stack */ #if DEBUG void db_stop_tokens(void) /* Display the current stop token array. */ { int token; a_token_set_array_element *stop_tokens; stop_tokens = curr_stop_token_stack_entry->stop_tokens; for (token = 0; token != (int)tok_last; token++) { if (stop_tokens[token] != 0) { fprintf(f_debug, "stop_tokens[\"%s\"] = %d\n", token_names[token], stop_tokens[token]); } /* if */ } /* for */ } /* db_stop_tokens */ #endif /* DEBUG */ #if CHECKING void check_all_stop_token_entries_are_reset(a_token_set_array stop_tokens) /* Check that the stop token array elements all made it back to zero. (Every add_stop_token is supposed to have a corresponding remove_stop_token.) */ { int token; a_boolean any_error = FALSE; for (token = 0; token != (int)tok_last; token++) { if (stop_tokens[token] != 0) { any_error = TRUE; #if DEBUG if (debug_level != 0) { fprintf(f_debug, "stop_tokens[\"%s\"] != 0\n", token_names[token]); } /* if */ #endif /* DEBUG */ } /* if */ } /* for */ check_assertion_str2(!any_error, "check_all_stop_token_entries_are_reset:", "stop token array not all zero"); } /* check_all_stop_token_entries_are_reset */ #endif /* CHECKING */ static void pop_stop_token_stack_full(ARG_UNUSED a_boolean final_pop) /* Pop the current entry off of the stop token stack. If final_pop is TRUE, this call pops the last entry off of the stack. This is used to alter the consistency check at the end of the routine. */ { a_stop_token_stack_entry_ptr stsep; stsep = curr_stop_token_stack_entry; #if CHECKING /* If we are doing expensive checking, or if the debug flag for "check_stop_tokens" is set, then call the routine to make sure all of the stop tokens have been reset. */ { a_boolean check_stop_tokens = FALSE; #if DEBUG if (db_flag_is_set("check_stop_tokens")) check_stop_tokens = TRUE; #endif /* DEBUG */ #if EXPENSIVE_CHECKING check_stop_tokens = TRUE; #endif /* EXPENSIVE_CHECKING */ if (check_stop_tokens) { /*lint !e774*/ /* Make sure that all of the array elements of the entry being popped have been reset to their initial value of zero. */ /* coverity[dead_error_line] */ check_all_stop_token_entries_are_reset(stsep->stop_tokens); } /* if */ } #endif /* CHECKING */ /* Unlink this entry from the stack. */ curr_stop_token_stack_entry = stsep->next; /* Add the old entry to the list of available stack entries. */ stsep->next = avail_stop_token_stack_entries; avail_stop_token_stack_entries = stsep; /* The current entry should only be NULL if this is the final pop. */ check_assertion_str((curr_stop_token_stack_entry == NULL) == final_pop, "pop_stop_token_stack: wrong number of pops"); } /* pop_stop_token_stack_full */ void pop_stop_token_stack(void) /* Interface to pop_stop_token_stack_full that passes in final_pop == FALSE. */ { pop_stop_token_stack_full(/*final_pop=*/FALSE); } /* pop_stop_token_stack */ static a_lexical_state_stack_entry_ptr alloc_lexical_state_stack_entry(void) /* Allocate a new lexical state stack entry, initialize it, and return a pointer to it. */ { a_lexical_state_stack_entry_ptr lssep; if (avail_lexical_state_stack_entries != NULL) { /* Reuse an existing entry. */ lssep = avail_lexical_state_stack_entries; avail_lexical_state_stack_entries = avail_lexical_state_stack_entries->next; } else { /* Allocate a new entry. */ lssep = alloc_fe_of_type(a_lexical_state_stack_entry); #if DEBUG num_lexical_state_stack_entries_allocated++; #endif /* DEBUG */ } /* if */ lssep->next = NULL; lssep->cache_tokens = 0; lssep->last_tsn_in_cache = NO_TOKEN_SEQUENCE_NUMBER; lssep->error_position = null_source_position; new (&lssep->cache) Opt(); lssep->caching_tokens = FALSE; lssep->suspend_caching_tokens = FALSE; lssep->flushing_tokens = FALSE; return lssep; } /* alloc_lexical_state_stack_entry */ void push_lexical_state_stack(void) /* Push a new entry on the lexical state stack. This is used when a new lexical context is being processed. A pointer to the previous entry is saved for use when the stack is popped. */ { a_lexical_state_stack_entry_ptr lssep; lssep = alloc_lexical_state_stack_entry(); lssep->next = curr_lexical_state_stack_entry; lssep->error_position = error_position; lssep->next_token_is_top_level_decl_start = next_token_is_top_level_decl_start; lssep->caching_tokens = caching_tokens; caching_tokens = FALSE; curr_lexical_state_stack_entry = lssep; /* Push a new stop token stack entry too. */ push_stop_token_stack(); #if !FULLY_RESOLVED_MACRO_POSITIONS /* Make sure a macro position is not inadvertently used. */ pos_of_macro_invocation = null_source_position; #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ } /* push_lexical_state_stack */ static INLINE void pop_lexical_state_stack_full(a_boolean final_pop) /* Pop the current entry off of the lexical state stack. If final_pop is TRUE, this call pops the last entry off of the stack. This is used to alter the consistency check at the end of the routine. */ { a_lexical_state_stack_entry_ptr lssep = curr_lexical_state_stack_entry; /* Ensure that when finishing caching tokens, the current token's pragmas are cleared. This in turn ensures that the next call to get_token does not process the pragmas left over from building the token cache. */ if (caching_tokens && !suppress_pragma_processing) { curr_token_pragmas->clear(); } /* if */ /* Unlink this entry from the stack. */ curr_lexical_state_stack_entry = lssep->next; /* Either this is the final pop, and the lexical state stack entry should be cleared, or this is not the final pop, and something should be on the lexical state stack. */ check_assertion((curr_lexical_state_stack_entry == NULL && final_pop) || (curr_lexical_state_stack_entry != NULL && !final_pop)); /* Add the old entry to the list of available stack entries. */ lssep->next = avail_lexical_state_stack_entries; error_position = lssep->error_position; caching_tokens = lssep->caching_tokens; next_token_is_top_level_decl_start = lssep->next_token_is_top_level_decl_start; /* Discard any tokens that may have been cached. */ lssep->cache.~Opt(); check_assertion_str2(lssep->cache_tokens == 0, "pop_lexical_stack_state:", "cache_tokens not zero"); avail_lexical_state_stack_entries = lssep; /* The current entry should only be NULL if this is the final pop. */ check_assertion_str((curr_lexical_state_stack_entry == NULL) == final_pop, "pop_lexical_state_stack: wrong number of pops"); /* Pop the stop token stack too. */ pop_stop_token_stack_full(final_pop); #if !FULLY_RESOLVED_MACRO_POSITIONS /* Make sure a macro position is not inadvertently used. */ pos_of_macro_invocation = null_source_position; #endif /* !FULLY_RESOLVED_MACRO_POSITIONS */ } /* pop_lexical_state_stack_full */ void pop_lexical_state_stack(void) /* Interface to pop_lexical_state_stack_full that passes in final_pop == FALSE. */ { pop_lexical_state_stack_full(/*final_pop=*/FALSE); } /* pop_lexical_state_stack */ void begin_caching_fetched_tokens(a_boolean include_curr_token) /* Update the current lexical stack state to indicate that new tokens that are fetched should automatically be added to the cache associated with the current lexical state. If include_curr_token is TRUE, the current token is added to the cache. */ { a_lexical_state_stack_entry_ptr lssep; lssep = curr_lexical_state_stack_entry; if (lssep->cache_tokens == 0) { /* Reset the token cache when starting a new caching region. */ if (lssep->cache.has_value()) { /* A new cache is being started and this lexical state stack entry already has a token cache to use; reuse the existing token cache rather than potentially doing an unnecessary allocation of a new token cache. */ (*lssep->cache)->clear(); } else { lssep->cache = a_scanning_token_cache(); } /* if */ lssep->last_tsn_in_cache = NO_TOKEN_SEQUENCE_NUMBER; } /* if */ lssep->cache_tokens++; if (include_curr_token && last_token_sequence_number_of_token > lssep->last_tsn_in_cache) { /* There should always be a scanning token cache initialized above and not cleared until this lexical state stack entry is popped off the lexical state stack. */ check_assertion(lssep->cache.has_value()); cache_curr_token(lssep->cache->ptr()); lssep->last_tsn_in_cache = last_token_sequence_number_of_token; } /* if */ } /* begin_caching_fetched_tokens */ void end_caching_fetched_tokens(void) /* Update the current lexical stack state to indicate that new tokens that are fetched should no longer be added to the cache associated with the current lexical state. */ { a_lexical_state_stack_entry_ptr lssep; lssep = curr_lexical_state_stack_entry; check_assertion(lssep->cache_tokens != 0); lssep->cache_tokens--; } /* end_caching_fetched_tokens */ void flush_until_matching_token_full(a_boolean limit_flush) /* The current token is the opening token of a pair of matched tokens (e.g., an opening parenthesis). Flush to the corresponding closing token. If limit_flush is TRUE the number of lines flushed is limited to avoid skipping too far in error cases. */ { a_token_kind closing_token = tok_error; a_token_kind prev_token = tok_error; a_source_position start_pos; unsigned long paren_count = 0, bracket_count = 0, brace_count = 0; unsigned long max_lines; a_boolean done = FALSE; a_symbol_header_ptr prev_sym_header = NULL; a_boolean saved_flushing_tokens = curr_lexical_state_stack_entry->flushing_tokens; db_enter(3, "flush_until_matching_token_full"); curr_lexical_state_stack_entry->flushing_tokens = TRUE; /* Save the current position, to see later how much we have flushed. */ copy_source_position(pos_curr_token, start_pos); /* Determine the associated closing token and the maximum number of lines to throw away. */ max_lines = 2; switch (curr_token) { case tok_lparen: closing_token = tok_rparen; break; case tok_lbracket: closing_token = tok_rbracket; /* In Microsoft mode this could be the start of an attribute, so allow a longer flush. */ if (ms_extensions) max_lines = 10; break; case tok_lbrace: closing_token = tok_rbrace; max_lines = 20; break; case tok_lt: closing_token = tok_gt; break; default: unexpected_condition_str2("flush_until_matching_token:", "bad opening token"); } /* switch */ (void)get_token(); while (!done && (curr_token != closing_token || /*lint --e(845) LINTBUG */ paren_count != 0 || bracket_count != 0 || brace_count != 0)) { /* Count paired tokens within the skip. */ switch (curr_token) { case tok_lparen: paren_count++; break; case tok_rparen: if (paren_count > 0) paren_count--; break; case tok_lbracket: bracket_count++; break; case tok_rbracket: if (bracket_count > 0) bracket_count--; break; case tok_lbrace: brace_count++; break; case tok_rbrace: if (brace_count > 0) brace_count--; break; case tok_gt: /* A ">" is only meaningful when it is the token we are looking for. */ if (closing_token == tok_gt) { /* A ">" only counts as the end of the parameter list if we are not inside some other construct. For example when scanning "A<(1>2)>" the first ">" doesn't count. */ if (paren_count == 0 && bracket_count == 0 && brace_count == 0) { done = TRUE; } /* if */ } /* if */ break; case tok_shift_right: /* A ">>" may need to be treated as two ">". */ if (right_shift_can_be_angle_brackets && closing_token == tok_gt) { if (paren_count == 0 && bracket_count == 0 && brace_count == 0) { replace_right_shift_by_two_closing_angle_brackets(); /* Restart this loop iteration with the replaced tokens. */ continue; } /* if */ } /* if */ break; default:; } /* switch */ /* If we've skipped too many lines, give up the flush. */ if (limit_flush && (pos_curr_token.seq - start_pos.seq) > max_lines) break; /* Check for the start of a template parameter list, but only if we are looking for a ">". */ if (curr_token == tok_lt && closing_token == tok_gt && prev_token == tok_identifier) { if (!C_mode() && is_template_reference(prev_sym_header)) { flush_until_matching_token(); } /* if */ } /* if */ /* Always stop the flush on: 1) End of source; 2) A newline, if in a preprocessing directive. */ if (curr_token == tok_end_of_source || (in_preprocessing_directive && curr_token == tok_newline)) break; /* None of the conditions was satisfied, so keep flushing tokens. */ prev_token = curr_token; prev_sym_header = locator_for_curr_id.symbol_header; (void)get_token(); } /* while */ /* Restore the value of flushing_tokens. */ curr_lexical_state_stack_entry->flushing_tokens = saved_flushing_tokens; db_exit(); } /* flush_until_matching_token_full */ void flush_until_matching_token(void) /* Interface to flush_until_matching_token_full that supplies a default value for the limit_flush parameter. */ { flush_until_matching_token_full(/*limit_flush=*/TRUE); } /* flush_until_matching_token */ void flush_tokens_with_stop_tokens_and_warning_flag( a_token_set_array stop_tokens, a_boolean suppress_warning) /* Get and throw away tokens until a token is read that is in the set of stop tokens specified by stop_tokens. This routine is usually called to recover from syntax errors. A warning is sometimes issues depending on the number of tokens skipped. Suppress any diagnostics when suppress_warning is TRUE. This is used when this routine is called to skip tokens for some purpose other than error recovery. */ { a_source_position start_pos; a_token_kind prev_token = tok_error; a_symbol_header_ptr prev_sym_header = NULL; db_enter(3, "flush_tokens_with_stop_tokens"); /* Save the current position, to see later how much we have flushed. */ copy_source_position(pos_curr_token, start_pos); /* Flush tokens until something in the stop tokens set turns up. Stop flushing if the end of file is reached. While flushing, note parentheses, etc., and flush to matching tokens. */ /* Stop the flush on finding a token in the stop token set. */ while (stop_tokens[(int)curr_token] == 0) { /* On paired tokens, skip to the corresponding closing token. Note that we can't always trust whether prev_sym_header really is a template, so we only consider pairing a "<" token if we are in a context where it could make a difference (like an enclosing template argument list). */ if (curr_token == tok_lparen || curr_token == tok_lbracket || curr_token == tok_lbrace || (curr_token == tok_lt && ((prev_token == tok_identifier && !C_mode() && (stop_tokens[tok_gt] || stop_tokens[tok_comma]) && is_template_reference(prev_sym_header)) || prev_token == tok_template))) { /* In contexts where we are not issuing a warning, don't limit the flush because we are flushing to the end of a given construct. */ flush_until_matching_token_full(/*limit_flush=*/!suppress_warning); } /* if */ /* Always stop the flush on: 1) End of source; 2) A newline, e.g., in a preprocessing directive. */ if (curr_token == tok_end_of_source || curr_token == tok_newline) break; /* None of the conditions was satisfied, so keep flushing tokens. */ prev_token = curr_token; prev_sym_header = locator_for_curr_id.symbol_header; (void)get_token(); } /* while */ set_err_pos_to_curr_token(); /* If the flushing threw away more than just a little bit, put out a diagnostic to tell the user where the parsing recovered. If the flush finished because it hit the end of the translation unit, skip the warning, as it isn't helpful. */ if (!suppress_warning && pos_curr_token.seq - start_pos.seq > 2 && !is_at_end_of_translation_unit(pos_curr_token.seq)) { pos_warning(ec_end_of_flush, &error_position); } /* if */ db_exit(); } /* flush_tokens_with_stop_tokens_and_warning_flag */ static void flush_tokens_with_stop_tokens(a_token_set_array stop_tokens) /* Interface to flush_tokens_with_stop_tokens_and_warning_flag that indicates that warnings should be issued when appropriate. */ { flush_tokens_with_stop_tokens_and_warning_flag(stop_tokens, /*suppress_warning=*/FALSE); } /* flush_tokens_with_stop_tokens */ void flush_tokens(void) /* Get and throw away tokens until a token is read that is in the set of stop tokens. This routine is called to recover from syntax errors. This routine calls flush_tokens_with_stop_tokens, passing in the global stop token array. */ { flush_tokens_with_stop_tokens(curr_stop_token_stack_entry->stop_tokens); } /* flush_tokens */ void flush_tokens_without_warning(void) /* This routine is like flush_tokens, except that the warning that is sometimes issued when several lines are flushed is suppressed. */ { flush_tokens_with_stop_tokens_and_warning_flag( curr_stop_token_stack_entry->stop_tokens, /*suppress_warning=*/TRUE); } /* flush_tokens_without_warning */ void flush_to_closing_paren(void) /* Flush tokens until we reach an unmatched right parenthesis or brace. */ { a_token_set_array stop_tokens; /* Initialize a local stop token set. Also stop on newline and end of source for error cases. */ clear_token_set_array(stop_tokens); incr_token_set_array_element(stop_tokens, tok_newline); incr_token_set_array_element(stop_tokens, tok_end_of_source); incr_token_set_array_element(stop_tokens, tok_rbrace); incr_token_set_array_element(stop_tokens, tok_rparen); flush_tokens_with_stop_tokens(stop_tokens); } /* flush_to_closing_paren */ void flush_to_end_of_source(a_boolean suppress_warning) /* Flush tokens until we reach the end of source (this is useful when encountering an error while rescanning a terminated token cache). Suppress any diagnostics when suppress_warning is TRUE. */ { a_token_set_array stop_tokens; clear_token_set_array(stop_tokens); flush_tokens_with_stop_tokens_and_warning_flag(stop_tokens, suppress_warning); } /* flush_to_end_of_source */ static INLINE void advance_statement_token(a_token_cache_ptr cache) /* Consume the current token, adding it to cache if cache is non-NULL. */ { if (cache != NULL) cache_curr_token(cache); (void)get_token(); } /* advance_statement_token */ static a_boolean skip_statement_group(a_token_cache_ptr cache) /* Skip a balanced group, leaving its closing token current and uncached. Cache the preceding tokens if cache is non-NULL; otherwise flush them. Return FALSE if the closing token was not found. */ { a_boolean matched; if (cache != NULL) { matched = !cache_token_stream_until_matching_token(cache, CTS_NO_OPTIONS); } else { flush_until_matching_token_full(/*limit_flush=*/FALSE); matched = TRUE; } /* if */ return matched && curr_token != tok_end_of_source; } /* skip_statement_group */ static a_boolean skip_statement_header(a_token_cache_ptr cache) /* Skip the parenthesized header of a statement such as "while", "for", "switch" or "catch", caching it if cache is non-NULL. The current token should be the opening parenthesis. On success, leave the token following the closing parenthesis current and return TRUE. In flushing mode, recover from a missing opening parenthesis by treating the current token as the start of the controlled statement. In caching mode, leave diagnostics to the subsequent replay. */ { a_boolean result = FALSE; if (curr_token == tok_lparen) { if (skip_statement_group(cache)) { advance_statement_token(cache); result = TRUE; } /* if */ } else if (cache == NULL) { pos_error(ec_exp_lparen, &error_position); result = TRUE; } /* if */ return result; } /* skip_statement_header */ static a_token_kind next_try_clause_token(void) /* Return the kind of the token following the current try block or handler. Recognize the context-sensitive C++/CLI "finally" keyword when followed by "{". Preserve pragmas on following tokens, even when flushing a discarded statement. */ { a_token_kind next_tok; push_lexical_state_stack(); caching_tokens = TRUE; next_tok = next_token(); #if MICROSOFT_EXTENSIONS_ALLOWED if (cli_or_cx_enabled && next_tok == tok_identifier) { a_tiny_scanning_token_cache aux_cache; cache_curr_token(aux_cache.ptr()); (void)get_token(); if (curr_token_is_identifier_string("finally") && next_token() == tok_lbrace) { next_tok = tok_finally; } /* if */ rescan_cached_tokens(aux_cache.ptr()); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ pop_lexical_state_stack(); return next_tok; } /* next_try_clause_token */ static a_boolean skip_one_statement(a_token_cache_ptr cache) /* Skip exactly one statement without semantic analysis. The current token is its first token. Cache consumed tokens if cache is non-NULL; otherwise flush them. The caller sets caching_tokens or flushing_tokens as appropriate. Leave the last token current and uncached, so that the caller can leave the lexical state (or replay the cache) before fetching beyond the statement. This preserves pragma state (see flush_if_or_else_statement). Return TRUE if the current token belongs to the statement, FALSE if scanning stopped on an enclosing token or end-of-source in an error case. */ { a_boolean last_token_in_statement = FALSE; /* This routine is recursive because many statements have substatements, e.g., "while (a) while (b) { }" is a single statement. */ /* Skip a leading attribute-specifier-seq, including GNU attributes. */ while (std_attribute_tokens_next() || curr_token == tok_attribute) { if (curr_token == tok_attribute) { advance_statement_token(cache); if (curr_token == tok_lparen && !skip_statement_group(cache)) { goto done; } /* if */ } else if (cache != NULL) { cache_std_attribute(cache, /*add_tokens_to_cache=*/TRUE); if (curr_token == tok_end_of_source) goto done; } else if (!skip_statement_group(cache)) { goto done; } /* if */ advance_statement_token(cache); } /* while */ /* The rest of a C++26 expansion statement is scanned like a "for". */ if (curr_token == tok_template && next_token() == tok_for) { advance_statement_token(cache); } /* if */ switch (curr_token) { case tok_lbrace: /* Compound statement. */ last_token_in_statement = skip_statement_group(cache); break; case tok_if: advance_statement_token(cache); if (curr_token == tok_not && next_token() == tok_consteval) { advance_statement_token(cache); } /* if */ if (curr_token == tok_consteval) { advance_statement_token(cache); } else { if (curr_token == tok_constexpr) advance_statement_token(cache); if (!skip_statement_header(cache)) break; } /* if */ last_token_in_statement = skip_one_statement(cache); if (last_token_in_statement && next_token() == tok_else) { /* Consume the substatement's final token, then the "else". */ advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = skip_one_statement(cache); } /* if */ break; case tok_while: case tok_switch: #if MICROSOFT_EXTENSIONS_ALLOWED case tok_for_each: #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ advance_statement_token(cache); if (skip_statement_header(cache)) { last_token_in_statement = skip_one_statement(cache); } /* if */ break; #if UPC_EXTENSIONS_ALLOWED case tok_upc_forall: /* The upc_forall statement is similar to the standard for statement. */ FALLTHROUGH #endif /* UPC_EXTENSIONS_ALLOWED */ case tok_for: /* A range-based for may have "co_await" before its header. */ advance_statement_token(cache); if (curr_token == tok_coroutine_await) advance_statement_token(cache); if (skip_statement_header(cache)) { last_token_in_statement = skip_one_statement(cache); } /* if */ break; case tok_do: /* "do statement while ( expression ) ;" */ advance_statement_token(cache); last_token_in_statement = skip_one_statement(cache); if (last_token_in_statement && next_token() == tok_while) { advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = skip_statement_header(cache) && curr_token == tok_semicolon; } /* if */ break; case tok_try: { a_token_kind next_tok = tok_error; /* "try compound-statement handler-seq" */ advance_statement_token(cache); if (curr_token == tok_lbrace) { last_token_in_statement = skip_statement_group(cache); } /* if */ while (last_token_in_statement && (next_tok = next_try_clause_token()) == tok_catch) { /* Consume the preceding block's final token, then the "catch". */ advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = skip_statement_header(cache) && curr_token == tok_lbrace && skip_statement_group(cache); } /* while */ #if MICROSOFT_EXTENSIONS_ALLOWED if (last_token_in_statement && cli_or_cx_enabled && next_tok == tok_finally) { advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = curr_token == tok_lbrace && skip_statement_group(cache); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ break; } #if MICROSOFT_EXTENSIONS_ALLOWED case tok_microsoft_try: /* "__try compound-statement __except ( expression ) compound-statement" or "__try compound-statement __finally compound-statement". */ advance_statement_token(cache); if (curr_token == tok_lbrace) { last_token_in_statement = skip_statement_group(cache); } /* if */ if (last_token_in_statement && next_token() == tok_except) { advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = skip_statement_header(cache); } else if (last_token_in_statement && next_token() == tok_finally) { advance_statement_token(cache); advance_statement_token(cache); } /* if */ if (last_token_in_statement && curr_token == tok_lbrace) { last_token_in_statement = skip_statement_group(cache); } /* if */ break; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ case tok_case: { /* Find the label's ":", allowing conditional expressions. */ unsigned long quest_mark_count = 0; advance_statement_token(cache); while (curr_token != tok_end_of_source && curr_token != tok_semicolon && curr_token != tok_rbrace && (curr_token != tok_colon || quest_mark_count != 0)) { if (curr_token == tok_quest_mark) { quest_mark_count++; } else if (curr_token == tok_colon) { quest_mark_count--; } else if (curr_token == tok_lparen || curr_token == tok_lbracket || curr_token == tok_lbrace) { if (!skip_statement_group(cache)) goto done; } /* if */ advance_statement_token(cache); } /* while */ if (curr_token == tok_colon) { advance_statement_token(cache); last_token_in_statement = skip_one_statement(cache); } /* if */ break; } case tok_default: /* "default :" labels the statement that follows it. */ if (next_token() == tok_colon) { advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = skip_one_statement(cache); } /* if */ break; case tok_identifier: if (next_token() == tok_colon) { /* A label definition labels the statement that follows it. */ advance_statement_token(cache); advance_statement_token(cache); last_token_in_statement = skip_one_statement(cache); break; } /* if */ FALLTHROUGH default: { /* Expression, declaration, jump and empty statements end at the first ";" not nested in parentheses, brackets or braces. Also stop on right brace and end-of-source for error cases. */ a_token_set_array stop_tokens; clear_token_set_array(stop_tokens); incr_token_set_array_element(stop_tokens, tok_end_of_source); incr_token_set_array_element(stop_tokens, tok_rbrace); incr_token_set_array_element(stop_tokens, tok_semicolon); if (cache != NULL) { cache_token_stream(cache, stop_tokens); } else { flush_tokens_with_stop_tokens_and_warning_flag( stop_tokens, /*suppress_warning=*/TRUE); } /* if */ last_token_in_statement = curr_token == tok_semicolon; } break; } /* switch */ done: return last_token_in_statement; } /* skip_one_statement */ void flush_if_or_else_statement(void) /* Flush tokens of a dependent statement of an "if" or "else" statement. The current token is the token after the "if" or "else" keyword. Exactly one statement is flushed, so for an unbraced dependent statement such as "if constexpr (c) while (a) while (b) { } return;", the "return" is not flushed. On return the current token is the first token after the statement. */ { push_lexical_state_stack(); curr_lexical_state_stack_entry->flushing_tokens = TRUE; (void)skip_one_statement(/*cache=*/NULL); pop_lexical_state_stack(); if (curr_token != tok_end_of_source) (void)get_token(); } /* flush_if_or_else_statement */ void report_missing_closing_delimiter(an_error_code msg, an_error_code matching_msg, a_source_position *matching_pos) /* Issue an error using msg and error_position as the diagnostic and source position. If matching_pos designates a valid source location, add a note for matching_msg at the position given by matching_pos. */ { a_diag_list diag_list; a_source_position pos = *matching_pos; clear_diag_list(&diag_list); #if FULLY_RESOLVED_MACRO_POSITIONS if (pos.seq != pos.orig_seq) { /* The opening delimiter was contained in a macro expansion. Adjust the position to point to the original position of the delimiter. */ pos.seq = pos.orig_seq; pos.column = pos.orig_column; } /* if */ #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ if (pos.seq != 0) { more_info_diagnostic(matching_msg, &pos, &diag_list); #if FULLY_RESOLVED_MACRO_POSITIONS if (matching_pos->seq != matching_pos->orig_seq) { /* Add a note pointing to the macro invocation that generated the opening delimiter. */ more_info_diagnostic(ec_macro_invocation, matching_pos, &diag_list); } /* if */ #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ } /* if */ error_with_notes(msg, &error_position, &diag_list); } /* report_missing_closing_delimiter */ a_boolean required_token(a_token_kind token, an_error_code error_code, /* Defaulted: */ an_error_code matching_code, a_source_position *matching_start_pos) /* The current token is required to be "token". If it is, advance normally by calling get_token. If not, issue an error message and call flush_tokens. The error message is specified by error_code. If matching_start_pos designates a valid source position, add matching_code with that position to the diagnostic. In either case, return TRUE if the required token showed up. */ { a_boolean token_present; db_enter(5, "required_token"); if (curr_token == token) { (void)get_token(); token_present = TRUE; } else { /* Token not present. Report an error, flush to it or something else in the stop tokens set. */ add_stop_token(token); set_err_pos_to_curr_token(); if (matching_start_pos != NULL) { /* There's a matching starting delimiter. Put out the error message with the stating delimiter message note and flush tokens. */ report_missing_closing_delimiter(error_code, matching_code, matching_start_pos); flush_tokens(); } else { /* Report the error and flush tokens. */ syntax_error(error_code); } /* if */ remove_stop_token(token); /* If the token did show up, take it now. */ token_present = (curr_token == token); if (token_present) (void)get_token(); } /* if */ db_exit(); return token_present; } /* required_token */ a_boolean required_token_no_advance(a_token_kind token, an_error_code error_code, /* Defaulted: */ an_error_code matching_code, a_source_position *matching_start_pos) /* The current token is required to be "token". If it is not, issue an error message and call flush_tokens. The error message is specified by error_code. If matching_start_pos designates a valid source position, add matching_code with that position to the diagnostic. Do not advance past the token if it is found, but do return TRUE. */ { a_boolean token_present; db_enter(5, "required_token_no_advance"); if (curr_token == token) { token_present = TRUE; } else { /* Token not present. Report an error, flush to it or something else in the stop tokens set. */ add_stop_token(token); set_err_pos_to_curr_token(); if (matching_start_pos != NULL) { /* There's a matching starting delimiter. Put out the error message with the stating delimiter message note and flush tokens. */ report_missing_closing_delimiter(error_code, matching_code, matching_start_pos); flush_tokens(); } else { /* Report the error and flush tokens. */ syntax_error(error_code); } /* if */ remove_stop_token(token); /* If the token did show up, take it now. */ token_present = (curr_token == token); } /* if */ db_exit(); return token_present; } /* required_token_no_advance */ a_boolean loop_token(a_token_kind token) /* If the current token is the indicated token, then take that token (by calling get_token), and return TRUE; otherwise, do not call get_token, and return FALSE. This function is used for a bottom-of-loop test for repeated constructs, as in do { ... Scan the construct to be repeated. ... } while (loop_token(tok_comma)); */ { a_boolean return_value; if (curr_token == token) { (void)get_token(); return_value = TRUE; } else { return_value = FALSE; set_err_pos_to_curr_token(); } /* if */ return(return_value); } /* loop_token */ namespace { /* This is an implementation detail of the next_token functions that allows for traversal of the cached tokens with state preserving iteration. Instances of this class should be considered invalidated when either the token rescan stack or the reusable cache stack are modified. */ struct a_cached_token_supplier { INLINE a_cached_token_supplier(); INLINE a_cached_token_supplier operator++(int); INLINE an_immutable_cached_token* operator*(); private: INLINE a_boolean reusable_cache_has_next(); INLINE a_boolean is_currently_pragma(); INLINE void move_to_next_token(); INLINE void skip_pragmas(); size_t rescan_tokens_left; /* The number of tokens left to check on the cached token rescan stack. If 0 the reusable_token iterator should be checked instead. */ a_token_cache_iterator reusable_token_iter; /* The token to use in the reusable cache stack. */ }; /* a_cached_token_supplier */ a_cached_token_supplier::a_cached_token_supplier() /* Construct a new cached token iterator from the current lexical state. */ : rescan_tokens_left(cached_token_rescan_stack->length()), reusable_token_iter() { if (reusable_cache_stack != NULL) { this->reusable_token_iter = reusable_cache_stack->next_token; } /* if */ this->skip_pragmas(); } /* a_cached_token_supplier::a_cached_token_supplier */ a_cached_token_supplier a_cached_token_supplier::operator++(int) /* Move on to the next non-pragma token and return a copy of the current iterator. If no next token exists the next token is a default-constructed a_shared_token. */ { a_cached_token_supplier result = *this; this->move_to_next_token(); this->skip_pragmas(); return result; } /* a_cached_token_supplier::operator++ */ an_immutable_cached_token* a_cached_token_supplier::operator*() /* Return a pointer to the token represented by this iterator or NULL if no tokens remain. */ { an_immutable_cached_token *result = NULL; if (this->rescan_tokens_left > 0) { result = (*cached_token_rescan_stack)[this->rescan_tokens_left - 1].ptr(); } else if (this->reusable_cache_has_next()) { result = (*this->reusable_token_iter).ptr(); } /* if */ return result; } /* a_cached_token_supplier::operator* */ void a_cached_token_supplier::move_to_next_token() /* Move on to the next token. If no next token exists the next token is a default-constructed a_shared_token. */ { if (this->rescan_tokens_left > 0) { --this->rescan_tokens_left; } else if (this->reusable_cache_has_next()) { ++this->reusable_token_iter; } /* if */ } /* a_cached_token_supplier::move_to_next_token */ a_boolean a_cached_token_supplier::reusable_cache_has_next() /* Return TRUE if there's another token in the reusable cache; otherwise, return FALSE. */ { return (reusable_cache_stack != NULL && (this->reusable_token_iter != a_token_cache_iterator()) && (this->reusable_token_iter != reusable_cache_stack->token_cache->end())); } /* a_cached_token_supplier::reusable_cache_has_next */ a_boolean a_cached_token_supplier::is_currently_pragma() /* Return TRUE if the result of calling operator* is a pragma token; otherwise, return FALSE. This function is an optimized query of the result of operator* that does not require any new a_shared_token objects to be constructed. */ { a_boolean result = FALSE; if (this->rescan_tokens_left > 0) { const a_shared_token &tok = (*cached_token_rescan_stack)[this->rescan_tokens_left - 1]; if (tok->is_pragma()) { result = TRUE; } /* if */ } else if (this->reusable_cache_has_next()) { const a_shared_token &tok = *this->reusable_token_iter; if (tok->is_pragma()) { result = TRUE; } /* if */ } /* if */ return result; } /* a_cached_token_supplier::is_currently_pragma */ void a_cached_token_supplier::skip_pragmas() /* Move to the next non-pragma token. */ { while (this->is_currently_pragma()) { this->move_to_next_token(); } /* while */ } /* a_cached_token_supplier::skip_pragmas */ } /* namespace */ a_token_kind next_token_full(a_token_sequence_number *seq, a_symbol_header_ptr *sym_hdr) /* Return the next token after the current one while leaving the current token unchanged. This is used in recursive-descent parsing routines to peek ahead at the next token and decide on a path through the syntax. If seq is not NULL, return the token sequence number of the next token in the location pointed to by seq. If sym_hdr is not NULL, then set *sym_hdr to point to the symbol header if the next token is an identifier, else to NULL. */ { a_token_kind ntoken; an_immutable_cached_token *token; db_enter(5, "next_token_full"); if (in_preprocessing_directive && curr_token == tok_newline) { /* If we have reached the end of a preprocessing directive, don't attempt to scan tokens past the end. Return a tok_newline without actually looking at the next token. */ ntoken = tok_newline; /* If seq is not NULL, return the sequence number the current token (since we can't get the number of the next token. */ if (seq != NULL) *seq = curr_token_sequence_number; if (sym_hdr != NULL) *sym_hdr = NULL; goto done; } /* if */ /* If we are currently rescanning tokens from a cache then we should just be able to fetch the token kind from the next token on the list to be rescanned. This does not handle some of the more complex cases such as when we have to scan over the end of a reusable cache. In those cases we use the more general (and slower) method to fetch the next token. */ token = *a_cached_token_supplier(); /* If there is no cached token or if the token is the end-of-source token which is used to terminate the token cache, then disregard this token and fetch the next token using the slower method. */ if (token != NULL) { /* There is a cached token from which we can get then token kind. */ ntoken = token->get_kind(); /* If seq is not NULL, return the sequence number of the next token. */ if (seq != NULL) { *seq = token->get_starting_seq_number(); } /* if */ /* If sym_hdr is not NULL, return the symbol header pointer if the next token is an identifier. */ if (sym_hdr != NULL) { *sym_hdr = !token->is_identifier() ? NULL : token->get_locator().symbol_header; } /* if */ } else { /* The call to get_token below can change the error_position. Since this routine is meant to just "peek ahead" (without disturbing the current state of the token stream) save the current error position and restore it after the next token has been fetched. */ a_source_position saved_error_position; saved_error_position = error_position; /* Put the current token into a token cache so it can be rescanned. */ a_tiny_scanning_token_cache cache; cache_curr_token(cache.ptr()); /* Fetch the next token and remember its kind. */ ntoken = get_token(); /* If seq is not NULL, return the sequence number of the next token. */ if (seq != NULL) *seq = curr_token_sequence_number; /* If sym_hdr is not NULL, return the symbol header pointer if the next token is an identifier. */ if (sym_hdr != NULL) { *sym_hdr = (ntoken == tok_identifier) ? locator_for_curr_id.symbol_header : (a_symbol_header *)NULL; } /* if */ /* Put the two tokens in the cache (original, next) on the rescan list, and refetch the original token. Note that the "next" token remains on the rescan list. */ rescan_cached_tokens(cache.ptr()); error_position = saved_error_position; } /* if */ done: db_exit(); return ntoken; } /* next_token_full */ a_token_kind next_two_tokens(a_token_kind first_token_must_be, a_token_kind *token_2) /* Return the next two tokens after the current one while leaving the current token unchanged. The second token is only fetched if the first token matches the value passed by the caller, otherwise the second token is tok_error. The next token is the return value of the function, the second token is returned in the argument "token_2". This is like next_token except it fetches the next two tokens instead of only one. This routine cannot be used when fetching raw preprocessing tokens. */ { a_token_kind ntoken = tok_error; a_boolean tokens_found = FALSE; db_enter(3, "next_two_tokens"); if (in_preprocessing_directive && curr_token == tok_newline) { /* If we have reached the end of a preprocessing directive, don't attempt to scan tokens past the end. Return a tok_newline without actually looking at the next token. */ ntoken = tok_newline; *token_2 = tok_error; goto done; } /* if */ /* If we are currently rescanning tokens from a cache then we should just be able to fetch the token kind from the next token on the list to be rescanned. This does not handle some of the more complex cases such as when we have to scan over the end of a reusable cache. In these cases the more general (and slower) method is used to fetch the next token. */ { an_immutable_cached_token *token; a_cached_token_supplier token_supp; token = *(token_supp++); if (token != NULL) { ntoken = token->get_kind(); if (ntoken != first_token_must_be) { *token_2 = tok_error; tokens_found = TRUE; } else { token = *(token_supp++); if (token != NULL) { *token_2 = token->get_kind(); tokens_found = TRUE; } /* if */ } /* if */ } /* if */ } /* The next token information could not be determined just by looking at the token cache. Scan forward by getting and caching the necessary tokens. */ if (!tokens_found) { a_tiny_scanning_token_cache cache; /* Put the current token into a token cache so it can be rescanned. */ cache_curr_token(cache.ptr()); /* Fetch the token or possibly the two next tokens and remember their kinds. If the first token doesn't match the value specified by the caller the caller should not use the value in *token_2. */ ntoken = get_token(); if (ntoken == first_token_must_be) { cache_curr_token(cache.ptr()); *token_2 = get_token(); } else { *token_2 = tok_error; } /* if */ /* Put the two or three tokens in the cache (original, next 1, and possibly next 2) on the rescan list, and refetch the original token. Note that the "next" token remains on the rescan list. */ rescan_cached_tokens(cache.ptr()); } /* if */ done: db_exit(); return ntoken; } /* next_two_tokens */ void unget_token(void) /* "Unget" the current token, i.e., put it back on the input list so that it will be fetched again on the next get_token. On return, the current token is still the same. This is intended for use with unusual errors, so efficiency is not a prime concern. Note that this leaves last_token_sequence_number_of_token unchanged: The caller likely must restore it. */ { a_tiny_scanning_token_cache cache; db_enter(3, "unget_token"); /* Create a token cache containing a copy of the current token. */ cache_curr_token(cache.ptr()); /* Push the cache. This pushes two copies of the original token, and fetches one of them as the current token. */ rescan_cached_tokens(cache.ptr()); db_exit(); } /* unget_token */ static a_symbol_ptr dtor_matches_base_class(a_type_ptr tp) /* Determine if the name or specific symbol in the locator matches the type or name of a base class of tp. If so, return a pointer to the symbol associated with the matching base class. */ { a_base_class_ptr bcp; a_symbol_ptr result_sym = NULL; a_symbol_ptr sym_to_find = locator_for_curr_id.specific_symbol; a_type_ptr type_to_find = NULL; a_boolean is_nonreal = FALSE; if (sym_to_find != NULL && !sym_to_find->is_error && is_type_symbol(sym_to_find)) { type_to_find = type_symbol_type(sym_to_find); is_nonreal = type_to_find->variant.class_struct_union.is_nonreal_class; } /* if */ bcp = base_classes_of(tp); for (; bcp != NULL; bcp = bcp->next) { /* Get the symbol pointer associated with the base class. */ a_symbol_ptr sym; a_type_ptr base_type = bcp->type; sym = (a_symbol_ptr)base_type->source_corresp.assoc_info; /* If the locator contains a specific symbol, the base class type must match the type specified by the symbol. This is used when the locator refers to a template-id. */ if (type_to_find != NULL) { /* For nonreal classes we must check for equivalent nonreal classes. */ if (identical_types(type_to_find, bcp->type) || (is_nonreal && identical_types(type_to_find, bcp->type))) { result_sym = sym; break; } /* if */ } else if (sym->header == locator_for_curr_id.symbol_header) { result_sym = sym; break; } /* if */ } /* for */ return result_sym; } /* dtor_matches_base_class */ static a_symbol_ptr look_up_destructor_or_finalizer_name( a_symbol_locator *locator, a_boolean is_file_scope_qualified_name, a_symbol_ptr qualifier_sym, a_boolean no_normal_lookup, an_identifier_options_set options) /* Look up the name of a destructor or C++/CLI finalizer. The locator provides the name of the destructor or finalizer (the thing following the "~" or "!"). qualifier_sym points to a symbol that represent the qualifier that preceded the destructor or finalizer name, if any. is_file_scope_qualified_name is true if a leading :: was present. */ { a_symbol_ptr type_sym = NULL; clear_specific_symbol(*locator); if (is_file_scope_qualified_name) { type_sym = file_scope_id_lookup(il_header.primary_scope, locator, options); } else if (qualifier_sym != NULL && qualifier_sym->is_class_member) { type_sym = class_qualified_id_lookup(locator, sym_parent_class(qualifier_sym), options); } else if (qualifier_sym != NULL && sym_is_namespace_member(qualifier_sym)) { type_sym = namespace_qualified_id_lookup( locator, sym_parent_namespace(qualifier_sym), options); } else if (!no_normal_lookup) { type_sym = normal_id_lookup(locator, options); } /* if */ if (type_sym != NULL && type_sym->kind == (a_symbol_kind)sk_class_template) { /* If we found a class template symbol, ignore it. This should result in the normal lookup symbol being used. */ type_sym = NULL; } /* if */ return type_sym; } /* look_up_destructor_or_finalizer_name */ static a_boolean acceptable_dtor_or_finalizer_type( a_type_ptr field_sel_type, a_type_ptr dtor_or_finalizer_type) /* Determine whether dtor_or_finalizer_type is an acceptable type to be used in an explicit destructor/finalizer call for an object of field_sel_type. Normally the types must be identical, but if field_sel_type is a proxy class, we should accept any type, and in error cases dtor_or_finalizer_type can be an error type. */ { a_boolean result = FALSE; check_assertion(is_immediate_class_type(field_sel_type)); if (is_error_type(dtor_or_finalizer_type)) { result = FALSE; } else if (is_proxy_class(field_sel_type)) { result = TRUE; } else if (is_template_param_type(dtor_or_finalizer_type)) { /* The destructor/finalizer type is a template parameter. This could potentially match a real type during instantiation, so consider it okay for now. */ result = TRUE; } else { result = identical_types(field_sel_type, dtor_or_finalizer_type); } /* if */ return result; } /* acceptable_dtor_or_finalizer_type */ static a_boolean acceptable_dtor_or_finalizer_template_id( a_type_ptr field_sel_type) /* We have a construct like "p->A::~A()". Make sure that A names the type of "p". field_sel_type is the type of the object being destroyed. */ { a_symbol_ptr sym; a_boolean result = TRUE; sym = locator_for_curr_id.specific_symbol; if (sym != NULL) { if (is_class_symbol(sym)) { if (!acceptable_dtor_or_finalizer_type(field_sel_type, type_symbol_type(sym))) { /* The types do not match. */ result = FALSE; } /* if */ } /* if */ } /* if */ return result; } /* acceptable_dtor_or_finalizer_template_id */ static void get_destructor_or_finalizer_name( a_type_ptr field_sel_type, a_boolean is_file_scope_qualified_name, a_symbol_ptr qualifier_sym, a_boolean is_expr_context) /* The current token is the "~" at the start of a destructor name, or the "!" at the start of a C++/CLI finalizer name. Scan the name and build a locator for the destructor or finalizer name in locator_for_curr_id. is_expr_context is TRUE if this call is for a reference to a destructor or finalizer in an expression context (as opposed to a declarator). A destructor or finalizer declaration can only use the true name of the class. A destructor or finalizer reference (in a field selection operation), on the other hand, can use either the true name of the class or can use a typedef that refers to the class. Because of this, the lookup of the name that follows the "~" in a destructor reference or the "!" in a finalizer reference is quite complicated. If the destructor or finalizer reference is not a qualified name the lookup is done as a normal lookup and a lookup in the class of the right operand of the field selection. If the reference is a qualified name the lookup is done as a normal lookup, and a lookup in the scope that contains the class specified in the qualifier. For example: ::A::~B B is looked up in the file scope X::A::~B B is looked up in X In addition to these lookups, the name of the destructor or finalizer is compared with the name of the class of the right operand of the field selection. If any of these lookups result in a class that matches the class of the field selection operation, that result is used as the destructor or finalizer. Otherwise, each of the lookup results is checked to see if it matches a base class of the field selection. If exactly one of the lookups matches a base class, that result is used. If more than one matches, the lookup is ambiguous. If no match is found, the original name is converted to a destructor or finalizer name (e.g., "X" is changed to "~X") and a lookup error will be diagnosed by the caller. The caller is responsible for ensuring that the current token is "~", or "!" in the C++/CLI finalizer case, before calling this routine. This routine is called only in C++ mode. field_sel_type is NULL except when scanning the right operand of a field selection operator, in which case it points to the class type of the left operand. qualifier_sym points to a symbol that describes the qualifier when the destructor or finalizer is part of a qualified name (e.g., "A::B::~B"). */ { a_type_ptr type_for_locator = NULL; a_boolean is_destructor = (curr_token == tok_compl); a_boolean is_finalizer = (cli_or_cx_enabled && curr_token == tok_not); check_assertion(is_destructor || is_finalizer); /* Skip past the "~" or "!", check for an identifier. In this context, a "<" following the identifier is always interpreted as the delimiter of a template argument list. */ (void)get_token(); if (is_destructor && pack_index_next()) { /* In destructor-call syntax, allow a C++26 pack-index type-specifier: c->~T...[I]() Model this like the ~decltype(...) case. */ a_source_position pack_index_position = pos_curr_token; a_type_ptr pack_index_type = scan_pack_index_type_specifier( /*is_new_type_name=*/FALSE, /*is_implicit_type_context=*/FALSE, /*concept_okay=*/FALSE, /*might_be_id_start=*/TRUE); locator_for_curr_id = cleared_locator; locator_for_curr_id.variant.decltype_type = pack_index_type; locator_for_curr_id.source_position = pack_index_position; curr_token = tok_decltype_construct; } /* if */ if (!f_is_generalized_identifier_start(GID_DISALLOW_QUALIFIED_NAME | GID_DISALLOW_OPERATOR_NAME | GID_IS_DTOR_NAME | GID_TEMPLATE_ARGS_OPTIONAL, field_sel_type) && (curr_token != tok_decltype_construct || field_sel_type == NULL || qualifier_sym != NULL || is_finalizer)) { /* What follows the "~" or "!" is not an identifier or is something like X::~decltype(...). ~decltype is only allowed as an unqualified name. */ pos_error(ec_exp_identifier, &error_position); /* Put back the current token and make a fake error identifier. */ unget_token(); curr_token = tok_identifier; make_specific_symbol_error_locator(&locator_for_curr_id); } else { /* "~identifier" or "!identifier" is present. */ a_symbol_ptr specific_sym = NULL; if (field_sel_type == NULL) { a_type_ptr this_type; if (is_destructor && is_expr_context && variable_this_exists((a_variable_ptr*)NULL, &this_type)) { /* If no field selection type was specified, we're presumably dealing with an implicit "this->" selection. */ this_type = type_pointed_to(this_type); this_type = skip_typerefs(this_type); if (is_immediate_class_type(this_type) && !this_type->incomplete) { a_class_symbol_supplement_ptr cssp = class_symbol_supp(symbol_for(this_type)); if (cssp->destructor == NULL && cssp->has_trivial_destructor) { /* Represent the destructor explicitly so a forthcoming lookup can find it. */ add_trivial_dtor_representation(this_type); specific_sym = cssp->destructor; field_sel_type = this_type; } /* if */ } /* if */ } /* if */ } /* if */ if (microsoft_bugs && field_sel_type == NULL && qualifier_sym != NULL && is_type_symbol(qualifier_sym)) { /* In Microsoft bugs mode, use the qualifier type as the field selection type if no field selection type was specified. This permits a destructor/finalizer to be defined as "X::~X" or "X::!X", respectively, where X is a typedef name. */ field_sel_type = type_symbol_type(qualifier_sym); } /* if */ if (field_sel_type == NULL || !is_class_struct_union_type(field_sel_type)) { /* Either no field type was provided, or the type provided is not a class type. Don't do the special lookup processing in this case. */ } else { a_symbol_ptr field_sym; a_symbol_ptr type_sym = NULL; a_symbol_ptr orig_type_sym = NULL; a_symbol_locator normal_locator; a_symbol_locator other_locator; a_symbol_ptr normal_sym = NULL; a_symbol_ptr other_sym = NULL; a_symbol_ptr base_sym = NULL; a_symbol_ptr ambiguous_sym = NULL; a_boolean dtor_or_finalizer_okay = FALSE; a_type_ptr normal_tp = NULL; a_type_ptr other_tp = NULL; a_type_ptr tp; a_boolean ambiguous = FALSE; a_boolean error_already_issued = FALSE; a_type_ptr qualifier_type = NULL; a_type_ptr decltype_tp = NULL; field_sel_type = skip_typerefs(field_sel_type); field_sym = symbol_for(field_sel_type); check_assertion_str2(field_sym != NULL, "get_destructor_or_finalizer_name:", "NULL assoc_info"); if (qualifier_sym != NULL && is_type_symbol(qualifier_sym)) { /* If the destructor/finalizer name was specified with a qualified name, make sure the class specified by the qualifier names the field selection class or a base class thereof. */ qualifier_type = type_symbol_type(qualifier_sym); qualifier_type = skip_typerefs(qualifier_type); if (!acceptable_dtor_or_finalizer_type(field_sel_type, qualifier_type) && (!is_template_dependent_type(qualifier_type) && (!is_class_struct_union_type(qualifier_type) || find_base_class_of(field_sel_type, qualifier_type) == NULL))) { pos_ty2_error(!is_destructor ? ec_finalizer_qualifier_type_mismatch : ec_destructor_qualifier_type_mismatch, &locator_for_curr_id.source_position, qualifier_type, field_sel_type); set_to_error_locator(locator_for_curr_id); error_already_issued = TRUE; } /* if */ } /* if */ if (error_already_issued) { /* Skip this section if an error was already issued. */ } else if (field_sym->header == locator_for_curr_id.symbol_header && !is_proxy_class(field_sel_type) && (!locator_for_curr_id.is_template_id || acceptable_dtor_or_finalizer_template_id(field_sel_type))) { /* The destructor/finalizer name matches the class name -- this is a normal destructor/finalizer reference. */ dtor_or_finalizer_okay = TRUE; } else if (curr_token == tok_decltype_construct) { decltype_tp = locator_for_curr_id.variant.decltype_type; if (acceptable_dtor_or_finalizer_type(field_sel_type, decltype_tp)) { dtor_or_finalizer_okay = TRUE; if (is_template_dependent_type(field_sel_type) || is_template_dependent_type(decltype_tp)) { /* Use the proxy class in place of a dependent decltype. */ decltype_tp = proxy_class_for_template_param(decltype_tp); type_sym = symbol_for(decltype_tp); } else { decltype_tp = skip_typerefs(decltype_tp); type_sym = symbol_for(decltype_tp); } /* if */ curr_token = tok_identifier; } /* if */ } else { clear_specific_symbol(locator_for_curr_id); /* Do a normal lookup. If this produces a class symbol, see if the class matches the field selection class or one of its base classes. If it matches the class, the lookup is done. If it matches a base, save this result for later. Make a copy of the locator and use that for the lookup. A copy is made to preserve other flags set by the lookup (such as the semivisible nested class flag). */ normal_locator = locator_for_curr_id; normal_sym = normal_id_lookup(&normal_locator, IDL_MUST_BE_CLASS); #if MICROSOFT_EXTENSIONS_ALLOWED if (normal_sym != NULL) { if (cli_or_cx_enabled) { /* If the type is something like "int" get the corresponding C++/CLI type (e.g., "Int32"). We know the field selection type is a class type, so the destructor would be invalid for just "int". */ a_type_ptr new_tp; new_tp = type_symbol_type(normal_sym); new_tp = skip_typerefs(new_tp); new_tp = system_type_from_fundamental_type(new_tp); if (new_tp != NULL) normal_tp = new_tp; } /* if */ } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ if (normal_sym != NULL && (is_class_symbol(normal_sym) || is_template_param_type_symbol(normal_sym))) { normal_tp = type_symbol_type(normal_sym); normal_tp = skip_typerefs(normal_tp); if (acceptable_dtor_or_finalizer_type(field_sel_type, normal_tp)) { if (is_template_dependent_context() && is_template_dependent_type(normal_tp) && qualifier_type != NULL && !is_template_dependent_type(qualifier_type)) { /* In a reference like p->X::~T, where the type of "p" is dependent but X is not, use X for T because after this routine exits, we have only the name of T not its actual type. */ orig_type_sym = normal_sym; type_sym = qualifier_sym; } else { type_sym = normal_sym; } /* if */ dtor_or_finalizer_okay = TRUE; locator_for_curr_id = normal_locator; } else { if (is_template_param_type(normal_tp) || qualifier_type == NULL || !identical_types(qualifier_type, normal_tp) || find_base_class_of(field_sel_type, normal_tp) == NULL) { /* A base class is only allowed for qualified names and in a reference like X::~Y, X and Y must be the same type. */ normal_sym = NULL; } /* if */ } /* if */ } /* if */ if (!dtor_or_finalizer_okay) { /* Look up the destructor/finalizer name based on the qualifier that was present (if any). If no qualifier was present, look up the destructor/finalizer in the field selection class. */ other_locator = locator_for_curr_id; if (!is_template_dependent_type(field_sel_type) && (qualifier_sym != NULL || is_file_scope_qualified_name)) { other_sym = look_up_destructor_or_finalizer_name( &other_locator, is_file_scope_qualified_name, qualifier_sym, /*no_normal_lookup=*/TRUE, IDL_MUST_BE_CLASS); } else { other_sym = class_qualified_id_lookup(&other_locator, field_sel_type, IDL_MUST_BE_CLASS); } /* if */ if (other_sym != NULL && (is_class_symbol(other_sym) || is_template_param_type_symbol(other_sym))) { other_tp = type_symbol_type(other_sym); other_tp = skip_typerefs(other_tp); if (acceptable_dtor_or_finalizer_type(field_sel_type, other_tp)) { type_sym = other_sym; dtor_or_finalizer_okay = TRUE; locator_for_curr_id = other_locator; } else { other_sym = NULL; } /* if */ } /* if */ } /* if */ } /* if */ if (!dtor_or_finalizer_okay && microsoft_mode && decltype_tp == NULL) { /* If, in Microsoft mode, we haven't found a valid destructor or finalizer, look for a variable or data member (static or nonstatic) with a type that matches the destructor/finalizer type. */ a_symbol_ptr sym; clear_specific_symbol(locator_for_curr_id); sym = normal_id_lookup(&locator_for_curr_id, IDL_NO_OPTIONS); if (sym != NULL) { /* If this is one of the symbol kinds accepted by the Microsoft compiler, get the type of the entity to see if it matches the destructor/finalizer type. */ if (sym->kind == (a_symbol_kind)sk_variable) { tp = sym->variant.variable.ptr->type; } else if (sym->kind == (a_symbol_kind)sk_field) { tp = sym->variant.field.ptr->type; } else if (sym->kind == (a_symbol_kind)sk_static_data_member) { tp = sym->variant.static_data_member.variable->type; } else { tp = NULL; } /* if */ if (tp != NULL) { if (acceptable_dtor_or_finalizer_type(field_sel_type, tp)) { type_sym = (a_symbol_ptr)tp->source_corresp.assoc_info; dtor_or_finalizer_okay = TRUE; pos_sy_warning(is_finalizer ? ec_var_used_as_finalizer : ec_var_used_as_destructor, &locator_for_curr_id.source_position, sym); } /* if */ } /* if */ } /* if */ } /* if */ if (!dtor_or_finalizer_okay && !error_already_issued && decltype_tp == NULL) { /* None of the lookups match the field selection class. Determine whether any of them match a base class. If either of the previous lookups do match a base class, the symbol will still be set to the lookup result. Otherwise, the symbol (normal_sym and/or other_sym) will have been set to NULL. The "tp" pointer is used to point to the type of the destructor/finalizer found. This is used to determine whether two of the symbols actually point to the same type. The ambiguous flag is set if two or more of the symbols found point to different types. */ tp = NULL; clear_specific_symbol(locator_for_curr_id); if (qualifier_type != NULL && ((((gpp_mode && !clang_mode) || microsoft_mode) && (is_template_dependent_type(field_sel_type) || is_real_instantiation_context())) || is_template_dependent_type(qualifier_type))) { /* When processing something like X::~X, we can't rely on lookup to find an injected class name, so we go through the base class list. g++ and Microsoft accept a base class destructor in templates but not in non-template contexts. */ base_sym = dtor_matches_base_class(field_sel_type); } /* if */ if (base_sym != NULL) tp = type_symbol_type(base_sym); if (base_sym != NULL) type_sym = base_sym; /* Use the normal symbol if the base name lookup failed. */ if (normal_tp != NULL && (tp == NULL || !identical_types(normal_tp, tp))) { if (type_sym != NULL && tp != NULL) { ambiguous = TRUE; if (ambiguous_sym == NULL) ambiguous_sym = normal_sym; } /* if */ if (!ambiguous) { type_sym = normal_sym; tp = normal_tp; locator_for_curr_id = normal_locator; } /* if */ } /* if */ /* Use the "other" lookup symbol if both the base name lookup and normal lookups produced no result. */ if (other_sym != NULL && (other_tp == NULL || !identical_types(other_tp, tp))) { if (type_sym != NULL && other_tp != NULL) { ambiguous = TRUE; if (ambiguous_sym == NULL) ambiguous_sym = other_sym; } /* if */ if (!ambiguous) { type_sym = other_sym; locator_for_curr_id = other_locator; } /* if */ } /* if */ if (type_sym != NULL && is_type_symbol(type_sym)) { dtor_or_finalizer_okay = TRUE; } /* if */ } /* if */ if (error_already_issued) { /* Skip this section if an error was already issued. */ } else if (!dtor_or_finalizer_okay) { /* No match was found -- issue an error. */ pos_ty_error(is_finalizer ? ec_invalid_finalizer_name : ec_invalid_destructor_name, &locator_for_curr_id.source_position, field_sel_type); set_to_error_locator(locator_for_curr_id); } else if (ambiguous) { /* The destructor/finalizer reference is ambiguous. Although it is possible for three lookup results to be produced, only two are included in the diagnostic. */ pos_sy2_error(is_finalizer ? ec_ambiguous_finalizer : ec_ambiguous_destructor, &locator_for_curr_id.source_position, type_sym, ambiguous_sym); set_to_error_locator(locator_for_curr_id); } else if (type_sym == NULL) { /* We are using the original destructor/finalizer name for which we don't need to construct a new locator. */ } else { /* The lookup produced a unique symbol. */ a_source_position saved_position; if (locator_for_curr_id.is_semivisible_nested_type) { /* The symbol in the locator is a nested class that is not visible according to the ARM lookup rules but is returned in support of the nested class anachronism (ARM 18.3.5). Issue an anachronism diagnostic. */ sym_diagnostic(anachronism_error_severity, ec_nested_class_anachronism, locator_for_curr_id.specific_symbol); } /* if */ /* Create a locator that points to the type described by the symbol that was found. */ saved_position = locator_for_curr_id.source_position; if (orig_type_sym == NULL) orig_type_sym = type_sym; tp = type_symbol_type(type_sym); type_for_locator = tp; if (symbol_for(tp) != NULL) { /* In some error cases involving aliases the underlying type may not have a symbol. In such cases, use the symbol from the alias. */ type_sym = symbol_for(tp); } /* if */ make_locator_for_symbol(type_sym, &locator_for_curr_id); locator_for_curr_id.source_position = saved_position; if (decltype_tp != NULL) { /* For a dependent ~decltype(), preserve the type from the decltype. */ type_for_locator = decltype_tp; } else if (orig_type_sym != NULL) { /* Above, for X::~T (or X::!T) we used X as the type for the destructor (or finalizer). We want ~X (or !X) for the destructor (or finalizer) locator below, but we want to return T as the destructor (or finalizer) type. */ type_for_locator = type_symbol_type(orig_type_sym); if (!type_for_locator->source_corresp.is_class_member || !is_template_dependent_context() || !is_template_dependent_type(parent_class_of(type_for_locator))) { type_for_locator = skip_typerefs_not_dependent_decltypes(type_for_locator); } /* if */ } /* if */ } /* if */ } /* if */ /* Convert the locator to a locator for the destructor/finalizer. */ if (!is_error_locator(locator_for_curr_id)) { change_to_destructor_or_finalizer_locator( &locator_for_curr_id, is_finalizer); locator_for_curr_id.variant.destructor_type = type_for_locator; if (specific_sym != NULL) { locator_for_curr_id.specific_symbol = specific_sym; } /* if */ } /* if */ } /* if */ } /* get_destructor_or_finalizer_name */ static a_boolean scan_literal_operator_id(a_boolean is_class_member) /* The current token follows an "operator" token. If it is a string literal or a user-defined string literal (and user-defined literals are enabled) assume the whole construct is a literal-operator-id and return TRUE. For example: operator "" X or operator ""s12 If returning TRUE, validate the details of the literal-operator-id, make the current token a tok_identifier, and update locator_for_curr_id to reflect this construct (but the caller must update its position to that of the "operator" token). */ { a_boolean result = FALSE; if (user_defined_literals_enabled) { an_error_code err_code = ec_no_error; if (curr_token == tok_string_literal || curr_token == tok_ud_literal) { /* Guard against something like operator u8"xx"S */ if (const_for_curr_token.kind != (a_constant_repr_kind)ck_string || const_for_curr_token.variant.string.length != 1 || const_for_curr_token.variant.string.literal_kind != SCLK_ORDINARY_STRING_LITERAL) { /* If this is not a string literal of some sort, or the string is not empty, or the string has a prefix, the operator name is invalid. */ err_code = ec_invalid_literal_operator_id; } else if (curr_token == tok_string_literal) { /* Make sure the next token is a plain identifier and, if so, set up locator_for_curr_id for the operator name. */ (void)get_token(); if (curr_token != tok_identifier || locator_for_curr_id.is_qualified_name || locator_for_curr_id.is_operator_name || locator_for_curr_id.is_conversion_name || locator_for_curr_id.is_destructor_name || #if MICROSOFT_EXTENSIONS_ALLOWED locator_for_curr_id.is_finalizer_name || #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ locator_for_curr_id.is_udl_operator_name) { err_code = ec_invalid_literal_operator_id; if (curr_token != tok_identifier) { /* The suffix might be omitted and the next token might be useful in interpreting what comes next. */ unget_token(); } /* if */ } else { make_literal_opname_locator( locator_for_curr_id.symbol_header->identifier, locator_for_curr_id.symbol_header->identifier_length, &locator_for_curr_id, &pos_curr_token); } /* if */ } /* if */ if (err_code == ec_no_error) { if (is_class_member) { err_code = ec_udl_cannot_be_class_member; } else { result = TRUE; } /* if */ } /* if */ } /* if */ if (result) { curr_token = tok_identifier; locator_for_curr_id.is_udl_operator_name = TRUE; } else if (err_code != ec_no_error) { curr_token = tok_identifier; pos_error(err_code, &pos_curr_token); make_specific_symbol_error_locator(&locator_for_curr_id); result = TRUE; } /* if */ } /* if */ return result; } /* scan_literal_operator_id */ static void get_opname(a_boolean is_class_member, a_parent_class_or_namespace_ptr parent, a_type_ptr field_sel_type) /* The current token is the token "operator" at the start of an operator name, like "operator+". Scan the name and build a locator for the operator name in locator_for_curr_id. parent is a pointer to the class type or the namespace of the qualifier associated with the generalized identifier being scanned. is_class_member is TRUE if the parent points to a class, it is FALSE if parent points to a namespace or if there is no parent. If the parent pointer is not NULL then push a class or namespace reactivation scope before scanning the type name in a type conversion operator. If field_sel_type is not NULL, it is the type of the left operator of a field selection operation associated with this operator function reference. This routine is called only in C++ mode. */ { a_source_position start_position; a_token_kind token, second_token; an_opname_kind opname; start_position = pos_curr_token; /* Note the order of the tests here is important because scan_conversion_operator has to call get_token() for the "operator" token (after setting up the scope stack if needed). */ if (scan_conversion_operator(is_class_member, parent, field_sel_type)) { /* This is a conversion operator function -- "operator" followed by a type name. */ } else if (scan_literal_operator_id(is_class_member)) { /* A user-defined literal operator. */ locator_for_curr_id.source_position = start_position; } else { /* It must be an overloaded operator name (or an error). */ token = curr_token; opname = opname_kind_for_token[(int)token]; if (opname == (an_opname_kind)onk_function_call || opname == (an_opname_kind)onk_subscript) { /* Two-token operators: () and []. Peek ahead to the next token; if it's the right one, swallow it and leave the opname kind as is. Otherwise change the opname kind to onk_none so that an error will be issued. */ if (next_token() == (a_token_kind)((opname == (an_opname_kind)onk_function_call) ? tok_rparen : tok_rbracket)) { /* Advance to the second token. */ (void)get_token(); } else { /* Error case. */ opname = (an_opname_kind)onk_none; } /* if */ } else if (opname == (an_opname_kind)onk_new || opname == (an_opname_kind)onk_delete) { /* See if this is really new[] or delete[]. If so, adjust the opname. Note that second_token will be tok_error if the first token is not tok_lbracket. */ (void)next_two_tokens(tok_lbracket, &second_token); if (second_token == tok_rbracket) { if (!array_new_and_delete_enabled) { /* Issue an error if support for array new/delete is not enabled, but continue parsing as though it were. */ pos_error(ec_no_array_new_and_delete_support, &error_position); } /* if */ /* Advance past the two tokens. */ (void)get_token(); (void)get_token(); opname = (opname == (an_opname_kind)onk_new) ? (an_opname_kind)onk_array_new : (an_opname_kind)onk_array_delete; } /* if */ } /* if */ if (opname == (an_opname_kind)onk_none || opname == (an_opname_kind)onk_question) { /* Note that onk_question is not treated as an operator -- '?' is included in the opname kind table as a convenience in expression processing only. */ /* syntax_error is deliberately not called. */ pos_error(ec_exp_operator, &error_position); if (curr_token != tok_lparen && next_token() == tok_lparen) { /* Ignore the current token (whatever it might be -- e.g., '?') and make a fake identifier to represent the operator. */ } else { /* Either the operator token was omitted and we are at the '(' or else we're lost. Put back the current token and make a fake error identifier. */ unget_token(); } /* if */ make_specific_symbol_error_locator(&locator_for_curr_id); } else { /* Convert the locator to a locator for the operator. */ make_opname_locator(opname, &locator_for_curr_id, &start_position); } /* if */ curr_token = tok_identifier; pos_curr_token = error_position = start_position; } /* if */ } /* get_opname */ static a_boolean is_global_new_or_delete(void) /* Return TRUE if the current and next token form ":: new" or ":: delete". These look like qualified names but aren't. */ { a_boolean is_new_or_delete = FALSE; a_token_kind ntoken; if (curr_token == tok_colon_colon) { ntoken = next_token(); if (ntoken == tok_new || ntoken == tok_delete) { is_new_or_delete = TRUE; } /* if */ } /* if */ return is_new_or_delete; } /* is_global_new_or_delete */ static void flush_to_end_of_arg_list(void) /* Flush tokens in an argument list. */ { a_token_set_array_element save_comma_stop_token_count; a_token_set_array_element *comma_entry_ptr; /* Remove comma from the stop tokens set so that we can flush to the end of the argument list. */ comma_entry_ptr = &(curr_stop_token_stack_entry-> stop_tokens[(int)tok_comma]); save_comma_stop_token_count = *comma_entry_ptr; *comma_entry_ptr = 0; flush_tokens(); /* Restore comma as a stop token (if it was one). */ *comma_entry_ptr = save_comma_stop_token_count; } /* flush_to_end_of_arg_list */ a_template_ptr scan_template_template_argument( a_template_ptr param_template, a_source_position *err_pos, a_boolean is_default, a_boolean dependent_default) /* Scan the actual argument for a template template parameter. param_template is the template pointer of the corresponding template template parameter. err_pos is the position to be used to report any errors. is_default is TRUE when scanning the default argument of the template template parameter. dependent_default is TRUE if the template parameter list of the template parameter involves other template parameters, in which case checking the template parameter types of the default argument must be deferred until it is used. */ { a_symbol_ptr sym = NULL; a_boolean err = FALSE; a_template_ptr result = NULL; a_boolean any_errors = FALSE; a_template_symbol_supplement_ptr tssp; an_identifier_options_set options; an_identifier_lookup_mode ilm = ilm_normal; options = GID_TEMPLATE_ARGS_OPTIONAL; if ((gpp_mode && gnu_version >= 30400) || microsoft_mode) { /* The GNU and Microsoft compilers treat "T::template X" as a class template. Normally the "template" keyword there is only used to disambiguate a "<" following an identifier. */ options |= GID_CLASS_TEMPLATE_REQUIRED; ilm = ilm_template_template_arg; } /* if */ /* Microsoft allows a stray "typename" in this location. */ if (microsoft_mode && curr_token == tok_typename) (void)get_token(); if (is_generalized_identifier_start(options)) { sym = coalesce_and_lookup_generalized_identifier(options, ilm, &err); /* C++11 allows an injected class name to be used as a template template argument (core issue 1004). This is also allowed in g++ mode. If the symbol found is an injected template symbol, replace it with the template that it represents. */ if ((cpp11_mode || gpp_mode) && sym != NULL) { if (class_name_injection_enabled) { if (is_injected_template_symbol(sym)) { sym = class_template_for_injected_template_symbol(sym); } else if (is_prototype_instantiation_symbol(sym)) { a_class_symbol_supplement_ptr cssp; cssp = sym->variant.class_struct_union.extra_info; sym = cssp->class_template; } /* if */ } /* if */ } /* if */ /* Make sure the symbol found is accessible and unambiguous. */ check_ambiguity_and_verify_access(&locator_for_curr_id); if (err || (sym != NULL && sym->ambiguous)) { /* An error was already diagnosed by the identifier coalescing routines. */ any_errors = TRUE; } else if (sym == NULL) { any_errors = TRUE; pos_st_error(ec_undefined_identifier, err_pos, locator_for_curr_id.symbol_header->identifier); } else if (!is_class_template_symbol(sym)) { any_errors = TRUE; pos_sy_error(ec_sym_not_a_class_template, err_pos, sym); } /* if */ /* Bypass the identifier token. */ (void)get_token(); } else { /* Not an identifier. */ any_errors = TRUE; syntax_error(ec_exp_identifier); } /* if */ if (sym != NULL) { /* If this is a template template parameter, replace the template symbol with the one referred to by the parameter. */ sym = template_argument_if_template_template_param(sym); } /* if */ if (!any_errors && param_template != NULL) { /* Make sure this argument is compatible with the template template parameter. */ a_template_symbol_supplement_ptr tssp1; a_template_symbol_supplement_ptr tssp2; a_template_ptr arg_template; tssp1 = template_supplement_for_template(param_template); tssp2 = sym->variant.template_info; arg_template = tssp2->il_template_entry; if (tssp1->is_nonreal_member || tssp2->is_nonreal_member) { /* Nonreal members have no template parameter lists. The comparison will be done again later when a real member is available. */ } else if (dependent_default || (gpp_mode && is_default)) { /* The compatibility check for a default argument that depends on other template parameters must be delayed until the default is used. g++ doesn't check for a matching parameter list in a default template template argument until that argument is actually used. */ tssp1->variant.class_template.def_templ_templ_arg_check_delayed = TRUE; } else { if (!check_template_template_arg_compatibility(arg_template, param_template)) { pos_sy2_error(ec_not_compatible_with_templ_templ_param, err_pos, sym, symbol_for(param_template)); any_errors = TRUE; } /* if */ } /* if */ } /* if */ if (!any_errors) { /* The symbol is valid. Record the reference on the symbol. */ mark_referenced(sym, &locator_for_curr_id.source_position); } else { /* An error occurred while scanning the argument. Use a shared error class template as the result. */ sym = error_class_template(); } /* if */ tssp = sym->variant.template_info; result = tssp->il_template_entry; return result; } /* scan_template_template_argument */ a_template_arg_ptr scan_integer_pack(a_boolean record_operands) /* Scan a template argument of the form __integer_pack ( ) ... and return a representation of it to the caller. If the integer-constant is dependent, the representation is a single nontype template argument marked as being an "integer_pack" argument. Otherwise, it is a possibly-empty list of (integer) nontype template arguments of values 0, 1, ... N-1 where N is the value of the integer-constant. If record_operands is TRUE, record the values as an_arg_operand entries instead of a_constant entries. */ { a_constant_ptr bound = fs_constant((a_constant_repr_kind)ck_error); a_template_arg_ptr args = NULL; a_source_position bound_pos; a_boolean err = FALSE; check_assertion(curr_token == tok_integer_pack); (void)get_token(); add_stop_token(tok_ellipsis); add_stop_token(tok_rparen); if (curr_token == tok_lparen) { (void)get_token(); } else { pos_error(ec_exp_lparen, &pos_curr_token); } /* if */ bound_pos = pos_curr_token; scan_integral_constant_expression(bound); do_fs_constant_fixup(bound); (void)required_token(tok_rparen, ec_exp_rparen); (void)required_token(tok_ellipsis, ec_exp_ellipsis); remove_stop_token(tok_rparen); remove_stop_token(tok_ellipsis); if (is_error_constant(bound)) { err = TRUE; } else if (constant_is(bound, ck_template_param)) { /* The bound is dependent. Represent it using a tpck_integer_pack constant. */ a_constant_ptr cp = fs_constant((a_constant_repr_kind)ck_template_param); cp->type = bound->type; set_template_param_constant_kind( cp, (a_template_param_constant_kind)tpck_integer_pack); cp->variant.template_param.variant.bound = bound; args = alloc_template_arg((a_templ_arg_kind)tak_nontype); args->explicitly_specified = TRUE; args->is_integer_pack = TRUE; args->variant.constant = cp; } else if (constant_is(bound, ck_integer)) { /* The bound is non-dependent. Expand the construct immediately. */ a_boolean ovflo = FALSE; a_type_ptr bound_type = skip_typerefs(bound->type); a_host_large_integer bound_val = value_of_integer_constant(bound, &ovflo); if (bound_type->kind != (a_type_kind)tk_integer) { err = TRUE; pos_error(ec_exp_int_constant, &bound_pos); } else if (ovflo) { err = TRUE; pos_error(ec_integer_overflow, &bound_pos); } else if (bound_val < 0) { err = TRUE; pos_error(ec_negative_value, &bound_pos); } else { a_host_large_integer val; a_template_arg_ptr *p_arg = &args; an_integer_kind ikind = bound_type->variant.integer.int_kind; for (val = 0; val < bound_val; ++val) { a_constant_ptr cp = fs_constant((a_constant_repr_kind)ck_integer); *p_arg = alloc_template_arg((a_templ_arg_kind)tak_nontype); set_integer_constant(cp, val, ikind); (*p_arg)->variant.constant = cp; (*p_arg)->explicitly_specified = TRUE; p_arg = &(*p_arg)->next; } /* for */ } /* if */ } else { /* The type of the bound is a known, non-integral type: Issue an error. */ err = TRUE; pos_error(ec_exp_int_constant, &bound_pos); } /* if */ if (err) { check_assertion(is_at_least_one_error()); args = alloc_template_arg((a_templ_arg_kind)tak_nontype); args->variant.constant = bound; } /* if */ if (record_operands) { /* The caller requested that the template argument values be represented using an_arg_operand entries. */ a_template_arg_ptr arg; for (arg = args; arg != NULL; arg = arg->next) { arg->arg_operand = arg_operand_for_constant(arg->variant.constant); arg->variant.constant = NULL; } /* for */ } /* if */ return args; } /* scan_integer_pack */ static a_boolean complete_template_arg_pack_element( a_pack_expansion_stack_entry_ptr *p_pesep, a_template_arg_ptr arg_ptr, a_template_arg_ptr *p_first_pack, a_boolean use_builtin_pack_elem, a_boolean is_secondary_builtin_pack_elem) /* Complete processing of one template argument pack element and indicate whether more elements remain. For a C++26 pack-index argument not followed by "...", the pack context is abandoned and FALSE is returned to stop list iteration. Otherwise, the argument's pack_expansion_descr is finalized, *p_first_pack is set to arg_ptr if this is the first pack argument (when p_first_pack is non-NULL), and any active pack expansion is advanced. Return TRUE if more elements remain, or FALSE if this was the last element. use_builtin_pack_elem indicates whether builtin pack elements drive the iteration. is_secondary_builtin_pack_elem indicates this is a secondary element from such a pack. */ { a_pack_expansion_stack_entry_ptr pesep = *p_pesep; a_boolean stop_iteration = FALSE; if (!is_secondary_builtin_pack_elem) { a_boolean pack_index_context = pesep != NULL && pesep->expansion_descr->is_pack_index; if (pack_index_context && curr_token != tok_ellipsis) { /* A standalone pack-index argument (e.g., T...[I]) contributes a single argument unless it is followed by a list-level "...". */ arg_ptr->pack_expansion_descr = NULL; abandon_potential_pack_expansion_context(pesep); *p_pesep = NULL; stop_iteration = TRUE; } else { arg_ptr->pack_expansion_descr = end_potential_pack_expansion_context( pesep, /*is_declarator=*/FALSE); if (arg_ptr->pack_expansion_descr != NULL) arg_ptr->is_pack = TRUE; if (p_first_pack != NULL && arg_ptr->is_pack && *p_first_pack == NULL) { *p_first_pack = arg_ptr; } /* if */ } /* if */ } /* if */ return !stop_iteration && (use_builtin_pack_elem || advance_to_next_pack_element(pesep)); } /* complete_template_arg_pack_element */ a_template_arg_ptr scan_unknown_template_arg_list(a_boolean is_nonreal, a_boolean *p_err) /* Scan a template argument list associated with an unknown template parameter list. This is done when scanning the template arguments for an explicitly specified function template argument list, when the specific template whose arguments are being scanned may not be known yet. is_nonreal is FALSE to indicate that a concept or explicit function template argument list is being scanned. When is_nonreal is TRUE, the argument list being scanned is associated with a template that is a member of a proxy or nonreal class. This occurs as a result of constructs like T::A. In such cases there is no template parameter list to use as a basis for the template arguments that are scanned. For each argument, determine whether it is a type or nontype. This is done using the disambiguation routines. If p_err is non-NULL, set *p_err to TRUE if an error is detected. */ { a_template_arg_ptr arg_ptr; a_template_arg_ptr arg_list = NULL; a_template_arg_ptr last_arg = NULL; a_template_arg_ptr builtin_pack_elems = NULL; a_boolean is_type_param; a_templ_arg_kind arg_kind; a_constant_ptr constant; a_symbol_ptr sym; a_boolean saved_in_template_arg_list = scope_stack_top().in_template_arg_list; scope_stack_top().in_template_arg_list = TRUE; do { a_decl_sequence_number initial_inst_seq_num = class_instantiation_sequence_number; a_pack_expansion_stack_entry_ptr pesep; a_boolean any_args; /* If the current token is a ">" then exit the loop. This should only be possible on the first iteration if we have an empty argument list. If it occurs elsewhere, we must have a comma followed by the closing ">" of the template argument list. */ if (curr_token == tok_shift_right && right_shift_can_be_angle_brackets) { /* Check for the case where a "right shift" could be interpreted as two consecutive closing angle brackets. */ replace_right_shift_by_two_closing_angle_brackets(); } /* if */ any_args = begin_potential_pack_expansion_context_full( &pesep, (a_pack_expansion_descr_ptr*)NULL, /*is_lookahead=*/FALSE, /*allow_empty_list=*/FALSE, /*ignore_suppression=*/TRUE, /*claim_pack_index=*/FALSE); while (any_args) { a_boolean is_secondary_builtin_pack_elem = FALSE; a_token_kind next_tok; if (curr_token == tok_gt && builtin_pack_elems == NULL) { if (arg_list != NULL) { pos_error(ec_expected_template_arg, &error_position); } /* if */ abandon_potential_pack_expansion_context(pesep); break; } /* if */ add_stop_token(tok_comma); if (curr_token == tok_integer_pack) { /* An "__integer_pack(...)" construct can generate zero or more template arguments. */ a_template_arg_ptr args = scan_integer_pack(!is_nonreal); if (args == NULL) { /* No arguments were produced. */ (void)end_potential_pack_expansion_context( pesep, /*is_declarator=*/FALSE); any_args = advance_to_next_pack_element(pesep); remove_stop_token(tok_comma); continue; } /* if */ builtin_pack_elems = args->next; arg_ptr = args; arg_ptr->next = NULL; goto arg_produced; } else if (builtin_pack_elems != NULL) { arg_ptr = builtin_pack_elems; builtin_pack_elems = arg_ptr->next; arg_ptr->is_pack_element = FALSE; arg_ptr->next = NULL; is_secondary_builtin_pack_elem = TRUE; goto arg_produced; } /* if */ sym = NULL; /* Determine the kind of template argument. */ if (is_generalized_identifier_start(GID_TEMPLATE_ARGS_OPTIONAL | GID_IS_UNKNOWN_TEMPLATE_ARG | GID_IS_EXPR_CONTEXT)) { a_boolean err; sym = coalesce_and_lookup_generalized_identifier( GID_TEMPLATE_ARGS_OPTIONAL, ilm_normal, &err); } /* if */ if (sym != NULL && is_class_template_symbol(sym) && !locator_for_curr_id.is_template_id && (next_tok = next_token()) != tok_lparen && next_tok != tok_lbrace) { /* A template name that is not part of a template-id is usually a template template argument. However, starting with C++17, it might also introduce a functional-notation cast relying on class template argument deduction (CTAD). */ arg_kind = (a_templ_arg_kind)tak_template; } else { is_type_param = is_decl_not_expr(DFS_ABSTRACT_DECLARATOR_ALLOWED | DFS_SINGLE_TYPE_REQUIRED | DFS_IS_TEMPLATE_ARGUMENT); arg_kind = is_type_param ? (a_templ_arg_kind)tak_type : (a_templ_arg_kind)tak_nontype; } /* if */ arg_ptr = alloc_template_arg(arg_kind); if (is_type_templ_arg(arg_ptr)) { a_boolean is_injected_class_name; a_type_ptr argument_type; a_template_arg_ptr dedup_pack_args; argument_type = scan_template_type_argument(&is_injected_class_name, /*is_default_arg=*/FALSE); arg_ptr->variant.type = argument_type; argument_type = skip_lexical_typerefs(argument_type); if (!is_nonreal) { /* The "real" case will be for function template explicit arguments. If the type was specified using the injected class name, record that information. The injected class name can be used as both a type and a template template argument in certain cases. */ arg_ptr->type_is_injected_class_name = is_injected_class_name; } /* if */ if (symbol_for_builtin_dedup_pack != NULL && curr_token == tok_ellipsis && is_immediate_class_type(argument_type) && is_instance_of_class_template(argument_type, symbol_for_builtin_dedup_pack, &dedup_pack_args)) { /* Handle special __builtin_dedup_pack expansion. Skip the ellipsis. */ (void)get_token(); if (!is_template_dependent_type(argument_type)) { check_assertion(is_start_of_pack_expansion_templ_arg( dedup_pack_args)); dedup_pack_args = copy_template_type_arg_list_with_deduplication( dedup_pack_args->next); if (dedup_pack_args == NULL) { (void)end_potential_pack_expansion_context( pesep, /*is_declarator=*/FALSE); any_args = advance_to_next_pack_element(pesep); remove_stop_token(tok_comma); continue; } /* if */ *arg_ptr = *dedup_pack_args; arg_ptr->is_pack_element = FALSE; builtin_pack_elems = dedup_pack_args->next; dedup_pack_args->next = NULL; free_template_arg_list(dedup_pack_args); goto arg_produced; } else { arg_ptr->is_pack = TRUE; } /* if */ } /* if */ } else if (is_nontype_templ_arg(arg_ptr)) { if (is_nonreal) { /* Scan a constant. We can't know the type, so pass in a NULL type to indicate this. (Note: in the !is_nonreal case, we pass initial_inst_seq_num to scan_nontype_template_argument so it can decide whether the backing expression for the argument has semantic impacts or not. Here, in the is_nonreal case, we're in a template declaration and backing expressions are needed anyway, so we don't have to do anything with initial_inst_seq_num.) */ constant = fs_constant((a_constant_repr_kind)ck_error); scan_template_argument_constant_expression((a_type_ptr)NULL, constant); arg_ptr->variant.constant = constant; } else { /* Scan the expression, but retain it in the form of an operand so that the necessary conversions can be done later when the parameter type is known. (Note: we saved initial_inst_seq_num above, before the call to is_generalized_identifier_start, because that call could result in an instantiation. We pass that initial value to scan_nontype_template_argument, which compares it against the current value of class_instantiation_sequence_number after all its processing. The decision as to whether the argument caused an instantiation will thus include both the scan of the argument itself and that of any conversion function template instantiated to convert the argument to the parameter type.) */ arg_ptr->variant.constant = NULL; arg_ptr->arg_operand = scan_nontype_template_argument(initial_inst_seq_num); if (p_err != NULL && is_error_type(arg_ptr->arg_operand->operand.type)) { *p_err = TRUE; } /* if */ } /* if */ } else { /* A template template argument. */ a_template_ptr templ_ptr; check_assertion(is_template_templ_arg(arg_ptr)); templ_ptr = scan_template_template_argument( (a_template_ptr)NULL, &error_position, /*is_default=*/FALSE, /*dependent_default=*/FALSE); arg_ptr->variant.templ.ptr = templ_ptr; } /* if */ arg_produced: arg_ptr->explicitly_specified = TRUE; /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; any_args = complete_template_arg_pack_element( &pesep, arg_ptr, (a_template_arg_ptr*)NULL, builtin_pack_elems != NULL, is_secondary_builtin_pack_elem); remove_stop_token(tok_comma); } /* while */ } while (loop_token(tok_comma)); scope_stack_top().in_template_arg_list = saved_in_template_arg_list; return arg_list; } /* scan_unknown_template_arg_list */ static a_boolean assoc_template_param_is_pack(a_template_arg_ptr tap) /* If the value of tap is a template parameter type, constant, or template, locate the associated parameter and return TRUE if it is a parameter pack. Otherwise, return FALSE. */ { a_boolean result = FALSE; /* Loop through the scope stack and inspect any template instantiation scopes. */ if (depth_innermost_instantiation_scope != NO_SCOPE_DEPTH) { a_scope_stack_entry_ptr ssep; a_template_param_coordinate_ptr arg_tpcp; /* Call a routine that will return the coordinates if tap is an argument whose value is a template parameter. It will return NULL if the value of tap is not a template parameter. */ arg_tpcp = coordinates_of_template_arg(tap); if (arg_tpcp != NULL) { for (ssep = scope_stack_entry_for(depth_innermost_instantiation_scope); ssep != NULL; ssep = previous_scope_of(ssep)) { if (scope_is(ssep, sck_template_instantiation) && ssep->template_decl_info != NULL && ssep->template_decl_info->parameters != NULL) { /* If the nesting depth of the template parameters match the one we are looking for, go through the parameter list and find the corresponding parameter. */ a_template_param_ptr tpp; a_template_param_coordinate_ptr param_tpcp; tpp = ssep->template_decl_info->parameters; param_tpcp = coordinates_of_template_param(tpp); if (param_tpcp->depth == arg_tpcp->depth) { a_template_param_list_pos pos; for (pos = 1; pos < arg_tpcp->position && tpp != NULL; pos++) { tpp = tpp->next; } /* for */ result = tpp != NULL ? tpp->is_pack : FALSE; break; } /* if */ } /* if */ } /* for */ } /* if */ } /* if */ return result; } /* assoc_template_param_is_pack */ a_template_arg_ptr scan_template_argument_list( a_symbol_ptr template_sym, a_boolean type_constraint, a_boolean *any_errors, an_identifier_options_set options, long *first_defaulted_arg) /* Scan a comma separated list of arguments. The arguments can be type names, constant expressions, names or addresses of objects or functions with external linkage or of static class members, or names of templates. It is not necessary to distinguish among these cases because we can use the type of the formal parameter to make this selection. template_sym points to the template with which this argument list is associated. any_errors is set to TRUE if any errors are detected by this routine. Its value is unchanged if no errors are detected. first_defaulted_arg is set to the number (starting with 0) of the first argument that was taken from the parameter's default argument, or to -1 if all arguments were explicit. options is the set of options flags passed into the identifier scanning routines. If type_constraint is TRUE, the first parameter of the template is ignored and default arguments are not "filled in". For example, in: template concept C = true; template T> struct S {}; the is matched with U and no argument is generated for V. */ { a_template_param_ptr param_ptr = NULL; a_template_param_ptr orig_param_ptr; a_template_param_ptr param_list; a_symbol_ptr sym; a_symbol_ptr argument_template = NULL; a_symbol_ptr templ_sym_for_default = template_sym; a_type_ptr argument_type; a_constant_ptr constant; a_template_arg_ptr arg_ptr = NULL; a_template_arg_ptr arg_list = NULL; a_template_arg_ptr last_arg = NULL; a_template_arg_ptr builtin_pack_elems = NULL; a_templ_arg_kind arg_kind; a_template_decl_info_ptr decl_info; a_template_symbol_supplement_ptr tssp; a_boolean template_in_prototype_instantiation = FALSE; long arg_number; a_boolean in_pack = FALSE; a_boolean any_default_args = FALSE; a_template_arg_ptr first_pack = NULL; a_boolean saved_in_template_arg_list = scope_stack_top().in_template_arg_list; a_boolean too_many_args = FALSE; a_boolean any_args_in_list = FALSE; a_source_position arg1_pos, arg2_pos, arg3_pos; scope_stack_top().in_template_arg_list = TRUE; *first_defaulted_arg = -1L; tssp = template_sym->variant.template_info; decl_info = tssp->cache->decl_info; param_ptr = decl_info->parameters; /* Indicate that this is an error case if the template has any empty parameter list. */ if (param_ptr == NULL) { *any_errors = TRUE; } else if (type_constraint && !param_ptr->is_pack) { /* For a type constraint, ignore the first parameter (unless it is a pack). */ param_ptr = param_ptr->next; } /* if */ orig_param_ptr = param_ptr; /* Determine whether this is a template declared within a prototype instantiation. Such cases must be handled specially for rescanning purposes. */ if (template_sym->is_class_member && sym_parent_class(template_sym) ->variant.class_struct_union.is_prototype_instantiation && (options & GID_IS_CLASS_TEMPLATE_DECL) == 0) { template_in_prototype_instantiation = TRUE; } /* if */ if (symbol_is(template_sym, sk_class_template) && tssp->variant.class_template.template_template_param) { a_template_ptr subst_param_templ; a_template_symbol_supplement_ptr subst_param_tssp; /* If this is a template template parameter, see if there is a substituted version of the parameter templates. This comes up in cases where the template template parameter has template parameters that depends on other template parameters. In such cases, get the parameter list via the IL template entry for the substituted parameter template. */ subst_param_templ = template_sym->variant.template_info-> variant.class_template.substituted_param_template; if (subst_param_templ != NULL) { /* There will be no substituted parameter template for non-dependent template template parameters or for prototype instantiations. */ a_symbol_ptr subst_param_sym = symbol_for(subst_param_templ); subst_param_tssp = template_supplement_for_symbol(subst_param_sym); check_assertion(subst_param_tssp != NULL); /* Note that orig_param_ptr points to the parameter list of the template template parameter while param_ptr points to the template parameter list of the template template argument. The default argument information used below is based on the template parameter list of the template template parameter (i.e., orig_param_ptr). */ param_ptr = subst_param_tssp->cache->decl_info->parameters; } else { /* For non-dependent argument lists, use the one from the argument template for param_ptr (except for error cases). This is needed when the argument template is variadic. */ argument_template = tssp->variant.class_template.argument_template; subst_param_tssp = argument_template->variant.template_info; /* We can get here in an instantiation of an alias template with a template template argument that is a nonreal template. In such cases, don't attempt to use the parameter list from the argument template. */ if (!subst_param_tssp->is_nonreal_member && !subst_param_tssp->is_error) { param_ptr = subst_param_tssp->cache->decl_info->parameters; } /* if */ } /* if */ } /* if */ param_list = param_ptr; if (internal_templates_enabled) { /* Record the starting positions of the argument(s) in case they are needed for diagnostics later on. */ arg1_pos = pos_curr_token; arg2_pos = null_source_position; arg3_pos = null_source_position; } /* if */ arg_number = 0; do { a_source_position arg_pos; a_pack_expansion_stack_entry_ptr pesep; a_boolean any_args; /* For a template parameter declared "T... n", if T is a template parameter pack of an enclosing class template, and the enclosing class template is being instantiated with an empty T, we need to include a placeholder for the empty pack. */ while (param_ptr != NULL && param_ptr->is_empty_pack) { /* Create a start of parameter pack placeholder. */ arg_ptr = alloc_template_arg((a_templ_arg_kind)tak_start_of_pack_expansion); /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; param_ptr = param_ptr->next; if (orig_param_ptr != NULL) orig_param_ptr = orig_param_ptr->next; } /* while */ any_args = begin_potential_pack_expansion_context_full( &pesep, (a_pack_expansion_descr_ptr*)NULL, /*is_lookahead=*/FALSE, /*allow_empty_list=*/FALSE, /*ignore_suppression=*/TRUE, /*claim_pack_index=*/FALSE); next_builtin_pack_element: /* If we have run out of parameters but there are more arguments, exit the loop. This test is done here so that a construct like "A" will be accepted when args is an empty pack. */ if ((param_ptr == NULL || orig_param_ptr == NULL) && any_args) { too_many_args = TRUE; if (curr_token == tok_gt && any_args_in_list) { pos_error(ec_expected_template_arg, &error_position); } /* if */ abandon_potential_pack_expansion_context(pesep); break; } /* if */ while (any_args) { a_boolean is_secondary_builtin_pack_elem = FALSE; a_boolean stop_matching_params = FALSE; any_args_in_list = TRUE; if (!in_pack && param_ptr != NULL && param_ptr->is_pack) { /* Create a start of parameter pack placeholder. */ arg_ptr = alloc_template_arg((a_templ_arg_kind)tak_start_of_pack_expansion); /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; in_pack = !param_ptr->is_pack_element; } /* if */ if (builtin_pack_elems == NULL) { if (curr_token == tok_shift_right && right_shift_can_be_angle_brackets) { /* Check for the case where a "right shift" could be interpreted as two consecutive closing angle brackets. */ replace_right_shift_by_two_closing_angle_brackets(); } /* if */ /* If the current token is a ">", and this is the first argument, then exit the loop (an empty argument list). */ if (curr_token == tok_gt && (arg_list == NULL || (in_pack && arg_ptr == last_arg))) { abandon_potential_pack_expansion_context(pesep); break; } /* if */ arg_pos = pos_curr_token; } /* if */ if (internal_templates_enabled) { if (arg_number == 1) { arg2_pos = arg_pos; } else if (arg_number == 2) { arg3_pos = arg_pos; } /* if */ } /* if */ /* If the template parameter list is empty, exit the loop. This only occurs in error cases. */ if (param_ptr == NULL) { abandon_potential_pack_expansion_context(pesep); break; } /* if */ add_stop_token(tok_comma); sym = param_ptr->param_symbol; /* Determine the template argument kind for this parameter. */ arg_kind = templ_arg_kind_for_symbol_kind(sym->kind); arg_ptr = alloc_template_arg(arg_kind); arg_ptr->explicitly_specified = TRUE; if (is_type_templ_arg(arg_ptr)) { a_template_arg_ptr dedup_pack_args; a_boolean is_unnamed, is_local, is_vla, is_generic, is_invalid = FALSE; if (builtin_pack_elems != NULL) { if (is_type_templ_arg(builtin_pack_elems)) { /* Get the next pending type element from __builtin_dedup_pack. */ argument_type = builtin_pack_elems->variant.type; builtin_pack_elems = builtin_pack_elems->next; is_secondary_builtin_pack_elem = TRUE; } else { /* There are pending arguments from an __integer_pack(N) construct. Those cannot match a type parameter. */ pos_sy_error(ec_integer_pack_element_for_type, &arg_pos, sym); builtin_pack_elems = NULL; argument_type = error_type(); } /* if */ } else { argument_type = scan_template_type_argument( (a_boolean*)NULL, /*is_default_arg=*/FALSE); } /* if */ /* In standard C++98/C++03, template type arguments must have linkage, and therefore cannot be based on local or unnamed classes/enums. In Microsoft and C++11 modes, local class types are acceptable even though they have no linkage. */ if (is_invalid_template_arg_type( argument_type, &is_unnamed, &is_local, &is_vla, &is_generic)) { is_invalid = TRUE; if (is_local) { pos_error(ec_local_type_in_template_arg, &arg_pos); } else if (is_unnamed) { pos_error(ec_unnamed_type_in_template_arg, &arg_pos); } else if (is_vla) { pos_error(ec_vla_type_in_template_arg, &arg_pos); #if MICROSOFT_EXTENSIONS_ALLOWED } else if (is_generic) { /* A generic parameter cannot be used as a template argument. C++/CLI arrays, pin_ptrs and interior_ptrs are not really templates, so allow generic parameters for them. */ if (!is_cli_cx_pseudo_template(template_sym)) { pos_error(ec_generic_type_in_template_arg, &arg_pos); } else { is_invalid = FALSE; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { /* A named type nested in an unnamed class has no linkage in C++98/C++03. Use a different diagnostic for such cases. */ pos_error(ec_type_with_no_linkage_in_template_arg, &arg_pos); } /* if */ if (is_invalid) argument_type = error_type(); } /* if */ arg_ptr->variant.type = argument_type; argument_type = skip_lexical_typerefs(argument_type); if (symbol_for_builtin_dedup_pack != NULL && curr_token == tok_ellipsis && is_immediate_class_type(argument_type) && is_instance_of_class_template(argument_type, symbol_for_builtin_dedup_pack, &dedup_pack_args)) { /* Handle special __builtin_dedup_pack expansion. Skip the ellipsis. */ (void)get_token(); if (!is_template_dependent_type(argument_type)) { check_assertion(is_start_of_pack_expansion_templ_arg( dedup_pack_args)); dedup_pack_args = copy_template_type_arg_list_with_deduplication( dedup_pack_args->next); if (dedup_pack_args == NULL) { (void)end_potential_pack_expansion_context( pesep, /*is_declarator=*/FALSE); any_args = advance_to_next_pack_element(pesep); remove_stop_token(tok_comma); continue; } /* if */ *arg_ptr = *dedup_pack_args; arg_ptr->is_pack_element = FALSE; builtin_pack_elems = dedup_pack_args->next; dedup_pack_args->next = NULL; free_template_arg_list(dedup_pack_args); is_secondary_builtin_pack_elem = TRUE; } else { arg_ptr->is_pack = TRUE; } /* if */ } /* if */ } else if (is_nontype_templ_arg(arg_ptr)) { a_type_ptr constant_type = sym->variant.constant->type; /* If the type of a constant involves a template parameter type, rescan the declaration of the parameter type to get the type to be used in this argument list. */ if (param_ptr->variant.constant.type_involves_template_param && !template_in_prototype_instantiation) { a_template_arg_ptr templ_arg_list; if (type_constraint) { /* For a type constraint, add the implicit first template argument. */ check_assertion(decl_info->parameters->param_symbol->kind == sk_type); templ_arg_list = alloc_template_arg(tak_type); templ_arg_list->variant.type = decl_info->parameters->variant.type; templ_arg_list->next = arg_list; } else { templ_arg_list = arg_list; } /* if */ constant_type = rescan_template_constant_parameter( template_sym, sym, param_ptr, templ_arg_list, /*do_default_arg=*/FALSE, (a_constant_ptr*)NULL); } /* if */ if (curr_token == tok_integer_pack) { /* An "__integer_pack(...)" construct can generate zero or more template arguments. */ a_template_arg_ptr args; args = scan_integer_pack(/*record_operands=*/FALSE); if (args == NULL) { /* No arguments were produced. */ (void)end_potential_pack_expansion_context( pesep, /*is_declarator=*/FALSE); any_args = advance_to_next_pack_element(pesep); remove_stop_token(tok_comma); continue; } /* if */ builtin_pack_elems = args->next; arg_ptr = args; arg_ptr->next = NULL; if (constant_is(arg_ptr->variant.constant, ck_integer)) { /* Adjust the type of the constant to that expected by the parameter. */ a_boolean did_not_fold; type_change_constant(arg_ptr->variant.constant, constant_type, /*is_implicit_cast=*/TRUE, /*maintain_expression=*/FALSE, &did_not_fold, &arg_pos); } /* if */ } else if (builtin_pack_elems != NULL) { if (is_type_templ_arg(builtin_pack_elems)) { /* There are pending arguments from a __builtin_dedup_pack construct. Those cannot match a non-type template parameter. */ pos_sy_error(ec_type_pack_element_cannot_match, &arg_pos, sym); builtin_pack_elems = NULL; constant = fs_constant(ck_error); set_error_constant(constant); arg_ptr->variant.constant = constant; } else { arg_ptr = builtin_pack_elems; builtin_pack_elems = arg_ptr->next; arg_ptr->next = NULL; is_secondary_builtin_pack_elem = TRUE; if (constant_is(arg_ptr->variant.constant, ck_integer)) { /* Adjust the type of the constant to that expected by the parameter. */ a_boolean did_not_fold; type_change_constant(arg_ptr->variant.constant, constant_type, /*is_implicit_cast=*/TRUE, /*maintain_expression=*/FALSE, &did_not_fold, &arg_pos); } /* if */ } /* if */ } else { constant = fs_constant((a_constant_repr_kind)ck_error); scan_template_argument_constant_expression(constant_type, constant, arg_list, param_list); /* Make sure the constant does not use a local or nonexternal variable, etc. */ if (nontype_templ_arg_constant_involves_invalid_linkage(constant)) { pos_error(ec_nonexternal_entity_in_template_arg, &arg_pos); set_error_constant(constant); } /* if */ arg_ptr->variant.constant = constant; } /* if */ } else { /* A template template argument. */ a_template_ptr templ; a_template_ptr param_template; check_assertion_str(sym->kind == (a_symbol_kind)sk_class_template, "scan_template_argument_list: template expected"); param_template = param_ptr->variant.templ->il_template_entry; if (param_ptr->variant.templ-> variant.class_template.involves_template_param && !template_in_prototype_instantiation) { /* The template template parameter depends on another template parameter, for example: template class X> ... Rescan the template template parameter declaration to create a new parameter template. */ param_template = rescan_template_template_parameter( template_sym, param_ptr, arg_list); arg_ptr->variant.templ.substituted_param_template = param_template; } /* if */ if (builtin_pack_elems != NULL) { /* There are pending builtin pack arguments that cannot match a template template parameter. */ a_symbol_ptr error_sym; pos_sy_error(is_type_templ_arg(builtin_pack_elems) ? ec_type_pack_element_cannot_match : ec_integer_pack_element_for_template, &arg_pos, sym); builtin_pack_elems = NULL; error_sym = error_class_template(); templ = error_sym->variant.template_info->il_template_entry; } else { templ = scan_template_template_argument(param_template, &arg_pos, /*is_default=*/FALSE, /*dependent_default=*/FALSE); } /* if */ arg_ptr->variant.templ.ptr = templ; } /* if */ /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; remove_stop_token(tok_comma); ++arg_number; any_args = complete_template_arg_pack_element( &pesep, arg_ptr, &first_pack, builtin_pack_elems != NULL && param_ptr->is_pack, is_secondary_builtin_pack_elem); /* Record whether, for a pack expansion in an instantiation, the argument was part of a pack expansion. This is needed for C++-generating back end cases where pack_expansion_descr is NULL in some alias-in-template-decl cases. */ arg_ptr->has_pack_ellipsis = pesep != NULL && pesep->instantiation_descr != NULL && depth_innermost_instantiation_scope != NO_SCOPE_DEPTH && scope_stack[depth_innermost_instantiation_scope]. alias_in_template_decl && is_type_templ_arg(arg_ptr) && (!type_is(arg_ptr->variant.type, tk_template_param) || arg_ptr->variant.type-> variant.template_param.is_pack) && assoc_template_param_is_pack(arg_ptr); if (param_ptr->is_pack || param_ptr->is_pack_element) { /* Record that this argument was associated with a pack. */ arg_ptr->is_pack_element = TRUE; } /* if */ if (arg_ptr->is_pack && (!param_ptr->is_pack || (orig_param_ptr != NULL && !orig_param_ptr->is_pack))) { /* A pack expansion is being used as an argument to a non-pack parameter. */ a_boolean alias_pack_for_nonpack = FALSE; if (!param_ptr->is_pack) { a_symbol_ptr named_sym = argument_template != NULL ? argument_template : template_sym; a_template_ptr underlying_templ; a_symbol_ptr underlying_sym; underlying_templ = skip_simple_alias_templates( template_supplement_for_symbol(named_sym)->il_template_entry); underlying_sym = symbol_for(underlying_templ); if (symbol_is(underlying_sym, sk_class_template) && is_alias_template_symbol(underlying_sym) && is_prototype_instantiation_or_expansion_context()) { /* The direction of Core issue 1430 (still open) is that a pack expansion cannot be an argument for a non-pack parameter of an alias template. */ a_source_position *err_pos = &error_position; if (arg_ptr->pack_expansion_descr != NULL && arg_ptr->pack_expansion_descr->ellipsis_position.seq != 0) { err_pos = &arg_ptr->pack_expansion_descr->ellipsis_position; } /* if */ pos_sy2_error(ec_pack_expansion_for_non_pack_alias_param, err_pos, param_ptr->param_symbol, named_sym); *any_errors = TRUE; alias_pack_for_nonpack = TRUE; } /* if */ } /* if */ if (alias_pack_for_nonpack || curr_token == tok_comma) { /* Stop matching further parameters. If more arguments remain, scan them as an unknown argument list. */ if (curr_token == tok_comma) { a_template_arg_ptr other_args; (void)get_token(); other_args = scan_unknown_template_arg_list(/*is_nonreal=*/TRUE, (a_boolean*)NULL); last_arg->next = other_args; while (last_arg->next != NULL) last_arg = last_arg->next; } /* if */ param_ptr = NULL; if (alias_pack_for_nonpack) orig_param_ptr = NULL; stop_matching_params = TRUE; } /* if */ } /* if */ if (!stop_matching_params) { /* Advance to the next template parameter, unless the current parameter is a pack. */ if (!param_ptr->is_pack || param_ptr->is_pack_element) { /* Don't advance to the next parameter if this is a pack. */ param_ptr = param_ptr->next; } /* if */ if (orig_param_ptr != NULL && (!orig_param_ptr->is_pack || orig_param_ptr->is_pack_element)) { /* Don't advance to the next parameter if this is a pack. */ orig_param_ptr = orig_param_ptr->next; } /* if */ } /* if */ } /* while */ if (builtin_pack_elems != NULL) { /* Iterate over remaining builtin pack elements, unless we ran out of parameters. */ if (param_ptr == NULL) { too_many_args = TRUE; break; } else { any_args = TRUE; goto next_builtin_pack_element; } /* if */ } /* if */ } while (loop_token(tok_comma)); /* If we were processing arguments associated with a parameter pack, advance past the parameter pack now that we have reached the end of the explicitly supplied arguments. */ if (param_ptr != NULL && in_pack) { param_ptr = param_ptr->next; if (orig_param_ptr != NULL) orig_param_ptr = orig_param_ptr->next; } /* if */ /* All arguments should have been processed and the current token should be the closing angle bracket. */ if ((generalized_template_template_matching || (gpp_mode && gnu_version < 40200)) && orig_param_ptr == NULL && param_ptr != NULL) { /* With C++17-style template template argument matching, and in g++ mode, a template with more parameters can be passed to a template template parameter with fewer parameters if the argument template has default arguments for the additional parameters. In that case, we will have exhausted the list of original parameters. Use the remaining elements from the argument template. */ check_assertion(tssp->variant.class_template.template_template_param && argument_template != NULL); orig_param_ptr = param_ptr; templ_sym_for_default = argument_template; } /* if */ for (; param_ptr != NULL && orig_param_ptr != NULL; param_ptr = param_ptr->next, orig_param_ptr = orig_param_ptr->is_pack ? orig_param_ptr : orig_param_ptr->next) { a_template_param_ptr param_for_default = orig_param_ptr; if (orig_param_ptr->is_pack && param_ptr->has_default_arg && argument_template != NULL) { /* If the template template parameter has an argument that is a pack, use the default argument, if any, from the template parameter list of the template template argument. */ param_for_default = param_ptr; templ_sym_for_default = argument_template; } /* if */ /* There are still entries on the formal parameters list -- see if the remaining parameters have default values or are parameter packs. */ if (param_ptr->is_pack) { /* A parameter pack with no associated arguments. Add a start of pack expansion placeholder. */ arg_ptr = alloc_template_arg((a_templ_arg_kind)tak_start_of_pack_expansion); /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; } else if (param_for_default->has_default_arg && first_pack == NULL) { /* The template has parameters with default values. Fill in the remainder of the argument list with the defaults. Don't add default arguments at this point if one of the arguments was a pack expansion. */ if (!any_default_args) { *first_defaulted_arg = arg_number; any_default_args = TRUE; } /* if */ if (type_constraint) { /* For type constraints do not attempt to instantiate a default argument at this time since it might refer to template arguments that have not yet been determined. */ break; } /* if */ if (param_for_default->def_arg_has_not_been_scanned) { /* In some cases the default will not have been scanned when the template was first declared. In such cases, scan it on its first use. */ delayed_scan_of_template_param_default_arg(templ_sym_for_default, param_for_default, param_list); } /* if */ sym = param_ptr->param_symbol; /* Determine the template argument kind for this parameter. */ arg_kind = templ_arg_kind_for_symbol_kind(sym->kind); arg_ptr = alloc_template_arg(arg_kind); if (is_type_templ_arg(arg_ptr)) { if (param_for_default->has_default_arg) { /* A type parameter with a default value. The default can be either a type or a token cache that needs to be scanned. */ arg_ptr->variant.type = rescan_template_type_default_arg( templ_sym_for_default, param_for_default, arg_list); } else { /* A type parameter with no default argument. This occurs only in error cases. Use an error type. */ arg_ptr->variant.type = error_type(); } /* if */ } else if (is_template_templ_arg(arg_ptr)) { /* A template template argument. */ if (param_for_default->has_default_arg) { /* A template parameter with a default value. */ a_template_symbol_supplement_ptr tssp1, tssp2; a_template_ptr param_template; param_template = param_ptr->variant.templ->il_template_entry; if (param_ptr->variant.templ-> variant.class_template.involves_template_param && !template_in_prototype_instantiation) { /* The template template parameter depends on another template parameter, for example: template class X> ... Rescan the template template parameter declaration to create a new parameter template. */ param_template = rescan_template_template_parameter( template_sym, param_ptr, arg_list); arg_ptr->variant.templ.substituted_param_template = param_template; } /* if */ arg_ptr->variant.templ.ptr = rescan_template_template_default_arg(templ_sym_for_default, param_for_default, arg_list); tssp1 = param_for_default->variant.templ; if (tssp1->variant.class_template .def_templ_templ_arg_check_delayed) { /* The default template template argument has been scanned, but its parameter list was not checked against the parameter list of the template template parameter until the first use of that parameter. Perform the check now and issue an error if appropriate. (This is a GNU compatibility feature.) */ a_symbol_ptr arg_sym = symbol_for(arg_ptr->variant.templ.ptr); a_symbol_ptr param_sym = symbol_for(tssp1->il_template_entry); a_template_ptr arg_template; /* Update tssp1 to refer to the (possibly) rescanned template parameter created above. */ tssp1 = template_supplement_for_template(param_template); tssp2 = arg_sym->variant.template_info; arg_template = tssp2->il_template_entry; if (!tssp2->is_nonreal_member && !check_template_template_arg_compatibility(arg_template, param_template)) { pos_sy2_error(ec_not_compatible_with_templ_templ_param, &pos_curr_token, arg_sym, param_sym); tssp1->variant.class_template.def_templ_templ_arg_check_delayed = FALSE; } /* if */ } /* if */ } else { /* A template template parameter with no default argument. This occurs only in error cases. Use an error template. */ a_symbol_ptr error_sym; error_sym = error_class_template(); arg_ptr->variant.templ.ptr = error_sym->variant.template_info->il_template_entry; } /* if */ } else { /* A nontype argument. */ check_assertion(is_nontype_templ_arg(arg_ptr)); if (param_for_default->has_default_arg) { /* A constant parameter. The default value can be either a constant value or a token cache that needs to be scanned. Call a routine that will rescan the type declaration and/or default argument expression. */ (void)rescan_template_constant_parameter( templ_sym_for_default, sym, param_for_default, arg_list, /*do_default_arg=*/TRUE, &constant); arg_ptr->variant.constant = constant; } else { /* A nontype constant without a default argument. This also only occurs in error cases. Use an error constant. */ constant = alloc_error_constant(); arg_ptr->variant.constant = constant; } /* if */ } /* if */ /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; } else if (first_pack != NULL) { /* There were too few arguments, but the list contained a pack expansion. Treat this as okay for now. */ } else { /* The next parameter doesn't have a default value. Issue an error if no default arguments have been encountered. If we have already seen a default argument, an error will have been issued on the template declaration, except in Microsoft mode where the error is downgraded to a warning. */ a_boolean mismatch_error = TRUE; if (!any_default_args || microsoft_mode) { if (microsoft_mode && (caching_tokens || in_ms_nonreal_class_instantiation())) { /* Silently ignore this while caching tokens or performing Microsoft "nonreal instantiations". If the token cache is used, an error will be issued then. */ mismatch_error = FALSE; } else { sym_error(ec_too_few_template_args, templ_sym_for_default); } /* if */ /* Suppress any subsequent errors. */ any_default_args = TRUE; } /* if */ if (mismatch_error) *any_errors = TRUE; break; } /* if */ } /* for */ if (too_many_args && (curr_token != tok_gt || builtin_pack_elems != NULL)) { /* All of the formal parameters have been accounted for and there are more actuals -- too many arguments were supplied. */ pos_sy_error(ec_too_many_template_args, &pos_curr_token, template_sym); flush_to_end_of_arg_list(); *any_errors = TRUE; } /* if */ if (internal_templates_enabled && !*any_errors) { /* If internal templates are enabled, check that their arguments meet the requirements of the language. */ (void)check_internal_template_instantiation(template_sym, arg_list, &arg1_pos, &arg2_pos, &arg3_pos); } /* if */ scope_stack_top().in_template_arg_list = saved_in_template_arg_list; return arg_list; } /* scan_template_argument_list */ #if MICROSOFT_EXTENSIONS_ALLOWED static a_boolean is_valid_generic_argument(a_type_ptr argument_type) /* Check whether argument_type is a type that can be used as a generic type argument. Return TRUE if it is valid, FALSE otherwise. */ { a_boolean result = FALSE; if (is_value_class_or_fundamental_type(argument_type) || is_scoped_enum_type(argument_type)) { /* A value class, including the fundamental types that have a corresponding value class, can be used as a generic argument. System::RuntimeArgumentHandle cannot be used as a generic argument because it points into the CIL evaluation stack. Void cannot be used as a generic argument. Enum types are allowed despite being omitted from the list of allowed types in ECMA-372 31.2.2 ("Type Arguments"). */ a_type_ptr rah_type; if (cppcx_enabled) { /* C++/CX does not have a System::RuntimeArgumentHandle counterpart. */ rah_type = NULL; } else { rah_type = cli_class_type_for(csk_system_runtime_argument_handle); } /* if */ result = !(rah_type != NULL && same_entities(skip_typerefs(argument_type), rah_type)) && !is_void_type(argument_type); } else if (is_template_param_type(argument_type) || is_cli_generic_definition_argument_type(argument_type)) { /* A generic parameter is allowed. The ECMA standard does not say that template parameters are allowed, but they are. */ result = TRUE; } else if (is_handle_type(argument_type)) { a_type_ptr tp = type_pointed_to(argument_type); tp = skip_typerefs(tp); if (is_cli_ref_or_interface_class_type(tp)) { /* A handle to a ref class or interface class is allowed. */ result = TRUE; } else if (is_cli_array_type(tp)) { /* A handle to a C++/CLI array is allowed. */ result = TRUE; } /* if */ } /* if */ return result; } /* is_valid_generic_argument */ static a_template_arg_ptr scan_generic_argument_list(a_symbol_ptr generic_sym, a_boolean *any_errors) /* Scan a comma separated list of generic arguments. generic_sym points to the template with which this argument list is associated. If it is a generic with more than one arity, generic_sym points to the symbol found by lookup, which contains information about the other arity generics. *any_errors is set to TRUE if any errors are detected by this routine. Its value is unchanged if no errors are detected. */ { a_template_arg_ptr arg_ptr; a_template_arg_ptr arg_list = NULL; a_template_arg_ptr last_arg = NULL; uint32_t arity = 0; a_template_symbol_supplement_ptr tssp; a_boolean saved_in_template_arg_list = scope_stack_top().in_template_arg_list; scope_stack_top().in_template_arg_list = TRUE; tssp = generic_sym->variant.template_info; /* Because there can be a set of generics with the same name but different numbers of parameters, scan all of the arguments that are present. The particular generic that is being used will be determined by the caller. */ do { a_type_ptr argument_type; a_source_position start_pos; if (curr_token == tok_shift_right && right_shift_can_be_angle_brackets) { /* Check for the case where a "right shift" could be interpreted as two consecutive closing angle brackets. */ replace_right_shift_by_two_closing_angle_brackets(); } /* if */ /* If the current token is a ">", and this is the first argument, then exit the loop (an empty argument list). */ if (curr_token == tok_gt && arg_list == NULL) { break; } /* if */ if (arity == tssp->variant.class_template.max_arity) { /* There are more arguments than the maximum allowed. */ pos_sy_error(ec_too_many_generic_args, &pos_curr_token, generic_sym); *any_errors = TRUE; } /* if */ arity++; add_stop_token(tok_comma); start_pos = pos_curr_token; argument_type = scan_template_type_argument((a_boolean*)NULL, /*is_default_arg=*/FALSE); /* Make sure that the argument is a type that can be used as a generic argument. Constraint checking will be done later after we have identified the arity of this reference. */ if (!is_valid_generic_argument(argument_type)) { if (!is_or_contains_error_type(argument_type)) { /* Don't issue an error on something that contains an error type. */ pos_ty_error(ec_invalid_generic_arg, &start_pos, argument_type); } else { expect_error(); } /* if */ *any_errors = TRUE; } /* if */ arg_ptr = alloc_template_arg((a_templ_arg_kind)tak_type); arg_ptr->variant.type = argument_type; arg_ptr->explicitly_specified = TRUE; /* Link this entry on to the argument list. */ if (arg_list == NULL) arg_list = arg_ptr; if (last_arg != NULL) last_arg->next = arg_ptr; last_arg = arg_ptr; remove_stop_token(tok_comma); } while (loop_token(tok_comma)); if (arity < tssp->variant.class_template.min_arity) { /* There are fewer arguments than the minimum allowed. */ pos_sy_error(ec_too_few_generic_args, &pos_curr_token, generic_sym); *any_errors = TRUE; } /* if */ scope_stack_top().in_template_arg_list = saved_in_template_arg_list; return arg_list; } /* scan_generic_argument_list */ static void select_generic_based_on_arity(a_symbol_ptr *generic_sym, a_template_arg_ptr arg_list, a_source_position *arg_start_pos, a_boolean *any_errors) /* There can be multiple versions of a C++/CLI generic each with a different number of parameters. If there is only one version, see if the argument list has the correct number of parameters. If there are multiple versions, see if any of them match the argument list. If the generic that is matched is not the one referred to by *generic_sym, update *generic_sym to refer to the appropriate generic. *arg_start_pos is the position of the start of the first generic argument. *any_errors is set to TRUE if any errors are detected by this routine. Its value is unchanged if no errors are detected. */ { a_template_symbol_supplement_ptr tssp; uint32_t arity = 0; a_template_arg_ptr tap; /* Count the number of arguments. */ for (tap = arg_list; tap != NULL; tap = tap->next) arity++; tssp = (*generic_sym)->variant.template_info; if (arity == tssp->variant.class_template.min_arity && arity == tssp->variant.class_template.max_arity) { /* There is only one arity and this matches. */ } else { a_symbol_list_entry_ptr slep; for (slep = tssp->variant.class_template.generic_arity_list; slep != NULL; slep = slep->next) { if (arity_for_generic(slep->symbol) == arity) { /* We found a match. */ *generic_sym = slep->symbol; break; } /* if */ } /* for */ if (slep == NULL) { /* No match was found. */ pos_sy_error(ec_no_matching_arity, arg_start_pos, *generic_sym); *any_errors = TRUE; } /* if */ } /* if */ } /* select_generic_based_on_arity */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ static void f_check_closing_angle_bracket(a_boolean *any_errors) /* This routine is called when the end of a template argument list is reached but the current token is not ">". The main purpose of this routine is to treat a ">>" as two consecutive ">" tokens. This may be a matter of normal behavior in some modes (e.g., C++11), or for improved error recovery in other modes. */ { if (curr_token == tok_shift_right && (right_shift_can_be_angle_brackets || (scope_stack[depth_scope_stack].pending_templ_arg_lists > 1 && !*any_errors))) { /* A ">>" that appears to have been intended to close two template argument lists. In C++11 that is a valid construct. In other C++ modes, issue a special diagnostic for this case. Either way, this is handled by inserting a ">" into the token stream that will close the outer template argument list. (The outer angle bracket may also close a new-style cast.) */ if (!right_shift_can_be_angle_brackets) { pos_error(ec_exp_gt_not_shift_right, &error_position); *any_errors = TRUE; } /* if */ replace_right_shift_by_two_closing_angle_brackets(); } else if (!*any_errors) { /* There are not two template argument lists pending. Simply issue an "expected '>'" error. */ syntax_error(ec_exp_gt); if (right_shift_can_be_angle_brackets && curr_token == tok_shift_right) { replace_right_shift_by_two_closing_angle_brackets(); } /* if */ *any_errors = TRUE; } /* if */ } /* f_check_closing_angle_bracket */ #define check_closing_angle_bracket(p_any_errors) \ if (curr_token != tok_gt) { \ f_check_closing_angle_bracket((p_any_errors)); \ } /* if */ a_template_arg_ptr scan_concept_arg_list(a_symbol_ptr template_sym, a_boolean type_constraint, a_boolean *any_errors) /* Scan a template argument list for the given concept template. If type_constraint is TRUE, the first argument is for the second parameter (this occurs when scanning type-constraints). */ { a_template_arg_ptr templ_arg_list; long first_defaulted_arg = -1; ++scope_stack_top().pending_templ_arg_lists; templ_arg_list = scan_template_argument_list(template_sym, type_constraint, any_errors, GID_NO_OPTIONS, &first_defaulted_arg); check_closing_angle_bracket(any_errors); --scope_stack_top().pending_templ_arg_lists; return templ_arg_list; } /* scan_concept_arg_list */ static a_boolean is_class_template_member_def(a_symbol_ptr template_sym, a_boolean *is_outermost_tmc) /* Determine whether the template being used (template_sym) is either the class associated with a member that is being defined, or a template enclosing that class. The result of this determination is returned. *is_outermost_tmc is TRUE if in a name such as A::B::..., the template symbol is that of the initial qualifier (i.e., A in this case). The value of *is_outermost_tmc should only be used when this routine returns TRUE. */ { a_boolean is_templ_member_class_sym = FALSE; a_type_ptr type; *is_outermost_tmc = TRUE; /* Determine whether the template being used is either the class associated with a member that is being defined, or a template enclosing that class. */ if (depth_template_declaration_scope != NO_SCOPE_DEPTH && scope_stack[depth_template_declaration_scope].templ_member_class_sym != NULL) { a_symbol_ptr tmc_sym = scope_stack[depth_template_declaration_scope] .templ_member_class_sym; for (;;) { a_template_symbol_supplement_ptr tmc_tssp; /* Get the symbol associated with the nearest enclosing class template. */ type = type_symbol_type(tmc_sym); while (type->source_corresp.is_class_member && class_type_supp(type)->template_arg_list == NULL) { type = parent_class_of(type); *is_outermost_tmc = FALSE; } /* while */ /* Exit the loop if the type has no template argument list. */ if (class_type_supp(type)->template_arg_list == NULL) { break; } /* if */ tmc_sym = class_symbol_supp(symbol_for(type))->class_template; tmc_tssp = tmc_sym->variant.template_info; /* If the template of which a member is being defined is a partial specialization, use the primary template instead for the purpose of determining whether the template being used matches the template of which a member is being defined. */ if (tmc_tssp->primary_template_sym != NULL) { tmc_sym = tmc_tssp->primary_template_sym; } /* if */ if (tmc_sym == template_sym) { is_templ_member_class_sym = TRUE; break; } /* if */ /* Continue processing with the next parent type. */ if (tmc_sym->is_class_member) { type = sym_parent_class(tmc_sym); tmc_sym = symbol_for(type); *is_outermost_tmc = FALSE; } else { break; } /* if */ } /* for */ } /* if */ return is_templ_member_class_sym; } /* is_class_template_member_def */ a_symbol_ptr coalesce_template_class_reference( a_symbol_ptr template_sym, an_identifier_options_set options, a_boolean *err) /* The current identifier is a class template name. Look for an optional template argument list. If an argument list is present, scan the argument list and call a routine to lookup or create the symbol and type information for an instance of the class template. The template argument list is required unless either the GID_TEMPLATE_ARGS_OPTIONAL flag is set in the "options" argument, or the class template pointed to by "template_sym" is the same as the class template associated with the innermost instantiation scope. If no errors occur while scanning the argument list, we call a routine to lookup the appropriate instance (or generate one if needed). */ { a_source_position start_position; a_template_arg_ptr arg_list = NULL; a_symbol_ptr new_sym = NULL; a_symbol_ptr current_instantiation_sym; a_boolean any_errors = FALSE; a_memory_region_number region_to_switch_back_to; a_token_kind next_tok; a_boolean class_is_being_instantiated = FALSE; a_boolean arg_list_coalesced = FALSE; a_boolean arg_list_processed = FALSE; a_symbol_locator orig_locator; a_boolean error_locator_created = FALSE; a_boolean is_constructor_reference = FALSE; a_type_ptr orig_ctor_type = NULL; a_symbol_ptr orig_ctor_symbol = NULL; a_boolean is_expr_context = (options & GID_IS_EXPR_CONTEXT) != 0; a_boolean sun_gpp_undefined_template = FALSE; long first_defaulted_arg = -1L; a_template_symbol_supplement_ptr tssp = NULL; #if MICROSOFT_EXTENSIONS_ALLOWED a_source_position arg_start_pos = null_source_position; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ db_enter(3, "coalesce_template_class_reference"); *err = FALSE; next_tok = next_token(); /* Save source position for error reporting. */ start_position = pos_curr_token; /* Save the current locator. */ orig_locator = locator_for_curr_id; if ((ms_extensions || gpp_version_is(< 40000) || sun_mode) && template_sym != NULL && next_tok == tok_lt && is_constructor_symbol(template_sym)) { /* The symbol passed in is a constructor symbol followed by a template argument list, as in A::A. Replace the constructor symbol with the symbol associated with the original template so that the template argument list can be processed. Later, the constructor symbol will be substituted for the class instance that is created. This is only permitted in Microsoft, g++, and Sun mode. */ a_type_ptr parent_class; a_class_symbol_supplement_ptr parent_cssp; parent_class = sym_parent_class(template_sym); parent_cssp = symbol_supplement_for_class(parent_class); if (parent_cssp->class_template != NULL) { orig_ctor_symbol = template_sym; template_sym = parent_cssp->class_template; /* Get the primary template in case this instance is associated with a partial specialization. */ template_sym = primary_template_of(template_sym); is_constructor_reference = TRUE; orig_ctor_type = parent_class; } /* if */ } /* if */ if (template_sym == NULL || !is_class_template_or_injected_template_symbol(template_sym)) { /* The symbol is not a class template symbol. If the symbol is a type symbol followed by what looks like the beginning of a template argument list (i.e., a "<") issue an error indicating that the current symbol cannot have a template argument list. If the symbol is not a type the action taken depends on whether or not we are in a context in which a "<" is valid as part of an expression. If the "<" might be part of an expression, we return without further processing. Otherwise, a diagnostic is issued and the template argument list is processed. */ a_boolean lt_permitted_context; a_boolean is_error_symbol; lt_permitted_context = (options & GID_IS_NEW_TYPE_NAME) != 0 || (options & GID_IS_FIELD_SELECTION_OPERAND) != 0; is_error_symbol = template_sym == NULL || template_sym->is_error || template_sym->ambiguous || template_sym->kind == (a_symbol_kind)sk_undefined; if (template_sym != NULL && is_type_symbol(template_sym) && next_tok == tok_lt && !lt_permitted_context && !(ms_extensions && caching_tokens)) { /* A type name followed by a template argument list. The test of Microsoft mode and caching_tokens is used to cause this case to be handled by the ms_extensions case below (instead of resulting in an error). */ pos_sy_error(ec_unexpected_template_arg_list, &start_position, template_sym); template_sym = NULL; } else if ((sun_mode || (gpp_mode && gnu_version < 30400)) && template_sym != NULL && scope_stack[depth_scope_stack].in_prototype_instantiation && !is_type_symbol(template_sym) && !is_error_symbol && !is_expr_context && !lt_permitted_context) { /* A nontype symbol (probably from a nonreal base) during a prototype instantiation in g++ (versions < 30400) or Sun mode. Ignore this error. */ template_sym = NULL; sun_gpp_undefined_template = TRUE; } else if (ms_extensions && caching_tokens && scope_is(&scope_stack_top(), sck_template_declaration)) { /* We are caching tokens in a template declaration scope. Ignore an undefined template in Microsoft mode. This most frequently occurs when skipping a default template argument. */ template_sym = NULL; sun_gpp_undefined_template = TRUE; } else if (!is_error_symbol && !lt_permitted_context && !is_expr_context && !caching_tokens) { /* A nontype symbol followed by a template argument list in a nonexpression context. */ pos_sy_error(ec_sym_not_a_template, &start_position, template_sym); template_sym = NULL; } else if (is_error_symbol && !lt_permitted_context && !is_expr_context) { /* A NULL symbol, error symbol, or ambiguous symbol followed by a template argument list. */ if (template_sym == NULL) { /* If the symbol is an error symbol or ambiguous, assume an error has already been issued. */ if ((options & GID_IS_CLASS_TEMPLATE_DECL) != 0) { /* Use a special message in a class template declaration that indicates that a template argument list is not permitted on a primary template. */ pos_error(ec_partial_spec_is_primary_template, &start_position); } else if ((options & GID_IS_TEMPLATE_PRESCAN) != 0) { /* Suppress the diagnostic in this case. */ } else if (is_error_locator(locator_for_curr_id)) { /* An error locator. Don't issue a diagnostic for this case. */ } else { if ((sun_mode || (gpp_mode && gnu_version < 30400)) && scope_stack[depth_scope_stack].in_prototype_instantiation && !is_expr_context && !lt_permitted_context && (sun_mode || (options & GID_IMPLICIT_TYPE_CONTEXT) == 0)) { /* Such references are allowed in prototype instantiation contexts by the g++ and Sun compilers, so suppress this error in g++ and Sun modes. The Sun compiler allows both classes and functions while the g++ compiler only allows functions. The implicit type context test is used to prohibit classes in g++ mode. */ sun_gpp_undefined_template = TRUE; } else { pos_st_error(ec_not_a_template, &start_position, locator_for_curr_id.symbol_header->identifier); } /* if */ } /* if */ } /* if */ template_sym = NULL; } else { /* A valid symbol in an expression context. Just return the symbol that was passed in. */ new_sym = template_sym; goto skip_processing; } /* if */ } /* if */ /* Determine whether the class template (or a member of the class template) is currently being instantiated. This affects how references to the class template name are handled. */ current_instantiation_sym = template_sym; if (template_sym != NULL) { class_is_being_instantiated = current_class_symbol_if_class_template(¤t_instantiation_sym); tssp = template_supplement_for_symbol(template_sym); if (tssp != NULL && tssp->variant.class_template.is_alias_template && !tssp->variant.class_template.prototype_instantiation_complete) { /* The template alias name is used in the alias definition. */ pos_sy_error(ec_alias_used_in_type, &start_position, template_sym); /* Set a flag to prevent the instantiation of this alias. */ tssp->variant.class_template.alias_uses_own_type = TRUE; template_sym = NULL; } /* if */ } /* if */ if (next_tok != tok_lt) { if (options & GID_CLASS_TEMPLATE_REQUIRED) { /* The caller wants the class template symbol. Just return the class template symbol that we started with. */ new_sym = template_sym; goto skip_processing; } else if (template_sym != NULL && is_injected_template_symbol(template_sym)) { /* The template symbol is actually an sk_type symbol that points to the template instance type. When this symbol is not followed by a template argument list it may be used to refer to the current instance (which it already does). */ new_sym = template_sym; goto skip_processing; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (template_sym != NULL && is_cli_generic_class_symbol(template_sym) && non_generic_class_for_cli_generic(template_sym) != NULL) { /* The template_sym is a C++/CLI generic class for which there is also a non-generic class of the same name. Return that symbol. */ new_sym = non_generic_class_for_cli_generic(template_sym); goto skip_processing; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { /* There is no template argument list. If we are in an instantiation of this class template, use the symbol associated with the innermost instantiation of this class, otherwise just return the class template symbol. This mechanism is used when class name injection is not enabled and also if the enclosing class name is found in some other way (this can occur in qualified friend declarations where the namespace of the enclosing class could be reactivated). When class name injection is enabled, the injected name will usually be found in place of the template in contexts in which the current instance should be used. When the enclosing template is found as described above, the current instance will be used except when the template was named with a qualified name. A reference to a C++/CLI generic from a template instantiation scope (i.e., in the base class list) must have a generic argument list. */ if (class_is_being_instantiated && !locator_for_curr_id.is_qualified_name && !(tssp->is_generic && scope_is(&scope_stack_top(), sck_template_instantiation))) { /* We have the symbol for the current instantiation of the class template. */ new_sym = current_instantiation_sym; goto normal_exit; } else { a_scope_stack_entry_ptr ssep = &scope_stack_top(); /* First find the nearest enclosing non-function prototype scope. */ while (ssep->kind == (a_scope_kind)sck_func_prototype) ssep -= 1; if ((options & GID_TEMPLATE_ARGS_OPTIONAL) != 0 || (options & GID_IN_IF_EXISTS) != 0) { /* Template arguments are not required -- simply return the symbol of the class template. */ new_sym = template_sym; goto skip_processing; } else if (microsoft_bugs && (options & GID_IS_TAG_NAME) == 0 && ssep->kind == (a_scope_kind)sck_class_struct_union && is_prototype_instantiation_or_expansion_context()) { /* In the definition of a class template, the Microsoft compiler accepts use a reference such as A::i, where A is a different class template and something like A::i should be used. Accept the use and return the prototype instantiation symbol. */ check_assertion(tssp != NULL); new_sym = tssp->variant.class_template.prototype_instantiation; goto normal_exit; } else { /* Issue an error and return an error locator. */ if (template_sym != NULL) { /* template_sym will be NULL if the symbol passed in was invalid. */ pos_sy_error(ec_missing_template_arg_list, &start_position, template_sym); } /* if */ make_specific_symbol_error_locator(&locator_for_curr_id); new_sym = locator_for_curr_id.specific_symbol; any_errors = TRUE; error_locator_created = TRUE; goto normal_exit; } /* if */ } /* if */ } /* if */ } /* if */ if (ms_extensions && next_tok == tok_lt && (options & GID_CLASS_TEMPLATE_REQUIRED) != 0 && template_sym != NULL && template_sym->variant.template_info->is_nonreal_member) { /* The Microsoft compiler allows something like "T::U<>" to be used as a template template argument. If we have a nonreal template followed by "<>", just return the template. */ a_token_kind next_tok_2; (void)next_two_tokens(tok_lt, &next_tok_2); if (next_tok == tok_lt && next_tok_2 == tok_gt) { (void)get_token(); (void)get_token(); new_sym = template_sym; goto normal_exit; } /* if */ } /* if */ if (template_sym != NULL && is_injected_template_symbol(template_sym)) { /* The symbol is the injected name of a class template. In a template class this points to the current instance of the class. When followed by a template argument list, we need to substitute the class template symbol for the injected symbol. */ template_sym = class_template_for_injected_template_symbol(template_sym); tssp = template_supplement_for_symbol(template_sym); } /* if */ if (template_sym != NULL) { record_potential_pack_reference(template_sym, &start_position); } /* if */ /* Always allocate template arguments at the file scope. */ switch_to_file_scope_region(®ion_to_switch_back_to); add_stop_token(tok_gt); if (right_shift_can_be_angle_brackets) { /* The opening angle bracket might end up being closed by a double angle bracket written as a right shift token. */ add_stop_token(tok_shift_right); } /* if */ add_stop_token(tok_lbrace); add_stop_token(tok_semicolon); /* Skip the class template name. */ (void)get_token(); /* Skip the left angle bracket. */ (void)get_token(); /* Increment the number of template argument lists that are being scanned. */ scope_stack[depth_scope_stack].pending_templ_arg_lists++; if (template_sym != NULL && !template_sym->variant.template_info->is_nonreal_member && !template_sym->variant.template_info->is_error) { #if MICROSOFT_EXTENSIONS_ALLOWED if (is_cli_generic_class_symbol(template_sym)) { arg_start_pos = pos_curr_token; /* Scan the generic argument list. */ arg_list = scan_generic_argument_list(template_sym, &any_errors); if (!any_errors) { /* Determine which generic is being referenced based on the number of arguments provided. */ select_generic_based_on_arity(&template_sym, arg_list, &arg_start_pos, &any_errors); } /* if */ } else #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* Do not insert code here. */ { /* Scan the template argument list. */ arg_list = scan_template_argument_list( template_sym, /*type_constraint=*/FALSE, &any_errors, options, &first_defaulted_arg); } /* if */ } else { /* The template is a member of a proxy or nonreal class. This occurs as a result of constructs like T::A. In such cases there is no template parameter list to use as a basis for the template arguments that are scanned. Scan the template arguments that have been supplied. This kind of scan is also done when there is no template symbol, which happens if an undefined symbol is followed by a template argument list. */ arg_list = scan_unknown_template_arg_list(/*is_nonreal=*/TRUE, &any_errors); } /* if */ arg_list_processed = TRUE; /* We should now be at the closing angle bracket. Note that we don't scan the token after the closing angle because we update the current token below to represent the original identifier with the newly found template class symbol. */ set_err_pos_to_curr_token(); check_closing_angle_bracket(&any_errors); /* Decrement the number of template argument lists that are being scanned. */ scope_stack[depth_scope_stack].pending_templ_arg_lists--; if (!any_errors && template_sym != NULL) { /* Everything is OK -- find the instance that matches these arguments. Create a new instance if needed. There can be two instances of a class A. One is the prototype instantiation which is used when A is referenced within the definition of class template A or in the declarator of an out-of-line definition of a member function or static data member. The other is the nonreal class which is used when, for example, A is a member of class template B. We find the prototype instantiation unless we are currently inside the instantiation of a different class. */ a_boolean prototype_allowed; a_boolean is_templ_member_class_sym; a_type_ptr type; a_boolean is_outermost_tmc; int32_t *p_min_template_arguments; a_boolean instantiate_nonreal = FALSE; /* Determine whether the template being used is either the class associated with a member that is being defined, or a template enclosing that class. */ is_templ_member_class_sym = is_class_template_member_def( template_sym, &is_outermost_tmc); prototype_allowed = ((options & GID_USE_PROTOTYPE_NOT_NONREAL) != 0) || is_templ_member_class_sym; /* Find or create the template class for these arguments. In Microsoft mode certain nonreal base classes are actually instantiated. This is done if GID_IS_BASE_CLASS is set and if this is not the qualifier of a qualified name. */ if (ms_extensions && (options & GID_IS_BASE_CLASS) != 0) { instantiate_nonreal = !no_ms_nonreal_base_classes && next_token() != tok_colon_colon; } /* if */ new_sym = find_template_class(template_sym, &arg_list, prototype_allowed, current_instantiation_sym, instantiate_nonreal, (options & GID_IN_IF_EXISTS) != 0, /*in_substitution=*/FALSE); #if MICROSOFT_EXTENSIONS_ALLOWED if (new_sym == NULL) { /* A NULL symbol should only be returned in a Microsoft __if_exists if the template instance has not been created. */ check_assertion(ms_extensions && (options & GID_IN_IF_EXISTS) != 0); make_specific_symbol_error_locator(&locator_for_curr_id); new_sym = locator_for_curr_id.specific_symbol; any_errors = TRUE; error_locator_created = TRUE; goto normal_exit_with_stop_tokens; } /* if */ if (is_cli_generic_class_symbol(template_sym) && !is_prototype_instantiation_symbol(new_sym)) { /* Make sure the generic argument list satisfies the constraints of the generic. */ verify_generic_arg_list_satisfies_constraints( template_sym, new_sym->variant.class_struct_union.type-> variant.class_struct_union.extra_info-> template_arg_list, &arg_start_pos); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ if (new_sym->is_error) { /* If a constraint failed, an error symbol is returned. Make sure the error is propagated up. */ any_errors = TRUE; } /* if */ if ((options & GID_IS_CLASS_TEMPLATE_DECL) == 0 && tssp->variant.class_template.is_alias_template) { /* A class template member can be defined using an alias template as the qualifier in the qualified name. For example: template struct A; template struct A { A(int); }; template using B = A; template B::A(int) { } Check whether the type referred to by the alias B is a nonreal class type that is an instance of a class template, and if so, look for a matching prototype instantiation. Note that is_class_template_member_def is called again to see if the class template (A in this case) matches the declarator found during the prescan. During the prescan, this code will be used because of the GID_USE_PROTOTYPE_NOT_NONREAL flag. */ a_type_ptr new_tp = type_symbol_type(new_sym); if (gpp_version_is(any_version) && type_is(new_tp, tk_typeref) && is_nonspecialized_prototype_instantiation_context() && new_tp->variant.typeref.kind == trk_is_template_alias && new_tp->variant.typeref.is_dependent && !is_template_dependent_type(new_tp) && (scope_stack_top().in_template_arg_list || is_class_struct_union_type(new_tp))) { /* GCC does not look through alias templates while parsing templates. We cannot easily emulate that in contexts that might be redeclared later on (most deduction contexts and class member declarations), but in other prototype instantiation contexts we can replace a dependent alias template instance by a proxy class so that the front end will not try to verify the underlying type. For example: struct S {}; template using A = S; template struct D: A::B {}; This example is an error because A is known not to have a member B, but GCC ignores that. */ new_tp = proxy_class_for_template_param(new_tp); new_sym = symbol_for(new_tp); } /* if */ new_tp = skip_typerefs(new_tp); if (is_immediate_class_type(new_tp) && new_tp->variant.class_struct_union.is_nonreal_class && next_token() == tok_colon_colon) { a_class_symbol_supplement_ptr cssp; cssp = symbol_supplement_for_class(new_tp); if (cssp->class_template != NULL) { is_templ_member_class_sym = is_class_template_member_def( cssp->class_template, &is_outermost_tmc); prototype_allowed = ((options & GID_USE_PROTOTYPE_NOT_NONREAL) != 0) || is_templ_member_class_sym; if (prototype_allowed) { arg_list = copy_template_arg_list( new_tp->variant.class_struct_union.extra_info-> template_arg_list); new_sym = find_template_class(cssp->class_template, &arg_list, prototype_allowed, current_instantiation_sym, instantiate_nonreal, (options & GID_IN_IF_EXISTS) != 0, /*in_substitution=*/FALSE); } /* if */ } /* if */ } /* if */ } /* if */ if (gpp_version_is(< 40300) && prototype_allowed && scope_is(&scope_stack_top(), sck_template_declaration) && (!is_templ_member_class_sym || !is_outermost_tmc) && ((options & (GID_IS_TEMPLATE_PRESCAN | GID_IS_CLASS_TEMPLATE_DECL)) == 0) && is_nonreal_instance_class_symbol(new_sym) && !is_prototype_instantiation_symbol(new_sym)) { /* g++ allows a member of a nested class of a class template to be defined using an incorrect template argument list. template struct Outer { struct Inner { void f(); }; }; template void Outer::Inner::f() { } ^ should be V We emulate this in g++ mode by replacing the incorrect class with the expected one. We do this in all cases during prescans, but only when a nested class is present in the is_templ_member_class_sym case (as indicated by is_outermost_tmc). The GID_IS_CLASS_TEMPLATE_DECL and GID_IS_TEMPLATE_PRESCAN tests are used to suppress this processing when scanning a partial specialization declaration. */ a_symbol_ptr proto_sym; proto_sym = template_sym->variant.template_info-> variant.class_template.prototype_instantiation; if (proto_sym != NULL) { if (is_templ_member_class_sym) { /* The warning is issued only when processing a real declaration, not during the disambiguation prescan where this can be done for some additional cases that don't occur during the real scan. */ pos_sy2_warning(ec_bad_template_member_definition, &start_position, new_sym, proto_sym); } /* if */ new_sym = proto_sym; } /* if */ } /* if */ arg_list_coalesced = TRUE; type = type_symbol_type(new_sym); p_min_template_arguments = min_template_arguments_for_type(type, template_sym); if (p_min_template_arguments != NULL && first_defaulted_arg >= 0 && (*p_min_template_arguments == -1 || *p_min_template_arguments > first_defaulted_arg)) { /* Record the number of arguments used in this reference. */ *p_min_template_arguments = (int32_t)first_defaulted_arg; } /* if */ if (is_constructor_reference) { /* If a constructor symbol was passed originally, replace the class symbol that resulted from processing the template argument list with its constructor symbol. Make sure the class type is complete before doing so. */ a_class_symbol_supplement_ptr cssp; a_type_ptr new_type; new_type = type_symbol_type(new_sym); cssp = new_sym->variant.class_struct_union.extra_info; complete_class_type_is_needed(new_type); /* If the constructor name followed a class qualifier, make sure that the constructor name matches the original class name. */ if (identical_types(new_type, orig_ctor_type)) { new_sym = cssp->constructor; } else { pos_ty2_error(ec_bad_constructor_type, &error_position, new_type, orig_ctor_type); new_sym = orig_ctor_symbol; } /* if */ } /* if */ } else if (sun_gpp_undefined_template) { /* In g++ and Sun modes it is possible to refer to undeclared templates. We get here for example with template struct S { friend void f<>(); }; */ } else { /* Free any allocated template arguments. */ if (arg_list != NULL) { free_template_arg_list(arg_list); arg_list = NULL; } /* if */ /* An error occurred while scanning the argument list so make an error locator and return a pointer to its specific symbol. */ make_specific_symbol_error_locator(&locator_for_curr_id); error_locator_created = TRUE; new_sym = locator_for_curr_id.specific_symbol; } /* if */ #if MICROSOFT_EXTENSIONS_ALLOWED normal_exit_with_stop_tokens: #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ switch_back_to_original_region(region_to_switch_back_to); remove_stop_token(tok_gt); if (right_shift_can_be_angle_brackets) { remove_stop_token(tok_shift_right); } /* if */ remove_stop_token(tok_lbrace); remove_stop_token(tok_semicolon); normal_exit: if (arg_list_processed && curr_token != tok_gt) { /* Below we will set curr_token to tok_identifier. Do an unget of the token that stopped the flush so that it can be processed later. */ unget_token(); } /* if */ /* When we return to the caller the current identifier should be an identifier and the locator should point to the template class that we have just looked up. */ curr_token = tok_identifier; /* Update the locator to reflect the new symbol that is being returned. We start by restoring the locator as it was when this routine was called and then update it to reflect the new symbol that was produced by this process. The symbol header should already be correct but is updated just for safety. If a template argument list has been coalesced, set the do_not_clear_speecific symbol field of the locator. This is needed to ensure because the argument list information is now represented by the fact that the specific symbol points to a particular template class instance, and this information cannot be recreated once the template reference has been coalesced. */ if (error_locator_created) { /* Don't reset the locator if we created an error locator earlier. Just update the source position to reflect the position of the original locator. */ locator_for_curr_id.source_position = orig_locator.source_position; } else { locator_for_curr_id = orig_locator; } /* if */ /* Don't set the is_template_id field after processing a constructor reference followed by a template argument list in Microsoft mode. */ locator_for_curr_id.is_template_id = !is_constructor_reference; if (new_sym != NULL) { locator_for_curr_id.specific_symbol = new_sym; locator_for_curr_id.do_not_clear_specific_symbol = arg_list_coalesced; locator_for_curr_id.symbol_header = new_sym->header; if (!record_form_of_name_reference || !locator_for_curr_id.is_template_id) { free_template_arg_list(arg_list); arg_list = NULL; } /* if */ locator_for_curr_id.template_arg_list = arg_list; } /* if */ /* Set source position for error reporting. */ error_position = start_position; #if DEBUG if (debug_level >= 5) { db_symbol(template_sym, "Template symbol: ", 2); } /* if */ if (debug_level >= 4) { db_symbol(new_sym, "Returning: ", 2); } /* if */ #endif /* DEBUG */ skip_processing: if (!arg_list_processed && !any_errors && template_sym != NULL) { record_potential_pack_reference(template_sym, &start_position); } /* if */ *err = any_errors; db_exit(); return new_sym; } /* coalesce_template_class_reference */ static a_symbol_ptr coalesce_template_function_reference( a_symbol_ptr template_sym, a_token_kind next_tok, a_boolean *err) /* The current identifier is a function template symbol or an overload set containing a function template symbol, and if next_tok is tok_lt ("<"), is followed by a template argument list. Scan the template argument list and update the locator to point to it. The template arguments are not scanned with respect to a particular template parameter list because, in the general case, you don't know which of several potential parameter lists is the one actually associated with this reference. */ { a_source_position start_position; a_template_arg_ptr arg_list = NULL; a_memory_region_number region_to_switch_back_to; a_symbol_locator orig_locator; a_boolean any_errors = FALSE; a_boolean bad_arg_list = FALSE; db_enter(3, "coalesce_template_function_reference"); /* Save source position for error reporting. */ start_position = pos_curr_token; if (next_tok == tok_lt) { /* Save the current locator. */ orig_locator = locator_for_curr_id; /* Always allocate template arguments at the file scope. */ switch_to_file_scope_region(®ion_to_switch_back_to); add_stop_token(tok_gt); if (right_shift_can_be_angle_brackets) { /* The opening angle bracket might end up being closed by a double angle bracket written as a right shift token. */ add_stop_token(tok_shift_right); } /* if */ add_stop_token(tok_lbrace); add_stop_token(tok_semicolon); /* Get the angle bracket token. */ (void)get_token(); check_assertion(curr_token == tok_lt); /* Get token following opening angle bracket. */ (void)get_token(); /* Increment the number of template argument lists that are being scanned. */ scope_stack[depth_scope_stack].pending_templ_arg_lists++; /* Scan the template argument list. */ arg_list = scan_unknown_template_arg_list(/*is_nonreal=*/FALSE, &any_errors); /* We should now be at the closing angle bracket. Note that we don't scan the token after the closing angle because we update the current token below to represent the original identifier with the newly found template class symbol. */ set_err_pos_to_curr_token(); check_closing_angle_bracket(&any_errors); /* Decrement the number of template argument lists that are being scanned. */ scope_stack[depth_scope_stack].pending_templ_arg_lists--; switch_back_to_original_region(region_to_switch_back_to); remove_stop_token(tok_gt); if (right_shift_can_be_angle_brackets) { remove_stop_token(tok_shift_right); } /* if */ remove_stop_token(tok_lbrace); remove_stop_token(tok_semicolon); if (curr_token != tok_gt) { /* Below we will set curr_token to tok_identifier. Do an unget of the token that stopped the flush so that it can be processed later. */ unget_token(); if (template_sym == NULL) bad_arg_list = TRUE; } /* if */ /* Upon return, the locator should refer to the symbol that was passed in, but should also include the template argument list. */ curr_token = tok_identifier; locator_for_curr_id = orig_locator; } /* if */ if (!bad_arg_list || !adl_for_non_visible_templates) { /* To improve error recovery when ADL is used for non-visible templates, forget about the template argument list so that we don't potentially use the identifier as a type. */ locator_for_curr_id.is_template_id = TRUE; locator_for_curr_id.template_arg_list = arg_list; } /* if */ /* Set source position for error reporting. */ error_position = start_position; *err = *err || any_errors; db_exit(); return template_sym; } /* coalesce_template_function_reference */ static a_symbol_ptr coalesce_template_variable_reference( a_symbol_ptr template_sym, a_token_sequence_number start_tsn, an_identifier_options_set options, a_token_kind next_tok, a_boolean *err) /* The current identifier is a variable template symbol. If next_tok is tok_lt ("<"), it is followed by a template argument list. Scan the template argument list and update the locator to point to it. start_tsn is the token sequence number of the first token of the whole qualified name. options is the set of options flags passed into the identifier scanning routines. Return the symbol of the variable template instance indicated by the template argument list. */ { a_source_position start_position; a_template_arg_ptr arg_list = NULL; a_memory_region_number region_to_switch_back_to; a_symbol_locator orig_locator; a_boolean any_errors = FALSE; a_symbol_ptr new_sym = NULL; a_boolean is_template_id = TRUE; /* Save source position for error reporting. */ start_position = pos_curr_token; if (next_tok != tok_lt) { if ((options & GID_TEMPLATE_ARGS_OPTIONAL) != 0) { /* If template arguments are not required, return the template symbol. */ new_sym = template_sym; is_template_id = FALSE; } else { pos_sy_error(ec_missing_template_arg_list, &start_position, template_sym); make_specific_symbol_error_locator(&locator_for_curr_id); new_sym = locator_for_curr_id.specific_symbol; any_errors = TRUE; } /* if */ } else { /* Save the current locator. */ orig_locator = locator_for_curr_id; /* Always allocate template arguments at the file scope. */ switch_to_file_scope_region(®ion_to_switch_back_to); add_stop_token(tok_gt); if (right_shift_can_be_angle_brackets) { /* The opening angle bracket might end up being closed by a double angle bracket written as a right shift token. */ add_stop_token(tok_shift_right); } /* if */ add_stop_token(tok_lbrace); add_stop_token(tok_semicolon); /* Get the angle bracket token. */ (void)get_token(); check_assertion(curr_token == tok_lt); /* Get the token following the opening angle bracket. */ (void)get_token(); /* Increment the number of template argument lists that are being scanned. */ scope_stack[depth_scope_stack].pending_templ_arg_lists++; if (template_sym != NULL && !template_sym->variant.template_info->is_nonreal_member && !template_sym->variant.template_info->is_error) { long first_defaulted_arg = -1L; /* Scan the template argument list. */ arg_list = scan_template_argument_list( template_sym, /*type_constraint=*/FALSE, &any_errors, options, &first_defaulted_arg); /* Mark all non-default arguments as explicitly specified. */ for (a_template_arg_ptr tap = arg_list; tap != NULL && first_defaulted_arg != 0; --first_defaulted_arg, tap = tap->next) { tap->explicitly_specified = TRUE; } /* for */ } else { /* The template is a member of a proxy or nonreal class. This occurs as a result of constructs like T::A. In such cases there is no template parameter list to use as a basis for the template arguments that are scanned. Scan the template arguments that have been supplied. This kind of scan is also done when there is no template symbol, which happens if an undefined symbol is followed by a template argument list. */ arg_list = scan_unknown_template_arg_list(/*is_nonreal=*/TRUE, &any_errors); } /* if */ /* We should now be at the closing angle bracket. Note that we don't scan the token after the closing angle because we update the current token below to represent the original identifier with the newly found template class symbol. */ set_err_pos_to_curr_token(); check_closing_angle_bracket(&any_errors); /* Decrement the number of template argument lists that are being scanned. */ scope_stack[depth_scope_stack].pending_templ_arg_lists--; switch_back_to_original_region(region_to_switch_back_to); remove_stop_token(tok_gt); if (right_shift_can_be_angle_brackets) { remove_stop_token(tok_shift_right); } /* if */ remove_stop_token(tok_lbrace); remove_stop_token(tok_semicolon); if (curr_token != tok_gt) { /* Below we will set curr_token to tok_identifier. Do an unget of the token that stopped the flush so that it can be processed later. */ unget_token(); } /* if */ if (arg_list == NULL || any_errors) { make_specific_symbol_error_locator(&locator_for_curr_id); locator_for_curr_id.source_position = orig_locator.source_position; new_sym = locator_for_curr_id.specific_symbol; } else if (scope_stack_top().var_templ_decl_name_tsn == start_tsn) { /* We are scanning a variable template partial specialization and have hit the template argument list following the declarator name. Ignore this template argument list. */ new_sym = NULL; free_template_arg_list(arg_list); arg_list = NULL; } else { a_boolean prototype_allowed; a_boolean is_use = FALSE; check_assertion(template_sym != NULL); if ((options & GID_IS_EXPR_CONTEXT) != 0) { /* Determine if the variable template specialization must be instantiated. That is the case when its value is used, but also if it has a type deduced from its initializer. */ if (expr_stack != NULL && curr_expr_is_potentially_evaluated()) { is_use = TRUE; } else { a_template *templ = template_sym->variant.template_info ->il_template_entry; a_variable *vp = templ->prototype_instantiation.variable; if (vp != NULL && (vp->declared_with_auto_type_specifier || vp->declared_with_decltype_auto || vp->declared_with_class_template_placeholder || is_incomplete_array_type(vp->type))) { is_use = TRUE; } /* if */ } /* if */ } /* if */ prototype_allowed = (options & GID_USE_PROTOTYPE_NOT_NONREAL) != 0; new_sym = find_template_variable(template_sym, &arg_list, prototype_allowed, is_use, /*diagnose=*/TRUE); if (new_sym == NULL) { expect_error(); any_errors = TRUE; } /* if */ locator_for_curr_id = orig_locator; } /* if */ curr_token = tok_identifier; } /* if */ if (new_sym != NULL) { locator_for_curr_id.specific_symbol = new_sym; locator_for_curr_id.do_not_clear_specific_symbol = is_template_id; locator_for_curr_id.symbol_header = new_sym->header; locator_for_curr_id.is_template_id = is_template_id; locator_for_curr_id.template_arg_list = arg_list; } /* if */ /* Set the source position for error reporting. */ error_position = start_position; *err = any_errors; return new_sym; } /* coalesce_template_variable_reference */ static a_symbol_ptr ensure_correct_nonreal_instance_kind( a_symbol_ptr sym, an_identifier_options_set options, a_symbol_ptr orig_template_sym) /* When scanning a name that comes after the "template" keyword it is difficult to know whether the name is intended to be a class template reference or a function template reference. The name is a class template reference if it is part of a qualified name that was preceded with the "typename" keyword, or if the template argument list is followed by "::". For example: p->template g<1>(); // template is a function p->template g<1>::f(); // template is a class We can't know what follows the argument list until we get there. So, we initially scan the reference as an unknown class template reference. This routine converts the reference to a function if that is what is required. Actually, when this is done, "orig_template_sym" is returned, otherwise the original "sym" is returned. */ { a_class_symbol_supplement_ptr cssp; a_symbol_ptr template_sym; /* First make sure this is really the case we need to worry about. The caller only makes a cursory check. The check must be done when "sym" refers to a nonreal class that is an instance of a nonreal template. The caller already checked that it is a nonreal class. */ check_assertion(is_class_struct_union_symbol(sym)); cssp = sym->variant.class_struct_union.extra_info; template_sym = cssp->class_template; check_assertion(template_sym != NULL); if (template_sym->variant.template_info->is_nonreal_member) { /* The class is a member of a nonreal template. Determine whether we want a class or not. */ a_boolean type_wanted = FALSE; if ((options & GID_IS_TYPENAME) != 0) { type_wanted = TRUE; } else if (next_token() == tok_colon_colon) { type_wanted = TRUE; } else if (!orig_template_sym->variant.template_info->is_nonreal_member) { /* The instantiation of a real template resulted in a nonreal member. We know that a type is needed. */ type_wanted = TRUE; } else if ((use_implicit_typename() || (gpp_mode && gnu_version <= 40100)) && (options & GID_IS_EXPR_CONTEXT) == 0) { type_wanted = TRUE; } else if ((options & GID_IMPLICIT_TYPE_CONTEXT) != 0) { type_wanted = TRUE; } /* if */ if (!type_wanted) { /* Restore the template argument list from the template class. Make a copy that can be freed later when it is used. */ locator_for_curr_id.template_arg_list = copy_template_arg_list( sym->variant.class_struct_union.type-> variant.class_struct_union.extra_info->template_arg_list); sym = orig_template_sym; locator_for_curr_id.specific_symbol = sym; locator_for_curr_id.is_unknown_template_reference = TRUE; } /* if */ } /* if */ return sym; } /* ensure_correct_nonreal_instance_kind */ static a_boolean has_dependent_nontype_parameter(a_symbol_ptr template_sym) /* Return TRUE if the given template has a nontype template parameter that involves a template parameter. */ { a_boolean result = FALSE; a_template_param_ptr param = template_sym->variant.template_info-> cache->decl_info->parameters; for (; param != NULL; param = param->next) { if (symbol_is(param->param_symbol, sk_constant) && param->variant.constant.type_involves_template_param) { result = TRUE; } /* if */ } /* for */ return result; } /* has_dependent_nontype_parameter */ static a_symbol_ptr coalesce_template_id( a_symbol_ptr template_sym, a_token_kind next_tok, a_token_sequence_number start_tsn, an_identifier_options_set options, a_boolean is_name_start, a_boolean *err) /* This routine is called when an identifier is followed by "<" sign that may be the start of a template argument list, or if the identifier was preceded by the "template" keyword. next_tok is tok_lt ("<") if a template argument list is present. If the symbol is a function template symbol, or an overload set containing one or more templates, coalesce_template_function_reference is called to scan the argument list. Otherwise, coalesce_template_class_reference is called to either scan the class template argument list or diagnose an invalid template reference. start_tsn is the token sequence number of the first token of the whole qualified name. is_name_start is TRUE if this is the start of a possibly qualified name. It is FALSE if this is not the start of the name, or if it is a name that is part of a class member access (i.e., it follows a "." or "->"). */ { a_symbol_ptr result_sym; a_boolean treat_as_unknown_func = FALSE; if (template_sym != NULL && symbol_is(template_sym, sk_variable_template) && gpp_version_is(<130000) && scope_stack_top().in_prototype_instantiation && scope_is(&scope_stack_top(), sck_template_instantiation) && scope_stack_top().template_sym != NULL && is_alias_template_symbol(scope_stack_top().template_sym) && has_dependent_nontype_parameter(template_sym) && (expr_stack == NULL || curr_expr_is_potentially_evaluated())) { /* GCC sometimes does not substitute nondependent template-ids referring to variable templates if they appear in certain contexts, particularly in the definitions of alias templates. For example: template using Void = void; template bool vart; template using Alias = Void>; GCC 12 accepts that example. The mechanism used by GCC is not very clear, and it appears to depend on the particular version of GCC, but the criteria above cover examples found in real code. To avoid instantiating the variable template, treat it as an unknown function template instead. */ template_sym = find_unknown_function_symbol( template_sym, locator_for_curr_id.is_qualified_name); treat_as_unknown_func = TRUE; } /* if */ if (template_sym != NULL && symbol_is(template_sym, sk_variable_template)) { result_sym = coalesce_template_variable_reference(template_sym, start_tsn, options, next_tok, err); } else if ((adl_for_non_visible_templates && is_name_start && !(template_sym == NULL && caching_tokens && scope_stack_top().in_disambiguation) && (options & GID_IS_EXPR_CONTEXT) != 0 && (template_sym == NULL || (is_function_symbol(template_sym) && !template_sym->is_class_member))) || treat_as_unknown_func || (template_sym != NULL && !is_class_template_or_injected_template_symbol(template_sym) && symbol_is_or_contains_template(template_sym))) { /* A function template symbol or overload set containing a function template symbol. In C++20 (adl_for_non_visible_templates is TRUE) an undefined name or function name followed by "<" is treated as a template name to cause ADL to be performed. Don't consider this to be a not-yet-visible template during caching for disambiguation purposes. */ result_sym = coalesce_template_function_reference(template_sym, next_tok, err); } else { /* A class template symbol or a potential error case. */ result_sym = coalesce_template_class_reference(template_sym, options, err); if (result_sym != NULL && is_nonreal_instance_class_symbol(result_sym) && result_sym != template_sym) { /* We scanned this as a class template reference, but it is possible that it should really be considered a function (but we could not tell until we found out what token was after the template argument list). Check that we have the right kind of entity, and convert to the right kind if necessary. */ result_sym = ensure_correct_nonreal_instance_kind(result_sym, options, template_sym); } /* if */ } /* if */ return result_sym; } /* coalesce_template_id */ static a_boolean f_check_for_template_declarator_errors( an_identifier_options_set options, a_source_position *error_pos) /* Check for certain errors that can occur while scanning the identifier in a declarator of a template declaration. */ { a_boolean any_errors = FALSE; a_symbol_ptr sym = locator_for_curr_id.specific_symbol; a_symbol_ptr sym_parent; sym_parent = sym != NULL && sym->is_class_member ? symbol_for(sym_parent_class(sym)) : NULL; if (is_error_locator(locator_for_curr_id) || sym == NULL) { /* An error has already been issued. */ } else if (!locator_for_curr_id.is_qualified_name) { /* No error tests are done on unqualified names. */ } else if (symbol_is_or_contains_template(sym)) { /* Okay -- the symbol found refers to a template. */ } else if (scope_stack[depth_scope_stack].in_prototype_instantiation) { /* Ignore errors in prototype instantiations. */ } else if (sym->is_class_member && (is_prototype_instantiation_or_cli_generic(sym_parent))) { /* The symbol is a member of a prototype instantiation -- this is the definition of a member of a class template. */ } else if (options & GID_IS_TEMPLATE_SPECIALIZATION) { /* We are processing a template specialization (but not a full specialization), and the symbol found does not represent a template. This is an error. */ pos_sy_error(ec_partial_specialization_not_allowed, error_pos, sym); any_errors = TRUE; } else { /* The remaining valid cases are members of class templates or classes nested within class templates. */ if (!sym->is_class_member) { /* The named entity is not a template and is not a class member. No diagnostic is issued here. That will be left to the caller. Special treatment is given to class members to better diagnose invalid declarations of members of class templates. */ } else if ((options & GID_IS_FRIEND_DECL) != 0) { /* Friend declarations can refer to members of nonreal classes. */ } else { /* The qualifier class type must point to a prototype instantiation. */ a_type_ptr tp; a_symbol_ptr type_sym; tp = sym_parent_class(sym); type_sym = (a_symbol_ptr)tp->source_corresp.assoc_info; if (is_prototype_instantiation_or_cli_generic(type_sym)) { /* Okay. */ } else if (is_nonreal_instance_class_symbol(type_sym)) { /* If the class is not a real class type, then it is expected to be the prototype instantiation. Decide which of two errors should be issued for this case. The usual cause of this error is using an incorrect template argument list (one that does not match the template parameter list, but this may also be caused if the class template definition is currently incomplete (so there is no prototype instantiation yet). */ a_symbol_ptr template_sym; a_template_symbol_supplement_ptr tssp; template_sym = type_sym->variant.class_struct_union.extra_info->class_template; tssp = template_supplement_for_symbol(template_sym); /* See if this template is a subordinate template (a template in a template class that is based on a member template from the class template). If so, use the prototype template to check for a prototype instantiation. */ if (tssp->prototype_template != NULL && !tssp->is_specific_definition) { template_sym = tssp->prototype_template; } /* if */ if (template_sym->variant.template_info-> variant.class_template.prototype_instantiation == NULL) { /* There is no prototype yet. This is probably caused by the class template being incomplete at this point. */ issue_incomplete_type_diag(error_pos, tp); any_errors = TRUE; } else { /* The class is a template class but not the prototype instantiation. */ pos_error(ec_must_be_prototype_instantiation, error_pos); any_errors = TRUE; } /* if */ } else { /* Some other invalid type. */ pos_ty_error(ec_not_a_class_template, error_pos, tp); any_errors = TRUE; } /* if */ } /* if */ } /* if */ return any_errors; } /* f_check_for_template_declarator_errors */ static a_boolean f_check_for_generalized_identifier_errors (an_identifier_options_set options, a_source_position *pos) /* Compares the flags in locator_for_curr_id with the error flags in the options set passed by the caller and issues errors for cases not allowed by the options. Returns TRUE if any errors were diagnosed. */ { a_boolean any_errors = FALSE; a_boolean qualified_name_error; a_boolean global_qualifier_error; a_boolean operator_name_error; /* If no position was specified use the current error position. */ if (pos == NULL) pos = &error_position; qualified_name_error = (locator_for_curr_id.is_qualified_name && (options & GID_DISALLOW_QUALIFIED_NAME)); global_qualifier_error = (locator_for_curr_id.is_global_qualified_name && (options & GID_DISALLOW_GLOBAL_QUALIFIER)); operator_name_error = (locator_for_curr_id.is_operator_name || locator_for_curr_id.is_conversion_name) && (options & GID_DISALLOW_OPERATOR_NAME); if (qualified_name_error && global_qualifier_error) { /* If both errors are being checked for, and both errors exist, only issue one of the errors using the following rules: ::A Global qualifier error ::A::B Qualified name error */ if (locator_for_curr_id.is_file_scope_qualified_name) { qualified_name_error = FALSE; } else { global_qualifier_error = FALSE; } /* if */ } /* if */ if (qualified_name_error) { pos_error(ec_qualified_name_not_allowed, pos); any_errors = TRUE; } else if (global_qualifier_error) { pos_error(ec_global_qualifier_not_allowed, pos); any_errors = TRUE; } else if (operator_name_error) { pos_error(ec_operator_name_not_allowed, pos); any_errors = TRUE; } /* if */ return any_errors; } /* f_check_for_generalized_identifier_errors */ static a_hash_value hash_lexical_template_arg_list(a_template_arg_ptr args) /* Return a hash value for the indicated template argument list, based on lexical equivalence. */ { a_hash_value value = 0; for (a_template_arg_ptr tap = args; tap != NULL; tap = tap->next) { value += 31*value + 7*tap->kind; switch (tap->kind) { case tak_type: value += (a_hash_value)hash_ptr(tap->variant.type); break; case tak_nontype: if (tap->is_array_bound_of_unknown_type) { value += (a_hash_value)tap->variant.integer_value; } else { value += (a_hash_value)hash_ptr(tap->variant.constant); } /* if */ break; case tak_template: value += (a_hash_value)hash_ptr(tap->variant.templ.ptr); break; case tak_start_of_pack_expansion: break; default: unexpected_condition(); break; } /* switch */ } /* for */ return value; } /* hash_lexical_template_arg_list */ #if CREATE_LEXICAL_TYPEREFS /* Data structure used to represent a pair of a type and a name qualifier. */ typedef struct a_type_and_name_qualifier { a_type_ptr type; /* The type. */ a_name_qualifier_ptr name_qualifier; /* The name qualifier. */ a_boolean is_global_qualified_name; /* TRUE if the name was written using a global namespace qualifier. */ } a_type_and_name_qualifier; /* Data structure used to represent a pair of a type and a template argument list. */ typedef struct a_type_and_template_arg_list { a_type_ptr type; /* The type. */ a_template_arg_ptr template_arg_list; /* The template argument list. */ } a_type_and_template_arg_list; STATIC_THREAD a_hash_table_ptr name_qualifier_typeref_hash_table; /* A hash table used to find previously created name qualifiers for a type. */ STATIC_THREAD a_hash_table_ptr template_arg_list_typeref_hash_table; /* A hash table used to find previously created template argument lists for a type. */ a_hash_value hash_type_and_name_qualifier(a_void_ptr key) /* Produce a hash value for a type and name qualifier pair. The key is a pointer to a type and name qualifier pair. */ { a_type_and_name_qualifier *info = (a_type_and_name_qualifier*)key; a_hash_value value = (a_hash_value)(31*hash_ptr(info->type) + hash_ptr(info->name_qualifier) + 13*info->is_global_qualified_name); return value; } /* hash_type_and_name_qualifier */ a_boolean compare_type_and_name_qualifier(a_void_ptr entry, a_void_ptr key) /* Compare an entry in the type and name qualifier hash table with an entry to be found. "entry" and "key" are of type pointer to a_type_and_name_qualifier. Return TRUE if the key matches the entry. */ { a_type_and_name_qualifier *info = (a_type_and_name_qualifier*)key; a_type_ptr entry_tp = (a_type_ptr)entry; a_boolean result = info->type == entry_tp->variant.typeref.type && info->name_qualifier == typeref_supp(entry_tp)->name_qualifier && info->is_global_qualified_name == entry_tp->variant.typeref .is_global_qualified_name; return result; } /* compare_type_and_name_qualifier */ a_hash_value hash_type_and_template_arg_list(a_void_ptr key) /* Produce a hash value for a type and template argument list pair. The key is a pointer to a type and template argument list pair. */ { a_type_and_template_arg_list *info = (a_type_and_template_arg_list*)key; a_hash_value value = (a_hash_value)hash_ptr(info->type); value = 31*value + hash_lexical_template_arg_list(info->template_arg_list); return value; } /* hash_type_and_template_arg_list */ a_boolean compare_type_and_template_arg_list(a_void_ptr entry, a_void_ptr key) /* Compare an entry in the type and template argument list hash table with an entry to be found. "entry" and "key" are of type pointer to a_type_and_template_arg_list. Return TRUE if the key matches the entry. */ { a_type_and_template_arg_list *info = (a_type_and_template_arg_list*)key; a_type_ptr entry_tp = (a_type_ptr)entry; a_template_arg_ptr entry_tap = typeref_supp(entry_tp)->template_arg_list, key_tap = info->template_arg_list; a_boolean result; result = info->type == entry_tp->variant.typeref.type && are_template_args_lexically_identical(entry_tap, key_tap); return result; } /* compare_type_and_template_arg_list */ static a_type_ptr make_typeref_with_template_args(a_type_ptr tp, a_template_arg_ptr arg_list) /* Return a new tk_typeref entry with the given template argument list and whose underlying type is tp. */ { a_template_arg_ptr tap, prev_tap = NULL; a_type_ptr *tp_in_table; begin_template_arg_list_traversal_simple(arg_list, &tap); for (; tap != NULL && tap->explicitly_specified; advance_to_next_template_arg_simple(&tap)) { prev_tap = tap; } /* for */ /* Free any trailing template arguments that were not explicitly specified. */ free_template_arg_list(tap); if (prev_tap == NULL) { arg_list = NULL; } else { prev_tap->next = NULL; } /* if */ a_type_and_template_arg_list hash_key{tp, arg_list}; tp_in_table = (a_type_ptr*)hash_find(template_arg_list_typeref_hash_table, &hash_key, /*create=*/TRUE); if (*tp_in_table == NULL) { a_type_ptr new_tp = alloc_type(tk_typeref); new_tp->variant.typeref.kind = trk_template_arg_list; new_tp->variant.typeref.type = tp; new_tp->variant.typeref.extra_info->template_arg_list = arg_list; if (type_is(tp, tk_typeref)) { new_tp->variant.typeref.is_nonreal = tp->variant.typeref.is_nonreal; new_tp->variant.typeref.is_dependent = tp->variant.typeref.is_dependent; new_tp->variant.typeref.is_prototype_instantiation = tp->variant.typeref.is_prototype_instantiation; } /* if */ *tp_in_table = new_tp; } else { free_template_arg_list(arg_list); } /* if */ return *tp_in_table; } /* make_typeref_with_template_args */ a_type_ptr make_typeref_with_name_qualifier( a_type_ptr tp, a_name_qualifier_ptr name_qualifier, a_boolean is_global_qualified_name) /* Return a new tk_typeref entry with the given name qualifier (can be NULL if is_global_qualified_name is TRUE) and whose underlying type is tp. */ { a_type_and_name_qualifier hash_key{tp, name_qualifier, is_global_qualified_name}; a_type_ptr *tp_in_table = (a_type_ptr*)hash_find(name_qualifier_typeref_hash_table, &hash_key, /*create=*/TRUE); if (*tp_in_table == NULL) { a_type_ptr new_tp = alloc_type(tk_typeref); new_tp->variant.typeref.kind = trk_name_qualifier; new_tp->variant.typeref.type = tp; new_tp->variant.typeref.is_global_qualified_name =is_global_qualified_name; new_tp->variant.typeref.extra_info->name_qualifier = name_qualifier; if (type_is(tp, tk_typeref)) { new_tp->variant.typeref.is_nonreal = tp->variant.typeref.is_nonreal; new_tp->variant.typeref.is_dependent = tp->variant.typeref.is_dependent; new_tp->variant.typeref.is_prototype_instantiation = tp->variant.typeref.is_prototype_instantiation; } /* if */ *tp_in_table = new_tp; } /* if */ tp = *tp_in_table; return tp; } /* make_typeref_with_name_qualifier */ a_type_ptr make_typeref_with_lexical_information(a_type_ptr tp, a_symbol_locator *locator) /* If the given locator represents a qualified name, return a new tk_typeref entry with the given name qualifier and whose underlying type is tp. If the given locator includes an original template argument list, return a new tk_typeref entry with that template argument list and whose underlying type is tp. */ { if (locator->is_global_qualified_name || locator->name_qualifier != NULL) { tp = make_typeref_with_name_qualifier(tp, locator->name_qualifier, locator->is_global_qualified_name); } /* if */ if (locator->template_arg_list != NULL) { tp = make_typeref_with_template_args(tp, locator->template_arg_list); locator->template_arg_list = NULL; } /* if */ return tp; } /* make_typeref_with_lexical_information */ #else /* !CREATE_LEXICAL_TYPEREFS */ #define make_typeref_with_template_args(tp, args) (tp) #endif /* CREATE_LEXICAL_TYPEREFS */ #if DEBUG void db_name_qualifier(a_name_qualifier_ptr nqp) /* Display a name qualifier, for debugging purposes. */ { /* Display any parent qualifiers. */ if (nqp->previous_qualifier != NULL) { fprintf(f_debug, "["); db_name_qualifier(nqp->previous_qualifier); fprintf(f_debug, "]"); } /* if */ if (nqp->is_class) { db_type_name(nqp->qualifier.class_type); } else /*if (nqp->qualifier.namespace_ptr != NULL)*/ { db_name((a_source_correspondence*)&nqp-> qualifier.namespace_ptr->source_corresp); } /* if */ if (nqp->name != NULL) { fprintf(f_debug, " (named as %s)", nqp->name); } /* if */ fprintf(f_debug, "::"); } /* db_name_qualifier */ void db_name_reference(a_name_reference_ptr nrp) /* Display a name reference, for debugging purposes. */ { if (nrp->is_global_qualified_name) { fprintf(f_debug, "::"); } /* if */ if (nrp->is_super_qualified) { fprintf(f_debug, "__super::"); } /* if */ if (nrp->qualifier != NULL) db_name_qualifier(nrp->qualifier); fprintf(f_debug, "(name)"); if (nrp->is_template_id) { fprintf(f_debug, "<...>"); } /* if */ fprintf(f_debug, "\n"); } /* db_name_reference */ #endif /* DEBUG */ static void make_name_qualifier(a_name_qualifier_ptr *nqp, a_symbol_ptr qualifier_sym, a_symbol_ptr qualifier_template_sym, a_type_ptr qualifier_type, a_namespace_ptr qualifier_namespace, a_type_ptr decltype_type) /* Construct a name qualifier entry for the qualifier specified by "qualifier_sym" or "decltype_type". "nqp" points to the previous qualifier, if any. If "qualifier_sym" is a type symbol, "qualifier_type" is the type referred to, after any typerefs/typedefs have been removed. If "qualifier_sym" is a namespace symbol, "qualifier_namespace" is the namespace referred to, after any namespace aliases have been removed. "qualifier_template_sym" is non-NULL if the qualifier is a template-id. In that case, the name from "qualifier_template_sym" is used in preference to the "qualifier_sym" because the two can be different if the template is a template template parameter. If "decltype_type" is not NULL, it is the original type specified by a decltype qualifier and is used in place of the type specified by "qualifier_sym", which refers to the underlying type in this case. Some types named by decltype have no symbol for the underlying type. "qualifier_sym" is NULL in these cases. We look for a previously allocated name qualifier entry for classes and namespaces. The list is stored in the class or namespace supplement associated with the underlying class or namespace (after typerefs or aliases have been removed) but the qualifier itself points to the original type or namespace that was specified. */ { a_name_qualifier_ptr prev_nqp = *nqp; a_name_qualifier_ptr new_nqp = NULL; a_name_qualifier_ptr *qualifier_list = NULL; a_boolean is_type = FALSE; a_type_ptr new_type = NULL; a_namespace_ptr new_namespace = NULL; check_assertion(qualifier_sym != NULL || decltype_type != NULL); if (!prototype_instantiations_in_il && is_prototype_instantiation_or_expansion_context() && !scope_stack_top().record_dependent_name_references && (!create_template_deduction_name_references || !is_template_deduction_context())) { /* Don't build name reference information for prototype instantiations when prototype instantiations are not being included in the IL, except in template deduction contexts where the information may be needed for name mangling purposes. record_dependend_name_references is used to force dependent name references to be recorded when they would not otherwise be. */ goto done; } /* if */ if (decltype_type != NULL) { new_type = decltype_type; is_type = TRUE; } else { /* Get the class or namespace represented by the qualifier. A "class" can actually be an enum type or template parameter type in certain cases. */ switch (qualifier_sym->kind) { case sk_class_or_struct_tag: case sk_union_tag: case sk_enum_tag: case sk_type: /* Use the qualifier type. */ new_type = qualifier_type; is_type = TRUE; break; case sk_namespace: /* Get the namespace specified by the qualifier. */ new_namespace = qualifier_sym->variant.namespace_info.ptr; break; default: unexpected_condition(); break; } /* switch */ } /* if */ /* See if there is a list of previously used qualifiers that can be checked for an entry that can be reused. */ if (qualifier_type != NULL && is_class_struct_union_type(qualifier_type)) { a_class_symbol_supplement_ptr cssp; cssp = symbol_supplement_for_class(qualifier_type); qualifier_list = &cssp->name_qualifiers; } else if (qualifier_type != NULL && is_scoped_enum_type(qualifier_type)) { an_enum_symbol_supplement_ptr essp; essp = symbol_supplement_for_enum(qualifier_type); qualifier_list = &essp->name_qualifiers; } else if (qualifier_type != NULL) { /* There is no qualifier list for this case. */ } else if (qualifier_namespace != NULL) { a_namespace_symbol_supplement_ptr nssp; nssp = symbol_supplement_for_namespace(qualifier_namespace); qualifier_list = &nssp->name_qualifiers; } /* if */ /* Look for an entry with the appropriate previous qualifier on the list that was found, if any. */ if (qualifier_list != NULL) { for (new_nqp = *qualifier_list; new_nqp != NULL; new_nqp = new_nqp->next) { if (is_type == new_nqp->is_class && prev_nqp == new_nqp->previous_qualifier) { if (qualifier_sym == NULL) { /* If there is no qualifier symbol (i.e., for some decltype cases), do the comparison based on the type. */ check_assertion(is_type); if (identical_types(new_nqp->qualifier.class_type, new_type)) { break; } /* if */ } else { const char *name; name = new_nqp->name; /* When there is a symbol, do the comparison based on the name. */ if (is_type ? new_type == new_nqp->qualifier.class_type : new_namespace == new_nqp->qualifier.namespace_ptr) { if (strcmp(name, qualifier_sym->header->identifier) == 0) { /* We found a matching entry. */ break; } /* if */ } /* if */ } /* if */ } /* if */ } /* for */ } /* if */ /* If no entry was found, create one now. */ if (new_nqp == NULL) { a_symbol_ptr sym_to_use = qualifier_template_sym != NULL ? qualifier_template_sym : qualifier_sym; new_nqp = alloc_name_qualifier(); new_nqp->previous_qualifier = prev_nqp; new_nqp->is_class = is_type; if (sym_to_use != NULL) { new_nqp->name = copy_string_to_region(file_scope_region_number, sym_to_use->header->identifier); } /* if */ if (is_type) { new_nqp->qualifier.class_type = new_type; } else { new_nqp->qualifier.namespace_ptr = new_namespace; } /* if */ /* If there is a list of qualifier entries, add the new entry to the list. */ if (qualifier_list != NULL) { new_nqp->next = *qualifier_list; *qualifier_list = new_nqp; } /* if */ } /* if */ /* Update the name qualifier pointer passed by the caller. */ *nqp = new_nqp; done: return; } /* make_name_qualifier */ using a_name_references_map = Ptr_map; /* The type of a map that associates source correspondences with a hash table of name references. */ STATIC_THREAD a_name_references_map *name_references_map; /* A map from source correspondence pointers to a hash table of name references. */ a_hash_value hash_name_reference(a_void_ptr key) /* Produce a hash value for a name reference entry. The key is a_name_reference_ptr. */ { a_name_reference_ptr nrp = (a_name_reference_ptr)key; a_hash_value value = 0; value += (a_hash_value)possible_lossy_cast_from_pointer(nrp->qualifier); value += hash_lexical_template_arg_list(nrp->orig_template_arg_list); value += nrp->is_global_qualified_name; value += nrp->is_decltype_qualified * 2; value += nrp->is_template_id * 4; value += nrp->special_kind * 8; value += nrp->is_global_qualified_name * 16; value += nrp->from_prototype_instantiation * 32; value += nrp->is_super_qualified * 64; if (nrp->is_template_id) { value += (a_hash_value)(nrp->num_template_arguments * 128); } /* if */ return value; } /* hash_name_reference */ a_boolean compare_name_reference(a_void_ptr entry, a_void_ptr key) /* Compare an entry in the name references hash table with an entry to be found. "entry" and "key" are of type a_name_reference_ptr. Return TRUE if the key matches the entry. */ { a_name_reference_ptr enrp = (a_name_reference_ptr)entry; a_name_reference_ptr knrp = (a_name_reference_ptr)key; a_boolean result; result = enrp->qualifier == knrp->qualifier && enrp->num_template_arguments == knrp->num_template_arguments && enrp->is_global_qualified_name == knrp->is_global_qualified_name && enrp->is_decltype_qualified == knrp->is_decltype_qualified && enrp->is_template_id == knrp->is_template_id && enrp->special_kind == knrp->special_kind && #if MICROSOFT_EXTENSIONS_ALLOWED && !DO_IL_LOWERING (special_kind_is(enrp, sfk_none) ? enrp->variant.destructor_type == knrp->variant.destructor_type : enrp->variant.property_or_event_descr == knrp->variant.property_or_event_descr) && #else /* !(MICROSOFT_EXTENSIONS_ALLOWED && !DO_IL_LOWERING) */ enrp->variant.destructor_type == knrp->variant.destructor_type && #endif /* MICROSOFT_EXTENSIONS_ALLOWED && !DO_IL_LOWERING */ enrp->from_prototype_instantiation == knrp->from_prototype_instantiation && enrp->is_super_qualified == knrp->is_super_qualified && are_template_args_lexically_identical(enrp->orig_template_arg_list, knrp->orig_template_arg_list); return result; } /* compare_name_reference */ void make_name_reference_from_locator( a_symbol_locator *locator, a_name_reference_ptr nrp) /* Create a name reference entry in the location specified by "nrp" that describes the name specified by "locator". */ { check_assertion(!C_mode()); clear_name_reference(nrp); nrp->qualifier = locator->name_qualifier; nrp->is_global_qualified_name = locator->is_global_qualified_name; nrp->is_template_id = locator->is_template_id; nrp->is_super_qualified = locator->is_super_qualified; nrp->is_decltype_qualified = locator->is_decltype_qualified; nrp->from_prototype_instantiation = is_prototype_instantiation_or_expansion_context(); if (is_dtor_like_locator(*locator)) { a_type_ptr dtor_type = locator->variant.destructor_type; if (dtor_type != NULL) { nrp->variant.destructor_type = dtor_type; } /* if */ } /* if */ if (locator->is_template_id) { /* Set the number of explicitly-specified template arguments appearing in the reference. */ a_template_arg_ptr argp, argp_tail = NULL; nrp->num_template_arguments = 0; begin_template_arg_list_traversal_simple(locator->template_arg_list, &argp); for (; argp != NULL && argp->explicitly_specified; advance_to_next_template_arg_simple(&argp)) { ++nrp->num_template_arguments; argp_tail = argp; } /* for */ /* Only record the template arguments for variables. For functions, we need to wait until overload resolution has selected a specific overload. */ if (!locator->is_error && symbol_is(locator->specific_symbol, sk_variable)) { /* Remove any trailing default template arguments. */ free_template_arg_list(argp); if (argp_tail == NULL) { locator->template_arg_list = NULL; } else { argp_tail->next = NULL; } /* if */ nrp->orig_template_arg_list = locator->template_arg_list; } /* if */ } /* for */ #if DEBUG if (db_flag_is_set("name_refs") && locator->symbol_header != NULL) { db_name_reference(nrp); fprintf(f_debug, " locator name=%s\n", locator->symbol_header->identifier); } /* if */ #endif /* DEBUG */ } /* make_name_reference_from_locator */ a_name_reference_ptr find_allocated_name_reference( a_source_correspondence *scp, a_name_reference_ptr entry_to_copy) /* Return a pointer to a name reference entry that contains the information in "entry_to_copy". "scp" is the source correspondence of the IL entry referred to by the name. The IL entry is currently used only to look for a previously created entry that can be reused. */ { a_name_reference_ptr nrp = NULL; a_name_reference_ptr *nrp_in_table; a_hash_table_ptr htp; check_assertion(!C_mode()); if (!prototype_instantiations_in_il && is_prototype_instantiation_or_expansion_context() && (!create_template_deduction_name_references || !is_template_deduction_context())) { /* Don't build name reference information for prototype instantiations when prototype instantiations are not being included in the IL, except in template deduction contexts where the information may be needed for name mangling purposes. */ goto done; } /* if */ #if DEBUG if (db_flag_is_set("name_refs")) { fprintf(f_debug, "Looking for allocated name reference for: "); db_name_reference(entry_to_copy); if (entry_to_copy->qualifier != NULL) { db_name_qualifier(entry_to_copy->qualifier); } /* if */ fprintf(f_debug, " scp name=%s\n", scp->name); } /* if */ #endif /* DEBUG */ /* Look for a previously-created name reference that matches the information in the locator. */ if (!in_file_scope(scp)) { /* Do a linear search for non-file-scope nodes. */ for (nrp = scp->name_references; nrp != NULL; nrp = nrp->next) { if (compare_name_reference(nrp, entry_to_copy)) { /* A match was found. */ break; } /* if */ } /* for */ if (nrp == NULL) { nrp = alloc_name_reference(); *nrp = *entry_to_copy; /* Put this on the list of name references pointed to by the source correspondence. */ nrp->next = scp->name_references; scp->name_references = nrp; } /* if */ } else { if (scp->name_references == NULL) { /* Create a new name references hash table and associate it with the source correspondence. */ htp = alloc_hash_table(FRONT_END_REGION_NUMBER, (a_hash_table_size)11, fn_for_function(hash_name_reference), fn_for_function(compare_name_reference)); name_references_map->map(scp, htp); } else { htp = name_references_map->get(scp); check_assertion(htp != NULL); } /* if */ nrp_in_table = (a_name_reference_ptr*)hash_find(htp, (a_void_ptr)entry_to_copy, /*create=*/TRUE); if (*nrp_in_table == NULL) { /* No match was found -- create a new entry. */ nrp = alloc_name_reference(); *nrp = *entry_to_copy; #if IL_SHOULD_BE_WRITTEN_TO_FILE if (nrp->orig_template_arg_list != NULL) { /* IL reading cannot deal with shared template argument lists, so make a copy. */ nrp->orig_template_arg_list = copy_template_arg_list(nrp->orig_template_arg_list); } /* if */ #endif /* IL_SHOULD_BE_WRITTEN_TO_FILE */ *nrp_in_table = nrp; /* Put this on the list of name references pointed to by the source correspondence. */ nrp->next = scp->name_references; scp->name_references = nrp; } else { nrp = *nrp_in_table; } /* if */ } /* if */ done: return nrp; } /* find_allocated_name_reference */ a_name_reference_ptr make_name_reference( a_symbol_locator *locator, a_source_correspondence *scp) /* Return a pointer to a name reference entry that describes the name specified by "locator". "scp" is the source correspondence of the IL entry referred to by the name. The IL entry is currently used only to look for a previously created entry that can be reused. */ { a_name_reference nr; a_name_reference_ptr nrp; make_name_reference_from_locator(locator, &nr); nrp = find_allocated_name_reference(scp, &nr); return nrp; } /* make_name_reference */ static a_boolean qualifier_delimiter_does_not_follow_token(void) /* Return TRUE if we can tell that the token following the current token (usually an identifier) is not a "::", a "<", or, in cfront compatibility mode, a ".". These delimiters are used to indicate qualified names, e.g., A::B::x. If it is not easy to tell whether a delimiter appears, the safe answer of FALSE is returned. This routine is part of a speed optimization: if the token following an identifier is clearly not a qualified name delimiter, some expensive processing can be avoided. */ { a_boolean delim_does_not_follow = FALSE; char ch; if (rescanning_cached_tokens()) { /* Tokens are coming from a token cache. The optimization cannot be done in this case, but it is also not really needed because the next token can be inspected quickly when the tokens are coming from a cache. */ /* delim_does_not_follow = FALSE; -- already set. */ } else { /* Skip over any initial white space blanks and horizontal tabs. These are very common, so they're handled inline here. */ if ((ch = *curr_char_loc) == ' ' || ch == '\t') { do {} while ((ch = *(++curr_char_loc)) == ' ' || ch == '\t'); } /* if */ /* We still might need to skip white space if it is a comment or a control character other than a blank or tab. */ if (iscntrl((unsigned char)ch) || ch == '/') { skip_white_space(); ch = *curr_char_loc; } /* if */ if (isdigit((unsigned char)ch)) { delim_does_not_follow = TRUE; } else if (is_identifier_char(curr_char_loc, (int *)NULL, /*is_identifier_start=*/TRUE)) { /* This might be a macro call, so we can't tell. */ /* delim_does_not_follow = FALSE; -- already set. */ } else if (ch == ':' && curr_char_loc[1] == ':') { /* Definitely a "::". */ /* delim_does_not_follow = FALSE; -- already set. */ } else if (ch == '<') { /* A "<" that could be a template argument list delimiter. */ /* delim_does_not_follow = FALSE; -- already set. */ } else if (ch == '.' && (cfront_2_1_mode || microsoft_bugs)) { /* Definitely a "." in cfront and Microsoft bugs modes. */ /* delim_does_not_follow = FALSE; -- already set. */ } else if (ch == '#') { /* The beginning of a preprocessing directive. We don't know what token follows the directive, so assume it can be a qualifier. */ /* delim_does_not_follow = FALSE; -- already set. */ } else { /* Some other operator, e.g., ";" or "(", so delimiter does not follow the token. */ delim_does_not_follow = TRUE; } /* if */ } /* if */ return delim_does_not_follow; } /* qualifier_delimiter_does_not_follow_token */ static a_boolean is_template_from_prototype_instantiation(a_symbol_ptr sym) /* If sym is a member of a prototype instantiation that is a template symbol or an overload set containing a template symbol, return TRUE. Otherwise return FALSE. */ { a_boolean result = FALSE; if (sym->is_class_member) { a_type_ptr parent_tp = sym->parent.class_type; if (is_prototype_instantiation_type(parent_tp)) { if (symbol_is_or_contains_template(sym)) { result = TRUE; } /* if */ } /* if */ } /* if */ return result; } /* is_template_from_prototype_instantiation */ static a_symbol_ptr select_dual_lookup_symbol( a_type_ptr field_sel_type, a_symbol_ptr normal_fund_sym, a_symbol_ptr normal_sym, a_symbol_ptr class_fund_sym, a_symbol_ptr class_sym, a_boolean follows_template, a_boolean might_be_template, a_boolean prefer_class_member) /* normal_fund_sym and class_fund_sym are the results of a normal and class-qualified ID lookup, respectively. normal_sym and class_sym are the symbols placed in the specific_symbol field by those lookups, and which may point to projection symbols. follows_template is TRUE if the name is preceded by the "template" keyword. might_be_template is TRUE if the name being looked up is followed by a "<". Reconcile the two symbols according to the rules for the dual lookup, issue any diagnostics that might be needed, and return the symbol to be used. Set the specific symbol to the associated nonfundamental symbol. If prefer_class_member is TRUE, the class member is preferred over the normal lookup symbol. field_sel_type is the class type of the left operand of a field selection, or NULL if the lookup is not in the context of a field selection. */ { a_symbol_ptr result_sym; a_symbol_ptr specific_symbol; /* This implements the special handling in 3.4.5 (basic.lookup.classref) of possible template names in class member access expressions (e.g., "p->f<..."). If the name is found in the class, the class symbol should be used unless it is a class template, in which case the normal symbol can also be considered if it is also a class template. */ /* This assertion is to make the Coverity tool know that normal_fund_sym will be non-NULL if normal_sym is. */ check_assertion((normal_sym == NULL) == (normal_fund_sym == NULL)); if (normal_sym != NULL && class_sym != NULL && might_be_template) { a_boolean gpp_microsoft_mode_case = FALSE; if (is_template_symbol(class_fund_sym)) { /* When the identifier is followed by a "<", and the name is found as a template in the class, ignore the other symbol unless it is a class template. */ if (normal_sym != NULL && !is_class_template_or_injected_template_symbol(normal_fund_sym)) { normal_sym = NULL; } /* if */ } else if (is_constructor_symbol(class_sym)) { /* The class symbol is the constructor. Ignore this for purposes of this lookup, because the constructor cannot be referenced by name in this context. */ class_sym = NULL; } else if (class_fund_sym->is_nonreal_member && !is_template_symbol(class_fund_sym)) { /* The class symbol is a nonreal member that is not a template. */ if (is_class_or_injected_template_symbol(normal_fund_sym) || (gpp_microsoft_mode_case = (symbol_is_or_contains_template(normal_fund_sym) && (gpp_version_is(< 60000) || microsoft_mode)))) { /*lint !e820*/ /* The class symbol is a nonreal nontemplate and the normal symbol is a class template. Use the normal symbol. In g++ (prior to 6.x) and Microsoft mode, a function template or overload set containing a function template causes the template symbol to be returned (except in dependent cases (see below)). */ if (gpp_microsoft_mode_case && is_template_dependent_context() && is_nontype_template_param_symbol(class_fund_sym)) { /* In g++ and Microsoft mode, a reference like "t->f<1>()" is accepted, but in order to be represented properly in the IL we need to return a template symbol instead of a constant. Redo the lookup in such a way as to create a nonreal template symbol. Clear the normal_sym so that the newly created template will be used. Note that this only occurs in Microsoft mode if parsing of nonclass templates has been enabled. */ clear_specific_symbol(locator_for_curr_id); check_assertion(field_sel_type != NULL); class_sym = class_qualified_id_lookup(&locator_for_curr_id, field_sel_type, IDL_TREAT_AS_TEMPLATE_ID); class_fund_sym = class_sym == NULL ? NULL : fundamental_symbol_of(class_sym); normal_sym = normal_fund_sym = NULL; } else { class_sym = NULL; } /* if */ } else if (gpp_version_is(< 70000) && is_template_from_prototype_instantiation(normal_sym)) { /* Most of the special g++ behavior is handled above, but version 6.x still finds templates from the prototype instantiation via normal lookup. */ class_sym = NULL; } else { /* In other cases, use the class symbol. */ normal_sym = NULL; } /* if */ } else { /* The name is a member of the class that is not a nonreal template. Use that name and ignore the normal lookup name. */ normal_sym = NULL; } /* if */ } /* if */ if (normal_sym != NULL && class_sym != NULL) { /* There are two symbols -- see if they are equivalent. If the normal symbol refers to a class and the class symbol refers to the constructor, they are considered equivalent. */ a_boolean equiv_symbols = FALSE; a_boolean use_normal_if_equiv = TRUE; if (normal_fund_sym == class_fund_sym) { equiv_symbols = TRUE; } else if (is_class_symbol(normal_fund_sym) && is_injected_class_symbol(class_fund_sym)) { /* The normal symbol is a class and the class symbol is an injected class name. They are equivalent if they refer to the same type. */ equiv_symbols = identical_types(type_symbol_type(normal_fund_sym), class_fund_sym->variant.type.ptr); } else if (is_class_template_symbol(normal_fund_sym) && is_injected_template_symbol(class_fund_sym)) { /* The normal symbol is a class template and the class symbol is an injected template name. They are equivalent if the template associated with the injected name is the same as the class template. If they are equivalent, use the class symbol because it is the one for which a following template argument list is optional. */ equiv_symbols = normal_sym == class_template_for_injected_template_symbol(class_fund_sym); use_normal_if_equiv = FALSE; } else if (is_constructor_symbol(class_fund_sym) && is_class_symbol(normal_fund_sym)) { a_type_ptr normal_type; normal_type = type_symbol_type(normal_fund_sym); if (identical_types(normal_type, sym_parent_class(class_fund_sym))) { equiv_symbols = TRUE; } /* if */ } else if (class_fund_sym->is_nonreal_member && (follows_template && is_template_symbol(class_fund_sym))) { /* The class symbol is nonreal but is a template and follows the "template" keyword. Use that for now. The normal symbol might be used in a real instantiation, but we can't use that now because the argument list might not be appropriate for that template. */ equiv_symbols = TRUE; use_normal_if_equiv = FALSE; } else if (class_fund_sym->is_nonreal_member) { /* The class symbol is nonreal. Use the normal symbol for now. */ equiv_symbols = TRUE; } else if (is_type_symbol(normal_fund_sym) && is_type_symbol(class_fund_sym)) { if (cpp11_mode || microsoft_mode || (gpp_mode && gnu_version >= 30300)) { /* In C++11, Microsoft and (most) g++ modes, prefer the class symbol over the normal lookup symbol. The symbols are not really equivalent, but we use that flag to indicate whether this case should be diagnosed below. */ equiv_symbols = TRUE; use_normal_if_equiv = FALSE; } else { /* For type symbols, the symbols are equivalent if the underlying types are the same. */ a_type_ptr class_type; a_type_ptr normal_type; normal_type = type_symbol_type(normal_fund_sym); class_type = type_symbol_type(class_fund_sym); equiv_symbols = identical_types(class_type, normal_type); } /* if */ } /* if */ if (equiv_symbols) { /* The symbols are equivalent. Use the normal symbol unless otherwise specified. */ if (use_normal_if_equiv) { result_sym = normal_fund_sym; specific_symbol = normal_sym; } else { result_sym = class_fund_sym; specific_symbol = class_sym; } /* if */ } else { /* The symbols are not equivalent. Issue a diagnostic. When the name is followed by a "::" (when might_be_template is FALSE) the normal lookup symbol is used to duplicate the behavior that existed before the dual lookup was implemented. When the name is followed by a "<" (might_be_template is TRUE), or if prefer_class_member is TRUE, the class symbol is preferred. This is also done for compatibility to prevent something like "p->f < 1" from breaking when a global template "f" is declared. */ an_error_severity severity = strict_ansi_error_severity; a_symbol_ptr sym_to_use; a_symbol_ptr diag_sym_to_use; a_symbol_ptr sym_to_ignore; a_symbol_ptr diag_class_sym; /* If the class symbol is a constructor, use the class symbol in place of the constructor symbol for error reporting. */ if (is_constructor_symbol(class_fund_sym)) { diag_class_sym = symbol_for(sym_parent_class(class_fund_sym)); } else { diag_class_sym = class_fund_sym; } /* if */ /* In C++11 mode, the dual lookup is no longer "officially" done, so no diagnostic is issued. Because of the need to handle things in template contexts properly, we still use the normal symbol in some cases in C++11 mode, but in no case is it diagnosed. As part of the change to use the class member in C++11 mode, older modes have also been changed because we already used the class symbol in Microsoft and g++ modes, and to eliminate this as a unnecessary change in default behavior between C++03 mode and C++11 mode. A warning (or error, in strict mode) is still issued in normal C++03 mode. */ if (might_be_template || prefer_class_member || (!symbol_is(normal_fund_sym, sk_namespace) && class_fund_sym == class_sym)) { sym_to_use = class_fund_sym; diag_sym_to_use = diag_class_sym; specific_symbol = class_sym; sym_to_ignore = normal_fund_sym; if (might_be_template && !strict_ansi_mode) severity = es_warning; } else { sym_to_use = normal_fund_sym; diag_sym_to_use = sym_to_use; specific_symbol = normal_sym; sym_to_ignore = diag_class_sym; } /* if */ if (!cpp11_mode) { pos_sy2_diagnostic(severity, ec_dual_lookup_ambiguous_name, &error_position, diag_sym_to_use, sym_to_ignore); } /* if */ result_sym = sym_to_use; } /* if */ } else { /* One or both are NULL. If one is non-NULL, return that one, otherwise return a NULL. */ if (normal_sym != NULL) { result_sym = normal_fund_sym; specific_symbol = normal_sym; } else { result_sym = class_fund_sym; specific_symbol = class_sym; } /* if */ } /* if */ locator_for_curr_id.specific_symbol = specific_symbol; return result_sym; } /* select_dual_lookup_symbol */ static a_boolean is_microsoft_qualifier_start(a_symbol_ptr sym) /* In a construct like "X::Y", the Microsoft compiler does not consider this to be a qualification of X when X is a typedef to a non-class type. In other words, this is treated as "X" and "::Y" by the Microsoft compiler. */ { a_boolean result = TRUE; if (sym != NULL && sym->kind == (a_symbol_kind)sk_type) { a_type_ptr tp; tp = sym->variant.type.ptr; tp = skip_typerefs(tp); /* A class, struct, union, or template parameter type is permitted as the start of a qualified name. */ switch (tp->kind) { case tk_class: case tk_struct: case tk_union: case tk_template_param: break; case tk_enum: result = tp->source_corresp.is_class_member || microsoft_version >= 1400; break; default: result = FALSE; break; } /* switch */ } /* if */ return result; } /* is_microsoft_qualifier_start */ static a_symbol_ptr look_up_qualifier_start( an_id_lookup_options_set lookup_kind, a_type_ptr class_type, a_boolean might_be_vacuous_dtor_or_finalizer, a_boolean *is_vacuous_dtor_or_finalizer, a_boolean follows_template, a_boolean might_be_template, a_boolean in_if_exists, a_boolean prefer_class_member, a_boolean is_cli_typeid) /* This routine does the "dual lookup" that is done in contexts such as the "A" in "p->A::B" and "f" in "p->f<...>...". This involves looking the name up using a "normal" lookup and also looking it up in the class type of the left operand of the "." or "->". follows_template is TRUE if the name is preceded by the "template" keyword. might_be_template is TRUE if the token after the identifier is a "<". might_be_vacuous_dtor_or_finalizer is TRUE if the name being looked up is followed by "::~"/"::!", and a vacuous destructor/finalizer is valid in the current context. *is_vacuous_dtor_or_finalizer is set to TRUE if a symbol that can only be a vacuous destructor/finalizer is returned. class_type is the class type of the left operand of the field selection. in_if_exists is TRUE when scanning the identifier of a Microsoft __if_exists or __if_not_exists directive. If prefer_class_member is TRUE, the class member is preferred over the normal lookup symbol. is_cli_typeid is TRUE if we are scanning a C++/CLI typeid of the form X::typeid. */ { a_symbol_ptr normal_sym; a_symbol_ptr class_sym; a_symbol_ptr normal_fund_sym; a_symbol_ptr class_fund_sym; a_symbol_ptr sym = NULL; an_id_lookup_options_set normal_lookup_kind = lookup_kind; a_boolean do_class_lookup; /* Do the lookup if a type was provided that is a class type that is either complete or in the process of being defined. Also do the lookup if the class is nonreal. */ do_class_lookup = class_type != NULL && is_class_struct_union_type(class_type) && (!scope_is_null_or_placeholder(class_type_supp(class_type)->assoc_scope)|| (class_type->variant.class_struct_union.is_nonreal_class && !class_type->variant.class_struct_union.is_prototype_instantiation)); /* Only get normal_sym from the locator if a fundamental symbol was returned by the lookup. The specific symbol in the locator could be non-NULL in error cases. If a tentative template lookup was specified, convert this to a regular lookup (but one that does not create projection symbols) when we are in C++20 mode and treat certain names as potential templates. In addition, when we are caching tokens for disambiguation, don't create a projection symbol for tentative template lookups as this can impact lookup results. */ if ((normal_lookup_kind & IDL_TENTATIVE_TEMPLATE_LOOKUP) != 0) { if (adl_for_non_visible_templates) { normal_lookup_kind &= ~IDL_TENTATIVE_TEMPLATE_LOOKUP; normal_lookup_kind |= IDL_DO_NOT_CREATE_PROJ_SYM; } else if (caching_tokens && scope_stack_top().in_disambiguation) { normal_lookup_kind |= IDL_DO_NOT_CREATE_PROJ_SYM; } /* if */ } /* if */ normal_fund_sym = normal_id_lookup(&locator_for_curr_id, normal_lookup_kind); normal_sym = normal_fund_sym == NULL ? NULL : locator_for_curr_id.specific_symbol; if (do_class_lookup) { clear_specific_symbol(locator_for_curr_id); /* These lookups are speculative -- don't create projection symbols for them. */ lookup_kind |= IDL_DO_NOT_CREATE_PROJ_SYM | IDL_IS_FIELD_SELECTION_OPERAND; /* Only get class_sym from the locator if a fundamental symbol was returned by the lookup. The specific symbol in the locator could be non-NULL in error cases. */ class_fund_sym = class_qualified_id_lookup(&locator_for_curr_id, class_type, lookup_kind); class_sym = class_fund_sym == NULL ? NULL : locator_for_curr_id.specific_symbol; sym = select_dual_lookup_symbol(class_type, normal_fund_sym, normal_sym, class_fund_sym, class_sym, follows_template, might_be_template, prefer_class_member); } else { sym = normal_fund_sym; } /* if */ if (sym != NULL && might_be_vacuous_dtor_or_finalizer) { /* If the lookup above returned something that is only valid as a vacuous destructor, set the is_vacuous destructor flag. */ a_boolean only_valid_as_vacuous_dtor = FALSE; if (is_enum_symbol(sym)) { only_valid_as_vacuous_dtor = !is_valid_enum_qualifier_symbol(sym); } else if (sym->kind == (a_symbol_kind)sk_type && !is_class_symbol(sym) && !is_template_param_type_symbol(sym)) { only_valid_as_vacuous_dtor = TRUE; } /* if */ *is_vacuous_dtor_or_finalizer = only_valid_as_vacuous_dtor; } else if (sym == NULL && (might_be_vacuous_dtor_or_finalizer || is_cli_typeid || (microsoft_bugs && microsoft_version < 1300 && !in_if_exists))) { /* The lookup has failed so far. If this might be a vacuous destructor, do a more general lookup to find a nonclass type that might be used as a qualifier for a vacuous destructor. The more general lookup is also done in Microsoft bugs mode. The Microsoft compiler does not consider an identifier to start a qualified name if this lookup finds a type that is not a class, enum, or template parameter. */ normal_fund_sym = normal_id_lookup(&locator_for_curr_id, IDL_NO_OPTIONS); normal_sym = locator_for_curr_id.specific_symbol; if (do_class_lookup) { clear_specific_symbol(locator_for_curr_id); class_fund_sym = class_qualified_id_lookup(&locator_for_curr_id, class_type, IDL_NO_OPTIONS); class_sym = class_fund_sym == NULL ? NULL : locator_for_curr_id.specific_symbol; sym = select_dual_lookup_symbol(class_type, normal_fund_sym, normal_sym, class_fund_sym, class_sym, follows_template, might_be_template, prefer_class_member); } else { sym = normal_fund_sym; } /* if */ if (sym != NULL && !is_cli_typeid && !might_be_vacuous_dtor_or_finalizer) { /* In Microsoft bugs mode, ignore this symbol unless it is one that actually does not indicate the start of a qualified name. */ if (is_microsoft_qualifier_start(sym)) sym = NULL; } /* if */ *is_vacuous_dtor_or_finalizer = might_be_vacuous_dtor_or_finalizer; } /* if */ return sym; } /* look_up_qualifier_start */ static a_boolean sym_can_follow_template_keyword(a_symbol_ptr sym) /* In a construct like "p->template X<...>" or "p->T::template X<...>", this routine determines whether the symbol (representing X) is permitted to follow the template keyword. */ { a_boolean result = FALSE; /* The symbol must represent a template or an overload set containing a template. */ if (symbol_is_or_contains_template(sym)) { /* Originally, the symbol after the template keyword was required to name a member template. Core issue 228 modified this rule so that any template symbol is acceptable. */ result = TRUE; } /* if */ return result; } /* sym_can_follow_template_keyword */ static a_boolean gpp_omitted_template_okay(a_type_ptr tp) /* g++ versions prior to 4.1.1 allow the "template" keyword to be omitted in some cases when referring to a member class template. It may be omitted when the template argument list matches the implied template argument list of the prototype instantiation. In the example below, in A::B<...> the template parameter T has the same coordinates as the T of the prototype instantiation of A. template struct A { template struct B {}; }; template struct C { A::B ab1; // g++ accepts A::B ab2; // g++ accepts A::B ab3; // g++ gives error }; Return TRUE if it okay to omit the "template" keyword in this case. tp is the qualifier type. */ { a_boolean result = FALSE; if (is_immediate_class_type(tp)) { /* We only need to check nonreal types that are not prototype instantiations. */ if (tp->variant.class_struct_union.is_nonreal_class && !tp->variant.class_struct_union.is_prototype_instantiation) { a_template_arg_ptr nonreal_list; nonreal_list = tp->variant.class_struct_union.extra_info->template_arg_list; /* Only process classes that have template argument lists. */ if (nonreal_list != NULL) { a_symbol_ptr class_sym = symbol_for(tp); a_symbol_ptr template_sym; a_symbol_ptr prototype_sym; a_template_symbol_supplement_ptr tssp; a_template_arg_ptr prototype_list; /* Get the symbol for the template from which this class was generated. */ template_sym = template_symbol_for_class_symbol(class_sym); tssp = template_supplement_for_symbol(template_sym); /* If this is a class template defined within another class template, the prototype instantiation is associated with the definition within the original template. Get a pointer to the template symbol that is associated with the prototype instantiation. */ if (tssp->prototype_template != NULL && !tssp->is_specific_definition) { template_sym = tssp->prototype_template; tssp = template_supplement_for_symbol(template_sym); } /* if */ /* Get the template argument list from the prototype instantiation. */ prototype_sym = tssp->variant.class_template.prototype_instantiation; /* The will be no prototype instantiation for nonreal templates. */ if (prototype_sym != NULL) { prototype_list = prototype_sym->variant.class_struct_union.type-> variant.class_struct_union.extra_info->template_arg_list; /* See if the argument lists match. */ if (equiv_template_arg_lists(nonreal_list, prototype_list, ETA_IS_NONREAL_MEMBER)) { result = TRUE; } /* if */ } /* if */ } /* if */ } /* if */ } /* if */ return result; } /* gpp_omitted_template_okay */ /* Macro that calls qualifier_delimiter_does_not_follow_token to determine whether the next token may be one of the tokens that f_is_generalized_identifier_start needs to inspect and, if so, calls next_two_tokens; Otherwise, second_token is set to tok_error and tok_error is returned. */ /*lint -emacro(641,next_two_tokens_if_qualifier_delimiter)*/ #define next_two_tokens_if_qualifier_delimiter(separator, second_token) \ ((a_token_kind)(qualifier_delimiter_does_not_follow_token() ? \ (*(second_token) = tok_error), tok_error : \ next_two_tokens(separator, second_token))) /* Macro that returns whether the given token is "~" (which may introduce a destructor), or, in C++/CLI mode, "!" (which may introduce a finalizer). */ #if MICROSOFT_EXTENSIONS_ALLOWED #define is_dtor_or_finalizer_token(tok) \ ((tok) == tok_compl || (cli_or_cx_enabled && (tok) == tok_not)) #else /* !MICROSOFT_EXTENSIONS_ALLOWED */ #define is_dtor_or_finalizer_token(tok) \ ((tok) == tok_compl) #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ a_boolean spliced_name_qualifier_next(void) /* The current token is "[:". Return TRUE if the matching ":]" is followed by "::". */ { a_boolean result = FALSE; a_scanning_token_cache cache; if (!cache_token_stream_until_matching_token(cache.ptr(), CTS_NO_OPTIONS)) { check_assertion(curr_token == tok_rsplice); cache_curr_token(cache.ptr()); (void)get_token(); result = curr_token == tok_colon_colon; } /* if */ rescan_cached_tokens(cache.ptr()); return result; } /* spliced_name_qualifier_next */ a_boolean pack_index_next(void) /* Return TRUE if the current token sequence starts with a C++26 pack-index construct of the form: identifier ... [ constant-expression ] */ { a_boolean result = FALSE; if (pack_indexing_allowed && curr_token == tok_identifier) { a_token_kind token_2 = tok_error; if (next_two_tokens(tok_ellipsis, &token_2) == tok_ellipsis && token_2 == tok_lbracket) { a_tiny_scanning_token_cache cache; cache_curr_token(cache.ptr()); (void)get_token(); cache_curr_token(cache.ptr()); (void)get_token(); cache_curr_token(cache.ptr()); /* An empty "[]" is not a pack-index-specifier. In a declaration like "T ... []", the brackets are an array declarator following the function parameter pack ellipsis. */ if (get_token() != tok_rbracket) result = TRUE; rescan_cached_tokens(cache.ptr()); } /* if */ } /* if */ return result; } /* pack_index_next */ a_token_kind token_kind_following_pack_index_specifier(void) /* The current token is an identifier. If this begins a C++26 pack-index-specifier of the form type-name ... [ constant-expression ] return the token kind that follows the closing "]". Return tok_error otherwise. This routine must peek without consuming tokens: it is called during speculative lookahead (e.g., from is_decl_not_expr_full) where get_token-based caching would be unsafe because fetching through the end of an active reusable cache may cause the cache to be popped. */ { a_token_kind result = tok_error; if (pack_index_next()) { a_cached_token_supplier token_supp; an_immutable_cached_token *token; unsigned depth = 0; /* pack_index_next guaranteed that the next two tokens are "..." and "[". Step past them. */ token_supp++; token_supp++; /* Skip to the matching closing "]" of the index expression. */ for (;;) { token = *(token_supp++); if (token == NULL || token->is(tok_end_of_source)) goto done; switch (token->get_kind()) { case tok_lparen: case tok_lbracket: case tok_lbrace: case tok_lsplice: depth++; break; case tok_rparen: case tok_rbrace: case tok_rsplice: if (depth > 0) depth--; break; case tok_rbracket: if (depth == 0) { /* Found the matching "]". */ token = *(token_supp++); if (token != NULL) result = token->get_kind(); goto done; } /* if */ depth--; break; default:; } /* switch */ } /* for */ } /* if */ done: return result; } /* token_kind_following_pack_index_specifier */ static a_boolean pack_index_name_qualifier_next(void) /* The current token is an identifier. Return TRUE if this starts a C++26 pack-index-specifier that is followed by "::": type-name ... [ constant-expression ] :: */ { return token_kind_following_pack_index_specifier() == tok_colon_colon; } /* pack_index_name_qualifier_next */ static a_symbol_ptr make_intrinsic_member_type_symbol( a_symbol_header_ptr member_hdr, a_type_ptr result_tp, a_source_position *position) /* Create and return a type symbol named member_hdr (e.g., "type") whose type is result_tp, the type to which an intrinsically-resolved xyz::member expands. The symbol's type is a named tk_typeref carrying member_hdr, so that source regeneration reproduces the original member spelling rather than the name of the resolved type. The symbol is not entered into any scope and is not recorded as a member of xyz, so referring to it does not request the instantiation of xyz. Its declaration scope is set to the file scope of the translation unit being compiled so that, when several translation units are compiled together, the synthesized member type's IL is attributed to the correct translation unit. */ { a_symbol_ptr member_sym = alloc_symbol(sk_type, member_hdr, position); a_type_ptr member_typeref = alloc_type(tk_typeref); member_sym->decl_scope = file_scope_number; member_typeref->variant.typeref.type = result_tp; member_typeref->variant.typeref.is_intrinsic_member = TRUE; set_source_corresp(&member_typeref->source_corresp, member_sym); member_sym->variant.type.ptr = member_typeref; /* alloc_type places member_typeref in the file scope memory region, and the synthesized symbol is attributed to the file scope (see decl_scope above). Record the typeref on the file scope types list so that it is reachable by the file scope IL walk. */ add_to_types_list(member_typeref, DEPTH_OF_FILE_SCOPE); return member_sym; } /* make_intrinsic_member_type_symbol */ static a_boolean resolve_intrinsic_templ_type_member( a_symbol_locator *locator, a_type_ptr qualifier_type, a_boolean *no_such_member) /* If the qualified name described by locator (whose final component is named by locator->symbol_header) denotes a member of the instance qualifier_type that is resolved intrinsically, resolve it without completing qualifier_type and return TRUE. On successful resolution, set locator->specific_symbol to a synthesized type symbol denoting the member's type. If the member provably does not exist, set *no_such_member to TRUE and return TRUE without issuing a diagnostic: the caller reports the missing member, applying the same tolerance as an ordinary failed member lookup (a tentative type lookup or an __if_exists query may fail silently; in particular this is what makes a delayed Microsoft default template argument such as enable_if::type acceptable when it is never needed). Otherwise return FALSE so the caller falls back to ordinary lookup. */ { a_boolean handled = FALSE; a_type_ptr result_tp = NULL; *no_such_member = FALSE; if (intrinsic_templ_type_member_lookup(qualifier_type, locator->symbol_header, &result_tp, no_such_member)) { handled = TRUE; if (!*no_such_member) { /* Record the resolved member in the locator. The qualifier portion of the spelling (xyz::) is supplied by the locator's name qualifier (see make_typeref_with_lexical_information). Because the lookup never completes qualifier_type, referring to the resolved name does not request its instantiation. */ locator->specific_symbol = make_intrinsic_member_type_symbol( locator->symbol_header, result_tp, &locator->source_position); } /* if */ } /* if */ return handled; } /* resolve_intrinsic_templ_type_member */ a_boolean f_is_generalized_identifier_start( an_identifier_options_set options, a_type_ptr field_sel_type) /* Determine whether the current token is the start of a "generalized identifier" -- a qualified name, identifier, operator name, conversion name, destructor name, or C++/CLI finalizer name. If the token does begin an identifier, the tokens that make up the identifier are coalesced into a single token. This consists of scanning the tokens that make up the identifier, retaining the information conveyed by the tokens in the symbol locator, setting curr_token to tok_identifier, and setting pos_curr_token to the position of the first token in the sequence. We return TRUE if an identifier was found, otherwise we return FALSE. Pointer to members are coalesced into a tok_ptr_to_member. We return FALSE for pointer to members. Similarly, the C++/CLI X::typeid construct is coalesced into a tok_cli_typeid and FALSE is returned. Returns TRUE and sets curr_token to tok_identifier for the following cases: ::i X::i ::X::i ::A::B::i A::i A::operator = A::operator int A::~A() A:: ... anything except * ... NS::i NS::A::i decltype(expr)::something T...[N]::something i operator = operator int ~A When options & GID_DTOR_RECOGNIZED = TRUE !A When options & GID_DTOR_RECOGNIZED and cli_or_cx_enabled are both TRUE A Template reference will be coalesced NS::A Template reference will be coalesced int::~int When options & GID_VACUOUS_DTOR_RECOGNIZED = TRUE int::!int When options & GID_VACUOUS_DTOR_RECOGNIZED and cli_or_cx_enabled are both TRUE Returns FALSE and sets curr_token to tok_ptr_to_member for: X::* A::* Template reference will be coalesced Returns FALSE and sets curr_token to tok_cli_typeid for the C++/CLI constructs: X::typeid A::typeid Template reference will be coalesced Returns FALSE and sets curr_token to tok_declspec_construct for: decltype(expr) When not followed by :: Returns FALSE and leaves curr_token_unchanged for: ::new ::delete ~name When options & GID_DTOR_RECOGNIZED = FALSE !name When options & GID_DTOR_RECOGNIZED or cli_or_cx_enabled is FALSE anything else When the GID_TEMPLATE_ARGS_OPTIONAL flag is set in "options" the template argument list (e.g., "") can be omitted. This flag is passed to coalesce_template_class_reference. The "options" parameter allows the caller to specify the types of identifiers to be recognized and/or allowed. When the GID_VACUOUS_DTOR_RECOGNIZED flag is set in "options" a qualified destructor/finalizer name will be recognized for non-class types and for class types that have no destructors/finalizers. This is used to handle constructs such as "p->int::~int". Note that the parent.class_type field in the locator normally contains the type of the qualifier portion of a qualified name. For nonclass vacuous destructors/finalizers, however, it contains the type of the thing after the "::~"/"::!". This is necessary because some vacuous destructors/finalizers may not have a qualifier and the type information is still needed by the caller in this case. So qualifier_type starts out with the qualifier type and is updated by the vacuous destructor/finalizer code to contain the type of the destructor/finalizer name following the "::~"/"::!". This really only matters for error handling because in nonerror cases the two types will be the same. If the token following a qualifier is not part of a valid identifier we still return TRUE so that an appropriate diagnostic can be generated when an attempt is made to use the thing after the qualifier. This routine performs ambiguity and access checking on the components of the qualified name. field_sel_type is NULL except when scanning the right operand of a field selection operator, in which case it points to the type of the left operand. */ { a_type_ptr qualifier_type = NULL; a_type_ptr qualifier_lexical_type = NULL; a_boolean is_file_scope_qualified_name = FALSE; a_boolean is_global_qualified_name = FALSE; a_boolean is_qualified_name = FALSE; a_boolean is_ptr_to_member = FALSE; a_boolean is_identifier = FALSE; a_symbol_ptr qualifier_sym = NULL; a_symbol_ptr fund_qualifier_sym; a_symbol_ptr specific_sym = NULL; a_source_position start_position; a_source_position orig_error_position; a_token_kind next_tok = tok_error; a_token_kind next_tok_2; a_boolean result = FALSE; a_boolean err = FALSE; a_boolean can_be_vacuous_dtor_or_finalizer = (options & GID_VACUOUS_DTOR_RECOGNIZED) != 0; a_boolean dtor_or_finalizer_must_be_nonclass = (options & GID_DTOR_MUST_BE_NONCLASS) != 0; a_boolean is_dtor_or_finalizer = FALSE; a_boolean is_vacuous_dtor_or_finalizer = FALSE; a_boolean is_nonclass_dtor_or_finalizer = FALSE; a_type_ptr dtor_or_finalizer_class_type = NULL; a_type_ptr dtor_or_finalizer_type = NULL; a_boolean might_be_qualifier; a_token_kind qualifier_separator = tok_colon_colon; a_boolean qualifier_is_type = TRUE; a_boolean qualifier_is_enum = FALSE; #if MICROSOFT_EXTENSIONS_ALLOWED a_boolean qualifier_is_property_or_event = FALSE; a_symbol_ptr qualifier_property_or_event = NULL; a_type_ptr cli_system_type_for_keyword = NULL; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ a_boolean is_cli_typeid = FALSE; a_boolean qualifier_type_is_class = FALSE; a_namespace_ptr qualifier_namespace = NULL; a_token_sequence_number start_seq_number; a_boolean follows_template, is_dtor_name; a_boolean qualifier_is_super = FALSE; a_boolean is_super_qualified = FALSE; a_boolean is_conversion_type = FALSE; a_boolean qualifier_is_decltype = FALSE, qualifier_is_splice = FALSE, qualifier_is_pack_index = FALSE; a_boolean is_decltype_qualified = FALSE; a_type_ptr decltype_type = NULL; a_boolean qualified_conversion_operator = FALSE; a_boolean separator_warning_issued = FALSE; a_name_qualifier_ptr name_qualifier = NULL; a_symbol_ptr qualifier_template_sym = NULL; #if CREATE_LEXICAL_TYPEREFS a_template_arg_ptr orig_arg_list = NULL; #endif /* CREATE_LEXICAL_TYPEREFS */ /* Macro used to determine whether we are processing the identifier in a Microsoft __if_exists or __if_not_exists directive. */ #define in_if_exists ((options & GID_IN_IF_EXISTS) != 0) db_enter(4, "f_is_generalized_identifier_start"); /* If the current token is an identifier, then check the flag in the locator to see if it has already been coalesced. If so, simply return TRUE with no further processing. If the current token is a tok_ptr_to_member or tok_cli_typeid, return FALSE with no further processing. */ if (curr_token == tok_identifier && locator_for_curr_id.has_been_coalesced) { result = TRUE; goto exit; } else if (curr_token == tok_ptr_to_member if_microsoft_extensions(|| curr_token == tok_cli_typeid)) { result = FALSE; goto exit; } /* if */ start_position = pos_curr_token; start_seq_number = curr_token_sequence_number; orig_error_position = error_position; if (C_dialect != C_dialect_cplusplus) { /* Skip qualifier, destructor, finalizer, and operator processing if not in C++ mode. If we have an identifier go to the code that updates the locator. If we don't have an identifier, simply return. */ result = curr_token == tok_identifier; if (result) goto wrapup; goto exit; } /* if */ if (field_sel_type == NULL) { /* If no field selection type was specified, use the conversion parent type from the scope stack entry if one is present. This is set when scanning a type conversion operator. */ a_scope_stack_entry_ptr ssep = &scope_stack[depth_scope_stack]; field_sel_type = ssep->conversion_parent_type; qualified_conversion_operator = ssep->qualified_conversion_operator; ssep->conversion_parent_type = NULL; is_conversion_type = field_sel_type != NULL; } /* if */ follows_template = (options & GID_FOLLOWS_TEMPLATE) != 0; is_dtor_name = (options & GID_IS_DTOR_NAME) != 0; /* Look for a leading unary "::". Don't be fooled by "::new" and "::delete". Don't treat ::* as a pointer to member declarator. ::* would be rejected below as a pointer to member declarator, but doing so here provides better error recovery. */ if (curr_token == tok_colon_colon && !is_global_new_or_delete() && next_token() != tok_star) { is_global_qualified_name = TRUE; is_file_scope_qualified_name = TRUE; is_qualified_name = TRUE; if (follows_template) { /* "p->template ::..." is not valid. */ pos_error(ec_exp_identifier, &error_position); follows_template = FALSE; } /* if */ (void)get_token(); if (curr_token == tok_template) { /* Core issue 1929 clarified that the template keyword may be used following the global namespace qualifier. */ follows_template = TRUE; if (!cpp11_mode && strict_ansi_mode && !is_template_context() && !in_if_exists) { /* In strict C++98 mode the template keyword, when used for syntactic disambiguation, may only appear within a template. */ diagnostic(strict_ansi_discretionary_severity, ec_template_not_in_template); } /* if */ (void)get_token(); } /* if */ } /* if */ if (curr_token == tok_operator) { get_opname(/*is_class_member=*/FALSE, (a_parent_class_or_namespace*)NULL, field_sel_type); if (microsoft_mode && !follows_template && field_sel_type != NULL && is_template_dependent_context() && next_token() == tok_lt) { /* MSVC accepts "x.operator+()" with a dependent x, but not "x.template operator+()". We therefore treat the special case of an operator name followed by "<" in a template-dependent field selection context as if it were prefixed with the "template" keyword. */ follows_template = TRUE; } /* if */ } /* if */ /* For the next token to be part of the qualifier it must be a class name followed by "::". Templates make it more difficult to detect this situation so we accept an identifier followed by either a "::" or a left angle bracket. A vacuous destructor/finalizer reference can also begin with an identifier that is a type name or a token that begins a simple type name. The function "type_keyword" will detect a token that begins a simple type. We will check later to determine whether the identifier is a class name or a type name, if needed. */ might_be_qualifier = FALSE; if (curr_token == tok_identifier || is_resolved_id_pseudo_token(curr_token)) { next_tok = next_two_tokens_if_qualifier_delimiter(tok_colon_colon, &next_tok_2); if (next_tok == tok_colon_colon || next_tok == tok_lt || follows_template || is_conversion_type) { might_be_qualifier = TRUE; } else if ((cfront_2_1_mode || (microsoft_bugs && microsoft_version <= 1500)) && next_tok == tok_period && !(options & GID_IS_FIELD_SELECTION_OPERAND)) { /* Check for the anachronism of allowing a "." as a qualifier separator where a "::" should be used. This is done in cfront and Microsoft bugs modes. This is something that cfront labels as an anachronism but is not in the ARM list of anachronisms. "." qualifiers are not supported for vacuous destructor/finalizer references or template references. We can't tell yet whether this is a qualified name or simply a normal field reference. We'll assume this is a qualifier for now and make a final decision after we try to look up the identifier. A warning will be issued if appropriate, after the lookup is done. The "." may not be used as a qualifier separator in a field selection operator. If the initial separator is a ".", a "::" can still be used later (e.g., "A.B::C"). The Microsoft compiler also allows usage like "A::B.C". This is handled below without changing qualifier_separator. */ might_be_qualifier = TRUE; qualifier_separator = tok_period; } /* if */ if (!might_be_qualifier && !is_global_qualified_name && (options & GID_SUPPRESS_PACK_INDEX_QUALIFIER) == 0 && pack_index_name_qualifier_next()) { a_type_ptr tp; tp = scan_pack_index_type_specifier(/*is_new_type_name=*/FALSE, /*is_implicit_type_context=*/FALSE, /*concept_okay=*/FALSE, /*might_be_id_start=*/TRUE); next_tok = next_two_tokens_if_qualifier_delimiter(tok_colon_colon, &next_tok_2); if (next_tok == tok_colon_colon) { /* A pack-index type used as a nested-name-specifier: T...[N]::name Process this through the same qualifier path used for decltype-based qualifiers. */ might_be_qualifier = TRUE; if (tp->kind == tk_typeref && is_template_dependent_type(tp)) { tp = proxy_class_for_template_param(tp); qualifier_type = tp; } else { qualifier_type = skip_typerefs(tp); } /* if */ decltype_type = tp; qualifier_is_pack_index = TRUE; qualifier_sym = symbol_for(qualifier_type); if (is_error_type(tp)) err = TRUE; } /* if */ } /* if */ } else if (!is_global_qualified_name && ((curr_token == tok_decltype && !decltype_auto_tokens_next()) || (curr_token == tok_lsplice && spliced_name_qualifier_next()))) { /* This is most likely just a decltype specifier (e.g., "decltype(expr)") or a splicer (e.g., "[:^int:]"), but could also be a qualifier in a qualified name (e.g., "decltype(expr)::something"). The former is not treated as an identifier, while the latter is. */ a_type_ptr tp = NULL; a_scope_ptr spliced_scope = NULL; if (curr_token == tok_decltype) { tp = scan_decltype_operator((a_rescan_control_block *)NULL, /*might_be_id_start=*/TRUE); } else { a_tagged_pointer entity; entity = scan_type_or_namespace_splicer((a_rescan_control_block *)NULL, /*might_be_id_start=*/TRUE); if (entity.kind == iek_type) { tp = (a_type*)entity.ptr; } else { check_assertion(entity.kind == iek_scope); spliced_scope = (a_scope*)entity.ptr; } /* if */ qualifier_is_splice = TRUE; } /* if */ next_tok = next_two_tokens_if_qualifier_delimiter(tok_colon_colon, &next_tok_2); if (next_tok != tok_colon_colon) { check_assertion(tp != NULL); locator_for_curr_id = cleared_locator; locator_for_curr_id.variant.decltype_type = tp; locator_for_curr_id.source_position = pos_curr_token; curr_token = tok_decltype_construct; /* Since we're returning a pseudo-token, set pos_curr_token. */ pos_curr_token = start_position; curr_token_sequence_number = start_seq_number; /* Restore the original error position. */ error_position = orig_error_position; goto exit; } else if (tp == NULL) { /* The current token is the ":]" of the splicer. */ might_be_qualifier = TRUE; check_assertion(spliced_scope != NULL); if (scope_is(spliced_scope, sck_file)) { is_file_scope_qualified_name = TRUE; } else { a_namespace *spliced_nsp; check_assertion(scope_is(spliced_scope, sck_namespace)); spliced_nsp = spliced_scope->variant.assoc_namespace; qualifier_sym = symbol_for(spliced_nsp); } /* if */ } else { /* The current token is the ")" of the decltype. */ might_be_qualifier = TRUE; if (tp->kind == tk_typeref && is_template_dependent_type(tp)) { /* For dependent decltypes, use the proxy class of the decltype. */ tp = proxy_class_for_template_param(tp); decltype_type = tp; qualifier_type = tp; } else { decltype_type = tp; qualifier_type = skip_typerefs(tp); } /* if */ qualifier_is_type = TRUE; qualifier_is_decltype = TRUE; is_decltype_qualified = TRUE; /* Get the symbol for the qualifier. A type that can be used as a qualifier should have a symbol. A NULL symbol will result in an error below. */ qualifier_sym = symbol_for(qualifier_type); } /* if */ } else if (dtor_or_finalizer_must_be_nonclass) { if (is_global_qualified_name && curr_token != tok_identifier) { /* Something of the form "::~int", which is not allowed. */ pos_error(ec_exp_identifier, &error_position); } else { dtor_or_finalizer_class_type = type_keyword(); if (dtor_or_finalizer_class_type != NULL) { next_tok = next_two_tokens_if_qualifier_delimiter(tok_colon_colon, &next_tok_2); if (next_tok == tok_colon_colon && is_dtor_or_finalizer_token(next_tok_2)) { might_be_qualifier = TRUE; if (!type_keyword_in_dtor_allowed) { /* A vacuous destructor/finalizer reference is not normally permitted to use a type keyword, only a typedef name. */ pos_error(ec_exp_identifier, &error_position); } /* if */ } /* if */ } /* if */ } /* if */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cli_or_cx_enabled && (options & GID_IS_EXPR_CONTEXT) != 0 && is_type_keyword(curr_token) && next_token() == tok_colon_colon) { /* A construct like int::Parse("1"). If the type has an associated C++/CLI system type, treat this as a qualifier. */ a_type_ptr tp; /* Get the type represented by the current keyword token. */ tp = type_keyword(); if (tp != NULL) { /* Get the C++/CLI type, if any, that corresponds to the keyword type. */ tp = system_type_from_fundamental_type(tp); if (tp != NULL) { /* Save the type found for use later as the qualifier. */ cli_system_type_for_keyword = tp; might_be_qualifier = TRUE; next_tok = tok_colon_colon; } /* if */ } /* if */ } else if (curr_token == tok_super) { /* Microsoft __super qualifier. */ might_be_qualifier = TRUE; next_tok = next_two_tokens_if_qualifier_delimiter(tok_colon_colon, &next_tok_2); if (next_tok != tok_colon_colon) { /* The __super qualifier may only be used in a qualified name.*/ pos_error(ec_unqualified_super, &error_position); set_to_error_locator(locator_for_curr_id); err = TRUE; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* if */ if (might_be_qualifier) { /* Look up the identifier to see if it could be a class name. Note that we don't consider the normal eclipsing rules. A class or namespace can be found even when hidden by something else: class A {int i;}; int f() { int A; A::i = 1; // The class A is found. } If the name is not found, and vacuous destructor/finalizer references are recognized, and the token following the "::" is a tilde (or an exclamation point in the finalizer case), we repeat the lookup without the restriction that the name must be a class or namespace name. */ if (dtor_or_finalizer_class_type != NULL) { /* This looks like a vacuous destructor/finalizer reference. We may change this later if we don't find the right name following the "::". */ is_vacuous_dtor_or_finalizer = TRUE; qualifier_sym = NULL; } else if (qualifier_is_decltype || qualifier_is_pack_index || qualifier_is_splice) { /* A construct like "decltype(x)::something" or "T...[N]::something". The qualifier type or (for some splices) namespace was set above. */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (curr_token == tok_super) { /* The Microsoft __super qualifier. */ is_super_qualified = qualifier_is_super = TRUE; /* From now on, treat this as an identifier. */ curr_token = tok_identifier; if (is_global_qualified_name) { /* The __super keyword cannot appear after "::". */ pos_error(ec_super_after_scope, &error_position); err = TRUE; } else if (get_super_class_type() == NULL) { /* The __super keyword can only be used within a class or class reactivation scope. */ pos_error(ec_super_not_in_class, &error_position); err = TRUE; } /* if */ } else if (cli_system_type_for_keyword != NULL) { /* A construct like int::Parse("1"). Use the corresponding C++/CLI system type determined above. */ qualifier_sym = symbol_for(cli_system_type_for_keyword); #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if (is_resolved_id_pseudo_token(curr_token)) { qualifier_sym = resolve_id_pseudo_token_to_sym(); if (qualifier_sym == NULL) { /* A diagnostic will have been emitted by load_tok_ifc_entity_ref(). */ err = TRUE; make_error_locator(&locator_for_curr_id); } else { make_resolved_id_pseudo_token_locator(qualifier_sym, &locator_for_curr_id); locator_for_curr_id.source_position = pos_curr_token; } /* if */ } else { an_id_lookup_options_set lookup_kind; a_boolean might_be_vacuous_dtor_or_finalizer; /* A normal qualified name or a vacuous destructor/finalizer reference that begins with a normal qualified name (e.g., A::B::T::~T). If a vacuous destructor/finalizer reference is allowed and the token following the "::" is a tilde (or an exclamation point in the finalizer case), then the identifier we are looking up doesn't have to be a class name. If the class lookup fails, do another lookup without the requirement that a class be found. */ might_be_vacuous_dtor_or_finalizer = can_be_vacuous_dtor_or_finalizer && is_dtor_or_finalizer_token(next_tok_2); /* The lookup of a class name in a qualified name is done as a "must be class (or namespace)" lookup. If, however, the name being scanned is followed by a "<" we don't yet know whether this is a template reference or simply a less than sign. We must assume it could be a less than sign and do a normal (nonclass) lookup. Furthermore, this is done as a "tentative type" lookup so that, should a template not be found, a projection symbol is not created. This should not make any difference for file scope lookups because class template names cannot coexist with other names at file scope. A normal lookup must also be done when scanning what might be a use of a "." in place of "::" as a qualifier. We don't know whether the "." is being used as a qualifier or as a field selection operator so we need to do a normal lookup and then decide based on the type of the thing we find. */ if (follows_template || is_dtor_name) { lookup_kind = IDL_TREAT_AS_TEMPLATE_ID | IDL_TENTATIVE_TEMPLATE_LOOKUP; } else if (next_tok == tok_lt) { lookup_kind = IDL_TENTATIVE_TEMPLATE_LOOKUP; } else if (qualifier_separator == tok_period) { lookup_kind = IDL_NO_OPTIONS; } else if (is_conversion_type && next_tok != tok_colon_colon) { /* Something like "operator B ...". */ lookup_kind = IDL_NO_OPTIONS; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cli_or_cx_enabled && next_tok_2 == tok_typeid) { /* A C++/CLI typeid reference -- something like X::typeid. */ is_cli_typeid = TRUE; lookup_kind = IDL_MUST_BE_CLASS; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { lookup_kind = IDL_MUST_BE_CLASS_OR_NAMESPACE; } /* if */ if (is_global_qualified_name) { /* There was a leading unary "::", so look up the name in the file scope. */ qualifier_sym = file_scope_id_lookup(il_header.primary_scope, &locator_for_curr_id, lookup_kind); if (might_be_vacuous_dtor_or_finalizer || is_cli_typeid) { /* If we might have a vacuous destructor/finalizer or C++/CLI typeid and the lookup result above was invalid as a qualifier, consider this to be a vacuous destructor/finalizer. */ if (qualifier_sym == NULL) { /* No symbol was found. Do a broader lookup. */ qualifier_sym = file_scope_id_lookup(il_header.primary_scope, &locator_for_curr_id, IDL_NO_OPTIONS); is_vacuous_dtor_or_finalizer = TRUE; } else if (!is_valid_qualifier_symbol(qualifier_sym)) { /* A symbol was found by the first lookup, but is not valid except possibly as a vacuous destructor/finalizer. */ is_vacuous_dtor_or_finalizer = TRUE; } /* if */ } /* if */ check_ambiguity_and_access_full( &locator_for_curr_id, (a_boolean)(next_tok == tok_lt), /*is_qualifier=*/(next_tok == tok_colon_colon), (a_boolean *)NULL); } else { /* Usual case (no leading "::"). Look up the name in both the current context (i.e., a normal lookup) and in the class type of the field selection operator, if any. */ qualifier_sym = look_up_qualifier_start( lookup_kind, field_sel_type, might_be_vacuous_dtor_or_finalizer, &is_vacuous_dtor_or_finalizer, follows_template, /*might_be_template=*/next_tok == tok_lt || follows_template || is_dtor_name, in_if_exists, cpp11_mode ? is_conversion_type : qualified_conversion_operator, is_cli_typeid); if (locator_for_curr_id.is_semivisible_nested_type) { /* The symbol in the locator is a nested class that is not visible according to the ARM lookup rules but is returned in support of the nested class anachronism (ARM 18.3.5). Issue an anachronism diagnostic. */ sym_diagnostic(anachronism_error_severity, ec_nested_class_anachronism, locator_for_curr_id.specific_symbol); } /* if */ /* See if this name is ambiguous. This is done here because the specific symbol may be cleared and set to a different value below, and the ambiguity and access check must be done based on the projection symbol, not on the symbol pointed to by the projection symbol. An ambiguous injected template symbol is accepted if it unambiguously refers to a specific class template, and if the next token is a "<" indicating that we are really referring to the template and not the (ambiguous) class type. */ check_ambiguity_and_access_full( &locator_for_curr_id, (a_boolean)(next_tok == tok_lt), /*is_qualifier=*/(next_tok == tok_colon_colon), (a_boolean *)NULL); /* The call above will create an error locator if an ambiguity is is detected. */ if (is_error_locator(locator_for_curr_id)) { make_specific_symbol_error_locator(&locator_for_curr_id); qualifier_sym = locator_for_curr_id.specific_symbol; err = TRUE; } /* if */ } /* if */ if (follows_template && qualifier_sym != NULL && !sym_can_follow_template_keyword(qualifier_sym)) { /* A construct like "p->template X< ...". When the template keyword is so used, "X" must be a template. */ diagnostic(strict_ansi_discretionary_severity, ec_invalid_name_after_template); } /* if */ if (qualifier_separator == tok_period) { if (qualifier_sym != NULL && is_class_symbol(qualifier_sym)) { /* In cfront mode or Microsoft bugs mode we have a construct like "A." where "A" is a class name. This is a use of a cfront anachronism where "." is used in a qualified name where "::" should be used. Issue a warning. */ pos_warning(ec_period_used_as_qualifier, &error_position); separator_warning_issued = TRUE; } else { /* In cfront mode we have found a construct like "A." and A is not a class name. What we have is probably just a normal field reference. Reset the qualifier_separator so that we don't try to process this as a qualified name. */ qualifier_separator = tok_colon_colon; } /* if */ } /* if */ /* If we think we have a vacuous destructor/finalizer reference, make sure the symbol found is a type. An error will be issued below. */ if (is_vacuous_dtor_or_finalizer && qualifier_sym != NULL && !is_type_symbol(qualifier_sym)) { qualifier_sym = NULL; } /* if */ } /* if */ /* Clear the specific symbol field which may have been set by the lookups performed above. It must be cleared in case this isn't actually a qualified name. We clear it now because it may be set again if a template reference is coalesced and we don't want to lose that value. */ specific_sym = locator_for_curr_id.specific_symbol; if (is_resolved_id_pseudo_token(curr_token)) { /* Treat the token as an identifier from here, but do not clear the specific symbol even if this turns out to be an unqualified name. */ curr_token = tok_identifier; } else if (!(is_conversion_type || (specific_sym != NULL && symbol_is(specific_sym, sk_concept_template))) || next_tok == tok_colon_colon) { /* The specific symbol is needed when a special lookup was done for the identifier in a conversion operator. It is also needed when an identifier denotes a concept (since the caller is responsible for handling an optional template argument list that follows). */ clear_specific_symbol(locator_for_curr_id); } /* if */ /* If the class symbol is for a class template, process the argument list. */ fund_qualifier_sym = qualifier_sym == NULL ? NULL : fundamental_symbol_of(qualifier_sym); if ((qualifier_sym != NULL && is_class_template_or_injected_template_symbol(qualifier_sym)) || (next_tok == tok_lt && (qualifier_sym == NULL || ((options & GID_IS_EXPR_CONTEXT) != 0 && adl_for_non_visible_templates && is_function_symbol(qualifier_sym)) || !symbol_cannot_be_template(fund_qualifier_sym))) || follows_template) { /* Process a template reference. This is considered a potential template reference if the symbol points to a class template or if the next token is a "<" (which could be a function template reference or an error case). In C++20 (adl_for_non_visible_templates is TRUE) an undefined name or function name followed by "<" is treated as a template name to cause ADL to be performed. */ an_identifier_options_set template_options = options; /* Save the original qualifier_sym. This may be needed later to know the name used in the qualifier if the symbol is a template template parameter. */ qualifier_template_sym = qualifier_sym; if (gpp_mode && follows_template && qualifier_sym != NULL && symbol_is(qualifier_sym, sk_class_template) && qualifier_sym->variant.template_info->is_nonreal_member) { /* g++ and clang allow constructs such as "a.template x()", where "a" is a nonreal type. Allow a missing template argument list for such cases. */ template_options |= GID_TEMPLATE_ARGS_OPTIONAL; } /* if */ qualifier_sym = coalesce_template_id(qualifier_sym, next_tok, start_seq_number, template_options, field_sel_type == NULL, &err); specific_sym = locator_for_curr_id.specific_symbol; #if CREATE_LEXICAL_TYPEREFS orig_arg_list = locator_for_curr_id.template_arg_list; #endif /* CREATE_LEXICAL_TYPEREFS */ } /* if */ /* See if the identifier is followed by "::". Note that nex_tok is not used because the next token may have changed while scanning a template argument list. */ if (dtor_or_finalizer_class_type != NULL) { /* We have a vacuous destructor/finalizer reference (e.g., "int::~..."). Skip the code in the "else" clause that further processes the class qualifier. */ qualifier_type = dtor_or_finalizer_class_type; qualifier_is_type = TRUE; qualifier_type_is_class = FALSE; (void)get_token(); /* Gets the type name. */ (void)get_token(); /* The "::" that follows the type name. */ is_qualified_name = TRUE; } else if (((next_tok = next_token()) == qualifier_separator || (is_qualified_name && !is_global_qualified_name && (microsoft_bugs && microsoft_version <= 1500) && next_tok == tok_period)) && #if GNU_EXTENSIONS_ALLOWED !(gpp_mode && qualifier_sym == NULL && !err && !(options & GID_IS_EXPR_CONTEXT) && !qualifier_is_decltype && !qualifier_is_pack_index && !qualifier_is_splice && !qualifier_is_super) && #endif /* GNU_EXTENSIONS_ALLOWED */ ((!microsoft_bugs || microsoft_version >= 1300) || is_vacuous_dtor_or_finalizer || in_if_exists || is_microsoft_qualifier_start(qualifier_sym))) { /* This is an identifier followed by the qualifier separator (usually something like "X::"). In Microsoft bugs mode, a "." can be used as the separator even if "::" was used earlier in the name. Scan the qualified name. Note that in GNU C++ mode, we don't treat a qualifier separator token following an invalid type as an actual qualifier separator (in non-expression contexts). For example: typedef int I; I ::x; // Treated as "I (::x);" in GNU C++ mode. */ a_source_position type_position; type_position = start_position; /* This is a qualifier. */ is_qualified_name = TRUE; if (!is_vacuous_dtor_or_finalizer) is_file_scope_qualified_name = FALSE; /* Restore the specific_symbol with the class symbol determined earlier. This needs to be restored so that access and ambiguity checking can be done. */ locator_for_curr_id.specific_symbol = specific_sym; for (;;) { /* Keep looping while there are more levels of class qualification. Exit from loop is in the middle. */ a_boolean is_template = FALSE; a_symbol_ptr prev_qualifier_sym = qualifier_sym; a_boolean invalid_qualifier_sym = FALSE; a_type_ptr qualifier_sym_type; qualifier_lexical_type = NULL; if (err || (qualifier_sym == NULL && !qualifier_is_super && !qualifier_is_decltype && !qualifier_is_pack_index)) { invalid_qualifier_sym = TRUE; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (qualifier_is_super) { /* The Microsoft __super qualifier. */ qualifier_is_type = TRUE; qualifier_type_is_class = FALSE; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if (qualifier_is_decltype || qualifier_is_pack_index) { /* decltype(...):: or pack-index-specifier:: */ qualifier_is_type = TRUE; qualifier_type_is_class = is_class_struct_union_type(qualifier_type); qualifier_is_enum = is_enum_type(qualifier_type); if ((qualifier_sym == NULL || !is_valid_qualifier_symbol(qualifier_sym)) && !(is_template_dependent_context() && is_template_dependent_type(qualifier_type))) { invalid_qualifier_sym = TRUE; if (!is_or_contains_error_type(decltype_type)) { expr_pos_ty_error(ec_bad_decltype_qualifier, &type_position, decltype_type); } /* if */ err = TRUE; } /* if */ } else if (qualifier_sym->is_error) { invalid_qualifier_sym = TRUE; err = TRUE; } else if (qualifier_sym->kind == (a_symbol_kind)sk_class_template) { invalid_qualifier_sym = TRUE; } else if (is_class_symbol(qualifier_sym)) { /* Get the type associated with the class symbol. */ qualifier_sym_type = type_symbol_type(qualifier_sym); qualifier_type = skip_typerefs(qualifier_sym_type); #if CREATE_LEXICAL_TYPEREFS if (record_form_of_name_reference && orig_arg_list != NULL) { qualifier_lexical_type = make_typeref_with_template_args( qualifier_sym_type, orig_arg_list); orig_arg_list = NULL; } else #endif /* CREATE_LEXICAL_TYPEREFS */ /* Do not insert code here. */ { qualifier_lexical_type = qualifier_sym_type; } /* if */ qualifier_is_type = TRUE; qualifier_type_is_class = TRUE; } else if (is_namespace_symbol(qualifier_sym)) { /* Get the namespace from the symbol entry. */ qualifier_namespace = namespace_symbol_namespace(qualifier_sym); qualifier_is_type = FALSE; } else if (is_valid_enum_qualifier_symbol(qualifier_sym)) { /* Enum qualifiers are accepted in some modes. Earlier Microsoft compilers only accept them with member enum types. */ qualifier_type = type_symbol_type(qualifier_sym); qualifier_lexical_type = qualifier_type; qualifier_type = skip_typerefs(qualifier_type); qualifier_is_type = TRUE; qualifier_type_is_class = FALSE; qualifier_is_enum = TRUE; #if GNU_EXTENSIONS_ALLOWED if (clang_mode) { report_gnu_cpp11_extension_if_needed(&error_position, ec_enum_qualifier_is_cpp11); } /* if */ #endif /* GNU_EXTENSIONS_ALLOWED */ } else if ((qualifier_sym->kind == (a_symbol_kind)sk_type && (is_template_param_type(qualifier_sym->variant.type.ptr) || is_vacuous_dtor_or_finalizer)) || (qualifier_sym->kind == (a_symbol_kind)sk_enum_tag && is_vacuous_dtor_or_finalizer)) { /* The class symbol points to a type. This is the case when a class qualifier contains template parameter types or for the last qualifier of a vacuous destructor/finalizer. Set class type to the type pointed to. */ qualifier_type = type_symbol_type(qualifier_sym); #if CREATE_LEXICAL_TYPEREFS if (record_form_of_name_reference && orig_arg_list != NULL) { qualifier_lexical_type = make_typeref_with_template_args( qualifier_type, orig_arg_list); orig_arg_list = NULL; } else #endif /* CREATE_LEXICAL_TYPEREFS */ /* Do not insert code here. */ { qualifier_lexical_type = qualifier_type; } /* if */ qualifier_is_type = TRUE; qualifier_type_is_class = FALSE; check_assertion(is_template_param_type(qualifier_type) || is_vacuous_dtor_or_finalizer); #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cli_or_cx_enabled && next_tok_2 == tok_typeid) { /* A C++/CLI typeid reference. */ qualifier_is_type = TRUE; if (is_type_symbol(qualifier_sym)) { qualifier_type = type_symbol_type(qualifier_sym); } else { pos_error(ec_name_before_typeid_not_type, &pos_curr_token); invalid_qualifier_sym = TRUE; err = TRUE; } /* if */ } else if (cli_or_cx_enabled && symbol_is(qualifier_sym, sk_type) && (qualifier_sym_type = system_type_from_fundamental_type( skip_typerefs(qualifier_sym->variant.type.ptr))) != NULL) { /* A fundamental type (e.g., wchar_t) that maps on a C++/CLI System value type. Proceed with the value type. */ qualifier_type = skip_typerefs(qualifier_sym_type); qualifier_is_type = TRUE; qualifier_type_is_class = TRUE; check_assertion(is_immediate_managed_class_type(qualifier_type)); } else if (symbol_is(qualifier_sym, sk_field) || symbol_is(qualifier_sym, sk_static_data_member) || symbol_is(qualifier_sym, sk_property_set)) { /* A C++/CLI property or event field. */ check_assertion(is_cppcli_property_or_event(qualifier_sym)); qualifier_is_property_or_event = TRUE; qualifier_property_or_event = qualifier_sym; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { /* Some other kind of symbol -- must be an error. */ invalid_qualifier_sym = TRUE; } /* if */ if (invalid_qualifier_sym) { /* The identifier is followed by a "::" but is not a class symbol. */ if (!err) { if (options & GID_PERMIT_UNKNOWN_QUALIFIER) { /* Silently ignore the error. */ } else if (is_vacuous_dtor_or_finalizer) { pos_error(ec_id_must_be_class_or_type_name, &error_position); } else { pos_error(ec_id_must_be_class_or_namespace_name, &error_position); } /* if */ err = TRUE; } /* if */ qualifier_is_type = TRUE; qualifier_type = NULL; qualifier_type_is_class = FALSE; qualifier_sym = NULL; #if CREATE_LEXICAL_TYPEREFS orig_arg_list = NULL; #endif /* CREATE_LEXICAL_TYPEREFS */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (qualifier_is_super) { /* The Microsoft __super qualifier. */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if (qualifier_is_decltype || qualifier_is_pack_index) { /* A decltype(x):: or T...[N]:: qualifier that may have no associated symbol. */ } else { /* The qualifier symbol is valid. Record the reference on the symbol. */ check_assertion(qualifier_sym != NULL); mark_referenced(qualifier_sym, &locator_for_curr_id.source_position); record_potential_pack_reference( qualifier_sym, &locator_for_curr_id.source_position); } /* if */ #if CREATE_LEXICAL_TYPEREFS check_assertion(orig_arg_list == NULL); #endif /* CREATE_LEXICAL_TYPEREFS */ if (record_name_references_in_context()) { /* Create an entry that describes this qualifier. Find a previously created entry if possible. */ if (err) { /* Don't try to build a qualifier if an error occurred. */ name_qualifier = NULL; } else if (qualifier_is_super) { /* No name qualifier entry is created for the __super level. */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (qualifier_is_property_or_event) { /* No name qualifier entry is created for the property or event name. */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { make_name_qualifier(&name_qualifier, qualifier_sym, qualifier_template_sym, qualifier_lexical_type != NULL ? qualifier_lexical_type : qualifier_type, qualifier_namespace, decltype_type); } /* if */ } /* if */ /* Clear the decltype_type so it won't be used in subsequent iterations. */ decltype_type = NULL; /* Skip over the qualifier and the "::". After the two get_token calls, the current token will be whatever follows the qualifier. */ (void)get_token(); (void)get_token(); qualifier_template_sym = NULL; if (curr_token == tok_template) { is_template = TRUE; if (!cpp11_mode && strict_ansi_mode && !is_template_context() && !in_if_exists) { /* In strict C++98 mode the template keyword, when used for syntactic disambiguation, may only appear within a template. Core issue 468 allows the construct to be used outside of templates. */ diagnostic(strict_ansi_discretionary_severity, ec_template_not_in_template); is_template = FALSE; } /* if */ (void)get_token(); if (curr_token == tok_star) { /* A construct like "T::template *". Template must be followed by an identifier. */ diagnostic(strict_ansi_discretionary_severity, ec_invalid_token_after_template); is_template = FALSE; } /* if */ } /* if */ /* If the current token begins an operator name, then coalesce the operator. */ if (curr_token == tok_operator) { /* get_opname requires a parent class or namespace pointer. Construct one for the qualifier that has been scanned. */ a_parent_class_or_namespace parent; if (qualifier_is_type) { parent.class_type = qualifier_type; } else { parent.namespace_ptr = qualifier_namespace; } /* if */ get_opname(qualifier_is_type, &parent, field_sel_type); } /* if */ if (is_template && curr_token == tok_identifier && ((gpp_mode && gnu_version >= 30400) || microsoft_mode || clang_mode) && (options & (GID_IS_UNKNOWN_TEMPLATE_ARG | GID_CLASS_TEMPLATE_REQUIRED)) == 0 && (is_template_dependent_context() || gpp_version_is(>= 30400)) && !locator_for_curr_id.is_template_id && next_token() != tok_lt) { /* g++, Clang and Microsoft allow usage like "p->A::template f()", where the name (at least during the prototype instantiation) is not a template. Ignore the template keyword in this case. An exception is made when the caller specifies the GID_CLASS_TEMPLATE_REQUIRED option. This usage is not permitted in dependent template argument lists (indicated in this case by the unknown template arg flag), so suppress the special processing in such contexts. Note that this only occurs in Microsoft mode if parsing of nonclass templates has been enabled. g++ also incorrectly accepts "p->template f()" as a call to a non-template member function if the overload set includes a template. */ is_template = FALSE; } /* if */ next_tok = next_two_tokens_if_qualifier_delimiter (qualifier_separator, &next_tok_2); #if MICROSOFT_EXTENSIONS_ALLOWED if (curr_token == tok_super) { /* The __super keyword cannot appear after "::". */ pos_error(ec_super_after_scope, &error_position); err = TRUE; /* From now on, treat this as an identifier. */ curr_token = tok_identifier; set_to_error_locator(locator_for_curr_id); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ if (is_resolved_id_pseudo_token(curr_token)) { a_symbol_ptr sym = resolve_id_pseudo_token_to_sym(); if (sym == NULL) { err = TRUE; } else { make_resolved_id_pseudo_token_locator(sym, &locator_for_curr_id); locator_for_curr_id.source_position = pos_curr_token; curr_token = tok_identifier; } /* if */ } /* if */ if ((curr_token != tok_identifier && !is_resolved_id_pseudo_token(curr_token)) || ((next_tok != qualifier_separator && next_tok != tok_colon_colon && (!(microsoft_bugs && microsoft_version <= 1500) || (is_qualified_name && (next_tok != tok_period || (qualifier_is_type && is_template_dependent_type(qualifier_type)))))) && next_tok != tok_lt && !is_template)) { /* Not an identifier followed by "::" or "<", so end the loop. In Microsoft bugs mode a "." can be used in place of "::" in some cases. Don't allow this usage if the qualifier is dependent because we can't tell which usage was intended, so we need to take the conservative approach of treating it as a field selection operator. */ break; } /* if */ /* There is another level of qualification. Search for the identifier in the given scope. Once again, if vacuous destructor/finalizer references are allowed we may need to repeat the lookup without the requirement that a class be found. */ if (!err) { a_boolean might_be_vacuous_dtor_or_finalizer = can_be_vacuous_dtor_or_finalizer && is_dtor_or_finalizer_token(next_tok_2); is_cli_typeid = FALSE; /* If xyz::member is used as an intermediate qualifier (e.g., xyz::member::inner) and member resolves to a type intrinsically, continue the qualifier loop with that type as the new qualifier, without completing (instantiating) xyz. */ if (qualifier_is_type && qualifier_type_is_class && !is_template) { a_type_ptr intrinsic_result_tp = NULL; a_boolean intrinsic_no_member = FALSE; if (symbol_for(qualifier_type)->header->has_intrinsic_name && intrinsic_templ_type_member_lookup( qualifier_type, locator_for_curr_id.symbol_header, &intrinsic_result_tp, &intrinsic_no_member) && !intrinsic_no_member) { /* Continue the qualifier loop using a synthesized type symbol named for the intrinsic member that denotes the resolved type (e.g., "member" in "xyz::member"). The next iteration records a name qualifier that preserves the original member spelling (e.g., "member") and looks up the following component in the resolved type. */ qualifier_sym = make_intrinsic_member_type_symbol( locator_for_curr_id.symbol_header, intrinsic_result_tp, &locator_for_curr_id.source_position); continue; } /* if */ } /* if */ if (qualifier_is_type && qualifier_type_is_class) { /* Make sure that this class has been instantiated. */ complete_class_type_is_needed(qualifier_type); } /* if */ /* Make sure that the class type is a complete type. */ if (qualifier_is_type && qualifier_type_is_class && is_incomplete_type(qualifier_type) && scope_is_null_or_placeholder(class_type_supp(qualifier_type) ->assoc_scope)) { /* If the type is incomplete we also check whether the type is currently being defined -- it is considered complete if it is being defined. We determine this by checking the assoc_scope field of the class type supplement. */ if (!in_if_exists) { issue_incomplete_type_diag(&type_position, qualifier_type); } /* if */ err = TRUE; qualifier_sym = NULL; } else { an_id_lookup_options_set lookup_options; /* The lookup of a class name in a qualified name is done as a "must be class (or namespace)" lookup. If, however, the name being scanned is followed by a "<" we don't yet know whether this is a template reference or simply a less than sign. We must assume it could be a less than sign and do a normal (nonclass) lookup. */ if (next_tok == tok_lt || is_template) { lookup_options = IDL_TENTATIVE_TEMPLATE_LOOKUP; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cli_or_cx_enabled && next_tok_2 == tok_typeid) { /* A C++/CLI typeid reference -- something like X::typeid. */ lookup_options = IDL_MUST_BE_CLASS; is_cli_typeid = TRUE; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { lookup_options = IDL_MUST_BE_CLASS_OR_NAMESPACE; } /* if */ /* If the identifier was preceded by "template", the name can be assumed to be a template. */ if (is_template) { lookup_options |= IDL_TREAT_AS_TEMPLATE_ID; } /* if */ if ((options & GID_IS_TYPENAME) != 0) { /* If this name followed the typename keyword, indicate that the name found must be a type. This affects creation of members of proxy classes. A "<" seen in a qualified name that follows "typename" is considered to be the start of a template argument list. */ lookup_options |= IDL_TYPENAME_LOOKUP; if (((next_tok == tok_lt && !clang_mode) || is_template) && (gpp_mode || microsoft_mode || use_implicit_typename())) { /* When implicit typename is used, and in g++ and Microsoft modes, in a name prefixed by "typename", treat a "<" as the start of a template argument list. */ lookup_options |= IDL_TREAT_AS_TEMPLATE_ID; } /* if */ } else if ((use_implicit_typename() || microsoft_mode || (gpp_mode && gnu_version <= 40100 && qualifier_is_type && gpp_omitted_template_okay(qualifier_type))) && (options & GID_IS_EXPR_CONTEXT) == 0) { /* If this is a name being used in a declarative context (i.e., not in an expression), and we are in implicit typename mode, and the name is followed by a "<", set the "treat as template ID" flag to indicate that if a nonreal class member needs to be created, it should be created as a template name. This is also done in Microsoft mode, which makes the "template" keyword optional, even in non-permissive mode, and in certain cases in g++ mode (see gpp_omitted_template_okay for more information). */ if (next_tok == tok_lt || is_template) { lookup_options |= IDL_TREAT_AS_TEMPLATE_ID; } /* if */ } else if ((options & GID_IS_EXPR_CONTEXT) != 0) { /* Pass in a special flag for expression contexts. This controls the type of nonreal class member created. */ lookup_options |= IDL_IS_EXPR_CONTEXT; } /* if */ if ((options & GID_USE_PROTOTYPE_NOT_NONREAL) != 0) { /* Pass along the flag to use the prototype version of types, not the nonreal one. */ lookup_options |= IDL_USE_PROTOTYPE_NOT_NONREAL; } /* if */ /* Set the is_qualified_name field of the locator so that it can be used by the lookup routines called below. This is needed in cases such as the creation of a symbol for a member of an unknown base. */ locator_for_curr_id.is_qualified_name = TRUE; if (qualifier_is_type) { if (qualifier_is_enum) { /* In some modes (e.g., C++11), an enumeration can be used as the qualifier in a qualified name. Look up the name in the enumeration. */ qualifier_sym = enum_qualified_id_lookup(&locator_for_curr_id, qualifier_type); #if MICROSOFT_EXTENSIONS_ALLOWED } else if (qualifier_is_super) { qualifier_sym = super_qualified_id_lookup( &locator_for_curr_id, lookup_options); } else if (qualifier_is_property_or_event) { /* A property or event cannot be followed by "::". */ qualifier_sym = NULL; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { /* Look up the name in the class specified by the qualifier that has been scanned so far. */ qualifier_sym = class_qualified_id_lookup (&locator_for_curr_id, qualifier_type, lookup_options); if (might_be_vacuous_dtor_or_finalizer || is_cli_typeid) { /* If we might have a vacuous destructor/finalizer or C++/CLI typeid and the lookup result above was invalid as a qualifier. */ if (qualifier_sym == NULL) { /* No symbol was found. Do a broader lookup. */ qualifier_sym = class_qualified_id_lookup( &locator_for_curr_id, qualifier_type, IDL_NO_OPTIONS); /* Unless this is known to be a C++/CLI typeid, consider this a vacuous destructor/finalizer. */ if (!is_cli_typeid) is_vacuous_dtor_or_finalizer = TRUE; } else if (!is_valid_qualifier_symbol(qualifier_sym)) { /* A symbol was found by the first lookup, but is not valid except possibly as a vacuous destructor/ finalizer. */ is_vacuous_dtor_or_finalizer = TRUE; } /* if */ if (qualifier_sym != NULL && !is_type_symbol(qualifier_sym)) { qualifier_sym = NULL; } /* if */ } /* if */ } /* if */ } else { /* Look up the name in the namespace that has been scanned so far. */ check_assertion(qualifier_namespace != NULL); qualifier_sym = namespace_qualified_id_lookup (&locator_for_curr_id, qualifier_namespace, lookup_options); /* If the namespace lookup fails, and a vacuous destructor/ finalizer is allowed, do another lookup without the requirement that a class be found. This could occur for a vacuous destructor/finalizer reference of the form i->N::T::~T, where N is a namespace, and T is a typedef in that namespace. */ if (might_be_vacuous_dtor_or_finalizer) { if (qualifier_sym == NULL) { /* No symbol was found. Do a broader lookup. */ qualifier_sym = namespace_qualified_id_lookup (&locator_for_curr_id, qualifier_namespace, IDL_NO_OPTIONS); is_vacuous_dtor_or_finalizer = TRUE; } else if (!is_valid_qualifier_symbol(qualifier_sym)) { /* A symbol was found by the first lookup, but is not valid except possibly as a vacuous destructor/ finalizer. */ is_vacuous_dtor_or_finalizer = TRUE; } /* if */ } /* if */ } /* if */ } /* if */ /* If we have a Microsoft qualified name of the form "X::Y.Z", exit the loop if Y was not found by the lookups above. This occurs when Y is, for example, a static data member of Z. */ if (next_tok == tok_period) { if (qualifier_sym == NULL) break; if (!separator_warning_issued) { pos_warning(ec_period_used_as_qualifier, &error_position); separator_warning_issued = TRUE; } /* if */ } /* if */ if (qualifier_sym != NULL) { if (qualifier_sym->is_class_member || locator_for_curr_id.specific_symbol->ambiguous) { /* Do ambiguity and access control checking on the qualifier symbol. Access checking is only done for class members to ensure that the check will be suppressed for template parameters (i.e., the T in T::X). Access for template parameters should be checked at the point at which the type is used as a template argument. The routine is also called if the symbol is known to be ambiguous, so that it can report the ambiguity error. No access checking will be done when an ambiguity error exists. */ check_ambiguity_and_access_full( &locator_for_curr_id, (a_boolean)(next_tok == tok_lt), /*is_qualifier=*/TRUE, (a_boolean *)NULL); /* The call above will create an error locator if an ambiguity is is detected. */ if (is_error_locator(locator_for_curr_id)) { make_specific_symbol_error_locator(&locator_for_curr_id); qualifier_sym = locator_for_curr_id.specific_symbol; err = TRUE; } /* if */ } /* if */ } /* if */ if (is_template && qualifier_sym != NULL && !err && !sym_can_follow_template_keyword(qualifier_sym) && !in_if_exists) { /* A construct like "p->A::template X< ...". When the template keyword is so used, "X" must be a template. */ diagnostic(strict_ansi_discretionary_severity, ec_invalid_name_after_template); } /* if */ if (qualifier_sym != NULL && (is_class_template_or_injected_template_symbol(qualifier_sym) || is_template || (next_tok == tok_lt && ((!caching_tokens && !symbol_is(qualifier_sym, sk_concept_template)) || symbol_is_or_contains_template(qualifier_sym))))) { /* Save the original qualifier_sym. This may be needed later to know the name used in the qualifier if the symbol is a template template parameter. We generally treat a "<" as potentially beginning a template argument list at this point because it improves error recovery. When we are caching tokens, don't treat it that way unless it follows something known to be a template. Also, do not auto-coalesce concept-ids.*/ qualifier_template_sym = qualifier_sym; /* Process a template reference. This is considered a potential template reference if the symbol points to a class template or if the next token is a "<" (which could be a function template reference or an error case). */ qualifier_sym = coalesce_template_id(qualifier_sym, next_tok, start_seq_number, options, /*is_name_start=*/FALSE, &err); #if CREATE_LEXICAL_TYPEREFS orig_arg_list = locator_for_curr_id.template_arg_list; #endif /* CREATE_LEXICAL_TYPEREFS */ /* We can only now determine whether this template reference is followed by a "::". If it is not, break out of the qualifier loop. */ if (next_token() != tok_colon_colon) break; } else if (next_tok == tok_lt) { if (qualifier_sym != NULL && symbol_is(qualifier_sym, sk_concept_template)) { /* A concept-id: Don't scan the template arguments, but retain the specific symbol. */ } else { /* The qualified name is followed by a "<" but this is not a template reference. Reset qualifier_sym to the value it had before the lookup was done. Clear the specific_symbol field in the symbol locator. Break out of the qualifier scanning loop as this must be the final identifier. */ qualifier_sym = prev_qualifier_sym; clear_specific_symbol(locator_for_curr_id); } /* if */ break; } /* if */ } /* if */ type_position = pos_curr_token; qualifier_is_super = FALSE; qualifier_is_decltype = FALSE; qualifier_is_pack_index = FALSE; } /* for */ } /* if */ } else if (is_resolved_id_pseudo_token(curr_token)) { a_symbol_ptr sym = resolve_id_pseudo_token_to_sym(); if (sym == NULL) { err = TRUE; } else { make_resolved_id_pseudo_token_locator(sym, &locator_for_curr_id); locator_for_curr_id.source_position = pos_curr_token; curr_token = tok_identifier; } /* if */ } /* if */ /* Assume we have found an identifier until we discover otherwise. */ is_identifier = TRUE; is_cli_typeid = FALSE; if (is_qualified_name && qualifier_is_type && !is_file_scope_qualified_name) { /* This is a qualifier (but not a file scope qualified name such as ::x) -- see if it is a pointer to member. */ if (curr_token == tok_star) { /* We have a pointer to member token (i.e., A::*). Update the current token. */ is_identifier = FALSE; is_ptr_to_member = TRUE; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cli_or_cx_enabled && curr_token == tok_typeid) { /* We have a C++/CLI typeid (i.e., A::typeid). */ is_identifier = FALSE; is_cli_typeid = TRUE; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if ((options & GID_SIMPLIFY_CURR_CLASS_QUALIFIED_NAME) != 0 && scope_is(&scope_stack_top(), sck_class_struct_union) && same_entities(scope_stack_top().assoc_type, qualifier_type)) { /* Treat e.g. X::Y::N as just N in the body of class X::Y. See also simplify_curr_class_qualified_name, which achieves the same effect at a higher level (permitting context-sensitive diagnostics). */ is_qualified_name = FALSE; qualifier_is_type = FALSE; qualifier_sym = NULL; qualifier_type = NULL; } /* if */ } /* if */ if (is_identifier) { /* We still think we have an identifier. curr_token contains either the token following the qualifier or the first token of what might be an identifier. Determine whether it is an identifier. */ if (is_vacuous_dtor_or_finalizer && !is_dtor_or_finalizer_token(curr_token)) { /* We thought we were processing a vacuous destructor/finalizer because we found something unusual in the qualifier, but there is no tilde (or exclamation point in the finalizer case) after the qualifier. Reset the flag. */ is_vacuous_dtor_or_finalizer = FALSE; } /* if */ if (curr_token == tok_identifier) { /* It is an identifier. */ } else if (curr_token == tok_operator) { /* Could be something like either "operator +" or "operator int". We don't determine at this point whether this is a legal operator, just that it couldn't legally be anything else. We recognize operator names even if they are disallowed by the "options" flags. An error will be issued later if needed. */ } else if (is_dtor_or_finalizer_token(curr_token) && ((options & GID_DTOR_RECOGNIZED) || (is_qualified_name && qualifier_is_type))) { /* A destructor/finalizer name (e.g., ~A or A::~A). Destructor/ finalizer names are always recognized after qualifiers. If not preceded by a qualifier, then they are only recognized when GID_DTOR_RECOGNIZED is TRUE. In either case, finalizer names are only recognized when cli_or_cx_enabled is TRUE. */ is_dtor_or_finalizer = TRUE; /* If we have already discovered that we have a vacuous destructor/ finalizer reference, then it must be a non-class destructor/ finalizer reference (e.g., int::~int). */ is_nonclass_dtor_or_finalizer = is_vacuous_dtor_or_finalizer; if (can_be_vacuous_dtor_or_finalizer && (field_sel_type == NULL || (qualifier_type != NULL && !is_class_struct_union_type(qualifier_type) && !is_template_dependent_type(qualifier_type)) || !is_class_struct_union_type(field_sel_type))) { /* A destructor/finalizer call for a non-class type is always vacuous, even if the destructor/finalizer name erroneously named a class type. */ is_nonclass_dtor_or_finalizer = is_vacuous_dtor_or_finalizer = TRUE; } else if (!is_vacuous_dtor_or_finalizer && can_be_vacuous_dtor_or_finalizer && (is_qualified_name && qualifier_is_type) && qualifier_type_is_class && !err) { /* So far this looks like a normal destructor/finalizer reference (i.e., the qualified name represents a class, not some other type). See if the class has a destructor/finalizer. If it does not, this is a vacuous reference. */ check_assertion_str(qualifier_type != NULL, "figis: qualifier_type == NULL"); if (curr_token == tok_compl && symbol_supplement_for_class(qualifier_type)->destructor == NULL) { is_vacuous_dtor_or_finalizer = TRUE; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cli_or_cx_enabled && curr_token == tok_not && symbol_supplement_for_class(qualifier_type)->finalizer == NULL) { is_vacuous_dtor_or_finalizer = TRUE; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* if */ } else if (dtor_or_finalizer_must_be_nonclass && !is_qualified_name) { /* A destructor/finalizer that is not part of a qualified name must be a nonclass vacuous destructor when we are in "dtor_or_finalizer_must_be_nonclass" mode. */ is_vacuous_dtor_or_finalizer = is_nonclass_dtor_or_finalizer = TRUE; } /* if */ } else if (is_qualified_name) { /* A qualifier followed by something invalid. Proceed as if it is an identifier and let an error be diagnosed later when we have more information about what is being processed. */ } else { /* Anything else is not the start of an identifier. */ is_identifier = FALSE; } /* if */ } /* if */ /* This routine has two functions: determining whether the thing being scanned is a generalized identifier, pointer to member, C++/CLI typeid operator, or something else; and, if it is an identifier, coalescing the identifier and storing the information in the locator. The code above performed the first part of the job. The code below does the coalescing now that we know what we are scanning. */ if (is_ptr_to_member if_microsoft_extensions(|| is_cli_typeid)) { curr_token = tok_ptr_to_member; #if MICROSOFT_EXTENSIONS_ALLOWED if (is_cli_typeid) { curr_token = tok_cli_typeid; if (qualifier_type == NULL) qualifier_type = error_type(); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ /* parent.class_type and is_class_member are the only fields of the locator that are valid when curr_token is tok_ptr_to_member or tok_cli_typeid. Note that in error cases, qualifier_type can be NULL for tok_ptr_to_member, but will be an error type (set above) for tok_cli_typeid cases. */ locator_for_curr_id.parent.class_type = qualifier_lexical_type != NULL ? qualifier_lexical_type : qualifier_type; locator_for_curr_id.is_class_member = TRUE; /* Clear the is_template_id flag in the locator in case it was set before this was recognized to be ptr-to-member. */ locator_for_curr_id.is_template_id = FALSE; /* Since we're returning a pseudo-token, set pos_curr_token. */ pos_curr_token = start_position; curr_token_sequence_number = start_seq_number; /* Restore the original error position. */ error_position = orig_error_position; } else if (is_identifier) { /* A possibly qualified identifier. */ result = TRUE; if (is_qualified_name) { /* Make sure that the class has been instantiated. However, when the final member of a name of the form xyz::member is resolved intrinsically, resolve it here without completing (instantiating) xyz. The synthesized member symbol is recorded in the locator, so the subsequent lookup in coalesce_and_lookup_qualified_name is short-circuited. */ if (!err && qualifier_is_type && qualifier_type != NULL && qualifier_type_is_class) { a_boolean no_such_member = FALSE; if (symbol_for(qualifier_type)->header->has_intrinsic_name && curr_token == tok_identifier && locator_for_curr_id.symbol_header != NULL && resolve_intrinsic_templ_type_member(&locator_for_curr_id, qualifier_type, &no_such_member)) { /* xyz::member was handled intrinsically without completing (instantiating) xyz. On success the synthesized member symbol is recorded in the locator. A provably-absent member is left unrecorded; it is diagnosed, with the appropriate tolerance, by coalesce_and_lookup_qualified_name. */ } else { complete_class_type_is_needed(qualifier_type); } /* if */ } /* if */ } /* if */ set_err_pos_to_curr_token(); /* Process the identifier after the optional qualifier. Coalesce multi-token identifiers (such as "operator +"). This is done by scanning the tokens of the identifier, updating the locator to reflect what was scanned, and setting curr_token to tok_identifier. Most of this processing is actually done by get_destructor_or_finalizer_name and get_opname. */ /* Check for a destructor/finalizer name. Destructor/finalizer names are always recognized following a class qualifier, but otherwise are only recognized if the GID_DTOR_RECOGNIZED flag is set. In either case, finalizer names are only recognized when cli_or_cx_enabled is TRUE. */ if (is_dtor_or_finalizer) { a_source_position dtor_or_finalizer_position; dtor_or_finalizer_position = pos_curr_token; if (is_nonclass_dtor_or_finalizer) { a_boolean is_destructor_name = (curr_token == tok_compl); a_boolean is_finalizer_name = (cli_or_cx_enabled && curr_token == tok_not); /* Don't do normal destructor/finalizer processing on a non-class vacuous destructor/finalizer. Set the destructor/finalizer flag in the locator and look up the identifier or type that follows the tilde (or the exclamation point in the finalizer case). */ (void)get_token(); /* Get the token after the "~" or "!". */ if (is_destructor_name && curr_token == tok_identifier && pack_index_next()) { /* A C++26 pack-index pseudo-destructor: ~Ts...[I]. Scan the full pack-index-specifier and use the resolved (or dependent) type as the destructor type. */ dtor_or_finalizer_type = scan_pack_index_type_specifier( /*is_new_type_name=*/FALSE, /*is_implicit_type_context=*/FALSE, /*concept_okay=*/FALSE, /*might_be_id_start=*/TRUE); if (is_error_type(dtor_or_finalizer_type)) { err = TRUE; } else { dtor_or_finalizer_class_type = qualifier_type; if (dtor_or_finalizer_class_type == NULL) { dtor_or_finalizer_class_type = field_sel_type; } /* if */ qualifier_type = dtor_or_finalizer_type; qualifier_is_type = TRUE; } /* if */ curr_token = tok_identifier; } else if (curr_token == tok_identifier) { /* A typedef name -- look up the symbol and find the type pointed to. This will be something like "i::~i" or "A::i::~i". If "i" is a member of a class then we need to do the lookup in the class of which "i" is a member. Likewise, if "i" is a member of a namespace then we need to do the lookup in the namespace of which "i" is a member. If "i" is not a member, then do either a normal or file-scope lookup depending on whether "i" had used a global scope qualifier. */ a_symbol_ptr type_sym = NULL; /* Set dtor_or_finalizer_class_type to class_type. This is only needed when we have a typedef name. For a type name like "int" it will already have been set. */ dtor_or_finalizer_class_type = qualifier_type; /* Look up the destructor/finalizer name based on the qualifier that was present (if any). */ type_sym = look_up_destructor_or_finalizer_name( &locator_for_curr_id, is_file_scope_qualified_name, qualifier_sym, /*no_normal_lookup=*/FALSE, IDL_NO_OPTIONS); if (type_sym != NULL) { /* Make sure the lookup of the destructor/finalizer type was not ambiguous. */ check_for_ambiguity(&locator_for_curr_id); } /* if */ /* Clear the specific symbol found by these lookups. */ clear_specific_symbol(locator_for_curr_id); if (type_sym != NULL && is_type_symbol(type_sym)) { /* If the symbol found is a type, get the type pointed to. */ dtor_or_finalizer_type = type_symbol_type(type_sym); /* This will eventually result in the locator qualifier class type being set to the type of the vacuous destructor/finalizer. */ qualifier_type = dtor_or_finalizer_type; qualifier_is_type = TRUE; /* In some cases, such as "p->::~T", dtor_or_finalizer_class_type is not set yet. If so, set it now. */ if (dtor_or_finalizer_class_type == NULL) { dtor_or_finalizer_class_type = dtor_or_finalizer_type; } /* if */ } else { if (!in_if_exists && !is_error_locator(locator_for_curr_id)) { pos_st_error(ec_not_a_type_name, &dtor_or_finalizer_position, locator_for_curr_id.symbol_header->identifier); } /* if */ err = TRUE; } /* if */ } else if (curr_token == tok_decltype || (type_keyword_in_dtor_allowed && (dtor_or_finalizer_type = type_keyword()) != NULL)) { /* "~decltype(x)" or a type keyword such as "~int" (which is allowed for some modes). */ if (curr_token == tok_decltype) { a_type_ptr tp; /* might_be_id_start is passed in as TRUE to prevent the token after the closing ")" from being fetched. */ tp = scan_decltype_operator((a_rescan_control_block *)NULL, /*might_be_id_start=*/TRUE); dtor_or_finalizer_type = skip_typerefs(tp); } /* if */ /* If the thing being scanned looks like "T::~int", where T is a typedef, save the type pointed to as dtor_or_finalizer_class_type. This will be used later for error checking. */ check_assertion(qualifier_is_type == TRUE); if (dtor_or_finalizer_class_type == NULL) { dtor_or_finalizer_class_type = qualifier_type; } /* if */ /* This will eventually result in the locator qualifier class type being set to the type of the vacuous destructor/finalizer. */ qualifier_type = dtor_or_finalizer_type; /* Make the current token a tok_identifier. */ curr_token = tok_identifier; } else { /* The token after the "~" is not an identifier or a type name. */ if (!in_if_exists) pos_error(ec_exp_identifier, &error_position); err = TRUE; } /* if */ if (is_destructor_name) { locator_for_curr_id.is_destructor_name = TRUE; #if MICROSOFT_EXTENSIONS_ALLOWED } else if (is_finalizer_name) { locator_for_curr_id.is_finalizer_name = TRUE; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* if */ if (!err && field_sel_type != NULL) { a_type_ptr unqual_field_sel_type; a_type_ptr dtor_or_finalizer_type_under_typerefs; /* When field_sel_type is non-NULL we are processing the right hand side of a field selection (e.g., "p->~X()"). Make sure that the destructor/finalizer type that has been found matches the type of the left operand. */ check_assertion(dtor_or_finalizer_type != NULL); unqual_field_sel_type = make_unqualified_type(field_sel_type); if (!strict_ansi_mode && is_array_type(dtor_or_finalizer_type) && !is_array_type(unqual_field_sel_type)) { /* For "p->~T()", where T is an array type and p is not an array type, decay the array type to a pointer. */ dtor_or_finalizer_type = type_after_array_to_pointer_transformation( dtor_or_finalizer_type); /* Update the qualifier type that will be returned in the locator. */ qualifier_type = dtor_or_finalizer_type; } /* if */ dtor_or_finalizer_type_under_typerefs = skip_typerefs(dtor_or_finalizer_type); if (!identical_types(unqual_field_sel_type, dtor_or_finalizer_type_under_typerefs) && !is_template_param_type(dtor_or_finalizer_type_under_typerefs) && !is_proxy_class(unqual_field_sel_type)) { if (!in_if_exists) { pos_ty_error(is_finalizer_name ? ec_invalid_finalizer_name : ec_invalid_destructor_name, &dtor_or_finalizer_position, field_sel_type); } /* if */ err = TRUE; } /* if */ } /* if */ if (err) { /* An error has occurred. Set the result types to NULL to prevent subsequent errors. */ qualifier_type = NULL; dtor_or_finalizer_type = NULL; dtor_or_finalizer_class_type = NULL; } /* if */ } else if (((options & GID_DTOR_RECOGNIZED) || (is_qualified_name && qualifier_is_type)) && !is_file_scope_qualified_name) { /* The name can be a destructor name like "~A" or a C++/CLI finalizer name like "!A". */ if (is_dtor_or_finalizer_token(curr_token)) { get_destructor_or_finalizer_name( field_sel_type, is_file_scope_qualified_name, qualifier_sym, (options & GID_IS_EXPR_CONTEXT) != 0); if (locator_for_curr_id.specific_symbol != NULL) { is_vacuous_dtor_or_finalizer = FALSE; } /* if */ } /* if */ } /* if */ /* The position of the current identifier should be the tilde that begins the destructor name (or the exclamation point in the finalizer case). */ locator_for_curr_id.source_position = dtor_or_finalizer_position; if (is_vacuous_dtor_or_finalizer && is_qualified_name) { /* Make sure a vacuous destructor/finalizer reference is correctly formed. These tests only apply if the vacuous destructor/finalizer is part of a qualified name. */ if (!is_nonclass_dtor_or_finalizer) { /* If qualifier_type is NULL an error must have already occurred. */ if (qualifier_type != NULL) { /* If this is a vacuous destructor/finalizer reference, just make sure the name of the destructor matches the name of the class. */ if (is_error_locator(locator_for_curr_id)) { /* An error occurred earlier while checking the destructor/ finalizer. */ err = TRUE; /* Set the class type to NULL as an indicator to the coalesce routine that an error has occurred. */ qualifier_type = NULL; } /* if */ } /* if */ } else { /* This is a vacuous destructor/finalizer reference for a non-class type (e.g., int::~int). Make sure the type of the thing before the "::" matches the type of the thing after it. */ /* If the dtor_or_finalizer_class_type is NULL then an error occurred while scanning the part before the "::~", so don't issue another error here. If the type of the thing after the "::~" is NULL, or doesn't match dtor_or_finalizer_class_type, issue an error. */ if (dtor_or_finalizer_class_type == NULL) { qualifier_type = NULL; } else if (dtor_or_finalizer_type == NULL || (!identical_types_ignoring_qualifiers( dtor_or_finalizer_class_type, dtor_or_finalizer_type) && !is_template_param_type(dtor_or_finalizer_type))) { if (!in_if_exists) { pos_ty_error( #if MICROSOFT_EXTENSIONS_ALLOWED (cli_or_cx_enabled && locator_for_curr_id.is_finalizer_name) ? ec_finalizer_type_mismatch : #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ ec_destructor_type_mismatch, &dtor_or_finalizer_position, dtor_or_finalizer_class_type); } /* if */ err = TRUE; /* Set the class type to NULL as an indicator to the coalesce routine that an error has occurred. */ qualifier_type = NULL; } /* if */ } /* if */ } /* if */ } /* if */ /* The name can be an operator name like "operator+". */ if (curr_token == tok_operator) { /* get_opname requires a parent class or namespace pointer. Construct one for the qualifier that has been scanned. */ a_parent_class_or_namespace parent; if (qualifier_is_type) { parent.class_type = qualifier_type; } else { parent.namespace_ptr = qualifier_namespace; } /* if */ get_opname(qualifier_is_type, &parent, field_sel_type); } /* if */ wrapup: /* The current token must now be the final identifier of the qualified name, e.g., "x" in "A::B::x". In the destructor, finalizer, and operator name cases, curr_token has been changed to tok_identifier. */ if (curr_token != tok_identifier && !is_nonclass_dtor_or_finalizer) { /* The final identifier is missing. Unget the current token and build an error locator. */ unget_token(); curr_token = tok_identifier; /* syntax_error is deliberately not called. */ if (!in_if_exists) pos_error(ec_exp_identifier, &error_position); /* For the error cases, set the current locator to an error locator with specific_symbol pointing to a newly-created error symbol of kind sk_undefined. */ make_specific_symbol_error_locator(&locator_for_curr_id); } /* if */ #if MICROSOFT_EXTENSIONS_ALLOWED if (qualifier_is_property_or_event) { locator_for_curr_id.is_property_or_event_accessor = TRUE; locator_for_curr_id.property_or_event_parent = qualifier_property_or_event; if (qualifier_type == NULL && qualifier_sym != NULL) { /* If, for example, class type C has an event e, we treat "e::raise" as an unqualified name (e.g., p->e::raise may be a virtual call) but we record C as a parent type. "C::e::raise" is treated as a qualified name as usual. The qualifier_sym can be NULL in some error cases. */ check_assertion(qualifier_is_type); qualifier_type = sym_parent_class(qualifier_sym); is_qualified_name = FALSE; } /* if */ } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ if (qualifier_is_type) { locator_for_curr_id.parent.class_type = qualifier_type; locator_for_curr_id.is_class_member = qualifier_type != NULL; } else { if (ignore_std_namespace && qualifier_namespace == symbol_for_namespace_std->variant.namespace_info.ptr) { /* When using the g++ compatibility mode where "std" is an alias for the global namespace, ignore a qualifier that refers to "std" namespace. */ locator_for_curr_id.parent.namespace_ptr = NULL; is_file_scope_qualified_name = TRUE; } else { locator_for_curr_id.parent.namespace_ptr = qualifier_namespace; } /* if */ } /* if */ /* Update the locator with information about the qualifier (if any) that was discovered by is_generalized_identifier_start. */ locator_for_curr_id.is_qualified_name = is_qualified_name; locator_for_curr_id.is_global_qualified_name = is_global_qualified_name; locator_for_curr_id.is_file_scope_qualified_name = is_file_scope_qualified_name; locator_for_curr_id.has_been_coalesced = TRUE; locator_for_curr_id.is_vacuous_destructor_reference = is_vacuous_dtor_or_finalizer; locator_for_curr_id.is_nonclass_destructor = is_nonclass_dtor_or_finalizer; locator_for_curr_id.qualifier_is_super = qualifier_is_super; locator_for_curr_id.is_super_qualified = is_super_qualified; locator_for_curr_id.is_decltype_qualified = is_decltype_qualified; locator_for_curr_id.name_qualifier = name_qualifier; /* Since we're returning a pseudo-token, set pos_curr_token. */ pos_curr_token = start_position; #if EXTRA_SOURCE_POSITIONS_IN_IL /* The ending position should already be set correctly. */ #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ curr_token_sequence_number = start_seq_number; /* Restore the original error position. */ error_position = orig_error_position; /* Perform error checks as specified in "options". */ err |= check_for_generalized_identifier_errors(options, &pos_curr_token); } /* if */ if (is_dtor_like_locator(locator_for_curr_id) && locator_for_curr_id.is_vacuous_destructor_reference && locator_for_curr_id.is_nonclass_destructor) { /* For nonclass vacuous destructors, the parent class will be set to the type after the "~". For classes, the destructor type will have already been set by get_destructor_or_finalizer_name. */ dtor_or_finalizer_type = locator_for_curr_id.parent.class_type; if (dtor_or_finalizer_type != NULL) { locator_for_curr_id.variant.destructor_type = dtor_or_finalizer_type; } /* if */ } /* if */ if (err) { locator_for_curr_id.is_error = TRUE; } /* if */ exit: db_exit(); return result; #undef in_if_exists } /* f_is_generalized_identifier_start */ a_boolean coalesce_and_lookup_qualified_name (an_identifier_options_set options, an_identifier_lookup_mode ilm, a_boolean *err) /* Coalesces a generalized identifier. If the identifier was qualified (e.g., A::x or ::x) the identifier is looked up. Returns TRUE if identifier is a qualified name. The caller must guarantee that is_generalized_identifier_start is TRUE (i.e., that the thing being scanned is, in fact, an identifier). See also coalesce_and_lookup_generalized_identifier. */ { a_boolean return_value = FALSE; a_boolean okay = TRUE; a_type_ptr qualifier_type = NULL; a_namespace_ptr qualifier_namespace = NULL; a_boolean qualifier_is_type = TRUE; a_boolean is_vacuous_dtor_or_finalizer = FALSE; db_enter(4, "coalesce_and_lookup_qualified_name"); /* Macro used to determine whether we are processing the identifier in a Microsoft __if_exists or __if_not_exists directive. */ #define in_if_exists ((options & GID_IN_IF_EXISTS) != 0) *err = FALSE; if (C_dialect != C_dialect_cplusplus) goto exit; if (curr_token == tok_identifier && locator_for_curr_id.specific_symbol != NULL) { /* The current token is already a qualified name or specific symbol. Recheck for qualifier errors since the options specified may be different than a previous call. */ return_value = (locator_for_curr_id.is_qualified_name || locator_for_curr_id.is_implicitly_qualified); /* Perform error checks as specified in "options". */ okay = TRUE; /* Don't check any further if we already have an error locator. Leave okay TRUE so that we don't try to build a new error locator later. */ *err = is_error_locator(locator_for_curr_id); if (!*err) { if (check_for_generalized_identifier_errors(options, &error_position) || check_for_template_declarator_errors(options, &error_position)) { *err = TRUE; okay = FALSE; } /* if */ } /* if */ } else { /* Mask the error flags out of the options flags to prevent the errors from being diagnosed more than once. */ if (is_generalized_identifier_start(options & ~GID_ERROR_FLAGS) && curr_token == tok_identifier && locator_for_curr_id.is_qualified_name) { /* The current identifier is a qualified name. */ an_error_code error_code; a_source_position identifier_pos; a_boolean qualifier_is_super = FALSE; identifier_pos = locator_for_curr_id.source_position; /* If the qualifier is a type, qualifier_type will point to the type and qualifier_namespace will be NULL. If the qualifier is a namespace, qualifier_type will be NULL and qualifier_namespace will point to the namespace. */ qualifier_type = qualifier_class_type(locator_for_curr_id); qualifier_namespace = qualifier_namespace_ptr(locator_for_curr_id); qualifier_is_type = locator_for_curr_id.is_class_member; qualifier_is_super = locator_for_curr_id.qualifier_is_super; is_vacuous_dtor_or_finalizer = locator_for_curr_id.is_vacuous_destructor_reference; return_value = TRUE; *err |= is_error_locator(locator_for_curr_id); /* For Coverity. */ check_assertion(!qualifier_is_type || qualifier_type != NULL); /* Perform error checks as specified in "options". */ if (*err) { /* Don't try to lookup the identifier if an error occurred earlier. */ okay = FALSE; } else if (check_for_generalized_identifier_errors(options, &pos_curr_token)) { *err = TRUE; okay = FALSE; } else if (locator_for_curr_id.is_decltype_qualified && !is_vacuous_dtor_or_finalizer && qualifier_type != NULL && !is_class_struct_union_type(qualifier_type) && !is_enum_type(qualifier_type) && !(is_template_dependent_context() && is_template_dependent_type(qualifier_type))) { /* The qualified name was "decltype(something)::name", but the type from the decltype is not a class or enumeration type. For cases like "decltype(something)::x::name", this will have been diagnosed when the identifier was coalesced. */ expr_pos_ty_error(ec_bad_decltype_qualifier, &pos_curr_token, qualifier_type); set_to_error_locator(locator_for_curr_id); *err = TRUE; } else { a_boolean is_nonclass_dtor_or_finalizer = locator_for_curr_id.is_nonclass_destructor; an_id_lookup_options_set idl_options; /* Translate the general identifier options into ID lookup options. */ idl_options = idl_options_for_lookup_mode[(int)ilm]; /* If this is the declarator for a friend declaration, pass the friend lookup flag into the lookup routines. */ if ((options & GID_IS_FRIEND_DECL) != 0) { idl_options |= IDL_FRIEND_LOOKUP; } /* if */ /* If this is a C++20 implicit type context, pass the corresponding flag to the lookup routines. */ if ((options & GID_IMPLICIT_TYPENAME_CONTEXT) != 0) { idl_options |= IDL_IMPLICIT_TYPENAME_CONTEXT; } /* if */ if (qualifier_type != NULL && ((idl_options & IDL_IS_DECLARATOR) != 0)) { /* If this is a template parameter that represents a nested class of a class template, use the original nested type in its place. */ qualifier_type = orig_nested_type_if_nonreal_nested_type(qualifier_type); } /* if */ /* No errors were diagnosed. */ if (locator_for_curr_id.is_file_scope_qualified_name) { /* Look up the id in the file scope. */ if (is_vacuous_dtor_or_finalizer) { /* If qualifier_type is NULL an error occurred while processing the vacuous destructor. Treat this the same way we would a failed lookup. */ okay = qualifier_type != NULL; } else if (file_scope_id_lookup(il_header.primary_scope, &locator_for_curr_id, idl_options) != NULL) { } else { /* The identifier could not be found in the file scope. */ if (ilm == ilm_tentative_type || in_if_exists) { /* It is OK for a tentative type lookup to fail. */ okay = TRUE; } else { /* Issue one of several difference messages depending on the kind of symbol we are looking for. */ if (ilm == ilm_tag) { error_code = ec_name_not_tag_in_file_scope; } else if (ilm == ilm_class || ilm == ilm_qualified_ctor_initializer_name) { error_code = ec_name_not_class_in_file_scope; } else { error_code = ec_name_not_found_in_file_scope; } /* if */ pos_st_error(error_code, &identifier_pos, locator_for_curr_id.symbol_header->identifier); okay = FALSE; } /* if */ } /* if */ } else { if (!qualifier_is_super && ((qualifier_is_type && is_error_type(qualifier_type)) || (!qualifier_is_type && qualifier_namespace == NULL))) { okay = FALSE; } else if (!qualifier_is_super && qualifier_is_type && !is_nonclass_dtor_or_finalizer && is_incomplete_type(qualifier_type) && is_immediate_class_type(qualifier_type) && scope_is_null_or_placeholder( class_type_supp(qualifier_type)->assoc_scope)) { /* An error must have occurred while scanning the class qualifier. Don't try to do the lookup of the identifier following the qualifier. If the type is incomplete we also check whether the type is currently being defined -- it is considered complete if it is being defined. We determine this by checking the assoc_scope field of the class type supplement. If the type is incomplete an error will have been issued earlier. The check for the type kind being a class/struct/union type is needed because the type may also be a template parameter type. */ okay = FALSE; if (in_if_exists) { /* An incomplete type is silently ignored in this cases. */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (scanning_generated_code_from_metadata && (options & GID_IS_CPPCLI_CONSTRAINT) != 0 && qualifier_type->variant.class_struct_union. is_open_constructed_type) { /* In some C++/CLI cases the constraint from the initial declaration cannot be processed correctly (for example, if the constraint is A::B on the declaration of A where B is not yet known). We ignore the error here. The constraint from the later declaration will be used. */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { a_boolean intrinsic_no_member = FALSE; a_type_ptr intrinsic_member_tp = NULL; if (qualifier_is_type && locator_for_curr_id.symbol_header != NULL && symbol_for(qualifier_type)->header->has_intrinsic_name && intrinsic_templ_type_member_lookup( qualifier_type, locator_for_curr_id.symbol_header, &intrinsic_member_tp, &intrinsic_no_member) && intrinsic_no_member) { /* xyz::member, where xyz is an undefined class and member is intrinsically known not to exist (e.g. enable_if::type). Report the missing member rather than the incompleteness of xyz, and without instantiating it. */ pos_stsy_error(ec_not_a_member, &identifier_pos, locator_for_curr_id.symbol_header->identifier, symbol_for(skip_typerefs(qualifier_type))); } else { issue_incomplete_type_diag(&pos_curr_token, qualifier_type); } /* if */ } /* if */ } else { a_class_symbol_supplement_ptr cssp_for_dtor = NULL; /* For destructors, get the class symbol supplement for use below. In declarators we still do the lookup below because typedef names cannot be used to define destructors (except in Microsoft bugs mode). */ if (locator_for_curr_id.is_destructor_name && qualifier_is_type && (ilm != ilm_declarator || microsoft_bugs) && is_immediate_class_type(qualifier_type)) { cssp_for_dtor = class_symbol_supp(symbol_for(qualifier_type)); } /* if */ /* Don't try to look up a vacuous destructor name. */ if (is_vacuous_dtor_or_finalizer) { /* If qualifier_type is NULL an error occurred while processing the vacuous destructor. Treat this the same way we would a failed lookup. */ okay = qualifier_type != NULL; } else { /* Look up the id in the class scope. */ a_boolean qualifier_is_enum_type; a_boolean intrinsic_no_member = FALSE; if (ilm == ilm_using_declaration && qualifier_is_type && gpp_version_is(any_version) && scope_is(&scope_stack_top(), sck_class_struct_union)) { /* GCC accepts the following: struct E {}; template struct D: BT { using E::x; }; We emulate that by replacing qualifier_type by a type of the same name looked up in a dependent base. */ a_type_ptr curr_class = scope_stack_top().assoc_type; if (curr_class->variant.class_struct_union .is_prototype_instantiation && !is_template_dependent_type(qualifier_type)) { qualifier_type = look_up_qualifier_in_dependent_bases( qualifier_type); } /* if */ } /* if */ qualifier_is_enum_type = enum_qualifiers_enabled && qualifier_is_type && is_enum_type(qualifier_type); if (qualifier_is_enum_type && enum_qualified_id_lookup(&locator_for_curr_id, qualifier_type) != NULL) { /* In some modes, enumerations can be used as qualifiers. The name was found as an enumerator. */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (qualifier_is_super && super_qualified_id_lookup(&locator_for_curr_id, idl_options) != NULL) { #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if (cssp_for_dtor != NULL && cssp_for_dtor->destructor != NULL) { /* Get the destructor name for the class. The validity of the name was checked earlier. */ locator_for_curr_id.specific_symbol = cssp_for_dtor->destructor; } else if (qualifier_is_type && is_immediate_class_type(qualifier_type) && symbol_for(qualifier_type)->header ->has_intrinsic_name && !is_vacuous_dtor_or_finalizer && resolve_intrinsic_templ_type_member( &locator_for_curr_id, qualifier_type, &intrinsic_no_member)) { /* The qualified name xyz::member was resolved intrinsically, without completing (instantiating) xyz. */ if (intrinsic_no_member) { /* The member provably does not exist. Apply the same tolerance as the ordinary failed member lookup below: a tentative type lookup or an __if_exists query may fail without a diagnostic. This is what allows a delayed Microsoft default template argument such as enable_if::type to be accepted when it is never needed; otherwise the missing member is diagnosed. */ if (ilm == ilm_tentative_type || in_if_exists) { okay = TRUE; } else { pos_stsy_error( ec_not_a_member, &identifier_pos, locator_for_curr_id.symbol_header->identifier, symbol_for(skip_typerefs(qualifier_type))); *err = TRUE; okay = FALSE; } /* if */ } /* if */ } else if (qualifier_is_type && !qualifier_is_enum_type && class_qualified_id_lookup(&locator_for_curr_id, qualifier_type, idl_options) != NULL) { /* Ambiguity and access control checking is not done because we don't know yet what kind of reference this is. */ } else if (!qualifier_is_type && qualifier_namespace != NULL && namespace_qualified_id_lookup (&locator_for_curr_id, skip_namespace_aliases(qualifier_namespace), idl_options) != NULL) { /* The identifier was found in the namespace. */ } else { /* The identifier could not be found in the class or namespace scope. */ if (ilm == ilm_tentative_type || in_if_exists) { /* It is OK for a tentative type lookup to fail. */ okay = TRUE; } else if (is_error_locator(locator_for_curr_id)) { /* An error was previously issued. */ } else if (qualifier_is_super) { pos_st_error(ec_not_a_base_class_member, &identifier_pos, locator_for_curr_id.symbol_header->identifier); okay = FALSE; } else { /* Issue one of several difference messages depending on the kind of symbol we are looking for. */ a_symbol_ptr err_sym; if (ilm == ilm_tag || ilm == ilm_template_tag) { error_code = ec_not_a_tag_member; } else if (ilm == ilm_class || ilm == ilm_qualified_ctor_initializer_name) { error_code = ec_not_a_member_class; } else { error_code = C_mode() ? ec_not_a_field : ec_not_a_member; } /* if */ if (qualifier_is_type) { err_sym = symbol_for(skip_typerefs(qualifier_type)); } else { err_sym = symbol_for(qualifier_namespace); } /* if */ pos_stsy_error(error_code, &identifier_pos, locator_for_curr_id.symbol_header->identifier, err_sym); okay = FALSE; } /* if */ } /* if */ } /* if */ } /* if */ } /* if */ } /* if */ /* If the symbol found is a class template then this must be a reference to a instance of the class template. This can occur when a template name appears in a qualified name without a template argument list (if a template argument list were specified it would have been coalesced by f_is_generalized_identifier_start). */ { a_symbol_ptr symbol; a_boolean templ_err = FALSE; symbol = locator_for_curr_id.specific_symbol; if (symbol != NULL) { symbol = fundamental_symbol_of(symbol); if (symbol->kind == (a_symbol_kind)sk_class_template) { (void)coalesce_template_class_reference(symbol, options, &templ_err); if (templ_err) { okay = FALSE; *err = TRUE; } /* if */ } /* if */ } /* if */ } /* A qualified declarator name in a template declaration must name a template or a member of a class template. */ if (check_for_template_declarator_errors(options, &error_position)) { *err = TRUE; okay = FALSE; } /* if */ } else { /* Not a qualified name -- possibly not an identifier. If an error locator was built by is_generalized_identifier_start, set err to TRUE. */ if (is_error_locator(locator_for_curr_id)) { *err = TRUE; } /* if */ } /* if */ } /* if */ if (!okay) { /* For the error cases, set the current locator to an error locator. Restore the type of the qualifier so that it can be used for additional error diagnosis. For example, in an access adjustment such as "A::b" we would like to be able to give an error if "A" is not a base class of the current class even if "b" is not a member of "A". Also restore the vacuous destructor flag to assist in error diagnosis by the caller. */ make_specific_symbol_error_locator(&locator_for_curr_id); if (qualifier_is_type) { locator_for_curr_id.parent.class_type = qualifier_type; locator_for_curr_id.is_class_member = qualifier_type != NULL; } else { locator_for_curr_id.parent.namespace_ptr = qualifier_namespace; locator_for_curr_id.is_class_member = FALSE; } /* if */ locator_for_curr_id.is_vacuous_destructor_reference = is_vacuous_dtor_or_finalizer; *err = TRUE; } /* if */ /* Set error position to the beginning of the coalesced pseudo-token. */ error_position = pos_curr_token; #if DEBUG if (debug_level >= 4) { fprintf(f_debug, "coalesce_and_lookup_qualified_name: "); if (return_value) { if (is_error_locator(locator_for_curr_id)) { fprintf(f_debug, "\n"); } else if (locator_for_curr_id.specific_symbol == NULL) { fprintf(f_debug, "name = %s (no specific symbol)\n", locator_for_curr_id.symbol_header->identifier); } else { fprintf(f_debug, "name = %s\n", locator_for_curr_id.specific_symbol->header->identifier); } /* if */ } else { fprintf(f_debug, "not qualified name\n"); } /* if */ } /* if */ #endif /* DEBUG */ exit: db_exit(); return return_value; #undef in_if_exists } /* coalesce_and_lookup_qualified_name */ a_symbol_ptr coalesce_and_lookup_generalized_identifier (an_identifier_options_set options, an_identifier_lookup_mode ilm, a_boolean *err) /* The current token is the start of a name, qualified or not. Call coalesce_and_lookup_qualified_name and then normal_id_lookup, and return a pointer to the symbol found, if any. options is a bit set of options controlling the lookup of the final id of a qualified name or the normal identifier. The caller must guarantee that is_generalized_identifier_start is TRUE (i.e., that the thing being scanned is, in fact, an identifier). */ { a_symbol_ptr symbol; an_id_lookup_options_set idl_options; a_boolean templ_err = FALSE; /* Macro used to determine whether we are processing the identifier in a Microsoft __if_exists or __if_not_exists directive. */ #define in_if_exists ((options & GID_IN_IF_EXISTS) != 0) /* Mask the error flags out of the options flags to prevent the errors from being diagnosed more than once. */ if (coalesce_and_lookup_qualified_name(options & ~GID_ERROR_FLAGS, ilm, err)) { /* The identifier is a qualified name. */ symbol = locator_for_curr_id.specific_symbol; #if CHECKING if (symbol == NULL && ilm != ilm_tentative_type && !in_if_exists) { internal_error ("coalesce_and_lookup_generalized_identifier: specific_symbol is NULL"); } /* if */ #endif /* CHECKING */ if (symbol != NULL) reduce_projection_symbol_to_fundamental_symbol(symbol); } else if (is_resolved_id_pseudo_token(curr_token)) { symbol = resolve_id_pseudo_token_to_sym(); if (symbol != NULL) { make_resolved_id_pseudo_token_locator(symbol, &locator_for_curr_id); locator_for_curr_id.source_position = pos_curr_token; curr_token = tok_identifier; } else { make_error_locator(&locator_for_curr_id); } /* if */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (locator_for_curr_id.symbol_header == NULL) { /* This can happen when producing tokens from an IFC file. */ *err = TRUE; symbol = NULL; make_error_locator(&locator_for_curr_id); #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { /* Normal identifier -- look it up. */ check_assertion_str( curr_token == tok_identifier, "coalesce_and_lookup_generalized_identifier: not identifier"); /* Translate the general identifier options into ID lookup options. */ idl_options = idl_options_for_lookup_mode[(int)ilm]; #if MICROSOFT_EXTENSIONS_ALLOWED if (in_if_exists) { idl_options |= IDL_IF_EXISTS_LOOKUP; } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ symbol = normal_id_lookup(&locator_for_curr_id, idl_options); } /* if */ /* If this is the symbol of a class template then this must be a reference to a instance of the class template. Scan the argument list and get a pointer to the symbol for the specific instance of the template class. is_template_id will be TRUE if the template reference has already been coalesced. If this is a variable template without an argument list, an error will be issued. */ a_boolean is_var_templ = FALSE, treat_as_unknown_func = FALSE; if (symbol != NULL && (is_class_template_or_injected_template_symbol(symbol) || (is_var_templ = symbol_is(symbol, sk_variable_template)))) { if (symbol != NULL && is_var_templ && gpp_version_is(<130000) && scope_stack_top().in_prototype_instantiation && scope_is(&scope_stack_top(), sck_template_instantiation) && scope_stack_top().template_sym != NULL && is_alias_template_symbol(scope_stack_top().template_sym) && has_dependent_nontype_parameter(symbol) && (expr_stack == NULL || curr_expr_is_potentially_evaluated())) { /* GCC sometimes does not substitute nondependent template-ids referring to variable templates if they appear in certain contexts, particularly in the definitions of alias templates. For example: template using Void = void; template bool vart; template using Alias = Void>; GCC 12 accepts that example. The mechanism used by GCC is not very clear, and it appears to depend on the particular version of GCC, but the criteria above cover examples found in real code. To avoid instantiating the variable template, treat it as an unknown function template instead. */ symbol = find_unknown_function_symbol( symbol, locator_for_curr_id.is_qualified_name); treat_as_unknown_func = TRUE; } /* if */ if (locator_for_curr_id.is_unknown_template_reference) { /* This is a template class reference that was changed back to a template reference by ensure_correct_nonreal_instance_kind. It is now being used in a way that requires a type of some kind. Redo the operation that finds the specific class being named. */ if (ilm == ilm_tag || ilm == ilm_typename || ilm == ilm_class || ilm == ilm_using_typename || ilm == ilm_qualified_ctor_initializer_name || (options & GID_IMPLICIT_TYPENAME_CONTEXT) != 0 || (ilm == ilm_tentative_type && use_implicit_typename())) { a_template_arg_ptr arg_list; arg_list = locator_for_curr_id.template_arg_list; symbol = find_template_class(symbol, &arg_list, /*prototype_allowed=*/FALSE, (a_symbol_ptr)NULL, /*instantiate_nonreal=*/FALSE, /*do_not_create=*/FALSE, /*in_substitution=*/FALSE); if (!record_form_of_name_reference && arg_list != NULL) { free_template_arg_list(arg_list); arg_list = NULL; } /* if */ locator_for_curr_id.is_unknown_template_reference = FALSE; locator_for_curr_id.specific_symbol = symbol; locator_for_curr_id.template_arg_list = arg_list; locator_for_curr_id.do_not_clear_specific_symbol = TRUE; locator_for_curr_id.symbol_header = symbol->header; } /* if */ } else { /* Coalesce the template class or variable. For a class, this can end up being resolved (e.g., to the injected template name). For the variable case, an error will result (from the missing argument list). */ if (treat_as_unknown_func) { if (locator_for_curr_id.is_template_id) { /* The template argument list was previously coalesced. Create a tpck_template_ref entry on top of the tpck_unknown_function entry to record the template arguments. */ a_constant *cp = fs_constant(ck_template_param), *fcp = symbol->variant.constant; symbol = alloc_symbol(sk_constant, locator_for_curr_id.symbol_header, &locator_for_curr_id.source_position); symbol->variant.constant = cp; *cp = *fcp; cp->source_corresp.assoc_info = (char*)symbol; cp->variant.template_param.kind = tpck_template_ref; cp->variant.template_param.variant.template_ref.con = fcp; cp->variant.template_param.variant.template_ref.arg_list = locator_for_curr_id.template_arg_list; } else { symbol = coalesce_template_function_reference(symbol, next_token(), &templ_err); } /* if */ } else if (is_var_templ) { symbol = coalesce_template_variable_reference( symbol, curr_token_sequence_number, options, next_token(), &templ_err); } else { symbol = coalesce_template_class_reference(symbol, options, &templ_err); } /* if */ } /* if */ } /* if */ *err |= templ_err; /* If an error occurred while scanning the template argument list, return a NULL symbol. The locator will already be set to an error locator. */ if (templ_err) symbol = NULL; /* Perform error checks as specified in "options". */ *err |= check_for_generalized_identifier_errors(options, &error_position); return symbol; #undef in_if_exists } /* coalesce_and_lookup_generalized_identifier */ a_boolean curr_token_is_identifier_string(a_const_char *tok_str) /* Return TRUE if the current token is an identifier spelled like *tok_str. */ { a_boolean result = FALSE; if (curr_token == tok_identifier) { a_symbol_header_ptr sym_hdr = locator_for_curr_id.symbol_header; if (sym_hdr != NULL && symbol_header_is_for_identifier_string(sym_hdr, tok_str)) { result = TRUE; } /* if */ } /* if */ return result; } /* curr_token_is_identifier_string */ a_boolean check_context_sensitive_keyword(a_token_kind tok_kind, a_const_char *tok_str) /* If the current token is an identifier spelled like *tok_str, turn that token into the given token kind. Return whether (after this transformation) the current token is tok_kind. This is useful to handle context-sensitive keywords. */ { if (curr_token_is_identifier_string(tok_str)) { curr_token = tok_kind; } /* if */ return curr_token == tok_kind; } /* check_context_sensitive_keyword */ static a_file_suffix_ptr alloc_file_suffix(void) /* Allocate a file suffix entry, initialize it, and return a pointer to it. */ { a_file_suffix_ptr fsp; fsp = (a_file_suffix_ptr)alloc_general(sizeof(a_file_suffix)); #if DEBUG num_file_suffixes_allocated++; #endif /* DEBUG */ fsp->next = NULL; fsp->suffix = NULL; return fsp; } /* alloc_file_suffix */ static void add_to_file_suffix_list(a_file_suffix_ptr *list_ptr, a_const_char *suffix, sizeof_t length) /* Add a new entry to the end of the file suffix list specified by list_ptr. If the entry is already on the list the new entry is ignored. */ { a_file_suffix_ptr fsp; a_file_suffix_ptr prev_fsp = NULL; a_boolean found = FALSE; fsp = *list_ptr; while (fsp != NULL) { if (strcmp(fsp->suffix, suffix) == 0) { /* The suffix is already on the list. */ found = TRUE; break; } /* if */ prev_fsp = fsp; fsp = fsp->next; } /* while */ if (!found) { /* If the suffix is not found add it to the end of the list which is now pointed to by prev_fsp. */ fsp = alloc_file_suffix(); /* Allocate space for the suffix including a null delimiter. */ fsp->suffix = (char *)alloc_general((sizeof_t)(unsigned)length + 1); strncpy(fsp->suffix, suffix, length); /* Terminate the copy of the string with a null character. */ fsp->suffix[length] = '\0'; if (prev_fsp == NULL) { /* This is the first entry on the list. */ *list_ptr = fsp; } else { prev_fsp->next = fsp; } /* if */ #if DEBUG if (db_flag_is_set("add_to_file_suffix_list")) { fprintf(f_debug, "Added \"%s\" to the suffix list.\n", fsp->suffix); } /* if */ #endif /* DEBUG */ } /* if */ } /* add_to_file_suffix_list */ static a_file_suffix_ptr conv_string_to_file_suffix_list(a_const_char *list) /* Convert the members of a colon separated list of file suffixes to a list of file suffix entries. Return a pointer to the newly created list. */ { a_const_char *ptr = list; a_const_char *start; a_const_char *end; a_file_suffix_ptr list_fsp = NULL; /* Skip over an initial ":" in the string. */ if (*ptr == ':') ptr++; while (*ptr) { /* Skip any spaces. */ while (*ptr == ' ') ptr++; /* See if we've reached the end of the string. */ if (!*ptr) break; start = ptr; /* Find the ending delimiter. */ ptr = strchr(start, ':'); if (ptr == NULL) { /* If there is no ending delimiter use the end of the string. */ ptr = start + strlen(start); } /* if */ /* Get a pointer to the last character of the string. */ end = ptr - 1; /* Move back past any trailing spaces. */ while (*end == ' ') end--; add_to_file_suffix_list(&list_fsp, start, (sizeof_t)(end - start + 1)); /* If we haven't reached the end of the string, move the pointer past the delimiter. */ if (*ptr) ptr++; } /* while */ return list_fsp; } /* conv_string_to_file_suffix_list */ static void cache_to_compound_stmt(a_token_cache *p_token_cache, a_token_set_array stop_tokens) /* Cache tokens into p_token_cache until a left brace or semicolon is encountered. stop_tokens is the stop token set to be used. */ { incr_token_set_array_element(stop_tokens, tok_lbrace); incr_token_set_array_element(stop_tokens, tok_semicolon); cache_token_stream(p_token_cache, stop_tokens); decr_token_set_array_element(stop_tokens, tok_lbrace); decr_token_set_array_element(stop_tokens, tok_semicolon); } /* cache_to_compound_stmt */ static void cache_compound_stmt(a_token_cache *p_token_cache, a_token_set_array stop_tokens) /* Cache a compound statement (i.e., "{...}") into p_token_cache. stop_tokens is the stop token set to be used. After this routine is called the current token is the right brace, except for error cases. */ { push_lexical_state_stack(); caching_tokens = TRUE; check_assertion(curr_token == tok_lbrace); /* Cache the "{" and advance past it. */ cache_curr_token(p_token_cache); (void)get_token(); /* Cache all tokens up to the "}" (or end-of-source). */ incr_token_set_array_element(stop_tokens, tok_rbrace); cache_token_stream(p_token_cache, stop_tokens); /* Cache the "}" and append an end-of-source token. */ if (curr_token == tok_rbrace) { cache_curr_token(p_token_cache); /* A get_token is intentionally not done -- the caller will advance past the end of the template declaration. */ } /* if */ pop_lexical_state_stack(); } /* cache_compound_stmt */ static void cache_function_try_block_clauses(a_token_cache *p_token_cache, a_token_set_array stop_tokens) /* Cache into p_token_cache the catch clauses that follow a function definition that are associated with a function try block. In C++/CLI mode, the optional finally clause is also cached. stop_tokens is the stop token set to be used. After this routine is called the current token is the right brace, except for error cases. */ { while (next_token() == tok_catch) { /* Advance past the right brace that has already been cached. */ (void)get_token(); /* Cache the "catch" and advance past it. */ cache_curr_token(p_token_cache); (void)get_token(); /* Cache the parameter declaration. */ cache_to_compound_stmt(p_token_cache, stop_tokens); /* Cache the catch compound statement (if it looks like one is next). */ if (curr_token == tok_lbrace) { cache_compound_stmt(p_token_cache, stop_tokens); } /* if */ /* Exit the loop if we are not at the expected close of the compound statement. */ if (curr_token != tok_rbrace) break; } /* while */ #if MICROSOFT_EXTENSIONS_ALLOWED if (cli_or_cx_enabled && curr_token == tok_rbrace && next_token() == tok_identifier) { a_tiny_scanning_token_cache aux_cache; /* We create an auxiliary token cache to hold the tok_rbrace as we look ahead and determine if the upcoming identifier is "finally". */ cache_curr_token(aux_cache.ptr()); /* Advance past the tok_rbrace so that locator_for_curr_id points to the tok_identifier. */ (void)get_token(); if (curr_token_is_identifier_string("finally") && next_token() == tok_lbrace) { /* Cache and advance past the "finally". */ cache_curr_token(p_token_cache); (void)get_token(); /* curr_token is now tok_lbrace, begin caching the compound statement. */ cache_compound_stmt(p_token_cache, stop_tokens); } else { /* It was not a "finally" clause. Put the tok_rbrace and the current token back on the token stream. After this, current token will again point to the tok_rbrace from the end of the previous compound statement. p_token_cache was not modified. */ rescan_cached_tokens(aux_cache.ptr()); } /* if */ } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* cache_function_try_block_clauses */ a_boolean cache_expansion_statement_body(a_token_cache_ptr cache) /* Lexically prescan an expansion statement body into cache, leaving its last token current. Return TRUE if the current token was cached and should be consumed after prototype replay; in error cases it may instead be a token of the enclosing statement. The caller terminates and replays the cache. */ { a_boolean last_token_cached; push_lexical_state_stack(); caching_tokens = TRUE; last_token_cached = skip_one_statement(cache); if (last_token_cached) cache_curr_token(cache); pop_lexical_state_stack(); return last_token_cached; } /* cache_expansion_statement_body */ static void cache_ctor_initializers( a_token_cache *p_token_cache, a_token_set_array stop_tokens, a_boolean *compound_stmt_cached, a_source_position *start_pos) /* Cache any ctor-initializers of a constructor into p_token_cache. stop_tokens is the stop token set to be used. This is used when C++11 brace-enclosed initializer lists are allowed because we can't simply cache up to an opening brace to find the compound statement of the function body. In some cases, we don't know whether a right brace is part of an initializer or is the function body. Only after reaching the closing brace can we decide. If it turns out to be a function body, we set *compound_stmt_cached to TRUE and set *start_pos to the starting position of the compound statement. */ { a_source_position local_start_pos = null_source_position; a_token_kind next_tok = tok_error; a_boolean might_be_compound_stmt; check_assertion(curr_token == tok_colon); *compound_stmt_cached = FALSE; incr_token_set_array_element(stop_tokens, tok_rbrace); incr_token_set_array_element(stop_tokens, tok_lbrace); incr_token_set_array_element(stop_tokens, tok_semicolon); for (;;) { might_be_compound_stmt = FALSE; /* Cache up to a stop token. */ cache_token_stream(p_token_cache, stop_tokens); /* If we stopped on a left brace, cache to the matching right brace. */ if (curr_token == tok_lbrace) { might_be_compound_stmt = TRUE; local_start_pos = pos_curr_token; (void)cache_token_stream_until_matching_token(p_token_cache, CTS_NO_OPTIONS); cache_curr_token(p_token_cache); next_tok = next_token(); } /* if */ /* If we didn't find a matching right brace, some kind of syntax error must have occurred. Exit the loop. */ if (curr_token != tok_rbrace) break; /* We should be at the end of a brace-enclosed initializer (but it could also have been the compound statement of the function body). If variadic templates are enabled, look for a pack expansion. */ if (next_tok == tok_ellipsis && variadic_templates_enabled) { (void)get_token(); cache_curr_token(p_token_cache); next_tok = next_token(); /* We know this was not a compound statement if it was followed by an ellipsis. */ might_be_compound_stmt = FALSE; } /* if */ if (next_tok != tok_comma) break; /* Get and cache the comma. */ (void)get_token(); cache_curr_token(p_token_cache); (void)get_token(); } /* for */ /* If the next token is not a left brace (presumably the start of the compound statement of the function body), assume we have already cached the compound statement. */ if (next_tok != tok_lbrace && might_be_compound_stmt) { check_assertion(cmp_source_positions(local_start_pos, null_source_position) != 0); *compound_stmt_cached = TRUE; if (start_pos != NULL) *start_pos = local_start_pos; } else { /* Don't skip past a semicolon or end-of-source in error cases. */ if (curr_token != tok_semicolon && curr_token != tok_end_of_source) { (void)get_token(); } /* if */ } /* if */ decr_token_set_array_element(stop_tokens, tok_rbrace); decr_token_set_array_element(stop_tokens, tok_lbrace); decr_token_set_array_element(stop_tokens, tok_semicolon); } /* cache_ctor_initializers */ void skip_function_body(a_boolean is_constructor) /* Skip over the tokens of a function body. is_constructor is TRUE if the function is a constructor. */ { a_token_cache cache; (void)cache_function_body(&cache, is_constructor, (a_boolean*)NULL, (a_token_sequence_number*)NULL, (a_token_sequence_number*)NULL, (a_source_position*)NULL, (a_source_position*)NULL); } /* skip_function_body */ a_boolean cache_function_body( a_token_cache *p_token_cache, a_boolean is_constructor, a_boolean *missing_end, a_token_sequence_number *first_tsn, a_token_sequence_number *last_tsn, a_source_position *start_pos, a_source_position *end_pos) /* Scan the tokens of a function definition. This can include scanning the tokens of ctor-initializers for a constructor definition and can also include scanning the tokens of a function try block. Return TRUE if a complete function definition was scanned. is_constructor is TRUE if the function is a constructor. missing_end is set to TRUE if the ending brace of the function body is not found; it can be NULL if this information need not be returned. first_tsn and last_tsn are the token sequence numbers of the first and last tokens of the function body; they can be NULL if the token sequence numbers need not be returned. start_pos and end_pos are the starting and ending source positions of the function body; they can be NULL if the positions need not be returned. */ { a_token_set_array stop_tokens; a_boolean result = FALSE; a_boolean try_found = FALSE; a_boolean save_caching_tokens = caching_tokens; a_boolean compound_stmt_cached = FALSE; a_boolean defaulted; db_enter(3, "cache_function_body"); /* Set a flag that indicates that the tokens being scanned are to be cached. */ caching_tokens = TRUE; if (first_tsn != NULL) *first_tsn = NO_TOKEN_SEQUENCE_NUMBER; if (last_tsn != NULL) *last_tsn = NO_TOKEN_SEQUENCE_NUMBER; if (missing_end != NULL) *missing_end = FALSE; if (start_pos != NULL) *start_pos = null_source_position; if (end_pos != NULL) *end_pos = null_source_position; if (curr_token == tok_lbrace || curr_token == tok_try || (curr_token == tok_colon && is_constructor)) { /* Initialize a local stop token set. */ clear_token_set_array(stop_tokens); /* Save the token sequence number of the first token of the definition. */ if (first_tsn != NULL) *first_tsn = curr_token_sequence_number; /* Make a note whether this is a function try block. This controls whether we look for catch clauses later. */ try_found = curr_token == tok_try; if (try_found) { /* Cache the "try" and advance past it. */ cache_curr_token(p_token_cache); (void)get_token(); } /* if */ if (curr_token == tok_colon) { /* This is a ctor-initializer list on a constructor -- cache it. When brace-enclosed initializers are enabled we need to handle constructs like ": x{...}, y{...}", so a special function is called. When that feature is not enabled, we can just cache to the compound statement of the function body. */ if (list_init_enabled) { cache_ctor_initializers(p_token_cache, stop_tokens, &compound_stmt_cached, start_pos); } else { cache_to_compound_stmt(p_token_cache, stop_tokens); } /* if */ } /* if */ /* If compound_stmt_cached is TRUE, the routine that cached the ctor-initializers also ended up caching the compound statement as well. This is okay as we were about to cache it below. Continue processing as normal, but suppress the actual caching of the compound statement below. */ if (curr_token == tok_lbrace || compound_stmt_cached) { /* This is a compound statement that is the body of the function. */ if (!compound_stmt_cached) { /* Save the starting position of the main block of the function. */ if (start_pos != NULL) *start_pos = pos_curr_token; cache_compound_stmt(p_token_cache, stop_tokens); } /* if */ #if EXTRA_SOURCE_POSITIONS_IN_IL /* Save the ending position of the main block of the function. */ if (end_pos != NULL) *end_pos = end_pos_curr_token; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ if (try_found) { /* If this function is a function try block, cache the associated catch or C++/CLI finally clauses. */ cache_function_try_block_clauses(p_token_cache, stop_tokens); } /* if */ if (curr_token == tok_rbrace) { /* A get_token is intentionally not done -- the caller will advance past the end of the template declaration. */ result = TRUE; } else { /* The end of the function body was not found. This is usually the result of a mismatched delimiter. */ if (missing_end != NULL) *missing_end = TRUE; } /* if */ /* Save the token sequence number of the last token of the definition. */ if (last_tsn != NULL) *last_tsn = curr_token_sequence_number; } /* if */ /* Add an end-of-source token to the end of the token cache to assure that we don't scan past the end of the cache in the actual scan. */ terminate_token_cache(p_token_cache); } else if (curr_token == tok_assign && deleted_or_defaulted_def_next(&defaulted)) { /* Cache "= delete" or "= default" (leave the semicolon for the caller). */ if (start_pos != NULL) *start_pos = pos_curr_token; cache_curr_token(p_token_cache); (void)get_token(); cache_curr_token(p_token_cache); (void)get_token(); if (curr_token == tok_semicolon) { #if EXTRA_SOURCE_POSITIONS_IN_IL if (end_pos != NULL) *end_pos = end_pos_curr_token; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ result = TRUE; /* For defaulted functions, include the ";" as part of the body. */ if (defaulted) cache_curr_token(p_token_cache); } else if (missing_end != NULL) { *missing_end = TRUE; } /* if */ } /* if */ /* Clear the flag that indicates that the tokens being scanned are to be cached. */ caching_tokens = save_caching_tokens; db_exit(); return result; } /* cache_function_body */ static void push_string_insert_cache_entry(void) /* Push a dummy entry on the cached token rescan list indicating that tokens should be fetched from the insertion string. */ { cached_token_rescan_stack->emplace_back( detail::a_token_factory::build_tok_insert_string()); } /* push_string_insert_cache_entry */ static void pop_string_insert_cache_entry(void) /* Remove the dummy entry from the cached token rescan list that indicates that tokens should be fetched from the insertion string. */ { check_assertion(fetching_tokens_from_insert_string()); cached_token_rescan_stack->pop_back(); } /* pop_string_insert_cache_entry */ namespace { /* This structure captures and restores the lexer state values that need restored when escaping normal lexing to process a string into tokens. */ struct a_lexical_state_capture { inline a_lexical_state_capture(); inline ~a_lexical_state_capture(); private: a_boolean save_in_token_insertion_from_string; /* The saved original value of in_token_insertion_from_string. */ a_const_char *save_curr_source_line; /* The saved original value of curr_source_line. */ a_boolean save_treat_newline_as_token; /* The saved original value of treat_newline_as_token. */ a_boolean save_no_modifs_to_curr_source_line; /* The saved original value of no_modifs_to_curr_source_line. */ a_const_char *save_after_end_of_curr_source_line; /* The saved original value of after_end_of_curr_source_line. */ a_boolean save_caching_tokens; /* The saved original value of caching_tokens. */ a_boolean save_expand_macros; /* The saved original value of expand_macros. */ a_source_position save_token_insertion_position; /* The saved original value of token_insertion_position. */ a_pointer_registration *save_registered_pointers; /* The saved original value of registered_pointers. */ a_const_char *save_curr_char_loc; /* The saved original value of curr_char_loc. */ a_pointer_registration save_curr_char_loc_reg; /* The pointer registration used to register save_curr_char_loc. */ }; /* a_lexical_saved_state */ a_lexical_state_capture::a_lexical_state_capture() /* Capture the current state values. */ : save_in_token_insertion_from_string(in_token_insertion_from_string), save_curr_source_line(curr_source_line), save_treat_newline_as_token(treat_newline_as_token), save_no_modifs_to_curr_source_line(no_modifs_to_curr_source_line), save_after_end_of_curr_source_line(after_end_of_curr_source_line), save_caching_tokens(caching_tokens), save_expand_macros(expand_macros), save_token_insertion_position(token_insertion_position), save_registered_pointers(registered_pointers), save_curr_char_loc(NULL), save_curr_char_loc_reg() { /* WATCH OUT: Pointers into macro_buffer or the raw_text of a macro arg are dangerous, since those things can be reallocated. Such pointers must be registered by calling register_pointer_variable so that they can be updated on any reallocation. */ register_pointer_variable(this->save_curr_char_loc, this->save_curr_char_loc_reg); this->save_curr_char_loc = curr_char_loc; } /* a_lexical_state_capture::a_lexical_state_capture */ a_lexical_state_capture::~a_lexical_state_capture() /* Restore the original state values. */ { curr_char_loc = this->save_curr_char_loc; registered_pointers = this->save_registered_pointers; token_insertion_position = this->save_token_insertion_position; expand_macros = this->save_expand_macros; caching_tokens = this->save_caching_tokens; after_end_of_curr_source_line = this->save_after_end_of_curr_source_line; no_modifs_to_curr_source_line = this->save_no_modifs_to_curr_source_line; treat_newline_as_token = this->save_treat_newline_as_token; curr_source_line = this->save_curr_source_line; in_token_insertion_from_string = this->save_in_token_insertion_from_string; } /* a_lexical_state_capture::~a_lexical_state_capture */ } /* namespace */ static a_string create_token_buffer_from_string(a_const_char *string) /* Given an unprocessed string, return a string appropriate for scanning. */ { char line_end_lex_escapes[] = { /* Add the lexical escape for a newline. */ LE_ESCAPE, LE_NEWLINE, /* Add the end of line escape. */ LE_ESCAPE, LE_END_OF_LINE }; a_string_view line_end_lex_escapes_view( line_end_lex_escapes, sizeof(line_end_lex_escapes) / sizeof(char)); /* Form a temporary buffer of the string followed by the escape sequence. */ return a_string(string, line_end_lex_escapes_view); } /* create_token_buffer_from_string */ void insert_string_into_token_stream( a_const_char *string, a_boolean insert_after, a_boolean p_expand_macros, a_boolean suspend_caching, a_source_position position_for_tokens/*lint !e1746*/) /* Scan "string" as a sequence of tokens. Build a token cache and insert it into the token stream at the current position. "insert_after" is TRUE if the tokens should be inserted after the current token; FALSE if they should be inserted before the current token. p_expand_macros is TRUE if macros should be expanded while processing the tokens. position_for_tokens is used as the beginning and end source position for each token in the string. If suspend_caching is TRUE, background caching of tokens is disabled while the tokens from the string are processed. Note this function must be carefully used when the current token's start_of_curr_token and end_of_curr_token values are potentially relevant. It internally uses token caches, which will not preserve the start_of_curr_token and end_of_curr_token values unless fetch_pp_tokens is TRUE, so this information could be lost during insertion of the given string. See the documentation of start_of_curr_token and end_of_curr_token for more information. */ { a_scanning_token_cache cache; a_tiny_scanning_token_cache curr_token_cache; a_boolean save_suspend_caching_tokens = curr_lexical_state_stack_entry->suspend_caching_tokens; { /* Use a_lexical_state_capture to capture and restore the lexer state. */ a_lexical_state_capture state; /* Reset the information used by get_token to fetch the tokens from the string in the buffer. */ token_insertion_position = position_for_tokens; curr_lexical_state_stack_entry->suspend_caching_tokens = suspend_caching; treat_newline_as_token = TRUE; no_modifs_to_curr_source_line = FALSE; /* Form a temporary buffer of the string followed by the escape sequence. */ a_string buffer = create_token_buffer_from_string(string); a_const_char *buff_str = buffer.as_temp_characters(); curr_char_loc = buff_str; curr_source_line = curr_char_loc; after_end_of_curr_source_line = &buff_str[buffer.length()]; in_token_insertion_from_string = TRUE; caching_tokens = TRUE; expand_macros = p_expand_macros; /* Push a marker into the cached token rescan list indicating that tokens should be fetched from the insert string. */ push_string_insert_cache_entry(); /* Put the current token in the cache. When the token string is inserted after the current token we just put the current token at the start of the cache to be rescanned. When inserting before the current token we create a separate cache containing the current token. */ if (insert_after) { cache_curr_token(cache.ptr()); } else { cache_curr_token(curr_token_cache.ptr()); } /* if */ /* Fetch tokens from the buffer. Stop on a newline. */ for (;;) { /* All of the tokens should have begin and end positions as specified by the caller. */ pos_curr_token = position_for_tokens; #if EXTRA_SOURCE_POSITIONS_IN_IL end_pos_curr_token = position_for_tokens; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ if (get_token() == tok_newline) break; cache_curr_token(cache.ptr()); } /* for */ } /* Resume fetching tokens from the previous source. */ pop_string_insert_cache_entry(); /* Discard the newline token from the buffer when rescanning from the token cache. */ if (!insert_after) { /* If the string is inserted before the current token, rescan the current token. */ rescan_cached_tokens(curr_token_cache.ptr(), /*discard_curr_token=*/TRUE); } /* if */ /* Scan the tokens from the cache. */ rescan_cached_tokens(cache.ptr(), insert_after); /* Restore the original token caching state. */ curr_lexical_state_stack_entry->suspend_caching_tokens = save_suspend_caching_tokens; } /* insert_string_into_token_stream */ void cache_tokens_from_string(a_const_char *string, a_token_cache_ptr cache, const a_source_position *position_for_tokens) /* Scan "string" as a sequence of tokens and place them in the given cache, giving each token the given position (both the start and end position of those tokens). Macros are not expanded. */ { a_const_char *save_start_of_curr_token = start_of_curr_token; a_const_char *save_end_of_curr_token = end_of_curr_token; a_tiny_scanning_token_cache curr_token_cache; { /* Use a_lexical_state_capture to capture and restore the lexer state. */ a_lexical_state_capture state; /* Adjust the insertion position */ token_insertion_position = *position_for_tokens; /* Cache the current token so it can be restored when we're done. */ cache_curr_token(curr_token_cache.ptr()); /* Create a new lexical state for processing of the buffer. */ push_lexical_state_stack(); /* Reset the information used by get_token to fetch the tokens from the string in the buffer. */ treat_newline_as_token = TRUE; no_modifs_to_curr_source_line = FALSE; /* Form a temporary buffer of the string followed by the escape sequence. */ a_string buffer = create_token_buffer_from_string(string); a_const_char *buff_str = buffer.as_temp_characters(); curr_char_loc = buff_str; curr_source_line = curr_char_loc; after_end_of_curr_source_line = &buff_str[buffer.length()]; in_token_insertion_from_string = TRUE; caching_tokens = TRUE; expand_macros = FALSE; /* Push a marker into the cached token rescan list indicating that tokens should be fetched from the insert string. */ push_string_insert_cache_entry(); /* Fetch tokens from the buffer. Stop on a newline. */ for (;;) { /* All of the tokens should have begin and end positions as specified by the caller. */ pos_curr_token = *position_for_tokens; #if EXTRA_SOURCE_POSITIONS_IN_IL end_pos_curr_token = *position_for_tokens; #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ if (get_token() == tok_newline) break; cache_curr_token(cache); } /* for */ } /* Resume fetching tokens from the previous source. */ pop_string_insert_cache_entry(); /* Rescan the original current token, discarding the current token state (which is already copied into the given cache). */ rescan_cached_tokens(curr_token_cache.ptr(), /*discard=*/TRUE); /* Restore the start and end of the current token. This ensures that this function doesn't have an observable affect on parsing outside of the cached tokens. This must be done after the rescan as the rescan will wipe out the start and end values. */ end_of_curr_token = save_end_of_curr_token; start_of_curr_token = save_start_of_curr_token; /* Return to the original lexical state. */ pop_lexical_state_stack(); } /* cache_tokens_from_string */ void begin_rescan_of_pragma_tokens(a_pending_pragma_ptr ppp) /* Activate the token cache containing the pragma to be scanned and push a pragma scope to be used while scanning the pragma tokens. */ { check_assertion_str2(ppp->descr_ptr->binding_kind != pbk_preproc_immediate, "begin_rescan_of_pragma_tokens:", "cannot be used for preproc_immediate pragmas"); /* Start a new lexical state. */ push_lexical_state_stack(); /* If the pragma was scanned as pp-tokens, go into pp-token mode now. */ fetch_pp_tokens = ppp->descr_ptr->fetch_pp_tokens; rescan_shared_reusable_cache(ppp->token_cache); /* Push a pragma scope. This prevents names introduced by the pragma processing from polluting the current scope. */ (void)push_scope((a_scope_kind)sck_pragma, NO_SCOPE_NUMBER, (a_type_ptr)NULL, (a_routine_ptr)NULL); } /* begin_rescan_of_pragma_tokens */ void wrapup_rescan_of_pragma_tokens(a_boolean error_in_pragma) /* This routine is called by pragma processing routines when they have reached the end of the pragma directive being scanned. This routine fetches the token that terminates the token cache and returns the token stream to its original state. If the current token is not the end-of-source token that terminates the pragma directive, an error is issued (unless the error_in_pragma flag is set indicating the pragma processing routine already diagnosed an error). The pragma scope pushed when the token cache is actived is popped here. */ { if (curr_token != tok_end_of_source) { if (!error_in_pragma) { pos_error(ec_extra_text_in_pp_directive, &pos_curr_token); } /* if */ /* Flush any tokens until an end-of-source token is found. */ while (curr_token != tok_end_of_source) { (void)get_token(); } /* while */ } /* if */ /* Bypass the cache terminator. */ (void)get_token(); fetch_pp_tokens = FALSE; /* Restore the lexical state as at entry. */ pop_lexical_state_stack(); /* Pop the pragma scope. */ pop_scope(); } /* wrapup_rescan_of_pragma_tokens */ /* Static variables and routines that are used to build up a string representation of a token cache. Characters are added to the temp_text_buffer, which is indefinitely expandable, and then a string is created -- a copy of the assigned portion of the buffer, with a null terminator appended. */ STATIC_THREAD a_seq_number curr_seq; /* The sequence number of the source position of the most recently added token, etc., in the buffer. */ STATIC_THREAD an_il_to_str_output_control_block octl; /* Output control block used to interface to the il_to_str routines. */ STATIC_THREAD a_boolean keep_spacing_in_token_string; /* If TRUE, spacing between tokens in the cache should be preserved in the string, including newlines for line breaks. */ #if MICROSOFT_EXTENSIONS_ALLOWED STATIC_THREAD a_boolean suppress_identifier_wrapping_in_token_string; /* If TRUE, suppress wrapping of identifiers with the Microsoft __identifier(...) construct when they have names that conflict with keywords. Used in contexts like attributes that do not allow __identifier(...). Not meaningful outside of Microsoft mode.*/ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ static void add_whitespace_to_string(a_seq_number seq_incr, a_column_number column_incr) /* Add seq_incr newline characters and column_incr blanks to temp_text_buffer, incrementing pos_in_temp_text_buffer accordingly. */ { if (keep_spacing_in_token_string) { for (; seq_incr > 0; --seq_incr) { put_ch_to_temp_text_buffer('\n'); } /* for */ for (; column_incr > 0; --column_incr) { put_ch_to_temp_text_buffer(' '); } /* for */ } else if (!scanning_module_macro && (seq_incr > 0 || column_incr > 0)) { /* Not keeping original spacing, so just put out one space, unless this is a macro definition coming from a module, in which case whitespace will be explicitly added.*/ put_ch_to_temp_text_buffer(' '); } /* if */ } /* add_whitespace_to_string */ static inline a_boolean preceding_space_needed(a_token_kind token) /* Given a token that's about to be added to a string (see add_token_to_string below), determine whether or not a preceding space should be added. */ { a_boolean result = TRUE; switch (token) { case tok_comma: case tok_semicolon: result = FALSE; break; default: ;/* result = TRUE; */ } /* switch */ if (result) { if (pos_in_temp_text_buffer == 0) { result = FALSE; } /* if */ } /* if */ return result; } /* preceding_space_needed */ static void add_token_to_string(const a_shared_token &tok) /* Copy characters representing the token into temp_text_buffer, and increase pos_in_temp_text_buffer by the number of characters added. */ { a_seq_number seq_incr; a_column_number column_incr; a_token_kind token = tok->get_kind(); db_enter(5, "add_token_to_string"); const a_source_position *src_pos = tok->get_source_position(); if (src_pos->seq <= curr_seq) { /* We're on the same line as the previous token processed, so just add a space (in most cases) to separate the tokens. (Note: the line for the current token may be less than curr_seq when a macro expansion occurs. Treat the token as being on the current line.) */ if (!preceding_space_needed(token)) { column_incr = 0; } else { check_assertion(pos_in_temp_text_buffer > 0 || curr_seq < src_pos->seq); /* Add a single space. */ column_incr = 1; } /* if */ /* Don't add a line feed. */ seq_incr = 0; } else { /* We've moved to a new line. Compute the indentation. */ column_incr = src_pos->column - 1; /* Compute the number of line feed characters to add. */ seq_incr = src_pos->seq - curr_seq; /* Reset the current line. */ curr_seq = src_pos->seq; } /* if */ /* Add any spaces and line feeds that might be required. */ if (seq_incr > 0 || column_incr > 0) { add_whitespace_to_string(seq_incr, column_incr); } /* if */ /* Now put out the characters representing the token. */ if (token == tok_newline) { /* Ignore tok_newline. It only comes up in Microsoft asm blocks. The required number of newline characters will be added when the sequence number changes. */ } else if (tok->is_pp_token()) { /* A token that was saved as a pp-token. This token should be added to the string using the start of token pointer. Note that the copy of the token made for a cached pp-token is known to be null terminated. */ put_str_to_temp_text_buffer(tok->get_pp_token_descr()->token_start); } else if (token == tok_int_constant || token == tok_float_constant || token == tok_string_literal || token == tok_char_constant || token == tok_gen_constant || token == tok_ud_literal || is_microsoft_tok_uuid(token)) { a_constant *constant = (token != tok_ud_literal ? const_cast(tok->get_constant()) : tok->get_ud_literal_descr()->value_con); /* Write out a string that represents the constant. */ if (constant != NULL && constant->kind == ck_error) { /* If there was an error in scanning the token, reset the flag to avoid an assertion failure in the subroutine. This means something like "" will be put out in the template string. */ octl.gen_compilable_code = FALSE; } /* if */ if (token == tok_ud_literal) { /* Put out a user-defined literal. */ a_boolean use_token_spelling = FALSE; const a_ud_literal_descr *ud_lit_descr = tok->get_ud_literal_descr(); a_symbol_ptr ud_lit_op_sym = ud_lit_descr->op_sym; if (ud_lit_op_sym == NULL) { /* The associated symbol was not known when the token was cached. */ } else if (symbol_is(ud_lit_op_sym, sk_function_template)) { /* The literal is associated with a literal operator template, so the constant contains the spelling of the literal. */ use_token_spelling = TRUE; constant = ud_lit_descr->spelling_con; } else if (symbol_is(ud_lit_op_sym, sk_routine)) { a_routine_ptr rout = ud_lit_op_sym->variant.routine.ptr; if (rout->is_raw_literal_operator) { /* The literal is associated with a raw literal operator, so the constant contains the spelling of the literal. */ use_token_spelling = TRUE; constant = ud_lit_descr->spelling_con; } /* if */ } /* if */ if (use_token_spelling) { /* The constant is a ck_string whose value is the exact spelling of the literal. */ check_assertion(constant != NULL && constant->kind == (a_constant_repr_kind)ck_string); put_str_to_temp_text_buffer(constant->variant.string.value); } else { /* The constant should just be put out as normal. */ octl.part_of_ud_literal = TRUE; form_constant(constant, /*need_parent=*/FALSE, &octl); octl.part_of_ud_literal = FALSE; } /* if */ /* Now put out the ud-suffix. */ put_str_to_temp_text_buffer(ud_lit_descr->suffix); if (octl.pending_right_paren) { /* The il_to_str routines preceded the literal portion with a left parenthesis. Close it now. */ put_ch_to_temp_text_buffer(')'); octl.pending_right_paren = FALSE; } /* if */ } else { /* Just put out the constant. */ form_constant(constant, /*need_parens=*/TRUE, &octl); } /* if */ /* Reset the flag, in case it had been changed. */ octl.gen_compilable_code = TRUE; #if CHECKING /* Check for tokens that should not show up in the cached tokens of a template declaration. These tokens will be accepted if cached as pp-tokens. */ } else if (token == tok_end_of_source || token == tok_header_name || token == tok_pp_number || token == tok_digit_sequence || token == tok_cpp_quote || if_microsoft_extensions(token == tok_cli_typeid ||) token == tok_ptr_to_member) { internal_error("add_token_to_string: unexpected token"); #endif /* CHECKING */ } else if (token == tok_identifier) { /* An identifier. */ const a_symbol_locator *sym_loc = tok->get_symbol_locator(); #if MICROSOFT_EXTENSIONS_ALLOWED a_boolean microsoft_identifier_used = (sym_loc->symbol_header->microsoft_identifier_used && !suppress_identifier_wrapping_in_token_string); if (microsoft_identifier_used) { put_str_to_temp_text_buffer("__identifier("); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ put_str_to_temp_text_buffer(sym_loc->symbol_header->identifier); #if MICROSOFT_EXTENSIONS_ALLOWED if (microsoft_identifier_used) { put_str_to_temp_text_buffer(")"); } /* if */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else if (is_restrict_token(token)) { #if !SUPPRESS_RESTRICT_IN_GENERATED_CODE a_const_char *restrict_kw = "restrict"; #if BACK_END_IS_C_GEN_BE || BACK_END_IS_CP_GEN_BE /* When targeting a gcc/g++ compiler, put out "__restrict__" since "restrict" may not be accepted. Clang accepts "restrict", but only in C mode. */ if (gcc_or_clang_is_generated_code_target) restrict_kw = "__restrict__"; #endif /* BACK_END_IS_C_GEN_BE || BACK_END_IS_CP_GEN_BE */ put_str_to_temp_text_buffer(restrict_kw); #endif /* !SUPPRESS_RESTRICT_IN_GENERATED_CODE */ } else if (microsoft_mode && token == tok_asm) { /* In Microsoft mode always put out "__asm", since "asm" is not necessarily accepted by the Microsoft compiler. */ put_str_to_temp_text_buffer("__asm"); } else if (gnu_mode && token == tok_asm) { /* In GNU mode always put out "__asm__", since "asm" is not accepted by gcc in some modes. */ put_str_to_temp_text_buffer("__asm__"); #if GCC_BUILTIN_VARARGS } else if (gnu_mode && token == tok_va_start) { /* In GNU mode when using GCC_BUILTIN_VARARGS, several spellings of the va_start, etc. tokens are accepted. In such cases, put out the built-in spelling as it is accepted by the GNU compilers even if the header that defines va_start, etc. has not been included. */ put_str_to_temp_text_buffer("__builtin_va_start"); } else if (gnu_mode && token == tok_va_arg) { put_str_to_temp_text_buffer("__builtin_va_arg"); } else if (gnu_mode && token == tok_va_end) { put_str_to_temp_text_buffer("__builtin_va_end"); } else if (gnu_mode && token == tok_va_copy) { put_str_to_temp_text_buffer("__builtin_va_copy"); #endif /* GCC_BUILTIN_VARARGS */ #if BACK_END_IS_CP_GEN_BE } else if (microsoft_dialect_is_generated_code_target && token == tok_pretty_function_name) { /* In the Microsoft dialect, __PRETTY_FUNCTION__ is __FUNCSIG__. */ put_str_to_temp_text_buffer("__FUNCSIG__"); } else if (gcc_or_clang_is_generated_code_target && token == tok_ext_alignof) { /* g++/clang expect the lower-case variant of the keyword spelling. */ put_str_to_temp_text_buffer("__alignof__"); } else if (gcc_or_clang_is_generated_code_target && token == tok_c99_complex) { /* In GNU mode, the __complex__ type is handled as the C99 _Complex type. Map the keyword back to GNU form. */ put_str_to_temp_text_buffer("__complex__"); } else if (gcc_or_clang_is_generated_code_target && token == tok_intaddr) { /* The g++ builtin function __offsetof is identical to the EDG-specific __INTADDR__. */ put_str_to_temp_text_buffer("__offsetof"); } else if ((microsoft_dialect_is_generated_code_target || sun_is_generated_code_target) && token == tok_ext_alignof) { put_str_to_temp_text_buffer("__alignof"); } else if (gcc_or_clang_is_generated_code_target && token == tok_decltype) { /* Current versions of g++ only accept the decltype keyword with -std=c++0x, but they accept __decltype in both modes. Use the safer form. */ put_str_to_temp_text_buffer("__decltype"); } else if (gcc_or_clang_is_generated_code_target && token == tok_typeof) { /* Current versions of g++ only accept the "typeof" keyword with -std=gnu++XX, so use the extension spelling. */ put_str_to_temp_text_buffer("__typeof__"); #endif /* BACK_END_IS_CP_GEN_BE */ #if MICROSOFT_EXTENSIONS_ALLOWED } else if (cppcx_enabled && token == tok_gcnew) { /* In C++/CX mode, scan_whitespace_keyword recognizes tok_ref_new, but returns tok_gcnew. Map the keyword back to "ref new". */ put_str_to_temp_text_buffer(token_names[(int)tok_ref_new]); #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } else { /* A keyword or other token whose literal name can be put out. */ put_str_to_temp_text_buffer(token_names[(int)token]); } /* if */ db_exit(); } /* add_token_to_string */ static void add_pragmas_to_string(const a_pending_pragma_list &pragmas) /* If the current token has any pragmas associated with it, add strings to represent them to temp_text_buffer. Note that all pragmas that are encountered, whatever their other characteristics, are included. */ { a_boolean is_pseudo_pragma; a_seq_number seq_incr = 0; a_column_number column_incr = 0; db_enter(5, "add_pragmas_to_string"); for (const a_shared_pending_pragma &ppp : pragmas) { a_boolean is_pragma_directive; a_boolean new_seq = ppp->pragma_position.seq >= curr_seq; is_pseudo_pragma = ppp->descr_ptr->is_pseudo_pragma; is_pragma_directive = !is_pseudo_pragma && !ppp->is_function_style_pragma && !ppp->is_microsoft_pragma_operator; if (new_seq || is_pragma_directive) { /* We have moved to a new line, or we are generating a #pragma (which must begin on a new line). Compute the indentation. */ column_incr = ppp->pragma_position.column - 1; /* Compute the number of line feed characters to add. */ if (new_seq) { seq_incr = ppp->pragma_position.seq - curr_seq; } /* if */ if (is_pragma_directive) { if (seq_incr == 0) seq_incr = 1; } /* if */ /* Reset the current line. */ curr_seq = ppp->pragma_position.seq; } else if (ppp->pragma_position.seq <= curr_seq) { /* We're on the same line as the previous token processed, so just add a space (in most cases) to separate the tokens. (Note: the line for the current token may be less than curr_seq when a macro expansion occurs. Treat the token as being on the current line.) */ column_incr = 1; /* Don't add a line feed. */ seq_incr = 0; } /* if */ /* Add any spaces and line feeds that might be required. */ if (seq_incr > 0 || column_incr > 0) { add_whitespace_to_string(seq_incr, column_incr); } /* if */ if (is_pseudo_pragma) { /* Add comment delimiter to the template string. */ put_str_to_temp_text_buffer("/*"); } else if (ppp->is_microsoft_pragma_operator) { /* A Microsoft __pragma operator. */ put_str_to_temp_text_buffer("__pragma("); } else if (ppp->is_function_style_pragma) { /* The _Pragma form. */ put_str_to_temp_text_buffer("_Pragma("); } else { /* Add "#pragma " to the template string. */ put_str_to_temp_text_buffer("#pragma "); } /* if */ if (ppp->descr_ptr->record_pragma_text) { /* Note: the pragma id is already part of pragma_text. */ check_assertion(ppp->pragma_text != NULL); if (ppp->is_function_style_pragma && !ppp->is_microsoft_pragma_operator) { /* The operand of _Pragma is a quoted string, but the enclosing quotes aren't part of the recorded pragma text. Add enclosing quotes and escape any embedded quote and backslash characters (the escapes in the original will have been removed by the process of converting the string operand of _Pragma to the non-string operand of #pragma). */ put_ch_to_temp_text_buffer('"'); for (a_const_char *p = ppp->pragma_text; *p != '\0'; ++p) { if (*p == '"' || *p == '\\') { put_ch_to_temp_text_buffer('\\'); } /* if */ put_ch_to_temp_text_buffer(*p); } /* for */ put_str_to_temp_text_buffer("\""); } else { /* The recorded pragma text can be used directly. */ put_str_to_temp_text_buffer(ppp->pragma_text); } /* if */ } else { /* The pragma id is not part of pragma_text, so it has to be added explicitly. */ if (ppp->is_function_style_pragma && !ppp->is_microsoft_pragma_operator) { /* The operand of _Pragma is a quoted string. Enclose the pragma name and its operands in quotes. */ put_ch_to_temp_text_buffer('"'); } /* if */ put_str_to_temp_text_buffer(pragma_ids[(int)ppp->descr_ptr->kind]); if (is_pseudo_pragma) { /* Do nothing for pseudo pragmas; they should have no token cache. */ check_assertion(ppp->token_cache.ptr() == NULL); } else { /* Otherwise, there should always be a token cache. */ check_assertion(ppp->token_cache.ptr() != NULL); if (!ppp->token_cache->is_empty()) { /* Add the tokens from the pragma token cache to the string. */ add_token_cache_to_string(ppp->token_cache.ptr()); } /* if */ } /* if */ if (ppp->is_function_style_pragma && !ppp->is_microsoft_pragma_operator) { put_ch_to_temp_text_buffer('"'); } /* if */ } /* if */ if (is_pseudo_pragma) { /* Add terminating comment delimiter to the template string. */ put_str_to_temp_text_buffer("*/"); } else if (ppp->is_microsoft_pragma_operator || ppp->is_function_style_pragma) { /* Put out the closing parenthesis for the _Pragma or Microsoft __pragma operator. */ put_str_to_temp_text_buffer(")"); } /* if */ } /* for */ db_exit(); } /* add_pragmas_to_string */ static void put_pseudo_token_start_to_temp_text_buffer( a_token_kind kind, a_token_sequence_number tsn) /* Add a starting character sequence, indicating the beginning of a pseudo token of the given kind at the given token sequence number, to the temporary text buffer. */ { put_str_to_temp_text_buffer(" (!: TSN "); put_uint_to_temp_text_buffer(tsn); put_str_to_temp_text_buffer(" aka ("); put_str_to_temp_text_buffer(token_names[(int)kind]); } /* put_pseudo_token_start_to_temp_text_buffer */ static void put_pseudo_token_start_to_temp_text_buffer( const a_shared_token &tok) /* Add a starting character sequence, indicating the beginning of a pseudo token cached as the given cached token, to the temporary text buffer. */ { put_pseudo_token_start_to_temp_text_buffer(tok->get_kind(), tok->get_starting_seq_number()); } /* put_pseudo_token_start_to_temp_text_buffer */ static void put_pseudo_token_end_to_temp_text_buffer() /* Add an ending character sequence, indicating the end of a pseudo token, to the temporary text buffer. */ { put_str_to_temp_text_buffer(") :!)"); } /* put_pseudo_token_end_to_temp_text_buffer */ #if DEBUG static constexpr a_const_char *tok_dbg_bright_colors [] = { "092", "094", "091"}; /* Colors used for -d-brightcolor token accenting. */ static constexpr a_const_char *tok_dbg_dark_colors [] = { "032", "034", "031"}; /* Colors used for -d-darkcolor token accenting. */ static constexpr unsigned tok_dbg_num_bright_colors = sizeof(tok_dbg_bright_colors) / sizeof(a_const_char*); /* The number of colors available for use by -d-brightcolor token accenting. */ static constexpr unsigned tok_dbg_num_dark_colors = sizeof(tok_dbg_dark_colors) / sizeof(a_const_char*); /* The number of colors available for use by -d-darkcolor token accenting. */ #endif /* DEBUG */ static void add_cached_token_to_string( const a_shared_token &tok, a_boolean expand_pseudo_tokens) /* Append the given token as a string to the temp_text_buffer. If expand_pseudo_tokens is TRUE, details of the pseudo token will be included in the string. */ { if (tok->is_pragma()) { /* This token entry represents one or more pragmas. Call a routine to add the pragmas to the string. */ add_pragmas_to_string(*tok->get_pragma_list()); } else if (expand_pseudo_tokens && (tok->is_extracted_template_body() || tok->is(tok_removed_expr))) { put_pseudo_token_start_to_temp_text_buffer(tok); put_pseudo_token_end_to_temp_text_buffer(); } else if (expand_pseudo_tokens && tok->is_ifc_reference()) { put_pseudo_token_start_to_temp_text_buffer(tok); #if DEBUG { put_str_to_temp_text_buffer(" - "); a_lexical_ifc_index_reference ifc_idx = tok->get_ifc_index(); a_string idx_as_str = s_db_lexical_ifc_index(ifc_idx); put_str_to_temp_text_buffer(idx_as_str.as_temp_characters()); } #endif /* DEBUG */ put_pseudo_token_end_to_temp_text_buffer(); } else { /* A normal token (including, possibly, a pp-token). */ if (expand_pseudo_tokens && tok->is(tok_end_of_source)) { put_pseudo_token_start_to_temp_text_buffer(tok); put_pseudo_token_end_to_temp_text_buffer(); } else if (tok->is(tok_removed_template_body)) { if (expand_pseudo_tokens) { put_pseudo_token_start_to_temp_text_buffer(tok); put_pseudo_token_end_to_temp_text_buffer(); } else { /* If a removed template body token is re-cached, it may not have the extra info. Replace it with a semicolon. */ put_ch_to_temp_text_buffer(';'); } /* if */ } else { add_token_to_string(tok); } /* if */ } /* if */ if (tok->is_asm_string()) { /* A Microsoft asm string. Add the asm string to the buffer. */ /* Insert a space between the asm token and the asm string. */ add_whitespace_to_string((a_seq_number)0, (a_column_number)1); put_str_to_temp_text_buffer(tok->get_asm_string()); } /* if */ } /* add_cached_token_to_string */ a_string token_to_string(const a_shared_token &tok, /* Defaulted: */ a_boolean expand_pseudo_tokens) /* Return the given token as a string. If expand_pseudo_tokens is TRUE, details of the pseudo token will be included in the returned string. */ { a_temp_text_buffer_swap tmp_buffer_swap; add_cached_token_to_string(tok, expand_pseudo_tokens); return a_string(a_string_view(temp_text_buffer, pos_in_temp_text_buffer)); } /* token_to_string */ void add_cached_tokens_to_string(a_const_token_cache_iterator begin, a_const_token_cache_iterator end, a_token_sequence_number start_tsn, a_token_sequence_number end_tsn) /* Go through a list of cached tokens and add the tokens to the string that is being constructed that represents the tokens. If start_tsn and/or end_tsn are not NO_TOKEN_SEQUENCE_NUMBER only the tokens (inclusively) following start_tsn and (exclusively) preceding end_tsn are included in the string. This function assumes token sequence numbers appear in order. Additionally, if an end of source token is encountered before end_tsn, this function will stop before the end_tsn. */ { a_const_token_cache_iterator it = begin; check_assertion(start_tsn <= end_tsn || start_tsn == NO_TOKEN_SEQUENCE_NUMBER || end_tsn == NO_TOKEN_SEQUENCE_NUMBER); /* Skip any tokens that are before the desired starting point. */ if (start_tsn != NO_TOKEN_SEQUENCE_NUMBER) { for (; it != end; ++it) { const a_shared_token &tok = *it; if (tok->get_starting_seq_number() >= start_tsn) { break; } /* if */ } /* for */ } /* if */ for (; it != end; ++it) { const a_shared_token &tok = *it; /* Stop when we run out of tokens or hit an end-of-source token. */ if (tok->is(tok_end_of_source)) { break; } /* if */ /* Stop if we've reached the specified ending token sequence number. */ if (end_tsn != NO_TOKEN_SEQUENCE_NUMBER && tok->get_starting_seq_number() >= end_tsn) { break; } /* if */ if (tok->is(tok_removed_expr)) { /* A special token that indicates the location of a removed default argument or exception specification. The actual default argument tokens should still be used for purposes of generating the template string. */ const a_token_cache *removed_cache = tok->get_removed_expression(); if (removed_cache != NULL) { /* Recurse into the removed cache. */ add_cached_tokens_to_string(removed_cache->begin(), removed_cache->end(), start_tsn, end_tsn); } else { /* If the cache is NULL, this is a cache representing a missing expression. */ } /* if */ continue; } /* if */ /* Do special processing for extracted body tokens. */ if (tok->is_extracted_template_body()) { const an_extracted_template_descr *tdescr = tok->get_extracted_template_descr(); a_boolean add_body_string = TRUE; /* A template body was extracted at this location. Insert the body of the template at this point in the string. */ a_symbol_ptr sym = tdescr->symbol; a_template_symbol_supplement_ptr tssp = template_supplement_for_symbol(sym); #if NONCLASS_TEMPLATE_INSTANTIATIONS_IN_SOURCE_SEQUENCE_LISTS && \ REMOVE_INLINE_BODIES_FROM_CLASS_TEMPLATE_DEFINITIONS if (sym->kind == sk_member_function && !entity_cannot_be_specialized(sym)) { /* When source sequence entries for nonclass template instantiations are generated, member function bodies of class templates are suppressed because certain compilers don't permit a member function to be both defined in the class and specialized later. (If the member function cannot be specialized, don't suppress the body since it will be needed for instantiations.) */ add_body_string = FALSE; } /* if */ #endif /* NONCLASS_TEMPLATE_INSTANTIATIONS_IN_SOURCE_SEQUENCE_LISTS && ... */ if (add_body_string && tssp->cache->tokens.ptr() != NULL) { add_cached_tokens_to_string(tssp->cache->tokens->begin(), tssp->cache->tokens->end(), /*start_tsn=*/NO_TOKEN_SEQUENCE_NUMBER, /*end_tsn=*/NO_TOKEN_SEQUENCE_NUMBER); } /* if */ /* This semicolon was inserted and so should be suppressed if the body is output above. */ a_boolean add_orig_token = !tok->is(tok_removed_template_body) && (!add_body_string || !tdescr->semicolon_inserted); if (add_orig_token) { add_token_to_string(tok); } /* if */ } else { /* Otherwise, fallback to the common token printing, ignoring pseudo tokens. */ add_cached_token_to_string(tok, /*expand_pseudo_tokens=*/FALSE); } /* if */ } /* for */ } /* add_cached_tokens_to_string */ void add_token_cache_segment_to_string(const a_token_cache *cache, a_token_sequence_number start_tsn, a_token_sequence_number end_tsn) /* Go through a token cache and add the tokens to the string that is being constructed that represents the tokens in the cache. If start_tsn and/or end_tsn are not NO_TOKEN_SEQUENCE_NUMBER only the tokens >= start_tsn and < end_tsn are included in the string. */ { #if DEBUG if (db_flag_is_set("atcts")) { db_token_cache(cache, "add_token_cache_segment_to_string"); } /* if */ #endif /* DEBUG */ add_cached_tokens_to_string(cache->begin(), cache->end(), start_tsn, end_tsn); } /* add_token_cache_segment_to_string */ void add_token_cache_to_string(const a_token_cache *cache) /* Go through a token cache and add the tokens to the string that is being constructed that represents the tokens in the cache. This is an interface to add_token_cache_segment_to_string that supplies default values for the starting/ending token sequence numbers. */ { add_token_cache_segment_to_string( cache, (a_token_sequence_number)NO_TOKEN_SEQUENCE_NUMBER, (a_token_sequence_number)NO_TOKEN_SEQUENCE_NUMBER); } /* add_token_cache_to_string */ void init_token_string(const a_source_position *pos, a_boolean keep_spacing, ARG_UNUSED a_boolean suppress_identifier_wrapping) /* Prepare to generate a string from one or more token caches. Initialize the string length to zero and set the current sequence number to the position specified by pos. If keep_spacing is TRUE, the string will preserve the original spacing, including newlines to indicate line breaks in the source. If suppress_identifier_wrapping is TRUE, do not wrap identifiers that look like keywords in __identifier(...) in Microsoft mode. */ { curr_seq = pos->seq; pos_in_temp_text_buffer = 0; keep_spacing_in_token_string = keep_spacing; #if MICROSOFT_EXTENSIONS_ALLOWED suppress_identifier_wrapping_in_token_string = suppress_identifier_wrapping; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ } /* init_token_string */ a_const_char *token_string(void) /* Return the string generated from token caches since the last call to init_token_string. The result points into the temporary buffer that accumulates that string and is therefore invalidated by the next call to init_token_string. */ { /* The accumulated characters are delimited by their recorded length, and characters written for an earlier token string may still follow them, so a null character must be supplied before the string can be read as one. */ ensure_temp_text_buffer_space(pos_in_temp_text_buffer + 1); temp_text_buffer[pos_in_temp_text_buffer] = '\0'; return temp_text_buffer; } /* token_string */ char *make_copy_of_token_string(void) /* Make a copy of the string generated from token caches and return a pointer to it. The source string is in the templ_text_buffer. The copy is made in IL memory. */ { char *il_string; il_string = (char *)alloc_il((sizeof_t)(pos_in_temp_text_buffer + 1)); (void)memcpy(il_string, temp_text_buffer, size_t_arg(pos_in_temp_text_buffer)); /* Add a null terminator. */ il_string[pos_in_temp_text_buffer] = '\0'; return il_string; } /* make_copy_of_token_string */ a_const_char *il_string_for_curr_token(void) /* Return a pointer to a null-terminated string representing the current token. For non-identifiers, the string may occasionally be slightly different from the source form (e.g., digraphs are returned as ordinary tokens). */ { a_const_char *result; if (curr_token == tok_identifier) { /* For identifiers reuse the string already stored in IL memory. */ result = locator_for_curr_id.symbol_header->identifier; } else if (curr_token == tok_error || curr_token == tok_removed_expr || curr_token == tok_removed_template_body) { /* These tokens do not have a text representation. We only get here with input that contains severe syntax errors. */ expect_error(); result = ""; } else { /* No source characters are (reliably) available. Create a cache containing the current token and recreate the string from that. */ a_tiny_scanning_token_cache cache; cache_curr_token(cache.ptr()); init_token_string(&pos_curr_token, /*keep_spacing=*/FALSE, /*suppress_identifier_wrapping=*/FALSE); add_token_cache_to_string(cache.ptr()); result = make_copy_of_token_string(); } /* if */ return result; } /* il_string_for_curr_token */ #if GET_DEFINITION_OF_CLASS_NEEDED void get_definition_of_class(a_type_ptr class_type) /* This routine is used to allow an incomplete class declaration to be created at one point and to allow the class to be defined by scanning tokens at some later point. Unlike such a class definition processed from normal source code, this processing can happen at an arbitrary point in time much like a template instantiation. class_type is the class to be defined. (This includes C++/CLI delegate class types.) */ { a_symbol_ptr class_sym; a_symbol_ptr class_sym_for_context; a_template_decl_info_ptr tdip; a_boolean define_class = FALSE; sizeof_t size = 0; a_type_ptr class_type_for_context = class_type; a_class_type_supplement_ptr ctsp = class_type_supp(class_type); a_source_position position_for_tokens; a_scope_depth saved_non_local_class_fixup_depth = non_local_class_fixup_depth; #if GENERATE_SOURCE_SEQUENCE_LISTS a_boolean saved_source_sequence_entries_disallowed; #endif /* GENERATE_SOURCE_SEQUENCE_LISTS */ #if CPPCLI_ENABLING_POSSIBLE a_boolean saved_scanning_generated_code_from_metadata; a_boolean saved_scanning_generated_code; a_boolean is_delegate = FALSE; a_boolean is_generic_definition = FALSE; an_assembly_index assembly_index = 0; an_assembly_index saved_assembly_index = curr_assembly_index; an_assembly_scope_index assembly_scope_index = 0; a_cpp_cli_token metadata_type_def_token = 0; #endif /* CPPCLI_ENABLING_POSSIBLE */ class_sym = symbol_for(class_type); #if CPPCLI_ENABLING_POSSIBLE if (class_type->incomplete && !class_type->definition_pending) { if (ctsp->assembly_scope_index != 0 && ctsp->metadata_type_def_token != 0) { /* The class is from an assembly. Load the definition of the class now. */ assembly_scope_index = ctsp->assembly_scope_index; assembly_index = assembly_index_from_assembly_scope_index( assembly_scope_index); metadata_type_def_token = ctsp->metadata_type_def_token; if (class_sym->variant.class_struct_union.extra_info->being_defined) { /* The class is already in the process of being defined, so another definition should not be started. This can occur with metadata generated by other languages (e.g., Visual Basic), where a class can implement its own nested interface. */ type_error(ec_class_metadata_not_representable, class_type); } else { define_class = TRUE; } /* if */ } /* if */ } /* if */ #endif /* CPPCLI_ENABLING_POSSIBLE */ /* This routine cannot handle local classes. */ if (class_type->source_corresp.is_local_to_function) { define_class = FALSE; } /* if */ if (!define_class) goto done; #if DEBUG if (db_flag_is_set("gdoc")) { fprintf(f_debug, "Getting definition of "); db_type_name(class_type); fprintf(f_debug, "\n"); } /* if */ #endif /* DEBUG */ class_type->definition_pending = TRUE; class_sym_for_context = class_sym; /* The template instantiation scope stack management infrastructure is used to reestablish the context in which the tokens of the class definition should be processed. A special template declaration information entry is created for purposes of creating the context for the class. */ #if GENERATE_SOURCE_SEQUENCE_LISTS /* Don't generate source sequence entries for the injected class definitions. */ saved_source_sequence_entries_disallowed = source_sequence_entries_disallowed; scope_stack_top().source_sequence_entries_disallowed = TRUE; source_sequence_entries_disallowed = TRUE; #endif /* GENERATE_SOURCE_SEQUENCE_LISTS */ #if CPPCLI_ENABLING_POSSIBLE curr_assembly_index = assembly_index; saved_scanning_generated_code_from_metadata = scanning_generated_code_from_metadata; scanning_generated_code_from_metadata = TRUE; is_generic_definition = class_type-> variant.class_struct_union.is_generic_definition; /* For nested classes of generic definitions, don't attempt to push an instantiation type for the nested class because we don't have the appropriate information to pass for the template decl info. */ if (is_generic_definition && class_type->source_corresp.is_class_member && sym_parent_class(class_sym)-> variant.class_struct_union.is_generic_definition) { class_type_for_context = NULL; class_sym_for_context = NULL; } /* if */ #endif /* CPPCLI_ENABLING_POSSIBLE */ saved_scanning_generated_code = scanning_generated_code; scanning_generated_code = TRUE; tdip = alloc_template_decl_info(); set_template_decl_info_for_class_definition(tdip, class_type_for_context); (void)push_template_instantiation_scope( tdip, class_type_for_context, (a_routine_ptr)NULL, class_sym_for_context, class_sym_for_context, (a_template_arg_ptr)NULL, /*push_lex_state=*/TRUE, PS_CLASS_DEFINITION_CONTEXT); /* Set the fixup depth for non-local classes to the context scope pushed above so that classes created by this routine will be fixed up by process_deferred_class_fixups_and_instantiations below. */ non_local_class_fixup_depth = depth_scope_stack; /* By default, the instantiation scope context pushed by the call to push_template_instantiation_scope just copies the name linkage from the previous entry on the scope stack, which may not be related to that of the class. */ scope_stack_top().default_name_linkage = class_type->source_corresp.name_linkage; if (class_def_buffer == NULL) class_def_buffer = alloc_text_buffer(1024); reset_text_buffer(class_def_buffer); #if CPPCLI_ENABLING_POSSIBLE /* Get the definition from metadata. */ size = class_def_buffer->allocated_size; import_class_definition(assembly_scope_index, metadata_type_def_token, class_def_buffer->buffer, &size, &is_delegate); if (size <= class_def_buffer->allocated_size) { /* The buffer fits. Mark the size that has been written. */ class_def_buffer->size = size; } else { /* Expand the buffer */ reset_text_buffer(class_def_buffer); expand_text_buffer(class_def_buffer, size); import_class_definition(assembly_scope_index, metadata_type_def_token, class_def_buffer->buffer, &size, &is_delegate); check_assertion(size <= class_def_buffer->allocated_size); class_def_buffer->size = size; } /* if */ #if DEBUG if (db_flag_is_set("dump_metadata") || db_flag_is_set("dump_full_metadata")) { fprintf(f_debug, "Class definition for 0x%x/0x%08x: ", assembly_scope_index, metadata_type_def_token); db_dump_metadata(class_def_buffer, db_flag_is_set("dump_metadata") ? 256 : 0); } /* if */ #endif /* DEBUG */ #else /* !CPPCLI_ENABLING_POSSIBLE */ /* Add code here to construct in the text buffer the string to be used to define the class. It may also be desirable to disable macro expansion while the tokens are being scanned. This shows a simple class definition: add_string_to_text_buffer(class_def_buffer, "{int i; void f(int j=1){} };"); Note that the definition starts with what would appear after the class name in a normal class definition (i.e., the base classes or the opening brace of the class) and ends with the closing brace and semicolon. */ #endif /* CPPCLI_ENABLING_POSSIBLE */ #if CPPCLI_ENABLING_POSSIBLE if (assembly_index != 0) { /* The definition is coming from an assembly file; set the position information to reflect that. */ a_cli_metadata_file_ptr cmfp = map_assembly_index_to_cmfp(assembly_index); check_assertion(cmfp != NULL); position_for_tokens = cmfp->inserted_position; } else #endif /* CPPCLI_ENABLING_POSSIBLE */ /* Do not insert code here. */ { /* Insert the definition as though it's at the current token. */ position_for_tokens = pos_curr_token; } /* if */ check_assertion(class_def_buffer->size != 0); /* Terminate the buffer. */ add_char_to_text_buffer(class_def_buffer, '\0'); insert_string_into_token_stream(class_def_buffer->buffer, /*insert_after=*/FALSE, /*p_expand_macros=*/FALSE, /*suspend_caching=*/FALSE, position_for_tokens); #if CPPCLI_ENABLING_POSSIBLE /* Generics are processed differently than other types. They are cached for instantiation purposes, then an initial scan is done to do the semantic analysis of the generic definition. */ if (is_generic_definition) { scan_cli_generic_class_definition_from_assembly_import(); } else if (is_delegate) { /* Delegate definitions are a special kind of class definition that is not handled by the call to scan_class_definition below. */ scan_cli_delegate_definition_from_assembly_import(class_type); (void)get_token(); } else #endif /* CPPCLI_ENABLING_POSSIBLE */ /* Do not insert code here. */ { (void)scan_class_definition( class_type, (a_decl_parse_state*)NULL, depth_innermost_namespace_scope, /*is_partial=*/FALSE, /*is_local_class=*/FALSE, /*delayed_nested_class_def=*/ class_type->source_corresp.is_class_member, /*is_template_instantiation=*/FALSE, /*is_template_specialization=*/FALSE, (a_template_ptr)NULL, (a_decl_pos_block_ptr)NULL); process_deferred_class_fixups_and_instantiations( /*for_instantiation=*/TRUE); (void)get_token(); } /* if */ class_type->definition_pending = FALSE; non_local_class_fixup_depth = saved_non_local_class_fixup_depth; pop_template_instantiation_scope(); free_template_decl_info(tdip); #if BACK_END_IS_CP_GEN_BE /* Set this flag so that the C++-generating back end will not use elaborated type specifiers when referring to this type (e.g., "X" instead of "class X"). */ class_type->has_been_declared = TRUE; #endif /* BACK_END_IS_CP_GEN_BE */ #if CPPCLI_ENABLING_POSSIBLE scanning_generated_code_from_metadata = saved_scanning_generated_code_from_metadata; curr_assembly_index = saved_assembly_index; #endif /* CPPCLI_ENABLING_POSSIBLE */ #if GENERATE_SOURCE_SEQUENCE_LISTS source_sequence_entries_disallowed = saved_source_sequence_entries_disallowed; scope_stack_top().source_sequence_entries_disallowed = saved_source_sequence_entries_disallowed; #endif /* GENERATE_SOURCE_SEQUENCE_LISTS */ scanning_generated_code = saved_scanning_generated_code; done: return; } /* get_definition_of_class */ #endif /* GET_DEFINITION_OF_CLASS_NEEDED */ #if MICROSOFT_EXTENSIONS_ALLOWED char *generate_top_level_metadata_code(an_assembly_index idx) /* Generate top level declarations for types being imported and return the address of the buffer. The buffer is reused by each successive call, so the caller should copy the contents as needed. */ { sizeof_t size = 0; a_text_buffer_ptr buffer; if (metadata_import_buffer == NULL) { /* Allocate the buffer used to read the metadata. The argument passed to alloc_text_buffer is both the initial size and the allocation increment. METADATA_IMPORT_BUFFER_SIZE is quite large, so we don't want to also use that as the allocation increment so we initially allocate it with a smaller size and immediately resize it. */ metadata_import_buffer = alloc_text_buffer(METADATA_IMPORT_BUFFER_ALLOCATION_INCREMENT); expand_text_buffer(metadata_import_buffer, METADATA_IMPORT_BUFFER_SIZE); } /* if */ reset_text_buffer(metadata_import_buffer); buffer = metadata_import_buffer; size = buffer->allocated_size; import_all_types(idx, buffer->buffer, &size); if (size <= buffer->allocated_size) { /* The buffer fits. Mark the size that has been written. */ buffer->size = size; } else { /* Expand the buffer */ reset_text_buffer(buffer); expand_text_buffer(buffer, size); import_all_types(idx, buffer->buffer, &size); check_assertion(size <= buffer->allocated_size); buffer->size = size; } /* if */ /* Terminate the buffer. */ add_char_to_text_buffer(buffer, '\0'); #if DEBUG if (db_flag_is_set("dump_metadata") || db_flag_is_set("dump_full_metadata")){ fprintf(f_debug, "Import types from %x: ", (unsigned int)idx); db_dump_metadata(buffer, db_flag_is_set("dump_metadata") ? 256 : 0); } /* if */ #endif /* DEBUG */ return buffer->buffer; } /* generate_top_level_metadata_code */ #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ #if DEBUG template static void db_cached_token(const Token_type &tok) /* Display a single cached token. */ { a_token_kind kind = tok.get_kind(); fprintf(f_debug, " kind: %s", token_names[(int)kind]); if (kind == tok_identifier) { const a_symbol_locator* sym_loc = tok.get_symbol_locator(); fprintf(f_debug, " %s", sym_loc->symbol_header->identifier); } /* if */ fprintf(f_debug, "\n"); if (tok.get_starting_seq_number() == tok.get_ending_seq_number()) { fprintf(f_debug, " sequence_number: %lu\n", (unsigned long)tok.get_starting_seq_number()); } else { fprintf(f_debug, " sequence_number (start): %lu\n", (unsigned long)tok.get_starting_seq_number()); fprintf(f_debug, " sequence_number (end): %lu\n", (unsigned long)tok.get_ending_seq_number()); } /* if */ if (!tok.is_basic()) { a_const_char *s = NULL; if (tok.is_pp_token()) { s = "pp-token"; } else if (tok.is_pragma()) { s = "pragma"; } else if (tok.is_extracted_template_body()) { s = "extracted-template-body"; } else if (tok.is_ud_literal()) { s = "ud-literal"; } else if (tok.is_unresolved_ud_literal()) { s = "unresolved ud-literal"; } else if (tok.is_asm_string()) { s = "asm-string"; } else if (tok.is_ifc_reference()) { s = "ifc-reference"; } else if (tok.is_constant()) { if (tok.is_string_literal()) { s = "string-literal"; } else { s = "constant"; } /* if */ } /* if */ fprintf(f_debug, " special kind: %s\n", s); } /* if */ if (tok.is_pragma()) { for (const a_shared_pending_pragma &ppp : *tok.get_pragma_list()) { fprintf(f_debug, " Pragma: %s\n", pragma_ids[(int)ppp->descr_ptr->kind]); } /* for */ } else if (tok.is_ud_literal()) { const a_ud_literal_descr *ud_lit_descr = tok.get_ud_literal_descr(); if (ud_lit_descr->op_sym != NULL) { db_symbol(ud_lit_descr->op_sym, " Literal operator: ", 4); } else if (ud_lit_descr->suffix != NULL) { fprintf(f_debug, " Literal operator suffix: %s\n", ud_lit_descr->suffix); } /* if */ } /* if */ } /* db_cached_token */ a_shared_token get_cache_token(a_token_cache_ptr cache, a_token_sequence_number seq_number) /* Return a copy of the token identified by the given sequence number in the given cache, or a default-initialized cached token if not found. This function is useful for quickly grabbing a particular cached token for inspection in a debugger; not optimized for general usage. */ { a_shared_token result = {}; for (const a_shared_token &tok : *cache) { if (tok->get_starting_seq_number() == seq_number) { result = tok; break; } /* if */ } /* for */ return result; } /* get_cache_token */ void db_rescan_stack(void) /* Display the tokens on the cached_token_rescan_stack. */ { size_t num_tokens = cached_token_rescan_stack->length(); for (size_t i = num_tokens; i != 0; --i) { if (i != num_tokens) { fprintf(f_debug, "\n"); } /* if */ a_shared_token tok = (*cached_token_rescan_stack)[i - 1]; print(a_string("Token ", num_tokens - i, ":"), f_debug); db_cached_token(*tok); } /* for */ } /* db_rescan_stack */ void db_token_cache(const a_token_cache *cache, a_const_char *cache_name) /* Display the contents of a token cache. */ { fprintf(f_debug, "%s token cache at %p\n", cache_name, (void *)cache); if (cache != NULL) { print(a_string("token_count:", cache->length()), f_debug); unsigned long count = 0; for (const a_shared_token &tok : *cache) { if (count != 0) { fprintf(f_debug, "\n"); } /* if */ fprintf(f_debug, "Token %lu:\n", count++); db_cached_token(*tok); } /* for */ } /* if */ } /* db_token_cache */ static a_boolean is_tok_equiv(const a_shared_token &tok1, const a_shared_token &tok2) /* Return TRUE if the cached token represented by tok1 is approximately the same as the cached token represented by tok2; otherwise, return FALSE. Note this function has an incomplete definition of equivalency and intentionally excludes some factors such as the token's sequence number. */ { a_boolean result = TRUE; if (tok1->get_kind() != tok2->get_kind()) { result = FALSE; } else if (tok1->get_extra_kind() != tok2->get_extra_kind()) { result = FALSE; } else { switch (tok1->get_extra_kind()) { case teik_none: break; case teik_identifier: { const a_symbol_locator *loc1 = tok1->get_symbol_locator(); const a_symbol_locator *loc2 = tok2->get_symbol_locator(); if (loc1->symbol_header != loc2->symbol_header) { result = FALSE; } /* if */ } break; case teik_constant: { const a_constant *const1 = tok1->get_constant(); const a_constant *const2 = tok2->get_constant(); if (!eq_constants(const_cast(const1), const_cast(const2))) { result = FALSE; } /* if */ } break; case teik_pragma: case teik_pp_token: case teik_extracted_body: case teik_removed_expr: case teik_asm_string: case teik_insert_string: case teik_ud_lit: case teik_unresolved_ud_lit: case teik_ifc_index: /* Currently no comparison is implement. */ break; default_is_unexpected(); } /* switch */ } /* if */ return result; } /* is_tok_equiv */ static a_boolean are_token_caches_equiv(const a_token_cache &cache_a, const a_token_cache &cache_b) /* Given two token caches, return TRUE if they're equivalent under the rules of is_tok_equiv; otherwise, return FALSE. */ { a_boolean result = TRUE; if (cache_a.length() != cache_b.length()) { result = FALSE; } else { for (size_t i = 0; i < cache_a.length(); ++i) { if (is_tok_equiv(cache_a[i], cache_b[i])) { continue; } /* if */ result = FALSE; break; } /* for */ } /* if */ return result; } /* are_token_caches_equiv */ static a_string make_color_code_string(a_const_char *color_code) /* Given an ANSI escape code color (e.g., "31" is red), form and return an ANSI color code string. */ { a_const_char color_escape = '\033'; return a_string(a_string_view(&color_escape, 1), "[", color_code, "m"); } /* make_color_code_string */ namespace { /* A class for facilitating printing of token caches with optional comments. */ struct a_commentary_token_printer { a_commentary_token_printer() : printed_anything(FALSE), prev_token(), prev_line_num(0), curr_column(0), printed_comment(FALSE) {} inline ~a_commentary_token_printer(); a_boolean last_printed_a_token() const { return this->printed_anything && !this->printed_comment; } inline void print_token(const a_shared_token &tok, a_const_char *color_code = NULL); inline void print_comment(const a_string &comment, a_const_char *color_code = NULL); private: inline void print_internal(const a_string &text, a_const_char *color_code = NULL); a_boolean printed_anything; /* TRUE if anything has been printed. */ Opt prev_token; /* The most recently printed token. */ a_line_number prev_line_num; /* The line number of the most recently printed token. */ a_column_number curr_column; /* The current column position following from all text processed by print_internal. */ a_boolean printed_comment; /* TRUE if the last text printed was a comment. */ }; /* a_commentary_token_printer */ a_commentary_token_printer::~a_commentary_token_printer() /* Destroy the commentary token printer, printing a newline if needed to finish off the token printing (if the last thing printed was a token cache). */ { if (this->last_printed_a_token()) { print(a_string(""), f_debug); } /* if */ } /* a_commentary_token_printer::~a_commentary_token_printer */ void a_commentary_token_printer::print_token(const a_shared_token &tok, /* Defaulted: */ a_const_char *color_code) /* Given a token, print the token with style hinting based on the source position information. If color_code is not NULL, the given color code will be used for the token. */ { a_line_number line_num; a_boolean at_end_of_source; const a_source_position *pos = tok->get_source_position(); (void)source_file_for_seq(pos->seq, &line_num, &at_end_of_source, /*physical_line=*/TRUE); /* If the line has progressed, print a new line. Note that only cases where the new line is larger are considered to "massage" token caches that have insufficient source position information (e.g., those coming from IFC files). */ if (line_num > this->prev_line_num) { /* If a comment has been printed, the output has already been moved to a new line. Thus, an additional line print is unnecessary and spurious. */ if (!this->printed_comment) { print_internal(a_string("\n")); } /* if */ this->prev_line_num = line_num; } /* if */ if (pos->column != 0) { /* If there's a jump in the token indent in the original source information, increase the indent appropriately. */ a_string padding_str(""); a_column_number start = this->curr_column; a_column_number end = pos->column - 1; for (a_column_number i = start; i < end; ++i) { padding_str.append(" "); } /* for */ print_internal(padding_str); } /* if */ a_string token_as_string = a_string(token_to_string(tok), " "); print_internal(token_as_string, color_code); /* Update the state to reflect the token being printed. */ this->prev_token = tok; this->printed_comment = FALSE; this->printed_anything = TRUE; } /* a_commentary_token_printer::print_token */ void a_commentary_token_printer::print_comment(const a_string &comment, /* Defaulted: */ a_const_char *color_code) /* Given a comment, print the comment. If color_code is not NULL, the given color code will be used for the token. */ { if (this->last_printed_a_token()) { /* Break out of the token printing. */ print(a_string("\n"), f_debug, /*end=*/""); } /* if */ /* Print the comment followed by a newline. */ print_internal(a_string(comment, "\n"), color_code); /* Update the state to reflect the comment being printed. */ this->curr_column = 0; this->printed_comment = TRUE; this->printed_anything = TRUE; } /* a_commentary_token_printer::print_comment */ void a_commentary_token_printer::print_internal(const a_string &text, /* Defaulted: */ a_const_char *color_code) /* Given a string, print the text and update the internal column position tracking. If color_code is not NULL, the given color code will be used for the token. */ { for (size_t i = 0; i < text.length(); ++i) { if (text[i] == '\n') { this->curr_column = 0; } else { ++this->curr_column; } /* if */ } /* for */ if (color_code != NULL) { print(make_color_code_string(color_code), f_debug, /*end=*/""); } /* if */ print(text, f_debug, /*end=*/""); if (color_code != NULL) { print(make_color_code_string("0"), f_debug, /*end=*/""); } /* if */ } /* a_commentary_token_printer::print_internal */ } /* namespace */ static void print_token_range_summary(a_const_token_cache_iterator begin, a_const_token_cache_iterator end) /* Print a summary including the approximate source position of the given token range (starting at the given token and ending inclusively at last_tsn) and the token sequence number range itself. */ { /* If any of the tokens in the cache has an associated position, output a description of that position first. */ for (a_const_token_cache_iterator it = begin; it != end; ++it) { const a_shared_token &tok = *it; const a_source_position *src_pos = tok->get_source_position(); if (src_pos->seq != 0) { if (it != begin) { fprintf(f_debug, "(approx.) "); } /* if */ db_source_position(src_pos); fprintf(f_debug, " (seq = %ld) ", (long)src_pos->seq); break; } /* if */ } /* for */ /* Then output the token sequence range. */ a_token_sequence_number min_tsn = NO_TOKEN_SEQUENCE_NUMBER; a_token_sequence_number max_tsn = NO_TOKEN_SEQUENCE_NUMBER; if (begin != end) { min_tsn = max_tsn = (*begin)->get_starting_seq_number(); for (a_const_token_cache_iterator it = begin + 1; it != end; ++it) { a_token_sequence_number tsn = (*it)->get_starting_seq_number(); if (tsn < min_tsn) { min_tsn = tsn; } else if (tsn > max_tsn) { max_tsn = tsn; } /* if */ } /* for */ } /* if */ fprintf(f_debug, "[tsn: %ld - %ld]\n", (long)min_tsn, (long)max_tsn); } /* print_token_range_summary */ void db_diff_token_caches(const a_token_cache *cache_a, const a_token_cache *cache_b) /* Given two token caches, compare the two caches for mismatched tokens and write a diff-like output to f_debug. */ { Opt result; print_token_range_summary(cache_a->begin(), cache_a->end()); print_token_range_summary(cache_b->begin(), cache_b->end()); a_commentary_token_printer printer; if (are_token_caches_equiv(*cache_a, *cache_b)) { /* The token caches are equivalent: just print the tokens. */ printer.print_comment(a_string("/* TOKENS EQUIVALENT */")); for (const a_shared_token &tok : *cache_a) { printer.print_token(tok); } /* for */ } else { /* The token caches have one or more detected differences: print the differences. */ printer.print_comment(a_string("/* TOKENS DIFFER */")); Seq_comparator comparator(&(*(cache_a->begin())), cache_a->length(), &(*(cache_b->begin())), cache_b->length(), is_tok_equiv); auto diff_consumer = [&printer](const a_shared_token *from_tok, const a_shared_token *to_tok) { if (from_tok == NULL || to_tok == NULL) { a_const_char *color_code = NULL; /* Determine the color to use. */ if (db_color_flag_is_set()) { if (from_tok == NULL) { /* to_tok didn't exist previously: use green for addition. */ color_code = "32"; } else if (to_tok == NULL) { /* from_tok no longer exists: use red for removal. */ color_code = "31"; } /* if */ } /* if */ /* Print the introducing line if relevant. */ if (printer.last_printed_a_token()) { printer.print_comment(a_string("/* DIFF DETECTED */"), color_code); } /* if */ /* Print the actual change. */ if (from_tok == NULL) { /* to_tok didn't exist previously: print the addition. */ a_string addition("+ ", token_to_string(*to_tok)); printer.print_comment(addition, color_code); } else { /* from_tok was deleted: print the removal. */ a_string removal("- ", token_to_string(*from_tok)); printer.print_comment(removal, color_code); } /* if */ } else { printer.print_token(*from_tok); } /* if */ }; comparator.diff(diff_consumer); } /* if */ } /* db_diff_token_caches */ void db_source_position(const a_source_position *pos) /* Output the given source position to f_debug, both using a raw sequence number and using the associated file name and line number. */ { if (pos->seq > 0) { a_const_char *file_name, *full_name; a_line_number line_number; a_boolean at_end_of_source; (void)conv_seq_to_file_and_line(pos->seq, &file_name, &full_name, &line_number, &at_end_of_source); if (seq_is_in_include_file(pos->seq)) { (void)fprintf(f_debug, "file %s ", file_name); } /* if */ if (at_end_of_source) { (void)fprintf(f_debug, "end of source"); } else { (void)fprintf(f_debug, "line %lu, column %lu", (unsigned long)line_number, (unsigned long)pos->column); } /* if */ } else { (void)fprintf(f_debug, "null source position (col. = %lu)", (unsigned long)pos->column); } /* if */ } /* db_source_position */ static void add_cached_tokens_to_string_for_debug( a_const_token_cache_iterator begin, a_const_token_cache_iterator end) /* Go through a list of cached tokens and add the tokens to the string that is being constructed that represents the tokens. */ { unsigned color_idx = 0; for (a_const_token_cache_iterator it = begin; it != end; ++it) { const a_shared_token &tok = *it; /* Add the color rotation if relevant. */ if (db_color_flag_is_set()) { put_ch_to_temp_text_buffer('\033'); put_ch_to_temp_text_buffer('['); /* Update the current color. */ a_const_char *color_code; if (db_flag_is_set("brightcolor")) { color_idx = (color_idx + 1) % tok_dbg_num_bright_colors; color_code = tok_dbg_bright_colors[color_idx]; } else if (db_flag_is_set("darkcolor")) { color_idx = (color_idx + 1) % tok_dbg_num_dark_colors; color_code = tok_dbg_dark_colors[color_idx]; } else { unexpected_condition(); } /* if */ put_str_to_temp_text_buffer(color_code); put_ch_to_temp_text_buffer('m'); } /* if */ add_cached_token_to_string(tok, /*print_pseudo_tokens=*/TRUE); } /* for */ if (db_color_flag_is_set()) { put_ch_to_temp_text_buffer('\033'); put_ch_to_temp_text_buffer('['); put_ch_to_temp_text_buffer(0); put_ch_to_temp_text_buffer('m'); } /* if */ } /* add_cached_tokens_to_string_for_debug */ static void db_token_range_internal(a_const_token_cache_iterator begin, a_const_token_cache_iterator end) /* This function is an implementation detail of db_token_range. It prints the range of tokens from [begin, end) without any contextual annotation or special processing. */ { a_temp_text_buffer_swap tmp_buffer_swap; /* Print a summary of the token range. */ print_token_range_summary(begin, end); /* Print the tokens themselves. */ if (!db_flag_is_set("old_db_tokens")) { a_commentary_token_printer printer; unsigned color_idx = 0; for (a_const_token_cache_iterator it = begin; it != end; ++it) { const a_shared_token &tok = *it; /* Add the color rotation if relevant. */ a_const_char *color_code = NULL; if (db_color_flag_is_set()) { /* Update the current color. */ if (db_flag_is_set("brightcolor")) { color_idx = (color_idx + 1) % tok_dbg_num_bright_colors; color_code = tok_dbg_bright_colors[color_idx]; } else if (db_flag_is_set("darkcolor")) { color_idx = (color_idx + 1) % tok_dbg_num_dark_colors; color_code = tok_dbg_dark_colors[color_idx]; } else { unexpected_condition(); } /* if */ } /* if */ printer.print_token(tok, color_code); } /* for */ } else { /* Add the tokens to the temp_text_buffer. */ add_cached_tokens_to_string_for_debug(begin, end); sizeof_t k = 0; sizeof_t indent = 0; /* Skip leading spaces. */ while (temp_text_buffer[k] == ' ') ++k; /* A local lambda expression used in a subsequent loop whenever we want to establish indentation after switching to a new line. The lambda also moves the given index to that of the next non-space character in temp_text_buffer (to avoid extraneous indentation). */ auto do_indent = [&indent](sizeof_t *p_k) { for (sizeof_t i = 0; i= 2) indent -= 2; do_indent(&k); (void)fputc('}', f_debug); if (k+1get_starting_seq_number(); } /* if */ for (; it != end; ++it) { if ((*it)->get_starting_seq_number() < highest_prev_range) { db_token_range_internal(last_print, it); last_print = it; } /* if */ } /* for */ db_token_range_internal(last_print, end); } /* db_token_range */ void db_tokens(const a_token_cache *cache) /* Display the contents of the given list of cached tokens as text, with some simple-minded formatting. */ { db_token_range(cache->begin(), cache->end()); } /* db_tokens */ void db_tokens(const a_shared_token_cache &cache) /* A wrapper around db_tokens for shared token caches. */ { db_tokens(cache.ptr()); } /* db_tokens */ void db_tokens(const a_reusable_token_cache &cache) /* A wrapper around db_tokens for reusable token caches. */ { db_tokens(cache.ptr()); } /* db_tokens */ void db_tokens(a_token_cache_iterator it) /* A wrapper around db_token_range for printing from a token cache iterator to the end of the associated token cache. */ { db_token_range(it, it.cache->end()); } /* db_tokens */ void db_tokens(a_const_token_cache_iterator it) /* A wrapper around db_token_range for printing from a const token cache iterator to the end of the associated token cache. */ { db_token_range(it, it.cache->end()); } /* db_tokens */ unsigned long show_lexical_space_used(void) /* Display and return the amount of space used for various lexical tables. */ { unsigned long num, size, total, grand_total = 0; db_space_used_header("Lexical table use:"); db_space_used_lost("orig line modif", avail_orig_line_modifs, num_orig_line_modifs_allocated, an_orig_line_modif); db_space_used_lost("source line modif", avail_source_line_modifs, num_source_line_modifs_allocated, a_source_line_modif); db_space_used_lost("concatenation record", avail_concatenation_records, num_concatenation_records_allocated, a_concatenation_record); db_space_used("shared token (states)", num_shared_token_states_allocated, a_shared_token_ctrl_block); db_space_used_lost("cached constant", avail_cached_constants, num_cached_constants_allocated, a_constant); db_space_used_lost("cache stack entry", avail_reusable_cache_entries, num_reusable_cache_entries_allocated, a_reusable_cache_entry); db_space_used_lost("stop token stack entry", avail_stop_token_stack_entries, num_stop_token_stack_entries_allocated, a_stop_token_stack_entry); db_space_used_lost("lexical state stack entry", avail_lexical_state_stack_entries, num_lexical_state_stack_entries_allocated, a_lexical_state_stack_entry); db_space_used("pragma kind descriptions", num_pragma_descriptions_allocated, a_pragma_kind_description); db_space_used("file suffixes", num_file_suffixes_allocated, a_file_suffix); db_space_used("include file histories", num_include_file_histories_allocated, an_include_file_history); db_space_used_general("preinclude files", num_preinclude_files_allocated, a_preinclude_file); db_space_used_general("include search results", num_include_search_results_allocated, an_include_search_result); db_space_used_other("cached pp token strings", cached_pp_token_string_space, ""); grand_total += cached_pp_token_string_space; total = (unsigned long)(after_end_of_curr_source_line - curr_source_line); db_space_used_general_buffer("curr_source_line", total); if (size_pp_dir_string_buffer != 0) { db_space_used_general_buffer ("pragma string", ((unsigned long)size_pp_dir_string_buffer)); } /* if */ if (after_end_of_raw_listing_buffer != NULL) { total = (unsigned long)(after_end_of_raw_listing_buffer - raw_listing_buffer); db_space_used_general_buffer("raw_listing_buffer", total); } /* if */ if (num_lookups_in_source_line_modif_hash_table != 0) { db_space_used_float_other( "Avg slm hash comp/search", (double)num_compares_in_source_line_modif_hash_table / (double)num_lookups_in_source_line_modif_hash_table, ""); } /* if */ #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED db_space_used("id representations",num_id_representations_allocated, an_id_representation); db_space_used("spelling storage buffers", num_spelling_storage_buffers_allocated, a_spelling_storage_buffer); db_space_used_other("spelling buffer storage", spelling_storage_buffer_space, ""); grand_total += spelling_storage_buffer_space; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ db_space_used_total(); return (grand_total); } /* show_lexical_space_used */ #endif /* DEBUG */ static void create_sun_include_file_suffixes(void) /* The Sun compiler does special processing of certain files included with the <..> syntax. Create a special include file suffix list to emulate this behavior. Also, add the special suffix to the normal include file suffix list. The sun_include_file_suffix_list is used for files with a .h suffix; the normal include_file_suffix_list is used for unsuffixed files. */ { a_const_char *suffix; sun_include_file_suffix_list = conv_string_to_file_suffix_list("h.SUNWCCh:h:"); suffix = "SUNWCCh"; add_to_file_suffix_list(&include_file_suffix_list, suffix, strlen(suffix)); } /* create_sun_include_file_suffixes */ static void init_include_file_suffixes(void) /* Create the include file suffix list used for header files with no suffix. */ { include_file_suffix_list = NULL; if (include_file_suffixes != NULL && *include_file_suffixes != '\0') { include_file_suffix_list = conv_string_to_file_suffix_list(include_file_suffixes); } else { /* The list is empty. The empty suffix should be included in such cases. */ add_to_file_suffix_list(&include_file_suffix_list, "", 0); } /* if */ sun_include_file_suffix_list = NULL; if (sun_mode) { /* Add include file suffix entries needed for Sun compiler emulation. */ create_sun_include_file_suffixes(); } /* if */ } /* init_include_file_suffixes */ void init_name_linkage_constants(void) /* Create an array of string constants from the string literals that describe which name linkages are recognized. This process ensures that any needed host-target conversions are performed. */ { a_name_linkage_kind kind; unsigned long num_chars; an_error_code err_code; a_const_char *err_pos; char *p; name_linkage_constants = (a_constant_ptr)alloc_fe((int)nlk_last*sizeof(a_constant)); for (kind = (a_name_linkage_kind)nlk_cplusplus_external; (int)kind < (int)nlk_last; kind = (a_name_linkage_kind)(kind + 1)) { a_const_char *name_linkage = name_linkage_kind_names[kind]; sizeof_t orig_len = strlen(name_linkage); /* First initialize the current source line to scan the string. */ ensure_min_curr_source_line_length(orig_len+2+2*LE_ESCAPE_LEN); p = (char *)curr_source_line; p[0] = '"'; strcpy(p+1, name_linkage); p[orig_len+1] = '"'; p[orig_len+2] = LE_ESCAPE; p[orig_len+3] = LE_NEWLINE; /* If the following LE_ESCAPE is changed to be at a different offset, the setting of curr_char_loc below must be updated too. */ p[orig_len+4] = LE_ESCAPE; p[orig_len+5] = LE_END_OF_LINE; start_of_curr_token = curr_char_loc = curr_source_line; logical_char_info_entries_used = 0; /* Tokenize the string. */ curr_char_loc++; num_chars = 0; if (accum_quoted_string(&num_chars, /*is_header_name=*/FALSE, SCLK_ORDINARY_STRING_LITERAL, '"', NULL, -1, curr_char_loc - 1)) { unexpected_condition(); } /* if */ /* Convert it to internal form. */ conv_string_literal(start_of_curr_token + 1, end_of_curr_token, SCLK_ORDINARY_STRING_LITERAL, num_chars, &err_code, &err_pos); check_assertion(err_code == ec_no_error); /* Copy the result for later use. */ copy_constant(&const_for_curr_token, name_linkage_constants+(int)kind); /* Advance curr_char_loc to the end-of-line escape. */ curr_char_loc = &curr_source_line[orig_len+4]; } /* for */ } /* init_name_linkage_constants */ static inline void check_module_name_part(a_string_view str, a_source_position_ptr pos) /* Given a component of a module name (for either the primary name or partition name) at the given position, scan it for disallowed names and issue a diagnostic, if appropriate. */ { /* Both "import" and "module" have 6 characters. Any identifier with fewer than 6 characters is not a match. */ if (str.length() >= 6) { if (str == "import") { pos_error(ec_import_name_not_allowed, pos); } else if (str == "module") { pos_error(ec_module_name_not_allowed, pos); } /* if */ } /* if */ } /* check_module_name_part */ static a_string scan_module_qualified_name() /* Scan a qualified module name portion (either the primary name or the partition name) and return a string containing the name. curr_token refers to the first qualifier in the module name portion being scanned (a tok_identifier if the name is non-empty). */ { a_string result; while (curr_token == tok_identifier) { a_symbol_header_ptr sym_hdr = locator_for_curr_id.symbol_header; a_string_view sym_text(sym_hdr->identifier, sym_hdr->identifier_length); check_module_name_part(sym_text, &pos_curr_token); if (result.is_empty()) { result.append(sym_text); } else { result.append(".", sym_text); } /* if */ (void)get_token(); /* Advance past the identifier. */ if (curr_token == tok_period) { /* This was a qualifier - we expect a tok_identifier to come next. */ (void)get_token(); if (!required_token_no_advance(tok_identifier, ec_exp_identifier)) { set_to_error_locator(locator_for_curr_id); } /* if */ } /* if */ } /* while */ return result; } /* scan_module_qualified_name */ void scan_module_name(a_string *primary_name, a_string *partition_name) /* Scan a module name, including its partition (if present) into the provided symbol pointers. A module or partition name can have any number of qualifiers, e.g., "A.B.C:D.E.F". */ { add_stop_token(tok_semicolon); add_stop_token(tok_colon); if (curr_token != tok_colon) { if (!required_token_no_advance(tok_identifier, ec_exp_identifier)) { set_to_error_locator(locator_for_curr_id); } /* if */ } /* if */ *primary_name = scan_module_qualified_name(); remove_stop_token(tok_colon); if (curr_token == tok_colon) { (void)get_token(); if (!required_token_no_advance(tok_identifier, ec_exp_identifier)) { set_to_error_locator(locator_for_curr_id); } /* if */ *partition_name = scan_module_qualified_name(); } /* if */ remove_stop_token(tok_semicolon); } /* scan_module_name */ void lexical_one_time_init(void) /* Do one-time initialization of variables related to lexical processing. (Variables that need to be reinitialized with each new translation unit are handled in lexical_init.) */ { int c; /* Has to be "int" so "for" loop will work. */ /* Do the initial allocation for curr_source_line. (Since the space is allocated in general storage, it does not need to be reallocated for each source file; for the same reason, after_end_of_curr_source_line should not be reset.) The space will be reallocated (larger) if necessary, but the size here should be big enough for the expected cases. */ /* Allocate two more bytes than required: one at the end so that a pointer past the end will not have the same address as a pointer to the next object in memory, and one before the start to make it safe to check the previous byte without an additional test. */ curr_source_line = alloc_resizable_buffer( (sizeof_t)(CURR_SOURCE_LINE_INITIAL_ALLOCATION+2)); curr_source_line += 1; ((char*)curr_source_line)[-1] = ' '; after_end_of_curr_source_line = curr_source_line + CURR_SOURCE_LINE_INITIAL_ALLOCATION; #if MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED logical_char_info = (a_const_char**)alloc_resizable_buffer( (sizeof_t)(CURR_SOURCE_LINE_INITIAL_ALLOCATION * sizeof(a_const_char*))); #endif /* MULTIBYTE_CHARS_IN_SOURCE_SUPPORTED */ logical_char_info_entries_used = 0; set_cached_logical_char_info_entries_used(0); raw_listing_buffer = NULL; after_end_of_raw_listing_buffer = NULL; if (f_raw_listing != NULL) { /* Similar allocation for raw_listing_buffer. Similar reasoning. */ raw_listing_buffer = alloc_resizable_buffer( (sizeof_t)RAW_LISTING_BUFFER_INITIAL_ALLOCATION); after_end_of_raw_listing_buffer = raw_listing_buffer + RAW_LISTING_BUFFER_INITIAL_ALLOCATION; clear_raw_listing_buffer(); } /* if */ input_stack = NULL; size_input_stack = 0; in_pcc_mode_half_comment = FALSE; #if CHECKING /* Make sure there are not too many expr kinds to fit into an_expr_node_kind once the bit field restriction has been applied. */ if ((0x1 << NUM_BITS_FOR_EXPR_NODE_KIND) <= enk_last) { internal_error("lexical_one_time_init: " "NUM_BITS_FOR_EXPR_NODE_KIND is too small"); } /* if */ #endif /* CHECKING */ /* Initialize is_id_char to the characters that can appear in an identifier after the first character (i.e., a-z, A-Z, 0-9, and "_"). See C++11 Standard, 2.11. Also used in scanning pp-numbers; the same set applies. See C++11 Standard, 2.10. The identifier characters also cover most of the characters that can appear in the d-char-sequence of a raw string literal; see C++11 Standard 2.14.5. */ for (c = CHAR_MIN; c <= CHAR_MAX; c++) { is_id_char[c-CHAR_MIN] = (isalpha((unsigned char)c) || isdigit((unsigned char)c)); is_raw_string_delimiter_char[c-CHAR_MIN] = is_id_char[c-CHAR_MIN]; } /* for */ is_id_char['_' - CHAR_MIN] = TRUE; if (allow_dollar_in_id_chars) { is_id_char['$' - CHAR_MIN] = TRUE; } /* if */ /* Some character sets use some C special characters as letters, e.g., the position that is ASCII "]" is "U umlaut" in German. Take those characters back. Note that a setlocale call has been done already. */ /* "@" and "`" are letters in some European character sets, but they're not used in C so there's no need to take them back. */ is_id_char['[' - CHAR_MIN] = FALSE; is_id_char['\\'- CHAR_MIN] = FALSE; is_id_char[']' - CHAR_MIN] = FALSE; is_id_char['^' - CHAR_MIN] = FALSE; is_id_char['{' - CHAR_MIN] = FALSE; is_id_char['|' - CHAR_MIN] = FALSE; is_id_char['}' - CHAR_MIN] = FALSE; is_id_char['~' - CHAR_MIN] = FALSE; /* Mark the remaining raw string delimiter characters. */ is_raw_string_delimiter_char['_' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['{' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['}' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['[' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char[']' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['#' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['<' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['>' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['%' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char[':' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char[';' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['.' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['?' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['*' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['+' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['-' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['/' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['^' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['&' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['|' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['~' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['!' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['=' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char[',' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['"' - CHAR_MIN] = TRUE; is_raw_string_delimiter_char['\'' - CHAR_MIN] = TRUE; #if UNICODE_SOURCE_SUPPORTED /* Also build a version used to check identifier characters once UTF-8 multibyte characters have been turned into a single code point, and change is_id_char to return TRUE only for non-multibyte characters. */ for (c = CHAR_MIN; c <= CHAR_MAX; c++) { unsigned char uc = (unsigned char)c; if (uc < 0x80) { /* Copy the values set above for characters in the range 0-0x7f. */ is_id_char_no_mbc[uc] = is_id_char[c-CHAR_MIN]; } else { /* The C++ Standard (ISO/IEC 14882:2011, Annex E.1) specifies the Unicode code points in the range 0x80-0xff that are considered identifier characters. */ /* coverity[dead_error_line] */ if (uc == 0xa8 || uc == 0xaa || uc == 0xad || uc == 0xaf || (uc >= 0xb2 && uc <= 0xb5) || (uc >= 0xb7 && uc <= 0xba) || (uc >= 0xbc && uc <= 0xbe) || (uc >= 0xc0 && uc <= 0xd6) || (uc >= 0xd8 && uc <= 0xf6) || uc >= 0xf8) { is_id_char_no_mbc[uc] = TRUE; } else { is_id_char_no_mbc[uc] = FALSE; } /* if */ if (multibyte_chars_in_source_enabled) { /* If multibyte characters are supported, assume any character above 0x7f is part of a multibyte character and thus not an identifier character. Otherwise, do not change the table, as it accurately reflects the interpretation of the single-byte characters under the current locale. */ is_id_char[c-CHAR_MIN] = FALSE; } /* if */ } /* if */ } /* for */ #endif /* UNICODE_SOURCE_SUPPORTED */ for (c = CHAR_MIN; c <= CHAR_MAX; c++) { switch (c) { case '[': case ']': case '(': case ')': case '{': case '}': case ',': case '~': case ':': case ';': case '?': case '-': case '+': case '*': case '/': case '&': case '%': case '<': case '>': case '=': case '|': case '^': case '$': case '"': case ' ': case '#': case LE_ESCAPE: char_ends_id[c-CHAR_MIN] = TRUE; char_ends_number[c-CHAR_MIN] = TRUE; break; case '.': case '\'': char_ends_id[c-CHAR_MIN] = TRUE; char_ends_number[c-CHAR_MIN] = FALSE; break; default: char_ends_id[c-CHAR_MIN] = FALSE; char_ends_number[c-CHAR_MIN] = FALSE; break; } /* switch */ } /* for */ /* Also initialize pp_lexical_category, used to determine whether or not extra token-separating blanks are required between tokens resulting from macro expansion. See gen_pp_output_for_curr_line. */ for (c = CHAR_MIN; c <= CHAR_MAX; c++) { if (is_id_char[c-CHAR_MIN] || c == '.') { pp_lexical_category[c-CHAR_MIN] = PLC_ID_OR_NUMBER; } else { switch (c) { /* White-space characters are always singletons: */ case '\f': case VERTICAL_TAB_CHARACTER: case '\t': case ' ': case '\n': /* Operators and punctuators that don't appear as part of other tokens are also singletons (see 3.1.5, 3.1.6). */ case '[': case ']': case '{': case '}': case '(': case ')': case ',': case '~': case ';': case '?': pp_lexical_category[c-CHAR_MIN] = PLC_SINGLETON; break; case ':': /* In C++, ":" can be part of "::". */ pp_lexical_category[c-CHAR_MIN] = (C_dialect == C_dialect_cplusplus) ? PLC_OTHER : PLC_SINGLETON; break; default: pp_lexical_category[c-CHAR_MIN] = PLC_OTHER; } /* switch */ } /* if */ } /* for */ initialize_opname_names(); #if DEBUG num_file_suffixes_allocated = 0; #endif /* DEBUG */ #if INSTANTIATION_BY_IMPLICIT_INCLUSION /* Create the instantiation file suffix list. */ implicit_instantiation_file_suffix_list = conv_string_to_file_suffix_list(DEFAULT_INSTANTIATION_FILE_SUFFIX_LIST); #endif /* INSTANTIATION_BY_IMPLICIT_INCLUSION */ init_include_file_suffixes(); /* Save variables from lexical.h and lexical.c that are needed for precompiled headers */ if (precompiled_header_processing_required) { STATIC_THREAD a_pch_saved_variable saved_vars[] = { pch_saved_var_array_elem(curr_seq_number), pch_saved_var_array_elem(seq_number_last_read), pch_saved_var_array_elem(avail_orig_line_modifs), pch_saved_var_array_elem(avail_source_line_modifs), pch_saved_var_array_elem(avail_concatenation_records), pch_saved_var_array_elem(sequence_id_for_source_line_modifs), pch_saved_var_array_elem(last_token_sequence_number_used), pch_saved_var_array_elem(avail_cached_constants), pch_saved_var_array_elem(avail_reusable_cache_entries), pch_saved_var_array_elem(avail_stop_token_stack_entries), pch_saved_var_array_elem(avail_lexical_state_stack_entries), pch_saved_var_array_elem(avail_token_ctrl_blocks), pch_saved_var_array_elem(avail_scanning_token_caches), pch_saved_var_array_elem(avail_tiny_scanning_token_caches), pch_saved_var_array_elem(include_file_history_hash_table), pch_saved_var_array_elem(name_linkage_constants), pch_saved_var_array_elem(curr_stop_token_stack_entry), pch_saved_var_array_elem(curr_lexical_state_stack_entry), pch_saved_var_array_elem(cached_token_rescan_stack), #if MICROSOFT_EXTENSIONS_ALLOWED pch_saved_var_array_elem(whitespace_keywords), #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED pch_saved_var_array_elem(id_representation_map), pch_saved_var_array_elem(spelling_storage_buffer_head), pch_saved_var_array_elem(spelling_storage_buffer_tail), #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ #if DEBUG pch_saved_var_array_elem(num_shared_token_states_allocated), pch_saved_var_array_elem(num_orig_line_modifs_allocated), pch_saved_var_array_elem(num_source_line_modifs_allocated), pch_saved_var_array_elem(num_concatenation_records_allocated), pch_saved_var_array_elem(num_cached_constants_allocated), pch_saved_var_array_elem(num_reusable_cache_entries_allocated), pch_saved_var_array_elem(num_pragmas_allocated), pch_saved_var_array_elem(num_pragma_descriptions_allocated), pch_saved_var_array_elem(num_stop_token_stack_entries_allocated), pch_saved_var_array_elem(num_lexical_state_stack_entries_allocated), pch_saved_var_array_elem(num_include_file_histories_allocated), pch_saved_var_array_elem(num_preinclude_files_allocated), pch_saved_var_array_elem(cached_pp_token_string_space), #endif /* DEBUG */ pch_saved_var_array_terminating_elem() }; register_pch_saved_variables(saved_vars); } /* if */ register_trans_unit_variable(next_token_is_top_level_decl_start); register_trans_unit_variable(curr_stop_token_stack_entry); register_trans_unit_variable(curr_lexical_state_stack_entry); register_trans_unit_variable(curr_token); register_trans_unit_variable(curr_token_pragmas); register_trans_unit_variable(const_for_curr_token); register_trans_unit_variable(pos_curr_token); #if EXTRA_SOURCE_POSITIONS_IN_IL register_trans_unit_variable(end_pos_curr_token); #endif /* EXTRA_SOURCE_POSITIONS_IN_IL */ register_trans_unit_variable(start_of_curr_token); register_trans_unit_variable(end_of_curr_token); register_trans_unit_variable(len_of_curr_token); register_trans_unit_variable(cached_token_rescan_stack); register_trans_unit_variable(reusable_cache_stack); register_trans_unit_variable(any_initial_get_token_tests_needed); register_trans_unit_variable(treat_newline_as_token); register_trans_unit_variable(curr_token_asm_string); register_trans_unit_variable(curr_token_sequence_number); include_search_hash_table = alloc_hash_table(NO_MEMORY_REGION_NUMBER, (a_hash_table_size)1024, fn_for_function(hash_include_search_result), fn_for_function(compare_include_search_result)); #if DEBUG num_include_search_results_allocated = 0; #endif /* DEBUG */ } /* lexical_one_time_init */ void lexical_pch_reset(void) /* Initialize any values that must be reset after a PCH files has been loaded. */ { #if UNIQUE_FILE_IDENTIFIER_AVAILABLE unique_file_id_hash_table = NULL; #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ } /* lexical_pch_reset */ void lexical_reset(void) /* Initialize variables that are used to record the state of the lexical routines. These variables are reset after the initial scan that is done to determine whether a precompiled header may be used. */ { /* Variables in lexical.h: */ depth_input_stack = -1; curr_token = tok_error; curr_ise = NULL; base_ise = NULL; include_file_depth = 0; #if UNICODE_SOURCE_SUPPORTED curr_file_unicode_source_kind = usk_none; clear_getc_source_state(&curr_file_getc_source_state, usk_none); #endif /* UNICODE_SOURCE_SUPPORTED */ if (is_primary_translation_unit) { /* These must be reset here after the PCH prefix has been read. */ curr_seq_number = 0; seq_number_last_read = 0; last_token_sequence_number_used = NO_TOKEN_SEQUENCE_NUMBER; } /* if */ orig_line_modif_list = NULL; end_orig_line_modif_list = NULL; last_splice_olmp = NULL; source_line_modif_list = NULL; line_start_source_line_modif = NULL; sequence_id_for_source_line_modifs = 0; last_source_line_modif_exited_while_skipping_white_space = NULL; preserve_white_space_kind = FALSE; delete_source_from_loc = NULL; curr_token_pragmas = new_fe(); at_end_of_source_file = FALSE; /* Static variables in lexical.c: */ curr_input_stream = NULL; eof_read_on_curr_input_stream = FALSE; after_end_of_all_source = FALSE; init_do_not_put_curr_line_in_pp_output = TRUE; curr_raw_listing_line_code = '\0'; cached_token_rescan_stack = new_fe( cached_token_rescan_init_capacity); reusable_cache_stack = NULL; any_initial_get_token_tests_needed = FALSE; treat_newline_as_token = FALSE; curr_token_sequence_number = NO_TOKEN_SEQUENCE_NUMBER; last_token_sequence_number_of_token = NO_TOKEN_SEQUENCE_NUMBER; any_tokens_fetched_from_curr_input_file = FALSE; curr_token_asm_string = NULL; scanning_microsoft_asm = FALSE; #if ASM_SUPPORT_NEEDED asm_func_body_buffer = NULL; size_asm_func_body_buffer = 0; #endif /* ASM_SUPPORT_NEEDED */ pending_nonsplice_backslash = FALSE; offset_of_nonsplice_backslash = 0; (void)memzero((char *)source_line_modif_hash_table, sizeof(source_line_modif_hash_table)); #if EXPENSIVE_CHECKING && DEBUG check_assertion(curr_translation_unit != NULL); curr_translation_unit->is_partially_sequenced = FALSE; curr_translation_unit->is_fully_sequenced = FALSE; { a_seq_number_lookup_entry_ptr snlep = il_header.seq_number_lookup_entries; /* Mark all active sequence number lookup entries as recyclable. */ while (snlep != NULL) { snlep->is_marked_for_recycle = TRUE; snlep = snlep->next; } /* while */ } #endif /* EXPENSIVE_CHECKING && DEBUG */ } /* lexical_reset */ void lexical_trans_unit_init(void) /* Initialize variables that are specific to a given translation unit. */ { lexical_reset(); /* The following variable is declared in decls.h, but initialized here since it is related to tokenization. */ next_token_is_top_level_decl_start = FALSE; include_file_history_hash_table = alloc_hash_table( FRONT_END_REGION_NUMBER, (a_hash_table_size)1024, fn_for_function(hash_include_file_history), fn_for_function(compare_include_file_history)); #if UNIQUE_FILE_IDENTIFIER_AVAILABLE unique_file_id_hash_table = NULL; #endif /* UNIQUE_FILE_IDENTIFIER_AVAILABLE */ trigraph_diagnostic_issued = FALSE; trigraph_column = 0; curr_stop_token_stack_entry = NULL; curr_lexical_state_stack_entry = NULL; push_lexical_state_stack(); } /* lexical_trans_unit_init */ void lexical_trans_unit_wrapup(void) /* Perform any wrapup operations needed for the translation unit. This is called after all processing for the translation unit (including template instantiations, etc.) has been done. */ { pop_lexical_state_stack_full(/*final_pop=*/TRUE); } /* lexical_trans_unit_wrapup */ void lexical_init(void) /* Initialize static variables related to the lexical routines. This is done as a subroutine (rather than relying on static initialization) so that it can be redone to compile more than one source file in a single invocation of the front end. */ { /* Variables in lexical.h: */ avail_orig_line_modifs = NULL; avail_source_line_modifs = NULL; avail_concatenation_records = NULL; avail_token_ctrl_blocks = new_fe>( /*capacity=*/token_ctrl_block_batch_size); FE_allocator::reserve_block(); avail_scanning_token_caches = new_fe>( /*capacity=*/5u); avail_tiny_scanning_token_caches = new_fe>( /*capacity=*/5u); sequence_id_for_source_line_modifs = 0; delete_source_from_loc = NULL; #if PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED embed_parse_data.directive = NULL; #endif /* PRESERVE_EMBED_DIRECTIVE_WHEN_OPTIMIZED */ /* Static variables in lexical.c: */ avail_cached_constants = NULL; avail_reusable_cache_entries = NULL; avail_stop_token_stack_entries = NULL; avail_lexical_state_stack_entries = NULL; #if GET_DEFINITION_OF_CLASS_NEEDED class_def_buffer = NULL; #endif /* GET_DEFINITION_OF_CLASS_NEEDED */ #if MICROSOFT_EXTENSIONS_ALLOWED metadata_import_buffer = NULL; scanning_for_whitespace_keyword = FALSE; #endif /* MICROSOFT_EXTENSIONS_ALLOWED */ ud_lit_suffix_buffer = NULL; in_token_insertion_from_string = FALSE; token_insertion_position = null_source_position; clear_embed_control_block(); #if !FULLY_RESOLVED_MACRO_POSITIONS pos_of_macro_invocation = null_source_position; #endif /* FULLY_RESOLVED_MACRO_POSITIONS */ ucn_buffer = NULL; suffix_replacement_buffer = NULL; caching_tokens = FALSE; /* Initialize the output control block for the il-to-str routines. */ clear_il_to_str_output_control_block(&octl); octl.output_str = put_str_to_temp_text_buffer_octl; octl.gen_compilable_code = TRUE; next_preinclude_file = NULL; processing_macro_preincludes = FALSE; name_references_map = alloc_fe_of_type(a_name_references_map); construct(name_references_map, /*mask_width=*/8u); #if CREATE_LEXICAL_TYPEREFS template_arg_list_typeref_hash_table = alloc_hash_table(FRONT_END_REGION_NUMBER, (a_hash_table_size)1024, fn_for_function(hash_type_and_template_arg_list), fn_for_function(compare_type_and_template_arg_list)); name_qualifier_typeref_hash_table = alloc_hash_table(FRONT_END_REGION_NUMBER, (a_hash_table_size)1024, fn_for_function(hash_type_and_name_qualifier), fn_for_function(compare_type_and_name_qualifier)); #endif /* CREATE_LEXICAL_TYPEREFS */ pending_overflow_reports = NULL; last_pending_overflow_report = NULL; available_overflow_reports = NULL; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED pending_bidi_controls = NULL; avail_pending_bidi_controls = NULL; id_representation_map = NULL; src_spelling = NULL; prototyped_spelling = NULL; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ #if DEBUG num_orig_line_modifs_allocated = 0; num_source_line_modifs_allocated = 0; num_concatenation_records_allocated = 0; num_shared_token_states_allocated = 0; num_cached_constants_allocated = 0; num_reusable_cache_entries_allocated = 0; num_pragmas_allocated = 0; num_stop_token_stack_entries_allocated = 0; num_lexical_state_stack_entries_allocated = 0; num_pragma_descriptions_allocated = 0; num_include_file_histories_allocated = 0; num_preinclude_files_allocated = 0; cached_pp_token_string_space = 0; num_compares_in_source_line_modif_hash_table = 0; num_lookups_in_source_line_modif_hash_table = 0; #if UNICODE_VULNERABILITY_DETECTION_SUPPORTED num_id_representations_allocated = 0; num_spelling_storage_buffers_allocated = 0; spelling_storage_buffer_space = 0; spelling_storage_buffer_head = NULL; spelling_storage_buffer_tail = NULL; #endif /* UNICODE_VULNERABILITY_DETECTION_SUPPORTED */ #endif /* DEBUG */ #if EXPENSIVE_CHECKING /* Make sure the UCN table is properly formed. Each element of the array must be a start-end range where the ending value is greater than the starting value, and the starting value is greater than the end of the previous range. */ { unsigned long last_end = 0; unsigned int i; for (i = 0; i < sizeof(UCN_table) / sizeof(a_UCN_range); i++) { const a_UCN_range *p = &UCN_table[i]; check_assertion_str2(p->start <= p->end && p->start > last_end, "lexical_init:", "UCN_table is not sorted properly"); last_end = p->end; } /* for */ } #endif /* EXPENSIVE_CHECKING */ #if CHECKING #if !DO_NOT_ASSUME_QUESTION_IS_LARGER_THAN_END_OF_LINE /* The source input routines assume that '?' > '\n' and '\r' to improve performance. Although that's true in practice, the assumption is not made if DO_NOT_ASSUME_QUESTION_IS_LARGER_THAN_END_OF_LINE is TRUE. */ check_assertion('?' > '\n' && '?' > '\r'); #endif /* !DO_NOT_ASSUME_QUESTION_IS_LARGER_THAN_END_OF_LINE */ #endif /* CHECKING */ } /* lexical_init */ #if MAKE_FRONT_END_CALLABLE void lexical_cleanup(void) /* This routine is called at the end of compilation, or if compilation is terminated prematurely for some reason. It performs any cleanup operations required. In particular, it closes any files that may have been open at the point at which the compilation was terminated. */ { int depth; if (input_stack != NULL) { /* Close any files on the input stack that are currently open. */ for (depth = depth_input_stack; depth >= 0; --depth) { an_input_stack_entry_ptr ise = &input_stack[depth]; if (ise->cloned_for_line_directive) { /* A cloned entry will refer to the same file as the original, so don't attempt to close the cloned copy. */ ise->file = NULL; } else { close_file_if_open(&ise->file); } /* if */ } /* for */ } /* if */ /* Make sure we don't go through the loop above if this routine is called again before input_stack has been reset. */ depth_input_stack = -1; base_ise = NULL; } /* lexical_cleanup */ #endif /* MAKE_FRONT_END_CALLABLE */ /* Conditionally close the "edg" namespace. */ END_EDG_NAMESPACE