mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 12:01:16 +00:00
Add compile time ternary $val ??? <expr> : <expr>.
This commit is contained in:
@@ -546,17 +546,10 @@ macro bool @is_valid_list(#expr) @const
|
||||
macro bool @is_valid_fill(left, right, fill_with)
|
||||
{
|
||||
if (@is_empty_macro_slot(fill_with)) return true;
|
||||
usz left_len = @select($defined(left.len()), left.len(), left.len);
|
||||
usz right_len = @select($defined(right.len()), right.len(), right.len);
|
||||
usz left_len = $defined(left.len()) ??? left.len() : left.len;
|
||||
usz right_len = $defined(right.len()) ??? right.len() : right.len;
|
||||
if (left_len == right_len) return true;
|
||||
return left_len > right_len ? $defined(($typeof(right[0]))fill_with) : $defined(($typeof(left[0]))fill_with);
|
||||
}
|
||||
|
||||
macro usz find_len(list)
|
||||
{
|
||||
$if $defined(list.len()):
|
||||
return list.len();
|
||||
$else
|
||||
return list.len;
|
||||
$endif
|
||||
}
|
||||
macro usz find_len(list) => $defined(list.len()) ??? list.len() : list.len;
|
||||
@@ -204,10 +204,10 @@ fn void StderrLogger.log(&self, LogPriority priority, LogCategory category, LogT
|
||||
}
|
||||
|
||||
alias LogFn = fn void(void*, LogPriority priority, LogCategory category, LogTag tag, String file, String function, int line, String fmt, any[] args);
|
||||
LogFn current_logfn = @select(env::LIBC, (LogFn)&StderrLogger.log, (LogFn)&NullLogger.log);
|
||||
LogFn current_logfn = env::LIBC ??? (LogFn)&StderrLogger.log : (LogFn)&NullLogger.log;
|
||||
OnceFlag log_init;
|
||||
Mutex logger_mutex;
|
||||
Logger current_logger = @select(env::LIBC, &stderr_logger, &null_logger);
|
||||
Logger current_logger = env::LIBC ??? &stderr_logger : &null_logger;
|
||||
StderrLogger stderr_logger @if (env::LIBC);
|
||||
NullLogger null_logger;
|
||||
LogPriority[256] config_priorities = { [0..255] = ERROR, [CATEGORY_APPLICATION] = INFO, [CATEGORY_TEST] = VERBOSE, [CATEGORY_ASSERT] = WARN};
|
||||
|
||||
@@ -43,7 +43,7 @@ macro promote_int(x)
|
||||
@param #value_2
|
||||
@returns `The selected value.`
|
||||
*>
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin
|
||||
macro @select(bool $bool, #value_1, #value_2) @builtin @deprecated("Use '$bool ? #value_1 : #value_2' instead.")
|
||||
{
|
||||
$if $bool:
|
||||
return #value_1;
|
||||
|
||||
@@ -193,9 +193,9 @@ fn usz? Socket.peek(&self, char[] bytes) @dynamic
|
||||
|
||||
enum SocketShutdownHow : (CInt native_value)
|
||||
{
|
||||
RECEIVE = @select(env::WIN32, libc::SD_RECEIVE, libc::SHUT_RD),
|
||||
SEND = @select(env::WIN32, libc::SD_SEND, libc::SHUT_WR),
|
||||
BOTH = @select(env::WIN32, libc::SD_BOTH, libc::SHUT_RDWR),
|
||||
RECEIVE = env::WIN32 ??? libc::SD_RECEIVE : libc::SHUT_RD,
|
||||
SEND = env::WIN32 ??? libc::SD_SEND : libc::SHUT_WR,
|
||||
BOTH = env::WIN32 ??? libc::SD_BOTH : libc::SHUT_RDWR,
|
||||
}
|
||||
|
||||
fn void? Socket.shutdown(&self, SocketShutdownHow how)
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
- Fix correct `?` after optional function name when reporting type errors.
|
||||
- Make `log` and `exp` no-strip.
|
||||
- `@test`/`@benchmark` on module would attach to interface and regular methods.
|
||||
- Add compile time ternary `$val ??? <expr> : <expr>`.
|
||||
- Deprecated `@select` in favor of `???`.
|
||||
|
||||
### Stdlib changes
|
||||
- Add `==` to `Pair`, `Triple` and TzDateTime. Add print to `Pair` and `Triple`.
|
||||
|
||||
@@ -781,6 +781,7 @@ typedef struct
|
||||
ExprId then_expr; // May be null for elvis!
|
||||
ExprId else_expr;
|
||||
bool grouped : 1;
|
||||
bool is_const : 1;
|
||||
} ExprTernary;
|
||||
|
||||
typedef struct
|
||||
|
||||
@@ -1145,6 +1145,7 @@ typedef enum
|
||||
TOKEN_CT_AND, // &&&
|
||||
TOKEN_CT_CONCAT, // +++
|
||||
TOKEN_CT_OR, // |||
|
||||
TOKEN_CT_TERNARY, // ???
|
||||
// Literals.
|
||||
TOKEN_IDENT, // Any normal ident.
|
||||
TOKEN_CONST_IDENT, // Any purely uppercase ident,
|
||||
|
||||
@@ -448,7 +448,7 @@ bool expr_is_runtime_const(Expr *expr)
|
||||
}
|
||||
goto RETRY;
|
||||
case EXPR_TERNARY:
|
||||
ASSERT(!exprid_is_runtime_const(expr->ternary_expr.cond));
|
||||
ASSERT(!exprid_is_runtime_const(expr->ternary_expr.cond) && !expr->ternary_expr.is_const);
|
||||
return false;
|
||||
case EXPR_FORCE_UNWRAP:
|
||||
case EXPR_LAST_FAULT:
|
||||
|
||||
@@ -1314,7 +1314,7 @@ static bool lexer_scan_token_inner(Lexer *lexer)
|
||||
case '^':
|
||||
return match(lexer, '=') ? new_token(lexer, TOKEN_BIT_XOR_ASSIGN, "^=") : new_token(lexer, TOKEN_BIT_XOR, "^");
|
||||
case '?':
|
||||
if (match(lexer, '?')) return new_token(lexer, TOKEN_QUESTQUEST, "??");
|
||||
if (match(lexer, '?')) return match(lexer, '?') ? new_token(lexer, TOKEN_CT_TERNARY, "???") : new_token(lexer, TOKEN_QUESTQUEST, "??");
|
||||
return match(lexer, ':') ? new_token(lexer, TOKEN_ELVIS, "?:") : new_token(lexer, TOKEN_QUESTION, "?");
|
||||
case '<':
|
||||
if (match(lexer, '<'))
|
||||
|
||||
@@ -761,12 +761,17 @@ static Expr *parse_ternary_expr(ParseContext *c, Expr *left_side, SourceSpan lhs
|
||||
ASSERT(expr_ok(left_side));
|
||||
|
||||
Expr *expr = expr_new(EXPR_TERNARY, lhs_start);
|
||||
advance_and_verify(c, TOKEN_QUESTION);
|
||||
bool is_const = tok_is(c, TOKEN_CT_TERNARY);
|
||||
advance(c);
|
||||
|
||||
// If we have no expression following *or* it is a '!' followed by no expression
|
||||
// in this case it's an optional expression.
|
||||
if (!rules[c->tok].prefix || ((c->tok == TOKEN_BANG || c->tok == TOKEN_BANGBANG) && !rules[peek(c)].prefix))
|
||||
if (is_const)
|
||||
{
|
||||
expr->ternary_expr.is_const = true;
|
||||
}
|
||||
else if (!rules[c->tok].prefix || ((c->tok == TOKEN_BANG || c->tok == TOKEN_BANGBANG) && !rules[peek(c)].prefix))
|
||||
{
|
||||
// If we have no expression following *or* it is a '!' followed by no expression
|
||||
// in this case it's an optional expression.
|
||||
expr->expr_kind = EXPR_OPTIONAL;
|
||||
expr->inner_expr = left_side;
|
||||
RANGE_EXTEND_PREV(expr);
|
||||
@@ -2197,6 +2202,7 @@ ParseRule rules[TOKEN_EOF + 1] = {
|
||||
[TOKEN_CT_QNAMEOF] = { parse_ct_call, NULL, PREC_NONE },
|
||||
[TOKEN_CT_SIZEOF] = { parse_ct_sizeof, NULL, PREC_NONE },
|
||||
[TOKEN_CT_STRINGIFY] = { parse_ct_stringify, NULL, PREC_NONE },
|
||||
[TOKEN_CT_TERNARY] = { NULL, parse_ternary_expr, PREC_TERNARY },
|
||||
[TOKEN_CT_TYPEFROM] = { parse_type_expr, NULL, PREC_NONE },
|
||||
[TOKEN_CT_TYPEOF] = { parse_type_expr, NULL, PREC_NONE },
|
||||
[TOKEN_CT_VAARG] = { parse_ct_arg, NULL, PREC_NONE },
|
||||
|
||||
@@ -1394,6 +1394,7 @@ Ast *parse_stmt(ParseContext *c)
|
||||
case TOKEN_CT_NAMEOF:
|
||||
case TOKEN_CT_OFFSETOF:
|
||||
case TOKEN_CT_OR:
|
||||
case TOKEN_CT_TERNARY:
|
||||
case TOKEN_CT_QNAMEOF:
|
||||
case TOKEN_CT_SIZEOF:
|
||||
case TOKEN_CT_STRINGIFY:
|
||||
|
||||
@@ -1027,11 +1027,20 @@ static inline bool sema_expr_analyse_ternary(SemaContext *context, Type *infer_t
|
||||
Expr *left = exprptrzero(expr->ternary_expr.then_expr);
|
||||
Expr *cond = exprptr(expr->ternary_expr.cond);
|
||||
CondResult path = COND_MISSING;
|
||||
bool is_const = expr->ternary_expr.is_const;
|
||||
// Normal
|
||||
if (left)
|
||||
{
|
||||
if (!sema_analyse_cond_expr(context, cond, &path)) return expr_poison(expr);
|
||||
if (!sema_analyse_maybe_dead_expr(context, left, path == COND_FALSE, infer_type)) return expr_poison(expr);
|
||||
if (is_const && path == COND_MISSING)
|
||||
{
|
||||
RETURN_SEMA_ERROR(cond, "When using '\?\?\?' the cond expression must evaluate to a constant.");
|
||||
}
|
||||
bool is_left = path == COND_TRUE;
|
||||
if (!is_const || is_left)
|
||||
{
|
||||
if (!sema_analyse_maybe_dead_expr(context, left, !is_left, infer_type)) return expr_poison(expr);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1069,8 +1078,28 @@ static inline bool sema_expr_analyse_ternary(SemaContext *context, Type *infer_t
|
||||
}
|
||||
}
|
||||
|
||||
bool is_right = path == COND_FALSE;
|
||||
|
||||
Expr *right = exprptr(expr->ternary_expr.else_expr);
|
||||
if (!sema_analyse_maybe_dead_expr(context, right, path == COND_TRUE, infer_type)) return expr_poison(expr);
|
||||
if (!is_const || is_right)
|
||||
{
|
||||
if (!sema_analyse_maybe_dead_expr(context, right, !is_right, infer_type)) return expr_poison(expr);
|
||||
}
|
||||
|
||||
if (is_const)
|
||||
{
|
||||
switch (path)
|
||||
{
|
||||
case COND_TRUE:
|
||||
expr_replace(expr, left);
|
||||
return true;
|
||||
case COND_FALSE:
|
||||
expr_replace(expr, right);
|
||||
return true;
|
||||
default:
|
||||
UNREACHABLE
|
||||
}
|
||||
}
|
||||
|
||||
Type *left_canonical = left->type->canonical;
|
||||
Type *right_canonical = right->type->canonical;
|
||||
|
||||
@@ -88,6 +88,8 @@ const char *token_type_to_string(TokenType type)
|
||||
return "|||";
|
||||
case TOKEN_CT_CONCAT:
|
||||
return "+++";
|
||||
case TOKEN_CT_TERNARY:
|
||||
return "???";
|
||||
case TOKEN_DIV_ASSIGN:
|
||||
return "/=";
|
||||
case TOKEN_DOTDOT:
|
||||
|
||||
25
test/test_suite/statements/ct_ternary.c3t
Normal file
25
test/test_suite/statements/ct_ternary.c3t
Normal file
@@ -0,0 +1,25 @@
|
||||
// #target: macos-x64
|
||||
module test;
|
||||
import std;
|
||||
|
||||
fn int main()
|
||||
{
|
||||
int aa = false ??? 1 + a : 2;
|
||||
int x = 42;
|
||||
int bb = true ??? x : 1 + a;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* #expect: test.ll
|
||||
|
||||
define i32 @main() #0 {
|
||||
entry:
|
||||
%aa = alloca i32, align 4
|
||||
%x = alloca i32, align 4
|
||||
%bb = alloca i32, align 4
|
||||
store i32 2, ptr %aa, align 4
|
||||
store i32 42, ptr %x, align 4
|
||||
%0 = load i32, ptr %x, align 4
|
||||
store i32 %0, ptr %bb, align 4
|
||||
ret i32 0
|
||||
}
|
||||
@@ -2,8 +2,8 @@ module std::core::values @test;
|
||||
|
||||
fn void test_select()
|
||||
{
|
||||
const int X = @select(true, 1, "Hello");
|
||||
const String Y = @select(false, 1, "Hello");
|
||||
const int X = true ??? 1 : "Hello";
|
||||
const String Y = false ??? 1 : "Hello";
|
||||
test::eq(X, 1);
|
||||
test::eq(Y, "Hello");
|
||||
}
|
||||
Reference in New Issue
Block a user