Make or_else and or_return operators (binary and suffix respectively)

This commit is contained in:
gingerBill
2021-08-15 17:14:35 +01:00
parent a3a20f09e2
commit 21cbac755e
16 changed files with 247 additions and 195 deletions

View File

@@ -57,7 +57,7 @@ link_error_delete :: proc(lerr: Maybe(Link_Error)) {
is_platform_error :: proc(ferr: Error) -> (err: i32, ok: bool) {
v := or_else(ferr.(Platform_Error), {});
v := ferr.(Platform_Error) or_else {};
return v.err, v.err != 0;
}

View File

@@ -13,7 +13,7 @@ error_to_io_error :: proc(ferr: Error) -> io.Error {
if ferr == nil {
return .None;
}
return or_else(ferr.(io.Error), .Unknown);
return ferr.(io.Error) or_else .Unknown;
}

View File

@@ -68,7 +68,7 @@ Thread_Os_Specific :: struct {
thread_create :: proc(procedure: Thread_Proc) -> ^Thread {
__windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD {
t := (^Thread)(t_);
context = or_else(t.init_context.?, runtime.default_context());
context = t.init_context.? or_else runtime.default_context();
t.procedure(t);

View File

@@ -46,7 +46,7 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^
t.start_gate = {};
t.start_mutex = {};
context = or_else(t.init_context.?, runtime.default_context());
context = t.init_context.? or_else runtime.default_context();
t.procedure(t);

View File

@@ -23,7 +23,7 @@ _create :: proc(procedure: Thread_Proc, priority := Thread_Priority.Normal) -> ^
__windows_thread_entry_proc :: proc "stdcall" (t_: rawptr) -> win32.DWORD {
t := (^Thread)(t_);
context = or_else(t.init_context.?, runtime.default_context());
context = t.init_context.? or_else runtime.default_context();
t.procedure(t);

View File

@@ -3,57 +3,57 @@ package all
// Imports every package
// This is useful for knowing what exists and producing documentation with `odin doc`
import "core:bufio"
import "core:bytes"
import "core:c"
import c_tokenizer "core:c/frontend/tokenizer"
import bufio "core:bufio"
import bytes "core:bytes"
import c "core:c"
import c_tokenizer "core:c/frontend/tokenizer"
import c_preprocessor "core:c/frontend/preprocessor"
import "core:compress"
import "core:compress/gzip"
import "core:compress/zlib"
import "core:container"
import "core:dynlib"
import "core:encoding"
import "core:encoding/base32"
import "core:encoding/base64"
import "core:encoding/csv"
import "core:encoding/hxa"
import "core:encoding/json"
import "core:fmt"
import "core:hash"
import "core:image"
import "core:image/png"
import "core:io"
import "core:log"
import "core:math"
import "core:math/big"
import "core:math/bits"
import "core:math/fixed"
import "core:math/linalg"
import "core:math/rand"
import "core:mem"
import "core:odin/ast"
import doc_format "core:odin/doc-format"
import "core:odin/format"
import "core:odin/parser"
import "core:odin/printer"
import compress "core:compress"
import gzip "core:compress/gzip"
import zlib "core:compress/zlib"
import container "core:container"
import dynlib "core:dynlib"
import encoding "core:encoding"
import base32 "core:encoding/base32"
import base64 "core:encoding/base64"
import csv "core:encoding/csv"
import hxa "core:encoding/hxa"
import json "core:encoding/json"
import fmt "core:fmt"
import hash "core:hash"
import image "core:image"
import png "core:image/png"
import io "core:io"
import log "core:log"
import math "core:math"
import big "core:math/big"
import bits "core:math/bits"
import fixed "core:math/fixed"
import linalg "core:math/linalg"
import rand "core:math/rand"
import mem "core:mem"
import ast "core:odin/ast"
import doc_format "core:odin/doc-format"
import odin_format "core:odin/format"
import odin_parser "core:odin/parser"
import odin_printer "core:odin/printer"
import odin_tokenizer "core:odin/tokenizer"
import "core:os"
import "core:path"
import "core:path/filepath"
import "core:reflect"
import "core:runtime"
import "core:slice"
import "core:sort"
import "core:strconv"
import "core:strings"
import "core:sync"
import "core:sync/sync2"
import "core:text/scanner"
import "core:thread"
import "core:time"
import "core:unicode"
import "core:unicode/utf8"
import "core:unicode/utf16"
import os "core:os"
import path "core:path"
import filepath "core:path/filepath"
import reflect "core:reflect"
import runtime "core:runtime"
import slice "core:slice"
import sort "core:sort"
import strconv "core:strconv"
import strings "core:strings"
import sync "core:sync"
import sync2 "core:sync/sync2"
import scanner "core:text/scanner"
import thread "core:thread"
import time "core:time"
import unicode "core:unicode"
import utf8 "core:unicode/utf8"
import utf16 "core:unicode/utf16"
main :: proc(){}

View File

@@ -2010,7 +2010,7 @@ or_else_procedure :: proc() {
i = 123;
}
// The above can be mapped to 'or_else'
i = or_else(m["hellope"], 123);
i = m["hellope"] or_else 123;
assert(i == 123);
}
@@ -2019,12 +2019,12 @@ or_else_procedure :: proc() {
// have optional ok semantics
v: union{int, f64};
i: int;
i = or_else(v.(int), 123);
i = or_else(v.?, 123); // Type inference magic
i = v.(int) or_else 123;
i = v.? or_else 123; // Type inference magic
assert(i == 123);
m: Maybe(int);
i = or_else(m.?, 456);
i = m.? or_else 456;
assert(i == 456);
}
}

View File

@@ -179,11 +179,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
// NOTE(bill): The first arg may be a Type, this will be checked case by case
break;
case BuiltinProc_or_else:
case BuiltinProc_or_return:
// NOTE(bill): The arguments may be multi-expr
break;
case BuiltinProc_DIRECTIVE: {
ast_node(bd, BasicDirective, ce->proc);
String name = bd->name.string;
@@ -1835,120 +1830,6 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
break;
}
case BuiltinProc_or_else: {
GB_ASSERT(ce->args.count == 2);
Ast *arg = ce->args[0];
Ast *default_value = ce->args[1];
Operand x = {};
Operand y = {};
check_multi_expr_with_type_hint(c, &x, arg, type_hint);
if (x.mode == Addressing_Invalid) {
operand->mode = Addressing_Value;
operand->type = t_invalid;
return false;
}
check_multi_expr_with_type_hint(c, &y, default_value, x.type);
error_operand_no_value(&y);
if (y.mode == Addressing_Invalid) {
operand->mode = Addressing_Value;
operand->type = t_invalid;
return false;
}
Type *left_type = nullptr;
Type *right_type = nullptr;
check_or_else_split_types(c, &x, builtin_name, &left_type, &right_type);
add_type_and_value(&c->checker->info, arg, x.mode, x.type, x.value);
if (left_type != nullptr) {
check_assignment(c, &y, left_type, builtin_name);
} else {
check_or_else_expr_no_value_error(c, builtin_name, x, type_hint);
}
if (left_type == nullptr) {
left_type = t_invalid;
}
operand->mode = Addressing_Value;
operand->type = left_type;
return true;
}
case BuiltinProc_or_return: {
GB_ASSERT(ce->args.count == 1);
Ast *arg = ce->args[0];
Operand x = {};
check_multi_expr_with_type_hint(c, &x, arg, type_hint);
if (x.mode == Addressing_Invalid) {
operand->mode = Addressing_Value;
operand->type = t_invalid;
return false;
}
Type *left_type = nullptr;
Type *right_type = nullptr;
check_or_return_split_types(c, &x, builtin_name, &left_type, &right_type);
add_type_and_value(&c->checker->info, arg, x.mode, x.type, x.value);
if (right_type == nullptr) {
check_or_else_expr_no_value_error(c, builtin_name, x, type_hint);
} else {
Type *proc_type = base_type(c->curr_proc_sig);
GB_ASSERT(proc_type->kind == Type_Proc);
Type *result_type = proc_type->Proc.results;
if (result_type == nullptr) {
error(call, "'%.*s' requires the current procedure to have at least one return value", LIT(builtin_name));
} else {
GB_ASSERT(result_type->kind == Type_Tuple);
auto const &vars = result_type->Tuple.variables;
Type *end_type = vars[vars.count-1]->type;
if (vars.count > 1) {
if (!proc_type->Proc.has_named_results) {
error(call, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(builtin_name));
}
}
Operand rhs = {};
rhs.type = right_type;
rhs.mode = Addressing_Value;
// TODO(bill): better error message
if (!check_is_assignable_to(c, &rhs, end_type)) {
gbString a = type_to_string(right_type);
gbString b = type_to_string(end_type);
gbString ret_type = type_to_string(result_type);
error(call, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(builtin_name));
if (vars.count == 1) {
error_line("\tProcedure return value type: %s\n", ret_type);
} else {
error_line("\tProcedure return value types: (%s)\n", ret_type);
}
gb_string_free(ret_type);
gb_string_free(b);
gb_string_free(a);
}
}
}
operand->type = left_type;
if (left_type != nullptr) {
operand->mode = Addressing_Value;
} else {
operand->mode = Addressing_NoValue;
}
if (c->curr_proc_sig == nullptr) {
error(call, "'%.*s' can only be used within a procedure", LIT(builtin_name));
}
return true;
}
case BuiltinProc_simd_vector: {
Operand x = {};
Operand y = {};

View File

@@ -114,6 +114,11 @@ bool check_builtin_procedure(CheckerContext *c, Operand *operand, Ast *call, i32
void check_promote_optional_ok(CheckerContext *c, Operand *x, Type **val_type_, Type **ok_type_);
void check_or_else_right_type(CheckerContext *c, Ast *expr, String const &name, Type *right_type);
void check_or_else_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_);
void check_or_else_expr_no_value_error(CheckerContext *c, String const &name, Operand const &x, Type *type_hint);
void check_or_return_split_types(CheckerContext *c, Operand *x, String const &name, Type **left_type_, Type **right_type_);
Entity *entity_from_expr(Ast *expr) {
expr = unparen_expr(expr);
switch (expr->kind) {
@@ -2960,6 +2965,25 @@ void update_untyped_expr_type(CheckerContext *c, Ast *e, Type *type, bool final)
update_untyped_expr_type(c, te->y, type, final);
case_end;
case_ast_node(ore, OrReturnExpr, e);
if (old->value.kind != ExactValue_Invalid) {
// See above note in UnaryExpr case
break;
}
update_untyped_expr_type(c, ore->expr, type, final);
case_end;
case_ast_node(oee, OrElseExpr, e);
if (old->value.kind != ExactValue_Invalid) {
// See above note in UnaryExpr case
break;
}
update_untyped_expr_type(c, oee->x, type, final);
update_untyped_expr_type(c, oee->y, type, final);
case_end;
case_ast_node(pe, ParenExpr, e);
update_untyped_expr_type(c, pe->expr, type, final);
case_end;
@@ -6602,6 +6626,118 @@ ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast *node, Type
}
case_end;
case_ast_node(oe, OrElseExpr, node);
String name = oe->token.string;
Ast *arg = oe->x;
Ast *default_value = oe->y;
Operand x = {};
Operand y = {};
check_multi_expr_with_type_hint(c, &x, arg, type_hint);
if (x.mode == Addressing_Invalid) {
o->mode = Addressing_Value;
o->type = t_invalid;
return Expr_Expr;
}
check_multi_expr_with_type_hint(c, &y, default_value, x.type);
error_operand_no_value(&y);
if (y.mode == Addressing_Invalid) {
o->mode = Addressing_Value;
o->type = t_invalid;
return Expr_Expr;
}
Type *left_type = nullptr;
Type *right_type = nullptr;
check_or_else_split_types(c, &x, name, &left_type, &right_type);
add_type_and_value(&c->checker->info, arg, x.mode, x.type, x.value);
if (left_type != nullptr) {
check_assignment(c, &y, left_type, name);
} else {
check_or_else_expr_no_value_error(c, name, x, type_hint);
}
if (left_type == nullptr) {
left_type = t_invalid;
}
o->mode = Addressing_Value;
o->type = left_type;
return Expr_Expr;
case_end;
case_ast_node(re, OrReturnExpr, node);
String name = re->token.string;
Operand x = {};
check_multi_expr_with_type_hint(c, &x, re->expr, type_hint);
if (x.mode == Addressing_Invalid) {
o->mode = Addressing_Value;
o->type = t_invalid;
return Expr_Expr;
}
Type *left_type = nullptr;
Type *right_type = nullptr;
check_or_return_split_types(c, &x, name, &left_type, &right_type);
add_type_and_value(&c->checker->info, re->expr, x.mode, x.type, x.value);
if (right_type == nullptr) {
check_or_else_expr_no_value_error(c, name, x, type_hint);
} else {
Type *proc_type = base_type(c->curr_proc_sig);
GB_ASSERT(proc_type->kind == Type_Proc);
Type *result_type = proc_type->Proc.results;
if (result_type == nullptr) {
error(node, "'%.*s' requires the current procedure to have at least one return value", LIT(name));
} else {
GB_ASSERT(result_type->kind == Type_Tuple);
auto const &vars = result_type->Tuple.variables;
Type *end_type = vars[vars.count-1]->type;
if (vars.count > 1) {
if (!proc_type->Proc.has_named_results) {
error(node, "'%.*s' within a procedure with more than 1 return value requires that the return values are named, allowing for early return", LIT(name));
}
}
Operand rhs = {};
rhs.type = right_type;
rhs.mode = Addressing_Value;
// TODO(bill): better error message
if (!check_is_assignable_to(c, &rhs, end_type)) {
gbString a = type_to_string(right_type);
gbString b = type_to_string(end_type);
gbString ret_type = type_to_string(result_type);
error(node, "Cannot assign end value of type '%s' to '%s' in '%.*s'", a, b, LIT(name));
if (vars.count == 1) {
error_line("\tProcedure return value type: %s\n", ret_type);
} else {
error_line("\tProcedure return value types: (%s)\n", ret_type);
}
gb_string_free(ret_type);
gb_string_free(b);
gb_string_free(a);
}
}
}
o->type = left_type;
if (left_type != nullptr) {
o->mode = Addressing_Value;
} else {
o->mode = Addressing_NoValue;
}
if (c->curr_proc_sig == nullptr) {
error(node, "'%.*s' can only be used within a procedure", LIT(name));
}
return Expr_Expr;
case_end;
case_ast_node(cl, CompoundLit, node);
Type *type = type_hint;
if (type != nullptr && is_type_untyped(type)) {

View File

@@ -33,9 +33,6 @@ enum BuiltinProcId {
BuiltinProc_soa_zip,
BuiltinProc_soa_unzip,
BuiltinProc_or_else,
BuiltinProc_or_return,
BuiltinProc_DIRECTIVE, // NOTE(bill): This is used for specialized hash-prefixed procedures
// "Intrinsics"
@@ -266,9 +263,6 @@ gb_global BuiltinProc builtin_procs[BuiltinProc_COUNT] = {
{STR_LIT("soa_zip"), 1, true, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("soa_unzip"), 1, false, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("or_else"), 2, false, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT("or_return"), 1, false, Expr_Expr, BuiltinProcPkg_builtin},
{STR_LIT(""), 0, true, Expr_Expr, BuiltinProcPkg_builtin}, // DIRECTIVE

View File

@@ -2305,6 +2305,14 @@ lbValue lb_build_expr(lbProcedure *p, Ast *expr) {
}
case_end;
case_ast_node(oe, OrElseExpr, expr);
return lb_emit_or_else(p, oe->x, oe->y, tv);
case_end;
case_ast_node(oe, OrReturnExpr, expr);
return lb_emit_or_return(p, oe->expr, tv);
case_end;
case_ast_node(ta, TypeAssertion, expr);
TokenPos pos = ast_token(expr).pos;
Type *type = tv.type;

View File

@@ -1252,11 +1252,6 @@ lbValue lb_build_builtin_proc(lbProcedure *p, Ast *expr, TypeAndValue const &tv,
case BuiltinProc_soa_unzip:
return lb_soa_unzip(p, ce, tv);
case BuiltinProc_or_else:
return lb_emit_or_else(p, ce->args[0], ce->args[1], tv);
case BuiltinProc_or_return:
return lb_emit_or_return(p, ce->args[0], tv);
// "Intrinsics"
case BuiltinProc_alloca:

View File

@@ -685,6 +685,21 @@ Ast *ast_ternary_when_expr(AstFile *f, Ast *x, Ast *cond, Ast *y) {
return result;
}
Ast *ast_or_else_expr(AstFile *f, Ast *x, Token const &token, Ast *y) {
Ast *result = alloc_ast_node(f, Ast_OrElseExpr);
result->OrElseExpr.x = x;
result->OrElseExpr.token = token;
result->OrElseExpr.y = y;
return result;
}
Ast *ast_or_return_expr(AstFile *f, Ast *expr, Token const &token) {
Ast *result = alloc_ast_node(f, Ast_OrReturnExpr);
result->OrReturnExpr.expr = expr;
result->OrReturnExpr.token = token;
return result;
}
Ast *ast_type_assertion(AstFile *f, Ast *expr, Token dot, Ast *type) {
Ast *result = alloc_ast_node(f, Ast_TypeAssertion);
result->TypeAssertion.expr = expr;
@@ -1340,6 +1355,8 @@ Token expect_operator(AstFile *f) {
// okay
} else if (prev.kind == Token_if || prev.kind == Token_when) {
// okay
} else if (prev.kind == Token_or_else || prev.kind == Token_or_return) {
// okay
} else if (!gb_is_between(prev.kind, Token__OperatorBegin+1, Token__OperatorEnd-1)) {
String p = token_to_string(prev);
syntax_error(f->curr_token, "Expected an operator, got '%.*s'",
@@ -2870,6 +2887,8 @@ i32 token_precedence(AstFile *f, TokenKind t) {
case Token_Question:
case Token_if:
case Token_when:
case Token_or_else:
case Token_or_return:
return 1;
case Token_Ellipsis:
case Token_RangeFull:
@@ -2924,14 +2943,18 @@ Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) {
// NOTE(bill): This will also catch operators that are not valid "binary" operators
break;
}
if (op.kind == Token_if || op.kind == Token_when) {
Token prev = f->prev_token;
Token prev = f->prev_token;
switch (op.kind) {
case Token_if:
case Token_when:
case Token_or_else:
case Token_or_return:
if (prev.pos.line < op.pos.line) {
// NOTE(bill): Check to see if the `if` or `when` is on the same line of the `lhs` condition
break;
goto loop_end;
}
break;
}
expect_operator(f); // NOTE(bill): error checks too
if (op.kind == Token_Question) {
@@ -2955,6 +2978,12 @@ Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) {
Token tok_else = expect_token(f, Token_else);
Ast *y = parse_expr(f, lhs);
expr = ast_ternary_when_expr(f, x, cond, y);
} else if (op.kind == Token_or_else) {
Ast *x = expr;
Ast *y = parse_expr(f, lhs);
expr = ast_or_else_expr(f, x, op, y);
} else if (op.kind == Token_or_return) {
expr = ast_or_return_expr(f, expr, op);
} else {
Ast *right = parse_binary_expr(f, false, prec+1);
if (right == nullptr) {
@@ -2965,6 +2994,7 @@ Ast *parse_binary_expr(AstFile *f, bool lhs, i32 prec_in) {
lhs = false;
}
loop_end:;
}
return expr;
}

View File

@@ -385,6 +385,8 @@ AST_KIND(_ExprBegin, "", bool) \
AST_KIND(FieldValue, "field value", struct { Token eq; Ast *field, *value; }) \
AST_KIND(TernaryIfExpr, "ternary if expression", struct { Ast *x, *cond, *y; }) \
AST_KIND(TernaryWhenExpr, "ternary when expression", struct { Ast *x, *cond, *y; }) \
AST_KIND(OrElseExpr, "or_else expression", struct { Ast *x; Token token; Ast *y; }) \
AST_KIND(OrReturnExpr, "or_return expression", struct { Ast *expr; Token token; }) \
AST_KIND(TypeAssertion, "type assertion", struct { \
Ast *expr; \
Token dot; \

View File

@@ -41,6 +41,8 @@ Token ast_token(Ast *node) {
case Ast_DerefExpr: return node->DerefExpr.op;
case Ast_TernaryIfExpr: return ast_token(node->TernaryIfExpr.x);
case Ast_TernaryWhenExpr: return ast_token(node->TernaryWhenExpr.x);
case Ast_OrElseExpr: return ast_token(node->OrElseExpr.x);
case Ast_OrReturnExpr: return ast_token(node->OrReturnExpr.expr);
case Ast_TypeAssertion: return ast_token(node->TypeAssertion.expr);
case Ast_TypeCast: return node->TypeCast.token;
case Ast_AutoCast: return node->AutoCast.token;
@@ -175,6 +177,8 @@ Token ast_end_token(Ast *node) {
case Ast_DerefExpr: return node->DerefExpr.op;
case Ast_TernaryIfExpr: return ast_end_token(node->TernaryIfExpr.y);
case Ast_TernaryWhenExpr: return ast_end_token(node->TernaryWhenExpr.y);
case Ast_OrElseExpr: return ast_end_token(node->OrElseExpr.y);
case Ast_OrReturnExpr: return node->OrReturnExpr.token;
case Ast_TypeAssertion: return ast_end_token(node->TypeAssertion.type);
case Ast_TypeCast: return ast_end_token(node->TypeCast.expr);
case Ast_AutoCast: return ast_end_token(node->AutoCast.expr);

View File

@@ -113,10 +113,12 @@ TOKEN_KIND(Token__KeywordBegin, ""), \
TOKEN_KIND(Token_transmute, "transmute"), \
TOKEN_KIND(Token_distinct, "distinct"), \
TOKEN_KIND(Token_using, "using"), \
TOKEN_KIND(Token_context, "context"), \
TOKEN_KIND(Token_or_else, "or_else"), \
TOKEN_KIND(Token_or_return, "or_return"), \
TOKEN_KIND(Token_asm, "asm"), \
TOKEN_KIND(Token_inline, "inline"), \
TOKEN_KIND(Token_no_inline, "no_inline"), \
TOKEN_KIND(Token_context, "context"), \
TOKEN_KIND(Token_asm, "asm"), \
TOKEN_KIND(Token__KeywordEnd, ""), \
TOKEN_KIND(Token_Count, "")