module parser import v.ast import v.token @[inline] fn (p &Parser) current_attr_string_quote() u8 { if p.tok.kind == .string && p.tok.pos >= 0 && p.tok.pos < p.scanner.text.len { quote := p.scanner.text[p.tok.pos] if quote in [`'`, `"`] { return quote } } return `'` } fn (mut p Parser) parse_attr_arg(err_context string) (ast.AttrKind, string, u8) { if p.tok.kind == .name { // `name: arg` return ast.AttrKind.plain, p.check_name(), `'` } else if p.tok.kind == .number { // `name: 123` arg := p.tok.lit p.next() return ast.AttrKind.number, arg, `'` } else if p.tok.kind == .string { // `name: 'arg'` arg := p.tok.lit quote := p.current_attr_string_quote() p.next() return ast.AttrKind.string, arg, quote } else if p.tok.kind == .key_true || p.tok.kind == .key_false { // `name: true` arg := p.tok.kind.str() p.next() return ast.AttrKind.bool, arg, `'` } else if token.is_key(p.tok.lit) { // `name: keyword` return ast.AttrKind.plain, p.check_name(), `'` } p.unexpected(additional_msg: 'an argument is expected${err_context}') return ast.AttrKind.plain, '', `'` } fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast.Attr { p.check(.lpar) mut base_kind := ast.AttrKind.plain mut base_arg := '' mut base_quote := u8(`'`) mut base_arg_name := '' mut base_call_arg_idx := -1 mut base_has_arg := false mut attrs := []ast.Attr{} mut has_base_arg := false mut positional_arg_idx := 1 mut call_arg_idx := 0 for p.tok.kind !in [.rpar, .eof] { mut is_named := false mut arg_name := '' if p.tok.kind == .name && p.peek_token(1).kind == .colon { is_named = true arg_name = p.tok.lit p.next() p.check(.colon) } kind, arg, quote := p.parse_attr_arg(' in `(...)`') if is_named { if name == 'deprecated' && arg_name == 'msg' { if has_base_arg { p.error_with_pos('duplicate `msg` argument for `@[deprecated(...)]` attribute', apos.extend(p.prev_tok.pos())) } base_has_arg = true base_arg = arg base_kind = kind base_quote = quote base_arg_name = arg_name base_call_arg_idx = call_arg_idx has_base_arg = true } else { attrs << ast.Attr{ name: '${name}_${arg_name}' has_arg: true arg: arg kind: kind quote: quote pos: apos.extend(p.prev_tok.pos()) has_at: is_at call_name: name call_arg_name: arg_name call_arg_idx: call_arg_idx } } } else if !has_base_arg { base_has_arg = true base_arg = arg base_kind = kind base_quote = quote base_arg_name = arg_name base_call_arg_idx = call_arg_idx has_base_arg = true } else { attrs << ast.Attr{ name: '${name}_${positional_arg_idx}' has_arg: true arg: arg kind: kind quote: quote pos: apos.extend(p.prev_tok.pos()) has_at: is_at call_name: name call_arg_idx: call_arg_idx } positional_arg_idx++ } call_arg_idx++ if p.tok.kind == .comma { p.next() continue } break } p.check(.rpar) base_attr := ast.Attr{ name: name has_arg: base_has_arg arg: base_arg kind: base_kind quote: base_quote pos: apos.extend(p.prev_tok.pos()) has_at: is_at call_name: name call_arg_name: base_arg_name call_arg_idx: base_call_arg_idx } attrs.insert(0, base_attr) return attrs } fn (mut p Parser) parse_attr(is_at bool) []ast.Attr { mut kind := ast.AttrKind.plain p.inside_attr_decl = true defer { p.inside_attr_decl = false } apos := if is_at { p.peek_token(-2).pos() } else { p.prev_tok.pos() } if p.tok.kind == .key_unsafe { p.next() mut call_name := '' if p.tok.kind == .lpar { p.check(.lpar) p.check(.rpar) call_name = 'unsafe' } return [ ast.Attr{ name: 'unsafe' kind: kind pos: apos.extend(p.prev_tok.pos()) has_at: is_at call_name: call_name }, ] } mut name := '' mut has_arg := false mut arg := '' mut quote := u8(`'`) mut comptime_cond := ast.empty_expr mut comptime_cond_opt := false if p.tok.kind == .key_if { kind = .comptime_define p.next() p.comptime_if_cond = true p.inside_if_expr = true p.inside_ct_if_expr = true comptime_cond = p.expr(0) p.comptime_if_cond = false p.inside_if_expr = false p.inside_ct_if_expr = false if comptime_cond is ast.PostfixExpr { comptime_cond_opt = true } name = comptime_cond.str() } else if p.tok.kind == .string { name = p.tok.lit quote = p.current_attr_string_quote() kind = .string p.next() } else { name = p.check_name() // support dot prefix `module.name: arg` if p.tok.kind == .dot { p.next() name += '.' name += p.check_name() } if p.tok.kind == .colon { has_arg = true p.next() kind, arg, quote = p.parse_attr_arg(' after `:`') } else if p.tok.kind == .lpar { return p.parse_attr_call(name, is_at, apos) } } return [ ast.Attr{ name: name has_arg: has_arg arg: arg kind: kind quote: quote ct_expr: comptime_cond ct_opt: comptime_cond_opt pos: apos.extend(p.tok.pos()) has_at: is_at }, ] } fn (mut p Parser) is_attributes() bool { if p.tok.kind != .lsbr { return false } mut i := 0 for { tok := p.peek_token(i) if tok.kind == .eof || tok.line_nr != p.tok.line_nr { return false } if tok.kind == .rsbr { break } i++ } if i == 1 { // empty `[]` is an array literal, not an attribute return false } peek_rsbr_tok := p.peek_token(i + 1) if peek_rsbr_tok.line_nr == p.tok.line_nr && peek_rsbr_tok.kind != .rcbr { return false } return true } // when is_top_stmt is true, attrs are added to p.attrs fn (mut p Parser) attributes() { start_pos := p.tok.pos() mut is_at := false if p.tok.kind == .lsbr { if p.pref.is_fmt { } else { p.error('`[attr]` has been deprecated, use `@[attr]` instead') } // [attr] p.check(.lsbr) } else if p.tok.kind == .at { // @[attr] p.check(.at) p.check(.lsbr) is_at = true } mut has_ctdefine := false for p.tok.kind != .rsbr { attr_start_pos := p.tok.pos() attrs := p.parse_attr(is_at) for attr in attrs { if p.attrs.contains(attr.name) && attr.name != 'wasm_export' { p.error_with_pos('duplicate attribute `${attr.name}`', attr_start_pos.extend(p.prev_tok.pos())) return } if attr.kind == .comptime_define { if has_ctdefine { p.error_with_pos('only one `[if flag]` may be applied at a time `${attr.name}`', attr_start_pos.extend(p.prev_tok.pos())) return } else { has_ctdefine = true } } p.attrs << attr } if p.tok.kind != .semicolon { if p.tok.kind == .rsbr { p.next() break } p.unexpected(expecting: '`;`') return } p.next() } if p.attrs.len == 0 { p.error_with_pos('attributes cannot be empty', start_pos.extend(p.tok.pos())) return } else { p.check_deprecated_attributes() } // TODO: remove when old attr syntax is removed if p.inside_struct_attr_decl && p.tok.kind == .lsbr { p.error_with_pos('multiple attributes should be in the same [], with ; separators', p.prev_tok.pos().extend(p.tok.pos())) return } else if p.inside_struct_attr_decl && p.tok.kind == .at { p.error_with_pos('multiple attributes should be in the same @[], with ; separators', p.prev_tok.pos().extend(p.tok.pos())) return } } fn (mut p Parser) check_deprecated_attributes() { mut deprecated := false mut deprecated_after := false mut deprecated_after_pos := token.Pos{} for attr in p.attrs { match attr.name { 'deprecated' { deprecated = true } 'deprecated_after' { deprecated_after = true deprecated_after_pos = attr.pos } else {} } } if deprecated_after && !deprecated { p.warn_with_pos('@[deprecated_after] is only valid, in the presence of a `@[deprecated]` attribute', deprecated_after_pos) } }