bitwise operations

This commit is contained in:
2026-07-19 00:36:31 +02:00
parent f9448363e4
commit c7e3162ecb
21 changed files with 219439 additions and 140077 deletions
+14 -1
View File
@@ -97,10 +97,17 @@ Expr_Kind :: enum u8 {
Comptime,
Negate,
Not,
Bit_Not,
Add,
Sub,
Mul,
Div,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
Eq,
Ne,
Lt,
@@ -165,6 +172,12 @@ Assignment_Op :: enum u8 {
Sub,
Mul,
Div,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
}
Stmt :: struct {
@@ -184,7 +197,7 @@ Stmt :: struct {
error_only: bool,
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
// the arithmetic variants are `+=`, `-=`, `*=`, and `/=`.
// the remaining variants preserve their corresponding compound operator.
assignment_op: Assignment_Op,
target: Expr_Id,
expr: Expr_Id,
+177 -7
View File
@@ -372,7 +372,7 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
case .Call, .Array, .Struct_Literal, .Slice:
append(&expr_stack, ..expr.args)
append(&expr_stack, expr.left)
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
append(&expr_stack, expr.left)
case .Comptime:
append(&expr_stack, expr.left)
@@ -380,7 +380,8 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
case .Catch:
append(&expr_stack, expr.left, expr.right)
append(&statement_stack, ..expr.body)
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&expr_stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole,
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
@@ -1243,6 +1244,23 @@ is_numeric_constant_expr :: proc(checker: ^Checker, expr_id: ast.Expr_Id) -> boo
return eval_constant(checker, expr_id).kind == .Value || is_float_constant_expr(checker, expr_id)
}
is_typed_integer_fold_candidate :: proc(checker: ^Checker, expr_id: ast.Expr_Id, depth := 0) -> bool {
if depth > 64 || expr_id == ast.INVALID_EXPR || int(expr_id) >= len(checker.ast_module.exprs) {
return false
}
expr := checker.ast_module.exprs[expr_id]
#partial switch expr.kind {
case .Integer:
return true
case .Negate, .Bit_Not, .Cast:
return is_typed_integer_fold_candidate(checker, expr.left, depth+1)
case .Add, .Sub, .Mul, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
return is_typed_integer_fold_candidate(checker, expr.left, depth+1) &&
is_typed_integer_fold_candidate(checker, expr.right, depth+1)
}
return false
}
is_numeric_demand :: proc(value: types.Type, selected := target.DEFAULT) -> bool {
return types.is_concrete_scalar(value) && !types.is_bool(value) ||
types.is_float(value, selected)
@@ -3249,7 +3267,7 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
if expr.left != ast.INVALID_EXPR {
append(&stack, expr.left)
}
case .Negate, .Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
case .Negate, .Not, .Bit_Not, .Address, .Deref, .Field, .Unwrap, .Try, .Keyed, .Enum_Literal, .Cast:
append(&stack, expr.left)
case .Comptime:
if expr.left != ast.INVALID_EXPR {
@@ -3268,7 +3286,8 @@ mark_expr_imports_used :: proc(checker: ^Checker, expr_id: ast.Expr_Id, file: as
function := checker.ast_module.functions[function_id]
mark_block_imports_used(checker, function.body, function.file)
}
case .Add, .Sub, .Mul, .Div, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left, .Shift_Right,
.Shift_Left_Saturating, .Index, .Orelse, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
append(&stack, expr.left, expr.right)
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type:
}
@@ -4290,6 +4309,29 @@ infer_compound_expr :: proc(
case .Not:
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
return types.BOOL
case .Bit_Not:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
operand := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
return operand if types.is_concrete_integer(operand) else types.INVALID
case .Bit_And, .Bit_Or, .Bit_Xor:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
left, right := types.INVALID, types.INVALID
if left_const && !right_const && !types.is_valid(hint) {
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, right)
} else {
left = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
right = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, hint if types.is_valid(hint) else left)
}
result := types.widest(left, right)
return result if types.is_concrete_integer(result) else types.INVALID
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types, hint)
right := infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types, types.U64)
return left if types.is_concrete_integer(left) && types.is_unsigned(right, checker.target) else types.INVALID
case .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
_ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded, local_types)
@@ -4599,7 +4641,8 @@ infer_expr :: proc(
_ = pop(&stack)
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
.Comptime, .Bool, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
.Comptime, .Bool, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range:
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
_ = pop(&stack)
case .Function_Literal:
@@ -7758,6 +7801,88 @@ build_compound_expr :: proc(
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bit_Not:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
operand := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, operand); propagated {
return invalid
}
operand_type := checker.module.exprs[operand].type
if !types.is_concrete_integer(operand_type) {
id := source.add(checker.diagnostics, expr.span, "'~' requires a concrete integer operand")
return invalid_hir_expr(checker, expr.span, id, operand_type)
}
return add_hir_expr(checker, hir.Expr{
kind=.Bit_Not, span=expr.span, type=operand_type, left=operand,
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Bit_And, .Bit_Or, .Bit_Xor:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left_const := is_numeric_constant_expr(checker, expr.left)
right_const := is_numeric_constant_expr(checker, expr.right)
left, right: hir.Expr_Id
if left_const && !right_const && !types.is_valid(hint) {
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, types.INVALID, pkg, file)
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, checker.module.exprs[right].type, pkg, file)
} else {
left = build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
right_hint := hint if types.is_valid(hint) else checker.module.exprs[left].type
right = build_nested_expr(checker, expr.right, locals, global_reads, calls, right_hint, pkg, file)
}
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
result_type := types.widest(checker.module.exprs[left].type, checker.module.exprs[right].type)
if !types.is_concrete_integer(result_type) {
id := source.add(checker.diagnostics, expr.span, "bitwise operation requires compatible concrete integer operands")
return invalid_hir_expr(checker, expr.span, id)
}
left = coerce_expr(checker, left, result_type, checker.module.exprs[left].span)
right = coerce_expr(checker, right, result_type, checker.module.exprs[right].span)
kind := hir.Expr_Kind.Bit_And
#partial switch expr.kind {
case .Bit_Or: kind = .Bit_Or
case .Bit_Xor: kind = .Bit_Xor
}
return add_hir_expr(checker, hir.Expr{
kind=kind, span=expr.span, type=result_type, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
hint := expected if types.is_concrete_integer(expected) else types.INVALID
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, hint, pkg, file)
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.U64, pkg, file)
if invalid, propagated := propagate_invalid_expr(checker, expr.span, left, right); propagated {
return invalid
}
left_type := checker.module.exprs[left].type
right_type := checker.module.exprs[right].type
if !types.is_concrete_integer(left_type) {
id := source.add(checker.diagnostics, checker.module.exprs[left].span, "shifted value must be a concrete integer")
return invalid_hir_expr(checker, expr.span, id, left_type)
}
if !types.is_unsigned(right_type, checker.target) {
id := source.add(checker.diagnostics, checker.module.exprs[right].span, "shift count must be an unsigned integer")
return invalid_hir_expr(checker, expr.span, id, left_type)
}
if constant := eval_integer_constant_in_context(checker, expr.right, pkg, file);
constant.kind == .Value && expr.kind != .Shift_Left_Saturating &&
constant.value >= i128(types.bits(left_type, checker.target)) {
id := source.addf(
checker.diagnostics, checker.module.exprs[right].span,
"shift count %d exceeds %s width", constant.value, types.name(left_type),
)
return invalid_hir_expr(checker, expr.span, id, left_type)
}
kind := hir.Expr_Kind.Shift_Left
#partial switch expr.kind {
case .Shift_Right: kind = .Shift_Right
case .Shift_Left_Saturating: kind = .Shift_Left_Saturating
}
return add_hir_expr(checker, hir.Expr{
kind=kind, span=expr.span, type=left_type, left=left, right=right,
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
})
case .And, .Or:
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
@@ -8096,6 +8221,14 @@ build_expr :: proc(
continue
}
}
if expr.kind == .Bit_Not || expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor ||
expr.kind == .Shift_Left || expr.kind == .Shift_Right || expr.kind == .Shift_Left_Saturating {
if folded, ok := try_fold_typed_integer_expr(checker, frame.expr, frame.expected, pkg, file); ok {
last = folded
_ = pop(&stack)
continue
}
}
constant := Constant{}
_, static_name := current_static_binding(checker, expr.name)
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
@@ -8109,7 +8242,8 @@ build_expr :: proc(
switch expr.kind {
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
.Bool, .Cast, .Comptime, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
.Bool, .Cast, .Comptime, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
.Shift_Right, .Shift_Left_Saturating, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or, .Range,
.Enum_Literal:
last = build_compound_expr(
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
@@ -9792,6 +9926,10 @@ build_block :: proc(
rhs_expected := target_type
if types.is_many_pointer(target_type, &checker.module.types) {
rhs_expected = types.USIZE if statement.assignment_op == .Add else types.INVALID
} else if statement.assignment_op == .Shift_Left ||
statement.assignment_op == .Shift_Right ||
statement.assignment_op == .Shift_Left_Saturating {
rhs_expected = types.U64
}
value = build_expr(
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
@@ -9824,10 +9962,42 @@ build_block :: proc(
case .Sub: assignment_op = .Sub
case .Mul: assignment_op = .Mul
case .Div: assignment_op = .Div
case .Bit_And: assignment_op = .Bit_And
case .Bit_Or: assignment_op = .Bit_Or
case .Bit_Xor: assignment_op = .Bit_Xor
case .Shift_Left: assignment_op = .Shift_Left
case .Shift_Right: assignment_op = .Shift_Right
case .Shift_Left_Saturating: assignment_op = .Shift_Left_Saturating
}
rhs_type := checker.module.exprs[value].type
is_shift := statement.assignment_op == .Shift_Left ||
statement.assignment_op == .Shift_Right ||
statement.assignment_op == .Shift_Left_Saturating
is_bitwise := statement.assignment_op == .Bit_And ||
statement.assignment_op == .Bit_Or ||
statement.assignment_op == .Bit_Xor
result_type := types.widest(target_type, rhs_type)
if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
if is_shift {
if !types.is_concrete_integer(target_type) || !types.is_unsigned(rhs_type, checker.target) {
id := source.add(checker.diagnostics, statement.span, "shift assignment requires an integer target and unsigned integer count")
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else if constant := eval_integer_constant_in_context(checker, statement.expr, ctx.pkg, ctx.file);
constant.kind == .Value && statement.assignment_op != .Shift_Left_Saturating &&
constant.value >= i128(types.bits(target_type, checker.target)) {
id := source.addf(
checker.diagnostics, statement.span,
"shift count %d exceeds %s width", constant.value, types.name(target_type),
)
value = invalid_hir_expr(checker, statement.span, id, target_type)
}
} else if is_bitwise {
if !types.is_concrete_integer(result_type) {
id := source.add(checker.diagnostics, statement.span, "bitwise assignment requires compatible concrete integer operands")
value = invalid_hir_expr(checker, statement.span, id, target_type)
} else {
value = coerce_expr(checker, value, target_type, statement.span)
}
} else if statement.assignment_op == .Div && types.is_concrete_integer(result_type) {
id := source.add(
checker.diagnostics,
statement.span,
+124 -3
View File
@@ -1270,14 +1270,18 @@ ct_eval_expr :: proc(
return ct_add_value(state, Ct_Value{
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
}), ct_flow(.Normal), true
case .Negate, .Not:
case .Negate, .Not, .Bit_Not:
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
return ct_eval_unary(state, expr.kind, value, expr.span)
case .Add, .Sub, .Mul, .Div, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
case .Add, .Sub, .Mul, .Div, .Bit_And, .Bit_Or, .Bit_Xor, .Eq, .Ne, .Lt, .Le, .Gt, .Ge:
left_expected := expected if expr.kind == .Div && types.is_float(expected, checker.target) else types.INVALID
if (expr.kind == .Bit_And || expr.kind == .Bit_Or || expr.kind == .Bit_Xor) &&
types.is_concrete_integer(expected) {
left_expected = expected
}
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
@@ -1287,6 +1291,16 @@ ct_eval_expr :: proc(
return INVALID_CT_VALUE, right_flow, right_ok
}
return ct_eval_binary(state, expr.kind, left, right, expr.span)
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
left, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
right, right_flow, right_ok := ct_eval_expr(state, expr.right, types.U64, depth+1)
if !right_ok || right_flow.kind != .Normal {
return INVALID_CT_VALUE, right_flow, right_ok
}
return ct_eval_binary(state, expr.kind, left, right, expr.span)
case .And, .Or:
left, flow, ok := ct_eval_expr(state, expr.left, types.BOOL, depth+1)
if !ok || flow.kind != .Normal {
@@ -2033,6 +2047,19 @@ ct_unwrap_optional :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span)
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "postfix '?' requires an optional")
}
ct_normalize_integer :: proc(state: ^Ct_State, value: i128, type: types.Type) -> i128 {
bits := types.bits(type, state.checker.target)
mask := (i128(1) << u32(bits))-1
raw := value & mask
if types.is_signed(type, state.checker.target) {
sign := i128(1) << u32(bits-1)
if raw & sign != 0 {
return raw-(i128(1) << u32(bits))
}
}
return raw
}
ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
@@ -2044,6 +2071,13 @@ ct_eval_unary :: proc(state: ^Ct_State, op: ast.Expr_Kind, id: Ct_Value_Id, span
}
return ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if value.integer == 0 else 0}), ct_flow(.Normal), true
}
if op == .Bit_Not {
if value.kind != .Integer || !types.is_concrete_integer(value.type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "'~' requires a concrete integer operand")
}
value.integer = ct_normalize_integer(state, ~value.integer, value.type)
return ct_add_value(state, value), ct_flow(.Normal), true
}
if value.kind == .Integer {
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
if overflow {
@@ -2125,6 +2159,62 @@ ct_eval_binary :: proc(state: ^Ct_State, op: ast.Expr_Kind, left_id, right_id: C
"integer '/' is not allowed; use divtrunc!, divfloor!, divexact!, or divceil!",
)
}
if op == .Bit_And || op == .Bit_Or || op == .Bit_Xor {
result_type := types.widest(left.type, right.type)
if !types.is_concrete_integer(result_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "bitwise operation requires compatible concrete integer operands")
}
value := left.integer & right.integer
#partial switch op {
case .Bit_Or: value = left.integer | right.integer
case .Bit_Xor: value = left.integer ~ right.integer
}
value = ct_normalize_integer(state, value, result_type)
return ct_add_value(state, Ct_Value{kind=.Integer, type=result_type, integer=value}), ct_flow(.Normal), true
}
if op == .Shift_Left || op == .Shift_Right || op == .Shift_Left_Saturating {
if !types.is_concrete_integer(left.type) || !types.is_unsigned(right.type, state.checker.target) || right.integer < 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift requires a concrete integer value and unsigned integer count")
}
bits := types.bits(left.type, state.checker.target)
if right.integer >= i128(bits) {
if op != .Shift_Left_Saturating {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "shift count exceeds integer width")
}
endpoint := i128(0)
if left.integer != 0 {
if types.is_signed(left.type, state.checker.target) {
endpoint = -(i128(1) << u32(bits-1)) if left.integer < 0 else (i128(1) << u32(bits-1))-1
} else {
endpoint = (i128(1) << u32(bits))-1
}
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=endpoint}), ct_flow(.Normal), true
}
count := u32(right.integer)
if op == .Shift_Right {
value := left.integer >> count
if !types.is_signed(left.type, state.checker.target) {
value = ct_normalize_integer(state, left.integer, left.type) >> count
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
}
if op == .Shift_Left_Saturating {
factor := i128(1) << count
value := left.integer*factor
if types.is_signed(left.type, state.checker.target) {
minimum := -(i128(1) << u32(bits-1))
maximum := (i128(1) << u32(bits-1))-1
value = max(minimum, min(maximum, value))
} else {
maximum := (i128(1) << u32(bits))-1
value = min(maximum, ct_normalize_integer(state, left.integer, left.type)*factor)
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
}
value := ct_normalize_integer(state, ct_normalize_integer(state, left.integer, left.type) << count, left.type)
return ct_add_value(state, Ct_Value{kind=.Integer, type=left.type, integer=value}), ct_flow(.Normal), true
}
value: i128
overflow := false
#partial switch op {
@@ -3478,7 +3568,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
flow = ct_flow(.Normal)
}
} else {
value, flow, ok = ct_eval_expr(state, statement.expr, expected, depth+1)
value_expected := expected
if statement.assignment_op == .Shift_Left || statement.assignment_op == .Shift_Right ||
statement.assignment_op == .Shift_Left_Saturating {
value_expected = types.U64
}
value, flow, ok = ct_eval_expr(state, statement.expr, value_expected, depth+1)
}
if !ok || flow.kind != .Normal {
return flow, ok
@@ -3493,6 +3588,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
case .Sub: op = .Sub
case .Mul: op = .Mul
case .Div: op = .Div
case .Bit_And: op = .Bit_And
case .Bit_Or: op = .Bit_Or
case .Bit_Xor: op = .Bit_Xor
case .Shift_Left: op = .Shift_Left
case .Shift_Right: op = .Shift_Right
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
case: op = .Add
}
bin_flow: Ct_Flow
@@ -4046,6 +4147,26 @@ eval_integer_constant_in_context :: proc(
return Constant{kind=.Value, value=integer}
}
try_fold_typed_integer_expr :: proc(
checker: ^Checker,
expr_id: ast.Expr_Id,
expected: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
) -> (hir.Expr_Id, bool) {
if !is_typed_integer_fold_candidate(checker, expr_id) {
return hir.INVALID_EXPR, false
}
state := ct_state_make(checker, pkg, file, diagnose=false)
defer ct_state_destroy(&state)
value, flow, ok := ct_eval_expr(&state, expr_id, expected)
if !ok || flow.kind != .Normal || value == INVALID_CT_VALUE || int(value) >= len(state.values) ||
state.values[value].kind != .Integer {
return hir.INVALID_EXPR, false
}
return ct_materialize_value(&state, value, checker.ast_module.exprs[expr_id].span, expected), true
}
eval_comptime_statements :: proc(
checker: ^Checker,
statements: []ast.Stmt_Id,
+14 -1
View File
@@ -110,6 +110,7 @@ Expr_Kind :: enum u8 {
Decay_Array_Pointer,
Negate,
Not,
Bit_Not,
Add,
Sub,
Mul,
@@ -121,6 +122,12 @@ Expr_Kind :: enum u8 {
Rem,
Mod,
Pointer_Add,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
Eq,
Ne,
Lt,
@@ -192,6 +199,12 @@ Assignment_Op :: enum u8 {
Mul,
Div,
Pointer_Add,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
}
Stmt :: struct {
@@ -206,7 +219,7 @@ Stmt :: struct {
expr: Expr_Id,
iterator_type: types.Type,
pointer_capture: bool,
// Assignments carry their operation explicitly. Arithmetic operations lower
// Assignments carry their operation explicitly. Compound operations lower
// by computing the target address once, loading its current value, applying
// the operation to `expr`, and storing through the original address.
assignment_op: Assignment_Op,
+7
View File
@@ -117,6 +117,13 @@ Opcode :: enum u8 {
Mod_Checked,
Pointer_Add,
Not,
Bit_Not,
Bit_And,
Bit_Or,
Bit_Xor,
Shift_Left,
Shift_Right,
Shift_Left_Saturating,
Compare,
Label,
Br,
+48 -4
View File
@@ -35,6 +35,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
case "orelse": return .Keyword_Orelse
case "and": return .Keyword_And
case "or": return .Keyword_Or
case "xor": return .Keyword_Xor
case "if": return .Keyword_If
case "while": return .Keyword_While
case "for": return .Keyword_For
@@ -156,7 +157,23 @@ lex :: proc(
case '<':
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
if cursor < len(bytes) && bytes[cursor] == '<' {
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '|' {
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Less_Less_Pipe_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Less_Less_Pipe, start, cursor)
}
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Less_Less_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Less_Less, start, cursor)
}
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Less_Equal, start, cursor)
} else {
@@ -165,7 +182,15 @@ lex :: proc(
case '>':
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
if cursor < len(bytes) && bytes[cursor] == '>' {
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Greater_Greater_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Greater_Greater, start, cursor)
}
} else if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Greater_Equal, start, cursor)
} else {
@@ -231,11 +256,20 @@ lex :: proc(
append_token(&stream, source_file, .Slash, start, cursor)
}
case '&':
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Ampersand_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Ampersand, start, cursor)
}
case '^':
append_token(&stream, source_file, .Caret, cursor, cursor+1)
cursor += 1
case '~':
append_token(&stream, source_file, .Tilde, cursor, cursor+1)
cursor += 1
case '?':
append_token(&stream, source_file, .Question, cursor, cursor+1)
cursor += 1
@@ -261,8 +295,14 @@ lex :: proc(
append_token(&stream, source_file, .Comma, cursor, cursor+1)
cursor += 1
case '|':
append_token(&stream, source_file, .Pipe, cursor, cursor+1)
start := cursor
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
append_token(&stream, source_file, .Pipe_Equal, start, cursor)
} else {
append_token(&stream, source_file, .Pipe, start, cursor)
}
case '"':
start := cursor
cursor += 1
@@ -370,6 +410,10 @@ lex :: proc(
}
text := source_file.text[start:cursor]
kind := keyword_kind(text)
if kind == .Keyword_Xor && cursor < len(bytes) && bytes[cursor] == '=' {
cursor += 1
kind = .Xor_Equal
}
id := symbol.INVALID
if kind == .Identifier || kind == .Underscore {
id = symbol.intern(symbols, text)
+122 -1
View File
@@ -260,7 +260,8 @@ valid_value :: proc(
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked,
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
.Rem_Checked, .Mod_Checked,
.Pointer_Add, .Not, .Compare, .Call:
.Pointer_Add, .Not, .Bit_Not, .Bit_And, .Bit_Or, .Bit_Xor,
.Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Compare, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
@@ -814,6 +815,94 @@ emit_division_builtin :: proc(
}
}
emit_shift :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
instruction_index: int,
instruction: ir.Instruction,
) {
count_type := types.INVALID
if valid_instruction(instructions, instruction.b) {
count_type = instructions[instruction.b].type
}
if !types.is_concrete_integer(instruction.type) ||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!types.is_concrete_integer(count_type) || !types.is_unsigned(count_type, emitter.module.target) ||
!valid_value(instructions, instruction.b, count_type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid shift operands")
return
}
type_name := llvm_type(instruction.type, &emitter.module.types)
bits := types.bits(instruction.type, emitter.module.target)
count_bits := types.bits(count_type, emitter.module.target)
if count_bits < 64 {
fmt.sbprintf(&emitter.builder, " %%shift_count64_%d = zext %s ", instruction_index, llvm_type(count_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.b, count_type, &emitter.module.types)
strings.write_string(&emitter.builder, " to i64\n")
} else {
fmt.sbprintf(&emitter.builder, " %%shift_count64_%d = select i1 true, i64 ", instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, count_type, &emitter.module.types)
strings.write_string(&emitter.builder, ", i64 0\n")
}
fmt.sbprintf(&emitter.builder, " %%shift_in_range%d = icmp ult i64 %%shift_count64_%d, %d\n", instruction_index, instruction_index, bits)
if instruction.op != .Shift_Left_Saturating {
fmt.sbprintf(
&emitter.builder,
" br i1 %%shift_in_range%d, label %%shift_continue%d, label %%shift_trap%d\nshift_trap%d:\n",
instruction_index, instruction_index, instruction_index, instruction_index,
)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "shift count exceeds integer width")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nshift_continue%d:\n", instruction_index)
if bits < 64 {
fmt.sbprintf(&emitter.builder, " %%shift_count%d = trunc i64 %%shift_count64_%d to %s\n", instruction_index, instruction_index, type_name)
}
operation := "shl"
if instruction.op == .Shift_Right {
operation = "ashr" if types.is_signed(instruction.type, emitter.module.target) else "lshr"
}
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
if bits < 64 {
fmt.sbprintf(&emitter.builder, ", %%shift_count%d\n", instruction_index)
} else {
fmt.sbprintf(&emitter.builder, ", %%shift_count64_%d\n", instruction_index)
}
return
}
// LLVM's saturating shift intrinsics produce poison for oversized counts.
// Select zero before narrowing, then explicitly select the Zig endpoint.
fmt.sbprintf(&emitter.builder, " %%shift_safe64_%d = select i1 %%shift_in_range%d, i64 %%shift_count64_%d, i64 0\n", instruction_index, instruction_index, instruction_index)
if bits < 64 {
fmt.sbprintf(&emitter.builder, " %%shift_safe%d = trunc i64 %%shift_safe64_%d to %s\n", instruction_index, instruction_index, type_name)
}
signed := types.is_signed(instruction.type, emitter.module.target)
intrinsic := "sshl" if signed else "ushl"
fmt.sbprintf(&emitter.builder, " %%shift_saturated%d = call %s @llvm.%s.sat.%s(%s ", instruction_index, type_name, intrinsic, type_name, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
if bits < 64 {
fmt.sbprintf(&emitter.builder, ", %s %%shift_safe%d)\n", type_name, instruction_index)
} else {
fmt.sbprintf(&emitter.builder, ", i64 %%shift_safe64_%d)\n", instruction_index)
}
fmt.sbprintf(&emitter.builder, " %%shift_nonzero%d = icmp ne %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", 0\n")
if signed {
minimum := -(i128(1) << u32(bits-1))
maximum := (i128(1) << u32(bits-1))-1
fmt.sbprintf(&emitter.builder, " %%shift_negative%d = icmp slt %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", 0\n %%shift_nonzero_endpoint%d = select i1 %%shift_negative%d, %s %d, %s %d\n", instruction_index, instruction_index, type_name, minimum, type_name, maximum)
fmt.sbprintf(&emitter.builder, " %%shift_endpoint%d = select i1 %%shift_nonzero%d, %s %%shift_nonzero_endpoint%d, %s 0\n", instruction_index, instruction_index, type_name, instruction_index, type_name)
} else {
fmt.sbprintf(&emitter.builder, " %%shift_endpoint%d = select i1 %%shift_nonzero%d, %s -1, %s 0\n", instruction_index, instruction_index, type_name, type_name)
}
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 %%shift_in_range%d, %s %%shift_saturated%d, %s %%shift_endpoint%d\n", instruction_index, instruction_index, type_name, instruction_index, type_name, instruction_index)
}
emit_instruction_stream :: proc(
emitter: ^Emitter,
instructions: []ir.Instruction,
@@ -1818,6 +1907,36 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 ", instruction_index, llvm_type(result_item.child, &emitter.module.types), instruction.a)
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
case .Bit_Not:
if !types.is_concrete_integer(instruction.type) ||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid bitwise complement operand")
continue
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = xor %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", -1\n")
case .Bit_And, .Bit_Or, .Bit_Xor:
if !types.is_concrete_integer(instruction.type) ||
!valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) ||
!valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid bitwise operands")
continue
}
operation := "and"
#partial switch instruction.op {
case .Bit_Or: operation = "or"
case .Bit_Xor: operation = "xor"
}
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, type_name)
write_operand(&emitter.builder, instructions, instruction.a, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, ", ")
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
case .Shift_Left, .Shift_Right, .Shift_Left_Saturating:
emit_shift(emitter, instructions, instruction_index, instruction)
case .Call:
function_id := ir.as_function(instruction.target)
if function_id == ir.INVALID_FUNCTION {
@@ -2488,6 +2607,8 @@ emit_declarations :: proc(emitter: ^Emitter) {
strings.write_string(&emitter.builder, ".with.overflow.i")
fmt.sbprintf(&emitter.builder, "%d(i%d, i%d)\n", bits, bits, bits)
}
fmt.sbprintf(&emitter.builder, "declare i%d @llvm.sshl.sat.i%d(i%d, i%d)\n", bits, bits, bits, bits)
fmt.sbprintf(&emitter.builder, "declare i%d @llvm.ushl.sat.i%d(i%d, i%d)\n", bits, bits, bits, bits)
}
strings.write_string(
&emitter.builder,
+20 -3
View File
@@ -720,11 +720,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Widen, .Sum_Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Scalar_Cast, .Pointer_Cast, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
case .Negate, .Bit_Not:
stack[frame_index].stage = 5
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Add, .Sub, .Mul, .Div, .Div_Trunc, .Div_Floor, .Div_Exact, .Div_Ceil,
.Rem, .Mod, .Pointer_Add:
.Rem, .Mod, .Pointer_Add, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
.Shift_Right, .Shift_Left_Saturating:
stack[frame_index].stage = 2
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Call:
@@ -762,8 +763,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
continue
}
if frame.stage == 5 {
op := ir.Opcode.Neg_Checked
if expr.kind == .Bit_Not {
op = .Bit_Not
}
last = append_instruction(state, ir.Instruction{
op=.Neg_Checked, span=expr.span, type=expr.type, target=ir.INVALID_REF,
op=op, span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
_ = pop(&stack)
@@ -810,6 +815,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Rem: op = .Rem_Checked
case .Mod: op = .Mod_Checked
case .Pointer_Add: op = .Pointer_Add
case .Bit_And: op = .Bit_And
case .Bit_Or: op = .Bit_Or
case .Bit_Xor: op = .Bit_Xor
case .Shift_Left: op = .Shift_Left
case .Shift_Right: op = .Shift_Right
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
}
last = append_instruction(state, ir.Instruction{
op=op,
@@ -916,6 +927,12 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
case .Mul: op = .Mul_Checked
case .Div: op = .Div_Checked
case .Pointer_Add: op = .Pointer_Add
case .Bit_And: op = .Bit_And
case .Bit_Or: op = .Bit_Or
case .Bit_Xor: op = .Bit_Xor
case .Shift_Left: op = .Shift_Left
case .Shift_Right: op = .Shift_Right
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
}
value := append_instruction(state, ir.Instruction{
op=op, span=statement.span, type=target_type,
+38 -3
View File
@@ -26,6 +26,9 @@ Parser :: struct {
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
// allow struct literals.
no_struct_literal: bool,
// At the top level of if/for headers, `|` begins captures. Bitwise OR in
// those headers remains available inside parentheses.
capture_pipe: bool,
hidden_names: [dynamic]symbol.Id,
}
@@ -1105,10 +1108,14 @@ infix_binding_power :: proc(kind: token.Kind) -> (left, right: int, ok: bool) {
return 6, 7, true
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
return 8, 9, true
case .Plus, .Minus:
case .Ampersand, .Keyword_Xor, .Pipe:
return 10, 11, true
case .Star, .Slash:
case .Less_Less, .Greater_Greater, .Less_Less_Pipe:
return 12, 13, true
case .Plus, .Minus:
return 14, 15, true
case .Star, .Slash:
return 16, 17, true
}
return 0, 0, false
}
@@ -1126,6 +1133,12 @@ infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
case .Less_Equal: return .Le
case .Greater: return .Gt
case .Greater_Equal: return .Ge
case .Ampersand: return .Bit_And
case .Pipe: return .Bit_Or
case .Keyword_Xor: return .Bit_Xor
case .Less_Less: return .Shift_Left
case .Greater_Greater: return .Shift_Right
case .Less_Less_Pipe: return .Shift_Left_Saturating
case .Plus: return .Add
case .Minus: return .Sub
case .Star: return .Mul
@@ -1140,6 +1153,12 @@ compound_assignment_op :: proc(kind: token.Kind) -> (ast.Assignment_Op, bool) {
case .Minus_Equal: return .Sub, true
case .Star_Equal: return .Mul, true
case .Slash_Equal: return .Div, true
case .Ampersand_Equal: return .Bit_And, true
case .Pipe_Equal: return .Bit_Or, true
case .Xor_Equal: return .Bit_Xor, true
case .Less_Less_Equal: return .Shift_Left, true
case .Greater_Greater_Equal: return .Shift_Right, true
case .Less_Less_Pipe_Equal: return .Shift_Left_Saturating, true
}
return .Set, false
}
@@ -1158,7 +1177,7 @@ is_simple_range_bound :: proc(expr: ast.Expr) -> bool {
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
#partial switch kind {
case .Minus, .Ampersand, .Bang, .Dollar, .Keyword_Try:
case .Minus, .Ampersand, .Bang, .Tilde, .Dollar, .Keyword_Try:
return 20, true
}
return 0, false
@@ -1196,6 +1215,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
#partial switch operator.kind {
case .Ampersand: prefix_kind = .Address
case .Bang: prefix_kind = .Not
case .Tilde: prefix_kind = .Bit_Not
case .Dollar: prefix_kind = .Comptime
case .Keyword_Try: prefix_kind = .Try
}
@@ -1352,6 +1372,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
})
continue
}
if parser.capture_pipe && parser.delimiter_depth == 0 && current(parser).kind == .Pipe {
break
}
left_power, right_power, ok := infix_binding_power(current(parser).kind)
if ok && (current(parser).kind == .Range || current(parser).kind == .Range_Inclusive) &&
parser.range_disabled > 0 {
@@ -2031,7 +2054,10 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
skip_newlines(parser)
saved := parser.no_struct_literal
parser.no_struct_literal = true
saved_capture_pipe := parser.capture_pipe
parser.capture_pipe = true
condition := parse_expression(parser)
parser.capture_pipe = saved_capture_pipe
parser.no_struct_literal = saved
captures: [dynamic]symbol.Id
captures.allocator = parser.module.allocator
@@ -2060,7 +2086,10 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
} else {
saved = parser.no_struct_literal
parser.no_struct_literal = true
saved_capture_pipe = parser.capture_pipe
parser.capture_pipe = true
guard = parse_expression(parser)
parser.capture_pipe = saved_capture_pipe
parser.no_struct_literal = saved
}
}
@@ -2166,6 +2195,8 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
} else if _, is_else := allow(parser, .Keyword_Else); !is_else {
saved := parser.no_struct_literal
parser.no_struct_literal = true
saved_capture_pipe := parser.capture_pipe
parser.capture_pipe = true
append(&patterns, parse_expression(parser))
for {
if _, ok := allow(parser, .Comma); !ok {
@@ -2174,6 +2205,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
skip_newlines(parser)
append(&patterns, parse_expression(parser))
}
parser.capture_pipe = saved_capture_pipe
parser.no_struct_literal = saved
if _, ok := allow(parser, .Pipe); ok {
if _, at_ok := allow(parser, .At); at_ok {
@@ -2324,7 +2356,10 @@ parse_for :: proc(parser: ^Parser) -> ast.Stmt_Id {
skip_newlines(parser)
saved := parser.no_struct_literal
parser.no_struct_literal = true
saved_capture_pipe := parser.capture_pipe
parser.capture_pipe = true
iterable := parse_expression(parser)
parser.capture_pipe = saved_capture_pipe
parser.no_struct_literal = saved
skip_newlines(parser)
+11
View File
@@ -22,8 +22,14 @@ Kind :: enum u8 {
Bang_Equal,
Less,
Less_Equal,
Less_Less,
Less_Less_Equal,
Less_Less_Pipe,
Less_Less_Pipe_Equal,
Greater,
Greater_Equal,
Greater_Greater,
Greater_Greater_Equal,
Plus,
Minus,
Slash,
@@ -39,7 +45,10 @@ Kind :: enum u8 {
Dollar,
Star,
Ampersand,
Ampersand_Equal,
Caret,
Tilde,
Xor_Equal,
Question,
Semicolon,
Left_Bracket,
@@ -50,6 +59,7 @@ Kind :: enum u8 {
Right_Brace,
Comma,
Pipe,
Pipe_Equal,
Keyword_Test,
Keyword_Func,
Keyword_C_Func,
@@ -71,6 +81,7 @@ Kind :: enum u8 {
Keyword_Orelse,
Keyword_And,
Keyword_Or,
Keyword_Xor,
Keyword_If,
Keyword_While,
Keyword_For,