diff --git a/vlib/compiler/parser.v b/vlib/compiler/parser.v index 95f1c2c81f..5861e2826e 100644 --- a/vlib/compiler/parser.v +++ b/vlib/compiler/parser.v @@ -668,227 +668,6 @@ fn key_to_type_cat(tok TokenKind) TypeCategory { return TypeCategory.builtin } -// also unions and interfaces -fn (p mut Parser) struct_decl() { - is_pub := p.tok == .key_pub - if is_pub { - p.next() - } - // V can generate Objective C for integration with Cocoa - // `[objc_interface:ParentInterface]` - is_objc := p.attr.starts_with('objc_interface') - objc_parent := if is_objc { p.attr.right(15) } else { '' } - // interface, union, struct - is_interface := p.tok == .key_interface - is_union := p.tok == .key_union - is_struct := p.tok == .key_struct - mut cat := key_to_type_cat(p.tok) - if is_objc { - cat = .objc_interface - } - p.fgen(p.tok.str() + ' ') - // Get type name - p.next() - mut name := p.check_name() - if name.contains('_') && !p.pref.translated { - p.error('type names cannot contain `_`') - } - if !p.builtin_mod && !name[0].is_capital() { - p.error('struct names must be capitalized: use `struct ${name.capitalize()}`') - } - if is_interface && !name.ends_with('er') { - p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)') - } - is_c := name == 'C' && p.tok == .dot - if is_c { - p.check(.dot) - name = p.check_name() - cat = .c_struct - if p.attr == 'typedef' { - cat = .c_typedef - } - } - if name.len == 1 && !p.pref.building_v && !p.pref.is_repl { - p.warn('struct names must have more than one character') - } - if !is_c && !good_type_name(name) { - p.error('bad struct name, e.g. use `HttpRequest` instead of `HTTPRequest`') - } - // Specify full type name - if !is_c && !p.builtin_mod && p.mod != 'main' { - name = p.prepend_mod(name) - } - mut typ := p.table.find_type(name) - if p.pass == .decl && p.table.known_type_fast(typ) { - //if name in reserved_type_param_names { - //p.error('name `$name` is reserved for type parameters') - //} else { - p.error('type `$name` redeclared') - //} - } - if is_objc { - // Forward declaration of an Objective-C interface with `@class` :) - p.gen_typedef('@class $name;') - } - else if !is_c { - kind := if is_union {'union'} else {'struct'} - p.gen_typedef('typedef $kind $name $name;') - } - // Register the type - mut is_ph := false - if typ.is_placeholder { - // Update the placeholder - is_ph = true - typ.name = name - typ.mod = p.mod - typ.is_c = is_c - typ.is_placeholder = false - typ.cat = cat - typ.parent = objc_parent - p.table.rewrite_type(typ) - } - else { - typ = Type { - name: name - mod: p.mod - is_c: is_c - cat: cat - parent: objc_parent - is_public: is_pub - } - } - // Struct `C.Foo` declaration, no body - if is_c && is_struct && p.tok != .lcbr { - p.table.register_type2(typ) - return - } - p.fgen(' ') - p.check(.lcbr) - // Struct fields - mut is_pub_field := false - mut is_mut := false - mut names := []string// to avoid dup names TODO alloc perf -/* - mut fmt_max_len := 0 - for field in typ.fields { - if field.name.len > max_len { - fmt_max_len = field.name.len - } - } - println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass') -*/ - - if !is_ph && p.first_pass() { - p.table.register_type2(typ) - //println('registering 1 nrfields=$typ.fields.len') - } - - mut did_gen_something := false - for p.tok != .rcbr { - if p.tok == .key_pub { - if is_pub_field { - p.error('structs can only have one `pub:`, all public fields have to be grouped') - } - is_pub_field = true - p.fmt_dec() - p.check(.key_pub) - if p.tok != .key_mut { - p.check(.colon) - } - p.fmt_inc() - p.fgenln('') - } - if p.tok == .key_mut { - if is_mut { - p.error('structs can only have one `mut:`, all private mutable fields have to be grouped') - } - is_mut = true - p.fmt_dec() - p.check(.key_mut) - if p.tok != .key_mut { - p.check(.colon) - } - p.fmt_inc() - p.fgenln('') - } - // if is_pub { - // } - // (mut) user *User - // if p.tok == .plus { - // p.next() - // } - // Check if reserved name - field_name_token_idx := p.cur_tok_index() - field_name := if name != 'Option' { p.table.var_cgen_name(p.check_name()) } else { p.check_name() } - // Check dups - if field_name in names { - p.error('duplicate field `$field_name`') - } - if !is_c && p.mod != 'os' && contains_capital(field_name) { - p.error('struct fields cannot contain uppercase letters, use snake_case instead') - } - names << field_name - // We are in an interface? - // `run() string` => run is a method, not a struct field - if is_interface { - f := p.interface_method(field_name, name) - if p.first_pass() { - p.add_method(typ.name, f) - } - continue - } - // `pub` access mod - access_mod := if is_pub_field { AccessMod.public } else { AccessMod.private} - p.fgen(' ') - field_type := p.get_type() - if field_type == name { - p.error_with_token_index( 'cannot embed struct `$name` in itself (field `$field_name`)', field_name_token_idx) - } - p.check_and_register_used_imported_type(field_type) - is_atomic := p.tok == .key_atomic - if is_atomic { - p.next() - } - // [ATTR] - mut attr := '' - if p.tok == .lsbr { - p.next() - attr = p.check_name() - if p.tok == .colon { - p.check(.colon) - mut val := '' - match p.tok { - .name { val = p.check_name() } - .str { val = p.check_string() } - else { - p.error('attribute value should be either name or string') - } - } - attr += ':' + val - } - p.check(.rsbr) - } - if attr == 'raw' && field_type != 'string' { - p.error('struct field with attribute "raw" should be of type "string" but got "$field_type"') - } - - did_gen_something = true - if p.first_pass() { - p.table.add_field(typ.name, field_name, field_type, is_mut, attr, access_mod) - } - p.fgenln('') - } - p.check(.rcbr) - if !is_c { - if !did_gen_something { - if p.first_pass() { - p.table.add_field(typ.name, '', 'EMPTY_STRUCT_DECLARATION', false, '', .private) - } - } - } - p.fgenln('\n') -} - // check_name checks for a name token and returns its literal fn (p mut Parser) check_name() string { name := p.lit @@ -3198,129 +2977,6 @@ fn (p mut Parser) array_init() string { return typ } -fn (p mut Parser) struct_init(typ string) string { - p.is_struct_init = true - t := p.table.find_type(typ) - if !t.is_public && t.mod != p.mod { - p.warn('type `$t.name` is private') - } - if p.gen_struct_init(typ, t) { return typ } - p.scanner.fmt_out.cut(typ.len) - ptr := typ.contains('*') - mut did_gen_something := false - // Loop thru all struct init keys and assign values - // u := User{age:20, name:'bob'} - // Remember which fields were set, so that we dont have to zero them later - mut inited_fields := []string - peek := p.peek() - if peek == .colon || p.tok == .rcbr { - for p.tok != .rcbr { - field := if typ != 'Option' { p.table.var_cgen_name( p.check_name() ) } else { p.check_name() } - if !p.first_pass() && !t.has_field(field) { - p.error('`$t.name` has no field `$field`') - } - if field in inited_fields { - p.error('already initialized field `$field` in `$t.name`') - } - f := t.find_field(field) or { - p.error('no such field: "$field" in type $typ') - break - } - inited_fields << field - p.gen_struct_field_init(field) - p.check(.colon) - p.fspace() - p.check_types(p.bool_expression(), f.typ) - if p.tok == .comma { - p.next() - } - if p.tok != .rcbr { - p.gen(',') - } - p.fgenln('') - did_gen_something = true - } - // If we already set some fields, need to prepend a comma - if t.fields.len != inited_fields.len && inited_fields.len > 0 { - p.gen(',') - } - // Zero values: init all fields (ints to 0, strings to '' etc) - for i, field in t.fields { - sanitized_name := if typ != 'Option' { p.table.var_cgen_name( field.name ) } else { field.name } - // println('### field.name') - // Skip if this field has already been assigned to - if sanitized_name in inited_fields { - continue - } - field_typ := field.typ - if !p.builtin_mod && field_typ.ends_with('*') && field_typ.contains('Cfg') { - p.error('pointer field `${typ}.${field.name}` must be initialized') - } - // init map fields - if field_typ.starts_with('map_') { - p.gen_struct_field_init(sanitized_name) - p.gen_empty_map(field_typ.right(4)) - inited_fields << sanitized_name - if i != t.fields.len - 1 { - p.gen(',') - } - did_gen_something = true - continue - } - def_val := type_default(field_typ) - if def_val != '' && def_val != '{0}' { - p.gen_struct_field_init(sanitized_name) - p.gen(def_val) - if i != t.fields.len - 1 { - p.gen(',') - } - did_gen_something = true - } - } - } - // Point{3,4} syntax - else { - mut T := p.table.find_type(typ) - // Aliases (TODO Hack, implement proper aliases) - if T.fields.len == 0 && T.parent != '' { - T = p.table.find_type(T.parent) - } - for i, ffield in T.fields { - expr_typ := p.bool_expression() - if !p.check_types_no_throw(expr_typ, ffield.typ) { - p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ') - } - if i < T.fields.len - 1 { - if p.tok != .comma { - p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)') - } - p.gen(',') - p.next() - } - } - // Allow `user := User{1,2,3,}` - // The final comma will be removed by vfmt, since we are not calling `p.fgen()` - if p.tok == .comma { - p.next() - } - if p.tok != .rcbr { - p.error('too many fields initialized: `$typ` has $T.fields.len field(s)') - } - did_gen_something = true - } - if !did_gen_something { - p.gen('EMPTY_STRUCT_INITIALIZATION') - } - p.gen('}') - if ptr && !p.is_js { - p.gen(', sizeof($t.name))') - } - p.check(.rcbr) - p.is_struct_init = false - p.is_c_struct_init = false - return typ -} - // `f32(3)` // tok is `f32` or `)` if `(*int)(ptr)` diff --git a/vlib/compiler/struct.v b/vlib/compiler/struct.v new file mode 100644 index 0000000000..1e32bdee87 --- /dev/null +++ b/vlib/compiler/struct.v @@ -0,0 +1,352 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module compiler + +// also unions and interfaces +fn (p mut Parser) struct_decl() { + is_pub := p.tok == .key_pub + if is_pub { + p.next() + } + // V can generate Objective C for integration with Cocoa + // `[objc_interface:ParentInterface]` + is_objc := p.attr.starts_with('objc_interface') + objc_parent := if is_objc { p.attr.right(15) } else { '' } + // interface, union, struct + is_interface := p.tok == .key_interface + is_union := p.tok == .key_union + is_struct := p.tok == .key_struct + mut cat := key_to_type_cat(p.tok) + if is_objc { + cat = .objc_interface + } + p.fgen(p.tok.str() + ' ') + // Get type name + p.next() + mut name := p.check_name() + if name.contains('_') && !p.pref.translated { + p.error('type names cannot contain `_`') + } + if !p.builtin_mod && !name[0].is_capital() { + p.error('struct names must be capitalized: use `struct ${name.capitalize()}`') + } + if is_interface && !name.ends_with('er') { + p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)') + } + is_c := name == 'C' && p.tok == .dot + if is_c { + p.check(.dot) + name = p.check_name() + cat = .c_struct + if p.attr == 'typedef' { + cat = .c_typedef + } + } + if name.len == 1 && !p.pref.building_v && !p.pref.is_repl { + p.warn('struct names must have more than one character') + } + if !is_c && !good_type_name(name) { + p.error('bad struct name, e.g. use `HttpRequest` instead of `HTTPRequest`') + } + // Specify full type name + if !is_c && !p.builtin_mod && p.mod != 'main' { + name = p.prepend_mod(name) + } + mut typ := p.table.find_type(name) + if p.pass == .decl && p.table.known_type_fast(typ) { + //if name in reserved_type_param_names { + //p.error('name `$name` is reserved for type parameters') + //} else { + p.error('type `$name` redeclared') + //} + } + if is_objc { + // Forward declaration of an Objective-C interface with `@class` :) + p.gen_typedef('@class $name;') + } + else if !is_c { + kind := if is_union {'union'} else {'struct'} + p.gen_typedef('typedef $kind $name $name;') + } + // Register the type + mut is_ph := false + if typ.is_placeholder { + // Update the placeholder + is_ph = true + typ.name = name + typ.mod = p.mod + typ.is_c = is_c + typ.is_placeholder = false + typ.cat = cat + typ.parent = objc_parent + p.table.rewrite_type(typ) + } + else { + typ = Type { + name: name + mod: p.mod + is_c: is_c + cat: cat + parent: objc_parent + is_public: is_pub + } + } + // Struct `C.Foo` declaration, no body + if is_c && is_struct && p.tok != .lcbr { + p.table.register_type2(typ) + return + } + p.fgen(' ') + p.check(.lcbr) + // Struct fields + mut is_pub_field := false + mut is_mut := false + mut names := []string// to avoid dup names TODO alloc perf +/* + mut fmt_max_len := 0 + for field in typ.fields { + if field.name.len > max_len { + fmt_max_len = field.name.len + } + } + println('fmt max len = $max_len nrfields=$typ.fields.len pass=$p.pass') +*/ + + if !is_ph && p.first_pass() { + p.table.register_type2(typ) + //println('registering 1 nrfields=$typ.fields.len') + } + + mut did_gen_something := false + for p.tok != .rcbr { + if p.tok == .key_pub { + if is_pub_field { + p.error('structs can only have one `pub:`, all public fields have to be grouped') + } + is_pub_field = true + p.fmt_dec() + p.check(.key_pub) + if p.tok != .key_mut { + p.check(.colon) + } + p.fmt_inc() + p.fgenln('') + } + if p.tok == .key_mut { + if is_mut { + p.error('structs can only have one `mut:`, all private mutable fields have to be grouped') + } + is_mut = true + p.fmt_dec() + p.check(.key_mut) + if p.tok != .key_mut { + p.check(.colon) + } + p.fmt_inc() + p.fgenln('') + } + // if is_pub { + // } + // (mut) user *User + // if p.tok == .plus { + // p.next() + // } + // Check if reserved name + field_name_token_idx := p.cur_tok_index() + field_name := if name != 'Option' { p.table.var_cgen_name(p.check_name()) } else { p.check_name() } + // Check dups + if field_name in names { + p.error('duplicate field `$field_name`') + } + if !is_c && p.mod != 'os' && contains_capital(field_name) { + p.error('struct fields cannot contain uppercase letters, use snake_case instead') + } + names << field_name + // We are in an interface? + // `run() string` => run is a method, not a struct field + if is_interface { + f := p.interface_method(field_name, name) + if p.first_pass() { + p.add_method(typ.name, f) + } + continue + } + // `pub` access mod + access_mod := if is_pub_field { AccessMod.public } else { AccessMod.private} + p.fgen(' ') + field_type := p.get_type() + if field_type == name { + p.error_with_token_index( 'cannot embed struct `$name` in itself (field `$field_name`)', field_name_token_idx) + } + p.check_and_register_used_imported_type(field_type) + is_atomic := p.tok == .key_atomic + if is_atomic { + p.next() + } + // [ATTR] + mut attr := '' + if p.tok == .lsbr { + p.next() + attr = p.check_name() + if p.tok == .colon { + p.check(.colon) + mut val := '' + match p.tok { + .name { val = p.check_name() } + .str { val = p.check_string() } + else { + p.error('attribute value should be either name or string') + } + } + attr += ':' + val + } + p.check(.rsbr) + } + if attr == 'raw' && field_type != 'string' { + p.error('struct field with attribute "raw" should be of type "string" but got "$field_type"') + } + + did_gen_something = true + if p.first_pass() { + p.table.add_field(typ.name, field_name, field_type, is_mut, attr, access_mod) + } + p.fgenln('') + } + p.check(.rcbr) + if !is_c { + if !did_gen_something { + if p.first_pass() { + p.table.add_field(typ.name, '', 'EMPTY_STRUCT_DECLARATION', false, '', .private) + } + } + } + p.fgenln('\n') +} + +// `User{ foo: bar }` +fn (p mut Parser) struct_init(typ string) string { + p.is_struct_init = true + t := p.table.find_type(typ) + if !t.is_public && t.mod != p.mod { + p.warn('type `$t.name` is private') + } + if p.gen_struct_init(typ, t) { return typ } + p.scanner.fmt_out.cut(typ.len) + ptr := typ.contains('*') + mut did_gen_something := false + // Loop thru all struct init keys and assign values + // u := User{age:20, name:'bob'} + // Remember which fields were set, so that we dont have to zero them later + mut inited_fields := []string + peek := p.peek() + if peek == .colon || p.tok == .rcbr { + for p.tok != .rcbr { + field := if typ != 'Option' { p.table.var_cgen_name( p.check_name() ) } else { p.check_name() } + if !p.first_pass() && !t.has_field(field) { + p.error('`$t.name` has no field `$field`') + } + if field in inited_fields { + p.error('already initialized field `$field` in `$t.name`') + } + f := t.find_field(field) or { + p.error('no such field: "$field" in type $typ') + break + } + inited_fields << field + p.gen_struct_field_init(field) + p.check(.colon) + p.fspace() + p.check_types(p.bool_expression(), f.typ) + if p.tok == .comma { + p.next() + } + if p.tok != .rcbr { + p.gen(',') + } + p.fgenln('') + did_gen_something = true + } + // If we already set some fields, need to prepend a comma + if t.fields.len != inited_fields.len && inited_fields.len > 0 { + p.gen(',') + } + // Zero values: init all fields (ints to 0, strings to '' etc) + for i, field in t.fields { + sanitized_name := if typ != 'Option' { p.table.var_cgen_name( field.name ) } else { field.name } + // println('### field.name') + // Skip if this field has already been assigned to + if sanitized_name in inited_fields { + continue + } + field_typ := field.typ + if !p.builtin_mod && field_typ.ends_with('*') && field_typ.contains('Cfg') { + p.error('pointer field `${typ}.${field.name}` must be initialized') + } + // init map fields + if field_typ.starts_with('map_') { + p.gen_struct_field_init(sanitized_name) + p.gen_empty_map(field_typ.right(4)) + inited_fields << sanitized_name + if i != t.fields.len - 1 { + p.gen(',') + } + did_gen_something = true + continue + } + def_val := type_default(field_typ) + if def_val != '' && def_val != '{0}' { + p.gen_struct_field_init(sanitized_name) + p.gen(def_val) + if i != t.fields.len - 1 { + p.gen(',') + } + did_gen_something = true + } + } + } + // Point{3,4} syntax + else { + mut T := p.table.find_type(typ) + // Aliases (TODO Hack, implement proper aliases) + if T.fields.len == 0 && T.parent != '' { + T = p.table.find_type(T.parent) + } + for i, ffield in T.fields { + expr_typ := p.bool_expression() + if !p.check_types_no_throw(expr_typ, ffield.typ) { + p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ') + } + if i < T.fields.len - 1 { + if p.tok != .comma { + p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)') + } + p.gen(',') + p.next() + } + } + // Allow `user := User{1,2,3,}` + // The final comma will be removed by vfmt, since we are not calling `p.fgen()` + if p.tok == .comma { + p.next() + } + if p.tok != .rcbr { + p.error('too many fields initialized: `$typ` has $T.fields.len field(s)') + } + did_gen_something = true + } + if !did_gen_something { + p.gen('EMPTY_STRUCT_INITIALIZATION') + } + p.gen('}') + if ptr && !p.is_js { + p.gen(', sizeof($t.name))') + } + p.check(.rcbr) + p.is_struct_init = false + p.is_c_struct_init = false + return typ +} + +