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
+31 -2
View File
@@ -27,7 +27,7 @@ roadmap and milestone history.
- exact-width integers, concrete pointer-sized `isize` / `usize`, `f32`, `f64`, `bool`, `void`, and `anyopaque`; contextual `int` accepts the whole integer family, while `uint` accepts only unsigned native and target-classified C integers
- target-dependent C scalar primitives from `c_char` through `c_longdouble`, kept semantically distinct from native scalars
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic, and compile-time folding for numeric constant expressions
- contextual integer/float/character literals, backward type-demand inference through names and arithmetic/bitwise expressions, and typed compile-time evaluation for numeric constant expressions
- strict numeric conversion by default, widening where valid, C scalar coercions at C boundaries, and explicit scalar keyword casts such as `i32(x)` / `c_float(x)`
- compile-time `minval!(T)` and `maxval!(T)` bounds for concrete native and C integer scalar types
- arrays `[N]T`, inferred-count arrays `[_]T`, sentinel arrays `[N;S]T`, compile-time expression array counts, slices `[]T` / `[;S]T`, single-item pointers `@T`, many-item pointers `*T`, and sentinel many-item pointers `[*;S]T`
@@ -90,7 +90,8 @@ fields. `_` is not a keyword member name.
### expressions and control flow
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
- assignments and compound assignments `+= -= *= /=` with single evaluation of complex lvalues; `/=` is float-only
- Zig-style integer bitwise complement `~`, binary `&`, `|`, `xor`, shifts `<<` / `>>`, and saturating left shift `<<|`; postfix `^` remains pointer dereference
- assignments and compound assignments `+= -= *= /= &= |= xor= <<= >>= <<|=` with single evaluation of complex lvalues; `/=` is float-only and `xor=` is contiguous
- field access through struct values and pointers, index/slice bounds contextually coerced to `usize`, and unsigned narrower index support
- boolean `if` / `else if` / `else` and `for` loops with braceless single-statement bodies when the preceding expression is parenthesized or a function call
- `while` loops with optional post-iteration update clauses
@@ -103,6 +104,34 @@ fields. `_` is not a keyword member name.
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
- direct `return match ...` and `yield match ...` value-control-flow operands
#### bitwise operations
Bitwise operands must be concrete integers. `~` preserves its operand type. `&`, `xor`, and `|`
use the ordinary common-integer widening rules; incompatible fixed integer families remain errors.
Shifts preserve the left operand type and require a concrete unsigned count. `>>` is arithmetic for
signed integers and logical for unsigned integers.
Ordinary `<<` and `>>` reject compile-time-known counts at least as large as the left type's bit
width and trap for such runtime counts. `<<` discards shifted-out bits. Saturating `<<|` permits any
unsigned count: zero remains zero, unsigned nonzero values clamp to the type maximum, and signed
values clamp to the minimum or maximum according to their sign.
Binary precedence, from tightest to loosest, is:
```text
* /
+ -
<< >> <<|
& xor |
== != < > <= >=
and
or
```
Each level is left-associative. Because `|` also delimits `if` and `for` captures, a bitwise-OR
header expression must be parenthesized before a capture list, for example
`if (flags | mask) |value| { ... }`.
#### division
Compiler intrinsics use direct unqualified `name!(...)` syntax. The `!` marks the call as an
+2 -1
View File
@@ -225,7 +225,8 @@ Current prototype features:
- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs
- String literals as immutable pointers to static zero-terminated byte arrays
- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals
- Contextual integer constants and compile-time folding of addition and unary negation trees
- Contextual integer constants and typed compile-time evaluation of arithmetic and Zig-style bitwise expressions
- Integer `~`, `&`, `|`, `xor`, guarded `<<` / `>>`, saturating `<<|`, and their compound assignments; postfix `^` remains pointer dereference
- Directory packages with merged declarations and file-local relative imports
- Relative C header imports as synthetic package namespaces
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
+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,
+46 -2
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
@@ -155,8 +156,24 @@ lex :: proc(
}
case '<':
start := cursor
cursor += 1
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 {
@@ -164,8 +181,16 @@ lex :: proc(
}
case '>':
start := cursor
cursor += 1
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,
+244
View File
@@ -177,6 +177,240 @@ lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) {
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
}
@(test)
lexer_recognizes_bitwise_operators_with_longest_match :: proc(t: ^testing.T) {
source_file := source.Source{path="test.bro", text="~ & &= | |= xor xor= << <<= >> >>= <<| <<|= ^"}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
expected := [?]token.Kind{
.Tilde, .Ampersand, .Ampersand_Equal, .Pipe, .Pipe_Equal, .Keyword_Xor, .Xor_Equal,
.Less_Less, .Less_Less_Equal, .Greater_Greater, .Greater_Greater_Equal,
.Less_Less_Pipe, .Less_Less_Pipe_Equal, .Caret, .Eof,
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, len(stream.items), len(expected))
for kind, index in expected {
testing.expect_value(t, stream.items[index].kind, kind)
}
}
@(test)
parser_applies_bitwise_precedence_and_preserves_capture_and_deref_pipes :: proc(t: ^testing.T) {
text := `main func() void {
_ = 1 + 2 << 1 & 7 xor 3 | 4 == 5 and true or false
value i32 = 1
pointer *i32 = &value
_ = pointer^ & 1
if (none | none) |captured| { _ = captured }
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&module)
root := module.exprs[module.statements[module.functions[0].body[0]].expr]
and_expr := module.exprs[root.left]
equality := module.exprs[and_expr.left]
bit_or := module.exprs[equality.left]
bit_xor := module.exprs[bit_or.left]
bit_and := module.exprs[bit_xor.left]
shift := module.exprs[bit_and.left]
addition := module.exprs[shift.left]
deref_and := module.exprs[module.statements[module.functions[0].body[3]].expr]
if_statement := module.statements[module.functions[0].body[4]]
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, root.kind, ast.Expr_Kind.Or)
testing.expect_value(t, and_expr.kind, ast.Expr_Kind.And)
testing.expect_value(t, equality.kind, ast.Expr_Kind.Eq)
testing.expect_value(t, bit_or.kind, ast.Expr_Kind.Bit_Or)
testing.expect_value(t, bit_xor.kind, ast.Expr_Kind.Bit_Xor)
testing.expect_value(t, bit_and.kind, ast.Expr_Kind.Bit_And)
testing.expect_value(t, shift.kind, ast.Expr_Kind.Shift_Left)
testing.expect_value(t, addition.kind, ast.Expr_Kind.Add)
testing.expect_value(t, deref_and.kind, ast.Expr_Kind.Bit_And)
testing.expect_value(t, module.exprs[deref_and.left].kind, ast.Expr_Kind.Deref)
testing.expect_value(t, module.exprs[if_statement.expr].kind, ast.Expr_Kind.Bit_Or)
testing.expect_value(t, len(if_statement.captures), 1)
}
@(test)
bitwise_operations_lower_to_guarded_llvm_integer_instructions :: proc(t: ^testing.T) {
text := `ops func(a i32, b i32, count u8) i32 {
_ = ~a
_ = a & b
_ = a | b
_ = a xor b
_ = a << count
_ = a >> count
return a <<| count
}
uops func(a u32, count u8) u32 {
_ = a >> count
return a <<| count
}
main func() i32 {
_ = uops(4, 1)
return ops(1, 2, 1)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
found: [7]bool
for function in ir_module.functions {
for instruction in function.instructions {
#partial switch instruction.op {
case .Bit_Not: found[0] = true
case .Bit_And: found[1] = true
case .Bit_Or: found[2] = true
case .Bit_Xor: found[3] = true
case .Shift_Left: found[4] = true
case .Shift_Right: found[5] = true
case .Shift_Left_Saturating: found[6] = true
}
}
}
testing.expect_value(t, len(diagnostics.items), 0)
for present in found {
testing.expect(t, present)
}
testing.expect(t, strings.contains(llvm_text, " = and i32 "))
testing.expect(t, strings.contains(llvm_text, " = or i32 "))
testing.expect(t, strings.contains(llvm_text, " = xor i32 "))
testing.expect(t, strings.contains(llvm_text, " = shl i32 "))
testing.expect(t, strings.contains(llvm_text, " = ashr i32 "))
testing.expect(t, strings.contains(llvm_text, " = lshr i32 "))
testing.expect(t, strings.contains(llvm_text, "@llvm.sshl.sat.i32"))
testing.expect(t, strings.contains(llvm_text, "@llvm.ushl.sat.i32"))
testing.expect(t, strings.contains(llvm_text, "shift_in_range"))
testing.expect(t, strings.contains(llvm_text, "shift_trap"))
}
@(test)
typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
text := `main func() void {
_ = ~u8(0)
_ = (u8(240) & u8(204)) xor u8(15)
_ = u8(129) << 1
_ = i8(-4) >> 1
_ = u8(1) <<| 8
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
for statement_id in hir_module.functions[0].body {
statement := hir_module.statements[statement_id]
testing.expect(t, statement.expr != hir.INVALID_EXPR)
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
}
}
@(test)
bitwise_checker_rejects_invalid_operands_and_known_overshifts :: proc(t: ^testing.T) {
text := `D :: distinct u8
E :: enum { one }
main func() void {
p *u8 = none
d D = D(1)
e E = .one
signed_count i8 = 1
_ = true & false
_ = 1.0 | 2.0
_ = ~p
_ = ~d
_ = ~e
_ = u8(1) & i8(1)
_ = u8(1) << signed_count
_ = u8(1) << 8
_ = p >> u8(1)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_bitwise := false
found_shift := false
found_overshift := false
for diagnostic in diagnostics.items {
found_bitwise = found_bitwise || strings.contains(diagnostic.message, "bitwise") || strings.contains(diagnostic.message, "'~'")
found_shift = found_shift || strings.contains(diagnostic.message, "shift count") || strings.contains(diagnostic.message, "shifted value")
found_overshift = found_overshift || strings.contains(diagnostic.message, "exceeds u8 width")
}
testing.expect(t, found_bitwise)
testing.expect(t, found_shift)
testing.expect(t, found_overshift)
}
@(test)
runtime_ordinary_overshift_traps :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-bitwise-overshift"
main_path := "/tmp/brolang-test-bitwise-overshift/main.bro"
output := "/tmp/brolang-test-bitwise-overshift-output"
text := `shift func(value u8, count u8) u8 { return value << count }
main func() i32 {
_ = shift(1, 8)
return 0
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state, stdout, stderr, err := os2.process_exec(os2.Process_Desc{command=[]string{output}}, context.allocator)
defer delete(stdout)
defer delete(stderr)
testing.expect(t, err == nil)
testing.expect(t, state.exit_code != 0)
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
}
@(test)
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
text := `sum func(a,
@@ -5236,6 +5470,16 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
testing.expect_value(t, state.exit_code, 42)
}
@(test)
bitwise_example_compiles_and_runs :: proc(t: ^testing.T) {
output := "/tmp/brolang-test-bitwise"
defer _ = os.remove(output)
status := compiler_core.compile_package("examples/programs/bitwise", output)
testing.expect_value(t, status, 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 42)
}
@(test)
value_loop_label_does_not_shadow_own_yield_target :: proc(t: ^testing.T) {
text := `main func() i32 {
+62
View File
@@ -0,0 +1,62 @@
fold_bits func(value u8) u8 {
return (~value & 255) xor 15
}
folded u8 :: fold_bits(240)
contextual u8 :: ~0 & 255
contextual_shift u8 :: 1 << 7
Buffer :: alias [u8(1) << 3]u8
runtime_left func(value u16, count u8) u16 {
return value << count
}
c_count_shift func(value u8, count c_uint) u8 {
return value << count
}
c_count_fold u8 :: c_count_shift(3, 2)
main func() i32 {
buffer Buffer = undefined
if (folded != 0) return 1
if (contextual != 255) return 28
if (contextual_shift != 128) return 29
if (buffer.len != 8) return 2
if ((u8(240) & u8(204)) != 192) return 3
if ((u8(240) | u8(15)) != 255) return 4
if ((u8(240) xor u8(255)) != 15) return 5
if ((u8(129) << 1) != 2) return 6
if ((i8(-4) >> 1) != -2) return 7
if ((u8(128) >> 1) != 64) return 8
if ((u8(64) <<| 2) != 255) return 9
if ((i8(64) <<| 2) != 127) return 10
if ((i8(-64) <<| 2) != -128) return 11
if ((u8(1) <<| 8) != 255) return 12
if ((u8(0) <<| 80) != 0) return 13
if (runtime_left(3, 4) != 48) return 14
if (c_count_shift(3, 2) != 12) return 30
if (c_count_fold != 12) return 31
if (~u16(0) != 65535) return 16
if ((i16(-2) >> 1) != -1) return 17
if ((u32(2147483648) >> 31) != 1) return 18
if ((i32(-2147483647) << 1) != 2) return 19
if ((~u64(0) >> 63) != 1) return 20
if ((i64(-2) >> 1) != -1) return 21
if ((c_uchar(128) >> 7) != 1) return 22
if ((c_int(-2) >> 1) != -1) return 23
if ((c_uint(3) << 4) != 48) return 24
if ((c_ulonglong(1) <<| 64) != ~c_ulonglong(0)) return 25
if ((c_ushort(240) xor c_ushort(255)) != 15) return 26
if ((c_longlong(64) <<| 60) != maxval!(c_longlong)) return 27
value u8 = 3
value <<= 2
value |= 1
value xor= 5
value &= 15
value >>= 1
value <<|= 7
if (value != 255) return 15
return 42
}
+13 -6
View File
@@ -7,10 +7,12 @@ const PREC = {
OR: 3,
AND: 4,
COMPARE: 5,
SUM: 6,
PRODUCT: 7,
PREFIX: 8,
POSTFIX: 9,
BITWISE: 6,
SHIFT: 7,
SUM: 8,
PRODUCT: 9,
PREFIX: 10,
POSTFIX: 11,
};
module.exports = grammar({
@@ -33,6 +35,9 @@ module.exports = grammar({
[$.block, $.tuple_literal],
[$.field_initializer, $.expression],
[$.tuple_literal],
[$.capture_list, $.expression],
[$.for_statement, $.expression],
[$.match_capture, $.expression],
],
rules: {
@@ -278,7 +283,7 @@ module.exports = grammar({
assignment_statement: $ => seq(
field('left', $.expression),
field('operator', choice('=', '+=', '-=', '*=', '/=')),
field('operator', choice('=', '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|=')),
repeat($._newline),
field('right', $._value),
),
@@ -454,6 +459,8 @@ module.exports = grammar({
prec.left(PREC.OR, seq(field('left', $.expression), 'or', repeat($._newline), field('right', $.expression))),
prec.left(PREC.AND, seq(field('left', $.expression), 'and', repeat($._newline), field('right', $.expression))),
prec.left(PREC.COMPARE, seq(field('left', $.expression), field('operator', choice('==', '!=', '<', '<=', '>', '>=')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.BITWISE, seq(field('left', $.expression), field('operator', choice('&', 'xor', '|')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.SHIFT, seq(field('left', $.expression), field('operator', choice('<<', '>>', '<<|')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.SUM, seq(field('left', $.expression), field('operator', choice('+', '-')), repeat($._newline), field('right', $.expression))),
prec.left(PREC.PRODUCT, seq(field('left', $.expression), field('operator', choice('*', '/')), repeat($._newline), field('right', $.expression))),
),
@@ -469,7 +476,7 @@ module.exports = grammar({
)),
unary_expression: $ => prec(PREC.PREFIX, seq(
field('operator', choice('-', '&', '!', '$', 'try')),
field('operator', choice('-', '&', '!', '~', '$', 'try')),
repeat($._newline),
field('operand', $.expression),
)),
@@ -91,6 +91,12 @@
"-="
"*="
"/="
"&="
"|="
"xor="
"<<="
">>="
"<<|="
"=="
"!="
"<"
@@ -101,6 +107,11 @@
"-"
"*"
"/"
"~"
"xor"
"<<"
">>"
"<<|"
"!"
"&"
"@"
+156 -10
View File
@@ -1830,6 +1830,30 @@
{
"type": "STRING",
"value": "/="
},
{
"type": "STRING",
"value": "&="
},
{
"type": "STRING",
"value": "|="
},
{
"type": "STRING",
"value": "xor="
},
{
"type": "STRING",
"value": "<<="
},
{
"type": "STRING",
"value": ">>="
},
{
"type": "STRING",
"value": "<<|="
}
]
}
@@ -3306,6 +3330,112 @@
{
"type": "PREC_LEFT",
"value": 6,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "&"
},
{
"type": "STRING",
"value": "xor"
},
{
"type": "STRING",
"value": "|"
}
]
}
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "expression"
}
}
]
}
},
{
"type": "PREC_LEFT",
"value": 7,
"content": {
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "left",
"content": {
"type": "SYMBOL",
"name": "expression"
}
},
{
"type": "FIELD",
"name": "operator",
"content": {
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "<<"
},
{
"type": "STRING",
"value": ">>"
},
{
"type": "STRING",
"value": "<<|"
}
]
}
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "FIELD",
"name": "right",
"content": {
"type": "SYMBOL",
"name": "expression"
}
}
]
}
},
{
"type": "PREC_LEFT",
"value": 8,
"content": {
"type": "SEQ",
"members": [
@@ -3354,7 +3484,7 @@
},
{
"type": "PREC_LEFT",
"value": 7,
"value": 9,
"content": {
"type": "SEQ",
"members": [
@@ -3466,7 +3596,7 @@
},
"unary_expression": {
"type": "PREC",
"value": 8,
"value": 10,
"content": {
"type": "SEQ",
"members": [
@@ -3488,6 +3618,10 @@
"type": "STRING",
"value": "!"
},
{
"type": "STRING",
"value": "~"
},
{
"type": "STRING",
"value": "$"
@@ -3519,7 +3653,7 @@
},
"field_expression": {
"type": "PREC_LEFT",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3557,7 +3691,7 @@
},
"intrinsic_call_expression": {
"type": "PREC",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3590,7 +3724,7 @@
},
"call_expression": {
"type": "PREC_LEFT",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3615,7 +3749,7 @@
},
"argument_list": {
"type": "PREC",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3721,7 +3855,7 @@
},
"index_expression": {
"type": "PREC_LEFT",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3768,7 +3902,7 @@
},
"slice_expression": {
"type": "PREC_LEFT",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3843,7 +3977,7 @@
},
"postfix_expression": {
"type": "PREC_LEFT",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -3877,7 +4011,7 @@
},
"struct_literal": {
"type": "PREC",
"value": 9,
"value": 11,
"content": {
"type": "SEQ",
"members": [
@@ -4803,6 +4937,18 @@
],
[
"tuple_literal"
],
[
"capture_list",
"expression"
],
[
"for_statement",
"expression"
],
[
"match_capture",
"expression"
]
],
"precedences": [],
+96
View File
@@ -111,6 +111,10 @@
"multiple": false,
"required": true,
"types": [
{
"type": "&=",
"named": false
},
{
"type": "*=",
"named": false
@@ -127,9 +131,29 @@
"type": "/=",
"named": false
},
{
"type": "<<=",
"named": false
},
{
"type": "<<|=",
"named": false
},
{
"type": "=",
"named": false
},
{
"type": ">>=",
"named": false
},
{
"type": "xor=",
"named": false
},
{
"type": "|=",
"named": false
}
]
},
@@ -191,6 +215,10 @@
"type": "!=",
"named": false
},
{
"type": "&",
"named": false
},
{
"type": "*",
"named": false
@@ -219,6 +247,14 @@
"type": "<",
"named": false
},
{
"type": "<<",
"named": false
},
{
"type": "<<|",
"named": false
},
{
"type": "<=",
"named": false
@@ -235,6 +271,10 @@
"type": ">=",
"named": false
},
{
"type": ">>",
"named": false
},
{
"type": "catch",
"named": false
@@ -242,6 +282,14 @@
{
"type": "orelse",
"named": false
},
{
"type": "xor",
"named": false
},
{
"type": "|",
"named": false
}
]
},
@@ -2187,6 +2235,10 @@
{
"type": "try",
"named": false
},
{
"type": "~",
"named": false
}
]
}
@@ -2422,6 +2474,10 @@
"type": "&",
"named": false
},
{
"type": "&=",
"named": false
},
{
"type": "(",
"named": false
@@ -2498,6 +2554,22 @@
"type": "<",
"named": false
},
{
"type": "<<",
"named": false
},
{
"type": "<<=",
"named": false
},
{
"type": "<<|",
"named": false
},
{
"type": "<<|=",
"named": false
},
{
"type": "<=",
"named": false
@@ -2518,6 +2590,14 @@
"type": ">=",
"named": false
},
{
"type": ">>",
"named": false
},
{
"type": ">>=",
"named": false
},
{
"type": "?",
"named": false
@@ -2839,6 +2919,14 @@
"type": "while",
"named": false
},
{
"type": "xor",
"named": false
},
{
"type": "xor=",
"named": false
},
{
"type": "yield",
"named": false
@@ -2851,8 +2939,16 @@
"type": "|",
"named": false
},
{
"type": "|=",
"named": false
},
{
"type": "}",
"named": false
},
{
"type": "~",
"named": false
}
]
+218100 -140034
View File
File diff suppressed because it is too large Load Diff
+134
View File
@@ -509,3 +509,137 @@ value uint :: 255
(builtin_type))
(expression
(integer))))
==================
Bitwise operations
==================
ops func(value u8, count u8) u8 {
value &= ~u8(1)
value |= 2
value xor= 3
value <<= count
value >>= count
value <<|= count
if (value | 1) |captured| { return captured }
return (value & 15) xor (value << 1) | (value >> 1) | (value <<| 8)
}
---
(source_file
(function_declaration
(identifier)
(parameter_list
(parameter
(identifier)
(type
(builtin_type)))
(parameter
(identifier)
(type
(builtin_type))))
(type
(builtin_type))
(block
(statement
(assignment_statement
(expression
(identifier))
(expression
(unary_expression
(expression
(call_expression
(expression
(builtin_type))
(argument_list
(expression
(integer)))))))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(integer))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(integer))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(identifier))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(identifier))))
(statement
(assignment_statement
(expression
(identifier))
(expression
(identifier))))
(statement
(if_statement
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))
(capture_list
(identifier))
(statement
(block
(statement
(return_statement
(expression
(identifier))))))))
(statement
(return_statement
(expression
(binary_expression
(expression
(binary_expression
(expression
(binary_expression
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))))
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer))))))))
(expression
(parenthesized_expression
(expression
(binary_expression
(expression
(identifier))
(expression
(integer)))))))))))))
@@ -0,0 +1,14 @@
value u8 = 1
value xor= 2
# ^^^^ operator
value <<|= 3
# ^^^^ operator
masked :: ~value & 15
# ^ operator
# ^ operator
shifted :: value <<| 8
# ^^^ operator