diff --git a/resources/grammar/c3.l b/resources/grammar/c3.l index fea223517..4add6a072 100644 --- a/resources/grammar/c3.l +++ b/resources/grammar/c3.l @@ -54,6 +54,7 @@ int comment_level = 0; "$eval" { count(); return(CT_EVAL); } "$evaltype" { count(); return(CT_EVALTYPE); } "$extnameof" { count(); return(CT_EXTNAMEOF); } +"$feature" { count(); return(CT_FEATURE); } "$for" { count(); return(CT_FOR); } "$foreach" { count(); return(CT_FOREACH); } "$if" { count(); return(CT_IF); } diff --git a/resources/grammar/grammar.y b/resources/grammar/grammar.y index 7ac84bc2e..f085c9a36 100644 --- a/resources/grammar/grammar.y +++ b/resources/grammar/grammar.y @@ -30,7 +30,7 @@ void yyerror(char *s); %token CT_ENDFOR CT_ENDSWITCH BUILTIN IMPLIES INITIALIZE FINALIZE CT_ECHO CT_ASSERT CT_EVALTYPE CT_VATYPE %token TRY CATCH SCOPE DEFER LVEC RVEC OPTELSE CT_TYPEFROM CT_TYPEOF TLOCAL %token CT_VASPLAT INLINE DISTINCT CT_VACONST CT_NAMEOF CT_VAREF CT_VACOUNT CT_VAARG -%token CT_SIZEOF CT_STRINGIFY CT_QNAMEOF CT_OFFSETOF CT_VAEXPR +%token CT_SIZEOF CT_STRINGIFY CT_QNAMEOF CT_OFFSETOF CT_VAEXPR CT_FEATURE %token CT_EXTNAMEOF CT_EVAL CT_DEFINED CT_CHECKS CT_ALIGNOF ASSERT %token ASM CHAR_LITERAL REAL TRUE FALSE CT_CONST_IDENT %token LBRAPIPE RBRAPIPE HASH_CONST_IDENT @@ -139,6 +139,7 @@ base_expr | ct_arg '(' expr ')' | ct_analyse '(' expr ')' | CT_VACOUNT + | CT_FEATURE '(' CONST_IDENT ')' | CT_CHECKS '(' expression_list ')' | lambda_decl compound_statement ; diff --git a/src/build/build_options.c b/src/build/build_options.c index 6f91eb027..1e3c73b0c 100644 --- a/src/build/build_options.c +++ b/src/build/build_options.c @@ -108,6 +108,8 @@ static void usage(void) OUTPUT(" -O3+ - Aggressive optimization, single module."); OUTPUT(" -Os+ - Optimize for size, single module."); OUTPUT(" -Oz+ - Optimize for tiny size, single module."); + OUTPUT(" -D - Add feature flag ."); + OUTPUT(" -U - Remove feature flag ."); OUTPUT(" --build-dir - Override build output directory."); OUTPUT(" --obj-out - Override object file output directory."); OUTPUT(" --llvm-out - Override llvm output directory for '--emit-llvm'."); @@ -203,9 +205,9 @@ static inline bool next_is_opt() return args[arg_index + 1][0] == '-'; } -static inline bool match_longopt(const char* name) +INLINE bool match_longopt(const char* name) { - return strcmp(¤t_arg[2], name) == 0; + return str_eq(¤t_arg[2], name); } static inline const char *match_argopt(const char* name) @@ -385,6 +387,29 @@ static void add_linker_arg(BuildOptions *options, const char *arg) options->linker_args[options->linker_arg_count++] = arg; } +void update_feature_flags(const char ***flags, const char ***removed_flags, const char *arg, bool add) +{ + const char **to_remove_from = add ? *removed_flags : *flags; + unsigned len = vec_size(to_remove_from); + // Remove if it's in there. + for (unsigned i = 0; i < len; i++) + { + if (str_eq(to_remove_from[i], arg)) + { + vec_erase_ptr_at(to_remove_from, i); + break; + } + } + const char ***to_add_to_ref = add ? flags : removed_flags; + unsigned add_len = vec_size(*to_add_to_ref); + for (unsigned i = 0; i < add_len; i++) + { + if (str_eq((*to_add_to_ref)[i], arg)) return; + } + vec_add(*to_add_to_ref, arg); +} + + static int parse_multi_option(const char *start, unsigned count, const char** elements) { const char *arg = current_arg; @@ -435,18 +460,42 @@ static void parse_option(BuildOptions *options) case 'h': break; case 'z': - if (at_end()) error_exit("error: -z needs a value"); - add_linker_arg(options, next_arg()); - return; - case 'o': - if (at_end()) error_exit("error: -o needs a name"); - options->output_name = next_arg(); - return; - case 'O': - if (options->optimization_setting_override != OPT_SETTING_NOT_SET) + if (match_shortopt("U")) { - FAIL_WITH_ERR("Multiple optimization levels were set."); + if (at_end()) error_exit("error: -z needs a value."); + add_linker_arg(options, next_arg()); + return; } + break; + case 'o': + if (match_shortopt("o")) + { + if (at_end()) error_exit("error: -o needs a name."); + options->output_name = next_arg(); + return; + } + break; + case 'D': + if (match_shortopt("D")) + { + if (at_end()) error_exit("error: -D needs a feature name."); + const char *arg = next_arg(); + if (!str_is_valid_constant(arg)) error_exit("Invalid feature name '%s', expected an all-uppercase constant name.", arg); + update_feature_flags(&options->feature_names, &options->removed_feature_names, arg, true); + return; + } + break; + case 'U': + if (match_shortopt("U")) + { + if (at_end()) error_exit("error: -U needs a feature name."); + const char *arg = next_arg(); + if (!str_is_valid_constant(arg)) error_exit("Invalid feature name '%s', expected an all-uppercase constant name.", arg); + update_feature_flags(&options->feature_names, &options->removed_feature_names, arg, false); + return; + } + break; + case 'O': if (match_shortopt("O0+")) { options->optimization_setting_override = OPT_SETTING_O0_PLUS; @@ -501,34 +550,54 @@ static void parse_option(BuildOptions *options) } return; case 'E': - if (options->compile_option != COMPILE_NORMAL) + if (match_shortopt("E")) { - FAIL_WITH_ERR("Illegal combination of compile options."); + if (options->compile_option != COMPILE_NORMAL) + { + FAIL_WITH_ERR("Illegal combination of compile options."); + } + options->compile_option = COMPILE_LEX_ONLY; + return; } - options->compile_option = COMPILE_LEX_ONLY; - return; + break; case 'L': - if (at_end() || next_is_opt()) error_exit("error: -L needs a directory."); - options->linker_lib_dir[options->linker_lib_dir_count++] = check_dir(next_arg()); - return; + if (match_shortopt("L")) + { + if (at_end() || next_is_opt()) error_exit("error: -L needs a directory."); + options->linker_lib_dir[options->linker_lib_dir_count++] = check_dir(next_arg()); + return; + } + break; case 'l': - if (at_end() || next_is_opt()) error_exit("error: -l needs a library name."); - options->linker_libs[options->linker_lib_count++] = next_arg(); - return; + if (match_shortopt("l")) + { + if (at_end() || next_is_opt()) error_exit("error: -l needs a library name."); + options->linker_libs[options->linker_lib_count++] = next_arg(); + return; + } + break; case 'P': - if (options->compile_option != COMPILE_NORMAL) + if (match_shortopt("P")) { - FAIL_WITH_ERR("Illegal combination of compile options."); + if (options->compile_option != COMPILE_NORMAL) + { + FAIL_WITH_ERR("Illegal combination of compile options."); + } + options->compile_option = COMPILE_LEX_PARSE_ONLY; + return; } - options->compile_option = COMPILE_LEX_PARSE_ONLY; - return; + break; case 'C': - if (options->compile_option != COMPILE_NORMAL) + if (match_shortopt("C")) { - FAIL_WITH_ERR("Illegal combination of compile options."); + if (options->compile_option != COMPILE_NORMAL) + { + FAIL_WITH_ERR("Illegal combination of compile options."); + } + options->compile_option = COMPILE_LEX_PARSE_CHECK_ONLY; + return; } - options->compile_option = COMPILE_LEX_PARSE_CHECK_ONLY; - return; + break; case '-': if (match_longopt("tb")) { diff --git a/src/build/build_options.h b/src/build/build_options.h index 0efc48a9a..94a0d412b 100644 --- a/src/build/build_options.h +++ b/src/build/build_options.h @@ -12,6 +12,7 @@ #define MAX_INCLUDES 2048 #define MAX_THREADS 0xFFFF +void update_feature_flags(const char ***flags, const char ***removed_flag, const char *arg, bool add); typedef enum { @@ -303,12 +304,14 @@ typedef struct BuildOptions_ const char *crtbegin; } linuxpaths; int build_threads; - const char** libraries_to_fetch; - const char** files; - const char* output_name; - const char* project_name; - const char* target_select; - const char* path; + const char **libraries_to_fetch; + const char **files; + const char **feature_names; + const char **removed_feature_names; + const char *output_name; + const char *project_name; + const char *target_select; + const char *path; const char *template; uint32_t symtab_size; unsigned version; @@ -441,6 +444,7 @@ typedef struct const char *cflags; const char **csource_dirs; const char **csources; + const char **feature_list; struct { SoftFloat soft_float : 3; diff --git a/src/build/builder.c b/src/build/builder.c index ee49f36eb..7c8fbd38c 100644 --- a/src/build/builder.c +++ b/src/build/builder.c @@ -192,6 +192,27 @@ static void update_build_target_from_options(BuildTarget *target, BuildOptions * target->backend = options->backend; + // Remove feature flags + FOREACH_BEGIN(const char *remove_feature, options->removed_feature_names) + FOREACH_BEGIN_IDX(i, const char *feature, target->feature_list) + if (str_eq(feature, remove_feature)) + { + vec_erase_ptr_at(target->feature_list, i); + break; + } + FOREACH_END(); + FOREACH_END(); + + // Add feature flags + FOREACH_BEGIN(const char *add_feature, options->feature_names) + FOREACH_BEGIN_IDX(i, const char *feature, target->feature_list) + if (str_eq(feature, add_feature)) goto NEXT; + FOREACH_END(); + vec_add(target->feature_list, add_feature); + NEXT:; + FOREACH_END(); + + update_build_target_with_opt_level(target, options->optimization_setting_override); if (options->cc) target->cc = options->cc; diff --git a/src/compiler/compiler.c b/src/compiler/compiler.c index 4afb1b679..af693addb 100644 --- a/src/compiler/compiler.c +++ b/src/compiler/compiler.c @@ -45,6 +45,7 @@ void compiler_init(const char *std_lib_dir) decltable_init(&global_context.symbols, INITIAL_SYMBOL_MAP); decltable_init(&global_context.generic_symbols, INITIAL_GENERIC_SYMBOL_MAP); + htable_init(&global_context.features, 1024); htable_init(&global_context.compiler_defines, 16 * 1024); global_context.module_list = NULL; global_context.generic_module_list = NULL; @@ -820,6 +821,12 @@ void compile() target_setup(&active_target); resolve_libraries(); + TokenType type = TOKEN_CONST_IDENT; + FOREACH_BEGIN(const char *feature_flag, active_target.feature_list) + feature_flag = symtab_preset(feature_flag, TOKEN_CONST_IDENT); + htable_set(&global_context.features, (void *)feature_flag, (void *)feature_flag); + FOREACH_END(); + setup_int_define("C_SHORT_SIZE", platform_target.width_c_short, type_long); setup_int_define("C_INT_SIZE", platform_target.width_c_int, type_long); setup_int_define("C_LONG_SIZE", platform_target.width_c_long, type_long); diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index fe7cfce72..fa2d0d125 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -1703,6 +1703,7 @@ typedef struct bool suppress_errors; Decl ***locals_list; HTable compiler_defines; + HTable features; Module std_module; DeclTable symbols; DeclTable generic_symbols; diff --git a/src/compiler/enums.h b/src/compiler/enums.h index f48d3652c..746bce913 100644 --- a/src/compiler/enums.h +++ b/src/compiler/enums.h @@ -568,6 +568,7 @@ typedef enum TOKEN_CT_EVALTYPE, // $evaltype TOKEN_CT_ERROR, // $error TOKEN_CT_EXTNAMEOF, // $extnameof + TOKEN_CT_FEATURE, // $feature TOKEN_CT_FOR, // $for TOKEN_CT_FOREACH, // $foreach TOKEN_CT_IF, // $if diff --git a/src/compiler/parse_expr.c b/src/compiler/parse_expr.c index 8a72b2997..7d3f10912 100644 --- a/src/compiler/parse_expr.c +++ b/src/compiler/parse_expr.c @@ -1017,6 +1017,7 @@ static Expr *parse_hash_ident(ParseContext *c, Expr *left) return expr; } + /** * ct_eval ::= CT_EVAL '(' expr ')' */ @@ -1104,7 +1105,7 @@ static Expr *parse_ct_embed(ParseContext *c, Expr *left) } /** - * ct_call ::= (ALIGNOF | EXTNAMEOF | OFFSETOF | NAMEOF | QNAMEOF) '(' flat_path ')' + * ct_call ::= (CT_ALIGNOF | CT_FEATURE | CT_EXTNAMEOF | CT_OFFSETOF | CT_NAMEOF | CT_QNAMEOF) '(' flat_path ')' * flat_path ::= expr ('.' primary) | '[' expr ']')* */ static Expr *parse_ct_call(ParseContext *c, Expr *left) @@ -1909,6 +1910,7 @@ ParseRule rules[TOKEN_EOF + 1] = { [TOKEN_CT_CHECKS] = { parse_ct_checks, NULL, PREC_NONE }, [TOKEN_CT_EMBED] = { parse_ct_embed, NULL, PREC_NONE }, [TOKEN_CT_EVAL] = { parse_ct_eval, NULL, PREC_NONE }, + [TOKEN_CT_FEATURE] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_EXTNAMEOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_OFFSETOF] = { parse_ct_call, NULL, PREC_NONE }, [TOKEN_CT_NAMEOF] = { parse_ct_call, NULL, PREC_NONE }, diff --git a/src/compiler/parse_stmt.c b/src/compiler/parse_stmt.c index 6343e99ce..28d3f0190 100644 --- a/src/compiler/parse_stmt.c +++ b/src/compiler/parse_stmt.c @@ -1208,50 +1208,51 @@ Ast *parse_stmt(ParseContext *c) return parse_ct_foreach_stmt(c); case TOKEN_CT_FOR: return parse_ct_for_stmt(c); - case TOKEN_STAR: case TOKEN_AMP: - case TOKEN_INTEGER: - case TOKEN_CHAR_LITERAL: + case TOKEN_AT: + case TOKEN_AT_CONST_IDENT: + case TOKEN_AT_IDENT: + case TOKEN_AT_TYPE_IDENT: + case TOKEN_BANG: case TOKEN_BIT_NOT: case TOKEN_BIT_OR: case TOKEN_BIT_XOR: + case TOKEN_BUILTIN: + case TOKEN_BYTES: + case TOKEN_CHAR_LITERAL: + case TOKEN_CT_ALIGNOF: + case TOKEN_CT_CHECKS: + case TOKEN_CT_CONST_IDENT: + case TOKEN_CT_DEFINED: + case TOKEN_CT_EMBED: + case TOKEN_CT_EVAL: + case TOKEN_CT_EXTNAMEOF: + case TOKEN_CT_FEATURE: + case TOKEN_CT_IDENT: + case TOKEN_CT_NAMEOF: + case TOKEN_CT_OFFSETOF: + case TOKEN_CT_QNAMEOF: + case TOKEN_CT_SIZEOF: + case TOKEN_CT_STRINGIFY: + case TOKEN_CT_VAARG: + case TOKEN_CT_VACONST: + case TOKEN_CT_VACOUNT: + case TOKEN_CT_VAEXPR: + case TOKEN_CT_VAREF: + case TOKEN_FALSE: + case TOKEN_INTEGER: + case TOKEN_LBRAPIPE: case TOKEN_LPAREN: case TOKEN_MINUS: - case TOKEN_BANG: + case TOKEN_MINUSMINUS: + case TOKEN_NULL: case TOKEN_OR: case TOKEN_PLUS: - case TOKEN_MINUSMINUS: case TOKEN_PLUSPLUS: - case TOKEN_CT_CONST_IDENT: - case TOKEN_CT_IDENT: - case TOKEN_STRING: case TOKEN_REAL: - case TOKEN_FALSE: - case TOKEN_NULL: + case TOKEN_STAR: + case TOKEN_STRING: case TOKEN_TRUE: - case TOKEN_LBRAPIPE: - case TOKEN_CT_OFFSETOF: - case TOKEN_CT_ALIGNOF: - case TOKEN_CT_EXTNAMEOF: - case TOKEN_CT_SIZEOF: - case TOKEN_CT_QNAMEOF: - case TOKEN_CT_NAMEOF: - case TOKEN_CT_DEFINED: - case TOKEN_CT_CHECKS: - case TOKEN_CT_STRINGIFY: - case TOKEN_CT_EVAL: - case TOKEN_BYTES: - case TOKEN_BUILTIN: - case TOKEN_CT_VACOUNT: - case TOKEN_CT_VAARG: - case TOKEN_CT_VAEXPR: - case TOKEN_CT_VACONST: - case TOKEN_CT_VAREF: - case TOKEN_AT_TYPE_IDENT: - case TOKEN_AT_CONST_IDENT: - case TOKEN_AT: - case TOKEN_AT_IDENT: - case TOKEN_CT_EMBED: return parse_expr_stmt(c); case TOKEN_ASSERT: return parse_assert_stmt(c); diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index bdf665e79..19e960dc1 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -7224,6 +7224,25 @@ FAIL_NO_INFER: return false; } +static inline bool sema_expr_analyse_ct_feature(SemaContext *context, Expr *expr) +{ + if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr); + + Expr *inner = expr->ct_call_expr.main_var; + if (expr->ct_call_expr.flat_path) goto ERROR; + if (inner->expr_kind != EXPR_IDENTIFIER) goto ERROR; + if (inner->resolve_status != RESOLVE_NOT_DONE) goto ERROR; + if (!inner->identifier_expr.is_const) goto ERROR; + + const char *name = inner->identifier_expr.ident; + void *value = htable_get(&global_context.features, (void *)name); + assert(!value || value == name); + expr_rewrite_const_bool(expr, type_bool, value != NULL); + return true; +ERROR: + RETURN_SEMA_ERROR(inner, "Expected a feature name here, e.g. $feature(MY_FEATURE)."); +} + static inline bool sema_expr_analyse_ct_defined(SemaContext *context, Expr *expr) { if (expr->resolve_status == RESOLVE_DONE) return expr_ok(expr); @@ -7491,6 +7510,8 @@ static inline bool sema_expr_analyse_ct_call(SemaContext *context, Expr *expr) case TOKEN_CT_NAMEOF: case TOKEN_CT_EXTNAMEOF: return sema_expr_analyse_ct_nameof(context, expr); + case TOKEN_CT_FEATURE: + return sema_expr_analyse_ct_feature(context, expr); default: UNREACHABLE } diff --git a/src/compiler/tokens.c b/src/compiler/tokens.c index d4e6cfc88..4cb3f37b9 100644 --- a/src/compiler/tokens.c +++ b/src/compiler/tokens.c @@ -342,6 +342,8 @@ const char *token_type_to_string(TokenType type) return "$evaltype"; case TOKEN_CT_ERROR: return "$error"; + case TOKEN_CT_FEATURE: + return "$feature"; case TOKEN_CT_FOR: return "$for"; case TOKEN_CT_FOREACH: diff --git a/src/utils/lib.h b/src/utils/lib.h index 2f7136784..dcc707c99 100644 --- a/src/utils/lib.h +++ b/src/utils/lib.h @@ -132,6 +132,11 @@ char *str_vprintf(const char *var, va_list list); void str_ellide_in_place(char *string, size_t max_size_shown); bool str_is_valid_lowercase_name(const char *string); bool str_is_valid_constant(const char *string); +const char *str_unescape(char *string); +bool str_is_identifier(const char *string); +bool str_eq(const char *str1, const char *str2); +bool str_is_type(const char *string); +bool str_is_integer(const char *string); bool str_has_no_uppercase(const char *string); char *str_copy(const char *start, size_t str_len); @@ -601,6 +606,19 @@ static inline bool char_is_lower_alphanum_(char c) } } +static inline bool char_is_upper_alphanum_(char c) +{ + switch (c) + { + case UPPER_CHAR_CASE: + case NUMBER_CHAR_CASE: + case '_': + return true; + default: + return false; + } +} + static inline bool char_is_letter(char c) { switch (c) diff --git a/src/utils/stringutils.c b/src/utils/stringutils.c index 0fe14687a..95fb5004e 100644 --- a/src/utils/stringutils.c +++ b/src/utils/stringutils.c @@ -43,16 +43,91 @@ bool str_is_valid_lowercase_name(const char *string) return true; } +static const char *scan_past_underscore(const char *string) +{ + while (string[0] == '_') string++; + if (string[0] == '\0') return NULL; + return string; +} + +const char *str_unescape(char *string) +{ + assert(string[0] == '"'); + char c; + size_t index = 0; + while ((c = string++[0]) != '"') + { + if (c == 0) return NULL; + if (c == '\\') + { + c = string++[0]; + } + string[index++] = c; + } + string[index] = '\0'; + return string; +} + +bool str_is_type(const char *string) +{ + string = scan_past_underscore(string); + if (!string) return false; + char c = string++[0]; + if (!char_is_upper(c)) return false; + bool found_lower = false; + while ((c = *(string++)) != '\0') + { + if (char_is_lower(c)) + { + found_lower = true; + continue; + } + if (!char_is_alphanum_(c)) return false; + } + return found_lower; +} + +bool str_is_identifier(const char *string) +{ + string = scan_past_underscore(string); + if (!string) return false; + char c = string++[0]; + if (!char_is_lower(c)) return false; + while ((c = *(string++)) != '\0') + { + if (!char_is_alphanum_(c)) return false; + } + return true; +} + +bool str_eq(const char *str1, const char *str2) +{ + return str1 == str2 || strcmp(str1, str2) == 0; +} + +bool str_is_integer(const char *string) +{ + if (string[0] == '-') string++; + if (!string[0]) return false; + char c; + // Must start with a lower case + while ((c = *(string++)) != '\0') + { + if (!char_is_digit(c)) return false; + } + return true; +} + bool str_is_valid_constant(const char *string) { - char c; - // Must start with a lower case - int length = 0; - while ((c = *(string++)) != '\0') - { - if (!char_is_upper(c) && c != '_') return false; - } - return true; + string = scan_past_underscore(string); + if (!string) return false; + char c = string++[0]; + if (!char_is_upper(c)) return false; + while ((c = *(string++)) != '\0') { + if (!char_is_upper_alphanum_(c)) return false; + } + return true; } void str_ellide_in_place(char *string, size_t max_size_shown) diff --git a/src/version.h b/src/version.h index 35e0cac25..5b8748452 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define COMPILER_VERSION "0.4.582" \ No newline at end of file +#define COMPILER_VERSION "0.4.583" \ No newline at end of file