diff options
| author | kx <kx@radix-linux.su> | 2026-10-01 12:02:28 +0300 |
|---|---|---|
| committer | kx <kx@radix-linux.su> | 2026-10-01 12:02:28 +0300 |
| commit | ba1b04d64bdfaf377915b22f77520216ebe47674 (patch) | |
| tree | 044343035c28b2b04ca9725f26e9b7c39342d19c /src | |
| parent | 124b140798456778e2a96851cc7268aa9a726698 (diff) | |
| download | mcpu-cpp-1.0.2.tar.xz | |
Version 1.0.21.0.2
Diffstat (limited to 'src')
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, "ed ) != 0 ) + { + mcpu_text expanded; + + mcpu_text_init( &expanded ); + if( mcpu_macro_expand(&cpp->macros, line + p, length - p, + current_language(cpp), &context, + &expanded) != 0 || + parse_include_filename(expanded.data, expanded.length, 0, + &requested, "ed) != 0 ) + { + fprintf( stderr, + "%s:%u: error: #%s does not expand to \"file\" or <file>\n", + nominal_filename, line_number, + include_next ? "include_next" : "include" ); + mcpu_text_free( &expanded ); + goto done; + } + mcpu_text_free( &expanded ); + } + + found = mcpu_include_find( &cpp->include_paths, + source_filename, + requested, + quoted, + current_language(cpp), + include_next, + include_next ? current_dir : NULL, + &found_dir ); + if( found == NULL ) + { + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->print_deps_missing_files ) + { + int dependency_is_system = source_is_system || !quoted; + + if( dependency_add_unresolved(cpp, requested, + dependency_is_system) != 0 ) + goto done; + + if( cpp->verbose ) + fprintf( stderr, "%s:%u: %s %s -> <generated> [%s]\n", + nominal_filename, line_number, + include_next ? "include_next" : "include", + requested, mcpu_language_name(current_language(cpp)) ); + + rc = 0; + goto done; + } + + fprintf( stderr, "%s:%u: error: cannot find %s file '%s'\n", + nominal_filename, line_number, + include_next ? "include_next" : "include", requested ); + goto done; + } + + if( cpp->verbose ) + fprintf( stderr, "%s:%u: %s %s -> %s [%s]\n", + nominal_filename, line_number, + include_next ? "include_next" : "include", + requested, found, mcpu_language_name(current_language(cpp)) ); + + { + int dependency_is_system = source_is_system || include_dir_is_system(found_dir); + + if( dependency_add(cpp, found, dependency_is_system) != 0 ) + goto done; + + if( once_file_seen(cpp, found) ) + { + rc = 0; + goto done; + } + + if( sync_output_position(cpp, line_number, nominal_filename) != 0 || + append_line_marker(cpp, 1, found, 1) != 0 ) + goto done; + if( process_file( cpp, found, found_dir, dependency_is_system ) != 0 ) + goto done; + } + if( append_line_marker( cpp, resume_line_number, nominal_filename, 2 ) != 0 ) + goto done; + + rc = 0; + +done: + free( requested ); + free( found ); + return( rc ); +} + +static int +process_source( mcpp *cpp, mcpu_source *source, + const mcpu_include_dir *include_dir, + int source_is_system ) +{ + mcpu_pp_lexer_state lexer; + mcpu_text prepared; + mcpu_text expanded; + const __mpu_char16_t *line; + size_t length; + unsigned line_number; + unsigned next_line_number; + int spliced; + size_t conditional_base; + int status; + mcpu_macro_expansion_context context; + const char *filename = source->filename; + char *nominal_filename; + int64_t line_delta = 0; + + if( cpp->include_depth >= MCPU_CPP_INCLUDE_STACK_SIZE ) + { + fprintf( stderr, "%s: error: include nesting exceeds %d files\n", + filename, MCPU_CPP_INCLUDE_STACK_SIZE ); + return( -1 ); + } + + nominal_filename = strdup( filename ); + if( nominal_filename == NULL ) + return( -1 ); + + ++cpp->include_depth; + conditional_base = cpp->conditional_depth; + mcpu_pp_lexer_init( &lexer ); + mcpu_text_init( &prepared ); + mcpu_text_init( &expanded ); + + while( (status = mcpu_source_next_line( source, &line, &length, + &line_number, + &next_line_number, &spliced )) > 0 ) + { + int64_t reported = (int64_t)line_number + line_delta; + int64_t reported_next = (int64_t)next_line_number + line_delta; + unsigned logical_line; + unsigned logical_next_line; + size_t hash = 0; + int directive; + + if( reported < 0 || reported > UINT_MAX || + reported_next < 0 || reported_next > UINT_MAX ) + { + fprintf( stderr, "%s:%u: error: logical line number out of range\n", + filename, line_number ); + goto fail; + } + + logical_line = (unsigned)reported; + logical_next_line = (unsigned)reported_next; + + mcpu_pp_lexer_set_diagnostics(&lexer, MCPP_OPTIONS(cpp), + nominal_filename, logical_line, + source->splice_offsets, + source->splice_count); + + if( mcpu_pp_prepare_line(&lexer, line, length, + current_language(cpp), &prepared) != 0 ) + goto fail; + + line = prepared.data; + length = prepared.length; + + if( !mcpu_source_valid_ucs2(line, length) ) + { + fprintf( stderr, "%s:%u: error: character outside UCS-2\n", + nominal_filename, logical_line ); + goto fail; + } + + directive = mcpu_pp_find_directive( line, length, &hash ); + if( directive < 0 ) + goto fail; + + if( directive ) + { + size_t name_start; + size_t name_length; + size_t after_name; + + if( parse_directive_name( line, length, hash, + &name_start, &name_length, + &after_name ) ) + { + enum mcpp_conditional_directive conditional; + + conditional = conditional_directive( line + name_start, name_length ); + if( conditional != MCPP_COND_NONE ) + { + if( handle_conditional(cpp, conditional, source->filename, + line_number, line, length, after_name, + conditional_base) != 0 ) + goto fail; + continue; + } + + if( !conditional_active(cpp) ) + continue; + + { + int diagnostic_error; + const char *diagnostic_name; + + if( diagnostic_directive(line + name_start, name_length, + &diagnostic_error, &diagnostic_name) ) + { + if( handle_diagnostic(cpp, nominal_filename, logical_line, + line, length, after_name, + diagnostic_error, diagnostic_name) != 0 ) + goto fail; + + continue; + } + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "line") ) + { + if( handle_line( cpp, nominal_filename, logical_line, + next_line_number, line, length, after_name, + &nominal_filename, &line_delta ) != 0 ) + goto fail; + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "pragma") ) + { + if( pragma_once_directive(line, length, after_name) ) + { + if( once_file_mark(cpp, source->filename) != 0 ) + { + fprintf( stderr, + "%s:%u: error: cannot record #pragma once: %s\n", + nominal_filename, logical_line, strerror(errno) ); + goto fail; + } + + continue; + } + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "include") || + mcpu_text_equal_ascii(line + name_start, name_length, + "include_next") ) + { + int include_next = + mcpu_text_equal_ascii( line + name_start, name_length, + "include_next" ); + + if( handle_include( cpp, source->filename, nominal_filename, + logical_line, logical_next_line, + line, length, after_name, include_next, + include_dir, source_is_system ) != 0 ) + goto fail; + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "define") ) + { + if( handle_define( cpp, nominal_filename, logical_line, + line, length, after_name, + MCPU_MACRO_ORIGIN_SOURCE ) != 0 ) + goto fail; + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_macros == MCPP_DUMP_DEFINITIONS && + append_visible_text(cpp, logical_line, nominal_filename, + line, length) != 0 ) + goto fail; + + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "undef") ) + { + if( handle_undef( cpp, nominal_filename, logical_line, + line, length, after_name ) != 0 ) + goto fail; + continue; + } + + if( mcpu_text_equal_ascii(line + name_start, name_length, "lang") ) + { + { + size_t output_start; + + if( sync_output_position(cpp, logical_line, nominal_filename) != 0 ) + goto fail; + output_start = cpp->output.length; + if( handle_lang(cpp, nominal_filename, logical_line, + line, length, after_name, spliced) != 0 ) + goto fail; + advance_output_position( cpp, cpp->output.data + output_start, + cpp->output.length - output_start ); + } + continue; + } + + if( mcpu_text_equal_ascii( line + name_start, + name_length, "endlang" ) ) + { + { + size_t output_start; + + if( sync_output_position(cpp, logical_line, nominal_filename) != 0 ) + goto fail; + output_start = cpp->output.length; + if( handle_endlang(cpp, nominal_filename, logical_line, + line, length) != 0 ) + goto fail; + advance_output_position( cpp, cpp->output.data + output_start, + cpp->output.length - output_start ); + } + continue; + } + } + } + + if( !conditional_active(cpp) ) + continue; + + macro_context( cpp, nominal_filename, logical_line, &context ); + if( mcpu_macro_expand(&cpp->macros, line, length, + current_language(cpp), &context, + &expanded) != 0 || + append_visible_text(cpp, logical_line, nominal_filename, + expanded.data, expanded.length) != 0 ) + goto fail; + } + + if( status < 0 ) + goto fail; + + if( lexer.in_block_comment ) + { + fprintf( stderr, "%s: error: unterminated comment at end of file\n", + nominal_filename ); + goto fail; + } + + if( cpp->conditional_depth != conditional_base ) + { + mcpp_conditional *frame = + &cpp->conditional_stack[cpp->conditional_depth - 1]; + + fprintf( stderr, "%s:%u: error: unterminated conditional directive\n", + frame->filename, frame->line_number ); + goto fail; + } + + --cpp->include_depth; + mcpu_text_free( &prepared ); + mcpu_text_free( &expanded ); + free( nominal_filename ); + return( 0 ); + +fail: + cpp->conditional_depth = conditional_base; + --cpp->include_depth; + mcpu_text_free( &prepared ); + mcpu_text_free( &expanded ); + free( nominal_filename ); + return( -1 ); +} + +static int +process_file( mcpp *cpp, const char *filename, + const mcpu_include_dir *include_dir, + int system_header ) +{ + mcpu_source source; + int rc; + + mcpu_source_init( &source ); + if( mcpu_source_open( &source, filename ) != 0 ) + { + fprintf( stderr, "%s: %s\n", filename, strerror(errno) ); + mcpu_source_free( &source ); + return( -1 ); + } + + rc = process_source( cpp, &source, include_dir, system_header ); + mcpu_source_free( &source ); + return( rc ); +} + +static int +process_stream( mcpp *cpp, FILE *stream, const char *name ) +{ + mcpu_source source; + int rc; + + mcpu_source_init( &source ); + if( mcpu_source_open_stream( &source, stream, name ) != 0 ) + { + fprintf( stderr, "%s: %s\n", name, strerror(errno) ); + mcpu_source_free( &source ); + return( -1 ); + } + + rc = process_source( cpp, &source, NULL, 0 ); + mcpu_source_free( &source ); + return( rc ); +} + +void +mcpp_init( mcpp *cpp ) +{ + if( cpp == NULL ) + return; + + cpp->options_data = NULL; + mcpu_config_init( &cpp->config ); + mcpp_runtime_paths_init( &cpp->runtime_paths ); + mcpu_include_paths_init( &cpp->include_paths ); + cpp->lang_depth = 1; + cpp->lang_stack[0] = MCPU_LANG_0; + cpp->include_depth = 0; + cpp->conditional_depth = 0; + cpp->base_filename = NULL; + cpp->preprocess_date[0] = 0; + cpp->preprocess_time[0] = 0; + cpp->builtins_initialized = 0; + cpp->command_line_macros_applied = 0; + mcpu_macro_table_init( &cpp->macros ); + cpp->once_files = NULL; + cpp->dependencies_first = NULL; + cpp->dependencies_last = NULL; + cpp->verbose = 0; + cpp->output_filename = NULL; + cpp->output_line = 0; + mcpu_text_init( &cpp->output ); +} + +void +mcpp_free( mcpp *cpp ) +{ + if( cpp == NULL ) + return; + + mcpu_config_free( &cpp->config ); + mcpp_runtime_paths_free( &cpp->runtime_paths ); + mcpu_include_paths_free( &cpp->include_paths ); + free( cpp->base_filename ); + cpp->base_filename = NULL; + mcpu_macro_table_free( &cpp->macros ); + once_files_free( cpp ); + dependencies_free( cpp ); + free( cpp->output_filename ); + cpp->output_filename = NULL; + cpp->output_line = 0; + mcpu_text_free( &cpp->output ); +} + +int +mcpp_load_config( mcpp *cpp, const char *explicit_filename, + int no_config ) +{ + const char *home; + + if( cpp == NULL ) + return( -1 ); + + /* + * The physical MCPU runtime root is not a configuration-file value. The + * sibling <root>/include directory is the lowest-priority default and + * therefore remains active even when --no-config suppresses all + * configuration-file reads. A subsequently read configuration file may + * replace it, including with an empty value that disables the configured + * system include tree. + */ + if( mcpu_config_set( &cpp->config, + "MCPU_CPP_SYSTEM_INCLUDE_PATH", + cpp->runtime_paths.system_include_path ) != 0 ) + return( -1 ); + + if( no_config ) + return( 0 ); + + if( explicit_filename ) + { + if( mcpu_config_read( &cpp->config, explicit_filename, 1 ) != 0 ) + { + fprintf( stderr, "%s: cannot read configuration: %s\n", + explicit_filename, strerror(errno) ); + return( -1 ); + } + } + else + { + char *user = NULL; + + if( cpp->runtime_paths.config_file == NULL || + mcpu_config_read( &cpp->config, + cpp->runtime_paths.config_file, 0 ) != 0 ) + return( -1 ); + + if( mcpu_config_read( &cpp->config, + MCPU_CPP_SYSTEM_CONFIG_FILE, 0 ) != 0 ) + return( -1 ); + + home = getenv( "HOME" ); + if( home && *home ) + { + size_t n = strlen(home) + sizeof("/.mcpu/mcpu-cpp.conf"); + user = (char *)malloc( n ); + if( user == NULL ) + return( -1 ); + snprintf( user, n, "%s/.mcpu/mcpu-cpp.conf", home ); + if( mcpu_config_read( &cpp->config, user, 0 ) != 0 ) + { + free( user ); + return( -1 ); + } + free( user ); + } + } + + return( 0 ); +} + + +int +mcpp_prepare( mcpp *cpp ) +{ + if( cpp == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( cpp->preprocess_date[0] == 0 && initialize_timestamp(cpp) != 0 ) + return( -1 ); + + if( initialize_builtins(cpp) != 0 ) + return( -1 ); + + return( apply_command_line_macros(cpp) ); +} + + +static void +restore_output_length( mcpp *cpp, size_t length ) +{ + if( cpp == NULL ) + return; + + cpp->output.length = length; + if( cpp->output.data != NULL ) + cpp->output.data[length] = 0; +} + + +static int +process_forced_file( mcpp *cpp, const mcpp_option_action *action, + int discard_output ) +{ + const mcpu_include_dir *found_dir = NULL; + char *found; + char *saved_output_filename = NULL; + size_t output_length; + unsigned saved_output_line = 0; + int system_header; + int rc = -1; + + if( cpp == NULL || action == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + /* + * GNU-style command-line forced files are resolved relative to the current + * working directory first. Passing "." as the synthetic containing source + * gives mcpu_include_find() exactly that quoted-include first step, followed + * by the ordinary configured include chain. The primary input directory is + * deliberately not involved. + */ + found = mcpu_include_find( &cpp->include_paths, ".", action->argument, 1, + current_language(cpp), 0, NULL, &found_dir ); + if( found == NULL ) + { + const char *progname = MCPP_OPTIONS(cpp) != NULL ? + MCPP_OPTIONS(cpp)->progname : "mcpu-cpp"; + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->print_deps_missing_files ) + { + /* + * GNU -MG also tolerates missing command-line -include/-imacros files. + * With no successful search there is no physical search provenance, so + * the command-line operand enters the unresolved registry as a user + * dependency exactly as written. + */ + if( dependency_add_unresolved(cpp, action->argument, 0) != 0 ) + return( -1 ); + + if( cpp->verbose ) + fprintf( stderr, "%s %s -> <generated> [%s]\n", + action->option, action->argument, + mcpu_language_name(current_language(cpp)) ); + return( 0 ); + } + + fprintf( stderr, "%s: error: %s cannot find file '%s'\n", + progname, action->option, action->argument ); + return( -1 ); + } + + system_header = include_dir_is_system( found_dir ); + if( dependency_add(cpp, found, system_header) != 0 ) + goto done; + + if( once_file_seen(cpp, found) ) + { + rc = 0; + goto done; + } + + output_length = cpp->output.length; + if( discard_output && cpp->output_filename != NULL ) + { + saved_output_filename = strdup( cpp->output_filename ); + if( saved_output_filename == NULL ) + goto done; + saved_output_line = cpp->output_line; + } + + if( cpp->verbose ) + fprintf( stderr, "%s %s -> %s [%s]\n", + action->option, action->argument, found, + mcpu_language_name(current_language(cpp)) ); + + if( append_line_marker(cpp, 1, found, 1) != 0 ) + goto forced_fail; + + /* + * A forced file is conceptually included by the primary source before its + * first line. Reserve the primary-source frame so __INCLUDE_LEVEL__ is 1 + * in the forced file and increases normally in headers included from it. + */ + if( cpp->include_depth >= MCPU_CPP_INCLUDE_STACK_SIZE ) + { + fprintf( stderr, "%s: error: include nesting exceeds %d files\n", + found, MCPU_CPP_INCLUDE_STACK_SIZE ); + goto forced_fail; + } + + ++cpp->include_depth; + rc = process_file( cpp, found, found_dir, system_header ); + --cpp->include_depth; + if( rc != 0 ) + goto forced_fail; + + if( discard_output ) + { + restore_output_length( cpp, output_length ); + if( saved_output_filename != NULL && + set_output_position(cpp, saved_output_line, saved_output_filename) != 0 ) + goto done; + } + else if( append_line_marker(cpp, 1, cpp->base_filename, 2) != 0 ) + { + rc = -1; + goto done; + } + + rc = 0; + goto done; + +forced_fail: + if( discard_output ) + { + restore_output_length( cpp, output_length ); + if( saved_output_filename != NULL ) + (void)set_output_position(cpp, saved_output_line, saved_output_filename); + } + rc = -1; + +done: + free( saved_output_filename ); + free( found ); + return( rc ); +} + + +static int +process_forced_files( mcpp *cpp ) +{ + size_t i; + int pass; + + if( cpp == NULL || MCPP_OPTIONS(cpp) == NULL ) + return( 0 ); + + /* + * Command-line -D/-U actions have already been applied by mcpp_prepare(). + * Process every -imacros file before every -include file while preserving + * command-line order inside each class. + */ + for( pass = 0; pass < 2; ++pass ) + { + enum mcpp_option_action_kind kind = + pass == 0 ? MCPP_ACTION_IMACROS : MCPP_ACTION_INCLUDE; + + for( i = 0; i < MCPP_OPTIONS(cpp)->action_count; ++i ) + { + const mcpp_option_action *action = &MCPP_OPTIONS(cpp)->actions[i]; + + if( action->kind != kind ) + continue; + + if( process_forced_file(cpp, action, kind == MCPP_ACTION_IMACROS) != 0 ) + return( -1 ); + } + } + + return( 0 ); +} + + +int +mcpp_dump_macros( mcpp *cpp, FILE *stream ) +{ + if( cpp == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( mcpp_prepare(cpp) != 0 ) + return( -1 ); + + return( mcpu_macro_dump(&cpp->macros, stream, + MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_only_plus_predefined) ); +} + + +int +mcpp_process( mcpp *cpp, const char *filename ) +{ + if( cpp == NULL || filename == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + free( cpp->base_filename ); + cpp->base_filename = strdup( filename ); + if( cpp->base_filename == NULL || mcpp_prepare(cpp) != 0 ) + return( -1 ); + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_macros == MCPP_DUMP_DEFINITIONS ) + { + const char *marker = "<built-in>"; + + if( append_line_marker(cpp, 0, filename, 0) != 0 || + mcpu_macro_dump_text(&cpp->macros, &cpp->output, marker) != 0 ) + return( -1 ); + } + + if( dependency_add(cpp, filename, 0) != 0 ) + return( -1 ); + + if( append_line_marker(cpp, 1, filename, 0) != 0 ) + return( -1 ); + + if( process_forced_files(cpp) != 0 ) + return( -1 ); + + if( process_file( cpp, filename, NULL, 0 ) != 0 ) + return( -1 ); + + if( cpp->lang_depth != 1 ) + { + fprintf( stderr, + "%s: error: unbalanced #lang/#endlang at end of translation unit (depth %lu)\n", + filename, (unsigned long)cpp->lang_depth ); + return( -1 ); + } + + return( 0 ); +} + + +int +mcpp_process_stream( mcpp *cpp, FILE *stream, const char *name ) +{ + if( cpp == NULL || stream == NULL || name == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + free( cpp->base_filename ); + cpp->base_filename = strdup( name ); + if( cpp->base_filename == NULL || mcpp_prepare(cpp) != 0 ) + return( -1 ); + + if( MCPP_OPTIONS(cpp) != NULL && + MCPP_OPTIONS(cpp)->dump_macros == MCPP_DUMP_DEFINITIONS ) + { + const char *marker = "<built-in>"; + + if( append_line_marker(cpp, 0, name, 0) != 0 || + mcpu_macro_dump_text(&cpp->macros, &cpp->output, marker) != 0 ) + return( -1 ); + } + + if( dependency_add(cpp, strcmp(name, "<stdin>") == 0 ? "-" : name, 0) != 0 ) + return( -1 ); + + if( append_line_marker(cpp, 1, name, 0) != 0 ) + return( -1 ); + + if( process_forced_files(cpp) != 0 ) + return( -1 ); + + if( process_stream(cpp, stream, name) != 0 ) + return( -1 ); + + if( cpp->lang_depth != 1 ) + { + fprintf( stderr, + "%s: error: unbalanced #lang/#endlang at end of translation unit (depth %lu)\n", + name, (unsigned long)cpp->lang_depth ); + return( -1 ); + } + + return( 0 ); +} + +static int +write_make_escaped( FILE *stream, const char *text ) +{ + const unsigned char *p = (const unsigned char *)text; + + while( *p ) + { + if( *p == '$' ) + { + if( fputs("$$", stream) == EOF ) + return( -1 ); + } + else + { + if( (*p == ' ' || *p == '\t' || *p == '#' || *p == '\\') && + fputc('\\', stream) == EOF ) + return( -1 ); + if( fputc(*p, stream) == EOF ) + return( -1 ); + } + ++p; + } + + return( 0 ); +} + + +static int +write_make_target_quoted( FILE *stream, const char *text ) +{ + const unsigned char *p = (const unsigned char *)text; + size_t slashes = 0; + + while( *p ) + { + unsigned char c = *p++; + + if( c == '\\' ) + { + if( fputc(c, stream) == EOF ) + return( -1 ); + ++slashes; + continue; + } + + if( c == ' ' || c == '\t' ) + { + size_t i; + + for( i = 0; i < slashes; ++i ) + if( fputc('\\', stream) == EOF ) + return( -1 ); + if( fputc('\\', stream) == EOF ) + return( -1 ); + } + else if( c == '#' ) + { + if( fputc('\\', stream) == EOF ) + return( -1 ); + } + else if( c == '$' ) + { + if( fputc('$', stream) == EOF ) + return( -1 ); + } + + slashes = 0; + if( fputc(c, stream) == EOF ) + return( -1 ); + } + + return( 0 ); +} + + +static char * +default_dependency_target( const mcpp *cpp ) +{ + const char *base; + const char *dot; + const char *suffix = ".o"; + size_t stem_length; + size_t suffix_length; + char *target; + + if( cpp == NULL || cpp->base_filename == NULL ) + return( NULL ); + + if( strcmp(cpp->base_filename, "<stdin>") == 0 || + strcmp(cpp->base_filename, "-") == 0 ) + return( strdup("-") ); + + base = strrchr( cpp->base_filename, '/' ); + base = base == NULL ? cpp->base_filename : base + 1; + dot = strrchr( base, '.' ); + stem_length = dot != NULL && dot != base ? (size_t)(dot - base) : strlen(base); + + if( MCPP_OPTIONS(cpp) != NULL && MCPP_OPTIONS(cpp)->object_suffix != NULL ) + suffix = MCPP_OPTIONS(cpp)->object_suffix; + suffix_length = strlen( suffix ); + + target = (char *)malloc( stem_length + suffix_length + 1 ); + if( target == NULL ) + return( NULL ); + + memcpy( target, base, stem_length ); + memcpy( target + stem_length, suffix, suffix_length + 1 ); + return( target ); +} + + +static int +write_dependency_targets( mcpp *cpp, FILE *stream ) +{ + char *target; + size_t i; + int pass; + int written = 0; + + if( cpp == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( MCPP_OPTIONS(cpp) != NULL ) + for( pass = 0; pass < 2; ++pass ) + { + enum mcpp_option_action_kind kind = pass == 0 ? + MCPP_ACTION_DEP_TARGET : MCPP_ACTION_DEP_TARGET_QUOTED; + + for( i = 0; i < MCPP_OPTIONS(cpp)->action_count; ++i ) + { + const mcpp_option_action *action = &MCPP_OPTIONS(cpp)->actions[i]; + + if( action->kind != kind ) + continue; + + if( written && fputc(' ', stream) == EOF ) + return( -1 ); + + if( kind == MCPP_ACTION_DEP_TARGET ) + { + if( fputs(action->argument, stream) == EOF ) + return( -1 ); + } + else if( write_make_target_quoted(stream, action->argument) != 0 ) + return( -1 ); + + written = 1; + } + } + + if( written ) + return( 0 ); + + target = default_dependency_target( cpp ); + if( target == NULL ) + return( -1 ); + + if( write_make_target_quoted(stream, target) != 0 ) + { + free( target ); + return( -1 ); + } + + free( target ); + return( 0 ); +} + + +int +mcpp_write_dependencies( mcpp *cpp, FILE *stream, int include_system ) +{ + mcpp_dependency *p; + + if( cpp == NULL || stream == NULL ) + { + errno = EINVAL; + return( -1 ); + } + + if( write_dependency_targets(cpp, stream) != 0 || fputs(":", stream) == EOF ) + return( -1 ); + + for( p = cpp->dependencies_first; p != NULL; p = p->next ) + { + if( !include_system && p->system_only ) + continue; + + if( fputc(' ', stream) == EOF || write_make_escaped(stream, p->path) != 0 ) + return( -1 ); + } + + if( fputc('\n', stream) == EOF ) + return( -1 ); + + return( ferror(stream) ? -1 : 0 ); +} + + +int +mcpp_write_output( mcpp *cpp, const char *filename ) +{ + char *utf8; + FILE *fp = stdout; + int close_fp = 0; + size_t n; + + if( cpp == NULL ) + return( -1 ); + + utf8 = mcpu_text_to_utf8( cpp->output.data, cpp->output.length ); + if( utf8 == NULL ) + return( -1 ); + + if( filename ) + { + fp = fopen( filename, "wb" ); + if( fp == NULL ) + { + free( utf8 ); + return( -1 ); + } + close_fp = 1; + } + + n = strlen( utf8 ); + if( fwrite( utf8, 1, n, fp ) != n ) + { + if( close_fp ) + fclose( fp ); + free( utf8 ); + return( -1 ); + } + + if( close_fp && fclose(fp) != 0 ) + { + free( utf8 ); + return( -1 ); + } + + free( utf8 ); + return( 0 ); +} 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 = ¯o->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__ */ |
