Skip to content

Commit

Permalink
parser: implement MyEnum.from(1)! generic static method (#20411)
Browse files Browse the repository at this point in the history
  • Loading branch information
spytheman committed Jan 20, 2024
1 parent d912268 commit 629bae4
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 5 deletions.
3 changes: 2 additions & 1 deletion vlib/v/ast/ast.v
Expand Up @@ -1333,7 +1333,8 @@ pub mut:
// enum field in enum declaration
pub struct EnumField {
pub:
name string
name string // just `lock`, or `abc`, etc, no matter if the name is a keyword or not.
source_name string // The name in the source, for example `@lock`, and `abc`. Note that `lock` is a keyword in V.
pos token.Pos
comments []Comment // comment after Enumfield in the same line
next_comments []Comment // comments between current EnumField and next EnumField
Expand Down
64 changes: 60 additions & 4 deletions vlib/v/parser/parser.v
Expand Up @@ -13,6 +13,7 @@ import v.vet
import v.errors
import os
import hash.fnv1a
import strings

@[minify]
pub struct Parser {
Expand Down Expand Up @@ -365,7 +366,7 @@ pub fn (mut p Parser) parse() &ast.File {

// codegen
if p.codegen_text.len > 0 && !p.pref.is_fmt {
ptext := 'module ' + p.mod.all_after_last('.') + p.codegen_text
ptext := 'module ' + p.mod.all_after_last('.') + '\n' + p.codegen_text
codegen_files << parse_text(ptext, p.file_name, p.table, p.comments_mode, p.pref)
}

Expand Down Expand Up @@ -473,9 +474,9 @@ pub fn parse_files(paths []string, table &ast.Table, pref_ &pref.Preferences) []
// checked, markused, cgen-ed etc further, just like user's V code.
pub fn (mut p Parser) codegen(code string) {
$if debug_codegen ? {
eprintln('parser.codegen:\n ${code}')
eprintln('parser.codegen: ${code}')
}
p.codegen_text += '\n' + code
p.codegen_text += code
}

fn (mut p Parser) init_parse_fns() {
Expand Down Expand Up @@ -4055,6 +4056,13 @@ fn (mut p Parser) global_decl() ast.GlobalDecl {
}
}

fn source_name(name string) string {
if token.is_key(name) {
return '@${name}'
}
return name
}

fn (mut p Parser) enum_decl() ast.EnumDecl {
p.top_level_statement_start()
is_pub := p.tok.kind == .key_pub
Expand All @@ -4068,6 +4076,10 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
return ast.EnumDecl{}
}
enum_name := p.check_name()
if enum_name.len == 0 {
p.error_with_pos('enum names can not be empty', end_pos)
return ast.EnumDecl{}
}
if enum_name.len == 1 {
p.error_with_pos('single letter capital names are reserved for generic template types.',
end_pos)
Expand Down Expand Up @@ -4119,6 +4131,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
next_comments := p.eat_comments()
fields << ast.EnumField{
name: val
source_name: source_name(val)
pos: pos
expr: expr
has_expr: has_expr
Expand All @@ -4131,6 +4144,7 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
p.check(.rcbr)
is_flag := p.attrs.contains('flag')
is_multi_allowed := p.attrs.contains('_allow_multiple_values')
pubfn := if p.mod == 'main' { 'fn' } else { 'pub fn' }
if is_flag {
if fields.len > 64 {
p.error('when an enum is used as bit field, it must have a max of 64 fields')
Expand All @@ -4143,7 +4157,6 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
return ast.EnumDecl{}
}
}
pubfn := if p.mod == 'main' { 'fn' } else { 'pub fn' }
all_bits_set_value := '0b' + '1'.repeat(fields.len)
p.codegen('
//
Expand All @@ -4158,6 +4171,49 @@ fn (mut p Parser) enum_decl() ast.EnumDecl {
//
')
}
// Add the generic `Enum.from[T](x T) !T {` static method too:
mut isb := strings.new_builder(1024)
isb.write_string('\n')
// TODO: see why changing `W` to `T` below, later fails `v vlib/vweb/tests/middleware_test_server.v` with seemingly unrelated error
isb.write_string('${pubfn} ${enum_name}.from[W](input W) !${enum_name} {\n')
isb.write_string(' \$if input is \$int {\n')
isb.write_string(' val := unsafe{ ${enum_name}(input) }\n')
isb.write_string(' match val {\n')
for f in fields {
isb.write_string(' .${f.source_name} { return ${enum_name}.${f.source_name} }\n')
}
if is_flag {
isb.write_string(' else{}\n')
}
isb.write_string(' }\n')
isb.write_string(' }\n')
isb.write_string(' \$if input is \$string {\n')
isb.write_string(' val := input.str()\n') // TODO: this should not be needed, the `$if input is $string` above should have already smartcasted `input`
isb.write_string(' match val {\n')
for f in fields {
isb.write_string(' \'${f.name}\' { return ${enum_name}.${f.source_name} }\n')
}
isb.write_string(' else{}\n')
isb.write_string(' }\n')
isb.write_string(' }\n')
isb.write_string(" return error('invalid value')\n")
isb.write_string('}\n')
isb.write_string('\n')
code_for_from_fn := isb.str()
$if debug_enumcodegen ? {
if p.mod == 'main' {
dump(code_for_from_fn)
}
}
if enum_name[0].is_capital() && fields.len > 0 {
// TODO: this check is to prevent avoidable later stage checker errors for generated code,
// since currently there is no way to show the proper source context :-|.
if p.pref.backend == .c {
// TODO: improve the other backends, to the point where they can handle generics or comptime checks too
p.codegen(code_for_from_fn)
}
}

idx := p.table.register_sym(ast.TypeSymbol{
kind: .enum_
name: name
Expand Down
62 changes: 62 additions & 0 deletions vlib/v/tests/enum_from_generic_static_method_test.v
@@ -0,0 +1,62 @@
enum MyEnum {
abc
def
@lock
xyz
@if
}

@[flag]
enum MyFlaggedEnum {
abc
def
xyz
}

/*
fn dump_enum_values[R,T](list []T) {
for input in list {
// TODO: R.from() should work, when R is an enum, but it does not right now
x := R.from(input) or {
eprintln('>>>> error input: `${input}` | err: `${err}`')
continue
}
eprintln('> input: ${input} | x: ${x}')
}
assert true
}
fn test_enums_conversion_using_from() {
dump_enum_values[MyEnum,string](['abc', 'bbb', 'xyz', 'if', 'def', 'zzz'])
dump_enum_values[MyFlaggedEnum,int]([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
}
*/

fn test_enum_from_string() {
x := MyEnum.from('def')!
dump(x)
assert x == .def
y := MyFlaggedEnum.from('xyz')!
dump(y)
assert y == .xyz
assert MyEnum.from('if')! == MyEnum.@if
assert MyEnum.from('lock')! == MyEnum.@lock
if z := MyEnum.from('unknown') {
assert false
} else {
assert err.msg() == 'invalid value'
}
}

fn test_enum_from_integer() {
x := MyEnum.from(3)!
dump(x)
assert x == .xyz
y := MyFlaggedEnum.from(4)!
dump(y)
assert y == .xyz
if z := MyFlaggedEnum.from(9999) {
assert false
} else {
assert err.msg() == 'invalid value'
}
}

0 comments on commit 629bae4

Please sign in to comment.