summaryrefslogtreecommitdiff
path: root/src/mcpp-lib.c
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-01 12:02:28 +0300
committerkx <kx@radix-linux.su>2026-10-01 12:02:28 +0300
commitba1b04d64bdfaf377915b22f77520216ebe47674 (patch)
tree044343035c28b2b04ca9725f26e9b7c39342d19c /src/mcpp-lib.c
parent124b140798456778e2a96851cc7268aa9a726698 (diff)
downloadmcpu-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.c3443
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, &quoted ) != 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, &quoted) != 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 );
+}