v2 / vlib / v / parser / attribute.v
328 lines · 320 sloc · 7.83 KB · 3ffc951cf555dc4818309a507ccb9d0da4de748f
Raw
1module parser
2
3import v.ast
4import v.token
5
6@[inline]
7fn (p &Parser) current_attr_string_quote() u8 {
8 if p.tok.kind == .string && p.tok.pos >= 0 && p.tok.pos < p.scanner.text.len {
9 quote := p.scanner.text[p.tok.pos]
10 if quote in [`'`, `"`] {
11 return quote
12 }
13 }
14 return `'`
15}
16
17fn (mut p Parser) parse_attr_arg(err_context string) (ast.AttrKind, string, u8) {
18 if p.tok.kind == .name { // `name: arg`
19 return ast.AttrKind.plain, p.check_name(), `'`
20 } else if p.tok.kind == .number { // `name: 123`
21 arg := p.tok.lit
22 p.next()
23 return ast.AttrKind.number, arg, `'`
24 } else if p.tok.kind == .string { // `name: 'arg'`
25 arg := p.tok.lit
26 quote := p.current_attr_string_quote()
27 p.next()
28 return ast.AttrKind.string, arg, quote
29 } else if p.tok.kind == .key_true || p.tok.kind == .key_false { // `name: true`
30 arg := p.tok.kind.str()
31 p.next()
32 return ast.AttrKind.bool, arg, `'`
33 } else if token.is_key(p.tok.lit) { // `name: keyword`
34 return ast.AttrKind.plain, p.check_name(), `'`
35 }
36 p.unexpected(additional_msg: 'an argument is expected${err_context}')
37 return ast.AttrKind.plain, '', `'`
38}
39
40fn (mut p Parser) parse_attr_call(name string, is_at bool, apos token.Pos) []ast.Attr {
41 p.check(.lpar)
42 mut base_kind := ast.AttrKind.plain
43 mut base_arg := ''
44 mut base_quote := u8(`'`)
45 mut base_arg_name := ''
46 mut base_call_arg_idx := -1
47 mut base_has_arg := false
48 mut attrs := []ast.Attr{}
49 mut has_base_arg := false
50 mut positional_arg_idx := 1
51 mut call_arg_idx := 0
52 for p.tok.kind !in [.rpar, .eof] {
53 mut is_named := false
54 mut arg_name := ''
55 if p.tok.kind == .name && p.peek_token(1).kind == .colon {
56 is_named = true
57 arg_name = p.tok.lit
58 p.next()
59 p.check(.colon)
60 }
61 kind, arg, quote := p.parse_attr_arg(' in `(...)`')
62 if is_named {
63 if name == 'deprecated' && arg_name == 'msg' {
64 if has_base_arg {
65 p.error_with_pos('duplicate `msg` argument for `@[deprecated(...)]` attribute',
66 apos.extend(p.prev_tok.pos()))
67 }
68 base_has_arg = true
69 base_arg = arg
70 base_kind = kind
71 base_quote = quote
72 base_arg_name = arg_name
73 base_call_arg_idx = call_arg_idx
74 has_base_arg = true
75 } else {
76 attrs << ast.Attr{
77 name: '${name}_${arg_name}'
78 has_arg: true
79 arg: arg
80 kind: kind
81 quote: quote
82 pos: apos.extend(p.prev_tok.pos())
83 has_at: is_at
84 call_name: name
85 call_arg_name: arg_name
86 call_arg_idx: call_arg_idx
87 }
88 }
89 } else if !has_base_arg {
90 base_has_arg = true
91 base_arg = arg
92 base_kind = kind
93 base_quote = quote
94 base_arg_name = arg_name
95 base_call_arg_idx = call_arg_idx
96 has_base_arg = true
97 } else {
98 attrs << ast.Attr{
99 name: '${name}_${positional_arg_idx}'
100 has_arg: true
101 arg: arg
102 kind: kind
103 quote: quote
104 pos: apos.extend(p.prev_tok.pos())
105 has_at: is_at
106 call_name: name
107 call_arg_idx: call_arg_idx
108 }
109 positional_arg_idx++
110 }
111 call_arg_idx++
112 if p.tok.kind == .comma {
113 p.next()
114 continue
115 }
116 break
117 }
118 p.check(.rpar)
119 base_attr := ast.Attr{
120 name: name
121 has_arg: base_has_arg
122 arg: base_arg
123 kind: base_kind
124 quote: base_quote
125 pos: apos.extend(p.prev_tok.pos())
126 has_at: is_at
127 call_name: name
128 call_arg_name: base_arg_name
129 call_arg_idx: base_call_arg_idx
130 }
131 attrs.insert(0, base_attr)
132 return attrs
133}
134
135fn (mut p Parser) parse_attr(is_at bool) []ast.Attr {
136 mut kind := ast.AttrKind.plain
137 p.inside_attr_decl = true
138 defer {
139 p.inside_attr_decl = false
140 }
141 apos := if is_at { p.peek_token(-2).pos() } else { p.prev_tok.pos() }
142 if p.tok.kind == .key_unsafe {
143 p.next()
144 mut call_name := ''
145 if p.tok.kind == .lpar {
146 p.check(.lpar)
147 p.check(.rpar)
148 call_name = 'unsafe'
149 }
150 return [
151 ast.Attr{
152 name: 'unsafe'
153 kind: kind
154 pos: apos.extend(p.prev_tok.pos())
155 has_at: is_at
156 call_name: call_name
157 },
158 ]
159 }
160 mut name := ''
161 mut has_arg := false
162 mut arg := ''
163 mut quote := u8(`'`)
164 mut comptime_cond := ast.empty_expr
165 mut comptime_cond_opt := false
166 if p.tok.kind == .key_if {
167 kind = .comptime_define
168 p.next()
169 p.comptime_if_cond = true
170 p.inside_if_expr = true
171 p.inside_ct_if_expr = true
172 comptime_cond = p.expr(0)
173 p.comptime_if_cond = false
174 p.inside_if_expr = false
175 p.inside_ct_if_expr = false
176 if comptime_cond is ast.PostfixExpr {
177 comptime_cond_opt = true
178 }
179 name = comptime_cond.str()
180 } else if p.tok.kind == .string {
181 name = p.tok.lit
182 quote = p.current_attr_string_quote()
183 kind = .string
184 p.next()
185 } else {
186 name = p.check_name()
187 // support dot prefix `module.name: arg`
188 if p.tok.kind == .dot {
189 p.next()
190 name += '.'
191 name += p.check_name()
192 }
193 if p.tok.kind == .colon {
194 has_arg = true
195 p.next()
196 kind, arg, quote = p.parse_attr_arg(' after `:`')
197 } else if p.tok.kind == .lpar {
198 return p.parse_attr_call(name, is_at, apos)
199 }
200 }
201 return [
202 ast.Attr{
203 name: name
204 has_arg: has_arg
205 arg: arg
206 kind: kind
207 quote: quote
208 ct_expr: comptime_cond
209 ct_opt: comptime_cond_opt
210 pos: apos.extend(p.tok.pos())
211 has_at: is_at
212 },
213 ]
214}
215
216fn (mut p Parser) is_attributes() bool {
217 if p.tok.kind != .lsbr {
218 return false
219 }
220 mut i := 0
221 for {
222 tok := p.peek_token(i)
223 if tok.kind == .eof || tok.line_nr != p.tok.line_nr {
224 return false
225 }
226 if tok.kind == .rsbr {
227 break
228 }
229 i++
230 }
231 if i == 1 {
232 // empty `[]` is an array literal, not an attribute
233 return false
234 }
235 peek_rsbr_tok := p.peek_token(i + 1)
236 if peek_rsbr_tok.line_nr == p.tok.line_nr && peek_rsbr_tok.kind != .rcbr {
237 return false
238 }
239 return true
240}
241
242// when is_top_stmt is true, attrs are added to p.attrs
243fn (mut p Parser) attributes() {
244 start_pos := p.tok.pos()
245 mut is_at := false
246 if p.tok.kind == .lsbr {
247 if p.pref.is_fmt {
248 } else {
249 p.error('`[attr]` has been deprecated, use `@[attr]` instead')
250 }
251 // [attr]
252 p.check(.lsbr)
253 } else if p.tok.kind == .at {
254 // @[attr]
255 p.check(.at)
256 p.check(.lsbr)
257 is_at = true
258 }
259 mut has_ctdefine := false
260 for p.tok.kind != .rsbr {
261 attr_start_pos := p.tok.pos()
262 attrs := p.parse_attr(is_at)
263 for attr in attrs {
264 if p.attrs.contains(attr.name) && attr.name != 'wasm_export' {
265 p.error_with_pos('duplicate attribute `${attr.name}`',
266 attr_start_pos.extend(p.prev_tok.pos()))
267 return
268 }
269 if attr.kind == .comptime_define {
270 if has_ctdefine {
271 p.error_with_pos('only one `[if flag]` may be applied at a time `${attr.name}`',
272 attr_start_pos.extend(p.prev_tok.pos()))
273 return
274 } else {
275 has_ctdefine = true
276 }
277 }
278 p.attrs << attr
279 }
280 if p.tok.kind != .semicolon {
281 if p.tok.kind == .rsbr {
282 p.next()
283 break
284 }
285 p.unexpected(expecting: '`;`')
286 return
287 }
288 p.next()
289 }
290 if p.attrs.len == 0 {
291 p.error_with_pos('attributes cannot be empty', start_pos.extend(p.tok.pos()))
292 return
293 } else {
294 p.check_deprecated_attributes()
295 }
296 // TODO: remove when old attr syntax is removed
297 if p.inside_struct_attr_decl && p.tok.kind == .lsbr {
298 p.error_with_pos('multiple attributes should be in the same [], with ; separators',
299 p.prev_tok.pos().extend(p.tok.pos()))
300 return
301 } else if p.inside_struct_attr_decl && p.tok.kind == .at {
302 p.error_with_pos('multiple attributes should be in the same @[], with ; separators',
303 p.prev_tok.pos().extend(p.tok.pos()))
304 return
305 }
306}
307
308fn (mut p Parser) check_deprecated_attributes() {
309 mut deprecated := false
310 mut deprecated_after := false
311 mut deprecated_after_pos := token.Pos{}
312 for attr in p.attrs {
313 match attr.name {
314 'deprecated' {
315 deprecated = true
316 }
317 'deprecated_after' {
318 deprecated_after = true
319 deprecated_after_pos = attr.pos
320 }
321 else {}
322 }
323 }
324 if deprecated_after && !deprecated {
325 p.warn_with_pos('@[deprecated_after] is only valid, in the presence of a `@[deprecated]` attribute',
326 deprecated_after_pos)
327 }
328}
329