diff options
| author | kx <kx@radix-linux.su> | 2026-10-01 12:02:28 +0300 |
|---|---|---|
| committer | kx <kx@radix-linux.su> | 2026-10-01 12:02:28 +0300 |
| commit | ba1b04d64bdfaf377915b22f77520216ebe47674 (patch) | |
| tree | 044343035c28b2b04ca9725f26e9b7c39342d19c /src/mcpp-lib.c | |
| parent | 124b140798456778e2a96851cc7268aa9a726698 (diff) | |
| download | mcpu-cpp-1.0.2.tar.xz | |
Version 1.0.21.0.2
Diffstat (limited to 'src/mcpp-lib.c')
| -rw-r--r-- | src/mcpp-lib.c | 3443 |
1 files changed, 3443 insertions, 0 deletions
diff --git a/src/mcpp-lib.c b/src/mcpp-lib.c new file mode 100644 index 0000000..a0879f2 --- /dev/null +++ b/src/mcpp-lib.c @@ -0,0 +1,3443 @@ +#include <defs.h> + +#include <sys/stat.h> + + +struct mcpp_dependency +{ + char *path; + dev_t device; + ino_t inode; + int has_identity; + int unresolved; + int system_only; + mcpp_dependency *next; +}; + + +static int +include_dir_is_system( const mcpu_include_dir *dir ) +{ + if( dir == NULL ) + return( 0 ); + + switch( dir->class_type ) + { + case MCPU_INCLUDE_SYSTEM_EXPLICIT: + case MCPU_INCLUDE_SYSTEM_CONFIG_LANGUAGE: + case MCPU_INCLUDE_SYSTEM_CONFIG: + case MCPU_INCLUDE_AFTER_EXPLICIT: + case MCPU_INCLUDE_AFTER_CONFIG: + return( 1 ); + default: + return( 0 ); + } +} + + +static int +dependency_add_internal( mcpp *cpp, const char *path, int system_header, + int unresolved ) +{ + mcpp_dependency *p; + struct stat st; + int has_identity; + + if( cpp == NULL || path == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + /* + * Resolved dependencies belong to the physical identity domain and are + * deduplicated by st_dev/st_ino whenever possible. -MG dependencies have + * no physical object yet, so they deliberately bypass stat() and occupy a + * separate unresolved identity domain keyed by the exact include operand. + * This prevents an unrelated file in the current directory from turning an + * unresolved <name> into a false physical match. + */ + has_identity = !unresolved && stat(path, &st) == 0; + + for( p = cpp->dependencies_first; p != NULL; p = p->next ) + { + int same = 0; + + if( p->unresolved != unresolved ) + continue; + + if( unresolved ) + same = strcmp(p->path, path) == 0; + else if( has_identity && p->has_identity ) + same = p->device == st.st_dev && p->inode == st.st_ino; + else if( !has_identity && !p->has_identity ) + same = strcmp(p->path, path) == 0; + + if( same ) + { + /* + * A resolved physical file may first be reached through a system path + * and later through a user path; preserve the established registry rule + * that the user reach makes it a user dependency. An unresolved -MG + * entry has no physical provenance, so its first classification remains + * authoritative, matching GNU CPP's missing-header behavior. + */ + if( !unresolved && !system_header ) + p->system_only = 0; + return( 0 ); + } + } + + p = (mcpp_dependency *)calloc( 1, sizeof(*p) ); + if( p == NULL ) + return( -1 ); + + p->path = strdup( path ); + if( p->path == NULL ) + { + free( p ); + return( -1 ); + } + + p->has_identity = has_identity; + p->unresolved = unresolved != 0; + if( has_identity ) + { + p->device = st.st_dev; + p->inode = st.st_ino; + } + p->system_only = system_header != 0; + + if( cpp->dependencies_last != NULL ) + cpp->dependencies_last->next = p; + else + cpp->dependencies_first = p; + cpp->dependencies_last = p; + + return( 0 ); +} + + +static int +dependency_add( mcpp *cpp, const char *path, int system_header ) +{ + return( dependency_add_internal(cpp, path, system_header, 0) ); +} + + +static int +dependency_add_unresolved( mcpp *cpp, const char *path, + int system_header ) +{ + return( dependency_add_internal(cpp, path, system_header, 1) ); +} + + +static void +dependencies_free( mcpp *cpp ) +{ + mcpp_dependency *p; + + if( cpp == NULL ) + return; + + p = cpp->dependencies_first; + while( p != NULL ) + { + mcpp_dependency *next = p->next; + free( p->path ); + free( p ); + p = next; + } + + cpp->dependencies_first = NULL; + cpp->dependencies_last = NULL; +} + +struct mcpp_once_file +{ + dev_t device; + ino_t inode; + mcpp_once_file *next; +}; + + +static int +once_file_identity( const char *filename, dev_t *device, ino_t *inode ) +{ + struct stat st; + + if( filename == NULL || device == NULL || inode == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( stat(filename, &st) != 0 ) + return( -1 ); + + *device = st.st_dev; + *inode = st.st_ino; + return( 0 ); +} + + +static int +once_file_seen_identity( const mcpp *cpp, dev_t device, ino_t inode ) +{ + const mcpp_once_file *p; + + if( cpp == NULL ) + return( 0 ); + + for( p = cpp->once_files; p != NULL; p = p->next ) + if( p->device == device && p->inode == inode ) + return( 1 ); + + return( 0 ); +} + + +static int +once_file_seen( const mcpp *cpp, const char *filename ) +{ + dev_t device; + ino_t inode; + + if( once_file_identity(filename, &device, &inode) != 0 ) + return( 0 ); + + return( once_file_seen_identity(cpp, device, inode) ); +} + + +static int +once_file_mark( mcpp *cpp, const char *filename ) +{ + mcpp_once_file *entry; + dev_t device; + ino_t inode; + + if( cpp == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + /* + * A stream such as <stdin> has no stable filesystem identity. In that + * case #pragma once is consumed but there is nothing that can be entered + * in the physical-file registry. + */ + if( once_file_identity(filename, &device, &inode) != 0 ) + return( 0 ); + + if( once_file_seen_identity(cpp, device, inode) ) + return( 0 ); + + entry = (mcpp_once_file *)calloc( 1, sizeof(*entry) ); + if( entry == NULL ) + return( -1 ); + + entry->device = device; + entry->inode = inode; + entry->next = cpp->once_files; + cpp->once_files = entry; + return( 0 ); +} + + +static void +once_files_free( mcpp *cpp ) +{ + mcpp_once_file *p; + + if( cpp == NULL ) + return; + + p = cpp->once_files; + while( p != NULL ) + { + mcpp_once_file *next = p->next; + free( p ); + p = next; + } + cpp->once_files = NULL; +} + + +static enum mcpu_language +current_language( const mcpp *cpp ) +{ + return( cpp->lang_stack[cpp->lang_depth - 1] ); +} + +static int +install_builtin( mcpp *cpp, const char *name, + enum mcpu_macro_builtin builtin ) +{ + mcpu_text text; + int rc; + + mcpu_text_init( &text ); + if( mcpu_text_from_utf8(&text, name) != 0 ) + return( -1 ); + + rc = mcpu_macro_define_builtin( &cpp->macros, text.data, text.length, + builtin ); + mcpu_text_free( &text ); + return( rc ); +} + + +static int +initialize_builtins( mcpp *cpp ) +{ + if( cpp->builtins_initialized ) + return( 0 ); + + if( install_builtin(cpp, "__FILE__", + MCPU_MACRO_BUILTIN_FILE) != 0 || + install_builtin(cpp, "__LINE__", + MCPU_MACRO_BUILTIN_LINE) != 0 || + install_builtin(cpp, "__DATE__", + MCPU_MACRO_BUILTIN_DATE) != 0 || + install_builtin(cpp, "__TIME__", + MCPU_MACRO_BUILTIN_TIME) != 0 || + install_builtin(cpp, "__BASE_FILE__", + MCPU_MACRO_BUILTIN_BASE_FILE) != 0 || + install_builtin(cpp, "__INCLUDE_LEVEL__", + MCPU_MACRO_BUILTIN_INCLUDE_LEVEL) != 0 || + mcpu_predefined_install(&cpp->macros) != 0 ) + return( -1 ); + + cpp->builtins_initialized = 1; + return( 0 ); +} + + +static int +initialize_timestamp( mcpp *cpp ) +{ + static const char *months[] = + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }; + time_t now; + struct tm *tm; + + now = time( NULL ); + if( now == (time_t)-1 ) + return( -1 ); + + tm = localtime( &now ); + if( tm == NULL || tm->tm_mon < 0 || tm->tm_mon > 11 ) + { + errno = EINVAL; + return( -1 ); + } + + snprintf( cpp->preprocess_date, sizeof(cpp->preprocess_date), + "%s %2d %4d", months[tm->tm_mon], tm->tm_mday, + tm->tm_year + 1900 ); + snprintf( cpp->preprocess_time, sizeof(cpp->preprocess_time), + "%02d:%02d:%02d", tm->tm_hour, tm->tm_min, tm->tm_sec ); + + return( 0 ); +} + + +static void +macro_context( const mcpp *cpp, const char *filename, + unsigned line_number, mcpu_macro_expansion_context *context ) +{ + context->filename = filename; + context->base_filename = cpp->base_filename; + context->line_number = line_number; + context->include_level = cpp->include_depth ? cpp->include_depth - 1 : 0; + context->date = cpp->preprocess_date; + context->time = cpp->preprocess_time; + context->options = MCPP_OPTIONS(cpp); +} + + +static char * +escape_line_filename( const char *filename ) +{ + size_t n; + size_t i; + size_t o = 0; + char *escaped; + + if( filename == NULL ) + { + errno = EINVAL; + return( NULL ); + } + + n = strlen( filename ); + if( n > (SIZE_MAX - 1) / 2 ) + { + errno = EOVERFLOW; + return( NULL ); + } + + escaped = (char *)malloc( n * 2 + 1 ); + if( escaped == NULL ) + return( NULL ); + + for( i = 0; i < n; ++i ) + { + switch( filename[i] ) + { + case '\\': + case '"': + escaped[o++] = '\\'; + escaped[o++] = filename[i]; + break; + case '\n': + escaped[o++] = '\\'; + escaped[o++] = 'n'; + break; + case '\r': + escaped[o++] = '\\'; + escaped[o++] = 'r'; + break; + case '\t': + escaped[o++] = '\\'; + escaped[o++] = 't'; + break; + default: + escaped[o++] = filename[i]; + break; + } + } + + escaped[o] = 0; + return( escaped ); +} + +static int +set_output_position( mcpp *cpp, unsigned line, const char *filename ) +{ + char *copy; + + if( cpp == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + copy = strdup( filename ); + if( copy == NULL ) + return( -1 ); + + free( cpp->output_filename ); + cpp->output_filename = copy; + cpp->output_line = line; + return( 0 ); +} + +static int +append_line_marker( mcpp *cpp, unsigned line, const char *filename, + int file_change ) +{ + char number[64]; + char *escaped; + char *position_filename; + + escaped = escape_line_filename( filename ); + if( escaped == NULL ) + return( -1 ); + + position_filename = strdup( filename ); + if( position_filename == NULL ) + { + free( escaped ); + return( -1 ); + } + + if( mcpu_text_append_ascii( &cpp->output, "# " ) != 0 ) + { + free( position_filename ); + free( escaped ); + return( -1 ); + } + + snprintf( number, sizeof(number), "%u", line ); + if( mcpu_text_append_ascii( &cpp->output, number ) != 0 || + mcpu_text_append_ascii( &cpp->output, " \"" ) != 0 || + mcpu_text_append_utf8( &cpp->output, escaped ) != 0 || + mcpu_text_append_ascii( &cpp->output, "\"" ) != 0 ) + { + free( position_filename ); + free( escaped ); + return( -1 ); + } + + if( file_change == 1 || file_change == 2 ) + { + if( mcpu_text_append_ascii(&cpp->output, + file_change == 1 ? " 1" : " 2") != 0 ) + { + free( position_filename ); + free( escaped ); + return( -1 ); + } + } + + if( mcpu_text_append_char(&cpp->output, '\n') != 0 ) + { + free( position_filename ); + free( escaped ); + return( -1 ); + } + + free( cpp->output_filename ); + cpp->output_filename = position_filename; + cpp->output_line = line; + free( escaped ); + return( 0 ); +} + +static int +append_output_newlines( mcpp *cpp, unsigned count ) +{ + unsigned i; + + for( i = 0; i < count; ++i ) + { + if( mcpu_text_append_char(&cpp->output, '\n') != 0 ) + return( -1 ); + } + + if( cpp->output_filename != NULL ) + cpp->output_line += count; + + return( 0 ); +} + +/* + * GNU CPP keeps short invisible source gaps as ordinary newlines, but once + * the next visible source position is eight or more lines away it emits a + * fresh line marker instead. The discarded text is therefore forgotten as + * output bytes, but never forgotten as source position: "remove, but + * remember". + */ +static int +sync_output_position( mcpp *cpp, unsigned line, const char *filename ) +{ + if( cpp->output_filename != NULL && + strcmp(cpp->output_filename, filename) == 0 && + line >= cpp->output_line && line - cpp->output_line < 8 ) + return( append_output_newlines(cpp, line - cpp->output_line) ); + + return( append_line_marker(cpp, line, filename, 0) ); +} + +static int +text_has_visible_tokens( const __mpu_char16_t *text, size_t length ) +{ + size_t i; + + for( i = 0; i < length; ++i ) + { + switch( text[i] ) + { + case ' ': + case '\t': + case '\f': + case '\v': + case '\r': + case '\n': + break; + default: + return( 1 ); + } + } + + return( 0 ); +} + +static void +advance_output_position( mcpp *cpp, + const __mpu_char16_t *text, size_t length ) +{ + size_t i; + + if( cpp->output_filename == NULL ) + return; + + for( i = 0; i < length; ++i ) + if( text[i] == '\n' ) + ++cpp->output_line; +} + +static int +append_visible_text( mcpp *cpp, unsigned line, const char *filename, + const __mpu_char16_t *text, size_t length ) +{ + if( !text_has_visible_tokens(text, length) ) + return( 0 ); + + if( sync_output_position(cpp, line, filename) != 0 || + mcpu_text_append(&cpp->output, text, length) != 0 ) + return( -1 ); + + advance_output_position( cpp, text, length ); + return( 0 ); +} + +static int +is_space16( __mpu_char16_t c ) +{ + return( c == ' ' || c == '\t' || c == '\f' || c == '\v' || c == '\r' ); +} + +static int +is_ident16( __mpu_char16_t c ) +{ + return( (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c >= 0x80 ); +} + +static int +replacement_quote16( __mpu_char16_t c, enum mcpu_language language ) +{ + if( c == '"' || c == '`' ) + return( 1 ); + + if( c == '\'' && language != MCPU_LANG_DIFF ) + return( 1 ); + + return( 0 ); +} + + +static int +normalize_macro_replacement( const __mpu_char16_t *text, size_t length, + enum mcpu_language language, + mcpu_text *normalized ) +{ + size_t p = 0; + __mpu_char16_t quote = 0; + int escaped = 0; + int pending_space = 0; + + while( p < length ) + { + __mpu_char16_t c = text[p++]; + + if( quote != 0 ) + { + if( mcpu_text_append_char(normalized, c) != 0 ) + return( -1 ); + + if( escaped ) + escaped = 0; + else if( c == '\\' ) + escaped = 1; + else if( c == quote || c == '\n' ) + quote = 0; + + continue; + } + + if( is_space16(c) || c == '\n' ) + { + if( normalized->length != 0 ) + pending_space = 1; + continue; + } + + if( pending_space ) + { + if( mcpu_text_append_char(normalized, ' ') != 0 ) + return( -1 ); + pending_space = 0; + } + + if( mcpu_text_append_char(normalized, c) != 0 ) + return( -1 ); + + if( replacement_quote16(c, language) ) + quote = c; + } + + return( 0 ); +} + + +static void +skip_space16( const __mpu_char16_t *line, size_t length, size_t *pos ) +{ + while( *pos < length && is_space16(line[*pos]) ) + ++*pos; +} + +static int +parse_directive_name( const __mpu_char16_t *line, size_t length, + size_t hash, size_t *name_start, size_t *name_length, + size_t *after_name ) +{ + size_t p = hash + 1; + size_t start; + + skip_space16( line, length, &p ); + start = p; + while( p < length && is_ident16(line[p]) ) + ++p; + + *name_start = start; + *name_length = p - start; + *after_name = p; + + return( *name_length != 0 ); +} + +static int +handle_lang( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p, + int spliced ) +{ + size_t start; + size_t end; + size_t q; + enum mcpu_language language; + char *name; + int newline; + + if( spliced ) + { + fprintf( stderr, + "%s:%u: error: #lang must be contained in one physical source line\n", + filename, line_number ); + return( -1 ); + } + + skip_space16( line, length, &p ); + if( p >= length || line[p] != '"' ) + { + fprintf( stderr, + "%s:%u: error: string constant expected after #lang\n", + filename, line_number ); + return( -1 ); + } + + start = ++p; + while( p < length && line[p] != '"' && line[p] != '\n' ) + ++p; + + if( p >= length || line[p] != '"' ) + { + fprintf( stderr, + "%s:%u: error: unterminated #lang string constant\n", + filename, line_number ); + return( -1 ); + } + end = p++; + + if( start == end ) + { + fprintf( stderr, + "%s:%u: error: empty language name in #lang\n", + filename, line_number ); + return( -1 ); + } + + for( q = start; q < end; ++q ) + { + if( line[q] == ' ' || line[q] == '\t' || line[q] == '\f' || + line[q] == '\v' || line[q] == '\r' || line[q] == '\n' ) + { + fprintf( stderr, + "%s:%u: error: #lang language name must be one word without whitespace\n", + filename, line_number ); + return( -1 ); + } + } + + skip_space16( line, length, &p ); + if( p < length && line[p] != '\n' ) + { + fprintf( stderr, + "%s:%u: error: extra text after #lang string constant\n", + filename, line_number ); + return( -1 ); + } + + if( mcpu_language_from_text( line + start, end - start, &language ) != 0 ) + { + name = mcpu_text_to_utf8( line + start, end - start ); + fprintf( stderr, "%s:%u: error: unknown language '%s'\n", + filename, line_number, name ? name : "?" ); + free( name ); + return( -1 ); + } + + if( cpp->lang_depth >= MCPU_CPP_LANG_STACK_SIZE ) + { + fprintf( stderr, "%s:%u: error: #lang stack overflow\n", + filename, line_number ); + return( -1 ); + } + + cpp->lang_stack[cpp->lang_depth++] = language; + + if( cpp->verbose ) + fprintf( stderr, "%s:%u: #lang %s (depth %lu)\n", + filename, line_number, mcpu_language_name(language), + (unsigned long)cpp->lang_depth ); + + newline = length != 0 && line[length - 1] == '\n'; + if( mcpu_text_append_ascii(&cpp->output, "#lang \"") != 0 || + mcpu_text_append(&cpp->output, line + start, end - start) != 0 || + mcpu_text_append_char(&cpp->output, '"') != 0 || + (newline && mcpu_text_append_char(&cpp->output, '\n') != 0) ) + return( -1 ); + + return( 0 ); +} + +static int +handle_endlang( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length ) +{ + if( cpp->lang_depth <= 1 ) + { + fprintf( stderr, "%s:%u: error: unbalanced #endlang\n", + filename, line_number ); + return( -1 ); + } + + --cpp->lang_depth; + + if( cpp->verbose ) + fprintf( stderr, "%s:%u: #endlang -> %s (depth %lu)\n", + filename, line_number, mcpu_language_name(current_language(cpp)), + (unsigned long)cpp->lang_depth ); + + return( mcpu_text_append( &cpp->output, line, length ) ); +} + +static int +parse_include_filename( const __mpu_char16_t *line, size_t length, size_t p, + char **filename, int *quoted ) +{ + __mpu_char16_t open; + __mpu_char16_t close; + size_t start; + size_t end; + + *filename = NULL; + *quoted = 0; + + skip_space16( line, length, &p ); + if( p >= length ) + return( -1 ); + + open = line[p++]; + if( open == '"' ) + { + close = '"'; + *quoted = 1; + } + else if( open == '<' ) + close = '>'; + else + return( -1 ); + + start = p; + while( p < length && line[p] != close && line[p] != '\n' ) + ++p; + end = p; + + if( p >= length || line[p] != close || end == start ) + return( -1 ); + + *filename = mcpu_text_to_utf8( line + start, end - start ); + return( *filename ? 0 : -1 ); +} + + +static size_t +line_content_end( const __mpu_char16_t *line, size_t length ) +{ + size_t end = length; + + if( end != 0 && line[end - 1] == '\n' ) + --end; + + while( end != 0 && is_space16(line[end - 1]) ) + --end; + + return( end ); +} + +static void +free_define_args( const __mpu_char16_t **argnames, + size_t *argname_lengths ) +{ + free( argnames ); + free( argname_lengths ); +} + + +static int +append_define_arg( const __mpu_char16_t ***argnames, + size_t **argname_lengths, + size_t *nargs, size_t *capacity, + const __mpu_char16_t *name, size_t name_length ) +{ + const __mpu_char16_t **new_names; + size_t *new_lengths; + size_t new_capacity; + + if( *nargs == *capacity ) + { + new_capacity = *capacity ? *capacity * 2 : 4; + if( new_capacity < *capacity || + new_capacity > SIZE_MAX / sizeof(**argnames) || + new_capacity > SIZE_MAX / sizeof(**argname_lengths) ) + { + errno = EOVERFLOW; + return( -1 ); + } + + new_names = (const __mpu_char16_t **)realloc( + (void *)*argnames, new_capacity * sizeof(**argnames) ); + if( new_names == NULL ) + return( -1 ); + + *argnames = new_names; + + new_lengths = (size_t *)realloc( *argname_lengths, + new_capacity * sizeof(**argname_lengths) ); + if( new_lengths == NULL ) + return( -1 ); + + *argname_lengths = new_lengths; + *capacity = new_capacity; + } + + (*argnames)[*nargs] = name; + (*argname_lengths)[*nargs] = name_length; + ++*nargs; + + return( 0 ); +} + + +static int +replacement_va_opt_range_valid( const __mpu_char16_t *text, + size_t length, + enum mcpu_language language, + int function_like, int variadic, + int inside_va_opt, + const char *filename, + unsigned line_number ) +{ + size_t p = 0; + __mpu_char16_t quote = 0; + int escaped = 0; + + while( p < length ) + { + __mpu_char16_t c = text[p]; + + if( quote ) + { + if( escaped ) + escaped = 0; + else if( c == '\\' ) + escaped = 1; + else if( c == quote || c == '\n' ) + quote = 0; + + ++p; + continue; + } + + if( c == '"' || c == '`' || + (c == '\'' && language != MCPU_LANG_DIFF) ) + { + quote = c; + ++p; + continue; + } + + if( mcpu_pp_is_identifier_start(c) ) + { + size_t q = p + 1; + + while( q < length && mcpu_pp_is_identifier_char(text[q]) ) + ++q; + + if( mcpu_text_equal_ascii(text + p, q - p, "__VA_OPT__") ) + { + size_t open; + size_t close; + size_t first; + size_t last; + size_t r; + int depth; + __mpu_char16_t inner_quote = 0; + int inner_escaped = 0; + + if( !function_like || !variadic ) + { + fprintf( stderr, + "%s:%u: error: '__VA_OPT__' may appear only in a variadic macro replacement list\n", + filename, line_number ); + return( 0 ); + } + + if( inside_va_opt ) + { + fprintf( stderr, + "%s:%u: error: '__VA_OPT__' may not appear inside another '__VA_OPT__'\n", + filename, line_number ); + return( 0 ); + } + + open = q; + while( open < length && is_space16(text[open]) ) + ++open; + + if( open >= length || text[open] != '(' ) + { + fprintf( stderr, + "%s:%u: error: '__VA_OPT__' must be followed by '('\n", + filename, line_number ); + return( 0 ); + } + + depth = 1; + close = open + 1; + while( close < length && depth != 0 ) + { + __mpu_char16_t d = text[close]; + + if( inner_quote ) + { + if( inner_escaped ) + inner_escaped = 0; + else if( d == '\\' ) + inner_escaped = 1; + else if( d == inner_quote || d == '\n' ) + inner_quote = 0; + ++close; + continue; + } + + if( d == '"' || d == '`' || + (d == '\'' && language != MCPU_LANG_DIFF) ) + { + inner_quote = d; + ++close; + continue; + } + + if( d == '(' ) + ++depth; + else if( d == ')' ) + --depth; + + ++close; + } + + if( depth != 0 ) + { + fprintf( stderr, + "%s:%u: error: unterminated '__VA_OPT__'\n", + filename, line_number ); + return( 0 ); + } + + --close; + first = open + 1; + while( first < close && is_space16(text[first]) ) + ++first; + last = close; + while( last > first && is_space16(text[last - 1]) ) + --last; + + if( first + 1 < last && text[first] == '#' && text[first + 1] == '#' ) + { + fprintf( stderr, + "%s:%u: error: '##' cannot appear at the beginning of '__VA_OPT__'\n", + filename, line_number ); + return( 0 ); + } + + if( last >= first + 2 && text[last - 2] == '#' && text[last - 1] == '#' ) + { + fprintf( stderr, + "%s:%u: error: '##' cannot appear at the end of '__VA_OPT__'\n", + filename, line_number ); + return( 0 ); + } + + if( !replacement_va_opt_range_valid(text + open + 1, + close - open - 1, + language, + function_like, variadic, 1, + filename, line_number) ) + return( 0 ); + + r = close + 1; + p = r; + continue; + } + + p = q; + continue; + } + + ++p; + } + + return( 1 ); +} + + +static int +replacement_va_opt_valid( const __mpu_char16_t *text, + size_t length, + enum mcpu_language language, + int function_like, int variadic, + const char *filename, + unsigned line_number ) +{ + return( replacement_va_opt_range_valid(text, length, + language, + function_like, variadic, 0, + filename, line_number) ); +} + + +static int +replacement_macro_operators_valid( const __mpu_char16_t *text, + size_t length, + enum mcpu_language language, + int function_like, int variadic, + const __mpu_char16_t *const *argnames, + const size_t *argname_lengths, + size_t nargs, + const char *filename, + unsigned line_number ) +{ + size_t p = 0; + __mpu_char16_t quote = 0; + int escaped = 0; + int saw_token = 0; + int paste_at_end = 0; + + while( p < length ) + { + __mpu_char16_t c = text[p]; + + if( quote ) + { + if( escaped ) + escaped = 0; + else if( c == '\\' ) + escaped = 1; + else if( c == quote || c == '\n' ) + quote = 0; + + ++p; + continue; + } + + if( c == '"' || c == '`' || + (c == '\'' && language != MCPU_LANG_DIFF) ) + { + quote = c; + saw_token = 1; + paste_at_end = 0; + ++p; + continue; + } + + if( c == '#' && p + 1 < length && text[p + 1] == '#' ) + { + if( !saw_token ) + { + fprintf( stderr, + "%s:%u: error: '##' cannot appear at the beginning of a macro replacement list\n", + filename, line_number ); + return( 0 ); + } + + paste_at_end = 1; + p += 2; + continue; + } + + if( c == '#' && function_like ) + { + size_t q = p + 1; + size_t start; + size_t i; + int found = 0; + + while( q < length && is_space16(text[q]) ) + ++q; + + if( q >= length || !mcpu_pp_is_identifier_start(text[q]) ) + { + fprintf( stderr, + "%s:%u: error: '#' operator is not followed by a macro argument name\n", + filename, line_number ); + return( 0 ); + } + + start = q++; + while( q < length && mcpu_pp_is_identifier_char(text[q]) ) + ++q; + + for( i = 0; i < nargs; ++i ) + { + if( argname_lengths[i] == q - start && + memcmp(argnames[i], text + start, + (q - start) * sizeof(__mpu_char16_t)) == 0 ) + { + found = 1; + break; + } + } + + if( !found && variadic && + (mcpu_text_equal_ascii(text + start, q - start, "__VA_ARGS__") || + mcpu_text_equal_ascii(text + start, q - start, "__VA_OPT__")) ) + found = 1; + + if( !found ) + { + fprintf( stderr, + "%s:%u: error: '#' operator should be followed by a macro argument name\n", + filename, line_number ); + return( 0 ); + } + + saw_token = 1; + paste_at_end = 0; + p = q; + continue; + } + + if( is_space16(c) ) + { + ++p; + continue; + } + + saw_token = 1; + paste_at_end = 0; + ++p; + } + + if( paste_at_end ) + { + fprintf( stderr, + "%s:%u: error: '##' cannot appear at the end of a macro replacement list\n", + filename, line_number ); + return( 0 ); + } + + return( 1 ); +} + + +static int +handle_define( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p, + enum mcpu_macro_origin origin ) +{ + size_t name_start; + size_t name_end; + size_t replacement_start; + size_t replacement_end; + const __mpu_char16_t **argnames = NULL; + size_t *argname_lengths = NULL; + size_t nargs = 0; + size_t capacity = 0; + int function_like = 0; + int variadic = 0; + mcpu_macro *old; + mcpu_text replacement; + int rc = -1; + + mcpu_text_init( &replacement ); + + skip_space16( line, length, &p ); + if( p >= length || !mcpu_pp_is_identifier_start(line[p]) ) + { + fprintf( stderr, "%s:%u: error: macro name expected after #define\n", + filename, line_number ); + return( -1 ); + } + + name_start = p++; + while( p < length && mcpu_pp_is_identifier_char(line[p]) ) + ++p; + name_end = p; + + /*************************************************************** + A function-like definition is recognized only when + the opening parenthesis immediately follows the macro name. + A space changes the definition into an object-like macro whose + replacement happens to begin with '('. + ***************************************************************/ + if( p < length && line[p] == '(' ) + { + size_t i; + + function_like = 1; + ++p; + skip_space16( line, length, &p ); + + if( p + 2 < length && + line[p] == '.' && line[p + 1] == '.' && line[p + 2] == '.' ) + { + variadic = 1; + p += 3; + skip_space16( line, length, &p ); + if( p >= length || line[p] != ')' ) + { + fprintf( stderr, + "%s:%u: error: badly punctuated parameter list in #define\n", + filename, line_number ); + goto done; + } + } + else + { + while( p < length && line[p] != ')' ) + { + size_t arg_start; + size_t arg_end; + + if( !mcpu_pp_is_identifier_start(line[p]) ) + { + fprintf( stderr, + "%s:%u: error: invalid macro parameter name in #define\n", + filename, line_number ); + goto done; + } + + arg_start = p++; + while( p < length && mcpu_pp_is_identifier_char(line[p]) ) + ++p; + arg_end = p; + + if( mcpu_text_equal_ascii(line + arg_start, arg_end - arg_start, + "__VA_ARGS__") ) + { + fprintf( stderr, + "%s:%u: error: '__VA_ARGS__' cannot be used as a macro parameter name\n", + filename, line_number ); + goto done; + } + + for( i = 0; i < nargs; ++i ) + { + if( argname_lengths[i] == arg_end - arg_start && + memcmp( argnames[i], line + arg_start, + (arg_end - arg_start) * sizeof(__mpu_char16_t) ) == 0 ) + { + char *arg = mcpu_text_to_utf8( line + arg_start, + arg_end - arg_start ); + fprintf( stderr, + "%s:%u: error: duplicate argument name '%s' in #define\n", + filename, line_number, arg ? arg : "?" ); + free( arg ); + goto done; + } + } + + if( append_define_arg(&argnames, &argname_lengths, + &nargs, &capacity, + line + arg_start, arg_end - arg_start) != 0 ) + goto done; + + skip_space16( line, length, &p ); + if( p >= length ) + { + fprintf( stderr, + "%s:%u: error: unterminated parameter list in #define\n", + filename, line_number ); + goto done; + } + + if( line[p] == ',' ) + { + ++p; + skip_space16( line, length, &p ); + + if( p + 2 < length && + line[p] == '.' && line[p + 1] == '.' && line[p + 2] == '.' ) + { + variadic = 1; + p += 3; + skip_space16( line, length, &p ); + if( p >= length || line[p] != ')' ) + { + fprintf( stderr, + "%s:%u: error: badly punctuated parameter list in #define\n", + filename, line_number ); + goto done; + } + break; + } + + if( p >= length || !mcpu_pp_is_identifier_start(line[p]) ) + { + fprintf( stderr, + "%s:%u: error: badly punctuated parameter list in #define\n", + filename, line_number ); + goto done; + } + continue; + } + + if( line[p] != ')' ) + { + fprintf( stderr, + "%s:%u: error: badly punctuated parameter list in #define\n", + filename, line_number ); + goto done; + } + } + } + + if( p >= length || line[p] != ')' ) + { + fprintf( stderr, + "%s:%u: error: unterminated parameter list in #define\n", + filename, line_number ); + goto done; + } + + ++p; + } + + skip_space16( line, length, &p ); + replacement_start = p; + replacement_end = line_content_end( line, length ); + if( replacement_end < replacement_start ) + replacement_end = replacement_start; + + if( normalize_macro_replacement(line + replacement_start, + replacement_end - replacement_start, + current_language(cpp), &replacement) != 0 ) + goto done; + + if( !replacement_va_opt_valid( + replacement.data, replacement.length, + current_language(cpp), function_like, variadic, + filename, line_number) || + !replacement_macro_operators_valid( + replacement.data, replacement.length, + current_language(cpp), function_like, variadic, + argnames, argname_lengths, nargs, + filename, line_number) ) + goto done; + + old = mcpu_macro_find( &cpp->macros, + line + name_start, name_end - name_start ); + if( old && + !mcpu_macro_definition_equal(old, + function_like ? (int)nargs : -1, + function_like ? variadic : 0, + argnames, argname_lengths, + replacement.data, + replacement.length) ) + { + char *name = mcpu_text_to_utf8( line + name_start, + name_end - name_start ); + if( mcpp_diagnostic_warning(MCPP_OPTIONS(cpp), filename, line_number, + "macro '%s' redefined", + name ? name : "?") != 0 ) + { + free( name ); + goto done; + } + free( name ); + } + + if( function_like ) + rc = mcpu_macro_define_function(&cpp->macros, + line + name_start, name_end - name_start, + argnames, argname_lengths, nargs, variadic, + replacement.data, replacement.length, + origin); + else + rc = mcpu_macro_define_object(&cpp->macros, + line + name_start, name_end - name_start, + replacement.data, replacement.length, + origin); + +done: + mcpu_text_free( &replacement ); + free_define_args( argnames, argname_lengths ); + return( rc ); +} + + +static int +handle_undef( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p ) +{ + size_t name_start; + size_t name_end; + + skip_space16( line, length, &p ); + if( p >= length || !mcpu_pp_is_identifier_start(line[p]) ) + { + fprintf( stderr, "%s:%u: error: macro name expected after #undef\n", + filename, line_number ); + return( -1 ); + } + + name_start = p++; + while( p < length && mcpu_pp_is_identifier_char(line[p]) ) + ++p; + name_end = p; + + skip_space16( line, length, &p ); + if( p < length && line[p] != '\n' ) + { + fprintf( stderr, "%s:%u: error: extra tokens after #undef\n", + filename, line_number ); + return( -1 ); + } + + if( mcpu_macro_undef(&cpp->macros, + line + name_start, name_end - name_start) < 0 ) + return( -1 ); + + return( 0 ); +} + + +static int +command_line_text( const mcpp *cpp, const mcpp_option_action *action, + mcpu_text *text ) +{ + if( mcpu_text_from_utf8(text, action->argument) == 0 ) + return( 0 ); + + if( errno == EILSEQ ) + fprintf( stderr, + "%s: %s: invalid UTF-8 or non-UCS-2 character\n", + MCPP_OPTIONS(cpp)->progname, action->option ); + else + fprintf( stderr, "%s: %s: %s\n", + MCPP_OPTIONS(cpp)->progname, action->option, strerror(errno) ); + + return( -1 ); +} + + +static size_t +command_line_define_declarator_end( const mcpu_text *text, size_t equal ) +{ + size_t p; + + if( text == NULL || text->length == 0 || equal == 0 ) + return( 0 ); + + if( !mcpu_pp_is_identifier_start(text->data[0]) ) + return( equal ); + + p = 1; + while( p < equal && mcpu_pp_is_identifier_char(text->data[p]) ) + ++p; + + if( p < equal && text->data[p] == '(' ) + { + ++p; + while( p < equal && text->data[p] != ')' ) + ++p; + if( p < equal && text->data[p] == ')' ) + ++p; + } + + return( p ); +} + + +static int +prepare_command_line_define( mcpp *cpp, const mcpp_option_action *action, + const mcpu_text *text, mcpu_text *prepared ) +{ + mcpu_text line; + mcpu_pp_lexer_state lexer; + size_t equal; + size_t declarator_end; + int has_equal; + int rc = -1; + + mcpu_text_init( &line ); + + equal = 0; + has_equal = 0; + while( equal < text->length ) + { + if( text->data[equal] == '=' ) + { + has_equal = 1; + break; + } + ++equal; + } + + declarator_end = command_line_define_declarator_end( + text, has_equal ? equal : text->length ); + + if( mcpu_text_append(&line, text->data, declarator_end) != 0 ) + goto done; + + if( has_equal ) + { + if( mcpu_text_append_char(&line, ' ') != 0 || + mcpu_text_append(&line, text->data + equal + 1, + text->length - equal - 1) != 0 ) + goto done; + } + else + { + if( mcpu_text_append_ascii(&line, " 1") != 0 ) + goto done; + } + + mcpu_pp_lexer_init( &lexer ); + if( mcpu_pp_prepare_line(&lexer, line.data, line.length, + current_language(cpp), prepared) != 0 ) + goto done; + + if( lexer.in_block_comment ) + { + fprintf( stderr, "%s: %s: unterminated comment\n", + MCPP_OPTIONS(cpp)->progname, action->option ); + goto done; + } + + rc = 0; + +done: + mcpu_text_free( &line ); + return( rc ); +} + + +static int +prepare_command_line_undef( mcpp *cpp, const mcpp_option_action *action, + const mcpu_text *text, mcpu_text *prepared ) +{ + mcpu_text line; + mcpu_pp_lexer_state lexer; + size_t p; + int rc = -1; + + mcpu_text_init( &line ); + + if( text->length != 0 && mcpu_pp_is_identifier_start(text->data[0]) ) + { + p = 1; + while( p < text->length && mcpu_pp_is_identifier_char(text->data[p]) ) + ++p; + } + else + p = text->length; + + if( mcpu_text_append(&line, text->data, p) != 0 ) + goto done; + + mcpu_pp_lexer_init( &lexer ); + if( mcpu_pp_prepare_line(&lexer, line.data, line.length, + current_language(cpp), prepared) != 0 ) + goto done; + + if( lexer.in_block_comment ) + { + fprintf( stderr, "%s: %s: unterminated comment\n", + MCPP_OPTIONS(cpp)->progname, action->option ); + goto done; + } + + rc = 0; + +done: + mcpu_text_free( &line ); + return( rc ); +} + + +static int +apply_command_line_define( mcpp *cpp, const mcpp_option_action *action ) +{ + mcpu_text text; + mcpu_text prepared; + int rc; + + mcpu_text_init( &text ); + mcpu_text_init( &prepared ); + + if( command_line_text(cpp, action, &text) != 0 || + prepare_command_line_define(cpp, action, &text, &prepared) != 0 ) + goto fail; + + rc = handle_define( cpp, "<command-line>", 0, + prepared.data, prepared.length, 0, + MCPU_MACRO_ORIGIN_COMMAND_LINE ); + mcpu_text_free( &prepared ); + mcpu_text_free( &text ); + return( rc ); + +fail: + mcpu_text_free( &prepared ); + mcpu_text_free( &text ); + return( -1 ); +} + + +static int +apply_command_line_undef( mcpp *cpp, const mcpp_option_action *action ) +{ + mcpu_text text; + mcpu_text prepared; + int rc; + + mcpu_text_init( &text ); + mcpu_text_init( &prepared ); + + if( command_line_text(cpp, action, &text) != 0 || + prepare_command_line_undef(cpp, action, &text, &prepared) != 0 ) + { + mcpu_text_free( &prepared ); + mcpu_text_free( &text ); + return( -1 ); + } + + rc = handle_undef( cpp, "<command-line>", 0, + prepared.data, prepared.length, 0 ); + mcpu_text_free( &prepared ); + mcpu_text_free( &text ); + return( rc ); +} + + +static int +apply_command_line_macros( mcpp *cpp ) +{ + size_t i; + + if( cpp->command_line_macros_applied || MCPP_OPTIONS(cpp) == NULL ) + return( 0 ); + + for( i = 0; i < MCPP_OPTIONS(cpp)->action_count; ++i ) + { + const mcpp_option_action *action = &MCPP_OPTIONS(cpp)->actions[i]; + + switch( action->kind ) + { + case MCPP_ACTION_DEFINE: + if( apply_command_line_define(cpp, action) != 0 ) + return( -1 ); + break; + case MCPP_ACTION_UNDEF: + if( apply_command_line_undef(cpp, action) != 0 ) + return( -1 ); + break; + default: + break; + } + } + + cpp->command_line_macros_applied = 1; + return( 0 ); +} + + +enum mcpp_conditional_directive +{ + MCPP_COND_NONE = 0, + MCPP_COND_IF, + MCPP_COND_IFDEF, + MCPP_COND_IFNDEF, + MCPP_COND_ELIF, + MCPP_COND_ELSE, + MCPP_COND_ENDIF +}; + +static int +diagnostic_directive( const __mpu_char16_t *name, size_t length, + int *is_error, const char **directive_name ) +{ + if( mcpu_text_equal_ascii(name, length, "error") ) + { + *is_error = 1; + *directive_name = "error"; + return( 1 ); + } + + if( mcpu_text_equal_ascii(name, length, "warning") ) + { + *is_error = 0; + *directive_name = "warning"; + return( 1 ); + } + + return( 0 ); +} + +static int +append_diagnostic_text( mcpu_text *message, + const __mpu_char16_t *line, size_t length, + size_t p ) +{ + int pending_space = 0; + __mpu_char16_t quote = 0; + int escaped = 0; + + skip_space16( line, length, &p ); + + while( p < length && line[p] != '\n' ) + { + __mpu_char16_t c = line[p++]; + + if( quote ) + { + if( mcpu_text_append_char(message, c) != 0 ) + return( -1 ); + + if( escaped ) + escaped = 0; + else if( c == '\\' ) + escaped = 1; + else if( c == quote ) + quote = 0; + continue; + } + + if( c == ' ' || c == '\t' || c == '\f' || c == '\v' || c == '\r' ) + { + if( message->length != 0 ) + pending_space = 1; + continue; + } + + if( pending_space ) + { + if( mcpu_text_append_char(message, ' ') != 0 ) + return( -1 ); + pending_space = 0; + } + + if( c == '"' || c == '\'' || c == '`' ) + quote = c; + + if( mcpu_text_append_char(message, c) != 0 ) + return( -1 ); + } + + return( 0 ); +} + +static int +handle_diagnostic( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p, + int is_error, const char *directive_name ) +{ + mcpu_text message; + char *utf8; + + mcpu_text_init( &message ); + if( append_diagnostic_text(&message, line, length, p) != 0 ) + { + mcpu_text_free( &message ); + return( -1 ); + } + + utf8 = mcpu_text_to_utf8( message.data, message.length ); + mcpu_text_free( &message ); + if( utf8 == NULL ) + return( -1 ); + + if( is_error ) + { + fprintf( stderr, "%s:%u: error: #%s%s%s\n", + filename, line_number, directive_name, + *utf8 ? " " : "", utf8 ); + free( utf8 ); + return( -1 ); + } + + { + int rc = mcpp_diagnostic_warning(MCPP_OPTIONS(cpp), filename, line_number, + "#%s%s%s", directive_name, + *utf8 ? " " : "", utf8); + free( utf8 ); + return( rc ); + } +} + +static enum mcpp_conditional_directive +conditional_directive( const __mpu_char16_t *name, size_t length ) +{ + if( mcpu_text_equal_ascii(name, length, "if") ) + return( MCPP_COND_IF ); + + if( mcpu_text_equal_ascii(name, length, "ifdef") ) + return( MCPP_COND_IFDEF ); + + if( mcpu_text_equal_ascii(name, length, "ifndef") ) + return( MCPP_COND_IFNDEF ); + + if( mcpu_text_equal_ascii(name, length, "elif") ) + return( MCPP_COND_ELIF ); + + if( mcpu_text_equal_ascii(name, length, "else") ) + return( MCPP_COND_ELSE ); + + if( mcpu_text_equal_ascii(name, length, "endif") ) + return( MCPP_COND_ENDIF ); + + return( MCPP_COND_NONE ); +} + +static int +conditional_active( const mcpp *cpp ) +{ + if( cpp->conditional_depth == 0 ) + return( 1 ); + + return( cpp->conditional_stack[cpp->conditional_depth - 1].active ); +} + +static int +conditional_push( mcpp *cpp, const char *filename, unsigned line_number, + int parent_active, int condition ) +{ + mcpp_conditional *frame; + + if( cpp->conditional_depth >= MCPU_CPP_CONDITIONAL_STACK_SIZE ) + { + fprintf( stderr, "%s:%u: error: conditional nesting exceeds %d levels\n", + filename, line_number, MCPU_CPP_CONDITIONAL_STACK_SIZE ); + return( -1 ); + } + + frame = &cpp->conditional_stack[cpp->conditional_depth++]; + frame->filename = filename; + frame->line_number = line_number; + frame->parent_active = parent_active; + frame->active = parent_active && condition; + frame->branch_succeeded = frame->active; + frame->seen_else = 0; + + return( 0 ); +} + +static int +conditional_identifier( mcpp *cpp, const char *filename, + unsigned line_number, + const __mpu_char16_t *line, size_t length, + size_t p, int negate, int parent_active ) +{ + size_t start; + size_t end; + int defined; + + if( !parent_active ) + return( conditional_push(cpp, filename, line_number, 0, 0) ); + + skip_space16( line, length, &p ); + if( p >= length || !mcpu_pp_is_identifier_start(line[p]) ) + { + fprintf( stderr, "%s:%u: error: identifier expected after #%s\n", + filename, line_number, negate ? "ifndef" : "ifdef" ); + return( -1 ); + } + + start = p++; + while( p < length && mcpu_pp_is_identifier_char(line[p]) ) + ++p; + end = p; + + skip_space16( line, length, &p ); + if( p < length && line[p] != '\n' ) + { + fprintf( stderr, "%s:%u: error: extra tokens after #%s\n", + filename, line_number, negate ? "ifndef" : "ifdef" ); + return( -1 ); + } + + defined = mcpu_macro_find( &cpp->macros, line + start, end - start ) != NULL; + if( negate ) defined = !defined; + + return( conditional_push(cpp, filename, line_number, 1, defined) ); +} + +static int +conditional_if( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p, + int parent_active ) +{ + mcpu_macro_expansion_context context; + int value = 0; + + if( parent_active ) + { + macro_context( cpp, filename, line_number, &context ); + if( mcpp_eval_if_expression(&cpp->macros, line + p, length - p, + current_language(cpp), &context, + filename, line_number, &value) != 0 ) + return( -1 ); + } + + return( conditional_push(cpp, filename, line_number, + parent_active, value) ); +} + +static int +conditional_elif( mcpp *cpp, const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p, + size_t conditional_base ) +{ + mcpp_conditional *frame; + mcpu_macro_expansion_context context; + int value = 0; + + if( cpp->conditional_depth <= conditional_base ) + { + fprintf( stderr, "%s:%u: error: '#elif' not within a conditional\n", + filename, line_number ); + return( -1 ); + } + + frame = &cpp->conditional_stack[cpp->conditional_depth - 1]; + if( frame->seen_else ) + { + fprintf( stderr, "%s:%u: error: '#elif' after '#else'\n", + filename, line_number ); + return( -1 ); + } + + if( !frame->parent_active || frame->branch_succeeded ) + { + frame->active = 0; + return( 0 ); + } + + macro_context( cpp, filename, line_number, &context ); + if( mcpp_eval_if_expression(&cpp->macros, line + p, length - p, + current_language(cpp), &context, + filename, line_number, &value) != 0 ) + return( -1 ); + + frame->active = value != 0; + if( frame->active ) + frame->branch_succeeded = 1; + + return( 0 ); +} + +static int +conditional_else( mcpp *cpp, const char *filename, unsigned line_number, + size_t conditional_base ) +{ + mcpp_conditional *frame; + + if( cpp->conditional_depth <= conditional_base ) + { + fprintf( stderr, "%s:%u: error: '#else' not within a conditional\n", + filename, line_number ); + return( -1 ); + } + + frame = &cpp->conditional_stack[cpp->conditional_depth - 1]; + if( frame->seen_else ) + { + fprintf( stderr, "%s:%u: error: '#else' after '#else'\n", + filename, line_number ); + return( -1 ); + } + + frame->seen_else = 1; + frame->active = frame->parent_active && !frame->branch_succeeded; + if( frame->active ) + frame->branch_succeeded = 1; + + return( 0 ); +} + +static int +conditional_endif( mcpp *cpp, const char *filename, unsigned line_number, + size_t conditional_base ) +{ + if( cpp->conditional_depth <= conditional_base ) + { + fprintf( stderr, "%s:%u: error: unbalanced '#endif'\n", + filename, line_number ); + return( -1 ); + } + + --cpp->conditional_depth; + return( 0 ); +} + +static int +handle_conditional( mcpp *cpp, enum mcpp_conditional_directive directive, + const char *filename, unsigned line_number, + const __mpu_char16_t *line, size_t length, size_t p, + size_t conditional_base ) +{ + int parent_active = conditional_active( cpp ); + + switch( directive ) + { + case MCPP_COND_IF: + return( conditional_if(cpp, filename, line_number, + line, length, p, parent_active) ); + + case MCPP_COND_IFDEF: + return( conditional_identifier(cpp, filename, line_number, + line, length, p, 0, parent_active) ); + + case MCPP_COND_IFNDEF: + return( conditional_identifier(cpp, filename, line_number, + line, length, p, 1, parent_active) ); + + case MCPP_COND_ELIF: + return( conditional_elif(cpp, filename, line_number, + line, length, p, conditional_base) ); + + case MCPP_COND_ELSE: + return( conditional_else(cpp, filename, line_number, + conditional_base) ); + + case MCPP_COND_ENDIF: + return( conditional_endif(cpp, filename, line_number, + conditional_base) ); + + default: + return( 0 ); + } +} + +static int +parse_line_filename( const __mpu_char16_t *text, size_t length, + size_t *pos, char **filename ) +{ + mcpu_text decoded; + size_t p = *pos; + + *filename = NULL; + if( p >= length || text[p] != '"' ) + return( 0 ); + + ++p; + mcpu_text_init( &decoded ); + + while( p < length && text[p] != '\n' ) + { + __mpu_char16_t c = text[p++]; + + if( c == '"' ) + { + *filename = mcpu_text_to_utf8( decoded.data, decoded.length ); + mcpu_text_free( &decoded ); + if( *filename == NULL ) + return( -1 ); + *pos = p; + return( 1 ); + } + + if( c == '\\' ) + { + unsigned value; + int digit; + unsigned count; + + if( p >= length || text[p] == '\n' ) + goto bad; + + c = text[p++]; + switch( c ) + { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': c = '\\'; break; + case '\'': c = '\''; break; + case '"': c = '"'; break; + case '?': c = '?'; break; + + case 'x': + case 'X': + value = 0; + count = 0; + while( p < length ) + { + __mpu_char16_t h = text[p]; + + if( h >= '0' && h <= '9' ) digit = h - '0'; + else if( h >= 'a' && h <= 'f' ) digit = h - 'a' + 10; + else if( h >= 'A' && h <= 'F' ) digit = h - 'A' + 10; + else break; + + if( value > 0xffffU / 16U ) + goto bad; + value = value * 16U + (unsigned)digit; + if( value > 0xffffU ) + goto bad; + ++p; + ++count; + } + if( count == 0 || value == 0 || + (value >= 0xd800U && value <= 0xdfffU) ) + goto bad; + c = (__mpu_char16_t)value; + break; + + default: + if( c >= '0' && c <= '7' ) + { + value = c - '0'; + for( count = 1; count < 3 && p < length; ++count ) + { + c = text[p]; + if( c < '0' || c > '7' ) break; + value = value * 8U + (unsigned)(c - '0'); + ++p; + } + if( value == 0 || value > 0xffffU || + (value >= 0xd800U && value <= 0xdfffU) ) + goto bad; + c = (__mpu_char16_t)value; + } + break; + } + } + + if( mcpu_text_append_char(&decoded, c) != 0 ) + goto fail; + } + +bad: + errno = EINVAL; +fail: + mcpu_text_free( &decoded ); + return( -1 ); +} + +static int +handle_line( mcpp *cpp, const char *filename, unsigned line_number, + unsigned next_physical_line, + const __mpu_char16_t *line, size_t length, size_t p, + char **nominal_filename, int64_t *line_delta ) +{ + mcpu_macro_expansion_context context; + mcpu_text expanded; + const __mpu_char16_t *text; + size_t text_length; + size_t end; + uintmax_t value = 0; + char *new_filename = NULL; + int parsed_filename; + + macro_context( cpp, *nominal_filename, line_number, &context ); + mcpu_text_init( &expanded ); + + if( mcpu_macro_expand(&cpp->macros, line + p, length - p, + current_language(cpp), &context, + &expanded) != 0 ) + goto fail; + + text = expanded.data; + text_length = expanded.length; + end = line_content_end( text, text_length ); + p = 0; + skip_space16( text, end, &p ); + + if( p >= end || text[p] < '0' || text[p] > '9' ) + { + fprintf( stderr, "%s:%u: error: invalid #line directive\n", + filename, line_number ); + goto fail; + } + + while( p < end && text[p] >= '0' && text[p] <= '9' ) + { + unsigned digit = text[p++] - '0'; + + if( value > (UINT_MAX - digit) / 10U ) + { + fprintf( stderr, "%s:%u: error: line number out of range in #line directive\n", + filename, line_number ); + goto fail; + } + value = value * 10U + digit; + } + + skip_space16( text, end, &p ); + parsed_filename = parse_line_filename( text, end, &p, &new_filename ); + if( parsed_filename < 0 ) + { + fprintf( stderr, "%s:%u: error: invalid filename in #line directive\n", + filename, line_number ); + goto fail; + } + + skip_space16( text, end, &p ); + if( p != end ) + { + fprintf( stderr, "%s:%u: error: garbage at end of #line directive\n", + filename, line_number ); + goto fail; + } + + if( parsed_filename > 0 ) + { + free( *nominal_filename ); + *nominal_filename = new_filename; + new_filename = NULL; + } + + *line_delta = (int64_t)value - (int64_t)next_physical_line; + + if( append_line_marker(cpp, (unsigned)value, *nominal_filename, 0) != 0 ) + goto fail; + + mcpu_text_free( &expanded ); + return( 0 ); + +fail: + free( new_filename ); + mcpu_text_free( &expanded ); + return( -1 ); +} + +static int process_file( mcpp *cpp, const char *filename, + const mcpu_include_dir *include_dir, + int system_header ); + +static int +pragma_once_directive( const __mpu_char16_t *line, size_t length, size_t p ) +{ + size_t end = line_content_end( line, length ); + + skip_space16( line, end, &p ); + if( p + 4 > end || + !mcpu_text_equal_ascii(line + p, 4, "once") ) + return( 0 ); + + p += 4; + skip_space16( line, end, &p ); + return( p == end ); +} + +static int +handle_include( mcpp *cpp, const char *source_filename, + const char *nominal_filename, + unsigned line_number, unsigned resume_line_number, + const __mpu_char16_t *line, size_t length, size_t p, + int include_next, const mcpu_include_dir *current_dir, + int source_is_system ) +{ + char *requested = NULL; + char *found = NULL; + const mcpu_include_dir *found_dir = NULL; + int quoted; + int rc = -1; + mcpu_macro_expansion_context context; + + macro_context( cpp, nominal_filename, line_number, &context ); + + if( parse_include_filename( line, length, p, &requested, "ed ) != 0 ) + { + mcpu_text expanded; + + mcpu_text_init( &expanded ); + if( mcpu_macro_expand(&cpp->macros, line + p, length - p, + current_language(cpp), &context, + &expanded) != 0 || + parse_include_filename(expanded.data, expanded.length, 0, + &requested, "ed) != 0 ) + { + fprintf( stderr, + "%s:%u: error: #%s does not expand to \"file\" or <file>\n", + nominal_filename, line_number, + include_next ? "include_next" : "include" ); + mcpu_text_free( &expanded ); + goto done; + } + mcpu_text_free( &expanded ); + } + + found = mcpu_include_find( &cpp->include_paths, + source_filename, + requested, + quoted, + current_language(cpp), + include_next, + include_next ? current_dir : NULL, + &found_dir ); + if( found == NULL ) + { + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->print_deps_missing_files ) + { + int dependency_is_system = source_is_system || !quoted; + + if( dependency_add_unresolved(cpp, requested, + dependency_is_system) != 0 ) + goto done; + + if( cpp->verbose ) + fprintf( stderr, "%s:%u: %s %s -> <generated> [%s]\n", + nominal_filename, line_number, + include_next ? "include_next" : "include", + requested, mcpu_language_name(current_language(cpp)) ); + + rc = 0; + goto done; + } + + fprintf( stderr, "%s:%u: error: cannot find %s file '%s'\n", + nominal_filename, line_number, + include_next ? "include_next" : "include", requested ); + goto done; + } + + if( cpp->verbose ) + fprintf( stderr, "%s:%u: %s %s -> %s [%s]\n", + nominal_filename, line_number, + include_next ? "include_next" : "include", + requested, found, mcpu_language_name(current_language(cpp)) ); + + { + int dependency_is_system = source_is_system || include_dir_is_system(found_dir); + + if( dependency_add(cpp, found, dependency_is_system) != 0 ) + goto done; + + if( once_file_seen(cpp, found) ) + { + rc = 0; + goto done; + } + + if( sync_output_position(cpp, line_number, nominal_filename) != 0 || + append_line_marker(cpp, 1, found, 1) != 0 ) + goto done; + if( process_file( cpp, found, found_dir, dependency_is_system ) != 0 ) + goto done; + } + if( append_line_marker( cpp, resume_line_number, nominal_filename, 2 ) != 0 ) + goto done; + + rc = 0; + +done: + free( requested ); + free( found ); + return( rc ); +} + +static int +process_source( mcpp *cpp, mcpu_source *source, + const mcpu_include_dir *include_dir, + int source_is_system ) +{ + mcpu_pp_lexer_state lexer; + mcpu_text prepared; + mcpu_text expanded; + const __mpu_char16_t *line; + size_t length; + unsigned line_number; + unsigned next_line_number; + int spliced; + size_t conditional_base; + int status; + mcpu_macro_expansion_context context; + const char *filename = source->filename; + char *nominal_filename; + int64_t line_delta = 0; + + if( cpp->include_depth >= MCPU_CPP_INCLUDE_STACK_SIZE ) + { + fprintf( stderr, "%s: error: include nesting exceeds %d files\n", + filename, MCPU_CPP_INCLUDE_STACK_SIZE ); + return( -1 ); + } + + nominal_filename = strdup( filename ); + if( nominal_filename == NULL ) + return( -1 ); + + ++cpp->include_depth; + conditional_base = cpp->conditional_depth; + mcpu_pp_lexer_init( &lexer ); + mcpu_text_init( &prepared ); + mcpu_text_init( &expanded ); + + while( (status = mcpu_source_next_line( source, &line, &length, + &line_number, + &next_line_number, &spliced )) > 0 ) + { + int64_t reported = (int64_t)line_number + line_delta; + int64_t reported_next = (int64_t)next_line_number + line_delta; + unsigned logical_line; + unsigned logical_next_line; + size_t hash = 0; + int directive; + + if( reported < 0 || reported > UINT_MAX || + reported_next < 0 || reported_next > UINT_MAX ) + { + fprintf( stderr, "%s:%u: error: logical line number out of range\n", + filename, line_number ); + goto fail; + } + + logical_line = (unsigned)reported; + logical_next_line = (unsigned)reported_next; + + mcpu_pp_lexer_set_diagnostics(&lexer, MCPP_OPTIONS(cpp), + nominal_filename, logical_line, + source->splice_offsets, + source->splice_count); + + if( mcpu_pp_prepare_line(&lexer, line, length, + current_language(cpp), &prepared) != 0 ) + goto fail; + + line = prepared.data; + length = prepared.length; + + if( !mcpu_source_valid_ucs2(line, length) ) + { + fprintf( stderr, "%s:%u: error: character outside UCS-2\n", + nominal_filename, logical_line ); + goto fail; + } + + directive = mcpu_pp_find_directive( line, length, &hash ); + if( directive < 0 ) + goto fail; + + if( directive ) + { + size_t name_start; + size_t name_length; + size_t after_name; + + if( parse_directive_name( line, length, hash, + &name_start, &name_length, + &after_name ) ) + { + enum mcpp_conditional_directive conditional; + + conditional = conditional_directive( line + name_start, name_length ); + if( conditional != MCPP_COND_NONE ) + { + if( handle_conditional(cpp, conditional, source->filename, + line_number, line, length, after_name, + conditional_base) != 0 ) + goto fail; + continue; + } + + if( !conditional_active(cpp) ) + continue; + + { + int diagnostic_error; + const char *diagnostic_name; + + if( diagnostic_directive(line + name_start, name_length, + &diagnostic_error, &diagnostic_name) ) + { + if( handle_diagnostic(cpp, nominal_filename, logical_line, + line, length, after_name, + diagnostic_error, diagnostic_name) != 0 ) + goto fail; + + continue; + } + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "line") ) + { + if( handle_line( cpp, nominal_filename, logical_line, + next_line_number, line, length, after_name, + &nominal_filename, &line_delta ) != 0 ) + goto fail; + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "pragma") ) + { + if( pragma_once_directive(line, length, after_name) ) + { + if( once_file_mark(cpp, source->filename) != 0 ) + { + fprintf( stderr, + "%s:%u: error: cannot record #pragma once: %s\n", + nominal_filename, logical_line, strerror(errno) ); + goto fail; + } + + continue; + } + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "include") || + mcpu_text_equal_ascii(line + name_start, name_length, + "include_next") ) + { + int include_next = + mcpu_text_equal_ascii( line + name_start, name_length, + "include_next" ); + + if( handle_include( cpp, source->filename, nominal_filename, + logical_line, logical_next_line, + line, length, after_name, include_next, + include_dir, source_is_system ) != 0 ) + goto fail; + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "define") ) + { + if( handle_define( cpp, nominal_filename, logical_line, + line, length, after_name, + MCPU_MACRO_ORIGIN_SOURCE ) != 0 ) + goto fail; + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_macros == MCPP_DUMP_DEFINITIONS && + append_visible_text(cpp, logical_line, nominal_filename, + line, length) != 0 ) + goto fail; + + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "undef") ) + { + if( handle_undef( cpp, nominal_filename, logical_line, + line, length, after_name ) != 0 ) + goto fail; + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "lang") ) + { + { + size_t output_start; + + if( sync_output_position(cpp, logical_line, nominal_filename) != 0 ) + goto fail; + output_start = cpp->output.length; + if( handle_lang(cpp, nominal_filename, logical_line, + line, length, after_name, spliced) != 0 ) + goto fail; + advance_output_position( cpp, cpp->output.data + output_start, + cpp->output.length - output_start ); + } + continue; + } + + if( mcpu_text_equal_ascii( line + name_start, + name_length, "endlang" ) ) + { + { + size_t output_start; + + if( sync_output_position(cpp, logical_line, nominal_filename) != 0 ) + goto fail; + output_start = cpp->output.length; + if( handle_endlang(cpp, nominal_filename, logical_line, + line, length) != 0 ) + goto fail; + advance_output_position( cpp, cpp->output.data + output_start, + cpp->output.length - output_start ); + } + continue; + } + } + } + + if( !conditional_active(cpp) ) + continue; + + macro_context( cpp, nominal_filename, logical_line, &context ); + if( mcpu_macro_expand(&cpp->macros, line, length, + current_language(cpp), &context, + &expanded) != 0 || + append_visible_text(cpp, logical_line, nominal_filename, + expanded.data, expanded.length) != 0 ) + goto fail; + } + + if( status < 0 ) + goto fail; + + if( lexer.in_block_comment ) + { + fprintf( stderr, "%s: error: unterminated comment at end of file\n", + nominal_filename ); + goto fail; + } + + if( cpp->conditional_depth != conditional_base ) + { + mcpp_conditional *frame = + &cpp->conditional_stack[cpp->conditional_depth - 1]; + + fprintf( stderr, "%s:%u: error: unterminated conditional directive\n", + frame->filename, frame->line_number ); + goto fail; + } + + --cpp->include_depth; + mcpu_text_free( &prepared ); + mcpu_text_free( &expanded ); + free( nominal_filename ); + return( 0 ); + +fail: + cpp->conditional_depth = conditional_base; + --cpp->include_depth; + mcpu_text_free( &prepared ); + mcpu_text_free( &expanded ); + free( nominal_filename ); + return( -1 ); +} + +static int +process_file( mcpp *cpp, const char *filename, + const mcpu_include_dir *include_dir, + int system_header ) +{ + mcpu_source source; + int rc; + + mcpu_source_init( &source ); + if( mcpu_source_open( &source, filename ) != 0 ) + { + fprintf( stderr, "%s: %s\n", filename, strerror(errno) ); + mcpu_source_free( &source ); + return( -1 ); + } + + rc = process_source( cpp, &source, include_dir, system_header ); + mcpu_source_free( &source ); + return( rc ); +} + +static int +process_stream( mcpp *cpp, FILE *stream, const char *name ) +{ + mcpu_source source; + int rc; + + mcpu_source_init( &source ); + if( mcpu_source_open_stream( &source, stream, name ) != 0 ) + { + fprintf( stderr, "%s: %s\n", name, strerror(errno) ); + mcpu_source_free( &source ); + return( -1 ); + } + + rc = process_source( cpp, &source, NULL, 0 ); + mcpu_source_free( &source ); + return( rc ); +} + +void +mcpp_init( mcpp *cpp ) +{ + if( cpp == NULL ) + return; + + cpp->options_data = NULL; + mcpu_config_init( &cpp->config ); + mcpp_runtime_paths_init( &cpp->runtime_paths ); + mcpu_include_paths_init( &cpp->include_paths ); + cpp->lang_depth = 1; + cpp->lang_stack[0] = MCPU_LANG_0; + cpp->include_depth = 0; + cpp->conditional_depth = 0; + cpp->base_filename = NULL; + cpp->preprocess_date[0] = 0; + cpp->preprocess_time[0] = 0; + cpp->builtins_initialized = 0; + cpp->command_line_macros_applied = 0; + mcpu_macro_table_init( &cpp->macros ); + cpp->once_files = NULL; + cpp->dependencies_first = NULL; + cpp->dependencies_last = NULL; + cpp->verbose = 0; + cpp->output_filename = NULL; + cpp->output_line = 0; + mcpu_text_init( &cpp->output ); +} + +void +mcpp_free( mcpp *cpp ) +{ + if( cpp == NULL ) + return; + + mcpu_config_free( &cpp->config ); + mcpp_runtime_paths_free( &cpp->runtime_paths ); + mcpu_include_paths_free( &cpp->include_paths ); + free( cpp->base_filename ); + cpp->base_filename = NULL; + mcpu_macro_table_free( &cpp->macros ); + once_files_free( cpp ); + dependencies_free( cpp ); + free( cpp->output_filename ); + cpp->output_filename = NULL; + cpp->output_line = 0; + mcpu_text_free( &cpp->output ); +} + +int +mcpp_load_config( mcpp *cpp, const char *explicit_filename, + int no_config ) +{ + const char *home; + + if( cpp == NULL ) + return( -1 ); + + /* + * The physical MCPU runtime root is not a configuration-file value. The + * sibling <root>/include directory is the lowest-priority default and + * therefore remains active even when --no-config suppresses all + * configuration-file reads. A subsequently read configuration file may + * replace it, including with an empty value that disables the configured + * system include tree. + */ + if( mcpu_config_set( &cpp->config, + "MCPU_CPP_SYSTEM_INCLUDE_PATH", + cpp->runtime_paths.system_include_path ) != 0 ) + return( -1 ); + + if( no_config ) + return( 0 ); + + if( explicit_filename ) + { + if( mcpu_config_read( &cpp->config, explicit_filename, 1 ) != 0 ) + { + fprintf( stderr, "%s: cannot read configuration: %s\n", + explicit_filename, strerror(errno) ); + return( -1 ); + } + } + else + { + char *user = NULL; + + if( cpp->runtime_paths.config_file == NULL || + mcpu_config_read( &cpp->config, + cpp->runtime_paths.config_file, 0 ) != 0 ) + return( -1 ); + + if( mcpu_config_read( &cpp->config, + MCPU_CPP_SYSTEM_CONFIG_FILE, 0 ) != 0 ) + return( -1 ); + + home = getenv( "HOME" ); + if( home && *home ) + { + size_t n = strlen(home) + sizeof("/.mcpu/mcpu-cpp.conf"); + user = (char *)malloc( n ); + if( user == NULL ) + return( -1 ); + snprintf( user, n, "%s/.mcpu/mcpu-cpp.conf", home ); + if( mcpu_config_read( &cpp->config, user, 0 ) != 0 ) + { + free( user ); + return( -1 ); + } + free( user ); + } + } + + return( 0 ); +} + + +int +mcpp_prepare( mcpp *cpp ) +{ + if( cpp == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( cpp->preprocess_date[0] == 0 && initialize_timestamp(cpp) != 0 ) + return( -1 ); + + if( initialize_builtins(cpp) != 0 ) + return( -1 ); + + return( apply_command_line_macros(cpp) ); +} + + +static void +restore_output_length( mcpp *cpp, size_t length ) +{ + if( cpp == NULL ) + return; + + cpp->output.length = length; + if( cpp->output.data != NULL ) + cpp->output.data[length] = 0; +} + + +static int +process_forced_file( mcpp *cpp, const mcpp_option_action *action, + int discard_output ) +{ + const mcpu_include_dir *found_dir = NULL; + char *found; + char *saved_output_filename = NULL; + size_t output_length; + unsigned saved_output_line = 0; + int system_header; + int rc = -1; + + if( cpp == NULL || action == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + /* + * GNU-style command-line forced files are resolved relative to the current + * working directory first. Passing "." as the synthetic containing source + * gives mcpu_include_find() exactly that quoted-include first step, followed + * by the ordinary configured include chain. The primary input directory is + * deliberately not involved. + */ + found = mcpu_include_find( &cpp->include_paths, ".", action->argument, 1, + current_language(cpp), 0, NULL, &found_dir ); + if( found == NULL ) + { + const char *progname = MCPP_OPTIONS(cpp) != NULL ? + MCPP_OPTIONS(cpp)->progname : "mcpu-cpp"; + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->print_deps_missing_files ) + { + /* + * GNU -MG also tolerates missing command-line -include/-imacros files. + * With no successful search there is no physical search provenance, so + * the command-line operand enters the unresolved registry as a user + * dependency exactly as written. + */ + if( dependency_add_unresolved(cpp, action->argument, 0) != 0 ) + return( -1 ); + + if( cpp->verbose ) + fprintf( stderr, "%s %s -> <generated> [%s]\n", + action->option, action->argument, + mcpu_language_name(current_language(cpp)) ); + return( 0 ); + } + + fprintf( stderr, "%s: error: %s cannot find file '%s'\n", + progname, action->option, action->argument ); + return( -1 ); + } + + system_header = include_dir_is_system( found_dir ); + if( dependency_add(cpp, found, system_header) != 0 ) + goto done; + + if( once_file_seen(cpp, found) ) + { + rc = 0; + goto done; + } + + output_length = cpp->output.length; + if( discard_output && cpp->output_filename != NULL ) + { + saved_output_filename = strdup( cpp->output_filename ); + if( saved_output_filename == NULL ) + goto done; + saved_output_line = cpp->output_line; + } + + if( cpp->verbose ) + fprintf( stderr, "%s %s -> %s [%s]\n", + action->option, action->argument, found, + mcpu_language_name(current_language(cpp)) ); + + if( append_line_marker(cpp, 1, found, 1) != 0 ) + goto forced_fail; + + /* + * A forced file is conceptually included by the primary source before its + * first line. Reserve the primary-source frame so __INCLUDE_LEVEL__ is 1 + * in the forced file and increases normally in headers included from it. + */ + if( cpp->include_depth >= MCPU_CPP_INCLUDE_STACK_SIZE ) + { + fprintf( stderr, "%s: error: include nesting exceeds %d files\n", + found, MCPU_CPP_INCLUDE_STACK_SIZE ); + goto forced_fail; + } + + ++cpp->include_depth; + rc = process_file( cpp, found, found_dir, system_header ); + --cpp->include_depth; + if( rc != 0 ) + goto forced_fail; + + if( discard_output ) + { + restore_output_length( cpp, output_length ); + if( saved_output_filename != NULL && + set_output_position(cpp, saved_output_line, saved_output_filename) != 0 ) + goto done; + } + else if( append_line_marker(cpp, 1, cpp->base_filename, 2) != 0 ) + { + rc = -1; + goto done; + } + + rc = 0; + goto done; + +forced_fail: + if( discard_output ) + { + restore_output_length( cpp, output_length ); + if( saved_output_filename != NULL ) + (void)set_output_position(cpp, saved_output_line, saved_output_filename); + } + rc = -1; + +done: + free( saved_output_filename ); + free( found ); + return( rc ); +} + + +static int +process_forced_files( mcpp *cpp ) +{ + size_t i; + int pass; + + if( cpp == NULL || MCPP_OPTIONS(cpp) == NULL ) + return( 0 ); + + /* + * Command-line -D/-U actions have already been applied by mcpp_prepare(). + * Process every -imacros file before every -include file while preserving + * command-line order inside each class. + */ + for( pass = 0; pass < 2; ++pass ) + { + enum mcpp_option_action_kind kind = + pass == 0 ? MCPP_ACTION_IMACROS : MCPP_ACTION_INCLUDE; + + for( i = 0; i < MCPP_OPTIONS(cpp)->action_count; ++i ) + { + const mcpp_option_action *action = &MCPP_OPTIONS(cpp)->actions[i]; + + if( action->kind != kind ) + continue; + + if( process_forced_file(cpp, action, kind == MCPP_ACTION_IMACROS) != 0 ) + return( -1 ); + } + } + + return( 0 ); +} + + +int +mcpp_dump_macros( mcpp *cpp, FILE *stream ) +{ + if( cpp == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( mcpp_prepare(cpp) != 0 ) + return( -1 ); + + return( mcpu_macro_dump(&cpp->macros, stream, + MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_only_plus_predefined) ); +} + + +int +mcpp_process( mcpp *cpp, const char *filename ) +{ + if( cpp == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + free( cpp->base_filename ); + cpp->base_filename = strdup( filename ); + if( cpp->base_filename == NULL || mcpp_prepare(cpp) != 0 ) + return( -1 ); + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_macros == MCPP_DUMP_DEFINITIONS ) + { + const char *marker = "<built-in>"; + + if( append_line_marker(cpp, 0, filename, 0) != 0 || + mcpu_macro_dump_text(&cpp->macros, &cpp->output, marker) != 0 ) + return( -1 ); + } + + if( dependency_add(cpp, filename, 0) != 0 ) + return( -1 ); + + if( append_line_marker(cpp, 1, filename, 0) != 0 ) + return( -1 ); + + if( process_forced_files(cpp) != 0 ) + return( -1 ); + + if( process_file( cpp, filename, NULL, 0 ) != 0 ) + return( -1 ); + + if( cpp->lang_depth != 1 ) + { + fprintf( stderr, + "%s: error: unbalanced #lang/#endlang at end of translation unit (depth %lu)\n", + filename, (unsigned long)cpp->lang_depth ); + return( -1 ); + } + + return( 0 ); +} + + +int +mcpp_process_stream( mcpp *cpp, FILE *stream, const char *name ) +{ + if( cpp == NULL || stream == NULL || name == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + free( cpp->base_filename ); + cpp->base_filename = strdup( name ); + if( cpp->base_filename == NULL || mcpp_prepare(cpp) != 0 ) + return( -1 ); + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_macros == MCPP_DUMP_DEFINITIONS ) + { + const char *marker = "<built-in>"; + + if( append_line_marker(cpp, 0, name, 0) != 0 || + mcpu_macro_dump_text(&cpp->macros, &cpp->output, marker) != 0 ) + return( -1 ); + } + + if( dependency_add(cpp, strcmp(name, "<stdin>") == 0 ? "-" : name, 0) != 0 ) + return( -1 ); + + if( append_line_marker(cpp, 1, name, 0) != 0 ) + return( -1 ); + + if( process_forced_files(cpp) != 0 ) + return( -1 ); + + if( process_stream(cpp, stream, name) != 0 ) + return( -1 ); + + if( cpp->lang_depth != 1 ) + { + fprintf( stderr, + "%s: error: unbalanced #lang/#endlang at end of translation unit (depth %lu)\n", + name, (unsigned long)cpp->lang_depth ); + return( -1 ); + } + + return( 0 ); +} + +static int +write_make_escaped( FILE *stream, const char *text ) +{ + const unsigned char *p = (const unsigned char *)text; + + while( *p ) + { + if( *p == '$' ) + { + if( fputs("$$", stream) == EOF ) + return( -1 ); + } + else + { + if( (*p == ' ' || *p == '\t' || *p == '#' || *p == '\\') && + fputc('\\', stream) == EOF ) + return( -1 ); + if( fputc(*p, stream) == EOF ) + return( -1 ); + } + ++p; + } + + return( 0 ); +} + + +static int +write_make_target_quoted( FILE *stream, const char *text ) +{ + const unsigned char *p = (const unsigned char *)text; + size_t slashes = 0; + + while( *p ) + { + unsigned char c = *p++; + + if( c == '\\' ) + { + if( fputc(c, stream) == EOF ) + return( -1 ); + ++slashes; + continue; + } + + if( c == ' ' || c == '\t' ) + { + size_t i; + + for( i = 0; i < slashes; ++i ) + if( fputc('\\', stream) == EOF ) + return( -1 ); + if( fputc('\\', stream) == EOF ) + return( -1 ); + } + else if( c == '#' ) + { + if( fputc('\\', stream) == EOF ) + return( -1 ); + } + else if( c == '$' ) + { + if( fputc('$', stream) == EOF ) + return( -1 ); + } + + slashes = 0; + if( fputc(c, stream) == EOF ) + return( -1 ); + } + + return( 0 ); +} + + +static char * +default_dependency_target( const mcpp *cpp ) +{ + const char *base; + const char *dot; + const char *suffix = ".o"; + size_t stem_length; + size_t suffix_length; + char *target; + + if( cpp == NULL || cpp->base_filename == NULL ) + return( NULL ); + + if( strcmp(cpp->base_filename, "<stdin>") == 0 || + strcmp(cpp->base_filename, "-") == 0 ) + return( strdup("-") ); + + base = strrchr( cpp->base_filename, '/' ); + base = base == NULL ? cpp->base_filename : base + 1; + dot = strrchr( base, '.' ); + stem_length = dot != NULL && dot != base ? (size_t)(dot - base) : strlen(base); + + if( MCPP_OPTIONS(cpp) != NULL && MCPP_OPTIONS(cpp)->object_suffix != NULL ) + suffix = MCPP_OPTIONS(cpp)->object_suffix; + suffix_length = strlen( suffix ); + + target = (char *)malloc( stem_length + suffix_length + 1 ); + if( target == NULL ) + return( NULL ); + + memcpy( target, base, stem_length ); + memcpy( target + stem_length, suffix, suffix_length + 1 ); + return( target ); +} + + +static int +write_dependency_targets( mcpp *cpp, FILE *stream ) +{ + char *target; + size_t i; + int pass; + int written = 0; + + if( cpp == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( MCPP_OPTIONS(cpp) != NULL ) + for( pass = 0; pass < 2; ++pass ) + { + enum mcpp_option_action_kind kind = pass == 0 ? + MCPP_ACTION_DEP_TARGET : MCPP_ACTION_DEP_TARGET_QUOTED; + + for( i = 0; i < MCPP_OPTIONS(cpp)->action_count; ++i ) + { + const mcpp_option_action *action = &MCPP_OPTIONS(cpp)->actions[i]; + + if( action->kind != kind ) + continue; + + if( written && fputc(' ', stream) == EOF ) + return( -1 ); + + if( kind == MCPP_ACTION_DEP_TARGET ) + { + if( fputs(action->argument, stream) == EOF ) + return( -1 ); + } + else if( write_make_target_quoted(stream, action->argument) != 0 ) + return( -1 ); + + written = 1; + } + } + + if( written ) + return( 0 ); + + target = default_dependency_target( cpp ); + if( target == NULL ) + return( -1 ); + + if( write_make_target_quoted(stream, target) != 0 ) + { + free( target ); + return( -1 ); + } + + free( target ); + return( 0 ); +} + + +int +mcpp_write_dependencies( mcpp *cpp, FILE *stream, int include_system ) +{ + mcpp_dependency *p; + + if( cpp == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( write_dependency_targets(cpp, stream) != 0 || fputs(":", stream) == EOF ) + return( -1 ); + + for( p = cpp->dependencies_first; p != NULL; p = p->next ) + { + if( !include_system && p->system_only ) + continue; + + if( fputc(' ', stream) == EOF || write_make_escaped(stream, p->path) != 0 ) + return( -1 ); + } + + if( fputc('\n', stream) == EOF ) + return( -1 ); + + return( ferror(stream) ? -1 : 0 ); +} + + +int +mcpp_write_output( mcpp *cpp, const char *filename ) +{ + char *utf8; + FILE *fp = stdout; + int close_fp = 0; + size_t n; + + if( cpp == NULL ) + return( -1 ); + + utf8 = mcpu_text_to_utf8( cpp->output.data, cpp->output.length ); + if( utf8 == NULL ) + return( -1 ); + + if( filename ) + { + fp = fopen( filename, "wb" ); + if( fp == NULL ) + { + free( utf8 ); + return( -1 ); + } + close_fp = 1; + } + + n = strlen( utf8 ); + if( fwrite( utf8, 1, n, fp ) != n ) + { + if( close_fp ) + fclose( fp ); + free( utf8 ); + return( -1 ); + } + + if( close_fp && fclose(fp) != 0 ) + { + free( utf8 ); + return( -1 ); + } + + free( utf8 ); + return( 0 ); +} |
