allow keywords as values in enums (and tagged unions)
This commit is contained in:
+28
@@ -39,6 +39,34 @@ roadmap and milestone history.
|
|||||||
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
- native sum composition with `A | B` for unbacked enums and tagged unions, using program-global `u16` variant ids
|
||||||
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
|
- fallible channel types `T ! E`, where `E` is a native enum/tagged union or supported sum composition
|
||||||
|
|
||||||
|
#### keyword member names
|
||||||
|
|
||||||
|
Reserved keywords are valid native enum members and tagged-union variants when used in an
|
||||||
|
unambiguous member context:
|
||||||
|
|
||||||
|
```bro
|
||||||
|
TokenKind :: enum {
|
||||||
|
if
|
||||||
|
else
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Token :: union(TokenKind) {
|
||||||
|
if i32
|
||||||
|
else void
|
||||||
|
return i32
|
||||||
|
}
|
||||||
|
|
||||||
|
conditional func() TokenKind { return TokenKind.if }
|
||||||
|
fallback func() TokenKind { return .else }
|
||||||
|
token func() Token { return Token{ if = 1 } }
|
||||||
|
```
|
||||||
|
|
||||||
|
Keyword variants also work with field access and `.variant` match patterns; `.else:` remains
|
||||||
|
distinct from the `else:` catch-all arm. No escaping syntax is required. Keywords remain reserved
|
||||||
|
for ordinary declarations, struct fields, untagged-union fields, and anonymous payload-struct
|
||||||
|
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`
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ lex :: proc(
|
|||||||
) -> token.Stream {
|
) -> token.Stream {
|
||||||
stream: token.Stream
|
stream: token.Stream
|
||||||
stream.items.allocator = allocator
|
stream.items.allocator = allocator
|
||||||
|
stream.symbols = symbols
|
||||||
bytes := transmute([]byte)source_file.text
|
bytes := transmute([]byte)source_file.text
|
||||||
cursor := 0
|
cursor := 0
|
||||||
|
|
||||||
|
|||||||
+34
-17
@@ -86,6 +86,18 @@ allow :: proc(parser: ^Parser, kind: token.Kind) -> (token.Token, bool) {
|
|||||||
return current(parser), false
|
return current(parser), false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parse_member_name :: proc(parser: ^Parser, allow_keyword := true) -> (token.Token, bool) {
|
||||||
|
name := current(parser)
|
||||||
|
if name.kind != .Identifier && (!allow_keyword || !token.is_keyword(name.kind)) {
|
||||||
|
return name, false
|
||||||
|
}
|
||||||
|
advance(parser)
|
||||||
|
if name.kind != .Identifier {
|
||||||
|
name.symbol = symbol.intern(parser.tokens.symbols, token_text(parser, name))
|
||||||
|
}
|
||||||
|
return name, true
|
||||||
|
}
|
||||||
|
|
||||||
skip_newlines :: proc(parser: ^Parser) {
|
skip_newlines :: proc(parser: ^Parser) {
|
||||||
for current(parser).kind == .Newline {
|
for current(parser).kind == .Newline {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
@@ -539,12 +551,11 @@ parse_keyed_initializers :: proc(
|
|||||||
args.allocator = parser.module.allocator
|
args.allocator = parser.module.allocator
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||||
field := current(parser)
|
field, field_ok := parse_member_name(parser)
|
||||||
if field.kind != .Identifier {
|
if !field_ok {
|
||||||
source.add(parser.diagnostics, field.span, "expected a keyed struct field initializer")
|
source.add(parser.diagnostics, field.span, "expected a keyed struct field initializer")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
advance(parser)
|
|
||||||
// A bare key (`T{ variant }`, no `= value`) constructs a void-payload union
|
// A bare key (`T{ variant }`, no `= value`) constructs a void-payload union
|
||||||
// variant; the checker validates that the field actually has a void type.
|
// variant; the checker validates that the field actually has a void type.
|
||||||
value := ast.INVALID_EXPR
|
value := ast.INVALID_EXPR
|
||||||
@@ -804,11 +815,10 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
return parse_array_literal(parser, nesting)
|
return parse_array_literal(parser, nesting)
|
||||||
case .Dot:
|
case .Dot:
|
||||||
start := advance(parser)
|
start := advance(parser)
|
||||||
member := current(parser)
|
member, member_ok := parse_member_name(parser)
|
||||||
if member.kind != .Identifier {
|
if !member_ok {
|
||||||
return invalid_expr(parser, member.span, "expected an enum member after '.'")
|
return invalid_expr(parser, member.span, "expected an enum member after '.'")
|
||||||
}
|
}
|
||||||
advance(parser)
|
|
||||||
payload := ast.INVALID_EXPR
|
payload := ast.INVALID_EXPR
|
||||||
end := member.span
|
end := member.span
|
||||||
if left_brace, ok := allow(parser, .Left_Brace); ok {
|
if left_brace, ok := allow(parser, .Left_Brace); ok {
|
||||||
@@ -868,11 +878,12 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
|
|||||||
name := first
|
name := first
|
||||||
qualifier := symbol.INVALID
|
qualifier := symbol.INVALID
|
||||||
if _, ok := allow(parser, .Dot); ok {
|
if _, ok := allow(parser, .Dot); ok {
|
||||||
if current(parser).kind != .Identifier {
|
member, member_ok := parse_member_name(parser)
|
||||||
|
if !member_ok {
|
||||||
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
|
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
|
||||||
}
|
}
|
||||||
qualifier = first.symbol
|
qualifier = first.symbol
|
||||||
name = advance(parser)
|
name = member
|
||||||
}
|
}
|
||||||
if current(parser).kind == .Left_Paren {
|
if current(parser).kind == .Left_Paren {
|
||||||
call := parse_call(parser, qualifier, first, name, nesting)
|
call := parse_call(parser, qualifier, first, name, nesting)
|
||||||
@@ -1074,12 +1085,11 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
|
|||||||
}
|
}
|
||||||
if current(parser).kind == .Dot {
|
if current(parser).kind == .Dot {
|
||||||
advance(parser)
|
advance(parser)
|
||||||
field := current(parser)
|
field, field_ok := parse_member_name(parser)
|
||||||
if field.kind != .Identifier {
|
if !field_ok {
|
||||||
left = invalid_expr(parser, field.span, "expected a field name after '.'")
|
left = invalid_expr(parser, field.span, "expected a field name after '.'")
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
advance(parser)
|
|
||||||
left_expr := parser.module.exprs[left]
|
left_expr := parser.module.exprs[left]
|
||||||
left = add_expr(parser, ast.Expr{
|
left = add_expr(parser, ast.Expr{
|
||||||
kind=.Field,
|
kind=.Field,
|
||||||
@@ -2252,6 +2262,7 @@ parse_record_body :: proc(
|
|||||||
fields: ^[dynamic]types.Field,
|
fields: ^[dynamic]types.Field,
|
||||||
expected_open: string,
|
expected_open: string,
|
||||||
allow_anonymous_struct_payload := false,
|
allow_anonymous_struct_payload := false,
|
||||||
|
allow_keyword_names := false,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if _, ok := allow(parser, .Left_Brace); !ok {
|
if _, ok := allow(parser, .Left_Brace); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, expected_open)
|
source.add(parser.diagnostics, current(parser).span, expected_open)
|
||||||
@@ -2259,7 +2270,8 @@ parse_record_body :: proc(
|
|||||||
}
|
}
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||||
if current(parser).kind != .Identifier {
|
field_name, field_ok := parse_member_name(parser, allow_keyword_names)
|
||||||
|
if !field_ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
|
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
|
||||||
for current(parser).kind != .Newline &&
|
for current(parser).kind != .Newline &&
|
||||||
current(parser).kind != .Right_Brace &&
|
current(parser).kind != .Right_Brace &&
|
||||||
@@ -2269,7 +2281,6 @@ parse_record_body :: proc(
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
field_name := advance(parser)
|
|
||||||
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
|
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
|
||||||
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
|
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
|
||||||
if _, ok := allow(parser, .Comma); ok {
|
if _, ok := allow(parser, .Comma); ok {
|
||||||
@@ -2317,7 +2328,7 @@ parse_inline_union_type :: proc(parser: ^Parser) -> types.Type {
|
|||||||
fields: [dynamic]types.Field
|
fields: [dynamic]types.Field
|
||||||
fields.allocator = parser.module.allocator
|
fields.allocator = parser.module.allocator
|
||||||
defer delete(fields)
|
defer delete(fields)
|
||||||
if !parse_record_body(parser, &fields, "expected '{' after inline union error type", true) || !valid {
|
if !parse_record_body(parser, &fields, "expected '{' after inline union error type", true, true) || !valid {
|
||||||
return types.INVALID
|
return types.INVALID
|
||||||
}
|
}
|
||||||
tag := synthesize_union_tag(parser, fields[:])
|
tag := synthesize_union_tag(parser, fields[:])
|
||||||
@@ -2372,7 +2383,13 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout: bool, is_unio
|
|||||||
fields.allocator = parser.module.allocator
|
fields.allocator = parser.module.allocator
|
||||||
defer delete(fields)
|
defer delete(fields)
|
||||||
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
|
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
|
||||||
_ = parse_record_body(parser, &fields, "expected '{' after struct fields", allow_anonymous_struct_payload)
|
_ = parse_record_body(
|
||||||
|
parser,
|
||||||
|
&fields,
|
||||||
|
"expected '{' after struct fields",
|
||||||
|
allow_anonymous_struct_payload,
|
||||||
|
allow_anonymous_struct_payload,
|
||||||
|
)
|
||||||
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
|
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
|
||||||
tag = synthesize_union_tag(parser, fields[:])
|
tag = synthesize_union_tag(parser, fields[:])
|
||||||
}
|
}
|
||||||
@@ -2479,7 +2496,8 @@ parse_enum_body :: proc(
|
|||||||
has_previous := false
|
has_previous := false
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
|
||||||
if current(parser).kind != .Identifier {
|
member, member_ok := parse_member_name(parser)
|
||||||
|
if !member_ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected an enum member name")
|
source.add(parser.diagnostics, current(parser).span, "expected an enum member name")
|
||||||
for current(parser).kind != .Newline &&
|
for current(parser).kind != .Newline &&
|
||||||
current(parser).kind != .Right_Brace &&
|
current(parser).kind != .Right_Brace &&
|
||||||
@@ -2489,7 +2507,6 @@ parse_enum_body :: proc(
|
|||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
member := advance(parser)
|
|
||||||
duplicate := false
|
duplicate := false
|
||||||
for existing in members^ {
|
for existing in members^ {
|
||||||
if existing.name == u32(member.symbol) {
|
if existing.name == u32(member.symbol) {
|
||||||
|
|||||||
@@ -114,6 +114,10 @@ Kind :: enum u8 {
|
|||||||
Keyword_C_Longdouble,
|
Keyword_C_Longdouble,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is_keyword :: proc(kind: Kind) -> bool {
|
||||||
|
return kind >= .Keyword_Func && kind <= .Keyword_C_Longdouble
|
||||||
|
}
|
||||||
|
|
||||||
Token :: struct {
|
Token :: struct {
|
||||||
span: source.Span,
|
span: source.Span,
|
||||||
symbol: symbol.Id,
|
symbol: symbol.Id,
|
||||||
@@ -122,5 +126,6 @@ Token :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Stream :: struct {
|
Stream :: struct {
|
||||||
items: [dynamic]Token,
|
items: [dynamic]Token,
|
||||||
|
symbols: ^symbol.Table,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10223,6 +10223,93 @@ main func() i32 {
|
|||||||
testing.expect(t, found_promotion)
|
testing.expect(t, found_promotion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
keywords_are_valid_enum_members_and_tagged_union_variants :: proc(t: ^testing.T) {
|
||||||
|
testing.expect(t, token.is_keyword(.Keyword_Func))
|
||||||
|
testing.expect(t, token.is_keyword(.Keyword_C_Longdouble))
|
||||||
|
testing.expect(t, !token.is_keyword(.Identifier))
|
||||||
|
testing.expect(t, !token.is_keyword(.Underscore))
|
||||||
|
|
||||||
|
text := `TokenKind :: enum {
|
||||||
|
if
|
||||||
|
else
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Token :: union(TokenKind) {
|
||||||
|
if i32
|
||||||
|
else void
|
||||||
|
return i32
|
||||||
|
}
|
||||||
|
kind func(value bool) TokenKind {
|
||||||
|
if value {
|
||||||
|
return .if
|
||||||
|
}
|
||||||
|
return TokenKind.else
|
||||||
|
}
|
||||||
|
main func() i32 {
|
||||||
|
first TokenKind = kind(true)
|
||||||
|
second TokenKind = .return
|
||||||
|
a Token = Token{ if = 1 }
|
||||||
|
b Token = Token{ else }
|
||||||
|
c Token = .return{2}
|
||||||
|
total i32 = a.if + c.return
|
||||||
|
match first {
|
||||||
|
.if: total = total + 1
|
||||||
|
.else: total = total + 2
|
||||||
|
.return: total = total + 3
|
||||||
|
}
|
||||||
|
match b {
|
||||||
|
.if |value|: total = total + value
|
||||||
|
.else: total = total + 4
|
||||||
|
.return |value|: total = total + value
|
||||||
|
}
|
||||||
|
_ = second
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
testing.expect_value(t, len(diagnostics.items), 0)
|
||||||
|
testing.expect(t, len(llvm_text) > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(test)
|
||||||
|
keyword_names_remain_invalid_for_struct_fields :: proc(t: ^testing.T) {
|
||||||
|
text := `Bad :: struct {
|
||||||
|
if i32
|
||||||
|
}
|
||||||
|
`
|
||||||
|
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)
|
||||||
|
|
||||||
|
found := false
|
||||||
|
for diagnostic in diagnostics.items {
|
||||||
|
found = found || strings.contains(diagnostic.message, "expected a struct field name")
|
||||||
|
}
|
||||||
|
testing.expect(t, found)
|
||||||
|
}
|
||||||
|
|
||||||
@(test)
|
@(test)
|
||||||
unbacked_enum_uses_global_u16_backing :: proc(t: ^testing.T) {
|
unbacked_enum_uses_global_u16_backing :: proc(t: ^testing.T) {
|
||||||
builder := strings.builder_make()
|
builder := strings.builder_make()
|
||||||
|
|||||||
Reference in New Issue
Block a user