Browse Source

add bit_field parsing to `core:odin/parser`

Also adds it to the core type thingy like it is in the compiler.
Laytan Laats 1 year ago
parent
commit
af6d2480fa

+ 3 - 2
base/runtime/core.odin

@@ -597,8 +597,9 @@ type_info_core :: proc "contextless" (info: ^Type_Info) -> ^Type_Info {
 	base := info
 	base := info
 	loop: for {
 	loop: for {
 		#partial switch i in base.variant {
 		#partial switch i in base.variant {
-		case Type_Info_Named:  base = i.base
-		case Type_Info_Enum:   base = i.base
+		case Type_Info_Named:     base = i.base
+		case Type_Info_Enum:      base = i.base
+		case Type_Info_Bit_Field: base = i.backing_type
 		case: break loop
 		case: break loop
 		}
 		}
 	}
 	}

+ 21 - 1
core/odin/ast/ast.odin

@@ -617,7 +617,7 @@ field_flag_strings := [Field_Flag]string{
 	.Any_Int            = "#any_int",
 	.Any_Int            = "#any_int",
 	.Subtype            = "#subtype",
 	.Subtype            = "#subtype",
 	.By_Ptr             = "#by_ptr",
 	.By_Ptr             = "#by_ptr",
-	.No_Broadcast       ="#no_broadcast",
+	.No_Broadcast       = "#no_broadcast",
 
 
 	.Results            = "results",
 	.Results            = "results",
 	.Tags               = "field tag",
 	.Tags               = "field tag",
@@ -842,6 +842,23 @@ Matrix_Type :: struct {
 	elem:         ^Expr,
 	elem:         ^Expr,
 }
 }
 
 
+Bit_Field_Type :: struct {
+	using node:   Expr,
+	tok_pos:      tokenizer.Pos,
+	backing_type: ^Expr,
+	open:         tokenizer.Pos,
+	fields:       []^Bit_Field_Field,
+	close:        tokenizer.Pos,
+}
+
+Bit_Field_Field :: struct {
+	using node: Node,
+	docs:       ^Comment_Group,
+	name:       ^Expr,
+	type:       ^Expr,
+	bit_size:   ^Expr,
+	comments:   ^Comment_Group,
+}
 
 
 Any_Node :: union {
 Any_Node :: union {
 	^Package,
 	^Package,
@@ -898,6 +915,7 @@ Any_Node :: union {
 	^Map_Type,
 	^Map_Type,
 	^Relative_Type,
 	^Relative_Type,
 	^Matrix_Type,
 	^Matrix_Type,
+	^Bit_Field_Type,
 
 
 	^Bad_Stmt,
 	^Bad_Stmt,
 	^Empty_Stmt,
 	^Empty_Stmt,
@@ -928,6 +946,7 @@ Any_Node :: union {
 	^Attribute,
 	^Attribute,
 	^Field,
 	^Field,
 	^Field_List,
 	^Field_List,
+	^Bit_Field_Field,
 }
 }
 
 
 
 
@@ -982,6 +1001,7 @@ Any_Expr :: union {
 	^Map_Type,
 	^Map_Type,
 	^Relative_Type,
 	^Relative_Type,
 	^Matrix_Type,
 	^Matrix_Type,
+	^Bit_Field_Type,
 }
 }
 
 
 
 

+ 7 - 0
core/odin/ast/clone.odin

@@ -336,6 +336,13 @@ clone_node :: proc(node: ^Node) -> ^Node {
 		case ^Relative_Type:
 		case ^Relative_Type:
 			r.tag = clone(r.tag)
 			r.tag = clone(r.tag)
 			r.type = clone(r.type)
 			r.type = clone(r.type)
+		case ^Bit_Field_Type:
+			r.backing_type = clone(r.backing_type)
+			r.fields       = auto_cast clone(r.fields)
+		case ^Bit_Field_Field:
+			r.name     = clone(r.name)
+			r.type     = clone(r.type)
+			r.bit_size = clone(r.bit_size)
 		case:
 		case:
 			fmt.panicf("Unhandled node kind: %v", r)
 			fmt.panicf("Unhandled node kind: %v", r)
 		}
 		}

+ 9 - 1
core/odin/ast/walk.odin

@@ -414,7 +414,15 @@ walk :: proc(v: ^Visitor, node: ^Node) {
 		walk(v, n.row_count)
 		walk(v, n.row_count)
 		walk(v, n.column_count)
 		walk(v, n.column_count)
 		walk(v, n.elem)
 		walk(v, n.elem)
-
+	case ^Bit_Field_Type:
+		walk(v, n.backing_type)
+		for f in n.fields {
+			walk(v, f)
+		}
+	case ^Bit_Field_Field:
+		walk(v, n.name)
+		walk(v, n.type)
+		walk(v, n.bit_size)
 	case:
 	case:
 		fmt.panicf("ast.walk: unexpected node type %T", n)
 		fmt.panicf("ast.walk: unexpected node type %T", n)
 	}
 	}

+ 45 - 2
core/odin/parser/parser.odin

@@ -531,7 +531,7 @@ is_semicolon_optional_for_node :: proc(p: ^Parser, node: ^ast.Node) -> bool {
 		return is_semicolon_optional_for_node(p, n.type)
 		return is_semicolon_optional_for_node(p, n.type)
 	case ^ast.Pointer_Type:
 	case ^ast.Pointer_Type:
 		return is_semicolon_optional_for_node(p, n.elem)
 		return is_semicolon_optional_for_node(p, n.elem)
-	case ^ast.Struct_Type, ^ast.Union_Type, ^ast.Enum_Type:
+	case ^ast.Struct_Type, ^ast.Union_Type, ^ast.Enum_Type, ^ast.Bit_Set_Type, ^ast.Bit_Field_Type:
 		// Require semicolon within a procedure body
 		// Require semicolon within a procedure body
 		return p.curr_proc == nil
 		return p.curr_proc == nil
 	case ^ast.Proc_Lit:
 	case ^ast.Proc_Lit:
@@ -2790,6 +2790,48 @@ parse_operand :: proc(p: ^Parser, lhs: bool) -> ^ast.Expr {
 		mt.column_count = column_count
 		mt.column_count = column_count
 		mt.elem = elem
 		mt.elem = elem
 		return mt
 		return mt
+	
+	case .Bit_Field:
+		tok := expect_token(p, .Bit_Field)
+
+		backing_type := parse_type_or_ident(p)
+		if backing_type == nil {
+			token := advance_token(p)
+			error(p, token.pos, "Expected a backing type for a 'bit_field'")
+		}
+
+		skip_possible_newline_for_literal(p)
+		open := expect_token_after(p, .Open_Brace, "bit_field")
+
+		fields: [dynamic]^ast.Bit_Field_Field
+		for p.curr_tok.kind != .Close_Brace && p.curr_tok.kind != .EOF {
+			name := parse_ident(p)
+			expect_token(p, .Colon)
+			type := parse_type(p)
+			expect_token(p, .Or)
+			bit_size := parse_expr(p, true)
+
+			field := ast.new(ast.Bit_Field_Field, name.pos, bit_size)
+
+			field.name     = name
+			field.type     = type
+			field.bit_size = bit_size
+
+			append(&fields, field)
+
+			allow_token(p, .Comma) or_break
+		}
+
+		close := expect_closing_brace_of_field_list(p)
+
+		bf := ast.new(ast.Bit_Field_Type, tok.pos, close.pos)
+
+		bf.tok_pos      = tok.pos
+		bf.backing_type = backing_type
+		bf.open         = open.pos
+		bf.fields       = fields[:]
+		bf.close        = close.pos
+		return bf
 
 
 	case .Asm:
 	case .Asm:
 		tok := expect_token(p, .Asm)
 		tok := expect_token(p, .Asm)
@@ -2897,7 +2939,8 @@ is_literal_type :: proc(expr: ^ast.Expr) -> bool {
 		^ast.Map_Type,
 		^ast.Map_Type,
 		^ast.Bit_Set_Type,
 		^ast.Bit_Set_Type,
 		^ast.Matrix_Type,
 		^ast.Matrix_Type,
-		^ast.Call_Expr:
+		^ast.Call_Expr,
+		^ast.Bit_Field_Type:
 		return true
 		return true
 	}
 	}
 	return false
 	return false

+ 51 - 1
core/odin/printer/visit.odin

@@ -445,7 +445,7 @@ visit_decl :: proc(p: ^Printer, decl: ^ast.Decl, called_in_stmt := false) {
 
 
 		for value in v.values {
 		for value in v.values {
 			#partial switch a in value.derived {
 			#partial switch a in value.derived {
-			case ^ast.Union_Type, ^ast.Enum_Type, ^ast.Struct_Type:
+			case ^ast.Union_Type, ^ast.Enum_Type, ^ast.Struct_Type, ^ast.Bit_Field_Type:
 				add_semicolon = false || called_in_stmt
 				add_semicolon = false || called_in_stmt
 			case ^ast.Proc_Lit:
 			case ^ast.Proc_Lit:
 				add_semicolon = false
 				add_semicolon = false
@@ -488,6 +488,37 @@ visit_exprs :: proc(p: ^Printer, list: []^ast.Expr, options := List_Options{}) {
 	}
 	}
 }
 }
 
 
+@(private)
+visit_bit_field_fields :: proc(p: ^Printer, list: []^ast.Bit_Field_Field, options := List_Options{}) {
+	if len(list) == 0 {
+		return
+	}
+
+	// we have to newline the expressions to respect the source
+	for v, i in list {
+		// Don't move the first expression, it looks bad
+		if i != 0 && .Enforce_Newline in options {
+			newline_position(p, 1)
+		} else if i != 0 {
+			move_line_limit(p, v.pos, 1)
+		}
+
+		visit_expr(p, v.name, options)
+		push_generic_token(p, .Colon, 0)
+		visit_expr(p, v.type, options)
+		push_generic_token(p, .Or, 1)
+		visit_expr(p, v.bit_size, options)
+
+		if (i != len(list) - 1 || .Trailing in options) && .Add_Comma in options {
+			push_generic_token(p, .Comma, 0)
+		}
+	}
+
+	if len(list) > 1 && .Enforce_Newline in options {
+		newline_position(p, 1)
+	}
+}
+
 @(private)
 @(private)
 visit_attributes :: proc(p: ^Printer, attributes: [dynamic]^ast.Attribute) {
 visit_attributes :: proc(p: ^Printer, attributes: [dynamic]^ast.Attribute) {
 	if len(attributes) == 0 {
 	if len(attributes) == 0 {
@@ -1293,6 +1324,25 @@ visit_expr :: proc(p: ^Printer, expr: ^ast.Expr, options := List_Options{}) {
 		visit_expr(p, v.column_count)
 		visit_expr(p, v.column_count)
 		push_generic_token(p, .Close_Bracket, 0)
 		push_generic_token(p, .Close_Bracket, 0)
 		visit_expr(p, v.elem)
 		visit_expr(p, v.elem)
+	case ^ast.Bit_Field_Type:
+		push_generic_token(p, .Bit_Field, 1)
+
+		visit_expr(p, v.backing_type)
+
+		if len(v.fields) == 0 || v.pos.line == v.close.line {
+			push_generic_token(p, .Open_Brace, 1)
+			visit_bit_field_fields(p, v.fields, {.Add_Comma})
+			push_generic_token(p, .Close_Brace, 0)
+		} else {
+			visit_begin_brace(p, v.pos, .Generic, len(v.fields))
+			newline_position(p, 1)
+			set_source_position(p, v.fields[0].pos)
+			visit_bit_field_fields(p, v.fields, {.Add_Comma, .Trailing, .Enforce_Newline})
+			set_source_position(p, v.close)
+			visit_end_brace(p, v.close)
+		}
+
+		set_source_position(p, v.close)
 	case:
 	case:
 		panic(fmt.aprint(expr.derived))
 		panic(fmt.aprint(expr.derived))
 	}
 	}

+ 47 - 3
tests/core/odin/test_parser.odin

@@ -1,9 +1,12 @@
 package test_core_odin_parser
 package test_core_odin_parser
 
 
-import "core:testing"
 import "core:fmt"
 import "core:fmt"
-import "core:os"
+import "core:odin/ast"
 import "core:odin/parser"
 import "core:odin/parser"
+import "core:odin/printer"
+import "core:os"
+import "core:strings"
+import "core:testing"
 
 
 
 
 TEST_count := 0
 TEST_count := 0
@@ -30,6 +33,7 @@ when ODIN_TEST {
 main :: proc() {
 main :: proc() {
 	t := testing.T{}
 	t := testing.T{}
 	test_parse_demo(&t)
 	test_parse_demo(&t)
+	test_parse_bitfield(&t)
 
 
 	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
 	fmt.printf("%v/%v tests successful.\n", TEST_count - TEST_fail, TEST_count)
 	if TEST_fail > 0 {
 	if TEST_fail > 0 {
@@ -47,4 +51,44 @@ test_parse_demo :: proc(t: ^testing.T) {
 	for key, value in pkg.files {
 	for key, value in pkg.files {
 		expect(t, value.syntax_error_count == 0, fmt.tprintf("%v should contain zero errors", key))
 		expect(t, value.syntax_error_count == 0, fmt.tprintf("%v should contain zero errors", key))
 	}
 	}
-}
+}
+
+@test
+test_parse_bitfield :: proc(t: ^testing.T) {
+	file := ast.File{
+		fullpath = "test.odin",
+		src = `
+package main
+
+Foo :: bit_field uint {}
+
+Foo :: bit_field uint {hello: bool | 1}
+
+Foo :: bit_field uint {
+	hello: bool | 1,
+	hello: bool | 5,
+}
+
+// Hellope 1.
+Foo :: bit_field uint {
+	// Hellope 2.
+	hello: bool | 1,
+	hello: bool | 5, // Hellope 3.
+}
+		`,
+	}
+
+	p := parser.default_parser()
+	ok := parser.parse_file(&p, &file)
+	expect(t, ok == true, "bad parse")
+
+	cfg := printer.default_style
+	cfg.newline_style = .LF
+	print := printer.make_printer(cfg)
+	out := printer.print(&print, &file)
+
+	tsrc := strings.trim_space(file.src)
+	tout := strings.trim_space(out)
+
+	expect(t, tsrc == tout, fmt.tprintf("\n%s\n!=\n%s", tsrc, tout))
+}