summaryrefslogtreecommitdiff
path: root/src
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
parent124b140798456778e2a96851cc7268aa9a726698 (diff)
downloadmcpu-cpp-1.0.2.tar.xz
Version 1.0.21.0.2
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am64
-rw-r--r--src/defs.h42
-rw-r--r--src/main.c412
-rw-r--r--src/mcpp-config-file.c512
-rw-r--r--src/mcpp-config-file.h30
-rw-r--r--src/mcpp-diagnostic.c28
-rw-r--r--src/mcpp-diagnostic.h11
-rw-r--r--src/mcpp-expr.h10
-rw-r--r--src/mcpp-expr.zubr723
-rw-r--r--src/mcpp-expression.c178
-rw-r--r--src/mcpp-expression.h15
-rw-r--r--src/mcpp-include-path.c467
-rw-r--r--src/mcpp-include-path.h61
-rw-r--r--src/mcpp-language.c108
-rw-r--r--src/mcpp-language.h24
-rw-r--r--src/mcpp-lexer.c371
-rw-r--r--src/mcpp-lexer.h38
-rw-r--r--src/mcpp-lib.c3443
-rw-r--r--src/mcpp-lib.h65
-rw-r--r--src/mcpp-macro.c2878
-rw-r--r--src/mcpp-macro.h126
-rw-r--r--src/mcpp-options.c365
-rw-r--r--src/mcpp-options.h74
-rw-r--r--src/mcpp-predefined.c627
-rw-r--r--src/mcpp-predefined.h12
-rw-r--r--src/mcpp-runtime.c268
-rw-r--r--src/mcpp-runtime.h19
-rw-r--r--src/mcpp-semantic.c349
-rw-r--r--src/mcpp-semantic.h67
-rw-r--r--src/mcpp-source.c332
-rw-r--r--src/mcpp-source.h35
-rw-r--r--src/mcpp-text.c237
-rw-r--r--src/mcpp-text.h27
33 files changed, 12018 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..b5e2ea2
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,64 @@
+
+mcpuprivatebindir = @MCPU_CPP_BINDIR@
+mcpuprivatebin_PROGRAMS = mcpu-cpp
+
+mcpu_cpp_SOURCES = \
+ main.c \
+ defs.h \
+ mcpp-options.c \
+ mcpp-options.h \
+ mcpp-diagnostic.c \
+ mcpp-diagnostic.h \
+ mcpp-text.c \
+ mcpp-text.h \
+ mcpp-config-file.c \
+ mcpp-config-file.h \
+ mcpp-runtime.c \
+ mcpp-runtime.h \
+ mcpp-language.c \
+ mcpp-language.h \
+ mcpp-include-path.c \
+ mcpp-include-path.h \
+ mcpp-source.c \
+ mcpp-source.h \
+ mcpp-lexer.c \
+ mcpp-lexer.h \
+ mcpp-macro.c \
+ mcpp-macro.h \
+ mcpp-semantic.c \
+ mcpp-semantic.h \
+ mcpp-expr.c \
+ mcpp-expr.h \
+ mcpp-expression.c \
+ mcpp-expression.h \
+ mcpp-predefined.c \
+ mcpp-predefined.h \
+ mcpp-lib.c \
+ mcpp-lib.h
+
+mcpu_cpp_CFLAGS = $(LIBMPUIO_CFLAGS)
+mcpu_cpp_LDFLAGS = $(LIBMPUIO_LDFLAGS)
+mcpu_cpp_LDADD = $(LIBMPUIO_LIBS)
+
+EXTRA_DIST = mcpp-expr.zubr
+
+ZUBR = zubr
+SUFFIXES = .zubr .c
+
+.zubr.c:
+ $(ZUBR) -vl -s -Bmcpp_ -o $@ $<
+
+CLEANFILES = z.output
+
+
+install-exec-hook:
+ $(MKDIR_P) "$(DESTDIR)$(bindir)"
+ rm -f "$(DESTDIR)$(bindir)/mcpu-cpp"
+ $(LN_S) "@MCPU_CPP_PUBLIC_LINK_TARGET@" "$(DESTDIR)$(bindir)/mcpu-cpp"
+
+uninstall-hook:
+ rm -f "$(DESTDIR)$(bindir)/mcpu-cpp"
+
+distclean-local:
+ -rm -rf $(DEPDIR)
+
diff --git a/src/defs.h b/src/defs.h
new file mode 100644
index 0000000..2b22f15
--- /dev/null
+++ b/src/defs.h
@@ -0,0 +1,42 @@
+#ifndef __MCPU_CPP_DEFS_H__
+#define __MCPU_CPP_DEFS_H__ 1
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <libmpuio.h>
+
+#define MCPU_CPP_LANG_STACK_SIZE 400
+#define MCPU_CPP_INCLUDE_STACK_SIZE 400
+#define MCPU_CPP_CONDITIONAL_STACK_SIZE 400
+
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#include <mcpp-text.h>
+#include <mcpp-config-file.h>
+#include <mcpp-runtime.h>
+#include <mcpp-language.h>
+#include <mcpp-include-path.h>
+#include <mcpp-lexer.h>
+#include <mcpp-macro.h>
+#include <mcpp-semantic.h>
+#include <mcpp-expression.h>
+#include <mcpp-predefined.h>
+#include <mcpp-source.h>
+#include <mcpp-options.h>
+#include <mcpp-diagnostic.h>
+#include <mcpp-lib.h>
+
+#endif /* __MCPU_CPP_DEFS_H__ */
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..0882904
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,412 @@
+#include <defs.h>
+
+static void
+welcome( mcpp_options *opts )
+{
+ if( opts == NULL )
+ return;
+
+ printf( "mcpu-cpp %s\n", PACKAGE_VERSION );
+ printf( "MCPU languages preprocessor\n" );
+}
+
+static void
+usage( mcpp_options *opts )
+{
+ printf( "Usage: %s [options] [input [output]]\n\n", opts->progname );
+ printf( "Options:\n" );
+ printf( " -o FILE write output to FILE\n" );
+ printf( " -D NAME[=VALUE] define a command-line macro\n" );
+ printf( " -U NAME undefine a command-line macro\n" );
+ printf( " -imacros FILE preprocess FILE for macro state; discard its output\n" );
+ printf( " -include FILE preprocess FILE before the primary input\n" );
+ printf( " -I DIR, -IDIR add user include directory\n" );
+ printf( " -isystem DIR add explicit system include directory\n" );
+ printf( " -idirafter DIR add include directory searched last\n" );
+ printf( " -nostdinc suppress effective standard-system include tree\n" );
+ printf( " -dM dump non-predefined macros after preprocessing\n" );
+ printf( " -dMP dump predefined macros first, then other macros\n" );
+ printf( " -dD preserve #define directives in normal output\n" );
+ printf( " -dconfig dump effective mcpu-cpp configuration\n" );
+ printf( " -dsearch-dirs dump effective include search directories\n" );
+ printf( " -M output make dependencies including system headers\n" );
+ printf( " -MM output make dependencies excluding system headers\n" );
+ printf( " -MG treat missing headers as generated dependencies\n" );
+ printf( " -MD write dependencies and keep preprocessing output\n" );
+ printf( " -MMD like -MD but exclude system headers\n" );
+ printf( " -MF FILE write dependencies to FILE ('-' means stdout)\n" );
+ printf( " -MT TARGET set unquoted make dependency target\n" );
+ printf( " -MQ TARGET set make-quoted dependency target\n" );
+ printf( " --object-suffix SFX set object suffix used for dependency targets\n" );
+ printf( " -w suppress all warnings\n" );
+ printf( " -Wcomment[s] warn about nested /* and multi-line // comments\n" );
+ printf( " -Wno-comment[s] disable comment warnings even under -Wall\n" );
+ printf( " -Wall enable all optional warning classes\n" );
+ printf( " -Werror promote every emitted warning to an error\n" );
+ printf( " -Wno-error keep emitted warnings as warnings\n" );
+ printf( " --config-file FILE use only FILE as configuration\n" );
+ printf( " --no-config do not read configuration files; keep runtime defaults\n" );
+ printf( " -v, --verbose print configuration/include activity\n" );
+ printf( " --help display this help and exit\n" );
+ printf( " --version display version and exit\n\n" );
+ printf( "If input is omitted or '-', read standard input.\n" );
+ printf( "If output is omitted or '-', write standard output.\n" );
+}
+
+static void
+pipe_closed( int sig )
+{
+ (void)sig;
+ _Exit( 0 );
+}
+
+static int
+apply_include_options( mcpp *cpp, const mcpp_options *opts )
+{
+ size_t i;
+
+ for( i = 0; i < opts->action_count; ++i )
+ {
+ const mcpp_option_action *action = &opts->actions[i];
+ enum mcpu_include_class class_type;
+
+ switch( action->kind )
+ {
+ case MCPP_ACTION_INCLUDE_USER:
+ if( action->argument[0] == 0 )
+ continue;
+ class_type = MCPU_INCLUDE_USER_EXPLICIT;
+ break;
+ case MCPP_ACTION_INCLUDE_SYSTEM:
+ class_type = MCPU_INCLUDE_SYSTEM_EXPLICIT;
+ break;
+ case MCPP_ACTION_INCLUDE_AFTER:
+ class_type = MCPU_INCLUDE_AFTER_EXPLICIT;
+ break;
+ default:
+ continue;
+ }
+
+ if( mcpu_include_paths_add(&cpp->include_paths,
+ action->argument, class_type) != 0 )
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+static int
+dump_verbose_config( const mcpp *cpp, FILE *stream )
+{
+ enum mcpu_language language;
+ const char *name;
+ const char *value;
+
+ if( cpp == NULL || stream == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ for( language = MCPU_LANG_DIFF;
+ language < MCPU_LANG__COUNT;
+ language = (enum mcpu_language)(language + 1) )
+ {
+ name = mcpu_language_config_variable( language );
+ value = mcpu_config_get( &cpp->config, name );
+ if( value != NULL && fprintf(stream, "config: %s=%s\n", name, value) < 0 )
+ return( -1 );
+ }
+
+ name = "MCPU_CPP_INCLUDE_PATH";
+ value = mcpu_config_get( &cpp->config, name );
+ if( value != NULL && fprintf(stream, "config: %s=%s\n", name, value) < 0 )
+ return( -1 );
+
+ name = "MCPU_CPP_SYSTEM_INCLUDE_PATH";
+ value = mcpu_config_get( &cpp->config, name );
+ if( value != NULL && fprintf(stream, "config: %s=%s\n", name, value) < 0 )
+ return( -1 );
+
+ name = "MCPU_CPP_AFTER_INCLUDE_PATH";
+ value = mcpu_config_get( &cpp->config, name );
+ if( value != NULL && fprintf(stream, "config: %s=%s\n", name, value) < 0 )
+ return( -1 );
+
+ return( ferror(stream) ? -1 : 0 );
+}
+
+
+static FILE *
+open_output( const mcpp_options *opts, int *close_stream )
+{
+ FILE *stream;
+
+ *close_stream = 0;
+ if( opts->out_fname == NULL || opts->out_fname[0] == 0 )
+ return( stdout );
+
+ stream = fopen( opts->out_fname, "wb" );
+ if( stream != NULL )
+ *close_stream = 1;
+
+ return( stream );
+}
+
+
+static char *
+dependency_default_filename( const mcpp_options *opts )
+{
+ const char *source;
+ const char *base;
+ const char *dot;
+ size_t stem_length;
+ char *filename;
+
+ if( opts == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ if( opts->out_fname != NULL && opts->out_fname[0] != 0 )
+ {
+ source = opts->out_fname;
+ base = source;
+ }
+ else if( opts->in_fname != NULL && opts->in_fname[0] != 0 )
+ {
+ base = strrchr( opts->in_fname, '/' );
+ source = base == NULL ? opts->in_fname : base + 1;
+ base = source;
+ }
+ else
+ {
+ source = "-";
+ base = source;
+ }
+
+ dot = strrchr( base, '.' );
+ stem_length = dot != NULL && dot != base ? (size_t)(dot - source) : strlen(source);
+
+ filename = (char *)malloc( stem_length + 3 );
+ if( filename == NULL )
+ return( NULL );
+
+ memcpy( filename, source, stem_length );
+ memcpy( filename + stem_length, ".d", 3 );
+ return( filename );
+}
+
+
+static FILE *
+open_dependency_output( const mcpp_options *opts, int side_effect,
+ char **allocated_name, int *close_stream )
+{
+ const char *name = NULL;
+ FILE *stream;
+
+ if( opts == NULL || allocated_name == NULL || close_stream == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ *allocated_name = NULL;
+ *close_stream = 0;
+
+ if( opts->deps_file != NULL )
+ name = opts->deps_file;
+ else if( side_effect )
+ {
+ *allocated_name = dependency_default_filename( opts );
+ if( *allocated_name == NULL )
+ return( NULL );
+ name = *allocated_name;
+ }
+ else if( opts->out_fname != NULL && opts->out_fname[0] != 0 )
+ name = opts->out_fname;
+
+ if( name == NULL || name[0] == 0 || strcmp(name, "-") == 0 )
+ return( stdout );
+
+ stream = fopen( name, "wb" );
+ if( stream != NULL )
+ *close_stream = 1;
+ else
+ fprintf( stderr, "%s: cannot open dependency output '%s': %s\n",
+ opts->progname, name, strerror(errno) );
+ return( stream );
+}
+
+static int
+run_preprocessor( mcpp *cpp, mcpp_options *opts )
+{
+ FILE *stream;
+ int close_stream;
+ int rc;
+
+ cpp->options_data = opts;
+ cpp->verbose = opts->verbose;
+ cpp->include_paths.no_standard_includes = opts->no_standard_includes;
+
+ if( mcpp_load_config(cpp, opts->config_fname, opts->no_config) != 0 )
+ return( -1 );
+
+ if( cpp->verbose && dump_verbose_config(cpp, stderr) != 0 )
+ return( -1 );
+
+ if( mcpu_include_paths_from_config(&cpp->include_paths, &cpp->config) != 0 ||
+ apply_include_options(cpp, opts) != 0 )
+ return( -1 );
+
+ if( opts->dump_config )
+ {
+ if( opts->out_fname[0] != 0 )
+ {
+ fprintf( stderr, "%s: -o/output file has no meaning with -dconfig\n",
+ opts->progname );
+ return( -1 );
+ }
+ return( mcpu_config_dump(&cpp->config, stdout) );
+ }
+
+ if( opts->dump_search_dirs )
+ {
+ if( opts->out_fname[0] != 0 )
+ {
+ fprintf( stderr, "%s: -o/output file has no meaning with -dsearch-dirs\n",
+ opts->progname );
+ return( -1 );
+ }
+ return( mcpu_include_paths_dump(&cpp->include_paths, stdout) );
+ }
+
+ if( mcpp_options_has_unimplemented(opts) )
+ {
+ fprintf( stderr,
+ "%s: requested option is parsed but its handler is not implemented yet\n",
+ opts->progname );
+ return( -1 );
+ }
+
+ if( opts->in_fname[0] != 0 )
+ rc = mcpp_process( cpp, opts->in_fname );
+ else
+ rc = mcpp_process_stream( cpp, stdin, "<stdin>" );
+ if( rc != 0 )
+ return( -1 );
+
+ if( opts->print_deps && opts->inhibit_output )
+ {
+ char *allocated_name;
+
+ stream = open_dependency_output( opts, 0, &allocated_name, &close_stream );
+ if( stream == NULL )
+ {
+ free( allocated_name );
+ return( -1 );
+ }
+
+ rc = mcpp_write_dependencies( cpp, stream, opts->print_deps == 2 );
+ if( close_stream && fclose(stream) != 0 )
+ rc = -1;
+ free( allocated_name );
+ return( rc );
+ }
+
+ if( opts->dump_macros == MCPP_DUMP_ONLY )
+ {
+ stream = open_output( opts, &close_stream );
+ if( stream == NULL )
+ return( -1 );
+
+ rc = mcpp_dump_macros( cpp, stream );
+ if( close_stream && fclose(stream) != 0 )
+ rc = -1;
+ return( rc );
+ }
+
+ rc = mcpp_write_output(cpp,
+ opts->out_fname[0] != 0 ? opts->out_fname : NULL);
+ if( rc != 0 || !opts->print_deps )
+ return( rc );
+
+ {
+ char *allocated_name;
+
+ stream = open_dependency_output( opts, 1, &allocated_name, &close_stream );
+ if( stream == NULL )
+ {
+ free( allocated_name );
+ return( -1 );
+ }
+
+ rc = mcpp_write_dependencies( cpp, stream, opts->print_deps == 2 );
+ if( close_stream && fclose(stream) != 0 )
+ rc = -1;
+ free( allocated_name );
+ }
+
+ return( rc );
+}
+
+int
+main( int argc, char **argv )
+{
+ mcpp cpp;
+ mcpp_options options;
+ const char *p;
+ int mpu_initialized = 0;
+ int rc = 1;
+
+ mcpp_options_init( &options, argc );
+ if( options.actions == NULL )
+ return( 1 );
+
+ p = argv[0] + strlen( argv[0] );
+ while( p != argv[0] && p[-1] != '/' ) --p;
+ options.progname = p;
+
+#ifdef SIGPIPE
+ signal( SIGPIPE, pipe_closed );
+#endif
+
+ if( mcpp_handle_options(&options, argc - 1, argv + 1) != 0 )
+ goto done_options;
+
+ if( options.version )
+ {
+ welcome( &options );
+ rc = 0;
+ goto done_options;
+ }
+
+ if( options.help )
+ {
+ welcome( &options );
+ usage( &options );
+ rc = 0;
+ goto done_options;
+ }
+
+ __mpu_init();
+ mpu_initialized = 1;
+ mcpp_init( &cpp );
+
+ if( mcpp_runtime_paths_discover(&cpp.runtime_paths, argv[0]) != 0 )
+ {
+ fprintf( stderr, "%s: cannot determine MCPU installation root: %s\n",
+ options.progname, strerror(errno) );
+ }
+ else if( run_preprocessor(&cpp, &options) == 0 )
+ rc = 0;
+
+ mcpp_free( &cpp );
+ if( mpu_initialized )
+ __mpu_free_context();
+
+done_options:
+ mcpp_options_free( &options );
+ return( rc );
+}
diff --git a/src/mcpp-config-file.c b/src/mcpp-config-file.c
new file mode 100644
index 0000000..8022af7
--- /dev/null
+++ b/src/mcpp-config-file.c
@@ -0,0 +1,512 @@
+#include <defs.h>
+
+static char *
+read_bytes( const char *filename, size_t *file_length )
+{
+ FILE *fp;
+ char *data;
+ long size;
+ size_t n;
+
+ if( file_length == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ *file_length = 0;
+
+ fp = fopen( filename, "rb" );
+ if( fp == NULL )
+ return( NULL );
+
+ if( fseek( fp, 0, SEEK_END ) != 0 )
+ {
+ fclose( fp );
+ return( NULL );
+ }
+
+ size = ftell( fp );
+ if( size < 0 || fseek( fp, 0, SEEK_SET ) != 0 )
+ {
+ fclose( fp );
+ return( NULL );
+ }
+
+ data = (char *)calloc( (size_t)size + 1, 1 );
+ if( data == NULL )
+ {
+ fclose( fp );
+ return( NULL );
+ }
+
+ n = fread( data, 1, (size_t)size, fp );
+ if( n != (size_t)size && ferror(fp) )
+ {
+ free( data );
+ fclose( fp );
+ return( NULL );
+ }
+
+ data[n] = 0;
+ fclose( fp );
+ *file_length = n;
+
+ return( data );
+}
+
+static int
+is_space16( __mpu_char16_t c )
+{
+ return( c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
+ c == '\f' || c == '\v' );
+}
+
+static int
+is_name16( __mpu_char16_t c )
+{
+ return( (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') || c == '_' );
+}
+
+static void
+skip_space_and_comments( const __mpu_char16_t *s, size_t n, size_t *pos )
+{
+ size_t p = *pos;
+
+ for( ;; )
+ {
+ while( p < n && is_space16(s[p]) )
+ ++p;
+
+ if( p + 1 < n && s[p] == '/' && s[p + 1] == '*' )
+ {
+ p += 2;
+ while( p + 1 < n && !(s[p] == '*' && s[p + 1] == '/') )
+ ++p;
+ if( p + 1 < n )
+ p += 2;
+ continue;
+ }
+
+ if( p + 1 < n && s[p] == '/' && s[p + 1] == '/' )
+ {
+ p += 2;
+ while( p < n && s[p] != '\n' )
+ ++p;
+ continue;
+ }
+
+ if( p < n && s[p] == '#' )
+ {
+ while( p < n && s[p] != '\n' )
+ ++p;
+ continue;
+ }
+
+ break;
+ }
+
+ *pos = p;
+}
+
+static char *
+expand_value( const mcpu_config *config, const char *value )
+{
+ size_t length = strlen( value );
+ size_t capacity = length + 64;
+ char *out = (char *)calloc( capacity, 1 );
+ size_t i = 0;
+ size_t o = 0;
+
+ if( out == NULL )
+ return( NULL );
+
+ while( i < length )
+ {
+ if( value[i] == '$' )
+ {
+ char name[256];
+ size_t ni = 0;
+ size_t j = i + 1;
+ int braced = 0;
+ const char *replacement;
+
+ if( j < length && value[j] == '{' )
+ {
+ braced = 1;
+ ++j;
+ }
+
+ while( j < length && ni + 1 < sizeof(name) &&
+ ((value[j] >= 'a' && value[j] <= 'z') ||
+ (value[j] >= 'A' && value[j] <= 'Z') ||
+ (value[j] >= '0' && value[j] <= '9') || value[j] == '_') )
+ name[ni++] = value[j++];
+
+ if( braced )
+ {
+ if( j >= length || value[j] != '}' )
+ {
+ free( out );
+ errno = EINVAL;
+ return( NULL );
+ }
+ ++j;
+ }
+
+ if( ni == 0 )
+ {
+ out[o++] = value[i++];
+ continue;
+ }
+
+ name[ni] = 0;
+ replacement = mcpu_config_get( config, name );
+ if( replacement == NULL )
+ replacement = getenv( name );
+ if( replacement == NULL )
+ replacement = "";
+
+ {
+ size_t rl = strlen( replacement );
+ if( o + rl + 1 > capacity )
+ {
+ char *p;
+ while( o + rl + 1 > capacity )
+ capacity *= 2;
+ p = (char *)realloc( out, capacity );
+ if( p == NULL )
+ {
+ free( out );
+ return( NULL );
+ }
+ out = p;
+ }
+ memcpy( out + o, replacement, rl );
+ o += rl;
+ }
+
+ i = j;
+ continue;
+ }
+
+ if( o + 2 > capacity )
+ {
+ char *p;
+ capacity *= 2;
+ p = (char *)realloc( out, capacity );
+ if( p == NULL )
+ {
+ free( out );
+ return( NULL );
+ }
+ out = p;
+ }
+
+ out[o++] = value[i++];
+ }
+
+ out[o] = 0;
+ return( out );
+}
+
+void
+mcpu_config_init( mcpu_config *config )
+{
+ if( config == NULL )
+ return;
+
+ config->first = NULL;
+ config->verbose = 0;
+}
+
+void
+mcpu_config_free( mcpu_config *config )
+{
+ mcpu_config_entry *p;
+
+ if( config == NULL )
+ return;
+
+ p = config->first;
+ while( p )
+ {
+ mcpu_config_entry *next = p->next;
+ free( p->name );
+ free( p->value );
+ free( p );
+ p = next;
+ }
+
+ config->first = NULL;
+}
+
+const char *
+mcpu_config_get( const mcpu_config *config, const char *name )
+{
+ mcpu_config_entry *p;
+
+ if( config == NULL || name == NULL )
+ return( NULL );
+
+ for( p = config->first; p; p = p->next )
+ if( strcmp( p->name, name ) == 0 )
+ return( p->value );
+
+ return( NULL );
+}
+
+int
+mcpu_config_set( mcpu_config *config, const char *name,
+ const char *value )
+{
+ mcpu_config_entry *p;
+ char *new_value;
+
+ if( config == NULL || name == NULL || value == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ new_value = expand_value( config, value );
+ if( new_value == NULL )
+ return( -1 );
+
+ for( p = config->first; p; p = p->next )
+ {
+ if( strcmp( p->name, name ) == 0 )
+ {
+ free( p->value );
+ p->value = new_value;
+ return( 0 );
+ }
+ }
+
+ p = (mcpu_config_entry *)calloc( 1, sizeof(*p) );
+ if( p == NULL )
+ {
+ free( new_value );
+ return( -1 );
+ }
+
+ p->name = strdup( name );
+ if( p->name == NULL )
+ {
+ free( new_value );
+ free( p );
+ return( -1 );
+ }
+
+ p->value = new_value;
+ p->next = config->first;
+ config->first = p;
+
+ return( 0 );
+}
+
+
+static int
+config_entry_compare( const void *a, const void *b )
+{
+ const mcpu_config_entry *ea = *(const mcpu_config_entry * const *)a;
+ const mcpu_config_entry *eb = *(const mcpu_config_entry * const *)b;
+ return( strcmp(ea->name, eb->name) );
+}
+
+
+int
+mcpu_config_dump( const mcpu_config *config, FILE *stream )
+{
+ const mcpu_config_entry *p;
+ mcpu_config_entry **list;
+ size_t count = 0;
+ size_t i = 0;
+
+ if( config == NULL || stream == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ for( p = config->first; p; p = p->next )
+ ++count;
+
+ list = count ? (mcpu_config_entry **)calloc( count, sizeof(*list) ) : NULL;
+ if( count != 0 && list == NULL )
+ return( -1 );
+
+ for( p = config->first; p; p = p->next )
+ list[i++] = (mcpu_config_entry *)p;
+
+ if( count > 1 )
+ qsort( list, count, sizeof(*list), config_entry_compare );
+
+ for( i = 0; i < count; ++i )
+ {
+ if( fprintf(stream, "%s = %s;\n", list[i]->name, list[i]->value) < 0 )
+ {
+ free( list );
+ return( -1 );
+ }
+ }
+
+ free( list );
+ return( ferror(stream) ? -1 : 0 );
+}
+
+
+int
+mcpu_config_read( mcpu_config *config, const char *filename,
+ int missing_is_error )
+{
+ char *bytes;
+ size_t byte_length;
+ mcpu_text text;
+ size_t p = 0;
+ int rc = -1;
+
+ if( config == NULL || filename == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ bytes = read_bytes( filename, &byte_length );
+ if( bytes == NULL )
+ {
+ if( !missing_is_error && errno == ENOENT )
+ return( 0 );
+ return( -1 );
+ }
+
+ mcpu_text_init( &text );
+ {
+ const char *input = bytes;
+
+ if( memchr( bytes, 0, byte_length ) != NULL )
+ {
+ fprintf( stderr, "%s: NUL character is not allowed in configuration text\n",
+ filename );
+ free( bytes );
+ return( -1 );
+ }
+
+ if( byte_length >= 3 &&
+ (unsigned char)bytes[0] == 0xef &&
+ (unsigned char)bytes[1] == 0xbb &&
+ (unsigned char)bytes[2] == 0xbf )
+ input += 3;
+
+ if( mcpu_text_from_utf8( &text, input ) != 0 )
+ {
+ fprintf( stderr, "%s: invalid UTF-8 or non-UCS-2 character\n",
+ filename );
+ free( bytes );
+ return( -1 );
+ }
+ }
+ free( bytes );
+
+ while( p < text.length )
+ {
+ size_t name_start;
+ size_t name_end;
+ size_t value_start;
+ size_t value_end;
+ char *name = NULL;
+ char *value = NULL;
+
+ skip_space_and_comments( text.data, text.length, &p );
+ if( p >= text.length )
+ break;
+
+ name_start = p;
+ while( p < text.length && is_name16(text.data[p]) )
+ ++p;
+ name_end = p;
+
+ if( name_end == name_start )
+ {
+ fprintf( stderr, "%s: malformed configuration variable\n", filename );
+ goto done;
+ }
+
+ skip_space_and_comments( text.data, text.length, &p );
+ if( p >= text.length || text.data[p] != '=' )
+ {
+ fprintf( stderr, "%s: '=' expected after configuration variable\n", filename );
+ goto done;
+ }
+ ++p;
+
+ while( p < text.length && is_space16(text.data[p]) )
+ ++p;
+
+ value_start = p;
+ if( p < text.length && (text.data[p] == '"' || text.data[p] == '\'') )
+ {
+ __mpu_char16_t quote = text.data[p++];
+ value_start = p;
+ while( p < text.length && text.data[p] != quote )
+ {
+ if( text.data[p] == '\\' && p + 1 < text.length )
+ p += 2;
+ else
+ ++p;
+ }
+ if( p >= text.length )
+ {
+ fprintf( stderr, "%s: unterminated quoted configuration value\n", filename );
+ goto done;
+ }
+ value_end = p++;
+ while( p < text.length && is_space16(text.data[p]) )
+ ++p;
+ }
+ else
+ {
+ while( p < text.length && text.data[p] != ';' && text.data[p] != '\n' )
+ ++p;
+ value_end = p;
+ while( value_end > value_start && is_space16(text.data[value_end - 1]) )
+ --value_end;
+ }
+
+ if( p >= text.length || text.data[p] != ';' )
+ {
+ fprintf( stderr, "%s: ';' expected after configuration value\n", filename );
+ goto done;
+ }
+ ++p;
+
+ name = mcpu_text_to_utf8( text.data + name_start, name_end - name_start );
+ value = mcpu_text_to_utf8( text.data + value_start, value_end - value_start );
+ if( name == NULL || value == NULL )
+ {
+ free( name );
+ free( value );
+ goto done;
+ }
+
+ if( mcpu_config_set( config, name, value ) != 0 )
+ {
+ free( name );
+ free( value );
+ goto done;
+ }
+
+ free( name );
+ free( value );
+ name = NULL;
+ value = NULL;
+ }
+
+ rc = 0;
+
+done:
+ mcpu_text_free( &text );
+ return( rc );
+}
diff --git a/src/mcpp-config-file.h b/src/mcpp-config-file.h
new file mode 100644
index 0000000..b2923ab
--- /dev/null
+++ b/src/mcpp-config-file.h
@@ -0,0 +1,30 @@
+#ifndef __MCPU_CPP_CONFIG_FILE_H__
+#define __MCPU_CPP_CONFIG_FILE_H__ 1
+
+#include <defs.h>
+
+typedef struct mcpu_config_entry mcpu_config_entry;
+struct mcpu_config_entry
+{
+ char *name;
+ char *value;
+ mcpu_config_entry *next;
+};
+
+typedef struct mcpu_config mcpu_config;
+struct mcpu_config
+{
+ mcpu_config_entry *first;
+ int verbose;
+};
+
+void mcpu_config_init( mcpu_config *config );
+void mcpu_config_free( mcpu_config *config );
+int mcpu_config_read( mcpu_config *config, const char *filename,
+ int missing_is_error );
+const char *mcpu_config_get( const mcpu_config *config, const char *name );
+int mcpu_config_set( mcpu_config *config, const char *name,
+ const char *value );
+int mcpu_config_dump( const mcpu_config *config, FILE *stream );
+
+#endif /* __MCPU_CPP_CONFIG_FILE_H__ */
diff --git a/src/mcpp-diagnostic.c b/src/mcpp-diagnostic.c
new file mode 100644
index 0000000..76e0c25
--- /dev/null
+++ b/src/mcpp-diagnostic.c
@@ -0,0 +1,28 @@
+#include <defs.h>
+#include <stdarg.h>
+
+int
+mcpp_diagnostic_warning( const mcpp_options *options,
+ const char *filename,
+ unsigned line_number,
+ const char *format, ... )
+{
+ va_list ap;
+ int as_error;
+
+ if( options != NULL && options->inhibit_warnings )
+ return( 0 );
+
+ as_error = options != NULL && options->warnings_are_errors;
+
+ fprintf( stderr, "%s:%u: %s: ",
+ filename != NULL ? filename : "<input>", line_number,
+ as_error ? "error" : "warning" );
+
+ va_start( ap, format );
+ vfprintf( stderr, format, ap );
+ va_end( ap );
+ fputc( '\n', stderr );
+
+ return( as_error ? -1 : 0 );
+}
diff --git a/src/mcpp-diagnostic.h b/src/mcpp-diagnostic.h
new file mode 100644
index 0000000..93cccb1
--- /dev/null
+++ b/src/mcpp-diagnostic.h
@@ -0,0 +1,11 @@
+#ifndef __MCPU_CPP_DIAGNOSTIC_H__
+#define __MCPU_CPP_DIAGNOSTIC_H__ 1
+
+typedef struct mcpp_options mcpp_options;
+
+int mcpp_diagnostic_warning( const mcpp_options *options,
+ const char *filename,
+ unsigned line_number,
+ const char *format, ... );
+
+#endif /* __MCPU_CPP_DIAGNOSTIC_H__ */
diff --git a/src/mcpp-expr.h b/src/mcpp-expr.h
new file mode 100644
index 0000000..6c5a962
--- /dev/null
+++ b/src/mcpp-expr.h
@@ -0,0 +1,10 @@
+#ifndef __MCPU_CPP_EXPR_H__
+#define __MCPU_CPP_EXPR_H__ 1
+
+#include <defs.h>
+
+int mcpp_expr_parse( const __mpu_char16_t *text, size_t length,
+ const char *filename, unsigned line_number,
+ const mcpp_options *options, int *result );
+
+#endif /* __MCPU_CPP_EXPR_H__ */
diff --git a/src/mcpp-expr.zubr b/src/mcpp-expr.zubr
new file mode 100644
index 0000000..1ec5823
--- /dev/null
+++ b/src/mcpp-expr.zubr
@@ -0,0 +1,723 @@
+/***************************************************************
+ MCPP_EXPR.C
+
+ This file containt the grammar & procedure for
+ parse expressions for MCPU-CPP .
+
+ PART OF : MCPU-CPP - MCPU language preproccessor .
+
+ NOTE : NONE .
+
+ Copyright (C) 1998 - 2026 by Andrey V.Kosteltsev.
+ All Rights Reserved.
+ ***************************************************************/
+
+%{
+
+#include <defs.h>
+
+static int mcpp_zubr_lex( void );
+static void mcpp_zubr_error( char *s );
+
+static mcpp_integer expression_value;
+static mcpp_semantic_context semantic_context;
+
+/************************************************************
+ Nonzero means do not evaluate this expression.
+ This is a count, since unevaluated expressions can nest.
+ ************************************************************/
+static int skip_evaluation;
+
+/************************************************************
+ During parsing of an MCPU-CPP expression, LEXPTR points to
+ the next UCS-2 character and LEXEND points one character
+ past the input expression.
+ ************************************************************/
+static const __mpu_char16_t *lexptr;
+static const __mpu_char16_t *lexend;
+
+%}
+
+%union
+{
+ mcpp_integer integer;
+ struct name
+ {
+ const __mpu_char16_t *address;
+ size_t length;
+ } name;
+}
+
+%type <integer> exp exp1 start
+%token <integer> INT CHAR
+%token <name> NAME
+%token <integer> ERROR
+
+%right '?' ':'
+%left ','
+%left OR
+%left AND
+%left '|'
+%left '^'
+%left '&'
+%left EQUAL NOTEQUAL
+%left '<' '>' LEQ GEQ
+%left LSH RSH
+%left '+' '-'
+%left '*' '/' '%'
+%right UNARY
+
+%%
+
+start: exp1
+ {
+ expression_value = $1;
+ }
+ ;
+
+/* Expressions, including the comma operator. */
+exp1: exp
+ | exp1 ',' exp
+ {
+ $$ = $3;
+ }
+ ;
+
+/* Expressions, not including the comma operator. */
+exp: '-' exp %prec UNARY
+ {
+ $$ = mcpp_semantic_neg( $2 );
+ }
+ | '!' exp %prec UNARY
+ {
+ $$ = mcpp_semantic_not( $2 );
+ }
+ | '+' exp %prec UNARY
+ {
+ $$ = $2;
+ }
+ | '~' exp %prec UNARY
+ {
+ $$ = mcpp_semantic_compl( $2 );
+ }
+ | '(' exp1 ')'
+ {
+ $$ = $2;
+ }
+ ;
+
+/* Binary operators in order of decreasing precedence. */
+exp: exp '*' exp
+ {
+ $$ = mcpp_semantic_mul( $1, $3 );
+ }
+ | exp '/' exp
+ {
+ $$ = mcpp_semantic_div( &semantic_context, $1, $3,
+ !skip_evaluation );
+ }
+ | exp '%' exp
+ {
+ $$ = mcpp_semantic_mod( &semantic_context, $1, $3,
+ !skip_evaluation );
+ }
+ | exp '+' exp
+ {
+ $$ = mcpp_semantic_add( $1, $3 );
+ }
+ | exp '-' exp
+ {
+ $$ = mcpp_semantic_sub( $1, $3 );
+ }
+ | exp LSH exp
+ {
+ $$ = mcpp_semantic_lshift( $1, $3 );
+ }
+ | exp RSH exp
+ {
+ $$ = mcpp_semantic_rshift( $1, $3 );
+ }
+ | exp EQUAL exp
+ {
+ $$ = mcpp_semantic_equal( $1, $3 );
+ }
+ | exp NOTEQUAL exp
+ {
+ $$ = mcpp_semantic_notequal( $1, $3 );
+ }
+ | exp LEQ exp
+ {
+ $$ = mcpp_semantic_leq( $1, $3 );
+ }
+ | exp GEQ exp
+ {
+ $$ = mcpp_semantic_geq( $1, $3 );
+ }
+ | exp '<' exp
+ {
+ $$ = mcpp_semantic_less( $1, $3 );
+ }
+ | exp '>' exp
+ {
+ $$ = mcpp_semantic_greater( $1, $3 );
+ }
+ | exp '&' exp
+ {
+ $$ = mcpp_semantic_bitand( $1, $3 );
+ }
+ | exp '^' exp
+ {
+ $$ = mcpp_semantic_bitxor( $1, $3 );
+ }
+ | exp '|' exp
+ {
+ $$ = mcpp_semantic_bitor( $1, $3 );
+ }
+ | exp AND
+ {
+ skip_evaluation += !mcpp_semantic_true( $1 );
+ }
+ exp
+ {
+ skip_evaluation -= !mcpp_semantic_true( $1 );
+ $$ = mcpp_semantic_and( $1, $4 );
+ }
+ | exp OR
+ {
+ skip_evaluation += !!mcpp_semantic_true( $1 );
+ }
+ exp
+ {
+ skip_evaluation -= !!mcpp_semantic_true( $1 );
+ $$ = mcpp_semantic_or( $1, $4 );
+ }
+ | exp '?'
+ {
+ skip_evaluation += !mcpp_semantic_true( $1 );
+ }
+ exp ':'
+ {
+ skip_evaluation += !!mcpp_semantic_true( $1 ) -
+ !mcpp_semantic_true( $1 );
+ }
+ exp
+ {
+ skip_evaluation -= !!mcpp_semantic_true( $1 );
+ $$ = mcpp_semantic_conditional( $1, $4, $7 );
+ }
+ | INT
+ {
+ $$ = $1;
+ }
+ | CHAR
+ {
+ $$ = $1;
+ }
+ | NAME
+ {
+ $$ = mcpp_semantic_make( 0, 0 );
+ }
+ ;
+
+/******* END OF GRAMMAR *******/
+%%
+
+struct token
+{
+ const char *operator;
+ int token;
+};
+
+static struct token tokentab2[] =
+{
+ { "&&", AND },
+ { "||", OR },
+ { "<<", LSH },
+ { ">>", RSH },
+ { "==", EQUAL },
+ { "!=", NOTEQUAL },
+ { "<=", LEQ },
+ { ">=", GEQ },
+ { "++", ERROR },
+ { "--", ERROR },
+ { NULL, ERROR }
+};
+
+static int
+mcpp_expr_space( __mpu_char16_t c )
+{
+ return( c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
+ c == '\f' || c == '\v' );
+}
+
+static int
+mcpp_expr_hex_digit( __mpu_char16_t c )
+{
+ if( c >= '0' && c <= '9' ) return( c - '0' );
+ if( c >= 'a' && c <= 'f' ) return( c - 'a' + 10 );
+ if( c >= 'A' && c <= 'F' ) return( c - 'A' + 10 );
+ return( -1 );
+}
+
+static int
+mcpp_expr_error_token( const char *message )
+{
+ mcpp_zubr_error( (char *)message );
+ return( ERROR );
+}
+
+static __mpu_uint16_t
+mcpp_expr_escape( int *failed )
+{
+ __mpu_char16_t c;
+ __mpu_uint32_t value;
+ int digit;
+ unsigned count;
+
+ *failed = 0;
+ if( lexptr >= lexend )
+ {
+ *failed = 1;
+ mcpp_zubr_error( "incomplete escape sequence in #if expression" );
+ return( 0 );
+ }
+
+ c = *lexptr++;
+ switch( c )
+ {
+ case 'a': return( (__mpu_uint16_t)'\a' );
+ case 'b': return( (__mpu_uint16_t)'\b' );
+ case 'e':
+ case 'E': return( (__mpu_uint16_t)033 );
+ case 'f': return( (__mpu_uint16_t)'\f' );
+ case 'n': return( (__mpu_uint16_t)'\n' );
+ case 'r': return( (__mpu_uint16_t)'\r' );
+ case 't': return( (__mpu_uint16_t)'\t' );
+ case 'v': return( (__mpu_uint16_t)'\v' );
+ case '\\': return( (__mpu_uint16_t)'\\' );
+ case '\'': return( (__mpu_uint16_t)'\'' );
+ case '"': return( (__mpu_uint16_t)'"' );
+ case '?': return( (__mpu_uint16_t)'?' );
+
+ case 'x':
+ case 'X':
+ value = 0;
+ count = 0;
+ while( lexptr < lexend )
+ {
+ digit = mcpp_expr_hex_digit( *lexptr );
+ if( digit < 0 ) break;
+ if( value > 0xffffU >> 4 )
+ {
+ *failed = 1;
+ mcpp_zubr_error( "hex escape sequence out of UCS-2 range" );
+ return( 0 );
+ }
+ value = (value << 4) + (unsigned)digit;
+ ++lexptr;
+ ++count;
+ }
+ if( count == 0 )
+ {
+ *failed = 1;
+ mcpp_zubr_error( "\\x used with no following hex digits" );
+ return( 0 );
+ }
+ return( (__mpu_uint16_t)value );
+
+ default:
+ if( c >= '0' && c <= '7' )
+ {
+ value = c - '0';
+ for( count = 1; count < 6 && lexptr < lexend; ++count )
+ {
+ c = *lexptr;
+ if( c < '0' || c > '7' ) break;
+ if( value > 0xffffU >> 3 )
+ {
+ *failed = 1;
+ mcpp_zubr_error( "octal escape sequence out of UCS-2 range" );
+ return( 0 );
+ }
+ value = (value << 3) + (c - '0');
+ ++lexptr;
+ }
+ if( value > 0xffffU )
+ {
+ *failed = 1;
+ mcpp_zubr_error( "octal escape sequence out of UCS-2 range" );
+ return( 0 );
+ }
+ return( (__mpu_uint16_t)value );
+ }
+ return( (__mpu_uint16_t)c );
+ }
+}
+
+static int
+mcpp_expr_character_constant( void )
+{
+ __mpu_uint64_t result = 0;
+ __mpu_uint16_t value;
+ __mpu_char16_t c;
+ unsigned count = 0;
+ int failed;
+
+ ++lexptr;
+
+ while( lexptr < lexend )
+ {
+ c = *lexptr++;
+ if( c == '\'' )
+ break;
+
+ if( c == '\n' )
+ return( mcpp_expr_error_token(
+ "unterminated character constant in #if expression") );
+
+ if( c == '\\' )
+ {
+ value = mcpp_expr_escape( &failed );
+ if( failed ) return( ERROR );
+ }
+ else
+ value = (__mpu_uint16_t)c;
+
+ ++count;
+ if( count > 4 )
+ return( mcpp_expr_error_token(
+ "character constant too long in #if expression") );
+
+ result = (result << 16) | (__mpu_uint64_t)value;
+ }
+
+ if( lexptr == lexend && (lexptr == 0 || lexptr[-1] != '\'') )
+ return( mcpp_expr_error_token(
+ "unterminated character constant in #if expression") );
+
+ if( count == 0 )
+ return( mcpp_expr_error_token(
+ "empty character constant in #if expression") );
+
+ mcpp_zubr_lval.integer = mcpp_semantic_make( result, 1 );
+ return( CHAR );
+}
+
+static int
+mcpp_expr_valid_integer_width( unsigned width )
+{
+ return( width >= 8 && width <= MPU_REAL_IO_LIMIT &&
+ (width & (width - 1)) == 0 );
+}
+
+static __mpu_uint64_t
+mcpp_expr_apply_integer_width( __mpu_uint64_t value, unsigned width,
+ int unsignedp )
+{
+ __mpu_uint64_t mask;
+ __mpu_uint64_t sign;
+
+ if( width >= 64 )
+ return( value );
+
+ mask = (((__mpu_uint64_t)1 << width) - 1);
+ value &= mask;
+
+ if( unsignedp )
+ return( value );
+
+ sign = ((__mpu_uint64_t)1 << (width - 1));
+ if( value & sign )
+ value |= ~mask;
+
+ return( value );
+}
+
+static int
+mcpp_expr_number( void )
+{
+ const __mpu_char16_t *start = lexptr;
+ const __mpu_char16_t *end;
+ const __mpu_char16_t *number_end;
+ const __mpu_char16_t *p;
+ __mpu_char8_t *ascii;
+ __mpu_uint64_t value = 0;
+ size_t number_length;
+ unsigned base = 10;
+ unsigned width = 0;
+ int digit;
+ int have_digit = 0;
+ int unsignedp = 0;
+ int have_width = 0;
+ int width_over_64 = 0;
+
+ while( lexptr < lexend &&
+ (mcpu_pp_is_identifier_char(*lexptr) || *lexptr == '.') )
+ ++lexptr;
+ end = lexptr;
+
+ for( p = start; p < end; ++p )
+ if( *p == '.' )
+ return( mcpp_expr_error_token(
+ "floating point numbers not allowed in #if expressions") );
+
+ p = start;
+ if( end - p >= 2 && p[0] == '0' &&
+ (p[1] == 'x' || p[1] == 'X') )
+ {
+ base = 16;
+ p += 2;
+ }
+ else if( end - p >= 2 && p[0] == '0' &&
+ (p[1] == 'b' || p[1] == 'B') )
+ {
+ base = 2;
+ p += 2;
+ }
+ else if( end - p > 1 && p[0] == '0' )
+ base = 8;
+
+ for( ; p < end; ++p )
+ {
+ if( *p > 0x7f )
+ break;
+ digit = mcpp_expr_hex_digit( *p );
+ if( digit < 0 || (unsigned)digit >= base )
+ break;
+ have_digit = 1;
+ }
+ number_end = p;
+
+ if( !have_digit )
+ return( mcpp_expr_error_token(
+ "invalid integer constant in #if expression") );
+
+ if( p < end && (*p == 'z' || *p == 'Z') )
+ {
+ have_width = 1;
+ ++p;
+ if( p == end || *p < '0' || *p > '9' )
+ return( mcpp_expr_error_token(
+ "integer width suffix requires decimal digits after z/Z") );
+
+ while( p < end && *p >= '0' && *p <= '9' )
+ {
+ unsigned d = (unsigned)(*p - '0');
+
+ if( !width_over_64 )
+ {
+ if( width > (64U - d) / 10U )
+ width_over_64 = 1;
+ else
+ {
+ width = width * 10U + d;
+ if( width > 64U )
+ width_over_64 = 1;
+ }
+ }
+ ++p;
+ }
+
+ if( p < end && (*p == 'u' || *p == 'U') )
+ {
+ unsignedp = 1;
+ ++p;
+ }
+
+ if( p != end )
+ return( mcpp_expr_error_token(
+ "invalid characters after integer width suffix") );
+
+ if( width_over_64 )
+ return( mcpp_expr_error_token(
+ "integer constants wider than 64 bits are not allowed in conditional directives") );
+
+ if( !mcpp_expr_valid_integer_width(width) )
+ {
+ mcpp_semantic_warning(
+ &semantic_context,
+ "invalid zNNN integer-width suffix; suffix ignored" );
+ have_width = 0;
+ }
+ }
+ else if( p < end && (*p == 'u' || *p == 'U') )
+ {
+ unsignedp = 1;
+ ++p;
+ if( p != end )
+ return( mcpp_expr_error_token(
+ "invalid characters after integer suffix") );
+ }
+ else if( p != end )
+ return( mcpp_expr_error_token(
+ "invalid integer suffix in #if expression") );
+
+ number_length = (size_t)(number_end - start);
+ ascii = (__mpu_char8_t *)malloc( number_length + 1 );
+ if( ascii == NULL )
+ {
+ mcpp_zubr_error( "out of memory while parsing #if expression" );
+ return( ERROR );
+ }
+
+ for( p = start; p < number_end; ++p )
+ {
+ if( *p > 0x7f )
+ {
+ free( ascii );
+ return( mcpp_expr_error_token(
+ "non-ASCII character in integer constant") );
+ }
+ ascii[p - start] = (__mpu_char8_t)*p;
+ }
+ ascii[number_length] = 0;
+
+ __mpu_clo();
+ iatoui( (mpu_int *)&value, ascii, (int)sizeof(value) );
+ free( ascii );
+
+ if( __mpu_gto() )
+ return( mcpp_expr_error_token(
+ "integer constant does not fit in 64 bits") );
+
+ if( have_width )
+ {
+ value = mcpp_expr_apply_integer_width( value, width, unsignedp );
+ mcpp_zubr_lval.integer = mcpp_semantic_make( value, unsignedp );
+ }
+ else
+ mcpp_zubr_lval.integer =
+ mcpp_semantic_make( value,
+ unsignedp || value > (__mpu_uint64_t)INT64_MAX );
+
+ return( INT );
+}
+
+static void
+mcpp_zubr_error( char *s )
+{
+ mcpp_semantic_error( &semantic_context, s );
+ skip_evaluation = 0;
+}
+
+/****************************************************
+ Read one token, getting UCS-2 characters through
+ LEXPTR.
+ ****************************************************/
+static int
+mcpp_zubr_lex( void )
+{
+ const __mpu_char16_t *tokstart;
+ struct token *toktab;
+ __mpu_char16_t c;
+
+retry:
+ while( lexptr < lexend && mcpp_expr_space(*lexptr) )
+ ++lexptr;
+
+ if( lexptr >= lexend )
+ return( 0 );
+
+ tokstart = lexptr;
+ c = *tokstart;
+
+ for( toktab = tokentab2; toktab->operator != NULL; ++toktab )
+ {
+ if( lexend - tokstart >= 2 &&
+ c == (__mpu_char16_t)(unsigned char)toktab->operator[0] &&
+ tokstart[1] == (__mpu_char16_t)(unsigned char)toktab->operator[1] )
+ {
+ lexptr += 2;
+ if( toktab->token == ERROR )
+ return( mcpp_expr_error_token(
+ "increment/decrement operator not allowed in #if expression") );
+ return( toktab->token );
+ }
+ }
+
+ if( c >= '0' && c <= '9' )
+ return( mcpp_expr_number() );
+
+ if( c == '\'' )
+ return( mcpp_expr_character_constant() );
+
+ if( mcpu_pp_is_identifier_start(c) )
+ {
+ ++lexptr;
+ while( lexptr < lexend && mcpu_pp_is_identifier_char(*lexptr) )
+ ++lexptr;
+ mcpp_zubr_lval.name.address = tokstart;
+ mcpp_zubr_lval.name.length = (size_t)(lexptr - tokstart);
+ return( NAME );
+ }
+
+ ++lexptr;
+ switch( c )
+ {
+ case '(':
+ case ')':
+ case '?':
+ case ':':
+ case ',':
+ case '*':
+ case '/':
+ case '%':
+ case '~':
+ case '^':
+ return( (int)c );
+
+ case '+':
+ case '-':
+ return( (int)c );
+
+ case '!':
+ case '<':
+ case '>':
+ case '&':
+ case '|':
+ return( (int)c );
+
+ case '"':
+ case '`':
+ return( mcpp_expr_error_token(
+ "string constants not allowed in #if expressions") );
+
+ default:
+ mcpp_zubr_error( "invalid token in #if expression" );
+ goto retry;
+ }
+}
+
+int
+mcpp_expr_parse( const __mpu_char16_t *text, size_t length,
+ const char *filename, unsigned line_number,
+ const mcpp_options *options, int *result )
+{
+ int rc;
+
+ if( text == NULL || filename == NULL || result == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpp_semantic_context_init( &semantic_context, filename, line_number,
+ options );
+ expression_value = mcpp_semantic_make( 0, 0 );
+ skip_evaluation = 0;
+ lexptr = text;
+ lexend = text + length;
+
+ if( length == 0 )
+ {
+ mcpp_zubr_error( "empty #if expression" );
+ return( -1 );
+ }
+
+ rc = mcpp_zubr_parse();
+ if( rc != 0 || mcpp_semantic_failed(&semantic_context) )
+ return( -1 );
+
+ *result = mcpp_semantic_true( expression_value );
+ return( 0 );
+}
diff --git a/src/mcpp-expression.c b/src/mcpp-expression.c
new file mode 100644
index 0000000..1f9532d
--- /dev/null
+++ b/src/mcpp-expression.c
@@ -0,0 +1,178 @@
+#include <defs.h>
+#include <mcpp-expr.h>
+
+/*
+ * The front end keeps the already established mcpu-cpp preprocessing order:
+ * handle the special defined operator first, expand macros second, then pass
+ * the resulting UCS-2 expression to the ZUBR parser generated from
+ * mcpp-expr.zubr.
+ */
+
+static int
+expr_identifier_start( __mpu_char16_t c )
+{
+ return( mcpu_pp_is_identifier_start(c) );
+}
+
+static int
+expr_identifier_char( __mpu_char16_t c )
+{
+ return( mcpu_pp_is_identifier_char(c) );
+}
+
+static int
+expr_space( __mpu_char16_t c )
+{
+ return( c == ' ' || c == '\t' || c == '\r' || c == '\n' ||
+ c == '\f' || c == '\v' );
+}
+
+static int
+append_defined_value( mcpu_macro_table *macros,
+ const __mpu_char16_t *text, size_t length,
+ size_t *pos, mcpu_text *output,
+ const char *filename, unsigned line_number )
+{
+ size_t p = *pos;
+ size_t start;
+ size_t end;
+ int parenthesized = 0;
+
+ while( p < length && expr_space(text[p]) ) ++p;
+ if( p < length && text[p] == '(' )
+ {
+ parenthesized = 1;
+ ++p;
+ while( p < length && expr_space(text[p]) ) ++p;
+ }
+
+ if( p >= length || !expr_identifier_start(text[p]) )
+ {
+ fprintf( stderr, "%s:%u: error: identifier expected after defined\n",
+ filename, line_number );
+ return( -1 );
+ }
+
+ start = p++;
+ while( p < length && expr_identifier_char(text[p]) ) ++p;
+ end = p;
+
+ while( p < length && expr_space(text[p]) ) ++p;
+ if( parenthesized )
+ {
+ if( p >= length || text[p] != ')' )
+ {
+ fprintf( stderr, "%s:%u: error: missing ')' after defined\n",
+ filename, line_number );
+ return( -1 );
+ }
+ ++p;
+ }
+
+ if( mcpu_text_append_ascii(
+ output, mcpu_macro_find(macros, text + start, end - start) ? "1" : "0") != 0 )
+ return( -1 );
+
+ *pos = p;
+ return( 0 );
+}
+
+static int
+replace_defined_operators( mcpu_macro_table *macros,
+ const __mpu_char16_t *text, size_t length,
+ mcpu_text *output,
+ const char *filename, unsigned line_number )
+{
+ size_t p = 0;
+ __mpu_char16_t quote = 0;
+ int escaped = 0;
+
+ mcpu_text_free( output );
+ mcpu_text_init( output );
+
+ while( p < length )
+ {
+ __mpu_char16_t c = text[p];
+
+ if( quote )
+ {
+ if( mcpu_text_append_char(output, c) != 0 ) return( -1 );
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote )
+ quote = 0;
+ ++p;
+ continue;
+ }
+
+ if( c == '\'' || c == '"' )
+ {
+ quote = c;
+ if( mcpu_text_append_char(output, c) != 0 ) return( -1 );
+ ++p;
+ continue;
+ }
+
+ if( expr_identifier_start(c) )
+ {
+ size_t start = p++;
+ while( p < length && expr_identifier_char(text[p]) ) ++p;
+
+ if( p - start == 7 &&
+ mcpu_text_equal_ascii(text + start, p - start, "defined") )
+ {
+ if( append_defined_value(macros, text, length, &p, output,
+ filename, line_number) != 0 )
+ return( -1 );
+ }
+ else if( mcpu_text_append(output, text + start, p - start) != 0 )
+ return( -1 );
+ continue;
+ }
+
+ if( mcpu_text_append_char(output, c) != 0 )
+ return( -1 );
+ ++p;
+ }
+
+ return( 0 );
+}
+
+int
+mcpp_eval_if_expression( mcpu_macro_table *macros,
+ const __mpu_char16_t *text, size_t length,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ const char *filename, unsigned line_number,
+ int *result )
+{
+ mcpu_text defined;
+ mcpu_text expanded;
+ int rc = -1;
+
+ if( macros == NULL || text == NULL || context == NULL ||
+ filename == NULL || result == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpu_text_init( &defined );
+ mcpu_text_init( &expanded );
+
+ if( replace_defined_operators(macros, text, length, &defined,
+ filename, line_number) != 0 ||
+ mcpu_macro_expand(macros, defined.data, defined.length,
+ language, context, &expanded) != 0 )
+ goto done;
+
+ rc = mcpp_expr_parse( expanded.data, expanded.length,
+ filename, line_number, context->options, result );
+
+done:
+ mcpu_text_free( &defined );
+ mcpu_text_free( &expanded );
+ return( rc );
+}
diff --git a/src/mcpp-expression.h b/src/mcpp-expression.h
new file mode 100644
index 0000000..3fc1c96
--- /dev/null
+++ b/src/mcpp-expression.h
@@ -0,0 +1,15 @@
+#ifndef __MCPU_CPP_EXPRESSION_H__
+#define __MCPU_CPP_EXPRESSION_H__ 1
+
+#include <defs.h>
+
+int mcpp_eval_if_expression( mcpu_macro_table *macros,
+ const __mpu_char16_t *text,
+ size_t length,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ const char *filename,
+ unsigned line_number,
+ int *result );
+
+#endif /* __MCPU_CPP_EXPRESSION_H__ */
diff --git a/src/mcpp-include-path.c b/src/mcpp-include-path.c
new file mode 100644
index 0000000..1b26137
--- /dev/null
+++ b/src/mcpp-include-path.c
@@ -0,0 +1,467 @@
+#include <defs.h>
+
+static char *
+join_path( const char *dir, const char *name )
+{
+ size_t dl;
+ size_t nl;
+ char *p;
+ int slash;
+
+ if( dir == NULL || name == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ dl = strlen( dir );
+ nl = strlen( name );
+ slash = dl != 0 && dir[dl - 1] != '/';
+
+ p = (char *)malloc( dl + (size_t)slash + nl + 1 );
+ if( p == NULL )
+ return( NULL );
+
+ memcpy( p, dir, dl );
+ if( slash )
+ p[dl++] = '/';
+ memcpy( p + dl, name, nl + 1 );
+
+ return( p );
+}
+
+static int
+file_exists( const char *path )
+{
+ FILE *fp = fopen( path, "rb" );
+
+ if( fp == NULL )
+ return( 0 );
+
+ fclose( fp );
+ return( 1 );
+}
+
+static char *
+source_directory( const char *filename )
+{
+ const char *slash;
+ size_t n;
+ char *dir;
+
+ if( filename == NULL )
+ return( NULL );
+
+ slash = strrchr( filename, '/' );
+ if( slash == NULL )
+ return( strdup(".") );
+
+ if( slash == filename )
+ return( strdup("/") );
+
+ n = (size_t)(slash - filename);
+ dir = (char *)malloc( n + 1 );
+ if( dir == NULL )
+ return( NULL );
+
+ memcpy( dir, filename, n );
+ dir[n] = 0;
+
+ return( dir );
+}
+
+void
+mcpu_include_paths_init( mcpu_include_paths *paths )
+{
+ if( paths == NULL )
+ return;
+
+ paths->first = NULL;
+ paths->last = NULL;
+ paths->no_standard_includes = 0;
+}
+
+void
+mcpu_include_paths_free( mcpu_include_paths *paths )
+{
+ mcpu_include_dir *p;
+
+ if( paths == NULL )
+ return;
+
+ p = paths->first;
+ while( p )
+ {
+ mcpu_include_dir *next = p->next;
+ free( p->path );
+ free( p );
+ p = next;
+ }
+
+ paths->first = NULL;
+ paths->last = NULL;
+}
+
+static int
+add_dir( mcpu_include_paths *paths, const char *path,
+ enum mcpu_include_class class_type,
+ int language_specific, enum mcpu_language language )
+{
+ mcpu_include_dir *dir;
+
+ if( paths == NULL || path == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( *path == 0 )
+ return( 0 );
+
+ dir = (mcpu_include_dir *)calloc( 1, sizeof(*dir) );
+ if( dir == NULL )
+ return( -1 );
+
+ dir->path = strdup( path );
+ if( dir->path == NULL )
+ {
+ free( dir );
+ return( -1 );
+ }
+
+ dir->class_type = class_type;
+ dir->language = language;
+ dir->language_specific = language_specific;
+
+ if( paths->last )
+ paths->last->next = dir;
+ else
+ paths->first = dir;
+ paths->last = dir;
+
+ return( 0 );
+}
+
+int
+mcpu_include_paths_add( mcpu_include_paths *paths, const char *path,
+ enum mcpu_include_class class_type )
+{
+ return( add_dir( paths, path, class_type, 0, MCPU_LANG_0 ) );
+}
+
+int
+mcpu_include_paths_add_language( mcpu_include_paths *paths,
+ const char *path,
+ enum mcpu_include_class class_type,
+ enum mcpu_language language )
+{
+ return( add_dir( paths, path, class_type, 1, language ) );
+}
+
+int
+mcpu_include_paths_add_list( mcpu_include_paths *paths,
+ const char *list,
+ enum mcpu_include_class class_type )
+{
+ const char *p;
+ const char *start;
+
+ if( paths == NULL || list == NULL )
+ return( 0 );
+
+ p = start = list;
+ for( ;; )
+ {
+ if( *p == ':' || *p == 0 )
+ {
+ size_t n = (size_t)(p - start);
+ if( n != 0 )
+ {
+ char *dir = (char *)malloc( n + 1 );
+ int rc;
+
+ if( dir == NULL )
+ return( -1 );
+ memcpy( dir, start, n );
+ dir[n] = 0;
+ rc = mcpu_include_paths_add( paths, dir, class_type );
+ free( dir );
+ if( rc != 0 )
+ return( -1 );
+ }
+
+ if( *p == 0 )
+ break;
+ start = p + 1;
+ }
+ ++p;
+ }
+
+ return( 0 );
+}
+
+static int
+add_language_list( mcpu_include_paths *paths, const char *list,
+ enum mcpu_include_class class_type,
+ enum mcpu_language language )
+{
+ const char *p;
+ const char *start;
+
+ if( list == NULL )
+ return( 0 );
+
+ p = start = list;
+ for( ;; )
+ {
+ if( *p == ':' || *p == 0 )
+ {
+ size_t n = (size_t)(p - start);
+ if( n != 0 )
+ {
+ char *dir = (char *)malloc( n + 1 );
+ int rc;
+
+ if( dir == NULL )
+ return( -1 );
+ memcpy( dir, start, n );
+ dir[n] = 0;
+ rc = mcpu_include_paths_add_language( paths, dir,
+ class_type, language );
+ free( dir );
+ if( rc != 0 )
+ return( -1 );
+ }
+ if( *p == 0 )
+ break;
+ start = p + 1;
+ }
+ ++p;
+ }
+
+ return( 0 );
+}
+
+static int
+add_system_root( mcpu_include_paths *paths, const char *root )
+{
+ enum mcpu_language language;
+
+ if( root == NULL || *root == 0 )
+ return( 0 );
+
+ for( language = MCPU_LANG_DIFF;
+ language < MCPU_LANG__COUNT;
+ language = (enum mcpu_language)(language + 1) )
+ {
+ const char *dirname = mcpu_language_directory_name( language );
+ char *dir;
+
+ if( dirname == NULL )
+ continue;
+
+ dir = join_path( root, dirname );
+ if( dir == NULL )
+ return( -1 );
+
+ if( mcpu_include_paths_add_language(paths, dir,
+ MCPU_INCLUDE_SYSTEM_CONFIG_LANGUAGE,
+ language) != 0 )
+ {
+ free( dir );
+ return( -1 );
+ }
+ free( dir );
+ }
+
+ return( mcpu_include_paths_add(paths, root,
+ MCPU_INCLUDE_SYSTEM_CONFIG) );
+}
+
+
+
+int
+mcpu_include_paths_dump( const mcpu_include_paths *paths, FILE *stream )
+{
+ enum mcpu_include_class class_type;
+ const mcpu_include_dir *dir;
+
+ if( paths == NULL || stream == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ for( class_type = MCPU_INCLUDE_USER_EXPLICIT;
+ class_type <= MCPU_INCLUDE_AFTER_CONFIG;
+ class_type = (enum mcpu_include_class)(class_type + 1) )
+ {
+ if( paths->no_standard_includes &&
+ (class_type == MCPU_INCLUDE_SYSTEM_CONFIG_LANGUAGE ||
+ class_type == MCPU_INCLUDE_SYSTEM_CONFIG) )
+ continue;
+
+ for( dir = paths->first; dir; dir = dir->next )
+ {
+ if( dir->class_type != class_type )
+ continue;
+
+ if( fprintf(stream, "search: %s\n", dir->path) < 0 )
+ return( -1 );
+ }
+ }
+
+ return( ferror(stream) ? -1 : 0 );
+}
+
+
+int
+mcpu_include_paths_from_config( mcpu_include_paths *paths,
+ const mcpu_config *config )
+{
+ enum mcpu_language language;
+ const char *value;
+
+ if( paths == NULL || config == NULL )
+ return( 0 );
+
+ /*
+ * Language-specific directories precede the common include root. The
+ * common MCPU_CPP_INCLUDE_PATH itself is language-independent and remains
+ * visible in every language state.
+ */
+ for( language = MCPU_LANG_DIFF;
+ language < MCPU_LANG__COUNT;
+ language = (enum mcpu_language)(language + 1) )
+ {
+ const char *name = mcpu_language_config_variable( language );
+ value = mcpu_config_get( config, name );
+ if( add_language_list(paths, value,
+ MCPU_INCLUDE_USER_CONFIG_LANGUAGE,
+ language) != 0 )
+ return( -1 );
+ }
+
+ value = mcpu_config_get( config, "MCPU_CPP_INCLUDE_PATH" );
+ if( mcpu_include_paths_add_list( paths, value,
+ MCPU_INCLUDE_USER_CONFIG ) != 0 )
+ return( -1 );
+
+ value = mcpu_config_get( config, "MCPU_CPP_SYSTEM_INCLUDE_PATH" );
+ if( add_system_root(paths, value) != 0 )
+ return( -1 );
+
+ value = mcpu_config_get( config, "MCPU_CPP_AFTER_INCLUDE_PATH" );
+ if( mcpu_include_paths_add_list( paths, value,
+ MCPU_INCLUDE_AFTER_CONFIG ) != 0 )
+ return( -1 );
+
+ return( 0 );
+}
+
+char *
+mcpu_include_find( const mcpu_include_paths *paths,
+ const char *source_filename,
+ const char *include_filename,
+ int quoted,
+ enum mcpu_language language,
+ int include_next,
+ const mcpu_include_dir *after_dir,
+ const mcpu_include_dir **found_dir )
+{
+ mcpu_include_dir *dir;
+ int past_after = after_dir == NULL;
+
+ if( found_dir != NULL )
+ *found_dir = NULL;
+
+ if( include_filename == NULL )
+ return( NULL );
+
+ if( include_filename[0] == '/' )
+ {
+ if( file_exists(include_filename) )
+ return( strdup(include_filename) );
+ return( NULL );
+ }
+
+ /*
+ * #include_next never searches the directory of the current source file.
+ * It continues in the configured include chain after the directory entry
+ * that supplied the containing header. If the containing file came from
+ * its source directory (or has no search-chain origin), the configured
+ * chain is searched from its beginning.
+ */
+ if( !include_next && quoted && source_filename )
+ {
+ char *base = source_directory( source_filename );
+ char *path;
+
+ if( base == NULL )
+ return( NULL );
+ path = join_path( base, include_filename );
+ free( base );
+ if( path && file_exists(path) )
+ return( path );
+ free( path );
+ }
+
+ if( paths == NULL )
+ return( NULL );
+
+ {
+ static const enum mcpu_include_class order[] =
+ {
+ MCPU_INCLUDE_USER_EXPLICIT,
+ MCPU_INCLUDE_SYSTEM_EXPLICIT,
+ MCPU_INCLUDE_USER_CONFIG_LANGUAGE,
+ MCPU_INCLUDE_USER_CONFIG,
+ MCPU_INCLUDE_SYSTEM_CONFIG_LANGUAGE,
+ MCPU_INCLUDE_SYSTEM_CONFIG,
+ MCPU_INCLUDE_AFTER_EXPLICIT,
+ MCPU_INCLUDE_AFTER_CONFIG
+ };
+ size_t pass;
+
+ for( pass = 0; pass < sizeof(order) / sizeof(order[0]); ++pass )
+ {
+ for( dir = paths->first; dir; dir = dir->next )
+ {
+ char *path;
+
+ if( dir->class_type != order[pass] )
+ continue;
+
+ if( !past_after )
+ {
+ if( dir == after_dir )
+ past_after = 1;
+ continue;
+ }
+
+ if( dir->language_specific && dir->language != language )
+ continue;
+
+ if( paths->no_standard_includes &&
+ (dir->class_type == MCPU_INCLUDE_SYSTEM_CONFIG_LANGUAGE ||
+ dir->class_type == MCPU_INCLUDE_SYSTEM_CONFIG) )
+ continue;
+
+ path = join_path( dir->path, include_filename );
+ if( path == NULL )
+ return( NULL );
+
+ if( file_exists(path) )
+ {
+ if( found_dir != NULL )
+ *found_dir = dir;
+ return( path );
+ }
+
+ free( path );
+ }
+ }
+ }
+
+ return( NULL );
+}
diff --git a/src/mcpp-include-path.h b/src/mcpp-include-path.h
new file mode 100644
index 0000000..98001f8
--- /dev/null
+++ b/src/mcpp-include-path.h
@@ -0,0 +1,61 @@
+#ifndef __MCPU_CPP_INCLUDE_PATH_H__
+#define __MCPU_CPP_INCLUDE_PATH_H__ 1
+
+#include <defs.h>
+#include <mcpp-config-file.h>
+#include <mcpp-language.h>
+
+enum mcpu_include_class
+{
+ MCPU_INCLUDE_USER_EXPLICIT = 0,
+ MCPU_INCLUDE_SYSTEM_EXPLICIT,
+ MCPU_INCLUDE_USER_CONFIG_LANGUAGE,
+ MCPU_INCLUDE_USER_CONFIG,
+ MCPU_INCLUDE_SYSTEM_CONFIG_LANGUAGE,
+ MCPU_INCLUDE_SYSTEM_CONFIG,
+ MCPU_INCLUDE_AFTER_EXPLICIT,
+ MCPU_INCLUDE_AFTER_CONFIG
+};
+
+typedef struct mcpu_include_dir mcpu_include_dir;
+struct mcpu_include_dir
+{
+ char *path;
+ enum mcpu_include_class class_type;
+ enum mcpu_language language;
+ int language_specific;
+ mcpu_include_dir *next;
+};
+
+typedef struct mcpu_include_paths mcpu_include_paths;
+struct mcpu_include_paths
+{
+ mcpu_include_dir *first;
+ mcpu_include_dir *last;
+ int no_standard_includes;
+};
+
+void mcpu_include_paths_init( mcpu_include_paths *paths );
+void mcpu_include_paths_free( mcpu_include_paths *paths );
+int mcpu_include_paths_add( mcpu_include_paths *paths, const char *path,
+ enum mcpu_include_class class_type );
+int mcpu_include_paths_add_language( mcpu_include_paths *paths,
+ const char *path,
+ enum mcpu_include_class class_type,
+ enum mcpu_language language );
+int mcpu_include_paths_add_list( mcpu_include_paths *paths,
+ const char *list,
+ enum mcpu_include_class class_type );
+int mcpu_include_paths_from_config( mcpu_include_paths *paths,
+ const mcpu_config *config );
+int mcpu_include_paths_dump( const mcpu_include_paths *paths, FILE *stream );
+char *mcpu_include_find( const mcpu_include_paths *paths,
+ const char *source_filename,
+ const char *include_filename,
+ int quoted,
+ enum mcpu_language language,
+ int include_next,
+ const mcpu_include_dir *after_dir,
+ const mcpu_include_dir **found_dir );
+
+#endif /* __MCPU_CPP_INCLUDE_PATH_H__ */
diff --git a/src/mcpp-language.c b/src/mcpp-language.c
new file mode 100644
index 0000000..d224bc8
--- /dev/null
+++ b/src/mcpp-language.c
@@ -0,0 +1,108 @@
+#include <defs.h>
+
+static int
+text_equal_ascii_nocase( const __mpu_char16_t *text, size_t length,
+ const char *ascii )
+{
+ size_t i;
+ size_t ascii_length;
+
+ if( text == NULL || ascii == NULL )
+ return( 0 );
+
+ ascii_length = strlen( ascii );
+ if( length != ascii_length )
+ return( 0 );
+
+ for( i = 0; i < length; ++i )
+ {
+ __mpu_char16_t c = text[i];
+ unsigned char a = (unsigned char)ascii[i];
+
+ if( c >= 'A' && c <= 'Z' )
+ c = (__mpu_char16_t)(c - 'A' + 'a');
+ if( a >= 'A' && a <= 'Z' )
+ a = (unsigned char)(a - 'A' + 'a');
+
+ if( c != (__mpu_char16_t)a )
+ return( 0 );
+ }
+
+ return( 1 );
+}
+
+const char *
+mcpu_language_name( enum mcpu_language language )
+{
+ switch( language )
+ {
+ case MCPU_LANG_0: return( "0" );
+ case MCPU_LANG_DIFF: return( "diff" );
+ case MCPU_LANG_DIFT: return( "dift" );
+ case MCPU_LANG_ALG: return( "alg" );
+ case MCPU_LANG_AS: return( "as" );
+ case MCPU_LANG_AVM: return( "avm" );
+ case MCPU_LANG_ACS: return( "ACS" );
+ default: return( "unknown" );
+ }
+}
+
+int
+mcpu_language_from_text( const __mpu_char16_t *text, size_t length,
+ enum mcpu_language *language )
+{
+ enum mcpu_language candidate;
+
+ if( text == NULL || language == NULL )
+ return( -1 );
+
+ /*
+ * MCPU_LANG_0 is the initial base language state. It is deliberately
+ * not a valid #lang argument. Every accepted name comes directly from
+ * the internal switched-language table and is matched case-insensitively.
+ */
+ for( candidate = MCPU_LANG_DIFF;
+ candidate < MCPU_LANG__COUNT;
+ candidate = (enum mcpu_language)(candidate + 1) )
+ {
+ if( text_equal_ascii_nocase(text, length,
+ mcpu_language_name(candidate)) )
+ {
+ *language = candidate;
+ return( 0 );
+ }
+ }
+
+ return( -1 );
+}
+
+const char *
+mcpu_language_config_variable( enum mcpu_language language )
+{
+ switch( language )
+ {
+ case MCPU_LANG_DIFF: return( "MCPU_CPP_DIFF_INCLUDE_PATH" );
+ case MCPU_LANG_DIFT: return( "MCPU_CPP_DIFT_INCLUDE_PATH" );
+ case MCPU_LANG_ALG: return( "MCPU_CPP_ALG_INCLUDE_PATH" );
+ case MCPU_LANG_AS: return( "MCPU_CPP_AS_INCLUDE_PATH" );
+ case MCPU_LANG_AVM: return( "MCPU_CPP_AVM_INCLUDE_PATH" );
+ case MCPU_LANG_ACS: return( "MCPU_CPP_ACS_INCLUDE_PATH" );
+ default: return( NULL );
+ }
+}
+
+
+const char *
+mcpu_language_directory_name( enum mcpu_language language )
+{
+ switch( language )
+ {
+ case MCPU_LANG_DIFF: return( "diff" );
+ case MCPU_LANG_DIFT: return( "dift" );
+ case MCPU_LANG_ALG: return( "alg" );
+ case MCPU_LANG_AS: return( "as" );
+ case MCPU_LANG_AVM: return( "avm" );
+ case MCPU_LANG_ACS: return( "acs" );
+ default: return( NULL );
+ }
+}
diff --git a/src/mcpp-language.h b/src/mcpp-language.h
new file mode 100644
index 0000000..a758e37
--- /dev/null
+++ b/src/mcpp-language.h
@@ -0,0 +1,24 @@
+#ifndef __MCPU_CPP_LANGUAGE_H__
+#define __MCPU_CPP_LANGUAGE_H__ 1
+
+#include <defs.h>
+
+enum mcpu_language
+{
+ MCPU_LANG_0 = 0,
+ MCPU_LANG_DIFF,
+ MCPU_LANG_DIFT,
+ MCPU_LANG_ALG,
+ MCPU_LANG_AS,
+ MCPU_LANG_AVM,
+ MCPU_LANG_ACS,
+ MCPU_LANG__COUNT
+};
+
+const char *mcpu_language_name( enum mcpu_language language );
+int mcpu_language_from_text( const __mpu_char16_t *text, size_t length,
+ enum mcpu_language *language );
+const char *mcpu_language_config_variable( enum mcpu_language language );
+const char *mcpu_language_directory_name( enum mcpu_language language );
+
+#endif /* __MCPU_CPP_LANGUAGE_H__ */
diff --git a/src/mcpp-lexer.c b/src/mcpp-lexer.c
new file mode 100644
index 0000000..067e1a2
--- /dev/null
+++ b/src/mcpp-lexer.c
@@ -0,0 +1,371 @@
+#include <defs.h>
+
+void
+mcpu_pp_lexer_init( mcpu_pp_lexer_state *state )
+{
+ if( state )
+ {
+ state->in_block_comment = 0;
+ state->options = NULL;
+ state->filename = NULL;
+ state->line_number = 0;
+ state->splice_offsets = NULL;
+ state->splice_count = 0;
+ }
+}
+
+void
+mcpu_pp_lexer_set_diagnostics( mcpu_pp_lexer_state *state,
+ const mcpp_options *options,
+ const char *filename,
+ unsigned line_number,
+ const size_t *splice_offsets,
+ size_t splice_count )
+{
+ if( state == NULL )
+ return;
+
+ state->options = options;
+ state->filename = filename;
+ state->line_number = line_number;
+ state->splice_offsets = splice_offsets;
+ state->splice_count = splice_count;
+}
+
+static unsigned
+lexer_warning_line( const mcpu_pp_lexer_state *state, size_t offset )
+{
+ size_t i;
+ unsigned line;
+
+ line = state->line_number;
+ for( i = 0; i < state->splice_count; ++i )
+ if( state->splice_offsets[i] <= offset )
+ ++line;
+
+ return( line );
+}
+
+static int
+lexer_has_splice_after( const mcpu_pp_lexer_state *state, size_t offset )
+{
+ size_t i;
+
+ for( i = 0; i < state->splice_count; ++i )
+ if( state->splice_offsets[i] >= offset )
+ return( 1 );
+
+ return( 0 );
+}
+
+static int
+lexer_comment_warning( const mcpu_pp_lexer_state *state, size_t offset,
+ const char *message )
+{
+ if( state->options == NULL || !state->options->warn_comments )
+ return( 0 );
+
+ return( mcpp_diagnostic_warning(state->options,
+ state->filename,
+ lexer_warning_line(state, offset),
+ "%s", message) );
+}
+
+int
+mcpu_pp_is_identifier_start( __mpu_char16_t c )
+{
+ return( c == '_' || mpu_ucs2_is_xid_start(c) );
+}
+
+int
+mcpu_pp_is_identifier_char( __mpu_char16_t c )
+{
+ return( c == '_' || c == '$' || mpu_ucs2_is_xid_continue(c) );
+}
+
+static int
+is_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
+append_space_once( mcpu_text *text )
+{
+ if( text->length != 0 )
+ {
+ __mpu_char16_t last = text->data[text->length - 1];
+ if( last == ' ' || last == '\t' || last == '\f' || last == '\v' ||
+ last == '\r' || last == '\n' )
+ return( 0 );
+ }
+
+ return( mcpu_text_append_char(text, ' ') );
+}
+
+static int
+is_blank16( __mpu_char16_t c )
+{
+ return( c == ' ' || c == '\t' || c == '\f' ||
+ c == '\v' || c == '\r' );
+}
+
+static void
+remove_comment_tail_space( mcpu_text *text )
+{
+ size_t end;
+ int newline;
+
+ if( text == NULL || text->length == 0 )
+ return;
+
+ newline = text->data[text->length - 1] == '\n';
+ end = newline ? text->length - 1 : text->length;
+
+ while( end != 0 && is_blank16(text->data[end - 1]) )
+ --end;
+
+ if( newline )
+ text->data[end++] = '\n';
+
+ text->length = end;
+ text->data[text->length] = 0;
+}
+
+int
+mcpu_pp_prepare_line( mcpu_pp_lexer_state *state,
+ const __mpu_char16_t *line,
+ size_t length,
+ enum mcpu_language language,
+ mcpu_text *prepared )
+{
+ size_t p = 0;
+ __mpu_char16_t quote = 0;
+ int escaped = 0;
+ int first_token = 1;
+ int saw_hash = 0;
+ int reading_directive = 0;
+ int directive_done = 0;
+ int include_directive = 0;
+ int include_argument_pending = 0;
+ int in_include_angle = 0;
+ __mpu_char16_t directive[32];
+ size_t directive_length = 0;
+ int comment_tail;
+
+ if( state == NULL || line == NULL || prepared == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpu_text_free( prepared );
+ mcpu_text_init( prepared );
+ comment_tail = state->in_block_comment;
+
+ while( p < length )
+ {
+ __mpu_char16_t c = line[p];
+
+ if( state->in_block_comment )
+ {
+ if( p + 1 < length && c == '/' && line[p + 1] == '*' )
+ {
+ if( lexer_comment_warning(state, p, "\"/*\" within comment") != 0 )
+ return( -1 );
+ }
+
+ if( p + 1 < length && c == '*' && line[p + 1] == '/' )
+ {
+ state->in_block_comment = 0;
+ p += 2;
+ }
+ else
+ {
+ if( c == '\n' && mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ ++p;
+ }
+ continue;
+ }
+
+ if( in_include_angle )
+ {
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ if( c == '>' || c == '\n' )
+ in_include_angle = 0;
+ ++p;
+ continue;
+ }
+
+ if( quote )
+ {
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote || c == '\n' )
+ quote = 0;
+
+ ++p;
+ continue;
+ }
+
+ if( p + 1 < length && c == '/' && line[p + 1] == '*' )
+ {
+ if( append_space_once(prepared) != 0 )
+ return( -1 );
+ comment_tail = 1;
+ state->in_block_comment = 1;
+ p += 2;
+ continue;
+ }
+
+ if( p + 1 < length && c == '/' && line[p + 1] == '/' )
+ {
+ if( lexer_has_splice_after(state, p + 2) &&
+ lexer_comment_warning(state, p, "multi-line comment") != 0 )
+ return( -1 );
+
+ if( append_space_once(prepared) != 0 )
+ return( -1 );
+ comment_tail = 1;
+ while( p < length && line[p] != '\n' )
+ ++p;
+ continue;
+ }
+
+ if( comment_tail && !is_blank16(c) && c != '\n' )
+ comment_tail = 0;
+
+ if( first_token )
+ {
+ if( c == ' ' || c == '\t' || c == '\f' || c == '\v' || c == '\r' )
+ {
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ ++p;
+ continue;
+ }
+
+ first_token = 0;
+ if( c == '#' )
+ {
+ saw_hash = 1;
+ reading_directive = 1;
+ }
+ }
+
+ if( reading_directive && !directive_done && saw_hash && c != '#' )
+ {
+ if( directive_length == 0 &&
+ (c == ' ' || c == '\t' || c == '\f' || c == '\v' || c == '\r') )
+ {
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ ++p;
+ continue;
+ }
+
+ if( mcpu_pp_is_identifier_char(c) )
+ {
+ if( directive_length + 1 < sizeof(directive) / sizeof(directive[0]) )
+ directive[directive_length++] = c;
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ ++p;
+ continue;
+ }
+
+ directive_done = 1;
+ reading_directive = 0;
+ if( (directive_length == 7 &&
+ directive[0] == 'i' && directive[1] == 'n' &&
+ directive[2] == 'c' && directive[3] == 'l' &&
+ directive[4] == 'u' && directive[5] == 'd' &&
+ directive[6] == 'e') ||
+ (directive_length == 12 &&
+ directive[0] == 'i' && directive[1] == 'n' &&
+ directive[2] == 'c' && directive[3] == 'l' &&
+ directive[4] == 'u' && directive[5] == 'd' &&
+ directive[6] == 'e' && directive[7] == '_' &&
+ directive[8] == 'n' && directive[9] == 'e' &&
+ directive[10] == 'x' && directive[11] == 't') )
+ {
+ include_directive = 1;
+ include_argument_pending = 1;
+ }
+ }
+
+ if( include_directive && include_argument_pending )
+ {
+ if( c == ' ' || c == '\t' || c == '\f' || c == '\v' || c == '\r' )
+ {
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ ++p;
+ continue;
+ }
+
+ include_argument_pending = 0;
+ if( c == '<' )
+ in_include_angle = 1;
+ }
+
+ if( is_quote16(c, language) )
+ quote = c;
+
+ if( mcpu_text_append_char(prepared, c) != 0 )
+ return( -1 );
+ ++p;
+ }
+
+ if( comment_tail )
+ remove_comment_tail_space( prepared );
+
+ return( 0 );
+}
+
+int
+mcpu_pp_find_directive( const __mpu_char16_t *line,
+ size_t length,
+ size_t *hash_offset )
+{
+ size_t p = 0;
+
+ if( line == NULL || hash_offset == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ while( p < length )
+ {
+ if( line[p] == ' ' || line[p] == '\t' || line[p] == '\f' ||
+ line[p] == '\v' || line[p] == '\r' )
+ {
+ ++p;
+ continue;
+ }
+
+ if( line[p] == '#' )
+ {
+ *hash_offset = p;
+ return( 1 );
+ }
+
+ break;
+ }
+
+ return( 0 );
+}
diff --git a/src/mcpp-lexer.h b/src/mcpp-lexer.h
new file mode 100644
index 0000000..cc18799
--- /dev/null
+++ b/src/mcpp-lexer.h
@@ -0,0 +1,38 @@
+#ifndef __MCPU_CPP_LEXER_H__
+#define __MCPU_CPP_LEXER_H__ 1
+
+#include <defs.h>
+#include <mcpp-language.h>
+#include <mcpp-text.h>
+
+typedef struct mcpp_options mcpp_options;
+typedef struct mcpu_pp_lexer_state mcpu_pp_lexer_state;
+struct mcpu_pp_lexer_state
+{
+ int in_block_comment;
+ const mcpp_options *options;
+ const char *filename;
+ unsigned line_number;
+ const size_t *splice_offsets;
+ size_t splice_count;
+};
+
+void mcpu_pp_lexer_init( mcpu_pp_lexer_state *state );
+void mcpu_pp_lexer_set_diagnostics( mcpu_pp_lexer_state *state,
+ const mcpp_options *options,
+ const char *filename,
+ unsigned line_number,
+ const size_t *splice_offsets,
+ size_t splice_count );
+int mcpu_pp_prepare_line( mcpu_pp_lexer_state *state,
+ const __mpu_char16_t *line,
+ size_t length,
+ enum mcpu_language language,
+ mcpu_text *prepared );
+int mcpu_pp_find_directive( const __mpu_char16_t *line,
+ size_t length,
+ size_t *hash_offset );
+int mcpu_pp_is_identifier_start( __mpu_char16_t c );
+int mcpu_pp_is_identifier_char( __mpu_char16_t c );
+
+#endif /* __MCPU_CPP_LEXER_H__ */
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 );
+}
diff --git a/src/mcpp-lib.h b/src/mcpp-lib.h
new file mode 100644
index 0000000..6974672
--- /dev/null
+++ b/src/mcpp-lib.h
@@ -0,0 +1,65 @@
+#ifndef __MCPU_CPP_CPP_H__
+#define __MCPU_CPP_CPP_H__ 1
+
+#include <defs.h>
+#include <mcpp-config-file.h>
+#include <mcpp-include-path.h>
+#include <mcpp-language.h>
+#include <mcpp-macro.h>
+#include <mcpp-text.h>
+
+typedef struct mcpp_conditional mcpp_conditional;
+struct mcpp_conditional
+{
+ const char *filename;
+ unsigned line_number;
+ int parent_active;
+ int active;
+ int branch_succeeded;
+ int seen_else;
+};
+
+typedef struct mcpp_once_file mcpp_once_file;
+typedef struct mcpp_dependency mcpp_dependency;
+
+typedef struct mcpp mcpp;
+struct mcpp
+{
+ mcpp_options *options_data;
+ mcpu_config config;
+ mcpp_runtime_paths runtime_paths;
+ mcpu_include_paths include_paths;
+ enum mcpu_language lang_stack[MCPU_CPP_LANG_STACK_SIZE];
+ size_t lang_depth;
+ size_t include_depth;
+ mcpp_conditional conditional_stack[MCPU_CPP_CONDITIONAL_STACK_SIZE];
+ size_t conditional_depth;
+ char *base_filename;
+ char preprocess_date[16];
+ char preprocess_time[16];
+ int builtins_initialized;
+ int command_line_macros_applied;
+ mcpu_macro_table macros;
+ mcpp_once_file *once_files;
+ mcpp_dependency *dependencies_first;
+ mcpp_dependency *dependencies_last;
+ int verbose;
+ char *output_filename;
+ unsigned output_line;
+ mcpu_text output;
+};
+
+void mcpp_init( mcpp *cpp );
+void mcpp_free( mcpp *cpp );
+int mcpp_load_config( mcpp *cpp, const char *explicit_filename,
+ int no_config );
+int mcpp_prepare( mcpp *cpp );
+int mcpp_process( mcpp *cpp, const char *filename );
+int mcpp_process_stream( mcpp *cpp, FILE *stream, const char *name );
+int mcpp_dump_macros( mcpp *cpp, FILE *stream );
+int mcpp_write_output( mcpp *cpp, const char *filename );
+int mcpp_write_dependencies( mcpp *cpp, FILE *stream, int include_system );
+
+#define MCPP_OPTIONS(_cpp_) ((_cpp_)->options_data)
+
+#endif /* __MCPU_CPP_CPP_H__ */
diff --git a/src/mcpp-macro.c b/src/mcpp-macro.c
new file mode 100644
index 0000000..1837548
--- /dev/null
+++ b/src/mcpp-macro.c
@@ -0,0 +1,2878 @@
+#include <defs.h>
+
+
+typedef struct mcpu_macro_arg mcpu_macro_arg;
+struct mcpu_macro_arg
+{
+ const __mpu_char16_t *raw;
+ size_t raw_length;
+ mcpu_text expanded;
+ int expanded_ready;
+};
+
+
+static unsigned
+macro_hash( const __mpu_char16_t *name, size_t length )
+{
+ unsigned h = 5381U;
+ size_t i;
+
+ for( i = 0; i < length; ++i )
+ h = ((h << 5) + h) ^ (unsigned)name[i];
+
+ return( h % MCPU_CPP_MACRO_BUCKETS );
+}
+
+
+static __mpu_char16_t *
+dup_text( const __mpu_char16_t *text, size_t length )
+{
+ __mpu_char16_t *copy;
+
+ copy = (__mpu_char16_t *)calloc( length + 1, sizeof(__mpu_char16_t) );
+ if( copy == NULL )
+ return( NULL );
+
+ if( length != 0 )
+ memcpy( copy, text, length * sizeof(__mpu_char16_t) );
+
+ return( copy );
+}
+
+
+static void
+free_argnames( __mpu_char16_t **argnames, size_t *argname_lengths,
+ int nargs )
+{
+ int i;
+
+ if( argnames )
+ {
+ for( i = 0; i < nargs; ++i )
+ free( argnames[i] );
+ }
+
+ free( argnames );
+ free( argname_lengths );
+}
+
+
+static void
+free_macro_contents( mcpu_macro *macro )
+{
+ if( macro == NULL )
+ return;
+
+ free( macro->name );
+ free_argnames( macro->argnames, macro->argname_lengths, macro->nargs );
+ free( macro->replacement );
+}
+
+
+void
+mcpu_macro_table_init( mcpu_macro_table *table )
+{
+ if( table )
+ memset( table, 0, sizeof(*table) );
+}
+
+
+void
+mcpu_macro_table_free( mcpu_macro_table *table )
+{
+ unsigned i;
+
+ if( table == NULL )
+ return;
+
+ for( i = 0; i < MCPU_CPP_MACRO_BUCKETS; ++i )
+ {
+ mcpu_macro *macro = table->bucket[i];
+
+ while( macro )
+ {
+ mcpu_macro *next = macro->next;
+ free_macro_contents( macro );
+ free( macro );
+ macro = next;
+ }
+
+ table->bucket[i] = NULL;
+ }
+}
+
+
+mcpu_macro *
+mcpu_macro_find( mcpu_macro_table *table,
+ const __mpu_char16_t *name, size_t name_length )
+{
+ mcpu_macro *macro;
+ unsigned h;
+
+ if( table == NULL || name == NULL || name_length == 0 )
+ return( NULL );
+
+ h = macro_hash( name, name_length );
+ for( macro = table->bucket[h]; macro; macro = macro->next )
+ {
+ if( macro->name_length == name_length &&
+ memcmp( macro->name, name,
+ name_length * sizeof(__mpu_char16_t) ) == 0 )
+ return( macro );
+ }
+
+ return( NULL );
+}
+
+
+static int
+copy_argnames( const __mpu_char16_t *const *argnames,
+ const size_t *argname_lengths, size_t nargs,
+ __mpu_char16_t ***copy_names, size_t **copy_lengths )
+{
+ __mpu_char16_t **names = NULL;
+ size_t *lengths = NULL;
+ size_t i;
+
+ *copy_names = NULL;
+ *copy_lengths = NULL;
+
+ if( nargs == 0 )
+ return( 0 );
+
+ names = (__mpu_char16_t **)calloc( nargs, sizeof(*names) );
+ lengths = (size_t *)calloc( nargs, sizeof(*lengths) );
+ if( names == NULL || lengths == NULL )
+ goto fail;
+
+ for( i = 0; i < nargs; ++i )
+ {
+ names[i] = dup_text( argnames[i], argname_lengths[i] );
+ if( names[i] == NULL )
+ goto fail;
+ lengths[i] = argname_lengths[i];
+ }
+
+ *copy_names = names;
+ *copy_lengths = lengths;
+ return( 0 );
+
+fail:
+ if( names )
+ {
+ for( i = 0; i < nargs; ++i )
+ free( names[i] );
+ }
+ free( names );
+ free( lengths );
+ return( -1 );
+}
+
+
+static int
+macro_install( mcpu_macro_table *table,
+ const __mpu_char16_t *name, size_t name_length,
+ int nargs, int variadic,
+ const __mpu_char16_t *const *argnames,
+ const size_t *argname_lengths,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length,
+ enum mcpu_macro_builtin builtin,
+ unsigned builtin_bits,
+ enum mcpu_macro_origin origin )
+{
+ mcpu_macro *macro;
+ __mpu_char16_t *new_replacement;
+ __mpu_char16_t **new_argnames = NULL;
+ size_t *new_argname_lengths = NULL;
+ unsigned h;
+
+ if( table == NULL || name == NULL || name_length == 0 ||
+ (replacement == NULL && replacement_length != 0) || nargs < -1 ||
+ (variadic && nargs < 0) ||
+ (nargs > 0 && (argnames == NULL || argname_lengths == NULL)) )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ new_replacement = dup_text( replacement, replacement_length );
+ if( new_replacement == NULL )
+ return( -1 );
+
+ if( nargs >= 0 &&
+ copy_argnames(argnames, argname_lengths, (size_t)nargs,
+ &new_argnames, &new_argname_lengths) != 0 )
+ {
+ free( new_replacement );
+ return( -1 );
+ }
+
+ macro = mcpu_macro_find( table, name, name_length );
+ if( macro )
+ {
+ free_argnames( macro->argnames, macro->argname_lengths, macro->nargs );
+ free( macro->replacement );
+ macro->nargs = nargs;
+ macro->variadic = variadic;
+ macro->argnames = new_argnames;
+ macro->argname_lengths = new_argname_lengths;
+ macro->replacement = new_replacement;
+ macro->replacement_length = replacement_length;
+ macro->builtin = builtin;
+ macro->builtin_bits = builtin_bits;
+ macro->origin = origin;
+ return( 0 );
+ }
+
+ macro = (mcpu_macro *)calloc( 1, sizeof(*macro) );
+ if( macro == NULL )
+ {
+ free_argnames( new_argnames, new_argname_lengths, nargs );
+ free( new_replacement );
+ return( -1 );
+ }
+
+ macro->name = dup_text( name, name_length );
+ if( macro->name == NULL )
+ {
+ free_argnames( new_argnames, new_argname_lengths, nargs );
+ free( new_replacement );
+ free( macro );
+ return( -1 );
+ }
+
+ macro->name_length = name_length;
+ macro->nargs = nargs;
+ macro->variadic = variadic;
+ macro->argnames = new_argnames;
+ macro->argname_lengths = new_argname_lengths;
+ macro->replacement = new_replacement;
+ macro->replacement_length = replacement_length;
+ macro->builtin = builtin;
+ macro->builtin_bits = builtin_bits;
+ macro->origin = origin;
+
+ h = macro_hash( name, name_length );
+ macro->next = table->bucket[h];
+ table->bucket[h] = macro;
+
+ return( 0 );
+}
+
+
+int
+mcpu_macro_define_object( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length,
+ enum mcpu_macro_origin origin )
+{
+ return( macro_install(table, name, name_length, -1, 0, NULL, NULL,
+ replacement, replacement_length,
+ MCPU_MACRO_BUILTIN_NONE, 0, origin) );
+}
+
+
+int
+mcpu_macro_define_builtin( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ enum mcpu_macro_builtin builtin )
+{
+ if( builtin == MCPU_MACRO_BUILTIN_NONE )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ return( macro_install(table, name, name_length, -1, 0, NULL, NULL,
+ NULL, 0, builtin, 0, MCPU_MACRO_ORIGIN_BUILTIN) );
+}
+
+
+int
+mcpu_macro_define_sized_builtin( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ enum mcpu_macro_builtin builtin,
+ unsigned bits )
+{
+ if( builtin < MCPU_MACRO_BUILTIN_REAL_MAX || bits == 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ return( macro_install(table, name, name_length, -1, 0, NULL, NULL,
+ NULL, 0, builtin, bits, MCPU_MACRO_ORIGIN_BUILTIN) );
+}
+
+
+int
+mcpu_macro_define_function( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ const __mpu_char16_t *const *argnames,
+ const size_t *argname_lengths,
+ size_t nargs, int variadic,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length,
+ enum mcpu_macro_origin origin )
+{
+ if( nargs > (size_t)INT_MAX )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ return( macro_install(table, name, name_length, (int)nargs, variadic,
+ argnames, argname_lengths,
+ replacement, replacement_length,
+ MCPU_MACRO_BUILTIN_NONE, 0, origin) );
+}
+
+
+int
+mcpu_macro_definition_equal( const mcpu_macro *macro,
+ int nargs, int variadic,
+ const __mpu_char16_t *const *argnames,
+ const size_t *argname_lengths,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length )
+{
+ int i;
+
+ if( macro == NULL || macro->builtin != MCPU_MACRO_BUILTIN_NONE ||
+ macro->nargs != nargs || macro->variadic != variadic ||
+ macro->replacement_length != replacement_length )
+ return( 0 );
+
+ if( replacement_length != 0 &&
+ memcmp( macro->replacement, replacement,
+ replacement_length * sizeof(__mpu_char16_t) ) != 0 )
+ return( 0 );
+
+ if( nargs < 0 )
+ return( 1 );
+
+ for( i = 0; i < nargs; ++i )
+ {
+ if( macro->argname_lengths[i] != argname_lengths[i] ||
+ memcmp( macro->argnames[i], argnames[i],
+ argname_lengths[i] * sizeof(__mpu_char16_t) ) != 0 )
+ return( 0 );
+ }
+
+ return( 1 );
+}
+
+
+int
+mcpu_macro_undef( mcpu_macro_table *table,
+ const __mpu_char16_t *name, size_t name_length )
+{
+ mcpu_macro **link;
+ unsigned h;
+
+ if( table == NULL || name == NULL || name_length == 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ h = macro_hash( name, name_length );
+ link = &table->bucket[h];
+
+ while( *link )
+ {
+ mcpu_macro *macro = *link;
+
+ if( macro->name_length == name_length &&
+ memcmp( macro->name, name,
+ name_length * sizeof(__mpu_char16_t) ) == 0 )
+ {
+ *link = macro->next;
+ free_macro_contents( macro );
+ free( macro );
+ return( 1 );
+ }
+
+ link = &macro->next;
+ }
+
+ return( 0 );
+}
+
+
+static int
+is_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
+is_space16( __mpu_char16_t c )
+{
+ return( c == ' ' || c == '\t' || c == '\f' || c == '\v' ||
+ c == '\r' || c == '\n' );
+}
+
+
+static int
+macro_argument_index( const mcpu_macro *macro,
+ const __mpu_char16_t *name, size_t name_length )
+{
+ int i;
+
+ for( i = 0; i < macro->nargs; ++i )
+ {
+ if( macro->argname_lengths[i] == name_length &&
+ memcmp( macro->argnames[i], name,
+ name_length * sizeof(__mpu_char16_t) ) == 0 )
+ return( i );
+ }
+
+ if( macro->variadic &&
+ mcpu_text_equal_ascii(name, name_length, "__VA_ARGS__") )
+ return( macro->nargs );
+
+ return( -1 );
+}
+
+
+static void
+macro_args_free( mcpu_macro_arg *args, size_t nargs )
+{
+ size_t i;
+
+ if( args == NULL )
+ return;
+
+ for( i = 0; i < nargs; ++i )
+ mcpu_text_free( &args[i].expanded );
+
+ free( args );
+}
+
+
+static int
+append_macro_arg( mcpu_macro_arg **args, size_t *nargs, size_t *capacity,
+ const __mpu_char16_t *raw, size_t raw_length )
+{
+ mcpu_macro_arg *new_args;
+ size_t new_capacity;
+
+ if( *nargs == *capacity )
+ {
+ new_capacity = *capacity ? *capacity * 2 : 4;
+ if( new_capacity < *capacity ||
+ new_capacity > SIZE_MAX / sizeof(**args) )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ new_args = (mcpu_macro_arg *)realloc( *args,
+ new_capacity * sizeof(**args) );
+ if( new_args == NULL )
+ return( -1 );
+
+ *args = new_args;
+ *capacity = new_capacity;
+ }
+
+ (*args)[*nargs].raw = raw;
+ (*args)[*nargs].raw_length = raw_length;
+ mcpu_text_init( &(*args)[*nargs].expanded );
+ (*args)[*nargs].expanded_ready = 0;
+ ++*nargs;
+
+ return( 0 );
+}
+
+
+static int
+parse_macro_args( const __mpu_char16_t *input, size_t length,
+ size_t open_paren, enum mcpu_language language,
+ mcpu_macro_arg **args_out, size_t *nargs_out,
+ size_t *after_call )
+{
+ mcpu_macro_arg *args = NULL;
+ size_t nargs = 0;
+ size_t capacity = 0;
+ size_t p = open_paren + 1;
+ size_t start = p;
+ int depth = 0;
+ __mpu_char16_t quote = 0;
+ int escaped = 0;
+
+ while( p < length )
+ {
+ __mpu_char16_t c = input[p];
+
+ if( quote )
+ {
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote )
+ quote = 0;
+ else if( c == '\n' && quote == '\'' )
+ quote = 0;
+
+ ++p;
+ continue;
+ }
+
+ if( is_quote16(c, language) )
+ {
+ quote = c;
+ ++p;
+ continue;
+ }
+
+ if( c == '(' )
+ {
+ ++depth;
+ ++p;
+ continue;
+ }
+
+ if( c == ')' )
+ {
+ if( depth != 0 )
+ {
+ --depth;
+ ++p;
+ continue;
+ }
+
+ if( append_macro_arg(&args, &nargs, &capacity,
+ input + start, p - start) != 0 )
+ goto fail;
+
+ *args_out = args;
+ *nargs_out = nargs;
+ *after_call = p + 1;
+ return( 0 );
+ }
+
+ if( c == ',' && depth == 0 )
+ {
+ if( append_macro_arg(&args, &nargs, &capacity,
+ input + start, p - start) != 0 )
+ goto fail;
+ start = ++p;
+ continue;
+ }
+
+ ++p;
+ }
+
+ errno = EINVAL;
+
+fail:
+ macro_args_free( args, nargs );
+ return( -1 );
+}
+
+
+static int
+prepare_variadic_args( const mcpu_macro *macro,
+ const __mpu_char16_t *input, size_t after_call,
+ mcpu_macro_arg **args_io, size_t *nargs_io )
+{
+ mcpu_macro_arg *args = *args_io;
+ size_t nargs = *nargs_io;
+ size_t fixed = (size_t)macro->nargs;
+ size_t i;
+
+ if( !macro->variadic || nargs < fixed )
+ return( 0 );
+
+ if( nargs == fixed )
+ {
+ mcpu_macro_arg *new_args;
+
+ new_args = (mcpu_macro_arg *)realloc( args,
+ (fixed + 1) * sizeof(*args) );
+ if( new_args == NULL )
+ return( -1 );
+
+ args = new_args;
+ args[fixed].raw = input + after_call - 1;
+ args[fixed].raw_length = 0;
+ mcpu_text_init( &args[fixed].expanded );
+ args[fixed].expanded_ready = 0;
+ nargs = fixed + 1;
+ }
+ else
+ {
+ const __mpu_char16_t *begin = args[fixed].raw;
+ const __mpu_char16_t *end = args[nargs - 1].raw +
+ args[nargs - 1].raw_length;
+
+ for( i = fixed + 1; i < nargs; ++i )
+ mcpu_text_free( &args[i].expanded );
+
+ args[fixed].raw = begin;
+ args[fixed].raw_length = (size_t)(end - begin);
+ nargs = fixed + 1;
+ }
+
+ *args_io = args;
+ *nargs_io = nargs;
+ return( 0 );
+}
+
+
+static int expand_range( mcpu_macro_table *table,
+ const __mpu_char16_t *input, size_t length,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output, unsigned depth );
+
+
+static int
+append_quoted_utf8( mcpu_text *output, const char *s )
+{
+ mcpu_text text;
+ size_t i;
+ int rc = -1;
+
+ if( output == NULL || s == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpu_text_init( &text );
+ if( mcpu_text_from_utf8(&text, s) != 0 ||
+ mcpu_text_append_char(output, '"') != 0 )
+ goto done;
+
+ for( i = 0; i < text.length; ++i )
+ {
+ switch( text.data[i] )
+ {
+ case '\\':
+ case '"':
+ if( mcpu_text_append_char(output, '\\') != 0 ||
+ mcpu_text_append_char(output, text.data[i]) != 0 )
+ goto done;
+ break;
+
+ case '\n':
+ if( mcpu_text_append_ascii(output, "\\n") != 0 )
+ goto done;
+ break;
+
+ case '\r':
+ if( mcpu_text_append_ascii(output, "\\r") != 0 )
+ goto done;
+ break;
+
+ case '\t':
+ if( mcpu_text_append_ascii(output, "\\t") != 0 )
+ goto done;
+ break;
+
+ default:
+ if( mcpu_text_append_char(output, text.data[i]) != 0 )
+ goto done;
+ break;
+ }
+ }
+
+ if( mcpu_text_append_char(output, '"') != 0 )
+ goto done;
+
+ rc = 0;
+
+done:
+ mcpu_text_free( &text );
+ return( rc );
+}
+
+
+static int
+append_unsigned( mcpu_text *output, size_t value )
+{
+ char buf[64];
+
+ snprintf( buf, sizeof(buf), "%lu", (unsigned long)value );
+ return( mcpu_text_append_ascii(output, buf) );
+}
+
+
+static int
+expand_builtin( const mcpu_macro *macro,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output )
+{
+ if( macro == NULL || output == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( macro->builtin >= MCPU_MACRO_BUILTIN_FILE &&
+ macro->builtin <= MCPU_MACRO_BUILTIN_INCLUDE_LEVEL && context == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ switch( macro->builtin )
+ {
+ case MCPU_MACRO_BUILTIN_FILE:
+ return( append_quoted_utf8(output, context->filename ?
+ context->filename : "") );
+
+ case MCPU_MACRO_BUILTIN_LINE:
+ return( append_unsigned(output, context->line_number) );
+
+ case MCPU_MACRO_BUILTIN_DATE:
+ return( append_quoted_utf8(output, context->date ? context->date : "") );
+
+ case MCPU_MACRO_BUILTIN_TIME:
+ return( append_quoted_utf8(output, context->time ? context->time : "") );
+
+ case MCPU_MACRO_BUILTIN_BASE_FILE:
+ return( append_quoted_utf8(output, context->base_filename ?
+ context->base_filename : "") );
+
+ case MCPU_MACRO_BUILTIN_INCLUDE_LEVEL:
+ return( append_unsigned(output, context->include_level) );
+
+ case MCPU_MACRO_BUILTIN_REAL_MAX:
+ case MCPU_MACRO_BUILTIN_REAL_MIN:
+ case MCPU_MACRO_BUILTIN_REAL_EPSILON:
+ case MCPU_MACRO_BUILTIN_REAL_MAX_EXP:
+ case MCPU_MACRO_BUILTIN_REAL_MIN_EXP:
+ case MCPU_MACRO_BUILTIN_REAL_MAX_10_EXP:
+ case MCPU_MACRO_BUILTIN_REAL_MIN_10_EXP:
+ return( mcpu_predefined_expand_builtin(macro->builtin,
+ macro->builtin_bits,
+ output) );
+
+ case MCPU_MACRO_BUILTIN_NONE:
+ default:
+ errno = EINVAL;
+ return( -1 );
+ }
+}
+
+
+static int
+ensure_arg_expanded( mcpu_macro_table *table, mcpu_macro_arg *arg,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth )
+{
+ const __mpu_char16_t *raw;
+ size_t raw_length;
+
+ if( arg->expanded_ready )
+ return( 0 );
+
+ raw = arg->raw;
+ raw_length = arg->raw_length;
+
+ while( raw_length != 0 && is_space16(*raw) )
+ {
+ ++raw;
+ --raw_length;
+ }
+ while( raw_length != 0 && is_space16(raw[raw_length - 1]) )
+ --raw_length;
+
+ if( expand_range(table, raw, raw_length,
+ language, context,
+ &arg->expanded, depth + 1) != 0 )
+ return( -1 );
+
+ arg->expanded_ready = 1;
+ return( 0 );
+}
+
+
+static int
+append_stringified_arg( mcpu_text *output,
+ const __mpu_char16_t *raw, size_t raw_length,
+ enum mcpu_language language )
+{
+ size_t p = 0;
+ __mpu_char16_t quote = 0;
+ int escaped = 0;
+ int pending_space = 0;
+
+ while( raw_length != 0 && is_space16(*raw) )
+ {
+ ++raw;
+ --raw_length;
+ }
+ while( raw_length != 0 && is_space16(raw[raw_length - 1]) )
+ --raw_length;
+
+ if( mcpu_text_append_char(output, '"') != 0 )
+ return( -1 );
+
+ while( p < raw_length )
+ {
+ __mpu_char16_t c = raw[p++];
+
+ if( quote == 0 && is_space16(c) )
+ {
+ pending_space = 1;
+ continue;
+ }
+
+ if( pending_space )
+ {
+ if( mcpu_text_append_char(output, ' ') != 0 )
+ return( -1 );
+ pending_space = 0;
+ }
+
+ if( c == '"' || (quote != 0 && c == '\\') )
+ {
+ if( mcpu_text_append_char(output, '\\') != 0 )
+ return( -1 );
+ }
+
+ if( mcpu_text_append_char(output, c) != 0 )
+ return( -1 );
+
+ if( quote != 0 )
+ {
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote )
+ quote = 0;
+ }
+ else if( is_quote16(c, language) )
+ {
+ quote = c;
+ escaped = 0;
+ }
+ }
+
+ return( mcpu_text_append_char(output, '"') );
+}
+
+
+/***************************************************************
+ Token concatenation works with
+ preprocessing tokens rather than plain character concatenation.
+ Parameters adjacent to ## are inserted without prescan, empty
+ arguments act as placemarkers, pasted tokens are formed first,
+ and the resulting replacement list is rescanned.
+ ***************************************************************/
+enum mcpu_macro_piece_kind
+{
+ MCPU_MACRO_PIECE_TOKEN = 0,
+ MCPU_MACRO_PIECE_SPACE,
+ MCPU_MACRO_PIECE_PASTE,
+ MCPU_MACRO_PIECE_PLACEMARKER
+};
+
+typedef struct mcpu_macro_piece mcpu_macro_piece;
+struct mcpu_macro_piece
+{
+ enum mcpu_macro_piece_kind kind;
+ mcpu_text text;
+};
+
+typedef struct mcpu_macro_piece_list mcpu_macro_piece_list;
+struct mcpu_macro_piece_list
+{
+ mcpu_macro_piece *piece;
+ size_t count;
+ size_t capacity;
+};
+
+
+static void
+macro_piece_list_init( mcpu_macro_piece_list *list )
+{
+ if( list )
+ memset( list, 0, sizeof(*list) );
+}
+
+
+static void
+macro_piece_list_free( mcpu_macro_piece_list *list )
+{
+ size_t i;
+
+ if( list == NULL )
+ return;
+
+ for( i = 0; i < list->count; ++i )
+ mcpu_text_free( &list->piece[i].text );
+
+ free( list->piece );
+ memset( list, 0, sizeof(*list) );
+}
+
+
+static int
+macro_piece_list_reserve( mcpu_macro_piece_list *list, size_t need )
+{
+ mcpu_macro_piece *p;
+ size_t capacity;
+
+ if( need <= list->capacity )
+ return( 0 );
+
+ capacity = list->capacity ? list->capacity : 16;
+ while( capacity < need )
+ {
+ if( capacity > SIZE_MAX / 2 )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+ capacity *= 2;
+ }
+
+ if( capacity > SIZE_MAX / sizeof(*p) )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ p = (mcpu_macro_piece *)realloc( list->piece,
+ capacity * sizeof(*p) );
+ if( p == NULL )
+ return( -1 );
+
+ list->piece = p;
+ list->capacity = capacity;
+ return( 0 );
+}
+
+
+static int
+macro_piece_list_append( mcpu_macro_piece_list *list,
+ enum mcpu_macro_piece_kind kind,
+ const __mpu_char16_t *text, size_t length )
+{
+ mcpu_macro_piece *piece;
+
+ if( macro_piece_list_reserve(list, list->count + 1) != 0 )
+ return( -1 );
+
+ piece = &list->piece[list->count];
+ piece->kind = kind;
+ mcpu_text_init( &piece->text );
+
+ if( length != 0 && mcpu_text_append(&piece->text, text, length) != 0 )
+ {
+ mcpu_text_free( &piece->text );
+ return( -1 );
+ }
+
+ ++list->count;
+ return( 0 );
+}
+
+
+static int
+macro_piece_list_append_piece( mcpu_macro_piece_list *list,
+ const mcpu_macro_piece *piece )
+{
+ return( macro_piece_list_append(list, piece->kind,
+ piece->text.data, piece->text.length) );
+}
+
+
+static void
+macro_piece_list_erase( mcpu_macro_piece_list *list,
+ size_t first, size_t count )
+{
+ size_t i;
+
+ if( count == 0 || first >= list->count )
+ return;
+
+ if( count > list->count - first )
+ count = list->count - first;
+
+ for( i = first; i < first + count; ++i )
+ mcpu_text_free( &list->piece[i].text );
+
+ if( first + count < list->count )
+ memmove( list->piece + first,
+ list->piece + first + count,
+ (list->count - first - count) * sizeof(*list->piece) );
+
+ list->count -= count;
+}
+
+
+static int
+macro_is_digit16( __mpu_char16_t c )
+{
+ return( c >= '0' && c <= '9' );
+}
+
+
+static size_t
+macro_pp_number_end( const __mpu_char16_t *text, size_t length, size_t p )
+{
+ size_t q = p + 1;
+
+ while( q < length )
+ {
+ __mpu_char16_t c = text[q];
+
+ if( mcpu_pp_is_identifier_char(c) || c == '.' )
+ {
+ ++q;
+ continue;
+ }
+
+ if( (c == '+' || c == '-') && q != p )
+ {
+ __mpu_char16_t prev = text[q - 1];
+
+ if( prev == 'e' || prev == 'E' || prev == 'p' || prev == 'P' )
+ {
+ ++q;
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ return( q );
+}
+
+
+static size_t
+macro_quote_end( const __mpu_char16_t *text, size_t length, size_t p )
+{
+ __mpu_char16_t quote = text[p++];
+ int escaped = 0;
+
+ while( p < length )
+ {
+ __mpu_char16_t c = text[p++];
+
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote || c == '\n' )
+ break;
+ }
+
+ return( p );
+}
+
+
+static size_t
+macro_literal_prefix_length( const __mpu_char16_t *text, size_t length,
+ size_t p, enum mcpu_language language )
+{
+ size_t q = p;
+
+ if( q + 2 < length && text[q] == 'u' && text[q + 1] == '8' &&
+ (text[q + 2] == '"' ||
+ (text[q + 2] == '\'' && language != MCPU_LANG_DIFF)) )
+ return( 2 );
+
+ if( q + 1 < length &&
+ (text[q] == 'L' || text[q] == 'u' || text[q] == 'U') &&
+ (text[q + 1] == '"' ||
+ (text[q + 1] == '\'' && language != MCPU_LANG_DIFF)) )
+ return( 1 );
+
+ return( 0 );
+}
+
+
+static int
+macro_ascii_at( const __mpu_char16_t *text, size_t length,
+ size_t p, const char *s )
+{
+ size_t i = 0;
+
+ while( s[i] )
+ {
+ if( p + i >= length ||
+ text[p + i] != (__mpu_char16_t)(unsigned char)s[i] )
+ return( 0 );
+ ++i;
+ }
+
+ return( 1 );
+}
+
+
+static size_t
+macro_punctuator_length( const __mpu_char16_t *text,
+ size_t length, size_t p )
+{
+ static const char *const punctuators[] =
+ {
+ "%:%:",
+ ">>=", "<<=", "...",
+ "->", "++", "--", "<<", ">>", "<=", ">=", "==", "!=",
+ "&&", "||", "*=", "/=", "%=", "+=", "-=", "&=", "^=", "|=",
+ "<:", ":>", "<%", "%>", "%:", "##",
+ "[", "]", "(", ")", "{", "}", ".", "&", "*", "+", "-",
+ "~", "!", "/", "%", "<", ">", "^", "|", "?", ":", ";",
+ "=", ",", "#",
+ NULL
+ };
+ size_t i;
+
+ for( i = 0; punctuators[i] != NULL; ++i )
+ {
+ size_t n = strlen( punctuators[i] );
+
+ if( macro_ascii_at(text, length, p, punctuators[i]) )
+ return( n );
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_piece_list_tokenize( mcpu_macro_piece_list *list,
+ const __mpu_char16_t *text, size_t length,
+ enum mcpu_language language,
+ int recognize_paste )
+{
+ size_t p = 0;
+
+ while( p < length )
+ {
+ size_t q;
+
+ if( is_space16(text[p]) )
+ {
+ q = p + 1;
+ while( q < length && is_space16(text[q]) )
+ ++q;
+
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_SPACE,
+ text + p, q - p) != 0 )
+ return( -1 );
+ p = q;
+ continue;
+ }
+
+ if( recognize_paste && p + 1 < length &&
+ text[p] == '#' && text[p + 1] == '#' )
+ {
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_PASTE,
+ NULL, 0) != 0 )
+ return( -1 );
+ p += 2;
+ continue;
+ }
+
+ {
+ size_t prefix_length;
+
+ prefix_length = macro_literal_prefix_length(text, length, p, language);
+ if( prefix_length != 0 )
+ {
+ q = macro_quote_end( text, length, p + prefix_length );
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_TOKEN,
+ text + p, q - p) != 0 )
+ return( -1 );
+ p = q;
+ continue;
+ }
+ }
+
+ if( is_quote16(text[p], language) )
+ {
+ q = macro_quote_end( text, length, p );
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_TOKEN,
+ text + p, q - p) != 0 )
+ return( -1 );
+ p = q;
+ continue;
+ }
+
+ if( mcpu_pp_is_identifier_start(text[p]) )
+ {
+ q = p + 1;
+ while( q < length && mcpu_pp_is_identifier_char(text[q]) )
+ ++q;
+
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_TOKEN,
+ text + p, q - p) != 0 )
+ return( -1 );
+ p = q;
+ continue;
+ }
+
+ if( macro_is_digit16(text[p]) ||
+ (text[p] == '.' && p + 1 < length && macro_is_digit16(text[p + 1])) )
+ {
+ q = macro_pp_number_end( text, length, p );
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_TOKEN,
+ text + p, q - p) != 0 )
+ return( -1 );
+ p = q;
+ continue;
+ }
+
+ q = macro_punctuator_length( text, length, p );
+ if( q != 0 )
+ {
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_TOKEN,
+ text + p, q) != 0 )
+ return( -1 );
+ p += q;
+ continue;
+ }
+
+ if( macro_piece_list_append(list, MCPU_MACRO_PIECE_TOKEN,
+ text + p, 1) != 0 )
+ return( -1 );
+ ++p;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_piece_is_ascii( const mcpu_macro_piece *piece, const char *s )
+{
+ if( piece == NULL || piece->kind != MCPU_MACRO_PIECE_TOKEN )
+ return( 0 );
+
+ return( mcpu_text_equal_ascii(piece->text.data, piece->text.length, s) );
+}
+
+
+static int
+macro_replacement_has_paste( const mcpu_macro *macro,
+ enum mcpu_language language )
+{
+ size_t p = 0;
+ __mpu_char16_t quote = 0;
+ int escaped = 0;
+
+ while( p < macro->replacement_length )
+ {
+ __mpu_char16_t c = macro->replacement[p];
+
+ if( quote )
+ {
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote || c == '\n' )
+ quote = 0;
+ ++p;
+ continue;
+ }
+
+ if( is_quote16(c, language) )
+ {
+ quote = c;
+ ++p;
+ continue;
+ }
+
+ if( c == '#' && p + 1 < macro->replacement_length &&
+ macro->replacement[p + 1] == '#' )
+ return( 1 );
+
+ ++p;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_replacement_has_va_opt( const mcpu_macro *macro,
+ enum mcpu_language language )
+{
+ size_t p = 0;
+ __mpu_char16_t quote = 0;
+ int escaped = 0;
+
+ while( p < macro->replacement_length )
+ {
+ __mpu_char16_t c = macro->replacement[p];
+
+ if( quote )
+ {
+ if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote || c == '\n' )
+ quote = 0;
+ ++p;
+ continue;
+ }
+
+ if( is_quote16(c, language) )
+ {
+ quote = c;
+ ++p;
+ continue;
+ }
+
+ if( mcpu_pp_is_identifier_start(c) )
+ {
+ size_t q = p + 1;
+
+ while( q < macro->replacement_length &&
+ mcpu_pp_is_identifier_char(macro->replacement[q]) )
+ ++q;
+
+ if( mcpu_text_equal_ascii(macro->replacement + p, q - p,
+ "__VA_OPT__") )
+ return( 1 );
+
+ p = q;
+ continue;
+ }
+
+ ++p;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_piece_parameter_index( const mcpu_macro *macro,
+ const mcpu_macro_piece *piece )
+{
+ if( piece == NULL || piece->kind != MCPU_MACRO_PIECE_TOKEN )
+ return( -1 );
+
+ return( macro_argument_index(macro, piece->text.data, piece->text.length) );
+}
+
+
+static int
+macro_piece_parameter_is_pasted( const mcpu_macro_piece_list *replacement,
+ size_t index )
+{
+ size_t p;
+
+ p = index;
+ while( p != 0 )
+ {
+ --p;
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_SPACE )
+ continue;
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_PASTE )
+ return( 1 );
+ break;
+ }
+
+ p = index + 1;
+ while( p < replacement->count )
+ {
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_SPACE )
+ {
+ ++p;
+ continue;
+ }
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_PASTE )
+ return( 1 );
+ break;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_append_raw_argument( mcpu_macro_piece_list *list,
+ const mcpu_macro_arg *arg,
+ enum mcpu_language language )
+{
+ const __mpu_char16_t *raw = arg->raw;
+ size_t raw_length = arg->raw_length;
+
+ while( raw_length != 0 && is_space16(*raw) )
+ {
+ ++raw;
+ --raw_length;
+ }
+ while( raw_length != 0 && is_space16(raw[raw_length - 1]) )
+ --raw_length;
+
+ if( raw_length == 0 )
+ return( macro_piece_list_append(list, MCPU_MACRO_PIECE_PLACEMARKER,
+ NULL, 0) );
+
+ return( macro_piece_list_tokenize(list, raw, raw_length,
+ language, 0) );
+}
+
+
+static int
+macro_append_expanded_argument( mcpu_macro_table *table,
+ mcpu_macro_piece_list *list,
+ mcpu_macro_arg *arg,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth )
+{
+ if( ensure_arg_expanded(table, arg, language, context, depth) != 0 )
+ return( -1 );
+
+ return( macro_piece_list_tokenize(list,
+ arg->expanded.data,
+ arg->expanded.length,
+ language, 0) );
+}
+
+
+static int
+macro_append_stringified_argument( mcpu_macro_piece_list *list,
+ const mcpu_macro_arg *arg,
+ enum mcpu_language language )
+{
+ mcpu_text stringified;
+ int rc;
+
+ mcpu_text_init( &stringified );
+ rc = append_stringified_arg( &stringified,
+ arg->raw, arg->raw_length,
+ language );
+ if( rc == 0 )
+ rc = macro_piece_list_append( list, MCPU_MACRO_PIECE_TOKEN,
+ stringified.data, stringified.length );
+
+ mcpu_text_free( &stringified );
+ return( rc );
+}
+
+
+static int macro_resolve_pastes(
+ mcpu_macro_piece_list *list,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context );
+static int macro_pieces_to_text(
+ const mcpu_macro_piece_list *list, mcpu_text *text );
+
+
+static int
+macro_va_opt_bounds( const mcpu_macro_piece_list *replacement,
+ size_t index, size_t limit,
+ size_t *content_start, size_t *content_end,
+ size_t *after )
+{
+ size_t p;
+ int depth;
+
+ if( replacement == NULL || index >= limit ||
+ !macro_piece_is_ascii(&replacement->piece[index], "__VA_OPT__") )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ p = index + 1;
+ while( p < limit && replacement->piece[p].kind == MCPU_MACRO_PIECE_SPACE )
+ ++p;
+
+ if( p >= limit || !macro_piece_is_ascii(&replacement->piece[p], "(") )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ *content_start = ++p;
+ depth = 1;
+ while( p < limit )
+ {
+ if( macro_piece_is_ascii(&replacement->piece[p], "(") )
+ ++depth;
+ else if( macro_piece_is_ascii(&replacement->piece[p], ")") )
+ {
+ --depth;
+ if( depth == 0 )
+ {
+ *content_end = p;
+ *after = p + 1;
+ return( 0 );
+ }
+ }
+ ++p;
+ }
+
+ errno = EINVAL;
+ return( -1 );
+}
+
+
+static int
+macro_va_opt_is_pasted( const mcpu_macro_piece_list *replacement,
+ size_t index, size_t after, size_t limit )
+{
+ size_t p;
+
+ p = index;
+ while( p != 0 )
+ {
+ --p;
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_SPACE )
+ continue;
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_PASTE )
+ return( 1 );
+ break;
+ }
+
+ p = after;
+ while( p < limit )
+ {
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_SPACE )
+ {
+ ++p;
+ continue;
+ }
+ if( replacement->piece[p].kind == MCPU_MACRO_PIECE_PASTE )
+ return( 1 );
+ break;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_piece_list_has_token( const mcpu_macro_piece_list *list )
+{
+ size_t i;
+
+ for( i = 0; i < list->count; ++i )
+ {
+ if( list->piece[i].kind == MCPU_MACRO_PIECE_TOKEN )
+ return( 1 );
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_piece_list_append_range( mcpu_macro_piece_list *dst,
+ const mcpu_macro_piece_list *src )
+{
+ size_t i;
+
+ for( i = 0; i < src->count; ++i )
+ {
+ if( macro_piece_list_append_piece(dst, &src->piece[i]) != 0 )
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_variadic_has_tokens( mcpu_macro_table *table,
+ const mcpu_macro *macro,
+ mcpu_macro_arg *args,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth )
+{
+ mcpu_macro_arg *arg;
+ size_t i;
+
+ if( !macro->variadic )
+ return( 0 );
+
+ arg = &args[macro->nargs];
+ if( ensure_arg_expanded(table, arg, language, context, depth) != 0 )
+ return( -1 );
+
+ for( i = 0; i < arg->expanded.length; ++i )
+ {
+ if( !is_space16(arg->expanded.data[i]) )
+ return( 1 );
+ }
+
+ return( 0 );
+}
+
+
+static int macro_substitute_piece_range(
+ mcpu_macro_table *table,
+ mcpu_macro *macro,
+ mcpu_macro_arg *args,
+ const mcpu_macro_piece_list *replacement,
+ size_t begin, size_t end,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth,
+ mcpu_macro_piece_list *substituted );
+
+
+static int
+macro_append_stringified_va_opt( mcpu_macro_table *table,
+ mcpu_macro *macro,
+ mcpu_macro_arg *args,
+ const mcpu_macro_piece_list *replacement,
+ size_t content_start, size_t content_end,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth,
+ mcpu_macro_piece_list *substituted )
+{
+ mcpu_macro_piece_list content;
+ mcpu_text text;
+ mcpu_text stringified;
+ int nonempty;
+ int rc = -1;
+
+ macro_piece_list_init( &content );
+ mcpu_text_init( &text );
+ mcpu_text_init( &stringified );
+
+ nonempty = macro_variadic_has_tokens(table, macro, args,
+ language, context, depth);
+ if( nonempty < 0 )
+ goto done;
+
+ if( nonempty )
+ {
+ if( macro_substitute_piece_range(table, macro, args, replacement,
+ content_start, content_end,
+ language, context, depth,
+ &content) != 0 ||
+ macro_resolve_pastes(&content, language, context) != 0 ||
+ macro_pieces_to_text(&content, &text) != 0 )
+ goto done;
+ }
+
+ if( append_stringified_arg(&stringified,
+ text.data, text.length, language) != 0 ||
+ macro_piece_list_append(substituted, MCPU_MACRO_PIECE_TOKEN,
+ stringified.data, stringified.length) != 0 )
+ goto done;
+
+ rc = 0;
+
+done:
+ mcpu_text_free( &stringified );
+ mcpu_text_free( &text );
+ macro_piece_list_free( &content );
+ return( rc );
+}
+
+
+static int
+macro_substitute_piece_range( mcpu_macro_table *table,
+ mcpu_macro *macro,
+ mcpu_macro_arg *args,
+ const mcpu_macro_piece_list *replacement,
+ size_t begin, size_t end,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth,
+ mcpu_macro_piece_list *substituted )
+{
+ size_t i = begin;
+
+ while( i < end )
+ {
+ const mcpu_macro_piece *piece = &replacement->piece[i];
+ int argno;
+
+ if( piece->kind == MCPU_MACRO_PIECE_SPACE ||
+ piece->kind == MCPU_MACRO_PIECE_PASTE )
+ {
+ if( macro_piece_list_append_piece(substituted, piece) != 0 )
+ return( -1 );
+ ++i;
+ continue;
+ }
+
+ if( macro_piece_is_ascii(piece, "#") )
+ {
+ size_t j = i + 1;
+
+ while( j < end &&
+ replacement->piece[j].kind == MCPU_MACRO_PIECE_SPACE )
+ ++j;
+
+ if( j >= end )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ argno = macro_piece_parameter_index( macro, &replacement->piece[j] );
+ if( argno >= 0 )
+ {
+ if( macro_append_stringified_argument(substituted,
+ &args[argno], language) != 0 )
+ return( -1 );
+ i = j + 1;
+ continue;
+ }
+
+ if( macro_piece_is_ascii(&replacement->piece[j], "__VA_OPT__") )
+ {
+ size_t content_start;
+ size_t content_end;
+ size_t after;
+
+ if( macro_va_opt_bounds(replacement, j, end,
+ &content_start, &content_end, &after) != 0 ||
+ macro_append_stringified_va_opt(table, macro, args, replacement,
+ content_start, content_end,
+ language, context, depth,
+ substituted) != 0 )
+ return( -1 );
+
+ i = after;
+ continue;
+ }
+
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( macro_piece_is_ascii(piece, "__VA_OPT__") )
+ {
+ mcpu_macro_piece_list content;
+ size_t content_start;
+ size_t content_end;
+ size_t after;
+ int nonempty;
+ int pasted;
+ int rc = -1;
+
+ macro_piece_list_init( &content );
+ if( macro_va_opt_bounds(replacement, i, end,
+ &content_start, &content_end, &after) != 0 )
+ goto va_done;
+
+ nonempty = macro_variadic_has_tokens(table, macro, args,
+ language, context, depth);
+ if( nonempty < 0 )
+ goto va_done;
+
+ pasted = macro_va_opt_is_pasted(replacement, i, after, end);
+ if( nonempty )
+ {
+ if( macro_substitute_piece_range(table, macro, args, replacement,
+ content_start, content_end,
+ language, context, depth,
+ &content) != 0 ||
+ macro_resolve_pastes(&content, language, context) != 0 )
+ goto va_done;
+
+ if( pasted && !macro_piece_list_has_token(&content) )
+ {
+ if( macro_piece_list_append(substituted,
+ MCPU_MACRO_PIECE_PLACEMARKER,
+ NULL, 0) != 0 )
+ goto va_done;
+ }
+ else if( macro_piece_list_append_range(substituted, &content) != 0 )
+ goto va_done;
+ }
+ else if( pasted &&
+ macro_piece_list_append(substituted,
+ MCPU_MACRO_PIECE_PLACEMARKER,
+ NULL, 0) != 0 )
+ goto va_done;
+
+ rc = 0;
+
+va_done:
+ macro_piece_list_free( &content );
+ if( rc != 0 )
+ return( -1 );
+ i = after;
+ continue;
+ }
+
+ argno = macro_piece_parameter_index( macro, piece );
+ if( argno >= 0 )
+ {
+ if( macro_piece_parameter_is_pasted(replacement, i) )
+ {
+ if( macro_append_raw_argument(substituted,
+ &args[argno], language) != 0 )
+ return( -1 );
+ }
+ else
+ {
+ if( macro_append_expanded_argument(table, substituted,
+ &args[argno], language,
+ context, depth) != 0 )
+ return( -1 );
+ }
+
+ ++i;
+ continue;
+ }
+
+ if( macro_piece_list_append_piece(substituted, piece) != 0 )
+ return( -1 );
+ ++i;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_build_function_pieces( mcpu_macro_table *table,
+ mcpu_macro *macro,
+ mcpu_macro_arg *args,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ unsigned depth,
+ mcpu_macro_piece_list *substituted )
+{
+ mcpu_macro_piece_list replacement;
+ int rc = -1;
+
+ macro_piece_list_init( &replacement );
+ if( macro_piece_list_tokenize(&replacement,
+ macro->replacement,
+ macro->replacement_length,
+ language, 1) != 0 )
+ goto done;
+
+ if( macro_substitute_piece_range(table, macro, args, &replacement,
+ 0, replacement.count,
+ language, context, depth,
+ substituted) != 0 )
+ goto done;
+
+ rc = 0;
+
+done:
+ macro_piece_list_free( &replacement );
+ return( rc );
+}
+
+
+static void
+macro_normalize_paste_spacing( mcpu_macro_piece_list *list )
+{
+ size_t i = 0;
+
+ while( i < list->count )
+ {
+ if( list->piece[i].kind == MCPU_MACRO_PIECE_SPACE &&
+ ((i != 0 && list->piece[i - 1].kind == MCPU_MACRO_PIECE_PASTE) ||
+ (i + 1 < list->count &&
+ list->piece[i + 1].kind == MCPU_MACRO_PIECE_PASTE)) )
+ {
+ macro_piece_list_erase( list, i, 1 );
+ if( i != 0 )
+ --i;
+ continue;
+ }
+ ++i;
+ }
+
+ i = 1;
+ while( i < list->count )
+ {
+ if( list->piece[i - 1].kind == MCPU_MACRO_PIECE_PASTE &&
+ list->piece[i].kind == MCPU_MACRO_PIECE_PASTE )
+ {
+ /***********************************************************
+ GNU CPP accepts a run of adjacent ## operators as one paste
+ operator. Preserve that long-standing behavior.
+ ***********************************************************/
+ macro_piece_list_erase( list, i, 1 );
+ continue;
+ }
+ ++i;
+ }
+}
+
+
+static int
+macro_paste_is_single_token( const mcpu_text *text,
+ enum mcpu_language language )
+{
+ mcpu_macro_piece_list tokens;
+ int valid;
+
+ macro_piece_list_init( &tokens );
+ if( macro_piece_list_tokenize(&tokens,
+ text->data, text->length,
+ language, 0) != 0 )
+ {
+ macro_piece_list_free( &tokens );
+ return( -1 );
+ }
+
+ valid = tokens.count == 1 &&
+ tokens.piece[0].kind == MCPU_MACRO_PIECE_TOKEN;
+ macro_piece_list_free( &tokens );
+ return( valid );
+}
+
+
+static int
+macro_warn_invalid_paste( const mcpu_macro_piece *left,
+ const mcpu_macro_piece *right,
+ const mcpu_macro_expansion_context *context )
+{
+ char *l = mcpu_text_to_utf8( left->text.data, left->text.length );
+ char *r = mcpu_text_to_utf8( right->text.data, right->text.length );
+
+ int rc;
+
+ rc = mcpp_diagnostic_warning(
+ context ? context->options : NULL,
+ context && context->filename ? context->filename : "<input>",
+ context ? context->line_number : 0,
+ "pasting \"%s\" and \"%s\" does not give a valid preprocessing token",
+ l ? l : "?", r ? r : "?" );
+
+ free( l );
+ free( r );
+ return( rc );
+}
+
+
+static int
+macro_resolve_pastes( mcpu_macro_piece_list *list,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context )
+{
+ size_t i;
+
+ macro_normalize_paste_spacing( list );
+
+ for( ;; )
+ {
+ for( i = 0; i < list->count; ++i )
+ {
+ if( list->piece[i].kind == MCPU_MACRO_PIECE_PASTE )
+ break;
+ }
+
+ if( i == list->count )
+ break;
+
+ if( i == 0 || i + 1 >= list->count ||
+ list->piece[i - 1].kind == MCPU_MACRO_PIECE_SPACE ||
+ list->piece[i + 1].kind == MCPU_MACRO_PIECE_SPACE ||
+ list->piece[i - 1].kind == MCPU_MACRO_PIECE_PASTE ||
+ list->piece[i + 1].kind == MCPU_MACRO_PIECE_PASTE )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( list->piece[i - 1].kind == MCPU_MACRO_PIECE_PLACEMARKER &&
+ list->piece[i + 1].kind == MCPU_MACRO_PIECE_PLACEMARKER )
+ {
+ macro_piece_list_erase( list, i, 2 );
+ continue;
+ }
+
+ if( list->piece[i - 1].kind == MCPU_MACRO_PIECE_PLACEMARKER )
+ {
+ macro_piece_list_erase( list, i - 1, 2 );
+ continue;
+ }
+
+ if( list->piece[i + 1].kind == MCPU_MACRO_PIECE_PLACEMARKER )
+ {
+ macro_piece_list_erase( list, i, 2 );
+ continue;
+ }
+
+ {
+ mcpu_text pasted;
+ int valid;
+
+ mcpu_text_init( &pasted );
+ if( mcpu_text_append(&pasted,
+ list->piece[i - 1].text.data,
+ list->piece[i - 1].text.length) != 0 ||
+ mcpu_text_append(&pasted,
+ list->piece[i + 1].text.data,
+ list->piece[i + 1].text.length) != 0 )
+ {
+ mcpu_text_free( &pasted );
+ return( -1 );
+ }
+
+ valid = macro_paste_is_single_token( &pasted, language );
+ if( valid < 0 )
+ {
+ mcpu_text_free( &pasted );
+ return( -1 );
+ }
+
+ if( !valid )
+ {
+ /***********************************************************
+ GNU CPP documents this case as a diagnostic followed by
+ emission of the two original tokens. Whitespace between
+ them is unspecified, so keep them adjacent and discard ##.
+ ***********************************************************/
+ if( macro_warn_invalid_paste(&list->piece[i - 1],
+ &list->piece[i + 1], context) != 0 )
+ {
+ mcpu_text_free( &pasted );
+ return( -1 );
+ }
+ mcpu_text_free( &pasted );
+ macro_piece_list_erase( list, i, 1 );
+ continue;
+ }
+
+ mcpu_text_free( &list->piece[i - 1].text );
+ list->piece[i - 1].text = pasted;
+ list->piece[i - 1].kind = MCPU_MACRO_PIECE_TOKEN;
+ macro_piece_list_erase( list, i, 2 );
+ }
+ }
+
+ i = 0;
+ while( i < list->count )
+ {
+ if( list->piece[i].kind == MCPU_MACRO_PIECE_PLACEMARKER )
+ {
+ macro_piece_list_erase( list, i, 1 );
+ continue;
+ }
+ ++i;
+ }
+
+ return( 0 );
+}
+
+
+static int
+macro_pieces_to_text( const mcpu_macro_piece_list *list, mcpu_text *text )
+{
+ size_t i;
+ int previous_space = 0;
+
+ for( i = 0; i < list->count; ++i )
+ {
+ if( list->piece[i].kind == MCPU_MACRO_PIECE_PASTE ||
+ list->piece[i].kind == MCPU_MACRO_PIECE_PLACEMARKER )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( list->piece[i].kind == MCPU_MACRO_PIECE_SPACE )
+ {
+ /***********************************************************
+ Removal of an empty parameter, placemarker or __VA_OPT__
+ fragment can make two replacement-list space pieces
+ adjacent. They are one logical whitespace separator.
+
+ Preserve the text of a single space piece: it may come from
+ inside an actual macro argument, whose formatting is not part
+ of replacement-list normalization.
+ ***********************************************************/
+ if( previous_space )
+ continue;
+ previous_space = 1;
+ }
+ else
+ previous_space = 0;
+
+ if( mcpu_text_append(text,
+ list->piece[i].text.data,
+ list->piece[i].text.length) != 0 )
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+
+static int
+expand_function_macro_with_paste( mcpu_macro_table *table,
+ mcpu_macro *macro,
+ mcpu_macro_arg *args,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output, unsigned depth )
+{
+ mcpu_macro_piece_list pieces;
+ mcpu_text substituted;
+ int rc = -1;
+
+ macro_piece_list_init( &pieces );
+ mcpu_text_init( &substituted );
+
+ if( macro_build_function_pieces(table, macro, args,
+ language, context, depth,
+ &pieces) != 0 ||
+ macro_resolve_pastes(&pieces, language, context) != 0 ||
+ macro_pieces_to_text(&pieces, &substituted) != 0 )
+ goto done;
+
+ macro->expanding = 1;
+ rc = expand_range( table, substituted.data, substituted.length,
+ language, context, output, depth + 1 );
+ macro->expanding = 0;
+
+done:
+ macro->expanding = 0;
+ mcpu_text_free( &substituted );
+ macro_piece_list_free( &pieces );
+ return( rc );
+}
+
+
+static int
+expand_object_macro_with_paste( mcpu_macro_table *table,
+ mcpu_macro *macro,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output, unsigned depth )
+{
+ mcpu_macro_piece_list pieces;
+ mcpu_text substituted;
+ int rc = -1;
+
+ macro_piece_list_init( &pieces );
+ mcpu_text_init( &substituted );
+
+ if( macro_piece_list_tokenize(&pieces,
+ macro->replacement,
+ macro->replacement_length,
+ language, 1) != 0 ||
+ macro_resolve_pastes(&pieces, language, context) != 0 ||
+ macro_pieces_to_text(&pieces, &substituted) != 0 )
+ goto done;
+
+ macro->expanding = 1;
+ rc = expand_range( table, substituted.data, substituted.length,
+ language, context, output, depth + 1 );
+ macro->expanding = 0;
+
+done:
+ macro->expanding = 0;
+ mcpu_text_free( &substituted );
+ macro_piece_list_free( &pieces );
+ return( rc );
+}
+
+
+static int
+expand_function_macro( mcpu_macro_table *table, mcpu_macro *macro,
+ mcpu_macro_arg *args, size_t nargs,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output, unsigned depth )
+{
+ mcpu_text substituted;
+ size_t p = 0;
+ size_t i;
+ int rc = -1;
+
+ if( !macro->variadic && macro->nargs == 0 && nargs == 1 )
+ {
+ int all_space = 1;
+
+ for( i = 0; i < args[0].raw_length; ++i )
+ {
+ if( !is_space16(args[0].raw[i]) )
+ {
+ all_space = 0;
+ break;
+ }
+ }
+
+ if( all_space )
+ nargs = 0;
+ }
+
+ if( nargs < (size_t)macro->nargs + (macro->variadic ? 1U : 0U) )
+ {
+ char *name = mcpu_text_to_utf8( macro->name, macro->name_length );
+ fprintf( stderr, "%s:%u: error: macro '%s' used with too few arguments\n",
+ context && context->filename ? context->filename : "<input>",
+ context ? context->line_number : 0,
+ name ? name : "?" );
+ free( name );
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( !macro->variadic && nargs > (size_t)macro->nargs )
+ {
+ char *name = mcpu_text_to_utf8( macro->name, macro->name_length );
+ fprintf( stderr, "%s:%u: error: macro '%s' used with too many arguments\n",
+ context && context->filename ? context->filename : "<input>",
+ context ? context->line_number : 0,
+ name ? name : "?" );
+ free( name );
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( macro_replacement_has_paste(macro, language) ||
+ macro_replacement_has_va_opt(macro, language) )
+ return( expand_function_macro_with_paste(table, macro, args,
+ language, context,
+ output, depth) );
+
+ mcpu_text_init( &substituted );
+
+ while( p < macro->replacement_length )
+ {
+ if( is_quote16(macro->replacement[p], language) )
+ {
+ __mpu_char16_t quote = macro->replacement[p];
+ int escaped = 0;
+ int first = 1;
+
+ do
+ {
+ __mpu_char16_t c = macro->replacement[p++];
+
+ if( mcpu_text_append_char(&substituted, c) != 0 )
+ goto substituted_fail;
+
+ if( first )
+ first = 0;
+ else if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote )
+ break;
+ else if( c == '\n' )
+ break;
+ }
+ while( p < macro->replacement_length );
+
+ continue;
+ }
+
+ if( macro->replacement[p] == '#' )
+ {
+ size_t q = p + 1;
+ size_t start;
+ int argno;
+
+ if( q < macro->replacement_length && macro->replacement[q] == '#' )
+ {
+ errno = EINVAL;
+ goto substituted_fail;
+ }
+
+ while( q < macro->replacement_length && is_space16(macro->replacement[q]) )
+ ++q;
+
+ if( q >= macro->replacement_length ||
+ !mcpu_pp_is_identifier_start(macro->replacement[q]) )
+ {
+ errno = EINVAL;
+ goto substituted_fail;
+ }
+
+ start = q++;
+ while( q < macro->replacement_length &&
+ mcpu_pp_is_identifier_char(macro->replacement[q]) )
+ ++q;
+
+ argno = macro_argument_index( macro,
+ macro->replacement + start, q - start );
+ if( argno < 0 )
+ {
+ errno = EINVAL;
+ goto substituted_fail;
+ }
+
+ if( append_stringified_arg(&substituted,
+ args[argno].raw,
+ args[argno].raw_length,
+ language) != 0 )
+ goto substituted_fail;
+
+ p = q;
+ continue;
+ }
+
+ if( mcpu_pp_is_identifier_start(macro->replacement[p]) )
+ {
+ size_t start = p++;
+ int argno;
+
+ while( p < macro->replacement_length &&
+ mcpu_pp_is_identifier_char(macro->replacement[p]) )
+ ++p;
+
+ argno = macro_argument_index( macro,
+ macro->replacement + start, p - start );
+ if( argno >= 0 )
+ {
+ /***********************************************************
+ Ordinary parameter occurrences use
+ the macro-expanded actual argument. Stringified occurrences
+ above deliberately use the raw spelling and therefore do not
+ force this expansion.
+ ***********************************************************/
+ if( ensure_arg_expanded(table, &args[argno], language,
+ context, depth) != 0 )
+ goto substituted_fail;
+
+ if( mcpu_text_append(&substituted,
+ args[argno].expanded.data,
+ args[argno].expanded.length) != 0 )
+ goto substituted_fail;
+ }
+ else if( mcpu_text_append(&substituted,
+ macro->replacement + start, p - start) != 0 )
+ goto substituted_fail;
+
+ continue;
+ }
+
+ if( mcpu_text_append_char(&substituted, macro->replacement[p++]) != 0 )
+ goto substituted_fail;
+ }
+
+ macro->expanding = 1;
+ rc = expand_range( table, substituted.data, substituted.length,
+ language, context,
+ output, depth + 1 );
+
+substituted_fail:
+ mcpu_text_free( &substituted );
+
+ macro->expanding = 0;
+ return( rc );
+}
+
+
+static int
+expand_range( mcpu_macro_table *table,
+ const __mpu_char16_t *input, size_t length,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output, unsigned depth )
+{
+ size_t p = 0;
+
+ if( depth > MCPU_CPP_MACRO_EXPANSION_LIMIT )
+ {
+ errno = ELOOP;
+ return( -1 );
+ }
+
+ while( p < length )
+ {
+ if( is_quote16(input[p], language) )
+ {
+ __mpu_char16_t quote = input[p];
+ int escaped = 0;
+ int first = 1;
+
+ do
+ {
+ __mpu_char16_t c = input[p++];
+
+ if( mcpu_text_append_char(output, c) != 0 )
+ return( -1 );
+
+ if( first )
+ first = 0;
+ else if( escaped )
+ escaped = 0;
+ else if( c == '\\' )
+ escaped = 1;
+ else if( c == quote )
+ break;
+ else if( c == '\n' )
+ break;
+ }
+ while( p < length );
+
+ continue;
+ }
+
+ if( mcpu_pp_is_identifier_start(input[p]) )
+ {
+ size_t start = p++;
+ mcpu_macro *macro;
+
+ while( p < length && mcpu_pp_is_identifier_char(input[p]) )
+ ++p;
+
+ macro = mcpu_macro_find( table, input + start, p - start );
+ if( macro && !macro->expanding )
+ {
+ if( macro->builtin != MCPU_MACRO_BUILTIN_NONE )
+ {
+ if( expand_builtin(macro, context, output) != 0 )
+ return( -1 );
+ continue;
+ }
+
+ if( macro->nargs < 0 )
+ {
+ int rc;
+
+ if( macro_replacement_has_paste(macro, language) )
+ rc = expand_object_macro_with_paste(table, macro,
+ language, context,
+ output, depth);
+ else
+ {
+ macro->expanding = 1;
+ rc = expand_range( table,
+ macro->replacement,
+ macro->replacement_length,
+ language, context,
+ output, depth + 1 );
+ macro->expanding = 0;
+ }
+
+ if( rc != 0 )
+ return( -1 );
+ continue;
+ }
+ else
+ {
+ size_t q = p;
+
+ while( q < length && is_space16(input[q]) )
+ ++q;
+
+ if( q < length && input[q] == '(' )
+ {
+ mcpu_macro_arg *args = NULL;
+ size_t nargs = 0;
+ size_t after_call;
+ int rc;
+
+ if( parse_macro_args(input, length, q, language,
+ &args, &nargs, &after_call) != 0 )
+ {
+ char *name = mcpu_text_to_utf8( macro->name,
+ macro->name_length );
+ fprintf( stderr,
+ "%s:%u: error: unterminated argument list for macro '%s'\n",
+ context && context->filename ? context->filename : "<input>",
+ context ? context->line_number : 0,
+ name ? name : "?" );
+ free( name );
+ return( -1 );
+ }
+
+ if( prepare_variadic_args(macro, input, after_call,
+ &args, &nargs) != 0 )
+ {
+ macro_args_free( args, nargs );
+ return( -1 );
+ }
+
+ rc = expand_function_macro( table, macro, args, nargs,
+ language, context,
+ output, depth );
+ macro_args_free( args, nargs );
+ if( rc != 0 )
+ return( -1 );
+
+ p = after_call;
+ continue;
+ }
+ }
+ }
+
+ if( mcpu_text_append(output, input + start, p - start) != 0 )
+ return( -1 );
+
+ continue;
+ }
+
+ if( mcpu_text_append_char(output, input[p++]) != 0 )
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+
+
+static int
+macro_name_compare( const void *a, const void *b )
+{
+ const mcpu_macro *ma = *(const mcpu_macro * const *)a;
+ const mcpu_macro *mb = *(const mcpu_macro * const *)b;
+ size_t n;
+ size_t i;
+
+ n = ma->name_length < mb->name_length ? ma->name_length : mb->name_length;
+ for( i = 0; i < n; ++i )
+ {
+ if( ma->name[i] < mb->name[i] )
+ return( -1 );
+ if( ma->name[i] > mb->name[i] )
+ return( 1 );
+ }
+
+ if( ma->name_length < mb->name_length )
+ return( -1 );
+ if( ma->name_length > mb->name_length )
+ return( 1 );
+ return( 0 );
+}
+
+
+int
+mcpu_macro_dump( const mcpu_macro_table *table, FILE *stream,
+ int include_predefined )
+{
+ mcpu_macro **list;
+ size_t predefined_count = 0;
+ size_t other_count = 0;
+ size_t count;
+ size_t i;
+ size_t k = 0;
+ size_t other_begin;
+
+ if( table == NULL || stream == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ for( i = 0; i < MCPU_CPP_MACRO_BUCKETS; ++i )
+ {
+ const mcpu_macro *macro;
+ for( macro = table->bucket[i]; macro; macro = macro->next )
+ {
+ /* Context-dependent special macros are not useful in a static dump. */
+ if( macro->builtin != MCPU_MACRO_BUILTIN_NONE &&
+ macro->builtin < MCPU_MACRO_BUILTIN_REAL_MAX )
+ continue;
+
+ if( macro->origin == MCPU_MACRO_ORIGIN_BUILTIN )
+ ++predefined_count;
+ else
+ ++other_count;
+ }
+ }
+
+ count = other_count + (include_predefined ? predefined_count : 0);
+ list = count ? (mcpu_macro **)calloc( count, sizeof(*list) ) : NULL;
+ if( count != 0 && list == NULL )
+ return( -1 );
+
+ if( include_predefined )
+ {
+ for( i = 0; i < MCPU_CPP_MACRO_BUCKETS; ++i )
+ {
+ mcpu_macro *macro;
+ for( macro = table->bucket[i]; macro; macro = macro->next )
+ {
+ if( (macro->builtin == MCPU_MACRO_BUILTIN_NONE ||
+ macro->builtin >= MCPU_MACRO_BUILTIN_REAL_MAX) &&
+ macro->origin == MCPU_MACRO_ORIGIN_BUILTIN )
+ list[k++] = macro;
+ }
+ }
+ }
+
+ other_begin = k;
+ for( i = 0; i < MCPU_CPP_MACRO_BUCKETS; ++i )
+ {
+ mcpu_macro *macro;
+ for( macro = table->bucket[i]; macro; macro = macro->next )
+ {
+ if( (macro->builtin == MCPU_MACRO_BUILTIN_NONE ||
+ macro->builtin >= MCPU_MACRO_BUILTIN_REAL_MAX) &&
+ macro->origin != MCPU_MACRO_ORIGIN_BUILTIN )
+ list[k++] = macro;
+ }
+ }
+
+ if( include_predefined && predefined_count > 1 )
+ qsort( list, predefined_count, sizeof(*list), macro_name_compare );
+ if( other_count > 1 )
+ qsort( list + other_begin, other_count, sizeof(*list), macro_name_compare );
+
+ for( i = 0; i < count; ++i )
+ {
+ mcpu_macro *macro = list[i];
+ char *name = mcpu_text_to_utf8( macro->name, macro->name_length );
+ char *replacement = NULL;
+ mcpu_text generated;
+ size_t replacement_length;
+ int j;
+
+ mcpu_text_init( &generated );
+ if( macro->builtin == MCPU_MACRO_BUILTIN_NONE )
+ {
+ replacement = mcpu_text_to_utf8( macro->replacement,
+ macro->replacement_length );
+ replacement_length = macro->replacement_length;
+ }
+ else
+ {
+ if( expand_builtin(macro, NULL, &generated) != 0 )
+ {
+ free( name );
+ mcpu_text_free( &generated );
+ free( list );
+ return( -1 );
+ }
+ replacement = mcpu_text_to_utf8( generated.data, generated.length );
+ replacement_length = generated.length;
+ }
+
+ if( name == NULL || replacement == NULL )
+ {
+ free( name );
+ free( replacement );
+ mcpu_text_free( &generated );
+ free( list );
+ return( -1 );
+ }
+
+ if( fprintf(stream, "#define %s", name) < 0 )
+ goto io_error;
+
+ if( macro->nargs >= 0 )
+ {
+ if( fputc('(', stream) == EOF )
+ goto io_error;
+ for( j = 0; j < macro->nargs; ++j )
+ {
+ char *arg = mcpu_text_to_utf8( macro->argnames[j],
+ macro->argname_lengths[j] );
+ if( arg == NULL )
+ goto io_error;
+ if( j != 0 && fputc(',', stream) == EOF )
+ {
+ free( arg );
+ goto io_error;
+ }
+ if( fputs(arg, stream) == EOF )
+ {
+ free( arg );
+ goto io_error;
+ }
+ free( arg );
+ }
+ if( macro->variadic )
+ {
+ if( macro->nargs != 0 && fputc(',', stream) == EOF )
+ goto io_error;
+ if( fputs("...", stream) == EOF )
+ goto io_error;
+ }
+ if( fputc(')', stream) == EOF )
+ goto io_error;
+ }
+
+ if( replacement_length != 0 )
+ {
+ if( fputc(' ', stream) == EOF || fputs(replacement, stream) == EOF )
+ goto io_error;
+ }
+
+ if( fputc('\n', stream) == EOF )
+ goto io_error;
+
+ free( name );
+ free( replacement );
+ mcpu_text_free( &generated );
+ continue;
+
+io_error:
+ free( name );
+ free( replacement );
+ mcpu_text_free( &generated );
+ free( list );
+ return( -1 );
+ }
+
+ free( list );
+ return( ferror(stream) ? -1 : 0 );
+}
+
+
+int
+mcpu_macro_dump_text( const mcpu_macro_table *table, mcpu_text *output,
+ const char *line_filename )
+{
+ mcpu_macro **list;
+ size_t count = 0;
+ size_t i;
+ size_t k = 0;
+
+ if( table == NULL || output == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ for( i = 0; i < MCPU_CPP_MACRO_BUCKETS; ++i )
+ {
+ const mcpu_macro *macro;
+
+ for( macro = table->bucket[i]; macro; macro = macro->next )
+ if( macro->builtin == MCPU_MACRO_BUILTIN_NONE ||
+ macro->builtin >= MCPU_MACRO_BUILTIN_REAL_MAX )
+ ++count;
+ }
+
+ list = count ? (mcpu_macro **)calloc( count, sizeof(*list) ) : NULL;
+ if( count != 0 && list == NULL )
+ return( -1 );
+
+ for( i = 0; i < MCPU_CPP_MACRO_BUCKETS; ++i )
+ {
+ mcpu_macro *macro;
+
+ for( macro = table->bucket[i]; macro; macro = macro->next )
+ if( macro->builtin == MCPU_MACRO_BUILTIN_NONE ||
+ macro->builtin >= MCPU_MACRO_BUILTIN_REAL_MAX )
+ list[k++] = macro;
+ }
+
+ if( count > 1 )
+ qsort( list, count, sizeof(*list), macro_name_compare );
+
+ for( i = 0; i < count; ++i )
+ {
+ mcpu_macro *macro = list[i];
+ mcpu_text generated;
+ const __mpu_char16_t *replacement;
+ size_t replacement_length;
+ int j;
+
+ mcpu_text_init( &generated );
+
+ if( line_filename != NULL )
+ {
+ const char *marker = macro->origin == MCPU_MACRO_ORIGIN_COMMAND_LINE ?
+ "<command-line>" : "<built-in>";
+
+ if( mcpu_text_append_ascii(output, "# 0 \"" ) != 0 ||
+ mcpu_text_append_utf8(output, marker) != 0 ||
+ mcpu_text_append_ascii(output, "\"\n") != 0 )
+ goto fail;
+ }
+
+ if( mcpu_text_append_ascii(output, "#define ") != 0 ||
+ mcpu_text_append(output, macro->name, macro->name_length) != 0 )
+ goto fail;
+
+ if( macro->nargs >= 0 )
+ {
+ if( mcpu_text_append_char(output, '(') != 0 )
+ goto fail;
+
+ for( j = 0; j < macro->nargs; ++j )
+ {
+ if( j != 0 && mcpu_text_append_char(output, ',') != 0 )
+ goto fail;
+ if( mcpu_text_append(output, macro->argnames[j],
+ macro->argname_lengths[j]) != 0 )
+ goto fail;
+ }
+
+ if( macro->variadic )
+ {
+ if( macro->nargs != 0 && mcpu_text_append_char(output, ',') != 0 )
+ goto fail;
+ if( mcpu_text_append_ascii(output, "...") != 0 )
+ goto fail;
+ }
+
+ if( mcpu_text_append_char(output, ')') != 0 )
+ goto fail;
+ }
+
+ if( macro->builtin == MCPU_MACRO_BUILTIN_NONE )
+ {
+ replacement = macro->replacement;
+ replacement_length = macro->replacement_length;
+ }
+ else
+ {
+ if( expand_builtin(macro, NULL, &generated) != 0 )
+ goto fail;
+ replacement = generated.data;
+ replacement_length = generated.length;
+ }
+
+ if( replacement_length != 0 &&
+ (mcpu_text_append_char(output, ' ') != 0 ||
+ mcpu_text_append(output, replacement, replacement_length) != 0) )
+ goto fail;
+
+ if( mcpu_text_append_char(output, '\n') != 0 )
+ goto fail;
+
+ mcpu_text_free( &generated );
+ continue;
+
+fail:
+ mcpu_text_free( &generated );
+ free( list );
+ return( -1 );
+ }
+
+ free( list );
+ return( 0 );
+}
+
+
+int
+mcpu_macro_expand( mcpu_macro_table *table,
+ const __mpu_char16_t *input, size_t length,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output )
+{
+ if( table == NULL || input == NULL || output == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpu_text_free( output );
+ mcpu_text_init( output );
+
+ return( expand_range(table, input, length, language,
+ context, output, 0) );
+}
diff --git a/src/mcpp-macro.h b/src/mcpp-macro.h
new file mode 100644
index 0000000..719972c
--- /dev/null
+++ b/src/mcpp-macro.h
@@ -0,0 +1,126 @@
+#ifndef __MCPU_CPP_MACRO_H__
+#define __MCPU_CPP_MACRO_H__ 1
+
+#include <defs.h>
+#include <mcpp-language.h>
+#include <mcpp-text.h>
+
+#define MCPU_CPP_MACRO_BUCKETS 257
+#define MCPU_CPP_MACRO_EXPANSION_LIMIT 400
+
+enum mcpu_macro_origin
+{
+ MCPU_MACRO_ORIGIN_SOURCE = 0,
+ MCPU_MACRO_ORIGIN_BUILTIN,
+ MCPU_MACRO_ORIGIN_COMMAND_LINE
+};
+
+
+enum mcpu_macro_builtin
+{
+ MCPU_MACRO_BUILTIN_NONE = 0,
+ MCPU_MACRO_BUILTIN_FILE,
+ MCPU_MACRO_BUILTIN_LINE,
+ MCPU_MACRO_BUILTIN_DATE,
+ MCPU_MACRO_BUILTIN_TIME,
+ MCPU_MACRO_BUILTIN_BASE_FILE,
+ MCPU_MACRO_BUILTIN_INCLUDE_LEVEL,
+ MCPU_MACRO_BUILTIN_REAL_MAX,
+ MCPU_MACRO_BUILTIN_REAL_MIN,
+ MCPU_MACRO_BUILTIN_REAL_EPSILON,
+ MCPU_MACRO_BUILTIN_REAL_MAX_EXP,
+ MCPU_MACRO_BUILTIN_REAL_MIN_EXP,
+ MCPU_MACRO_BUILTIN_REAL_MAX_10_EXP,
+ MCPU_MACRO_BUILTIN_REAL_MIN_10_EXP
+};
+
+typedef struct mcpp_options mcpp_options;
+typedef struct mcpu_macro_expansion_context mcpu_macro_expansion_context;
+struct mcpu_macro_expansion_context
+{
+ const char *filename;
+ const char *base_filename;
+ unsigned line_number;
+ size_t include_level;
+ const char *date;
+ const char *time;
+ const mcpp_options *options;
+};
+
+typedef struct mcpu_macro mcpu_macro;
+struct mcpu_macro
+{
+ __mpu_char16_t *name;
+ size_t name_length;
+
+ int nargs;
+ int variadic;
+ __mpu_char16_t **argnames;
+ size_t *argname_lengths;
+
+ __mpu_char16_t *replacement;
+ size_t replacement_length;
+
+ enum mcpu_macro_builtin builtin;
+ enum mcpu_macro_origin origin;
+ unsigned builtin_bits;
+ int expanding;
+ mcpu_macro *next;
+};
+
+typedef struct mcpu_macro_table mcpu_macro_table;
+struct mcpu_macro_table
+{
+ mcpu_macro *bucket[MCPU_CPP_MACRO_BUCKETS];
+};
+
+void mcpu_macro_table_init( mcpu_macro_table *table );
+void mcpu_macro_table_free( mcpu_macro_table *table );
+mcpu_macro *mcpu_macro_find( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length );
+int mcpu_macro_define_object( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length,
+ enum mcpu_macro_origin origin );
+int mcpu_macro_define_builtin( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ enum mcpu_macro_builtin builtin );
+int mcpu_macro_define_sized_builtin( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ enum mcpu_macro_builtin builtin,
+ unsigned bits );
+int mcpu_macro_define_function( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length,
+ const __mpu_char16_t *const *argnames,
+ const size_t *argname_lengths,
+ size_t nargs, int variadic,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length,
+ enum mcpu_macro_origin origin );
+int mcpu_macro_definition_equal( const mcpu_macro *macro,
+ int nargs, int variadic,
+ const __mpu_char16_t *const *argnames,
+ const size_t *argname_lengths,
+ const __mpu_char16_t *replacement,
+ size_t replacement_length );
+int mcpu_macro_undef( mcpu_macro_table *table,
+ const __mpu_char16_t *name,
+ size_t name_length );
+int mcpu_macro_dump( const mcpu_macro_table *table, FILE *stream,
+ int include_predefined );
+int mcpu_macro_dump_text( const mcpu_macro_table *table, mcpu_text *output,
+ const char *line_filename );
+int mcpu_macro_expand( mcpu_macro_table *table,
+ const __mpu_char16_t *input,
+ size_t length,
+ enum mcpu_language language,
+ const mcpu_macro_expansion_context *context,
+ mcpu_text *output );
+
+#endif /* __MCPU_CPP_MACRO_H__ */
diff --git a/src/mcpp-options.c b/src/mcpp-options.c
new file mode 100644
index 0000000..62fafc6
--- /dev/null
+++ b/src/mcpp-options.c
@@ -0,0 +1,365 @@
+#include <defs.h>
+
+static const char *
+option_argument( mcpp_options *opts, int argc, char **argv, int *i,
+ const char *attached, const char *option )
+{
+ if( attached != NULL && *attached != 0 )
+ return( attached );
+
+ if( *i + 1 >= argc )
+ {
+ fprintf( stderr, "%s: argument missing after '%s'\n",
+ opts->progname, option );
+ return( NULL );
+ }
+
+ ++*i;
+ return( argv[*i] );
+}
+
+static int
+append_action( mcpp_options *opts, enum mcpp_option_action_kind kind,
+ const char *option, const char *argument )
+{
+ if( opts->action_count >= opts->action_capacity )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ opts->actions[opts->action_count].kind = kind;
+ opts->actions[opts->action_count].option = option;
+ opts->actions[opts->action_count].argument = argument;
+ ++opts->action_count;
+
+ return( 0 );
+}
+
+static int
+set_filename( mcpp_options *opts, const char *name )
+{
+ if( opts->in_fname == NULL )
+ {
+ opts->in_fname = strcmp(name, "-") == 0 ? "" : name;
+ return( 0 );
+ }
+
+ if( opts->out_fname == NULL )
+ {
+ opts->out_fname = strcmp(name, "-") == 0 ? "" : name;
+ return( 0 );
+ }
+
+ fprintf( stderr, "%s: too many filenames\n", opts->progname );
+ return( -1 );
+}
+
+static int
+parse_dump_option( mcpp_options *opts, const char *arg )
+{
+ const char *p = arg + 2;
+
+ if( *p == 0 )
+ return( -1 );
+
+ while( *p )
+ {
+ switch( *p++ )
+ {
+ case 'M':
+ opts->dump_macros = MCPP_DUMP_ONLY;
+ opts->no_output = 1;
+ while( *p == 'P' )
+ {
+ opts->dump_only_plus_predefined = 1;
+ ++p;
+ }
+ break;
+ case 'D':
+ opts->dump_macros = MCPP_DUMP_DEFINITIONS;
+ break;
+ default:
+ return( -1 );
+ }
+ }
+
+ return( 0 );
+}
+
+void
+mcpp_options_init( mcpp_options *opts, int argc )
+{
+ if( opts == NULL )
+ return;
+
+ memset( opts, 0, sizeof(*opts) );
+ opts->progname = "mcpu-cpp";
+ opts->object_suffix = ".o";
+ opts->action_capacity = argc > 0 ? (size_t)argc * 2 : 2;
+ opts->actions = (mcpp_option_action *)calloc( opts->action_capacity,
+ sizeof(*opts->actions) );
+}
+
+void
+mcpp_options_free( mcpp_options *opts )
+{
+ if( opts == NULL )
+ return;
+
+ free( opts->actions );
+ opts->actions = NULL;
+ opts->action_count = 0;
+ opts->action_capacity = 0;
+}
+
+int
+mcpp_handle_options( mcpp_options *opts, int argc, char **argv )
+{
+ size_t j;
+ int i;
+
+ if( opts == NULL || argv == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ for( i = 0; i < argc; ++i )
+ {
+ const char *arg = argv[i];
+ const char *value;
+
+ if( arg[0] != '-' || arg[1] == 0 )
+ {
+ if( set_filename(opts, arg) != 0 ) return( -1 );
+ continue;
+ }
+
+ if( strcmp(arg, "--help") == 0 ) opts->help = 1;
+ else if( strcmp(arg, "--version") == 0 ) opts->version = 1;
+ else if( strcmp(arg, "--no-config") == 0 ) opts->no_config = 1;
+ else if( strcmp(arg, "--config-file") == 0 )
+ {
+ value = option_argument(opts, argc, argv, &i, NULL, arg);
+ if( value == NULL ) return( -1 );
+ opts->config_fname = value;
+ }
+ else if( strcmp(arg, "--object-suffix") == 0 )
+ {
+ value = option_argument(opts, argc, argv, &i, NULL, arg);
+ if( value == NULL ) return( -1 );
+ opts->object_suffix = value;
+ }
+ else if( strcmp(arg, "-dconfig") == 0 ) opts->dump_config = 1;
+ else if( strcmp(arg, "-dsearch-dirs") == 0 ) opts->dump_search_dirs = 1;
+ else if( arg[0] == '-' && arg[1] == 'd' && arg[2] != 0 )
+ {
+ if( parse_dump_option(opts, arg) != 0 ) goto unknown;
+ }
+ else if( strcmp(arg, "-v") == 0 || strcmp(arg, "--verbose") == 0 ) opts->verbose = 1;
+ else if( strcmp(arg, "-nostdinc") == 0 ) opts->no_standard_includes = 1;
+ else if( strcmp(arg, "-nostdinc++") == 0 ) opts->no_standard_kplusplus_includes = 1;
+ else if( strcmp(arg, "-E") == 0 ) { }
+ else if( strcmp(arg, "-w") == 0 ) opts->inhibit_warnings = 1;
+ else if( strcmp(arg, "-Wcomment") == 0 || strcmp(arg, "-Wcomments") == 0 )
+ {
+ opts->warn_comments = 1;
+ opts->warn_comments_explicit = 1;
+ }
+ else if( strcmp(arg, "-Wno-comment") == 0 || strcmp(arg, "-Wno-comments") == 0 )
+ {
+ opts->warn_comments = 0;
+ opts->warn_comments_explicit = 1;
+ }
+ else if( strcmp(arg, "-Wall") == 0 )
+ {
+ if( !opts->warn_comments_explicit )
+ opts->warn_comments = 1;
+ }
+ else if( strcmp(arg, "-Werror") == 0 ) opts->warnings_are_errors = 1;
+ else if( strcmp(arg, "-Wno-error") == 0 ) opts->warnings_are_errors = 0;
+ else if( strcmp(arg, "-lint") == 0 ) opts->for_lint = 1;
+ else if( strcmp(arg, "-M") == 0 )
+ {
+ opts->print_deps = 2;
+ opts->inhibit_output = 1;
+ }
+ else if( strcmp(arg, "-MM") == 0 )
+ {
+ opts->print_deps = 1;
+ opts->inhibit_output = 1;
+ }
+ else if( strcmp(arg, "-MD") == 0 )
+ {
+ opts->print_deps = 2;
+ opts->inhibit_output = 0;
+ }
+ else if( strcmp(arg, "-MMD") == 0 )
+ {
+ opts->print_deps = 1;
+ opts->inhibit_output = 0;
+ }
+ else if( arg[0] == '-' && arg[1] == 'M' && arg[2] == 'F' )
+ {
+ value = option_argument(opts, argc, argv, &i, arg + 3, "-MF");
+ if( value == NULL ) return( -1 );
+ opts->deps_file = value;
+ }
+ else if( arg[0] == '-' && arg[1] == 'M' &&
+ (arg[2] == 'T' || arg[2] == 'Q') )
+ {
+ enum mcpp_option_action_kind kind = arg[2] == 'T' ?
+ MCPP_ACTION_DEP_TARGET : MCPP_ACTION_DEP_TARGET_QUOTED;
+ const char *option = arg[2] == 'T' ? "-MT" : "-MQ";
+
+ value = option_argument(opts, argc, argv, &i, arg + 3, option);
+ if( value == NULL || append_action(opts, kind, option, value) != 0 )
+ return( -1 );
+ }
+ else if( strcmp(arg, "-MG") == 0 ) opts->print_deps_missing_files = 1;
+ else if( strcmp(arg, "-o") == 0 )
+ {
+ if( opts->out_fname != NULL )
+ {
+ fprintf(stderr, "%s: output filename specified twice\n", opts->progname);
+ return( -1 );
+ }
+ value = option_argument(opts, argc, argv, &i, NULL, arg);
+ if( value == NULL ) return( -1 );
+ opts->out_fname = strcmp(value, "-") == 0 ? "" : value;
+ }
+ else if( arg[1] == 'D' )
+ {
+ value = option_argument(opts, argc, argv, &i, arg + 2, "-D");
+ if( value == NULL || append_action(opts, MCPP_ACTION_DEFINE, "-D", value) != 0 ) return( -1 );
+ }
+ else if( arg[1] == 'U' )
+ {
+ value = option_argument(opts, argc, argv, &i, arg + 2, "-U");
+ if( value == NULL || append_action(opts, MCPP_ACTION_UNDEF, "-U", value) != 0 ) return( -1 );
+ }
+ else if( arg[1] == 'I' && arg[2] == '-' && arg[3] == 0 ) goto unknown;
+ else if( arg[1] == 'I' )
+ {
+ value = option_argument(opts, argc, argv, &i, arg + 2, "-I");
+ if( value == NULL || append_action(opts, MCPP_ACTION_INCLUDE_USER, "-I", value) != 0 ) return( -1 );
+ }
+ else if( strcmp(arg, "-include") == 0 || strcmp(arg, "-imacros") == 0 ||
+ strcmp(arg, "-isystem") == 0 || strcmp(arg, "-idirafter") == 0 )
+ {
+ enum mcpp_option_action_kind kind = MCPP_ACTION_INCLUDE;
+ if( strcmp(arg, "-imacros") == 0 ) kind = MCPP_ACTION_IMACROS;
+ else if( strcmp(arg, "-isystem") == 0 ) kind = MCPP_ACTION_INCLUDE_SYSTEM;
+ else if( strcmp(arg, "-idirafter") == 0 ) kind = MCPP_ACTION_INCLUDE_AFTER;
+ value = option_argument(opts, argc, argv, &i, NULL, arg);
+ if( value == NULL || append_action(opts, kind, arg, value) != 0 ) return( -1 );
+ }
+ else
+ {
+unknown:
+ fprintf( stderr, "%s: unknown option '%s'\n", opts->progname, arg );
+ return( -1 );
+ }
+ }
+
+ if( opts->in_fname == NULL ) opts->in_fname = "";
+ if( opts->out_fname == NULL ) opts->out_fname = "";
+
+ if( opts->deps_file != NULL && opts->print_deps == 0 )
+ {
+ fprintf( stderr,
+ "%s: -MF requires one of -M, -MM, -MD or -MMD\n",
+ opts->progname );
+ return( -1 );
+ }
+
+ if( opts->print_deps == 0 )
+ {
+ for( j = 0; j < opts->action_count; ++j )
+ if( opts->actions[j].kind == MCPP_ACTION_DEP_TARGET ||
+ opts->actions[j].kind == MCPP_ACTION_DEP_TARGET_QUOTED )
+ {
+ fprintf( stderr,
+ "%s: -MT/-MQ require one of -M, -MM, -MD or -MMD\n",
+ opts->progname );
+ return( -1 );
+ }
+ }
+
+ if( opts->print_deps_missing_files &&
+ (opts->print_deps == 0 || !opts->inhibit_output) )
+ {
+ fprintf( stderr, "%s: -MG may only be used with -M or -MM\n",
+ opts->progname );
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+static const char *
+action_name( enum mcpp_option_action_kind kind )
+{
+ switch( kind )
+ {
+ case MCPP_ACTION_DEFINE: return( "define" );
+ case MCPP_ACTION_UNDEF: return( "undef" );
+ case MCPP_ACTION_INCLUDE: return( "include" );
+ case MCPP_ACTION_IMACROS: return( "imacros" );
+ case MCPP_ACTION_INCLUDE_USER: return( "include-user" );
+ case MCPP_ACTION_INCLUDE_SYSTEM: return( "include-system" );
+ case MCPP_ACTION_INCLUDE_AFTER: return( "include-after" );
+ case MCPP_ACTION_DEP_TARGET: return( "dep-target" );
+ case MCPP_ACTION_DEP_TARGET_QUOTED: return( "dep-target-quoted" );
+ }
+ return( "unknown" );
+}
+
+int
+mcpp_options_dump( const mcpp_options *opts, FILE *stream )
+{
+ size_t i;
+
+ if( opts == NULL || stream == NULL ) return( -1 );
+
+ fprintf(stream, "input=%s\n", *opts->in_fname ? opts->in_fname : "<stdin>");
+ fprintf(stream, "output=%s\n", *opts->out_fname ? opts->out_fname : "<stdout>");
+ fprintf(stream, "verbose=%d\n", opts->verbose);
+ fprintf(stream, "nostdinc=%d\n", opts->no_standard_includes);
+ fprintf(stream, "dump_config=%d\n", opts->dump_config);
+ fprintf(stream, "dump_search_dirs=%d\n", opts->dump_search_dirs);
+ fprintf(stream, "dump_macros=%d\n", (int)opts->dump_macros);
+ fprintf(stream, "actions=%lu\n", (unsigned long)opts->action_count);
+ for( i = 0; i < opts->action_count; ++i )
+ fprintf(stream, "action[%lu]=%s:%s\n", (unsigned long)i,
+ action_name(opts->actions[i].kind), opts->actions[i].argument);
+ return( 0 );
+}
+
+int
+mcpp_options_has_unimplemented( const mcpp_options *opts )
+{
+ size_t i;
+ if( opts == NULL ) return( 0 );
+ if( opts->print_deps && opts->inhibit_output &&
+ opts->dump_macros != MCPP_DUMP_NONE )
+ return( 1 );
+ if( opts->print_deps && !opts->inhibit_output &&
+ opts->dump_macros == MCPP_DUMP_ONLY )
+ return( 1 );
+ if( opts->no_standard_kplusplus_includes || opts->for_lint )
+ return( 1 );
+ for( i = 0; i < opts->action_count; ++i )
+ if( opts->actions[i].kind != MCPP_ACTION_DEFINE &&
+ opts->actions[i].kind != MCPP_ACTION_UNDEF &&
+ opts->actions[i].kind != MCPP_ACTION_INCLUDE &&
+ opts->actions[i].kind != MCPP_ACTION_IMACROS &&
+ opts->actions[i].kind != MCPP_ACTION_INCLUDE_USER &&
+ opts->actions[i].kind != MCPP_ACTION_INCLUDE_SYSTEM &&
+ opts->actions[i].kind != MCPP_ACTION_INCLUDE_AFTER &&
+ opts->actions[i].kind != MCPP_ACTION_DEP_TARGET &&
+ opts->actions[i].kind != MCPP_ACTION_DEP_TARGET_QUOTED )
+ return( 1 );
+ return( 0 );
+}
diff --git a/src/mcpp-options.h b/src/mcpp-options.h
new file mode 100644
index 0000000..910db92
--- /dev/null
+++ b/src/mcpp-options.h
@@ -0,0 +1,74 @@
+#ifndef __MCPU_CPP_OPTIONS_H__
+#define __MCPU_CPP_OPTIONS_H__ 1
+
+enum mcpp_dump_macros
+{
+ MCPP_DUMP_NONE = 0,
+ MCPP_DUMP_ONLY,
+ MCPP_DUMP_DEFINITIONS
+};
+
+enum mcpp_option_action_kind
+{
+ MCPP_ACTION_DEFINE = 0,
+ MCPP_ACTION_UNDEF,
+ MCPP_ACTION_INCLUDE,
+ MCPP_ACTION_IMACROS,
+ MCPP_ACTION_INCLUDE_USER,
+ MCPP_ACTION_INCLUDE_SYSTEM,
+ MCPP_ACTION_INCLUDE_AFTER,
+ MCPP_ACTION_DEP_TARGET,
+ MCPP_ACTION_DEP_TARGET_QUOTED
+};
+
+typedef struct mcpp_option_action mcpp_option_action;
+struct mcpp_option_action
+{
+ enum mcpp_option_action_kind kind;
+ const char *option;
+ const char *argument;
+};
+
+typedef struct mcpp_options mcpp_options;
+struct mcpp_options
+{
+ const char *progname;
+ const char *in_fname;
+ const char *out_fname;
+ const char *config_fname;
+ const char *deps_file;
+ const char *deps_target;
+ const char *object_suffix;
+
+ int help;
+ int version;
+ int verbose;
+ int no_config;
+ int dump_config;
+ int dump_search_dirs;
+ int no_standard_includes;
+ int no_standard_kplusplus_includes;
+ int inhibit_output;
+ int no_output;
+ int print_deps;
+ int print_deps_missing_files;
+ int inhibit_warnings;
+ int warn_comments;
+ int warn_comments_explicit;
+ int warnings_are_errors;
+ int for_lint;
+ enum mcpp_dump_macros dump_macros;
+ int dump_only_plus_predefined;
+
+ mcpp_option_action *actions;
+ size_t action_count;
+ size_t action_capacity;
+};
+
+void mcpp_options_init( mcpp_options *opts, int argc );
+void mcpp_options_free( mcpp_options *opts );
+int mcpp_handle_options( mcpp_options *opts, int argc, char **argv );
+int mcpp_options_dump( const mcpp_options *opts, FILE *stream );
+int mcpp_options_has_unimplemented( const mcpp_options *opts );
+
+#endif
diff --git a/src/mcpp-predefined.c b/src/mcpp-predefined.c
new file mode 100644
index 0000000..556232c
--- /dev/null
+++ b/src/mcpp-predefined.c
@@ -0,0 +1,627 @@
+#include <defs.h>
+
+
+static int
+define_utf8( mcpu_macro_table *table, const char *name,
+ const char *replacement )
+{
+ mcpu_text n;
+ mcpu_text r;
+ int rc = -1;
+
+ mcpu_text_init( &n );
+ mcpu_text_init( &r );
+
+ if( mcpu_text_from_utf8(&n, name) != 0 ||
+ mcpu_text_from_utf8(&r, replacement ? replacement : "") != 0 )
+ goto done;
+
+ rc = mcpu_macro_define_object( table, n.data, n.length,
+ r.data, r.length,
+ MCPU_MACRO_ORIGIN_BUILTIN );
+
+done:
+ mcpu_text_free( &n );
+ mcpu_text_free( &r );
+ return( rc );
+}
+
+
+static int
+define_unsigned( mcpu_macro_table *table, const char *name,
+ unsigned long value )
+{
+ char text[64];
+
+ snprintf( text, sizeof(text), "%lu", value );
+ return( define_utf8(table, name, text) );
+}
+
+
+static int
+define_quoted( mcpu_macro_table *table, const char *name,
+ const char *value )
+{
+ size_t n;
+ char *text;
+ size_t i;
+ size_t o = 0;
+ int rc;
+
+ if( value == NULL )
+ value = "";
+
+ n = strlen( value );
+ if( n > (SIZE_MAX - 3) / 2 )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ text = (char *)malloc( n * 2 + 3 );
+ if( text == NULL )
+ return( -1 );
+
+ text[o++] = '"';
+ for( i = 0; i < n; ++i )
+ {
+ if( value[i] == '\\' || value[i] == '"' )
+ text[o++] = '\\';
+ text[o++] = value[i];
+ }
+ text[o++] = '"';
+ text[o] = 0;
+
+ rc = define_utf8( table, name, text );
+ free( text );
+ return( rc );
+}
+
+
+static int
+define_from_macro( mcpu_macro_table *table, const char *name,
+ const char *source_name )
+{
+ mcpu_text source;
+ mcpu_text target;
+ mcpu_macro *macro;
+ int rc = -1;
+
+ mcpu_text_init( &source );
+ mcpu_text_init( &target );
+
+ if( mcpu_text_from_utf8(&source, source_name) != 0 ||
+ mcpu_text_from_utf8(&target, name) != 0 )
+ goto done;
+
+ macro = mcpu_macro_find( table, source.data, source.length );
+ if( macro == NULL || macro->builtin != MCPU_MACRO_BUILTIN_NONE )
+ {
+ errno = EINVAL;
+ goto done;
+ }
+
+ rc = mcpu_macro_define_object( table, target.data, target.length,
+ macro->replacement,
+ macro->replacement_length,
+ MCPU_MACRO_ORIGIN_BUILTIN );
+
+done:
+ mcpu_text_free( &source );
+ mcpu_text_free( &target );
+ return( rc );
+}
+
+
+static int
+define_sized_builtin_utf8( mcpu_macro_table *table, const char *name,
+ enum mcpu_macro_builtin builtin, unsigned bits )
+{
+ mcpu_text n;
+ int rc;
+
+ mcpu_text_init( &n );
+ if( mcpu_text_from_utf8(&n, name) != 0 )
+ return( -1 );
+
+ rc = mcpu_macro_define_sized_builtin( table, n.data, n.length,
+ builtin, bits );
+ mcpu_text_free( &n );
+ return( rc );
+}
+
+
+static unsigned long
+mcpp_uint_decimal_digs( unsigned bits )
+{
+ uint64_t n;
+
+ if( bits < 1 )
+ return( 0 );
+
+ /*
+ * Number of decimal digits in 2^bits - 1. This follows the integer-only
+ * scheme used by LibMPU _int_digs(), but does not include a terminating
+ * NUL. The tighter upper approximation keeps the result exact for every
+ * integer width supported by the current LibMPU family through 65536 bits.
+ *
+ * log10(2) < 301029995664 / 1000000000000
+ */
+ n = ((uint64_t)bits * UINT64_C(301029995664) +
+ UINT64_C(999999999999)) / UINT64_C(1000000000000);
+
+ return( (unsigned long)n );
+}
+
+
+static unsigned long
+mcpp_int_decimal_digs( unsigned bits )
+{
+ if( bits < 2 )
+ return( 0 );
+
+ /* Signed maximum is 2^(bits - 1) - 1; sign is not part of DECIMAL_DIG. */
+ return( mcpp_uint_decimal_digs(bits - 1) );
+}
+
+
+static int
+define_fixed_integer_family( mcpu_macro_table *table, unsigned bits )
+{
+ size_t nb = (size_t)bits / 8;
+ mpu_int *value = NULL;
+ char *text = NULL;
+ char name[96];
+ size_t text_size;
+ unsigned long int_digs;
+ unsigned long uint_digs;
+ int rc = -1;
+
+ if( bits == 0 || (bits % 8) != 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ snprintf( name, sizeof(name), "__INT%u_TYPE__", bits );
+ {
+ char type[64];
+ snprintf( type, sizeof(type), "int%u", bits );
+ if( define_utf8(table, name, type) != 0 )
+ return( -1 );
+ }
+
+ snprintf( name, sizeof(name), "__UINT%u_TYPE__", bits );
+ {
+ char type[64];
+ snprintf( type, sizeof(type), "uint%u", bits );
+ if( define_utf8(table, name, type) != 0 )
+ return( -1 );
+ }
+
+ snprintf( name, sizeof(name), "__INT%u_WIDTH__", bits );
+ if( define_unsigned(table, name, bits) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__UINT%u_WIDTH__", bits );
+ if( define_unsigned(table, name, bits) != 0 )
+ return( -1 );
+
+ /*
+ * TYPE/WIDTH/SIZEOF and decimal-digit metadata remain available for the
+ * complete LibMPU integer family through NB_I_MAX * 8. Only textual MAX
+ * values are capped at 256 bits so -dMP stays compact and useful.
+ */
+ snprintf( name, sizeof(name), "__SIZEOF_INT%u__", bits );
+ if( define_unsigned(table, name, bits / 8) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__SIZEOF_UINT%u__", bits );
+ if( define_unsigned(table, name, bits / 8) != 0 )
+ return( -1 );
+
+ int_digs = mcpp_int_decimal_digs( bits );
+ uint_digs = mcpp_uint_decimal_digs( bits );
+ if( int_digs == 0 || uint_digs == 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+ snprintf( name, sizeof(name), "__INT%u_DECIMAL_DIG__", bits );
+ if( define_unsigned(table, name, int_digs) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__UINT%u_DECIMAL_DIG__", bits );
+ if( define_unsigned(table, name, uint_digs) != 0 )
+ return( -1 );
+
+ if( bits > 256 )
+ return( 0 );
+
+ value = (mpu_int *)malloc( nb );
+ if( value == NULL )
+ return( -1 );
+
+ text_size = (size_t)bits + 3;
+ text = (char *)calloc( text_size, 1 );
+ if( text == NULL )
+ goto done;
+
+ memset( value, 0xff, nb );
+ iuitoa( (__mpu_char8_t *)text, value, RADIX_HEX, LOWERCASE, (int)nb );
+
+ snprintf( name, sizeof(name), "__UINT%u_MAX__", bits );
+ if( define_utf8(table, name, text) != 0 )
+ goto done;
+
+ /*
+ * iuitoa() is the canonical LibMPU conversion. For the signed maximum
+ * the all-ones unsigned bit pattern has only its top bit cleared.
+ */
+ if( strlen(text) < 3 || text[0] != '0' || text[1] != 'x' )
+ {
+ errno = EINVAL;
+ goto done;
+ }
+ text[2] = '7';
+
+ snprintf( name, sizeof(name), "__INT%u_MAX__", bits );
+ if( define_utf8(table, name, text) != 0 )
+ goto done;
+
+ rc = 0;
+
+done:
+ free( text );
+ free( value );
+ return( rc );
+}
+
+typedef void (*real_value_fn)( mpu_real *, unsigned int, int );
+
+static int
+append_real_value( mcpu_text *output, unsigned bits, real_value_fn fn )
+{
+ int nb = (int)(bits / 8);
+ int max_string;
+ mpu_real *value;
+ char *text;
+ int rc = -1;
+
+ max_string = _real_max_string( nb );
+ if( max_string <= 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ value = (mpu_real *)calloc( (size_t)nb, 1 );
+ text = (char *)calloc( (size_t)max_string + 4096, 1 );
+ if( value == NULL || text == NULL )
+ goto done;
+
+ fn( value, 0, nb );
+ real_to_ascii( (__mpu_char8_t *)text, value,
+ _real_mant_digs(nb), 'e', 0, 0, nb );
+ if( text[0] == 0 )
+ {
+ errno = EINVAL;
+ goto done;
+ }
+
+ rc = mcpu_text_append_ascii( output, text );
+
+done:
+ free( text );
+ free( value );
+ return( rc );
+}
+
+
+static void
+real_epsilon_wrapper( mpu_real *value, unsigned int sign, int nb )
+{
+ (void)sign;
+ _real_epsilon( value, nb );
+}
+
+
+static int
+append_real_exponent( mcpu_text *output, unsigned bits,
+ void (*fn)(mpu_int *, int, int) )
+{
+ int nb_r = (int)(bits / 8);
+ int nb_e = _sizeof_exp( nb_r );
+ mpu_int *value;
+ char *text;
+ size_t text_size;
+ int rc = -1;
+
+ if( nb_e <= 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ value = (mpu_int *)calloc( (size_t)nb_e, 1 );
+ text_size = (size_t)nb_e * 8 + 3;
+ text = (char *)calloc( text_size, 1 );
+ if( value == NULL || text == NULL )
+ goto done;
+
+ fn( value, nb_e, nb_r );
+ iitoa( (__mpu_char8_t *)text, value, RADIX_DEC, LOWERCASE, nb_e );
+ if( text[0] == 0 )
+ {
+ errno = EINVAL;
+ goto done;
+ }
+
+ rc = mcpu_text_append_ascii( output, text );
+
+done:
+ free( text );
+ free( value );
+ return( rc );
+}
+
+
+static int
+define_real_family( mcpu_macro_table *table, unsigned bits )
+{
+ char name[96];
+ char type[64];
+ int nb = (int)(bits / 8);
+
+ /*
+ * Real and Complex language types exist only through the configured
+ * MPU_REAL_IO_LIMIT. Complex WIDTH is the width parameter in the language
+ * type name (complex128 -> 128), while SIZEOF is twice the Real storage.
+ */
+ if( bits > MCPU_CPP_MPU_REAL_IO_LIMIT )
+ return( 0 );
+
+ snprintf( name, sizeof(name), "__REAL%u_TYPE__", bits );
+ snprintf( type, sizeof(type), "real%u", bits );
+ if( define_utf8(table, name, type) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__COMPLEX%u_TYPE__", bits );
+ snprintf( type, sizeof(type), "complex%u", bits );
+ if( define_utf8(table, name, type) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__REAL%u_WIDTH__", bits );
+ if( define_unsigned(table, name, bits) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__COMPLEX%u_WIDTH__", bits );
+ if( define_unsigned(table, name, bits) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__SIZEOF_REAL%u__", bits );
+ if( define_unsigned(table, name, bits / 8) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__SIZEOF_COMPLEX%u__", bits );
+ if( define_unsigned(table, name, bits / 4) != 0 )
+ return( -1 );
+
+ /*
+ * These are compact structural/text-conversion properties. They are
+ * available for every configured Real type through MPU_REAL_IO_LIMIT.
+ * _real_max_string() returns a character count, not a byte count.
+ */
+ {
+ int exp_size = _sizeof_exp( nb );
+ int max_strlen = _real_max_string( nb );
+
+ if( exp_size <= 0 || max_strlen <= 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ snprintf( name, sizeof(name), "__SIZEOF_REAL%u_EXP__", bits );
+ if( define_unsigned(table, name, (unsigned long)exp_size) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__REAL%u_MAX_STRLEN__", bits );
+ if( define_unsigned(table, name, (unsigned long)max_strlen) != 0 )
+ return( -1 );
+ }
+
+ /*
+ * Precision metadata is compact and is useful for every configured Real
+ * width. Only large textual numeric values are capped at 256 bits.
+ */
+ snprintf( name, sizeof(name), "__REAL%u_MANT_DIG__", bits );
+ if( define_unsigned(table, name, (unsigned long)_real_mant_digs(nb)) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__REAL%u_DECIMAL_DIG__", bits );
+ if( define_unsigned(table, name, (unsigned long)_real_digs(nb)) != 0 )
+ return( -1 );
+
+ if( bits > 256 )
+ return( 0 );
+
+ snprintf( name, sizeof(name), "__REAL%u_MAX__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_MAX, bits) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__REAL%u_MIN__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_MIN, bits) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__REAL%u_EPSILON__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_EPSILON, bits) != 0 )
+ return( -1 );
+
+ snprintf( name, sizeof(name), "__REAL%u_MAX_EXP__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_MAX_EXP, bits) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__REAL%u_MIN_EXP__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_MIN_EXP, bits) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__REAL%u_MAX_10_EXP__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_MAX_10_EXP, bits) != 0 )
+ return( -1 );
+ snprintf( name, sizeof(name), "__REAL%u_MIN_10_EXP__", bits );
+ if( define_sized_builtin_utf8(table, name,
+ MCPU_MACRO_BUILTIN_REAL_MIN_10_EXP, bits) != 0 )
+ return( -1 );
+
+ return( 0 );
+}
+
+int
+mcpu_predefined_expand_builtin( enum mcpu_macro_builtin builtin,
+ unsigned bits,
+ mcpu_text *output )
+{
+ if( output == NULL || bits == 0 || bits > 256 ||
+ bits > MCPU_CPP_MPU_REAL_IO_LIMIT )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ switch( builtin )
+ {
+ case MCPU_MACRO_BUILTIN_REAL_MAX:
+ return( append_real_value(output, bits, _real_max) );
+ case MCPU_MACRO_BUILTIN_REAL_MIN:
+ return( append_real_value(output, bits, _real_min) );
+ case MCPU_MACRO_BUILTIN_REAL_EPSILON:
+ return( append_real_value(output, bits, real_epsilon_wrapper) );
+ case MCPU_MACRO_BUILTIN_REAL_MAX_EXP:
+ return( append_real_exponent(output, bits, _real_max_exp) );
+ case MCPU_MACRO_BUILTIN_REAL_MIN_EXP:
+ return( append_real_exponent(output, bits, _real_min_exp) );
+ case MCPU_MACRO_BUILTIN_REAL_MAX_10_EXP:
+ return( append_real_exponent(output, bits, _real_max_10_exp) );
+ case MCPU_MACRO_BUILTIN_REAL_MIN_10_EXP:
+ return( append_real_exponent(output, bits, _real_min_10_exp) );
+ default:
+ errno = EINVAL;
+ return( -1 );
+ }
+}
+
+
+int
+mcpu_predefined_install( mcpu_macro_table *table )
+{
+ unsigned bits;
+ char type[64];
+ char ref[96];
+
+ if( table == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( define_utf8(table, "_ARCH_MCPU", "1") != 0 ||
+ define_quoted(table, "__MCPU_CPP_VERSION__", PACKAGE_VERSION) != 0 ||
+ define_utf8(table, "__REGISTER_PREFIX__", "") != 0 ||
+ define_utf8(table, "__LOCAL_LABEL_PREFIX__", "") != 0 ||
+ define_utf8(table, "__USER_LABEL_PREFIX__", "") != 0 ||
+ define_utf8(table, "__IMMEDIATE_PREFIX__", "") != 0 ||
+ define_utf8(table, "__ORDER_LITTLE_ENDIAN__", "1234") != 0 ||
+ define_utf8(table, "__ORDER_BIG_ENDIAN__", "4321") != 0 ||
+ define_utf8(table, "__ORDER_PDP_ENDIAN__", "3412") != 0 )
+ return( -1 );
+
+#if MCPU_CPP_MPU_BYTE_ORDER == 1234
+ if( define_utf8(table, "__MCPU_BYTE_ORDER__", "__ORDER_LITTLE_ENDIAN__") != 0 )
+ return( -1 );
+#else
+ if( define_utf8(table, "__MCPU_BYTE_ORDER__", "__ORDER_BIG_ENDIAN__") != 0 )
+ return( -1 );
+#endif
+ if( define_utf8(table, "__BYTE_ORDER__", "__MCPU_BYTE_ORDER__") != 0 )
+ return( -1 );
+
+#if MCPU_CPP_MPU_WORD_ORDER == 1234
+ if( define_utf8(table, "__MCPU_WORD_ORDER__", "__ORDER_LITTLE_ENDIAN__") != 0 )
+ return( -1 );
+#else
+ if( define_utf8(table, "__MCPU_WORD_ORDER__", "__ORDER_BIG_ENDIAN__") != 0 )
+ return( -1 );
+#endif
+
+ if( define_unsigned(table, "__MCPU_MACHINE_REGISTER_WIDTH__",
+ MCPU_CPP_MPU_REGISTER_WIDTH) != 0 ||
+ define_unsigned(table, "__MCPU_REAL_IO_LIMIT__",
+ MCPU_CPP_MPU_REAL_IO_LIMIT) != 0 ||
+ define_unsigned(table, "__MCPU_MATH_FN_LIMIT__",
+ MCPU_CPP_MPU_MATH_FN_LIMIT) != 0 ||
+ define_unsigned(table, "__MCPU_INT_MAX_WIDTH__",
+ MCPU_CPP_MPU_INT_MAX_WIDTH) != 0 ||
+ define_unsigned(table, "__MCPU_REAL_MAX_WIDTH__",
+ MCPU_CPP_MPU_REAL_IO_LIMIT) != 0 ||
+ define_unsigned(table, "__MCPU_COMPLEX_MAX_WIDTH__",
+ MCPU_CPP_MPU_REAL_IO_LIMIT) != 0 ||
+ define_unsigned(table, "__SIZEOF_POINTER__", 8) != 0 ||
+ define_unsigned(table, "__MCPU_POINTER_WIDTH__", 64) != 0 ||
+ define_unsigned(table, "__MCPU_SIZEOF_SIZE__", MCPU_CPP_SIZEOF_SIZE_T) != 0 ||
+ define_unsigned(table, "__MCPU_SIZE_WIDTH__", MCPU_CPP_SIZE_WIDTH) != 0 ||
+ define_unsigned(table, "__SIZEOF_PTRDIFF__", 8) != 0 ||
+ define_unsigned(table, "__PTRDIFF_WIDTH__", 64) != 0 ||
+ define_utf8(table, "__CHAR8_TYPE__", "char8") != 0 ||
+ define_utf8(table, "__CHAR16_TYPE__", "char16") != 0 ||
+ define_unsigned(table, "__CHAR8_WIDTH__", 8) != 0 ||
+ define_unsigned(table, "__CHAR16_WIDTH__", 16) != 0 ||
+ define_unsigned(table, "__SIZEOF_CHAR8__", 1) != 0 ||
+ define_unsigned(table, "__SIZEOF_CHAR16__", 2) != 0 ||
+ define_utf8(table, "__CHAR8_MAX__", "0xff") != 0 ||
+ define_utf8(table, "__CHAR16_MAX__", "0xffff") != 0 )
+ return( -1 );
+
+ for( bits = 8; bits != 0 && bits <= MCPU_CPP_MPU_INT_MAX_WIDTH; bits *= 2 )
+ {
+ if( define_fixed_integer_family(table, bits) != 0 )
+ return( -1 );
+ }
+
+ snprintf( type, sizeof(type), "uint%u", (unsigned)MCPU_CPP_SIZE_WIDTH );
+ if( define_utf8(table, "__MCPU_SIZE_TYPE__", type) != 0 )
+ return( -1 );
+ snprintf( ref, sizeof(ref), "__UINT%u_MAX__", (unsigned)MCPU_CPP_SIZE_WIDTH );
+ if( define_from_macro(table, "__MCPU_SIZE_MAX__", ref) != 0 )
+ return( -1 );
+
+ snprintf( type, sizeof(type), "int%u", (unsigned)MCPU_CPP_SSIZE_WIDTH );
+ if( define_utf8(table, "__MCPU_SSIZE_TYPE__", type) != 0 ||
+ define_unsigned(table, "__MCPU_SSIZE_WIDTH__", MCPU_CPP_SSIZE_WIDTH) != 0 ||
+ define_unsigned(table, "__MCPU_SIZEOF_SSIZE__", MCPU_CPP_SIZEOF_SSIZE_T) != 0 )
+ return( -1 );
+ if( MCPU_CPP_SSIZE_WIDTH <= 256 )
+ {
+ snprintf( ref, sizeof(ref), "__INT%u_MAX__", (unsigned)MCPU_CPP_SSIZE_WIDTH );
+ if( define_from_macro(table, "__MCPU_SSIZE_MAX__", ref) != 0 )
+ return( -1 );
+ }
+
+ if( define_utf8(table, "__PTRDIFF_TYPE__", "int64") != 0 ||
+ define_unsigned(table, "__PTRDIFF_WIDTH__", 64) != 0 ||
+ define_utf8(table, "__PTRDIFF_MAX__", "0x7fffffffffffffff") != 0 ||
+ define_utf8(table, "__INTPTR_TYPE__", "int64") != 0 ||
+ define_utf8(table, "__UINTPTR_TYPE__", "uint64") != 0 ||
+ define_unsigned(table, "__INTPTR_WIDTH__", 64) != 0 ||
+ define_unsigned(table, "__UINTPTR_WIDTH__", 64) != 0 ||
+ define_utf8(table, "__INTPTR_MAX__", "0x7fffffffffffffff") != 0 ||
+ define_utf8(table, "__UINTPTR_MAX__", "0xffffffffffffffff") != 0 )
+ return( -1 );
+
+ for( bits = 32; bits != 0 && bits <= MCPU_CPP_MPU_REAL_IO_LIMIT; bits *= 2 )
+ {
+ if( define_real_family(table, bits) != 0 )
+ return( -1 );
+ }
+
+ return( 0 );
+}
diff --git a/src/mcpp-predefined.h b/src/mcpp-predefined.h
new file mode 100644
index 0000000..4010ae0
--- /dev/null
+++ b/src/mcpp-predefined.h
@@ -0,0 +1,12 @@
+#ifndef __MCPU_CPP_PREDEFINED_H__
+#define __MCPU_CPP_PREDEFINED_H__ 1
+
+#include <defs.h>
+#include <mcpp-macro.h>
+
+int mcpu_predefined_install( mcpu_macro_table *table );
+int mcpu_predefined_expand_builtin( enum mcpu_macro_builtin builtin,
+ unsigned bits,
+ mcpu_text *output );
+
+#endif /* __MCPU_CPP_PREDEFINED_H__ */
diff --git a/src/mcpp-runtime.c b/src/mcpp-runtime.c
new file mode 100644
index 0000000..f498b20
--- /dev/null
+++ b/src/mcpp-runtime.c
@@ -0,0 +1,268 @@
+#include <defs.h>
+
+#include <unistd.h>
+
+
+static char *
+path_dirname( const char *path )
+{
+ const char *slash;
+ size_t length;
+ char *result;
+
+ if( path == NULL || *path == 0 )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ slash = strrchr( path, '/' );
+ if( slash == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ if( slash == path )
+ length = 1;
+ else
+ length = (size_t)(slash - path);
+
+ result = (char *)malloc( length + 1 );
+ if( result == NULL )
+ return( NULL );
+
+ memcpy( result, path, length );
+ result[length] = 0;
+ return( result );
+}
+
+
+static char *
+path_join( const char *directory, const char *name )
+{
+ size_t directory_length;
+ size_t name_length;
+ int need_slash;
+ char *result;
+
+ if( directory == NULL || name == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ directory_length = strlen( directory );
+ name_length = strlen( name );
+ need_slash = directory_length != 0 && directory[directory_length - 1] != '/';
+
+ if( directory_length > SIZE_MAX - name_length - (size_t)need_slash - 1 )
+ {
+ errno = EOVERFLOW;
+ return( NULL );
+ }
+
+ result = (char *)malloc( directory_length + name_length +
+ (size_t)need_slash + 1 );
+ if( result == NULL )
+ return( NULL );
+
+ memcpy( result, directory, directory_length );
+ if( need_slash )
+ result[directory_length++] = '/';
+ memcpy( result + directory_length, name, name_length );
+ result[directory_length + name_length] = 0;
+ return( result );
+}
+
+
+static int
+resolve_proc_self_exe( char path[PATH_MAX] )
+{
+ ssize_t length;
+
+ length = readlink( "/proc/self/exe", path, PATH_MAX - 1 );
+ if( length < 0 )
+ return( -1 );
+
+ if( length >= PATH_MAX - 1 )
+ {
+ errno = ENAMETOOLONG;
+ return( -1 );
+ }
+
+ path[length] = 0;
+ return( 0 );
+}
+
+
+static int
+resolve_candidate( const char *candidate, char path[PATH_MAX] )
+{
+ if( candidate == NULL || *candidate == 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( realpath(candidate, path) == NULL )
+ return( -1 );
+
+ if( access(path, X_OK) != 0 )
+ return( -1 );
+
+ return( 0 );
+}
+
+
+static int
+resolve_argv0( const char *argv0, char path[PATH_MAX] )
+{
+ const char *search;
+ const char *entry;
+
+ if( argv0 == NULL || *argv0 == 0 )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( strchr(argv0, '/') != NULL )
+ return( resolve_candidate(argv0, path) );
+
+ search = getenv( "PATH" );
+ if( search == NULL )
+ {
+ errno = ENOENT;
+ return( -1 );
+ }
+
+ entry = search;
+ for( ;; )
+ {
+ const char *colon = strchr( entry, ':' );
+ size_t length = colon ? (size_t)(colon - entry) : strlen(entry);
+ const char *directory = entry;
+ char directory_buffer[PATH_MAX];
+ char *candidate;
+ int rc;
+
+ if( length == 0 )
+ directory = ".";
+ else
+ {
+ if( length >= sizeof(directory_buffer) )
+ {
+ errno = ENAMETOOLONG;
+ return( -1 );
+ }
+ memcpy( directory_buffer, entry, length );
+ directory_buffer[length] = 0;
+ directory = directory_buffer;
+ }
+
+ candidate = path_join( directory, argv0 );
+ if( candidate == NULL )
+ return( -1 );
+ rc = resolve_candidate( candidate, path );
+ free( candidate );
+ if( rc == 0 )
+ return( 0 );
+
+ if( colon == NULL )
+ break;
+ entry = colon + 1;
+ }
+
+ errno = ENOENT;
+ return( -1 );
+}
+
+
+void
+mcpp_runtime_paths_init( mcpp_runtime_paths *paths )
+{
+ if( paths == NULL )
+ return;
+
+ paths->executable_path = NULL;
+ paths->executable_dir = NULL;
+ paths->installation_root = NULL;
+ paths->config_file = NULL;
+ paths->system_include_path = NULL;
+}
+
+
+void
+mcpp_runtime_paths_free( mcpp_runtime_paths *paths )
+{
+ if( paths == NULL )
+ return;
+
+ free( paths->executable_path );
+ free( paths->executable_dir );
+ free( paths->installation_root );
+ free( paths->config_file );
+ free( paths->system_include_path );
+ mcpp_runtime_paths_init( paths );
+}
+
+
+int
+mcpp_runtime_paths_discover( mcpp_runtime_paths *paths, const char *argv0 )
+{
+ char resolved[PATH_MAX];
+ char *executable_path = NULL;
+ char *executable_dir = NULL;
+ char *installation_root = NULL;
+ char *config_file = NULL;
+ char *system_include_path = NULL;
+
+ if( paths == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( resolve_proc_self_exe(resolved) != 0 )
+ {
+ if( resolve_argv0(argv0, resolved) != 0 )
+ return( -1 );
+ }
+
+ executable_path = strdup( resolved );
+ if( executable_path == NULL )
+ goto fail;
+
+ executable_dir = path_dirname( executable_path );
+ if( executable_dir == NULL )
+ goto fail;
+
+ installation_root = path_dirname( executable_dir );
+ if( installation_root == NULL )
+ goto fail;
+
+ config_file = path_join( installation_root, "etc/mcpu-cpp.conf" );
+ if( config_file == NULL )
+ goto fail;
+
+ system_include_path = path_join( installation_root, "include" );
+ if( system_include_path == NULL )
+ goto fail;
+
+ mcpp_runtime_paths_free( paths );
+ paths->executable_path = executable_path;
+ paths->executable_dir = executable_dir;
+ paths->installation_root = installation_root;
+ paths->config_file = config_file;
+ paths->system_include_path = system_include_path;
+ return( 0 );
+
+fail:
+ free( executable_path );
+ free( executable_dir );
+ free( installation_root );
+ free( config_file );
+ free( system_include_path );
+ return( -1 );
+}
diff --git a/src/mcpp-runtime.h b/src/mcpp-runtime.h
new file mode 100644
index 0000000..75d1ddf
--- /dev/null
+++ b/src/mcpp-runtime.h
@@ -0,0 +1,19 @@
+#ifndef __MCPU_CPP_RUNTIME_H__
+#define __MCPU_CPP_RUNTIME_H__ 1
+
+typedef struct mcpp_runtime_paths mcpp_runtime_paths;
+struct mcpp_runtime_paths
+{
+ char *executable_path;
+ char *executable_dir;
+ char *installation_root;
+ char *config_file;
+ char *system_include_path;
+};
+
+void mcpp_runtime_paths_init( mcpp_runtime_paths *paths );
+void mcpp_runtime_paths_free( mcpp_runtime_paths *paths );
+int mcpp_runtime_paths_discover( mcpp_runtime_paths *paths,
+ const char *argv0 );
+
+#endif /* __MCPU_CPP_RUNTIME_H__ */
diff --git a/src/mcpp-semantic.c b/src/mcpp-semantic.c
new file mode 100644
index 0000000..3063139
--- /dev/null
+++ b/src/mcpp-semantic.c
@@ -0,0 +1,349 @@
+#include <defs.h>
+
+#define MCPP_INTEGER_BITS 64U
+
+static __mpu_int64_t
+mcpp_semantic_signed( mcpp_integer value )
+{
+ if( value.value <= (__mpu_uint64_t)INT64_MAX )
+ return( (__mpu_int64_t)value.value );
+
+ return( -1 - (__mpu_int64_t)(UINT64_MAX - value.value) );
+}
+
+static __mpu_uint64_t
+mcpp_semantic_unsigned_from_signed( __mpu_int64_t value )
+{
+ if( value >= 0 )
+ return( (__mpu_uint64_t)value );
+
+ return( UINT64_MAX - (__mpu_uint64_t)(-(value + 1)) );
+}
+
+static unsigned
+mcpp_semantic_shift_count( mcpp_integer value, int *negative )
+{
+ __mpu_uint64_t magnitude;
+
+ *negative = 0;
+ if( !value.unsignedp && mcpp_semantic_signed(value) < 0 )
+ {
+ *negative = 1;
+ magnitude = (__mpu_uint64_t)0 - value.value;
+ }
+ else
+ magnitude = value.value;
+
+ if( magnitude >= MCPP_INTEGER_BITS )
+ return( MCPP_INTEGER_BITS );
+
+ return( (unsigned)magnitude );
+}
+
+static mcpp_integer
+mcpp_semantic_shift_left_count( mcpp_integer value, unsigned shift )
+{
+ mcpp_integer result;
+
+ result.unsignedp = value.unsignedp;
+ if( shift >= MCPP_INTEGER_BITS )
+ result.value = 0;
+ else
+ result.value = value.value << shift;
+
+ return( result );
+}
+
+static mcpp_integer
+mcpp_semantic_shift_right_count( mcpp_integer value, unsigned shift )
+{
+ mcpp_integer result;
+ int negative;
+
+ result.unsignedp = value.unsignedp;
+ negative = !value.unsignedp && mcpp_semantic_signed(value) < 0;
+
+ if( shift >= MCPP_INTEGER_BITS )
+ {
+ result.value = negative ? UINT64_MAX : 0;
+ return( result );
+ }
+
+ if( shift == 0 || value.unsignedp || !negative )
+ {
+ result.value = value.value >> shift;
+ return( result );
+ }
+
+ result.value = (value.value >> shift) |
+ (UINT64_MAX << (MCPP_INTEGER_BITS - shift));
+ return( result );
+}
+
+void
+mcpp_semantic_context_init( mcpp_semantic_context *context,
+ const char *filename,
+ unsigned line_number,
+ const mcpp_options *options )
+{
+ context->filename = filename;
+ context->line_number = line_number;
+ context->options = options;
+ context->failed = 0;
+}
+
+void
+mcpp_semantic_error( mcpp_semantic_context *context, const char *message )
+{
+ if( context->failed )
+ return;
+
+ fprintf( stderr, "%s:%u: error: %s\n",
+ context->filename, context->line_number, message );
+ context->failed = 1;
+}
+
+void
+mcpp_semantic_warning( mcpp_semantic_context *context,
+ const char *message )
+{
+ if( mcpp_diagnostic_warning(context->options,
+ context->filename, context->line_number,
+ "%s", message) != 0 )
+ context->failed = 1;
+}
+
+int
+mcpp_semantic_failed( const mcpp_semantic_context *context )
+{
+ return( context->failed );
+}
+
+mcpp_integer
+mcpp_semantic_make( __mpu_uint64_t value, int unsignedp )
+{
+ mcpp_integer result;
+
+ result.value = value;
+ result.unsignedp = unsignedp ? 1 : 0;
+ return( result );
+}
+
+int
+mcpp_semantic_true( mcpp_integer value )
+{
+ return( value.value != 0 );
+}
+
+mcpp_integer
+mcpp_semantic_neg( mcpp_integer value )
+{
+ value.value = (__mpu_uint64_t)0 - value.value;
+ return( value );
+}
+
+mcpp_integer
+mcpp_semantic_not( mcpp_integer value )
+{
+ return( mcpp_semantic_make(!mcpp_semantic_true(value), 0) );
+}
+
+mcpp_integer
+mcpp_semantic_compl( mcpp_integer value )
+{
+ value.value = ~value.value;
+ return( value );
+}
+
+mcpp_integer
+mcpp_semantic_mul( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(left.value * right.value,
+ left.unsignedp || right.unsignedp) );
+}
+
+mcpp_integer
+mcpp_semantic_div( mcpp_semantic_context *context,
+ mcpp_integer left, mcpp_integer right, int evaluate )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+
+ if( right.value == 0 )
+ {
+ if( evaluate )
+ mcpp_semantic_error( context, "division by zero in #if expression" );
+ return( mcpp_semantic_make(0, unsignedp) );
+ }
+
+ if( unsignedp )
+ return( mcpp_semantic_make(left.value / right.value, 1) );
+
+ if( mcpp_semantic_signed(left) == INT64_MIN &&
+ mcpp_semantic_signed(right) == -1 )
+ return( mcpp_semantic_make((__mpu_uint64_t)1 << 63, 0) );
+
+ return( mcpp_semantic_make(
+ mcpp_semantic_unsigned_from_signed(
+ mcpp_semantic_signed(left) / mcpp_semantic_signed(right)), 0) );
+}
+
+mcpp_integer
+mcpp_semantic_mod( mcpp_semantic_context *context,
+ mcpp_integer left, mcpp_integer right, int evaluate )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+
+ if( right.value == 0 )
+ {
+ if( evaluate )
+ mcpp_semantic_error( context, "division by zero in #if expression" );
+ return( mcpp_semantic_make(0, unsignedp) );
+ }
+
+ if( unsignedp )
+ return( mcpp_semantic_make(left.value % right.value, 1) );
+
+ if( mcpp_semantic_signed(left) == INT64_MIN &&
+ mcpp_semantic_signed(right) == -1 )
+ return( mcpp_semantic_make(0, 0) );
+
+ return( mcpp_semantic_make(
+ mcpp_semantic_unsigned_from_signed(
+ mcpp_semantic_signed(left) % mcpp_semantic_signed(right)), 0) );
+}
+
+mcpp_integer
+mcpp_semantic_add( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(left.value + right.value,
+ left.unsignedp || right.unsignedp) );
+}
+
+mcpp_integer
+mcpp_semantic_sub( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(left.value - right.value,
+ left.unsignedp || right.unsignedp) );
+}
+
+mcpp_integer
+mcpp_semantic_lshift( mcpp_integer left, mcpp_integer right )
+{
+ unsigned shift;
+ int negative;
+
+ shift = mcpp_semantic_shift_count( right, &negative );
+ if( negative )
+ return( mcpp_semantic_shift_right_count(left, shift) );
+
+ return( mcpp_semantic_shift_left_count(left, shift) );
+}
+
+mcpp_integer
+mcpp_semantic_rshift( mcpp_integer left, mcpp_integer right )
+{
+ unsigned shift;
+ int negative;
+
+ shift = mcpp_semantic_shift_count( right, &negative );
+ if( negative )
+ return( mcpp_semantic_shift_left_count(left, shift) );
+
+ return( mcpp_semantic_shift_right_count(left, shift) );
+}
+
+mcpp_integer
+mcpp_semantic_equal( mcpp_integer left, mcpp_integer right )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+ int value = unsignedp ? left.value == right.value :
+ mcpp_semantic_signed(left) == mcpp_semantic_signed(right);
+ return( mcpp_semantic_make((__mpu_uint64_t)value, 0) );
+}
+
+mcpp_integer
+mcpp_semantic_notequal( mcpp_integer left, mcpp_integer right )
+{
+ mcpp_integer result = mcpp_semantic_equal( left, right );
+ result.value = !result.value;
+ return( result );
+}
+
+mcpp_integer
+mcpp_semantic_less( mcpp_integer left, mcpp_integer right )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+ int value = unsignedp ? left.value < right.value :
+ mcpp_semantic_signed(left) < mcpp_semantic_signed(right);
+ return( mcpp_semantic_make((__mpu_uint64_t)value, 0) );
+}
+
+mcpp_integer
+mcpp_semantic_greater( mcpp_integer left, mcpp_integer right )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+ int value = unsignedp ? left.value > right.value :
+ mcpp_semantic_signed(left) > mcpp_semantic_signed(right);
+ return( mcpp_semantic_make((__mpu_uint64_t)value, 0) );
+}
+
+mcpp_integer
+mcpp_semantic_leq( mcpp_integer left, mcpp_integer right )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+ int value = unsignedp ? left.value <= right.value :
+ mcpp_semantic_signed(left) <= mcpp_semantic_signed(right);
+ return( mcpp_semantic_make((__mpu_uint64_t)value, 0) );
+}
+
+mcpp_integer
+mcpp_semantic_geq( mcpp_integer left, mcpp_integer right )
+{
+ int unsignedp = left.unsignedp || right.unsignedp;
+ int value = unsignedp ? left.value >= right.value :
+ mcpp_semantic_signed(left) >= mcpp_semantic_signed(right);
+ return( mcpp_semantic_make((__mpu_uint64_t)value, 0) );
+}
+
+mcpp_integer
+mcpp_semantic_bitand( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(left.value & right.value,
+ left.unsignedp || right.unsignedp) );
+}
+
+mcpp_integer
+mcpp_semantic_bitxor( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(left.value ^ right.value,
+ left.unsignedp || right.unsignedp) );
+}
+
+mcpp_integer
+mcpp_semantic_bitor( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(left.value | right.value,
+ left.unsignedp || right.unsignedp) );
+}
+
+mcpp_integer
+mcpp_semantic_and( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(mcpp_semantic_true(left) && mcpp_semantic_true(right), 0) );
+}
+
+mcpp_integer
+mcpp_semantic_or( mcpp_integer left, mcpp_integer right )
+{
+ return( mcpp_semantic_make(mcpp_semantic_true(left) || mcpp_semantic_true(right), 0) );
+}
+
+mcpp_integer
+mcpp_semantic_conditional( mcpp_integer condition,
+ mcpp_integer yes, mcpp_integer no )
+{
+ mcpp_integer result = mcpp_semantic_true(condition) ? yes : no;
+
+ result.unsignedp = yes.unsignedp || no.unsignedp;
+ return( result );
+}
diff --git a/src/mcpp-semantic.h b/src/mcpp-semantic.h
new file mode 100644
index 0000000..efa793e
--- /dev/null
+++ b/src/mcpp-semantic.h
@@ -0,0 +1,67 @@
+#ifndef __MCPU_CPP_SEMANTIC_H__
+#define __MCPU_CPP_SEMANTIC_H__ 1
+
+#include <defs.h>
+
+typedef struct mcpp_options mcpp_options;
+typedef struct mcpp_integer mcpp_integer;
+struct mcpp_integer
+{
+ __mpu_uint64_t value;
+ int unsignedp;
+};
+
+typedef struct mcpp_semantic_context mcpp_semantic_context;
+struct mcpp_semantic_context
+{
+ const char *filename;
+ unsigned line_number;
+ const mcpp_options *options;
+ int failed;
+};
+
+void mcpp_semantic_context_init( mcpp_semantic_context *context,
+ const char *filename,
+ unsigned line_number,
+ const mcpp_options *options );
+void mcpp_semantic_error( mcpp_semantic_context *context,
+ const char *message );
+void mcpp_semantic_warning( mcpp_semantic_context *context,
+ const char *message );
+int mcpp_semantic_failed( const mcpp_semantic_context *context );
+
+mcpp_integer mcpp_semantic_make( __mpu_uint64_t value, int unsignedp );
+int mcpp_semantic_true( mcpp_integer value );
+
+mcpp_integer mcpp_semantic_neg( mcpp_integer value );
+mcpp_integer mcpp_semantic_not( mcpp_integer value );
+mcpp_integer mcpp_semantic_compl( mcpp_integer value );
+
+mcpp_integer mcpp_semantic_mul( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_div( mcpp_semantic_context *context,
+ mcpp_integer left, mcpp_integer right,
+ int evaluate );
+mcpp_integer mcpp_semantic_mod( mcpp_semantic_context *context,
+ mcpp_integer left, mcpp_integer right,
+ int evaluate );
+mcpp_integer mcpp_semantic_add( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_sub( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_lshift( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_rshift( mcpp_integer left, mcpp_integer right );
+
+mcpp_integer mcpp_semantic_equal( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_notequal( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_less( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_greater( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_leq( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_geq( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_bitand( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_bitxor( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_bitor( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_and( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_or( mcpp_integer left, mcpp_integer right );
+mcpp_integer mcpp_semantic_conditional( mcpp_integer condition,
+ mcpp_integer yes,
+ mcpp_integer no );
+
+#endif /* __MCPU_CPP_SEMANTIC_H__ */
diff --git a/src/mcpp-source.c b/src/mcpp-source.c
new file mode 100644
index 0000000..c40e370
--- /dev/null
+++ b/src/mcpp-source.c
@@ -0,0 +1,332 @@
+#include <defs.h>
+
+static char *
+read_stream_bytes( FILE *fp, size_t *file_length )
+{
+ char *data = NULL;
+ size_t length = 0;
+ size_t capacity = 0;
+
+ if( fp == NULL || file_length == NULL )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ *file_length = 0;
+
+ for( ;; )
+ {
+ size_t n;
+
+ if( capacity - length < 4096 )
+ {
+ size_t new_capacity = capacity ? capacity * 2 : 8192;
+ char *q = (char *)realloc( data, new_capacity + 1 );
+ if( q == NULL )
+ {
+ free( data );
+ return( NULL );
+ }
+ data = q;
+ capacity = new_capacity;
+ }
+
+ n = fread( data + length, 1, capacity - length, fp );
+ length += n;
+
+ if( n == 0 )
+ {
+ if( ferror(fp) )
+ {
+ free( data );
+ return( NULL );
+ }
+ break;
+ }
+ }
+
+ if( data == NULL )
+ {
+ data = (char *)calloc( 1, 1 );
+ if( data == NULL )
+ return( NULL );
+ }
+ else
+ data[length] = 0;
+
+ *file_length = length;
+ return( data );
+}
+
+static int
+normalize_newlines( mcpu_text *text )
+{
+ size_t r;
+ size_t w = 0;
+
+ if( text == NULL || text->data == NULL )
+ return( 0 );
+
+ for( r = 0; r < text->length; ++r )
+ {
+ if( text->data[r] == '\r' )
+ {
+ if( r + 1 < text->length && text->data[r + 1] == '\n' )
+ ++r;
+ text->data[w++] = '\n';
+ }
+ else
+ text->data[w++] = text->data[r];
+ }
+
+ text->length = w;
+ text->data[w] = 0;
+
+ return( 0 );
+}
+
+void
+mcpu_source_init( mcpu_source *source )
+{
+ if( source == NULL )
+ return;
+
+ source->filename = NULL;
+ mcpu_text_init( &source->text );
+ mcpu_text_init( &source->logical_line );
+ source->splice_offsets = NULL;
+ source->splice_count = 0;
+ source->splice_capacity = 0;
+ source->offset = 0;
+ source->line = 1;
+}
+
+void
+mcpu_source_free( mcpu_source *source )
+{
+ if( source == NULL )
+ return;
+
+ free( source->filename );
+ source->filename = NULL;
+ mcpu_text_free( &source->text );
+ mcpu_text_free( &source->logical_line );
+ free( source->splice_offsets );
+ source->splice_offsets = NULL;
+ source->splice_count = 0;
+ source->splice_capacity = 0;
+ source->offset = 0;
+ source->line = 1;
+}
+
+static int
+source_from_bytes( mcpu_source *source, char *bytes, size_t byte_length,
+ const char *name )
+{
+ const __mpu_char8_t *input = (const __mpu_char8_t *)bytes;
+ const __mpu_char8_t *p;
+
+ if( memchr( bytes, 0, byte_length ) != NULL )
+ {
+ fprintf( stderr, "%s: NUL character is not allowed in source text\n", name );
+ return( -1 );
+ }
+
+ if( byte_length >= 3 &&
+ (unsigned char)bytes[0] == 0xef &&
+ (unsigned char)bytes[1] == 0xbb &&
+ (unsigned char)bytes[2] == 0xbf )
+ input += 3;
+
+ if( !mpu_utf8valid(input) )
+ {
+ fprintf( stderr, "%s: invalid UTF-8\n", name );
+ return( -1 );
+ }
+
+ p = input;
+ while( *p != 0 )
+ {
+ __mpu_char32_t value;
+ const __mpu_char8_t *next;
+ __mpu_char16_t c;
+
+ next = mpu_utf8get( p, &value );
+ if( next == NULL )
+ {
+ fprintf( stderr, "%s: invalid UTF-8\n", name );
+ return( -1 );
+ }
+
+ if( value <= 0xffff )
+ c = (__mpu_char16_t)value;
+ else
+ c = MCPU_CPP_NON_UCS2_SENTINEL;
+
+ if( mcpu_text_append_char(&source->text, c) != 0 )
+ return( -1 );
+
+ p = next;
+ }
+
+ if( normalize_newlines(&source->text) != 0 )
+ return( -1 );
+
+ source->filename = strdup( name );
+ if( source->filename == NULL )
+ return( -1 );
+
+ return( 0 );
+}
+
+int
+mcpu_source_valid_ucs2( const __mpu_char16_t *text, size_t length )
+{
+ size_t i;
+
+ if( text == NULL && length != 0 )
+ return( 0 );
+
+ for( i = 0; i < length; ++i )
+ if( text[i] == MCPU_CPP_NON_UCS2_SENTINEL )
+ return( 0 );
+
+ return( 1 );
+}
+
+int
+mcpu_source_open_stream( mcpu_source *source, FILE *stream, const char *name )
+{
+ char *bytes;
+ size_t byte_length;
+ int rc;
+
+ if( source == NULL || stream == NULL || name == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpu_source_free( source );
+ mcpu_source_init( source );
+
+ bytes = read_stream_bytes( stream, &byte_length );
+ if( bytes == NULL )
+ return( -1 );
+
+ rc = source_from_bytes( source, bytes, byte_length, name );
+ free( bytes );
+ return( rc );
+}
+
+int
+mcpu_source_open( mcpu_source *source, const char *filename )
+{
+ FILE *fp;
+ int rc;
+
+ if( source == NULL || filename == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ fp = fopen( filename, "rb" );
+ if( fp == NULL )
+ return( -1 );
+
+ rc = mcpu_source_open_stream( source, fp, filename );
+ if( fclose(fp) != 0 && rc == 0 )
+ rc = -1;
+
+ return( rc );
+}
+
+static int
+append_splice_offset( mcpu_source *source, size_t offset )
+{
+ size_t *offsets;
+ size_t capacity;
+
+ if( source->splice_count < source->splice_capacity )
+ {
+ source->splice_offsets[source->splice_count++] = offset;
+ return( 0 );
+ }
+
+ capacity = source->splice_capacity ? source->splice_capacity * 2 : 8;
+ offsets = (size_t *)realloc( source->splice_offsets,
+ capacity * sizeof(*offsets) );
+ if( offsets == NULL )
+ return( -1 );
+
+ source->splice_offsets = offsets;
+ source->splice_capacity = capacity;
+ source->splice_offsets[source->splice_count++] = offset;
+ return( 0 );
+}
+
+
+int
+mcpu_source_next_line( mcpu_source *source,
+ const __mpu_char16_t **line,
+ size_t *length,
+ unsigned *line_number,
+ unsigned *next_line_number,
+ int *spliced )
+{
+ unsigned first_line;
+
+ if( source == NULL || line == NULL || length == NULL ||
+ line_number == NULL || next_line_number == NULL || spliced == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( source->offset >= source->text.length )
+ return( 0 );
+
+ mcpu_text_free( &source->logical_line );
+ mcpu_text_init( &source->logical_line );
+ source->splice_count = 0;
+ first_line = source->line;
+ *spliced = 0;
+
+ while( source->offset < source->text.length )
+ {
+ __mpu_char16_t c = source->text.data[source->offset++];
+
+ /*
+ * Backslash-newline deletion is preprocessing phase 2 and therefore
+ * happens before comments, directives and macro recognition.
+ */
+ if( c == '\\' && source->offset < source->text.length &&
+ source->text.data[source->offset] == '\n' )
+ {
+ if( append_splice_offset(source, source->logical_line.length) != 0 )
+ return( -1 );
+ ++source->offset;
+ ++source->line;
+ *spliced = 1;
+ continue;
+ }
+
+ if( mcpu_text_append_char( &source->logical_line, c ) != 0 )
+ return( -1 );
+
+ if( c == '\n' )
+ {
+ ++source->line;
+ break;
+ }
+ }
+
+ *line = source->logical_line.data;
+ *length = source->logical_line.length;
+ *line_number = first_line;
+ *next_line_number = source->line;
+
+ return( 1 );
+}
diff --git a/src/mcpp-source.h b/src/mcpp-source.h
new file mode 100644
index 0000000..9a7973a
--- /dev/null
+++ b/src/mcpp-source.h
@@ -0,0 +1,35 @@
+#ifndef __MCPU_CPP_SOURCE_H__
+#define __MCPU_CPP_SOURCE_H__ 1
+
+#include <defs.h>
+#include <mcpp-text.h>
+
+#define MCPU_CPP_NON_UCS2_SENTINEL 0xd800
+
+typedef struct mcpu_source mcpu_source;
+struct mcpu_source
+{
+ char *filename;
+ mcpu_text text;
+ mcpu_text logical_line;
+ size_t *splice_offsets;
+ size_t splice_count;
+ size_t splice_capacity;
+ size_t offset;
+ unsigned line;
+};
+
+void mcpu_source_init( mcpu_source *source );
+void mcpu_source_free( mcpu_source *source );
+int mcpu_source_open( mcpu_source *source, const char *filename );
+int mcpu_source_open_stream( mcpu_source *source, FILE *stream,
+ const char *name );
+int mcpu_source_valid_ucs2( const __mpu_char16_t *text, size_t length );
+int mcpu_source_next_line( mcpu_source *source,
+ const __mpu_char16_t **line,
+ size_t *length,
+ unsigned *line_number,
+ unsigned *next_line_number,
+ int *spliced );
+
+#endif /* __MCPU_CPP_SOURCE_H__ */
diff --git a/src/mcpp-text.c b/src/mcpp-text.c
new file mode 100644
index 0000000..febbd3b
--- /dev/null
+++ b/src/mcpp-text.c
@@ -0,0 +1,237 @@
+#include <defs.h>
+
+void
+mcpu_text_init( mcpu_text *text )
+{
+ if( text == NULL )
+ return;
+
+ text->data = NULL;
+ text->length = 0;
+ text->capacity = 0;
+}
+
+void
+mcpu_text_free( mcpu_text *text )
+{
+ if( text == NULL )
+ return;
+
+ free( text->data );
+ text->data = NULL;
+ text->length = 0;
+ text->capacity = 0;
+}
+
+int
+mcpu_text_reserve( mcpu_text *text, size_t need )
+{
+ __mpu_char16_t *p;
+ size_t capacity;
+
+ if( text == NULL )
+ return( -1 );
+
+ if( need <= text->capacity )
+ return( 0 );
+
+ capacity = text->capacity ? text->capacity : 256;
+ while( capacity < need )
+ {
+ if( capacity > (SIZE_MAX / 2) )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+ capacity *= 2;
+ }
+
+ if( capacity > (SIZE_MAX / sizeof(__mpu_char16_t)) )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ p = (__mpu_char16_t *)realloc( text->data,
+ capacity * sizeof(__mpu_char16_t) );
+ if( p == NULL )
+ return( -1 );
+
+ text->data = p;
+ text->capacity = capacity;
+
+ return( 0 );
+}
+
+int
+mcpu_text_append( mcpu_text *text,
+ const __mpu_char16_t *data, size_t length )
+{
+ if( text == NULL || (data == NULL && length != 0) )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ if( length > SIZE_MAX - text->length - 1 )
+ {
+ errno = EOVERFLOW;
+ return( -1 );
+ }
+
+ if( mcpu_text_reserve( text, text->length + length + 1 ) != 0 )
+ return( -1 );
+
+ if( length != 0 )
+ memcpy( text->data + text->length, data,
+ length * sizeof(__mpu_char16_t) );
+
+ text->length += length;
+ text->data[text->length] = 0;
+
+ return( 0 );
+}
+
+int
+mcpu_text_append_char( mcpu_text *text, __mpu_char16_t ch )
+{
+ return( mcpu_text_append( text, &ch, 1 ) );
+}
+
+int
+mcpu_text_append_ascii( mcpu_text *text, const char *s )
+{
+ if( text == NULL || s == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ while( *s )
+ {
+ unsigned char c = (unsigned char)*s++;
+
+ if( c > 0x7f )
+ {
+ errno = EILSEQ;
+ return( -1 );
+ }
+
+ if( mcpu_text_append_char( text, (__mpu_char16_t)c ) != 0 )
+ return( -1 );
+ }
+
+ return( 0 );
+}
+
+int
+mcpu_text_append_utf8( mcpu_text *text, const char *s )
+{
+ __mpu_char16_t *u;
+ __mpu_size_t n;
+ int rc;
+
+ if( text == NULL || s == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ n = mpu_utf8_to_ucs2( NULL, (const __mpu_char8_t *)s, 0 );
+ if( n == (__mpu_size_t)-1 )
+ return( -1 );
+
+ u = (__mpu_char16_t *)calloc( (size_t)n + 1, sizeof(__mpu_char16_t) );
+ if( u == NULL )
+ return( -1 );
+
+ if( mpu_utf8_to_ucs2( u, (const __mpu_char8_t *)s,
+ (size_t)n + 1 ) == (__mpu_size_t)-1 )
+ {
+ free( u );
+ return( -1 );
+ }
+
+ rc = mcpu_text_append( text, u, (size_t)n );
+ free( u );
+
+ return( rc );
+}
+
+int
+mcpu_text_from_utf8( mcpu_text *text, const char *s )
+{
+ if( text == NULL || s == NULL )
+ {
+ errno = EINVAL;
+ return( -1 );
+ }
+
+ mcpu_text_free( text );
+ mcpu_text_init( text );
+
+ return( mcpu_text_append_utf8( text, s ) );
+}
+
+char *
+mcpu_text_to_utf8( const __mpu_char16_t *data, size_t length )
+{
+ __mpu_char16_t *tmp;
+ __mpu_char8_t *out;
+ __mpu_size_t n;
+
+ if( data == NULL && length != 0 )
+ {
+ errno = EINVAL;
+ return( NULL );
+ }
+
+ tmp = (__mpu_char16_t *)calloc( length + 1, sizeof(__mpu_char16_t) );
+ if( tmp == NULL )
+ return( NULL );
+
+ if( length != 0 )
+ memcpy( tmp, data, length * sizeof(__mpu_char16_t) );
+
+ n = mpu_ucs2_to_utf8( NULL, tmp, 0 );
+ if( n == (__mpu_size_t)-1 )
+ {
+ free( tmp );
+ return( NULL );
+ }
+
+ out = (__mpu_char8_t *)calloc( (size_t)n + 1, 1 );
+ if( out == NULL )
+ {
+ free( tmp );
+ return( NULL );
+ }
+
+ if( mpu_ucs2_to_utf8( out, tmp, (size_t)n + 1 ) == (__mpu_size_t)-1 )
+ {
+ free( out );
+ free( tmp );
+ return( NULL );
+ }
+
+ free( tmp );
+ return( (char *)out );
+}
+
+int
+mcpu_text_equal_ascii( const __mpu_char16_t *data,
+ size_t length, const char *s )
+{
+ size_t i;
+
+ if( data == NULL || s == NULL )
+ return( 0 );
+
+ for( i = 0; i < length && s[i]; ++i )
+ {
+ if( data[i] != (__mpu_char16_t)(unsigned char)s[i] )
+ return( 0 );
+ }
+
+ return( i == length && s[i] == 0 );
+}
diff --git a/src/mcpp-text.h b/src/mcpp-text.h
new file mode 100644
index 0000000..dce651c
--- /dev/null
+++ b/src/mcpp-text.h
@@ -0,0 +1,27 @@
+#ifndef __MCPU_CPP_TEXT_H__
+#define __MCPU_CPP_TEXT_H__ 1
+
+#include <defs.h>
+
+typedef struct mcpu_text mcpu_text;
+struct mcpu_text
+{
+ __mpu_char16_t *data;
+ size_t length;
+ size_t capacity;
+};
+
+void mcpu_text_init( mcpu_text *text );
+void mcpu_text_free( mcpu_text *text );
+int mcpu_text_reserve( mcpu_text *text, size_t need );
+int mcpu_text_append( mcpu_text *text,
+ const __mpu_char16_t *data, size_t length );
+int mcpu_text_append_char( mcpu_text *text, __mpu_char16_t ch );
+int mcpu_text_append_ascii( mcpu_text *text, const char *s );
+int mcpu_text_append_utf8( mcpu_text *text, const char *s );
+int mcpu_text_from_utf8( mcpu_text *text, const char *s );
+char *mcpu_text_to_utf8( const __mpu_char16_t *data, size_t length );
+int mcpu_text_equal_ascii( const __mpu_char16_t *data,
+ size_t length, const char *s );
+
+#endif /* __MCPU_CPP_TEXT_H__ */