bitwise operations
This commit is contained in:
+31
-2
@@ -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
|
- 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
|
- 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)`
|
- 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
|
- 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`
|
- 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
|
### expressions and control flow
|
||||||
|
|
||||||
- checked integer `+ - *`, unary `-`, float-only `/`, IEEE float arithmetic, comparisons, `!`, `and`, and `or`
|
- 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
|
- 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
|
- 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
|
- `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
|
- fallible `try`, fallback `catch`, and `catch |e| { ... }` handler blocks
|
||||||
- direct `return match ...` and `yield match ...` value-control-flow operands
|
- 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
|
#### division
|
||||||
|
|
||||||
Compiler intrinsics use direct unqualified `name!(...)` syntax. The `!` marks the call as an
|
Compiler intrinsics use direct unqualified `name!(...)` syntax. The `!` marks the call as an
|
||||||
|
|||||||
@@ -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
|
- 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
|
- 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
|
- 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
|
- Directory packages with merged declarations and file-local relative imports
|
||||||
- Relative C header imports as synthetic package namespaces
|
- 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
|
- 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
@@ -97,10 +97,17 @@ Expr_Kind :: enum u8 {
|
|||||||
Comptime,
|
Comptime,
|
||||||
Negate,
|
Negate,
|
||||||
Not,
|
Not,
|
||||||
|
Bit_Not,
|
||||||
Add,
|
Add,
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
Bit_And,
|
||||||
|
Bit_Or,
|
||||||
|
Bit_Xor,
|
||||||
|
Shift_Left,
|
||||||
|
Shift_Right,
|
||||||
|
Shift_Left_Saturating,
|
||||||
Eq,
|
Eq,
|
||||||
Ne,
|
Ne,
|
||||||
Lt,
|
Lt,
|
||||||
@@ -165,6 +172,12 @@ Assignment_Op :: enum u8 {
|
|||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
|
Bit_And,
|
||||||
|
Bit_Or,
|
||||||
|
Bit_Xor,
|
||||||
|
Shift_Left,
|
||||||
|
Shift_Right,
|
||||||
|
Shift_Left_Saturating,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
@@ -184,7 +197,7 @@ Stmt :: struct {
|
|||||||
error_only: bool,
|
error_only: bool,
|
||||||
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
|
||||||
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
|
// 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,
|
assignment_op: Assignment_Op,
|
||||||
target: Expr_Id,
|
target: Expr_Id,
|
||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
|||||||
case .Call, .Array, .Struct_Literal, .Slice:
|
case .Call, .Array, .Struct_Literal, .Slice:
|
||||||
append(&expr_stack, ..expr.args)
|
append(&expr_stack, ..expr.args)
|
||||||
append(&expr_stack, expr.left)
|
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)
|
append(&expr_stack, expr.left)
|
||||||
case .Comptime:
|
case .Comptime:
|
||||||
append(&expr_stack, expr.left)
|
append(&expr_stack, expr.left)
|
||||||
@@ -380,7 +380,8 @@ block_reads_name :: proc(checker: ^Checker, statements: []ast.Stmt_Id, name: sym
|
|||||||
case .Catch:
|
case .Catch:
|
||||||
append(&expr_stack, expr.left, expr.right)
|
append(&expr_stack, expr.left, expr.right)
|
||||||
append(&statement_stack, ..expr.body)
|
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)
|
append(&expr_stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole,
|
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole,
|
||||||
.Type, .Name, .Function_Literal, .Anonymous_Struct_Type:
|
.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)
|
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 {
|
is_numeric_demand :: proc(value: types.Type, selected := target.DEFAULT) -> bool {
|
||||||
return types.is_concrete_scalar(value) && !types.is_bool(value) ||
|
return types.is_concrete_scalar(value) && !types.is_bool(value) ||
|
||||||
types.is_float(value, selected)
|
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 {
|
if expr.left != ast.INVALID_EXPR {
|
||||||
append(&stack, expr.left)
|
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)
|
append(&stack, expr.left)
|
||||||
case .Comptime:
|
case .Comptime:
|
||||||
if expr.left != ast.INVALID_EXPR {
|
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]
|
function := checker.ast_module.functions[function_id]
|
||||||
mark_block_imports_used(checker, function.body, function.file)
|
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)
|
append(&stack, expr.left, expr.right)
|
||||||
case .Invalid, .Integer, .Float, .String, .Bool, .None, .Undefined, .Inference_Hole, .Type, .Name, .Anonymous_Struct_Type:
|
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:
|
case .Not:
|
||||||
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
_ = infer_nested_expr(checker, expr.left, locals, pkg, file, demanded, local_types)
|
||||||
return types.BOOL
|
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:
|
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.left, locals, pkg, file, demanded, local_types)
|
||||||
_ = infer_nested_expr(checker, expr.right, 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)
|
_ = pop(&stack)
|
||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed, .Enum_Literal, .Cast,
|
.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)
|
last = infer_compound_expr(checker, expr, locals, pkg, file, demanded, local_types, frame.expected)
|
||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
case .Function_Literal:
|
case .Function_Literal:
|
||||||
@@ -7758,6 +7801,88 @@ build_compound_expr :: proc(
|
|||||||
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
|
kind=.Not, span=expr.span, type=types.BOOL, left=operand,
|
||||||
target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC,
|
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:
|
case .And, .Or:
|
||||||
left := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.BOOL, pkg, file)
|
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)
|
right := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.BOOL, pkg, file)
|
||||||
@@ -8096,6 +8221,14 @@ build_expr :: proc(
|
|||||||
continue
|
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{}
|
constant := Constant{}
|
||||||
_, static_name := current_static_binding(checker, expr.name)
|
_, static_name := current_static_binding(checker, expr.name)
|
||||||
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
if expr.kind != .Name || symbol.is_valid(expr.qualifier) || !static_name {
|
||||||
@@ -8109,7 +8242,8 @@ build_expr :: proc(
|
|||||||
switch expr.kind {
|
switch expr.kind {
|
||||||
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
case .String, .Array, .None, .Undefined, .Address, .Deref, .Index, .Slice,
|
||||||
.Field, .Unwrap, .Orelse, .Try, .Catch, .Struct_Literal, .Keyed,
|
.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:
|
.Enum_Literal:
|
||||||
last = build_compound_expr(
|
last = build_compound_expr(
|
||||||
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
checker, expr, locals, global_reads, calls, frame.expected, pkg, file,
|
||||||
@@ -9792,6 +9926,10 @@ build_block :: proc(
|
|||||||
rhs_expected := target_type
|
rhs_expected := target_type
|
||||||
if types.is_many_pointer(target_type, &checker.module.types) {
|
if types.is_many_pointer(target_type, &checker.module.types) {
|
||||||
rhs_expected = types.USIZE if statement.assignment_op == .Add else types.INVALID
|
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(
|
value = build_expr(
|
||||||
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
checker, statement.expr, ctx.locals^[:], ctx.global_reads, ctx.calls,
|
||||||
@@ -9824,10 +9962,42 @@ build_block :: proc(
|
|||||||
case .Sub: assignment_op = .Sub
|
case .Sub: assignment_op = .Sub
|
||||||
case .Mul: assignment_op = .Mul
|
case .Mul: assignment_op = .Mul
|
||||||
case .Div: assignment_op = .Div
|
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
|
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)
|
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(
|
id := source.add(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
statement.span,
|
statement.span,
|
||||||
|
|||||||
@@ -1270,14 +1270,18 @@ ct_eval_expr :: proc(
|
|||||||
return ct_add_value(state, Ct_Value{
|
return ct_add_value(state, Ct_Value{
|
||||||
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
|
kind=.Range, type=types.range(store, child_type), start=start, count=2, active=i64(expr.integer),
|
||||||
}), ct_flow(.Normal), true
|
}), ct_flow(.Normal), true
|
||||||
case .Negate, .Not:
|
case .Negate, .Not, .Bit_Not:
|
||||||
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
value, flow, ok := ct_eval_expr(state, expr.left, expected, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
return INVALID_CT_VALUE, flow, ok
|
||||||
}
|
}
|
||||||
return ct_eval_unary(state, expr.kind, value, expr.span)
|
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
|
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)
|
left, flow, ok := ct_eval_expr(state, expr.left, left_expected, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
if !ok || flow.kind != .Normal {
|
||||||
return INVALID_CT_VALUE, flow, ok
|
return INVALID_CT_VALUE, flow, ok
|
||||||
@@ -1287,6 +1291,16 @@ ct_eval_expr :: proc(
|
|||||||
return INVALID_CT_VALUE, right_flow, right_ok
|
return INVALID_CT_VALUE, right_flow, right_ok
|
||||||
}
|
}
|
||||||
return ct_eval_binary(state, expr.kind, left, right, expr.span)
|
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:
|
case .And, .Or:
|
||||||
left, flow, ok := ct_eval_expr(state, expr.left, types.BOOL, depth+1)
|
left, flow, ok := ct_eval_expr(state, expr.left, types.BOOL, depth+1)
|
||||||
if !ok || flow.kind != .Normal {
|
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")
|
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) {
|
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) {
|
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
|
||||||
return INVALID_CT_VALUE, ct_flow(.Normal), false
|
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
|
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 {
|
if value.kind == .Integer {
|
||||||
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
|
result, overflow := intrinsics.overflow_sub(i128(0), value.integer)
|
||||||
if overflow {
|
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!",
|
"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
|
value: i128
|
||||||
overflow := false
|
overflow := false
|
||||||
#partial switch op {
|
#partial switch op {
|
||||||
@@ -3478,7 +3568,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
|||||||
flow = ct_flow(.Normal)
|
flow = ct_flow(.Normal)
|
||||||
}
|
}
|
||||||
} else {
|
} 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 {
|
if !ok || flow.kind != .Normal {
|
||||||
return flow, ok
|
return flow, ok
|
||||||
@@ -3493,6 +3588,12 @@ ct_exec_assignment :: proc(state: ^Ct_State, statement: ast.Stmt, depth: int) ->
|
|||||||
case .Sub: op = .Sub
|
case .Sub: op = .Sub
|
||||||
case .Mul: op = .Mul
|
case .Mul: op = .Mul
|
||||||
case .Div: op = .Div
|
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
|
case: op = .Add
|
||||||
}
|
}
|
||||||
bin_flow: Ct_Flow
|
bin_flow: Ct_Flow
|
||||||
@@ -4046,6 +4147,26 @@ eval_integer_constant_in_context :: proc(
|
|||||||
return Constant{kind=.Value, value=integer}
|
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(
|
eval_comptime_statements :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
statements: []ast.Stmt_Id,
|
statements: []ast.Stmt_Id,
|
||||||
|
|||||||
+14
-1
@@ -110,6 +110,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Decay_Array_Pointer,
|
Decay_Array_Pointer,
|
||||||
Negate,
|
Negate,
|
||||||
Not,
|
Not,
|
||||||
|
Bit_Not,
|
||||||
Add,
|
Add,
|
||||||
Sub,
|
Sub,
|
||||||
Mul,
|
Mul,
|
||||||
@@ -121,6 +122,12 @@ Expr_Kind :: enum u8 {
|
|||||||
Rem,
|
Rem,
|
||||||
Mod,
|
Mod,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
Bit_And,
|
||||||
|
Bit_Or,
|
||||||
|
Bit_Xor,
|
||||||
|
Shift_Left,
|
||||||
|
Shift_Right,
|
||||||
|
Shift_Left_Saturating,
|
||||||
Eq,
|
Eq,
|
||||||
Ne,
|
Ne,
|
||||||
Lt,
|
Lt,
|
||||||
@@ -192,6 +199,12 @@ Assignment_Op :: enum u8 {
|
|||||||
Mul,
|
Mul,
|
||||||
Div,
|
Div,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
|
Bit_And,
|
||||||
|
Bit_Or,
|
||||||
|
Bit_Xor,
|
||||||
|
Shift_Left,
|
||||||
|
Shift_Right,
|
||||||
|
Shift_Left_Saturating,
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt :: struct {
|
Stmt :: struct {
|
||||||
@@ -206,7 +219,7 @@ Stmt :: struct {
|
|||||||
expr: Expr_Id,
|
expr: Expr_Id,
|
||||||
iterator_type: types.Type,
|
iterator_type: types.Type,
|
||||||
pointer_capture: bool,
|
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
|
// by computing the target address once, loading its current value, applying
|
||||||
// the operation to `expr`, and storing through the original address.
|
// the operation to `expr`, and storing through the original address.
|
||||||
assignment_op: Assignment_Op,
|
assignment_op: Assignment_Op,
|
||||||
|
|||||||
@@ -117,6 +117,13 @@ Opcode :: enum u8 {
|
|||||||
Mod_Checked,
|
Mod_Checked,
|
||||||
Pointer_Add,
|
Pointer_Add,
|
||||||
Not,
|
Not,
|
||||||
|
Bit_Not,
|
||||||
|
Bit_And,
|
||||||
|
Bit_Or,
|
||||||
|
Bit_Xor,
|
||||||
|
Shift_Left,
|
||||||
|
Shift_Right,
|
||||||
|
Shift_Left_Saturating,
|
||||||
Compare,
|
Compare,
|
||||||
Label,
|
Label,
|
||||||
Br,
|
Br,
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ keyword_kind :: proc(text: string) -> token.Kind {
|
|||||||
case "orelse": return .Keyword_Orelse
|
case "orelse": return .Keyword_Orelse
|
||||||
case "and": return .Keyword_And
|
case "and": return .Keyword_And
|
||||||
case "or": return .Keyword_Or
|
case "or": return .Keyword_Or
|
||||||
|
case "xor": return .Keyword_Xor
|
||||||
case "if": return .Keyword_If
|
case "if": return .Keyword_If
|
||||||
case "while": return .Keyword_While
|
case "while": return .Keyword_While
|
||||||
case "for": return .Keyword_For
|
case "for": return .Keyword_For
|
||||||
@@ -155,8 +156,24 @@ lex :: proc(
|
|||||||
}
|
}
|
||||||
case '<':
|
case '<':
|
||||||
start := cursor
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '<' {
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '|' {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
if cursor < len(bytes) && bytes[cursor] == '=' {
|
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
|
cursor += 1
|
||||||
append_token(&stream, source_file, .Less_Equal, start, cursor)
|
append_token(&stream, source_file, .Less_Equal, start, cursor)
|
||||||
} else {
|
} else {
|
||||||
@@ -164,8 +181,16 @@ lex :: proc(
|
|||||||
}
|
}
|
||||||
case '>':
|
case '>':
|
||||||
start := cursor
|
start := cursor
|
||||||
|
cursor += 1
|
||||||
|
if cursor < len(bytes) && bytes[cursor] == '>' {
|
||||||
cursor += 1
|
cursor += 1
|
||||||
if cursor < len(bytes) && bytes[cursor] == '=' {
|
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
|
cursor += 1
|
||||||
append_token(&stream, source_file, .Greater_Equal, start, cursor)
|
append_token(&stream, source_file, .Greater_Equal, start, cursor)
|
||||||
} else {
|
} else {
|
||||||
@@ -231,11 +256,20 @@ lex :: proc(
|
|||||||
append_token(&stream, source_file, .Slash, start, cursor)
|
append_token(&stream, source_file, .Slash, start, cursor)
|
||||||
}
|
}
|
||||||
case '&':
|
case '&':
|
||||||
append_token(&stream, source_file, .Ampersand, cursor, cursor+1)
|
start := cursor
|
||||||
cursor += 1
|
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 '^':
|
case '^':
|
||||||
append_token(&stream, source_file, .Caret, cursor, cursor+1)
|
append_token(&stream, source_file, .Caret, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
|
case '~':
|
||||||
|
append_token(&stream, source_file, .Tilde, cursor, cursor+1)
|
||||||
|
cursor += 1
|
||||||
case '?':
|
case '?':
|
||||||
append_token(&stream, source_file, .Question, cursor, cursor+1)
|
append_token(&stream, source_file, .Question, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
@@ -261,8 +295,14 @@ lex :: proc(
|
|||||||
append_token(&stream, source_file, .Comma, cursor, cursor+1)
|
append_token(&stream, source_file, .Comma, cursor, cursor+1)
|
||||||
cursor += 1
|
cursor += 1
|
||||||
case '|':
|
case '|':
|
||||||
append_token(&stream, source_file, .Pipe, cursor, cursor+1)
|
start := cursor
|
||||||
cursor += 1
|
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 '"':
|
case '"':
|
||||||
start := cursor
|
start := cursor
|
||||||
cursor += 1
|
cursor += 1
|
||||||
@@ -370,6 +410,10 @@ lex :: proc(
|
|||||||
}
|
}
|
||||||
text := source_file.text[start:cursor]
|
text := source_file.text[start:cursor]
|
||||||
kind := keyword_kind(text)
|
kind := keyword_kind(text)
|
||||||
|
if kind == .Keyword_Xor && cursor < len(bytes) && bytes[cursor] == '=' {
|
||||||
|
cursor += 1
|
||||||
|
kind = .Xor_Equal
|
||||||
|
}
|
||||||
id := symbol.INVALID
|
id := symbol.INVALID
|
||||||
if kind == .Identifier || kind == .Underscore {
|
if kind == .Identifier || kind == .Underscore {
|
||||||
id = symbol.intern(symbols, text)
|
id = symbol.intern(symbols, text)
|
||||||
|
|||||||
+122
-1
@@ -260,7 +260,8 @@ valid_value :: proc(
|
|||||||
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked,
|
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked,
|
||||||
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
|
.Div_Trunc_Checked, .Div_Floor_Checked, .Div_Exact_Checked, .Div_Ceil_Checked,
|
||||||
.Rem_Checked, .Mod_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
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
.Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
|
.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(
|
emit_instruction_stream :: proc(
|
||||||
emitter: ^Emitter,
|
emitter: ^Emitter,
|
||||||
instructions: []ir.Instruction,
|
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)
|
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)
|
write_operand(&emitter.builder, instructions, instruction.b, types.USIZE, &emitter.module.types)
|
||||||
strings.write_string(&emitter.builder, "\n")
|
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:
|
case .Call:
|
||||||
function_id := ir.as_function(instruction.target)
|
function_id := ir.as_function(instruction.target)
|
||||||
if function_id == ir.INVALID_FUNCTION {
|
if function_id == ir.INVALID_FUNCTION {
|
||||||
@@ -2488,6 +2607,8 @@ emit_declarations :: proc(emitter: ^Emitter) {
|
|||||||
strings.write_string(&emitter.builder, ".with.overflow.i")
|
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, "%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(
|
strings.write_string(
|
||||||
&emitter.builder,
|
&emitter.builder,
|
||||||
|
|||||||
@@ -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:
|
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
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Negate:
|
case .Negate, .Bit_Not:
|
||||||
stack[frame_index].stage = 5
|
stack[frame_index].stage = 5
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Add, .Sub, .Mul, .Div, .Div_Trunc, .Div_Floor, .Div_Exact, .Div_Ceil,
|
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
|
stack[frame_index].stage = 2
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Call:
|
case .Call:
|
||||||
@@ -762,8 +763,12 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if frame.stage == 5 {
|
if frame.stage == 5 {
|
||||||
|
op := ir.Opcode.Neg_Checked
|
||||||
|
if expr.kind == .Bit_Not {
|
||||||
|
op = .Bit_Not
|
||||||
|
}
|
||||||
last = append_instruction(state, ir.Instruction{
|
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,
|
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
})
|
})
|
||||||
_ = pop(&stack)
|
_ = 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 .Rem: op = .Rem_Checked
|
||||||
case .Mod: op = .Mod_Checked
|
case .Mod: op = .Mod_Checked
|
||||||
case .Pointer_Add: op = .Pointer_Add
|
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{
|
last = append_instruction(state, ir.Instruction{
|
||||||
op=op,
|
op=op,
|
||||||
@@ -916,6 +927,12 @@ lower_statements :: proc(state: ^State, statements: []hir.Stmt_Id) {
|
|||||||
case .Mul: op = .Mul_Checked
|
case .Mul: op = .Mul_Checked
|
||||||
case .Div: op = .Div_Checked
|
case .Div: op = .Div_Checked
|
||||||
case .Pointer_Add: op = .Pointer_Add
|
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{
|
value := append_instruction(state, ir.Instruction{
|
||||||
op=op, span=statement.span, type=target_type,
|
op=op, span=statement.span, type=target_type,
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ Parser :: struct {
|
|||||||
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
|
// struct literal. Nested `(`/`[`/call-arg contexts (delimiter_depth > 0) still
|
||||||
// allow struct literals.
|
// allow struct literals.
|
||||||
no_struct_literal: bool,
|
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,
|
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
|
return 6, 7, true
|
||||||
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
|
case .Equal_Equal, .Bang_Equal, .Less, .Less_Equal, .Greater, .Greater_Equal:
|
||||||
return 8, 9, true
|
return 8, 9, true
|
||||||
case .Plus, .Minus:
|
case .Ampersand, .Keyword_Xor, .Pipe:
|
||||||
return 10, 11, true
|
return 10, 11, true
|
||||||
case .Star, .Slash:
|
case .Less_Less, .Greater_Greater, .Less_Less_Pipe:
|
||||||
return 12, 13, true
|
return 12, 13, true
|
||||||
|
case .Plus, .Minus:
|
||||||
|
return 14, 15, true
|
||||||
|
case .Star, .Slash:
|
||||||
|
return 16, 17, true
|
||||||
}
|
}
|
||||||
return 0, 0, false
|
return 0, 0, false
|
||||||
}
|
}
|
||||||
@@ -1126,6 +1133,12 @@ infix_expr_kind :: proc(kind: token.Kind) -> ast.Expr_Kind {
|
|||||||
case .Less_Equal: return .Le
|
case .Less_Equal: return .Le
|
||||||
case .Greater: return .Gt
|
case .Greater: return .Gt
|
||||||
case .Greater_Equal: return .Ge
|
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 .Plus: return .Add
|
||||||
case .Minus: return .Sub
|
case .Minus: return .Sub
|
||||||
case .Star: return .Mul
|
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 .Minus_Equal: return .Sub, true
|
||||||
case .Star_Equal: return .Mul, true
|
case .Star_Equal: return .Mul, true
|
||||||
case .Slash_Equal: return .Div, 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
|
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) {
|
prefix_binding_power :: proc(kind: token.Kind) -> (right: int, ok: bool) {
|
||||||
#partial switch kind {
|
#partial switch kind {
|
||||||
case .Minus, .Ampersand, .Bang, .Dollar, .Keyword_Try:
|
case .Minus, .Ampersand, .Bang, .Tilde, .Dollar, .Keyword_Try:
|
||||||
return 20, true
|
return 20, true
|
||||||
}
|
}
|
||||||
return 0, false
|
return 0, false
|
||||||
@@ -1196,6 +1215,7 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
#partial switch operator.kind {
|
#partial switch operator.kind {
|
||||||
case .Ampersand: prefix_kind = .Address
|
case .Ampersand: prefix_kind = .Address
|
||||||
case .Bang: prefix_kind = .Not
|
case .Bang: prefix_kind = .Not
|
||||||
|
case .Tilde: prefix_kind = .Bit_Not
|
||||||
case .Dollar: prefix_kind = .Comptime
|
case .Dollar: prefix_kind = .Comptime
|
||||||
case .Keyword_Try: prefix_kind = .Try
|
case .Keyword_Try: prefix_kind = .Try
|
||||||
}
|
}
|
||||||
@@ -1352,6 +1372,9 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
})
|
})
|
||||||
continue
|
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)
|
left_power, right_power, ok := infix_binding_power(current(parser).kind)
|
||||||
if ok && (current(parser).kind == .Range || current(parser).kind == .Range_Inclusive) &&
|
if ok && (current(parser).kind == .Range || current(parser).kind == .Range_Inclusive) &&
|
||||||
parser.range_disabled > 0 {
|
parser.range_disabled > 0 {
|
||||||
@@ -2031,7 +2054,10 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
saved := parser.no_struct_literal
|
saved := parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
|
saved_capture_pipe := parser.capture_pipe
|
||||||
|
parser.capture_pipe = true
|
||||||
condition := parse_expression(parser)
|
condition := parse_expression(parser)
|
||||||
|
parser.capture_pipe = saved_capture_pipe
|
||||||
parser.no_struct_literal = saved
|
parser.no_struct_literal = saved
|
||||||
captures: [dynamic]symbol.Id
|
captures: [dynamic]symbol.Id
|
||||||
captures.allocator = parser.module.allocator
|
captures.allocator = parser.module.allocator
|
||||||
@@ -2060,7 +2086,10 @@ parse_if :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
} else {
|
} else {
|
||||||
saved = parser.no_struct_literal
|
saved = parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
|
saved_capture_pipe = parser.capture_pipe
|
||||||
|
parser.capture_pipe = true
|
||||||
guard = parse_expression(parser)
|
guard = parse_expression(parser)
|
||||||
|
parser.capture_pipe = saved_capture_pipe
|
||||||
parser.no_struct_literal = saved
|
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 {
|
} else if _, is_else := allow(parser, .Keyword_Else); !is_else {
|
||||||
saved := parser.no_struct_literal
|
saved := parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
|
saved_capture_pipe := parser.capture_pipe
|
||||||
|
parser.capture_pipe = true
|
||||||
append(&patterns, parse_expression(parser))
|
append(&patterns, parse_expression(parser))
|
||||||
for {
|
for {
|
||||||
if _, ok := allow(parser, .Comma); !ok {
|
if _, ok := allow(parser, .Comma); !ok {
|
||||||
@@ -2174,6 +2205,7 @@ parse_match_arm :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
append(&patterns, parse_expression(parser))
|
append(&patterns, parse_expression(parser))
|
||||||
}
|
}
|
||||||
|
parser.capture_pipe = saved_capture_pipe
|
||||||
parser.no_struct_literal = saved
|
parser.no_struct_literal = saved
|
||||||
if _, ok := allow(parser, .Pipe); ok {
|
if _, ok := allow(parser, .Pipe); ok {
|
||||||
if _, at_ok := allow(parser, .At); at_ok {
|
if _, at_ok := allow(parser, .At); at_ok {
|
||||||
@@ -2324,7 +2356,10 @@ parse_for :: proc(parser: ^Parser) -> ast.Stmt_Id {
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
saved := parser.no_struct_literal
|
saved := parser.no_struct_literal
|
||||||
parser.no_struct_literal = true
|
parser.no_struct_literal = true
|
||||||
|
saved_capture_pipe := parser.capture_pipe
|
||||||
|
parser.capture_pipe = true
|
||||||
iterable := parse_expression(parser)
|
iterable := parse_expression(parser)
|
||||||
|
parser.capture_pipe = saved_capture_pipe
|
||||||
parser.no_struct_literal = saved
|
parser.no_struct_literal = saved
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ Kind :: enum u8 {
|
|||||||
Bang_Equal,
|
Bang_Equal,
|
||||||
Less,
|
Less,
|
||||||
Less_Equal,
|
Less_Equal,
|
||||||
|
Less_Less,
|
||||||
|
Less_Less_Equal,
|
||||||
|
Less_Less_Pipe,
|
||||||
|
Less_Less_Pipe_Equal,
|
||||||
Greater,
|
Greater,
|
||||||
Greater_Equal,
|
Greater_Equal,
|
||||||
|
Greater_Greater,
|
||||||
|
Greater_Greater_Equal,
|
||||||
Plus,
|
Plus,
|
||||||
Minus,
|
Minus,
|
||||||
Slash,
|
Slash,
|
||||||
@@ -39,7 +45,10 @@ Kind :: enum u8 {
|
|||||||
Dollar,
|
Dollar,
|
||||||
Star,
|
Star,
|
||||||
Ampersand,
|
Ampersand,
|
||||||
|
Ampersand_Equal,
|
||||||
Caret,
|
Caret,
|
||||||
|
Tilde,
|
||||||
|
Xor_Equal,
|
||||||
Question,
|
Question,
|
||||||
Semicolon,
|
Semicolon,
|
||||||
Left_Bracket,
|
Left_Bracket,
|
||||||
@@ -50,6 +59,7 @@ Kind :: enum u8 {
|
|||||||
Right_Brace,
|
Right_Brace,
|
||||||
Comma,
|
Comma,
|
||||||
Pipe,
|
Pipe,
|
||||||
|
Pipe_Equal,
|
||||||
Keyword_Test,
|
Keyword_Test,
|
||||||
Keyword_Func,
|
Keyword_Func,
|
||||||
Keyword_C_Func,
|
Keyword_C_Func,
|
||||||
@@ -71,6 +81,7 @@ Kind :: enum u8 {
|
|||||||
Keyword_Orelse,
|
Keyword_Orelse,
|
||||||
Keyword_And,
|
Keyword_And,
|
||||||
Keyword_Or,
|
Keyword_Or,
|
||||||
|
Keyword_Xor,
|
||||||
Keyword_If,
|
Keyword_If,
|
||||||
Keyword_While,
|
Keyword_While,
|
||||||
Keyword_For,
|
Keyword_For,
|
||||||
|
|||||||
@@ -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)
|
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)
|
@(test)
|
||||||
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
|
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
|
||||||
text := `sum func(a,
|
text := `sum func(a,
|
||||||
@@ -5236,6 +5470,16 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
|
|||||||
testing.expect_value(t, state.exit_code, 42)
|
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)
|
@(test)
|
||||||
value_loop_label_does_not_shadow_own_yield_target :: proc(t: ^testing.T) {
|
value_loop_label_does_not_shadow_own_yield_target :: proc(t: ^testing.T) {
|
||||||
text := `main func() i32 {
|
text := `main func() i32 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -7,10 +7,12 @@ const PREC = {
|
|||||||
OR: 3,
|
OR: 3,
|
||||||
AND: 4,
|
AND: 4,
|
||||||
COMPARE: 5,
|
COMPARE: 5,
|
||||||
SUM: 6,
|
BITWISE: 6,
|
||||||
PRODUCT: 7,
|
SHIFT: 7,
|
||||||
PREFIX: 8,
|
SUM: 8,
|
||||||
POSTFIX: 9,
|
PRODUCT: 9,
|
||||||
|
PREFIX: 10,
|
||||||
|
POSTFIX: 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = grammar({
|
module.exports = grammar({
|
||||||
@@ -33,6 +35,9 @@ module.exports = grammar({
|
|||||||
[$.block, $.tuple_literal],
|
[$.block, $.tuple_literal],
|
||||||
[$.field_initializer, $.expression],
|
[$.field_initializer, $.expression],
|
||||||
[$.tuple_literal],
|
[$.tuple_literal],
|
||||||
|
[$.capture_list, $.expression],
|
||||||
|
[$.for_statement, $.expression],
|
||||||
|
[$.match_capture, $.expression],
|
||||||
],
|
],
|
||||||
|
|
||||||
rules: {
|
rules: {
|
||||||
@@ -278,7 +283,7 @@ module.exports = grammar({
|
|||||||
|
|
||||||
assignment_statement: $ => seq(
|
assignment_statement: $ => seq(
|
||||||
field('left', $.expression),
|
field('left', $.expression),
|
||||||
field('operator', choice('=', '+=', '-=', '*=', '/=')),
|
field('operator', choice('=', '+=', '-=', '*=', '/=', '&=', '|=', 'xor=', '<<=', '>>=', '<<|=')),
|
||||||
repeat($._newline),
|
repeat($._newline),
|
||||||
field('right', $._value),
|
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.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.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.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.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))),
|
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(
|
unary_expression: $ => prec(PREC.PREFIX, seq(
|
||||||
field('operator', choice('-', '&', '!', '$', 'try')),
|
field('operator', choice('-', '&', '!', '~', '$', 'try')),
|
||||||
repeat($._newline),
|
repeat($._newline),
|
||||||
field('operand', $.expression),
|
field('operand', $.expression),
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -91,6 +91,12 @@
|
|||||||
"-="
|
"-="
|
||||||
"*="
|
"*="
|
||||||
"/="
|
"/="
|
||||||
|
"&="
|
||||||
|
"|="
|
||||||
|
"xor="
|
||||||
|
"<<="
|
||||||
|
">>="
|
||||||
|
"<<|="
|
||||||
"=="
|
"=="
|
||||||
"!="
|
"!="
|
||||||
"<"
|
"<"
|
||||||
@@ -101,6 +107,11 @@
|
|||||||
"-"
|
"-"
|
||||||
"*"
|
"*"
|
||||||
"/"
|
"/"
|
||||||
|
"~"
|
||||||
|
"xor"
|
||||||
|
"<<"
|
||||||
|
">>"
|
||||||
|
"<<|"
|
||||||
"!"
|
"!"
|
||||||
"&"
|
"&"
|
||||||
"@"
|
"@"
|
||||||
|
|||||||
@@ -1830,6 +1830,30 @@
|
|||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "/="
|
"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",
|
"type": "PREC_LEFT",
|
||||||
"value": 6,
|
"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": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3354,7 +3484,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 7,
|
"value": 9,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3466,7 +3596,7 @@
|
|||||||
},
|
},
|
||||||
"unary_expression": {
|
"unary_expression": {
|
||||||
"type": "PREC",
|
"type": "PREC",
|
||||||
"value": 8,
|
"value": 10,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3488,6 +3618,10 @@
|
|||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "!"
|
"value": "!"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "STRING",
|
||||||
|
"value": "~"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "STRING",
|
"type": "STRING",
|
||||||
"value": "$"
|
"value": "$"
|
||||||
@@ -3519,7 +3653,7 @@
|
|||||||
},
|
},
|
||||||
"field_expression": {
|
"field_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3557,7 +3691,7 @@
|
|||||||
},
|
},
|
||||||
"intrinsic_call_expression": {
|
"intrinsic_call_expression": {
|
||||||
"type": "PREC",
|
"type": "PREC",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3590,7 +3724,7 @@
|
|||||||
},
|
},
|
||||||
"call_expression": {
|
"call_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3615,7 +3749,7 @@
|
|||||||
},
|
},
|
||||||
"argument_list": {
|
"argument_list": {
|
||||||
"type": "PREC",
|
"type": "PREC",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3721,7 +3855,7 @@
|
|||||||
},
|
},
|
||||||
"index_expression": {
|
"index_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3768,7 +3902,7 @@
|
|||||||
},
|
},
|
||||||
"slice_expression": {
|
"slice_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3843,7 +3977,7 @@
|
|||||||
},
|
},
|
||||||
"postfix_expression": {
|
"postfix_expression": {
|
||||||
"type": "PREC_LEFT",
|
"type": "PREC_LEFT",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -3877,7 +4011,7 @@
|
|||||||
},
|
},
|
||||||
"struct_literal": {
|
"struct_literal": {
|
||||||
"type": "PREC",
|
"type": "PREC",
|
||||||
"value": 9,
|
"value": 11,
|
||||||
"content": {
|
"content": {
|
||||||
"type": "SEQ",
|
"type": "SEQ",
|
||||||
"members": [
|
"members": [
|
||||||
@@ -4803,6 +4937,18 @@
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
"tuple_literal"
|
"tuple_literal"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"capture_list",
|
||||||
|
"expression"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"for_statement",
|
||||||
|
"expression"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"match_capture",
|
||||||
|
"expression"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"precedences": [],
|
"precedences": [],
|
||||||
|
|||||||
@@ -111,6 +111,10 @@
|
|||||||
"multiple": false,
|
"multiple": false,
|
||||||
"required": true,
|
"required": true,
|
||||||
"types": [
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "&=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "*=",
|
"type": "*=",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -127,9 +131,29 @@
|
|||||||
"type": "/=",
|
"type": "/=",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "<<=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<<|=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "=",
|
"type": "=",
|
||||||
"named": false
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": ">>=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "xor=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "|=",
|
||||||
|
"named": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -191,6 +215,10 @@
|
|||||||
"type": "!=",
|
"type": "!=",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "&",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "*",
|
"type": "*",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -219,6 +247,14 @@
|
|||||||
"type": "<",
|
"type": "<",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "<<",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<<|",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "<=",
|
"type": "<=",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -235,6 +271,10 @@
|
|||||||
"type": ">=",
|
"type": ">=",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": ">>",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "catch",
|
"type": "catch",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -242,6 +282,14 @@
|
|||||||
{
|
{
|
||||||
"type": "orelse",
|
"type": "orelse",
|
||||||
"named": false
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "xor",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "|",
|
||||||
|
"named": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2187,6 +2235,10 @@
|
|||||||
{
|
{
|
||||||
"type": "try",
|
"type": "try",
|
||||||
"named": false
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "~",
|
||||||
|
"named": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2422,6 +2474,10 @@
|
|||||||
"type": "&",
|
"type": "&",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "&=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "(",
|
"type": "(",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -2498,6 +2554,22 @@
|
|||||||
"type": "<",
|
"type": "<",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "<<",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<<=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<<|",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "<<|=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "<=",
|
"type": "<=",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -2518,6 +2590,14 @@
|
|||||||
"type": ">=",
|
"type": ">=",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": ">>",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": ">>=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "?",
|
"type": "?",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -2839,6 +2919,14 @@
|
|||||||
"type": "while",
|
"type": "while",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "xor",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "xor=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "yield",
|
"type": "yield",
|
||||||
"named": false
|
"named": false
|
||||||
@@ -2851,8 +2939,16 @@
|
|||||||
"type": "|",
|
"type": "|",
|
||||||
"named": false
|
"named": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "|=",
|
||||||
|
"named": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "}",
|
"type": "}",
|
||||||
"named": false
|
"named": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "~",
|
||||||
|
"named": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
+218100
-140034
File diff suppressed because it is too large
Load Diff
@@ -509,3 +509,137 @@ value uint :: 255
|
|||||||
(builtin_type))
|
(builtin_type))
|
||||||
(expression
|
(expression
|
||||||
(integer))))
|
(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
|
||||||
Reference in New Issue
Block a user