bitwise operations
This commit is contained in:
@@ -177,6 +177,240 @@ lexer_preserves_newlines_and_skips_comments :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, stream.items[1].kind, token.Kind.Identifier)
|
||||
}
|
||||
|
||||
@(test)
|
||||
lexer_recognizes_bitwise_operators_with_longest_match :: proc(t: ^testing.T) {
|
||||
source_file := source.Source{path="test.bro", text="~ & &= | |= xor xor= << <<= >> >>= <<| <<|= ^"}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
|
||||
expected := [?]token.Kind{
|
||||
.Tilde, .Ampersand, .Ampersand_Equal, .Pipe, .Pipe_Equal, .Keyword_Xor, .Xor_Equal,
|
||||
.Less_Less, .Less_Less_Equal, .Greater_Greater, .Greater_Greater_Equal,
|
||||
.Less_Less_Pipe, .Less_Less_Pipe_Equal, .Caret, .Eof,
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, len(stream.items), len(expected))
|
||||
for kind, index in expected {
|
||||
testing.expect_value(t, stream.items[index].kind, kind)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_applies_bitwise_precedence_and_preserves_capture_and_deref_pipes :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
_ = 1 + 2 << 1 & 7 xor 3 | 4 == 5 and true or false
|
||||
value i32 = 1
|
||||
pointer *i32 = &value
|
||||
_ = pointer^ & 1
|
||||
if (none | none) |captured| { _ = captured }
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
root := module.exprs[module.statements[module.functions[0].body[0]].expr]
|
||||
and_expr := module.exprs[root.left]
|
||||
equality := module.exprs[and_expr.left]
|
||||
bit_or := module.exprs[equality.left]
|
||||
bit_xor := module.exprs[bit_or.left]
|
||||
bit_and := module.exprs[bit_xor.left]
|
||||
shift := module.exprs[bit_and.left]
|
||||
addition := module.exprs[shift.left]
|
||||
deref_and := module.exprs[module.statements[module.functions[0].body[3]].expr]
|
||||
if_statement := module.statements[module.functions[0].body[4]]
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, root.kind, ast.Expr_Kind.Or)
|
||||
testing.expect_value(t, and_expr.kind, ast.Expr_Kind.And)
|
||||
testing.expect_value(t, equality.kind, ast.Expr_Kind.Eq)
|
||||
testing.expect_value(t, bit_or.kind, ast.Expr_Kind.Bit_Or)
|
||||
testing.expect_value(t, bit_xor.kind, ast.Expr_Kind.Bit_Xor)
|
||||
testing.expect_value(t, bit_and.kind, ast.Expr_Kind.Bit_And)
|
||||
testing.expect_value(t, shift.kind, ast.Expr_Kind.Shift_Left)
|
||||
testing.expect_value(t, addition.kind, ast.Expr_Kind.Add)
|
||||
testing.expect_value(t, deref_and.kind, ast.Expr_Kind.Bit_And)
|
||||
testing.expect_value(t, module.exprs[deref_and.left].kind, ast.Expr_Kind.Deref)
|
||||
testing.expect_value(t, module.exprs[if_statement.expr].kind, ast.Expr_Kind.Bit_Or)
|
||||
testing.expect_value(t, len(if_statement.captures), 1)
|
||||
}
|
||||
|
||||
@(test)
|
||||
bitwise_operations_lower_to_guarded_llvm_integer_instructions :: proc(t: ^testing.T) {
|
||||
text := `ops func(a i32, b i32, count u8) i32 {
|
||||
_ = ~a
|
||||
_ = a & b
|
||||
_ = a | b
|
||||
_ = a xor b
|
||||
_ = a << count
|
||||
_ = a >> count
|
||||
return a <<| count
|
||||
}
|
||||
uops func(a u32, count u8) u32 {
|
||||
_ = a >> count
|
||||
return a <<| count
|
||||
}
|
||||
main func() i32 {
|
||||
_ = uops(4, 1)
|
||||
return ops(1, 2, 1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
found: [7]bool
|
||||
for function in ir_module.functions {
|
||||
for instruction in function.instructions {
|
||||
#partial switch instruction.op {
|
||||
case .Bit_Not: found[0] = true
|
||||
case .Bit_And: found[1] = true
|
||||
case .Bit_Or: found[2] = true
|
||||
case .Bit_Xor: found[3] = true
|
||||
case .Shift_Left: found[4] = true
|
||||
case .Shift_Right: found[5] = true
|
||||
case .Shift_Left_Saturating: found[6] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
for present in found {
|
||||
testing.expect(t, present)
|
||||
}
|
||||
testing.expect(t, strings.contains(llvm_text, " = and i32 "))
|
||||
testing.expect(t, strings.contains(llvm_text, " = or i32 "))
|
||||
testing.expect(t, strings.contains(llvm_text, " = xor i32 "))
|
||||
testing.expect(t, strings.contains(llvm_text, " = shl i32 "))
|
||||
testing.expect(t, strings.contains(llvm_text, " = ashr i32 "))
|
||||
testing.expect(t, strings.contains(llvm_text, " = lshr i32 "))
|
||||
testing.expect(t, strings.contains(llvm_text, "@llvm.sshl.sat.i32"))
|
||||
testing.expect(t, strings.contains(llvm_text, "@llvm.ushl.sat.i32"))
|
||||
testing.expect(t, strings.contains(llvm_text, "shift_in_range"))
|
||||
testing.expect(t, strings.contains(llvm_text, "shift_trap"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
typed_bitwise_constants_fold_in_runtime_expressions :: proc(t: ^testing.T) {
|
||||
text := `main func() void {
|
||||
_ = ~u8(0)
|
||||
_ = (u8(240) & u8(204)) xor u8(15)
|
||||
_ = u8(129) << 1
|
||||
_ = i8(-4) >> 1
|
||||
_ = u8(1) <<| 8
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
for statement_id in hir_module.functions[0].body {
|
||||
statement := hir_module.statements[statement_id]
|
||||
testing.expect(t, statement.expr != hir.INVALID_EXPR)
|
||||
testing.expect_value(t, hir_module.exprs[statement.expr].kind, hir.Expr_Kind.Integer)
|
||||
}
|
||||
}
|
||||
|
||||
@(test)
|
||||
bitwise_checker_rejects_invalid_operands_and_known_overshifts :: proc(t: ^testing.T) {
|
||||
text := `D :: distinct u8
|
||||
E :: enum { one }
|
||||
main func() void {
|
||||
p *u8 = none
|
||||
d D = D(1)
|
||||
e E = .one
|
||||
signed_count i8 = 1
|
||||
_ = true & false
|
||||
_ = 1.0 | 2.0
|
||||
_ = ~p
|
||||
_ = ~d
|
||||
_ = ~e
|
||||
_ = u8(1) & i8(1)
|
||||
_ = u8(1) << signed_count
|
||||
_ = u8(1) << 8
|
||||
_ = p >> u8(1)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found_bitwise := false
|
||||
found_shift := false
|
||||
found_overshift := false
|
||||
for diagnostic in diagnostics.items {
|
||||
found_bitwise = found_bitwise || strings.contains(diagnostic.message, "bitwise") || strings.contains(diagnostic.message, "'~'")
|
||||
found_shift = found_shift || strings.contains(diagnostic.message, "shift count") || strings.contains(diagnostic.message, "shifted value")
|
||||
found_overshift = found_overshift || strings.contains(diagnostic.message, "exceeds u8 width")
|
||||
}
|
||||
testing.expect(t, found_bitwise)
|
||||
testing.expect(t, found_shift)
|
||||
testing.expect(t, found_overshift)
|
||||
}
|
||||
|
||||
@(test)
|
||||
runtime_ordinary_overshift_traps :: proc(t: ^testing.T) {
|
||||
directory := "/tmp/brolang-test-bitwise-overshift"
|
||||
main_path := "/tmp/brolang-test-bitwise-overshift/main.bro"
|
||||
output := "/tmp/brolang-test-bitwise-overshift-output"
|
||||
text := `shift func(value u8, count u8) u8 { return value << count }
|
||||
main func() i32 {
|
||||
_ = shift(1, 8)
|
||||
return 0
|
||||
}
|
||||
`
|
||||
_ = os2.remove_all(directory)
|
||||
defer _ = os2.remove_all(directory)
|
||||
defer _ = os.remove(output)
|
||||
testing.expect(t, os.make_directory(directory) == nil)
|
||||
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
|
||||
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
|
||||
state, stdout, stderr, err := os2.process_exec(os2.Process_Desc{command=[]string{output}}, context.allocator)
|
||||
defer delete(stdout)
|
||||
defer delete(stderr)
|
||||
testing.expect(t, err == nil)
|
||||
testing.expect(t, state.exit_code != 0)
|
||||
testing.expect(t, strings.contains(string(stderr), "shift count exceeds integer width"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_accepts_grouped_params_and_multiline_statements :: proc(t: ^testing.T) {
|
||||
text := `sum func(a,
|
||||
@@ -5236,6 +5470,16 @@ yield_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
|
||||
@(test)
|
||||
bitwise_example_compiles_and_runs :: proc(t: ^testing.T) {
|
||||
output := "/tmp/brolang-test-bitwise"
|
||||
defer _ = os.remove(output)
|
||||
status := compiler_core.compile_package("examples/programs/bitwise", output)
|
||||
testing.expect_value(t, status, 0)
|
||||
state := run_executable(output)
|
||||
testing.expect_value(t, state.exit_code, 42)
|
||||
}
|
||||
|
||||
@(test)
|
||||
value_loop_label_does_not_shadow_own_yield_target :: proc(t: ^testing.T) {
|
||||
text := `main func() i32 {
|
||||
|
||||
Reference in New Issue
Block a user