| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module fmt |
| 5 | |
| 6 | import v.ast |
| 7 | |
| 8 | pub fn (mut f Fmt) struct_decl(node ast.StructDecl, is_anon bool) { |
| 9 | f.attrs(node.attrs) |
| 10 | if node.is_pub && !is_anon { |
| 11 | f.write('pub ') |
| 12 | } |
| 13 | if node.is_option { |
| 14 | f.write('?') |
| 15 | } |
| 16 | if node.is_union { |
| 17 | f.write('union') |
| 18 | } else { |
| 19 | f.write('struct') |
| 20 | } |
| 21 | name := node.name.after('.') // strip prepended module |
| 22 | if !is_anon { |
| 23 | f.write(' ') |
| 24 | f.write_language_prefix(node.language) |
| 25 | f.write(name) |
| 26 | } |
| 27 | f.write_generic_types(node.generic_types) |
| 28 | if node.is_implements { |
| 29 | f.write(' implements ') |
| 30 | for i, t in node.implements_types { |
| 31 | f.write(f.table.type_to_str_using_aliases(t.typ, f.mod2alias)) |
| 32 | if i < node.implements_types.len - 1 { |
| 33 | f.write(', ') |
| 34 | } |
| 35 | } |
| 36 | } |
| 37 | if node.fields.len == 0 && node.embeds.len == 0 && node.pos.line_nr == node.pos.last_line { |
| 38 | f.writeln(' {}') |
| 39 | return |
| 40 | } |
| 41 | mut type_align := new_field_align(use_break_line: true) |
| 42 | mut default_expr_align := new_field_align(use_threshold: true) |
| 43 | mut attr_align := new_field_align(use_threshold: true) |
| 44 | mut comment_align := new_field_align(use_threshold: true) |
| 45 | mut field_types := []string{cap: node.fields.len} |
| 46 | // Calculate the alignments first |
| 47 | f.calculate_alignment(node.fields, mut type_align, mut comment_align, mut default_expr_align, mut |
| 48 | attr_align, mut field_types) |
| 49 | f.writeln(' {') |
| 50 | if node.pre_comments.len > 0 { |
| 51 | f.comments_before_field(node.pre_comments) |
| 52 | } |
| 53 | for embed in node.embeds { |
| 54 | styp := f.table.type_to_str_using_aliases(embed.typ, f.mod2alias) |
| 55 | |
| 56 | pre_comments := embed.comments.filter(it.pos.pos < embed.pos.pos) |
| 57 | comments := embed.comments[pre_comments.len..] |
| 58 | |
| 59 | f.comments_before_field(pre_comments) |
| 60 | if comments.len == 0 { |
| 61 | f.writeln('\t${styp}') |
| 62 | } else { |
| 63 | f.write('\t${styp}') |
| 64 | f.comments(comments, level: .indent) |
| 65 | } |
| 66 | } |
| 67 | // Now handle each field |
| 68 | mut inc_indent := false // for correct indents with multi line default exprs |
| 69 | for i, field in node.fields { |
| 70 | match true { |
| 71 | i == node.mut_pos { |
| 72 | f.writeln('mut:') |
| 73 | } |
| 74 | i == node.pub_pos { |
| 75 | f.writeln('pub:') |
| 76 | } |
| 77 | i == node.pub_mut_pos { |
| 78 | f.writeln('pub mut:') |
| 79 | } |
| 80 | i == node.global_pos { |
| 81 | f.writeln('__global:') |
| 82 | } |
| 83 | i == node.module_pos { |
| 84 | f.writeln('module:') |
| 85 | } |
| 86 | i > 0 && field.has_prev_newline { |
| 87 | f.writeln('') |
| 88 | } |
| 89 | else {} |
| 90 | } |
| 91 | |
| 92 | // Handle comments before the field |
| 93 | if field.pre_comments.len > 0 { |
| 94 | f.comments(field.pre_comments, level: .indent) |
| 95 | } |
| 96 | volatile_prefix := if field.is_volatile { 'volatile ' } else { '' } |
| 97 | f.write('\t${volatile_prefix}${field.name} ') |
| 98 | f.write(' '.repeat(type_align.max_len(field.pos.line_nr) - field.name.len)) |
| 99 | // Handle anon structs recursively |
| 100 | if !f.write_anon_struct_field_decl(field.typ, field.anon_struct_decl) { |
| 101 | f.write(field_types[i]) |
| 102 | } |
| 103 | attrs_len := inline_attrs_len(field.attrs) |
| 104 | if field.has_default_expr { |
| 105 | f.write(' '.repeat(default_expr_align.max_len(field.pos.line_nr) - field_types[i].len)) |
| 106 | f.write(' = ') |
| 107 | default_expr_pos := field.default_expr.pos() |
| 108 | if default_expr_pos.line_nr < default_expr_pos.last_line |
| 109 | || !expr_is_single_line(field.default_expr) { |
| 110 | f.indent++ |
| 111 | inc_indent = true |
| 112 | } |
| 113 | f.expr(field.default_expr) |
| 114 | if inc_indent { |
| 115 | f.indent-- |
| 116 | inc_indent = false |
| 117 | } |
| 118 | } |
| 119 | if field.attrs.len > 0 { |
| 120 | f.write(' '.repeat(attr_align.max_len(field.pos.line_nr) - field_types[i].len)) |
| 121 | f.single_line_attrs(field.attrs, same_line: true) |
| 122 | } |
| 123 | // Handle comments at the end of the line |
| 124 | if field.comments.len > 0 { |
| 125 | if field.has_default_expr { |
| 126 | f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - |
| 127 | field.default_expr.str().len - 2)) |
| 128 | } else if field.attrs.len > 0 { |
| 129 | f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - attrs_len)) |
| 130 | } else { |
| 131 | f.write(' '.repeat(comment_align.max_len(field.pos.line_nr) - field_types[i].len)) |
| 132 | } |
| 133 | f.write(' ') |
| 134 | f.comments(field.comments, level: .indent) |
| 135 | } else { |
| 136 | f.writeln('') |
| 137 | } |
| 138 | // Handle comments on the next lines |
| 139 | if field.next_comments.len > 0 { |
| 140 | f.comments(field.next_comments, level: .indent) |
| 141 | } |
| 142 | } |
| 143 | if is_anon || node.end_comments.len > 0 { |
| 144 | f.write('}') |
| 145 | } else { |
| 146 | f.writeln('}') |
| 147 | } |
| 148 | if node.end_comments.len > 0 { |
| 149 | f.comments(node.end_comments, same_line: true) |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | fn (mut f Fmt) write_anon_struct_field_decl(field_typ ast.Type, field_anon_decl ast.StructDecl) bool { |
| 154 | sym := f.table.sym(field_typ) |
| 155 | match sym.kind { |
| 156 | .struct { |
| 157 | info := sym.info as ast.Struct |
| 158 | if info.is_anon { |
| 159 | f.indent++ |
| 160 | if info.is_shared { |
| 161 | f.write('shared ') |
| 162 | } |
| 163 | f.struct_decl(field_anon_decl, true) |
| 164 | f.indent-- |
| 165 | return true |
| 166 | } |
| 167 | } |
| 168 | .array { |
| 169 | if sym.info is ast.Array { |
| 170 | elem_sym := f.table.sym(sym.info.elem_type) |
| 171 | if elem_sym.info is ast.Struct { |
| 172 | if elem_sym.info.is_anon { |
| 173 | if field_typ.has_flag(.option) { |
| 174 | f.write('?') |
| 175 | } |
| 176 | f.write('[]'.repeat(sym.info.nr_dims)) |
| 177 | f.write_anon_struct_field_decl(sym.info.elem_type, field_anon_decl) |
| 178 | return true |
| 179 | } |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | .array_fixed { |
| 184 | if sym.info is ast.ArrayFixed { |
| 185 | elem_sym := f.table.sym(sym.info.elem_type) |
| 186 | if elem_sym.info is ast.Struct { |
| 187 | if elem_sym.info.is_anon { |
| 188 | f.write('[${sym.info.size}]') |
| 189 | f.write_anon_struct_field_decl(sym.info.elem_type, field_anon_decl) |
| 190 | return true |
| 191 | } |
| 192 | } |
| 193 | } |
| 194 | } |
| 195 | else {} |
| 196 | } |
| 197 | |
| 198 | return false |
| 199 | } |
| 200 | |
| 201 | fn (mut f Fmt) write_anon_struct_type(typ ast.Type) bool { |
| 202 | sym := f.table.sym(typ) |
| 203 | if sym.info is ast.Struct && sym.info.is_anon { |
| 204 | f.struct_decl(ast.StructDecl{ |
| 205 | fields: sym.info.fields |
| 206 | }, true) |
| 207 | return true |
| 208 | } |
| 209 | return false |
| 210 | } |
| 211 | |
| 212 | pub fn (mut f Fmt) struct_init(node ast.StructInit) { |
| 213 | struct_init_save := f.is_struct_init |
| 214 | f.is_struct_init = true |
| 215 | defer { |
| 216 | f.is_struct_init = struct_init_save |
| 217 | } |
| 218 | use_type_expr := node.typ_expr !is ast.EmptyExpr && node.typ == ast.void_type |
| 219 | sym_name := if use_type_expr { '' } else { f.table.sym(node.typ).name } |
| 220 | // f.write('<old name: ${type_sym.name}>') |
| 221 | mut name := if !sym_name.starts_with('C.') && !sym_name.starts_with('JS.') { |
| 222 | f.no_cur_mod(f.short_module(sym_name)) // TODO: f.type_to_str? |
| 223 | } else { |
| 224 | sym_name |
| 225 | } |
| 226 | if name == 'void' { |
| 227 | name = '' |
| 228 | } |
| 229 | if node.typ.has_flag(.option) { |
| 230 | f.write('?') |
| 231 | } |
| 232 | if node.is_anon { |
| 233 | // Write the full anonymous struct definition inline, e.g.: |
| 234 | // `struct { foo string; bar int }{}` |
| 235 | sym := f.table.sym(node.typ) |
| 236 | if sym.info is ast.Struct && sym.info.is_anon { |
| 237 | f.writeln('struct {') |
| 238 | f.indent++ |
| 239 | for field in sym.info.fields { |
| 240 | f.write(field.name) |
| 241 | f.write(' ') |
| 242 | f.write(f.table.type_to_str_using_aliases(field.typ, f.mod2alias)) |
| 243 | f.writeln('') |
| 244 | } |
| 245 | f.indent-- |
| 246 | f.write('}') |
| 247 | if node.init_fields.len == 0 && !node.has_update_expr { |
| 248 | f.write('{}') |
| 249 | } else { |
| 250 | f.writeln('{') |
| 251 | f.indent++ |
| 252 | for init_field in node.init_fields { |
| 253 | f.write('${init_field.name}: ') |
| 254 | f.expr(init_field.expr) |
| 255 | f.writeln('') |
| 256 | } |
| 257 | f.indent-- |
| 258 | f.write('}') |
| 259 | } |
| 260 | return |
| 261 | } |
| 262 | f.write('struct ') |
| 263 | } |
| 264 | if node.init_fields.len == 0 && !node.has_update_expr { |
| 265 | // `Foo{}` on one line if there are no fields or comments |
| 266 | if node.pre_comments.len == 0 { |
| 267 | if use_type_expr { |
| 268 | f.expr(node.typ_expr) |
| 269 | f.write('{}') |
| 270 | } else { |
| 271 | f.write('${name}{}') |
| 272 | } |
| 273 | } else { |
| 274 | if use_type_expr { |
| 275 | f.expr(node.typ_expr) |
| 276 | f.writeln('{') |
| 277 | } else { |
| 278 | f.writeln('${name}{') |
| 279 | } |
| 280 | f.comments(node.pre_comments, same_line: true, has_nl: true, level: .indent) |
| 281 | f.write('}') |
| 282 | } |
| 283 | } else if node.no_keys { |
| 284 | // `Foo{1,2,3}` (short syntax, no keys) |
| 285 | if use_type_expr { |
| 286 | f.expr(node.typ_expr) |
| 287 | f.write('{') |
| 288 | } else { |
| 289 | f.write('${name}{') |
| 290 | } |
| 291 | if node.has_update_expr { |
| 292 | f.write('...') |
| 293 | f.expr(node.update_expr) |
| 294 | f.write(', ') |
| 295 | } |
| 296 | for i, init_field in node.init_fields { |
| 297 | f.expr(init_field.expr) |
| 298 | if i < node.init_fields.len - 1 { |
| 299 | f.write(', ') |
| 300 | } |
| 301 | } |
| 302 | f.write('}') |
| 303 | } else { |
| 304 | use_short_args := f.use_short_fn_args && !node.has_update_expr |
| 305 | f.use_short_fn_args = false |
| 306 | mut single_line_fields := f.single_line_fields |
| 307 | f.single_line_fields = false |
| 308 | if node.pos.line_nr < node.pos.last_line || node.pre_comments.len > 0 { |
| 309 | single_line_fields = false |
| 310 | } |
| 311 | if !use_short_args || node.is_anon { |
| 312 | if use_type_expr { |
| 313 | f.expr(node.typ_expr) |
| 314 | f.write('{') |
| 315 | } else { |
| 316 | f.write('${name}{') |
| 317 | } |
| 318 | if single_line_fields { |
| 319 | f.write(' ') |
| 320 | } |
| 321 | } |
| 322 | fields_start := f.out.len |
| 323 | fields_loop: for { |
| 324 | if !single_line_fields { |
| 325 | if use_short_args && f.out.last() == ` ` { |
| 326 | // v Remove space at tail of line |
| 327 | // f(a, b, c, \n |
| 328 | // f1: 0\n |
| 329 | // f2: 1\n |
| 330 | // ) |
| 331 | f.out.go_back(1) |
| 332 | } |
| 333 | f.writeln('') |
| 334 | f.indent++ |
| 335 | } |
| 336 | f.comments(node.pre_comments, same_line: true, has_nl: true, level: .keep) |
| 337 | if node.has_update_expr { |
| 338 | f.write('...') |
| 339 | f.expr(node.update_expr) |
| 340 | if single_line_fields { |
| 341 | if node.init_fields.len > 0 { |
| 342 | f.write(', ') |
| 343 | } |
| 344 | } else { |
| 345 | f.writeln('') |
| 346 | } |
| 347 | f.comments(node.update_expr_comments, same_line: true, has_nl: true, level: .keep) |
| 348 | } |
| 349 | mut value_align := new_field_align(use_break_line: true) |
| 350 | mut comment_align := new_field_align(use_threshold: true) |
| 351 | for init_field in node.init_fields { |
| 352 | value_align.add_info(init_field.name.len, init_field.pos.line_nr, |
| 353 | init_field.has_break_line) |
| 354 | if init_field.end_comments.len > 0 { |
| 355 | comment_align.add_info(init_field.expr.str().len, init_field.pos.line_nr, |
| 356 | init_field.has_break_line) |
| 357 | } |
| 358 | } |
| 359 | for i, init_field in node.init_fields { |
| 360 | if i > 0 && init_field.has_prev_newline { |
| 361 | f.writeln('') |
| 362 | } |
| 363 | if init_field.pre_comments.len > 0 { |
| 364 | f.comments(init_field.pre_comments, has_nl: true, level: .keep) |
| 365 | } |
| 366 | f.write('${init_field.name}: ') |
| 367 | if !single_line_fields { |
| 368 | f.write(' '.repeat(value_align.max_len(init_field.pos.line_nr) - init_field.name.len)) |
| 369 | } |
| 370 | f.expr(init_field.expr) |
| 371 | if init_field.end_comments.len > 0 { |
| 372 | f.write(' '.repeat(comment_align.max_len(init_field.pos.line_nr) - |
| 373 | init_field.expr.str().len + 1)) |
| 374 | f.comments(init_field.end_comments, has_nl: false, level: .indent) |
| 375 | } |
| 376 | if single_line_fields { |
| 377 | if i < node.init_fields.len - 1 { |
| 378 | f.write(', ') |
| 379 | } |
| 380 | } else { |
| 381 | f.writeln('') |
| 382 | } |
| 383 | f.comments(init_field.next_comments, has_nl: true, level: .keep) |
| 384 | if single_line_fields && (init_field.end_comments.len > 0 |
| 385 | || init_field.next_comments.len > 0 |
| 386 | || !expr_is_single_line(init_field.expr) || f.line_len > max_len) { |
| 387 | single_line_fields = false |
| 388 | f.out.go_back_to(fields_start) |
| 389 | f.line_len = fields_start |
| 390 | f.remove_new_line() |
| 391 | continue fields_loop |
| 392 | } |
| 393 | } |
| 394 | break |
| 395 | } |
| 396 | if !single_line_fields { |
| 397 | f.indent-- |
| 398 | } |
| 399 | if !use_short_args || node.is_anon { |
| 400 | if single_line_fields { |
| 401 | f.write(' ') |
| 402 | } |
| 403 | f.write('}') |
| 404 | } |
| 405 | } |
| 406 | } |
| 407 | |