| 1 | module js |
| 2 | |
| 3 | import v.ast |
| 4 | import strings |
| 5 | |
| 6 | const special_array_methods = [ |
| 7 | 'sort', |
| 8 | 'insert', |
| 9 | 'prepend', |
| 10 | 'index', |
| 11 | 'last_index', |
| 12 | 'contains', |
| 13 | ] |
| 14 | |
| 15 | fn (mut g JsGen) gen_array_index_method(left_type ast.Type, is_last_index bool) string { |
| 16 | unwrap_left_type := g.unwrap_generic(left_type) |
| 17 | mut left_sym := g.table.sym(unwrap_left_type) |
| 18 | mut left_type_str := g.styp(unwrap_left_type).trim('*') |
| 19 | fn_name := if is_last_index { '${left_type_str}_last_index' } else { '${left_type_str}_index' } |
| 20 | |
| 21 | if (!is_last_index && !left_sym.has_method('index')) |
| 22 | || (is_last_index && !left_sym.has_method('last_index')) { |
| 23 | info := left_sym.info as ast.Array |
| 24 | elem_sym := g.table.sym(info.elem_type) |
| 25 | if elem_sym.kind == .function { |
| 26 | left_type_str = 'Array_voidptr' |
| 27 | } |
| 28 | |
| 29 | mut fn_builder := strings.new_builder(512) |
| 30 | fn_builder.writeln('function ${fn_name}(a, v) {') |
| 31 | fn_builder.writeln('\tlet pelem = a.arr;') |
| 32 | if is_last_index { |
| 33 | fn_builder.writeln('\tif (pelem.arr.length == 0) return new int(-1);') |
| 34 | fn_builder.writeln('\tfor (let i = pelem.arr.length - 1; i >=0; --i) {') |
| 35 | } else { |
| 36 | fn_builder.writeln('\tfor (let i = 0; i < pelem.arr.length; ++i) {') |
| 37 | } |
| 38 | if elem_sym.kind == .string { |
| 39 | fn_builder.writeln('\t\tif (pelem.get(new int(i)).str == v.str) {') |
| 40 | } else if elem_sym.kind == .array && !info.elem_type.is_ptr() { |
| 41 | ptr_typ := g.gen_array_equality_fn(info.elem_type) |
| 42 | fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq(pelem.get(new int(i)), v).val) {') |
| 43 | } else if elem_sym.kind == .function && !info.elem_type.is_ptr() { |
| 44 | fn_builder.writeln('\t\tif (pelem.get(new int(i)) == v) {') |
| 45 | } else if elem_sym.kind == .map && !info.elem_type.is_ptr() { |
| 46 | ptr_typ := g.gen_map_equality_fn(info.elem_type) |
| 47 | fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(pelem.get(new int(i)), v).val) {') |
| 48 | } else if elem_sym.kind == .struct && !info.elem_type.is_ptr() { |
| 49 | ptr_typ := g.gen_struct_equality_fn(info.elem_type) |
| 50 | fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq(pelem.get(new int(i)), v)) {') |
| 51 | } else { |
| 52 | fn_builder.writeln('\t\tif (vEq(pelem.get(new int(i)), v)) {') |
| 53 | } |
| 54 | fn_builder.writeln('\t\t\treturn new int(i);') |
| 55 | fn_builder.writeln('\t\t}') |
| 56 | fn_builder.writeln('\t}') |
| 57 | fn_builder.writeln('\treturn new int(-1);') |
| 58 | fn_builder.writeln('}') |
| 59 | g.definitions.writeln(fn_builder.str()) |
| 60 | left_sym.register_method(ast.Fn{ |
| 61 | name: 'index' |
| 62 | params: [ast.Param{ |
| 63 | typ: unwrap_left_type |
| 64 | }, ast.Param{ |
| 65 | typ: info.elem_type |
| 66 | }] |
| 67 | }) |
| 68 | } |
| 69 | |
| 70 | return fn_name |
| 71 | } |
| 72 | |
| 73 | fn (mut g JsGen) gen_array_method_call(it ast.CallExpr) { |
| 74 | node := it |
| 75 | |
| 76 | match node.name { |
| 77 | 'index' { |
| 78 | g.gen_array_index(node, false) |
| 79 | return |
| 80 | } |
| 81 | 'last_index' { |
| 82 | g.gen_array_index(node, true) |
| 83 | return |
| 84 | } |
| 85 | 'contains' { |
| 86 | g.gen_array_contains(node) |
| 87 | return |
| 88 | } |
| 89 | 'insert' { |
| 90 | g.write('array_') |
| 91 | arg2_sym := g.table.sym(node.args[1].typ) |
| 92 | is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type |
| 93 | if is_arg2_array { |
| 94 | g.write('insert_many(') |
| 95 | } else { |
| 96 | g.write('insert(') |
| 97 | } |
| 98 | g.expr(it.left) |
| 99 | mut ltyp := it.left_type |
| 100 | for ltyp.is_ptr() { |
| 101 | g.write('.val') |
| 102 | ltyp = ltyp.deref() |
| 103 | } |
| 104 | g.write(',') |
| 105 | g.expr(node.args[0].expr) |
| 106 | g.write(',') |
| 107 | if is_arg2_array { |
| 108 | g.expr(node.args[1].expr) |
| 109 | g.write('.arr,') |
| 110 | g.expr(node.args[1].expr) |
| 111 | g.write('.len') |
| 112 | } else { |
| 113 | g.expr(node.args[1].expr) |
| 114 | } |
| 115 | g.write(')') |
| 116 | return |
| 117 | } |
| 118 | 'prepend' { |
| 119 | g.write('array_') |
| 120 | arg_sym := g.table.sym(node.args[0].typ) |
| 121 | is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type |
| 122 | if is_arg_array { |
| 123 | g.write('prepend_many(') |
| 124 | } else { |
| 125 | g.write('prepend(') |
| 126 | } |
| 127 | g.expr(it.left) |
| 128 | mut ltyp := it.left_type |
| 129 | for ltyp.is_ptr() { |
| 130 | g.write('.val') |
| 131 | ltyp = ltyp.deref() |
| 132 | } |
| 133 | g.write(',') |
| 134 | if is_arg_array { |
| 135 | g.expr(node.args[0].expr) |
| 136 | g.write('.arr, ') |
| 137 | g.expr(node.args[0].expr) |
| 138 | g.write('.len') |
| 139 | } else { |
| 140 | g.expr(node.args[0].expr) |
| 141 | } |
| 142 | g.write(')') |
| 143 | return |
| 144 | } |
| 145 | 'sort' { |
| 146 | g.gen_array_sort(node) |
| 147 | } |
| 148 | else {} |
| 149 | } |
| 150 | } |
| 151 | |
| 152 | fn (mut g JsGen) gen_array_index(node ast.CallExpr, is_last_index bool) { |
| 153 | fn_name := g.gen_array_index_method(node.left_type, is_last_index) |
| 154 | g.write('${fn_name}(') |
| 155 | g.expr(node.left) |
| 156 | g.gen_deref_ptr(node.left_type) |
| 157 | g.write(',') |
| 158 | g.expr(node.args[0].expr) |
| 159 | g.write(')') |
| 160 | } |
| 161 | |
| 162 | fn (mut g JsGen) gen_array_contains(node ast.CallExpr) { |
| 163 | fn_name := g.gen_array_contains_method(node.left_type) |
| 164 | g.write('${fn_name}(') |
| 165 | g.expr(node.left) |
| 166 | g.gen_deref_ptr(node.left_type) |
| 167 | g.write(',') |
| 168 | g.expr(node.args[0].expr) |
| 169 | g.write(')') |
| 170 | } |
| 171 | |
| 172 | fn (mut g JsGen) gen_array_contains_method(left_type ast.Type) string { |
| 173 | mut unwrap_left_type := g.unwrap_generic(left_type) |
| 174 | if unwrap_left_type.share() == .shared_t { |
| 175 | unwrap_left_type = unwrap_left_type.clear_flag(.shared_f) |
| 176 | } |
| 177 | mut left_sym := g.table.sym(unwrap_left_type) |
| 178 | left_final_sym := g.table.final_sym(unwrap_left_type) |
| 179 | mut left_type_str := g.styp(unwrap_left_type).replace('*', '') |
| 180 | fn_name := '${left_type_str}_contains' |
| 181 | if !left_sym.has_method('contains') { |
| 182 | left_info := left_final_sym.info as ast.Array |
| 183 | elem_sym := g.table.sym(left_info.elem_type) |
| 184 | if elem_sym.kind == .function { |
| 185 | left_type_str = 'Array_voidptr' |
| 186 | } |
| 187 | |
| 188 | mut fn_builder := strings.new_builder(512) |
| 189 | fn_builder.writeln('function ${fn_name}(a,v) {') |
| 190 | fn_builder.writeln('\tfor (let i = 0; i < a.len; ++i) {') |
| 191 | if elem_sym.kind == .string { |
| 192 | fn_builder.writeln('\t\tif (a.arr.get(new int(i)).str == v.str) {') |
| 193 | } else if elem_sym.kind == .array && left_info.elem_type.nr_muls() == 0 { |
| 194 | ptr_typ := g.gen_array_equality_fn(left_info.elem_type) |
| 195 | fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq(a.arr.get(new int(i)),v).val) {') |
| 196 | } else if elem_sym.kind == .function { |
| 197 | fn_builder.writeln('\t\tif (a.arr.get(new int(i)) == v) {') |
| 198 | } else if elem_sym.kind == .map && left_info.elem_type.nr_muls() == 0 { |
| 199 | ptr_typ := g.gen_map_equality_fn(left_info.elem_type) |
| 200 | fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(a.arr.get(new int(i)),v).val) {') |
| 201 | } else if elem_sym.kind == .struct && left_info.elem_type.nr_muls() == 0 { |
| 202 | ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) |
| 203 | fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq(a.arr.get(new int(i)),v).val) {') |
| 204 | } else { |
| 205 | fn_builder.writeln('\t\tif (vEq(a.arr.get(new int(i)),v)) {') |
| 206 | } |
| 207 | fn_builder.writeln('\t\t\treturn new bool(true);') |
| 208 | fn_builder.writeln('\t\t}') |
| 209 | fn_builder.writeln('\t}') |
| 210 | fn_builder.writeln('\treturn new bool(false);') |
| 211 | fn_builder.writeln('}') |
| 212 | g.definitions.writeln(fn_builder.str()) |
| 213 | left_sym.register_method(ast.Fn{ |
| 214 | name: 'contains' |
| 215 | params: [ast.Param{ |
| 216 | typ: unwrap_left_type |
| 217 | }, ast.Param{ |
| 218 | typ: left_info.elem_type |
| 219 | }] |
| 220 | }) |
| 221 | } |
| 222 | return fn_name |
| 223 | } |
| 224 | |
| 225 | fn (mut g JsGen) gen_array_sort(node ast.CallExpr) { |
| 226 | rec_sym := g.table.sym(node.receiver_type) |
| 227 | if rec_sym.kind != .array { |
| 228 | println(node.name) |
| 229 | verror('.sort() is an array method') |
| 230 | } |
| 231 | |
| 232 | info := rec_sym.info as ast.Array |
| 233 | |
| 234 | elem_stype := g.styp(info.elem_type) |
| 235 | mut compare_fn := 'compare_${elem_stype.replace('*', '_ptr')}' |
| 236 | mut comparison_type := g.unwrap(ast.void_type) |
| 237 | mut left_expr, mut right_expr := '', '' |
| 238 | |
| 239 | if node.args.len == 0 { |
| 240 | comparison_type = g.unwrap(info.elem_type.set_nr_muls(0)) |
| 241 | if compare_fn in g.array_sort_fn { |
| 242 | g.gen_array_sort_call(node, compare_fn) |
| 243 | return |
| 244 | } |
| 245 | |
| 246 | left_expr = 'a' |
| 247 | right_expr = 'b' |
| 248 | } else { |
| 249 | infix_expr := node.args[0].expr as ast.InfixExpr |
| 250 | comparison_type = g.unwrap(infix_expr.left_type.set_nr_muls(0)) |
| 251 | left_name := infix_expr.left.str() |
| 252 | if left_name.len > 1 { |
| 253 | compare_fn += '_by' + left_name[1..].replace_each(['.', '_', '[', '_', ']', '_']) |
| 254 | } |
| 255 | // is_reverse is `true` for `.sort(a > b)` and `.sort(b < a)` |
| 256 | is_reverse := (left_name.starts_with('a') && infix_expr.op == .gt) |
| 257 | || (left_name.starts_with('b') && infix_expr.op == .lt) |
| 258 | if is_reverse { |
| 259 | compare_fn += '_reverse' |
| 260 | } |
| 261 | if compare_fn in g.array_sort_fn { |
| 262 | g.gen_array_sort_call(node, compare_fn) |
| 263 | return |
| 264 | } |
| 265 | if left_name.starts_with('a') != is_reverse { |
| 266 | left_expr = g.expr_string(infix_expr.left) |
| 267 | right_expr = g.expr_string(infix_expr.right) |
| 268 | } else { |
| 269 | left_expr = g.expr_string(infix_expr.right) |
| 270 | right_expr = g.expr_string(infix_expr.left) |
| 271 | } |
| 272 | } |
| 273 | |
| 274 | // Register a new custom `compare_xxx` function for qsort() |
| 275 | // TODO: move to checker |
| 276 | g.table.register_fn(name: compare_fn, return_type: ast.int_type) |
| 277 | g.array_sort_fn[compare_fn] = true |
| 278 | |
| 279 | g.definitions.writeln('function ${compare_fn}(a,b) {') |
| 280 | g.definitions.writeln('\ta = new \$ref(a);') |
| 281 | g.definitions.writeln('\tb = new \$ref(b);') |
| 282 | c_condition := if comparison_type.sym.has_method('<') { |
| 283 | '${g.styp(comparison_type.typ)}__lt(${left_expr}, ${right_expr})' |
| 284 | } else if comparison_type.unaliased_sym.has_method('<') { |
| 285 | '${g.styp(comparison_type.unaliased)}__lt(${left_expr}, ${right_expr})' |
| 286 | } else { |
| 287 | '${left_expr}.valueOf() < ${right_expr}.valueOf()' |
| 288 | } |
| 289 | g.definitions.writeln('\tif (${c_condition}) return -1;') |
| 290 | g.definitions.writeln('\telse return 1;') |
| 291 | g.definitions.writeln('}\n') |
| 292 | |
| 293 | // write call to the generated function |
| 294 | g.gen_array_sort_call(node, compare_fn) |
| 295 | } |
| 296 | |
| 297 | fn (mut g JsGen) gen_array_sort_call(node ast.CallExpr, compare_fn string) { |
| 298 | g.write('v_sort(') |
| 299 | g.expr(node.left) |
| 300 | g.gen_deref_ptr(node.left_type) |
| 301 | g.write(',${compare_fn})') |
| 302 | } |
| 303 | |