| 1 | // Copyright (c) 2026 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 | |
| 5 | module cleanc |
| 6 | |
| 7 | import v2.ast |
| 8 | |
| 9 | fn (mut g Gen) try_gen_orm_create_call(c_name string, call_args []ast.Expr) bool { |
| 10 | if call_args.len != 3 || !c_name.ends_with('__create') { |
| 11 | return false |
| 12 | } |
| 13 | if !orm_table_can_emit(call_args[1]) || !orm_table_fields_array_can_emit(call_args[2]) { |
| 14 | return false |
| 15 | } |
| 16 | g.sb.write_string('${c_name}(') |
| 17 | g.gen_call_arg(c_name, 0, call_args[0]) |
| 18 | g.sb.write_string(', ') |
| 19 | g.write_orm_table_c_expr(call_args[1]) |
| 20 | g.sb.write_string(', ') |
| 21 | g.write_orm_table_fields_array_c_expr(call_args[2]) |
| 22 | g.sb.write_string(')') |
| 23 | if c_name != '' { |
| 24 | g.mark_called_fn_name(c_name) |
| 25 | } |
| 26 | return true |
| 27 | } |
| 28 | |
| 29 | fn (mut g Gen) try_gen_orm_create_call_expr(node ast.CallExpr) bool { |
| 30 | if !orm_create_call_can_emit(node) { |
| 31 | return false |
| 32 | } |
| 33 | lhs := node.lhs as ast.SelectorExpr |
| 34 | name := g.orm_create_call_name(node) or { return false } |
| 35 | call_args := [ |
| 36 | lhs.lhs, |
| 37 | node.args[0], |
| 38 | node.args[1], |
| 39 | ] |
| 40 | return g.try_gen_orm_create_call(name, call_args) |
| 41 | } |
| 42 | |
| 43 | fn orm_create_call_can_emit(node ast.CallExpr) bool { |
| 44 | if node.args.len != 2 || node.lhs !is ast.SelectorExpr { |
| 45 | return false |
| 46 | } |
| 47 | lhs := node.lhs as ast.SelectorExpr |
| 48 | return lhs.rhs.name == 'create' && orm_table_can_emit(node.args[0]) |
| 49 | && orm_table_fields_array_can_emit(node.args[1]) |
| 50 | } |
| 51 | |
| 52 | fn (mut g Gen) orm_create_call_name(node ast.CallExpr) ?string { |
| 53 | if node.lhs !is ast.SelectorExpr { |
| 54 | return none |
| 55 | } |
| 56 | lhs := node.lhs as ast.SelectorExpr |
| 57 | method_name := sanitize_fn_ident(lhs.rhs.name) |
| 58 | mut name := g.resolve_call_name(node.lhs, node.args.len) |
| 59 | if name != '' { |
| 60 | return name |
| 61 | } |
| 62 | mut base_type := g.method_receiver_base_type(lhs.lhs) |
| 63 | if base_type == '' { |
| 64 | base_type = g.get_expr_type(lhs.lhs).trim_space().trim_right('*') |
| 65 | } |
| 66 | if base_type == '' { |
| 67 | base_type = g.receiver_local_base_type(lhs.lhs) |
| 68 | } |
| 69 | if name == '' && base_type != '' { |
| 70 | if embedded := g.resolve_method_on_embedded_receiver(base_type, method_name) { |
| 71 | name = embedded.method_c_name |
| 72 | } |
| 73 | } |
| 74 | if name == '' && base_type != '' { |
| 75 | name = '${base_type}__${method_name}' |
| 76 | } |
| 77 | if name == '' { |
| 78 | return none |
| 79 | } |
| 80 | return name |
| 81 | } |
| 82 | |
| 83 | fn (mut g Gen) orm_create_call_result_type(node ast.CallExpr) ?string { |
| 84 | if !orm_create_call_can_emit(node) { |
| 85 | return none |
| 86 | } |
| 87 | name := g.orm_create_call_name(node) or { return none } |
| 88 | if ret := g.fn_return_types[name] { |
| 89 | return ret |
| 90 | } |
| 91 | return none |
| 92 | } |
| 93 | |
| 94 | fn orm_table_can_emit(expr ast.Expr) bool { |
| 95 | init := orm_init_expr(expr) or { return false } |
| 96 | name_expr := orm_init_field_value(init, 'name') or { return false } |
| 97 | attrs_expr := orm_init_field_value(init, 'attrs') or { return false } |
| 98 | return orm_string_value(name_expr) != none && orm_vattribute_array_can_emit(attrs_expr) |
| 99 | } |
| 100 | |
| 101 | fn orm_table_fields_array_can_emit(expr ast.Expr) bool { |
| 102 | arr := orm_array_init_expr(expr) or { return false } |
| 103 | for field_expr in arr.exprs { |
| 104 | if !orm_table_field_can_emit(field_expr) { |
| 105 | return false |
| 106 | } |
| 107 | } |
| 108 | return true |
| 109 | } |
| 110 | |
| 111 | fn orm_table_field_can_emit(expr ast.Expr) bool { |
| 112 | init := orm_init_expr(expr) or { return false } |
| 113 | name_expr := orm_init_field_value(init, 'name') or { return false } |
| 114 | typ_expr := orm_init_field_value(init, 'typ') or { return false } |
| 115 | nullable_expr := orm_init_field_value(init, 'nullable') or { return false } |
| 116 | default_val_expr := orm_init_field_value(init, 'default_val') or { return false } |
| 117 | attrs_expr := orm_init_field_value(init, 'attrs') or { return false } |
| 118 | is_arr_expr := orm_init_field_value(init, 'is_arr') or { return false } |
| 119 | return orm_string_value(name_expr) != none && orm_number_c_expr(typ_expr) != none |
| 120 | && orm_bool_c_expr(nullable_expr) != none && orm_string_value(default_val_expr) != none |
| 121 | && orm_vattribute_array_can_emit(attrs_expr) && orm_bool_c_expr(is_arr_expr) != none |
| 122 | } |
| 123 | |
| 124 | fn orm_vattribute_array_can_emit(expr ast.Expr) bool { |
| 125 | arr := orm_array_init_expr(expr) or { return false } |
| 126 | for attr_expr in arr.exprs { |
| 127 | if !orm_vattribute_can_emit(attr_expr) { |
| 128 | return false |
| 129 | } |
| 130 | } |
| 131 | return true |
| 132 | } |
| 133 | |
| 134 | fn orm_vattribute_can_emit(expr ast.Expr) bool { |
| 135 | init := orm_init_expr(expr) or { return false } |
| 136 | name_expr := orm_init_field_value(init, 'name') or { return false } |
| 137 | has_arg_expr := orm_init_field_value(init, 'has_arg') or { return false } |
| 138 | arg_expr := orm_init_field_value(init, 'arg') or { return false } |
| 139 | kind_expr := orm_init_field_value(init, 'kind') or { return false } |
| 140 | return orm_string_value(name_expr) != none && orm_bool_c_expr(has_arg_expr) != none |
| 141 | && orm_string_value(arg_expr) != none && orm_ident_c_expr(kind_expr) != none |
| 142 | } |
| 143 | |
| 144 | fn (mut g Gen) write_orm_table_c_expr(expr ast.Expr) { |
| 145 | init := orm_init_expr(expr) or { return } |
| 146 | name_expr := orm_init_field_value(init, 'name') or { return } |
| 147 | attrs_expr := orm_init_field_value(init, 'attrs') or { return } |
| 148 | g.sb.write_string('((orm__Table){.name = ') |
| 149 | g.write_orm_v_string_c_expr(name_expr) |
| 150 | g.sb.write_string(',.attrs = ') |
| 151 | g.write_orm_vattribute_array_c_expr(attrs_expr) |
| 152 | g.sb.write_string('})') |
| 153 | } |
| 154 | |
| 155 | fn (mut g Gen) write_orm_table_fields_array_c_expr(expr ast.Expr) { |
| 156 | arr := orm_array_init_expr(expr) or { return } |
| 157 | g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(orm__TableField), ') |
| 158 | if arr.exprs.len == 0 { |
| 159 | g.sb.write_string('&(array){0})') |
| 160 | return |
| 161 | } |
| 162 | g.sb.write_string('&(orm__TableField[${arr.exprs.len}]){') |
| 163 | for i, field_expr in arr.exprs { |
| 164 | if i > 0 { |
| 165 | g.sb.write_string(', ') |
| 166 | } |
| 167 | g.write_orm_table_field_c_expr(field_expr) |
| 168 | } |
| 169 | g.sb.write_string('})') |
| 170 | } |
| 171 | |
| 172 | fn (mut g Gen) write_orm_table_field_c_expr(expr ast.Expr) { |
| 173 | init := orm_init_expr(expr) or { return } |
| 174 | name_expr := orm_init_field_value(init, 'name') or { return } |
| 175 | typ_expr := orm_init_field_value(init, 'typ') or { return } |
| 176 | nullable_expr := orm_init_field_value(init, 'nullable') or { return } |
| 177 | default_val_expr := orm_init_field_value(init, 'default_val') or { return } |
| 178 | attrs_expr := orm_init_field_value(init, 'attrs') or { return } |
| 179 | is_arr_expr := orm_init_field_value(init, 'is_arr') or { return } |
| 180 | g.sb.write_string('((orm__TableField){.name = ') |
| 181 | g.write_orm_v_string_c_expr(name_expr) |
| 182 | g.sb.write_string(',.typ = ') |
| 183 | g.sb.write_string(orm_number_c_expr(typ_expr) or { return }) |
| 184 | g.sb.write_string(',.nullable = ') |
| 185 | g.sb.write_string(orm_bool_c_expr(nullable_expr) or { return }) |
| 186 | g.sb.write_string(',.default_val = ') |
| 187 | g.write_orm_v_string_c_expr(default_val_expr) |
| 188 | g.sb.write_string(',.attrs = ') |
| 189 | g.write_orm_vattribute_array_c_expr(attrs_expr) |
| 190 | g.sb.write_string(',.is_arr = ') |
| 191 | g.sb.write_string(orm_bool_c_expr(is_arr_expr) or { return }) |
| 192 | g.sb.write_string('})') |
| 193 | } |
| 194 | |
| 195 | fn (mut g Gen) write_orm_vattribute_array_c_expr(expr ast.Expr) { |
| 196 | arr := orm_array_init_expr(expr) or { return } |
| 197 | if arr.exprs.len == 0 { |
| 198 | g.sb.write_string('new_array_from_c_array(0, 0, sizeof(VAttribute), &(array){0})') |
| 199 | return |
| 200 | } |
| 201 | g.sb.write_string('new_array_from_c_array(${arr.exprs.len}, ${arr.exprs.len}, sizeof(VAttribute), &(VAttribute[${arr.exprs.len}]){') |
| 202 | for i, attr_expr in arr.exprs { |
| 203 | if i > 0 { |
| 204 | g.sb.write_string(', ') |
| 205 | } |
| 206 | g.write_orm_vattribute_c_expr(attr_expr) |
| 207 | } |
| 208 | g.sb.write_string('})') |
| 209 | } |
| 210 | |
| 211 | fn (mut g Gen) write_orm_vattribute_c_expr(expr ast.Expr) { |
| 212 | init := orm_init_expr(expr) or { return } |
| 213 | name_expr := orm_init_field_value(init, 'name') or { return } |
| 214 | has_arg_expr := orm_init_field_value(init, 'has_arg') or { return } |
| 215 | arg_expr := orm_init_field_value(init, 'arg') or { return } |
| 216 | kind_expr := orm_init_field_value(init, 'kind') or { return } |
| 217 | g.sb.write_string('((VAttribute){.name = ') |
| 218 | g.write_orm_v_string_c_expr(name_expr) |
| 219 | g.sb.write_string(',.has_arg = ') |
| 220 | g.sb.write_string(orm_bool_c_expr(has_arg_expr) or { return }) |
| 221 | g.sb.write_string(',.arg = ') |
| 222 | g.write_orm_v_string_c_expr(arg_expr) |
| 223 | g.sb.write_string(',.kind = ') |
| 224 | g.sb.write_string(orm_ident_c_expr(kind_expr) or { return }) |
| 225 | g.sb.write_string('})') |
| 226 | } |
| 227 | |
| 228 | fn orm_init_expr(expr ast.Expr) ?ast.InitExpr { |
| 229 | match expr { |
| 230 | ast.InitExpr { |
| 231 | return expr |
| 232 | } |
| 233 | ast.ParenExpr { |
| 234 | return orm_init_expr(expr.expr) |
| 235 | } |
| 236 | else {} |
| 237 | } |
| 238 | |
| 239 | return none |
| 240 | } |
| 241 | |
| 242 | fn orm_array_init_expr(expr ast.Expr) ?ast.ArrayInitExpr { |
| 243 | match expr { |
| 244 | ast.ArrayInitExpr { |
| 245 | return expr |
| 246 | } |
| 247 | ast.CallExpr { |
| 248 | name := orm_call_name(expr.lhs) |
| 249 | if name in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array', 'new_array_from_c_array'] |
| 250 | && expr.args.len >= 4 { |
| 251 | return orm_array_init_expr(expr.args[3]) |
| 252 | } |
| 253 | } |
| 254 | ast.PrefixExpr { |
| 255 | if expr.op == .amp { |
| 256 | return orm_array_init_expr(expr.expr) |
| 257 | } |
| 258 | } |
| 259 | ast.ParenExpr { |
| 260 | return orm_array_init_expr(expr.expr) |
| 261 | } |
| 262 | else {} |
| 263 | } |
| 264 | |
| 265 | return none |
| 266 | } |
| 267 | |
| 268 | fn orm_call_name(lhs ast.Expr) string { |
| 269 | match lhs { |
| 270 | ast.Ident { |
| 271 | return lhs.name |
| 272 | } |
| 273 | ast.SelectorExpr { |
| 274 | if lhs.lhs is ast.Ident { |
| 275 | left := lhs.lhs as ast.Ident |
| 276 | return '${left.name}__${lhs.rhs.name}' |
| 277 | } |
| 278 | } |
| 279 | else {} |
| 280 | } |
| 281 | |
| 282 | return '' |
| 283 | } |
| 284 | |
| 285 | fn orm_init_field_value(init ast.InitExpr, name string) ?ast.Expr { |
| 286 | for field in init.fields { |
| 287 | if field.name == name { |
| 288 | return field.value |
| 289 | } |
| 290 | } |
| 291 | return none |
| 292 | } |
| 293 | |
| 294 | fn (mut g Gen) write_orm_v_string_c_expr(expr ast.Expr) { |
| 295 | raw := orm_string_value(expr) or { return } |
| 296 | g.sb.write_string(c_static_v_string_expr_from_c_literal(c_string_literal_content_to_c(raw))) |
| 297 | } |
| 298 | |
| 299 | fn orm_string_value(expr ast.Expr) ?string { |
| 300 | match expr { |
| 301 | ast.StringLiteral { |
| 302 | mut val := strip_literal_quotes(expr.value) |
| 303 | if expr.kind == .raw { |
| 304 | val = val.replace('\\', '\\\\') |
| 305 | } else { |
| 306 | val = process_line_continuations(val) |
| 307 | } |
| 308 | return val |
| 309 | } |
| 310 | ast.BasicLiteral { |
| 311 | if expr.kind == .string { |
| 312 | return process_line_continuations(strip_literal_quotes(expr.value)) |
| 313 | } |
| 314 | } |
| 315 | else {} |
| 316 | } |
| 317 | |
| 318 | return none |
| 319 | } |
| 320 | |
| 321 | fn orm_bool_c_expr(expr ast.Expr) ?string { |
| 322 | match expr { |
| 323 | ast.BasicLiteral { |
| 324 | if expr.kind == .key_true || expr.value == 'true' { |
| 325 | return 'true' |
| 326 | } |
| 327 | if expr.kind == .key_false || expr.value == 'false' { |
| 328 | return 'false' |
| 329 | } |
| 330 | } |
| 331 | ast.Ident { |
| 332 | if expr.name == 'true' || expr.name == 'false' { |
| 333 | return expr.name |
| 334 | } |
| 335 | } |
| 336 | else {} |
| 337 | } |
| 338 | |
| 339 | return none |
| 340 | } |
| 341 | |
| 342 | fn orm_number_c_expr(expr ast.Expr) ?string { |
| 343 | match expr { |
| 344 | ast.BasicLiteral { |
| 345 | return sanitize_c_number_literal(expr.value) |
| 346 | } |
| 347 | ast.PrefixExpr { |
| 348 | if expr.op == .minus && expr.expr is ast.BasicLiteral { |
| 349 | inner := expr.expr as ast.BasicLiteral |
| 350 | return '-' + sanitize_c_number_literal(inner.value) |
| 351 | } |
| 352 | } |
| 353 | else {} |
| 354 | } |
| 355 | |
| 356 | return none |
| 357 | } |
| 358 | |
| 359 | fn orm_ident_c_expr(expr ast.Expr) ?string { |
| 360 | match expr { |
| 361 | ast.Ident { |
| 362 | return expr.name |
| 363 | } |
| 364 | ast.SelectorExpr { |
| 365 | if expr.lhs is ast.Ident { |
| 366 | left := expr.lhs as ast.Ident |
| 367 | return '${left.name}__${expr.rhs.name}' |
| 368 | } |
| 369 | } |
| 370 | else {} |
| 371 | } |
| 372 | |
| 373 | return none |
| 374 | } |
| 375 | |