cgen: move logic from cgen.v to auto_eq_methods.v and array.v (#7572)
							parent
							
								
									905a02ef1a
								
							
						
					
					
						commit
						79117f5581
					
				|  | @ -0,0 +1,531 @@ | |||
| // Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
 | ||||
| // Use of this source code is governed by an MIT license that can be found in the LICENSE file.
 | ||||
| module gen | ||||
| 
 | ||||
| import strings | ||||
| import v.ast | ||||
| import v.table | ||||
| 
 | ||||
| fn (mut g Gen) array_init(it ast.ArrayInit) { | ||||
| 	type_sym := g.table.get_type_symbol(it.typ) | ||||
| 	styp := g.typ(it.typ) | ||||
| 	mut shared_styp := '' // only needed for shared &[]{...}
 | ||||
| 	is_amp := g.is_amp | ||||
| 	g.is_amp = false | ||||
| 	if is_amp { | ||||
| 		g.out.go_back(1) // delete the `&` already generated in `prefix_expr()
 | ||||
| 		if g.is_shared { | ||||
| 			mut shared_typ := it.typ.set_flag(.shared_f) | ||||
| 			shared_styp = g.typ(shared_typ) | ||||
| 			g.writeln('($shared_styp*)memdup(&($shared_styp){.val = ') | ||||
| 		} else { | ||||
| 			g.write('($styp*)memdup(&') // TODO: doesn't work with every compiler
 | ||||
| 		} | ||||
| 	} else { | ||||
| 		if g.is_shared { | ||||
| 			g.writeln('{.val = ($styp*)') | ||||
| 		} | ||||
| 	} | ||||
| 	if type_sym.kind == .array_fixed { | ||||
| 		g.write('{') | ||||
| 		if it.has_val { | ||||
| 			for i, expr in it.exprs { | ||||
| 				g.expr(expr) | ||||
| 				if i != it.exprs.len - 1 { | ||||
| 					g.write(', ') | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			g.write('0') | ||||
| 		} | ||||
| 		g.write('}') | ||||
| 		return | ||||
| 	} | ||||
| 	elem_type_str := g.typ(it.elem_type) | ||||
| 	if it.exprs.len == 0 { | ||||
| 		elem_sym := g.table.get_type_symbol(it.elem_type) | ||||
| 		is_default_array := elem_sym.kind == .array && it.has_default | ||||
| 		if is_default_array { | ||||
| 			g.write('__new_array_with_array_default(') | ||||
| 		} else { | ||||
| 			g.write('__new_array_with_default(') | ||||
| 		} | ||||
| 		if it.has_len { | ||||
| 			g.expr(it.len_expr) | ||||
| 			g.write(', ') | ||||
| 		} else { | ||||
| 			g.write('0, ') | ||||
| 		} | ||||
| 		if it.has_cap { | ||||
| 			g.expr(it.cap_expr) | ||||
| 			g.write(', ') | ||||
| 		} else { | ||||
| 			g.write('0, ') | ||||
| 		} | ||||
| 		if elem_sym.kind == .function { | ||||
| 			g.write('sizeof(voidptr), ') | ||||
| 		} else { | ||||
| 			g.write('sizeof($elem_type_str), ') | ||||
| 		} | ||||
| 		if is_default_array { | ||||
| 			g.write('($elem_type_str[]){') | ||||
| 			g.expr(it.default_expr) | ||||
| 			g.write('}[0])') | ||||
| 		} else if it.has_default { | ||||
| 			g.write('&($elem_type_str[]){') | ||||
| 			g.expr(it.default_expr) | ||||
| 			g.write('})') | ||||
| 		} else if it.has_len && it.elem_type == table.string_type { | ||||
| 			g.write('&($elem_type_str[]){') | ||||
| 			g.write('_SLIT("")') | ||||
| 			g.write('})') | ||||
| 		} else { | ||||
| 			g.write('0)') | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	len := it.exprs.len | ||||
| 	elem_sym := g.table.get_type_symbol(it.elem_type) | ||||
| 	if elem_sym.kind == .function { | ||||
| 		g.write('new_array_from_c_array($len, $len, sizeof(voidptr), _MOV((voidptr[$len]){') | ||||
| 	} else { | ||||
| 		g.write('new_array_from_c_array($len, $len, sizeof($elem_type_str), _MOV(($elem_type_str[$len]){') | ||||
| 	} | ||||
| 	if len > 8 { | ||||
| 		g.writeln('') | ||||
| 		g.write('\t\t') | ||||
| 	} | ||||
| 	for i, expr in it.exprs { | ||||
| 		if it.is_interface { | ||||
| 			// sym := g.table.get_type_symbol(it.expr_types[i])
 | ||||
| 			// isym := g.table.get_type_symbol(it.interface_type)
 | ||||
| 			g.interface_call(it.expr_types[i], it.interface_type) | ||||
| 		} | ||||
| 		g.expr_with_cast(expr, it.expr_types[i], it.elem_type) | ||||
| 		if it.is_interface { | ||||
| 			g.write(')') | ||||
| 		} | ||||
| 		if i != len - 1 { | ||||
| 			g.write(', ') | ||||
| 		} | ||||
| 	} | ||||
| 	g.write('}))') | ||||
| 	if g.is_shared { | ||||
| 		g.write(', .mtx = sync__new_rwmutex()}') | ||||
| 		if is_amp { | ||||
| 			g.write(', sizeof($shared_styp))') | ||||
| 		} | ||||
| 	} else if is_amp { | ||||
| 		g.write(', sizeof($styp))') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // `nums.map(it % 2 == 0)`
 | ||||
| fn (mut g Gen) gen_array_map(node ast.CallExpr) { | ||||
| 	g.inside_lambda = true | ||||
| 	tmp := g.new_tmp_var() | ||||
| 	s := g.go_before_stmt(0) | ||||
| 	// println('filter s="$s"')
 | ||||
| 	ret_typ := g.typ(node.return_type) | ||||
| 	// inp_typ := g.typ(node.receiver_type)
 | ||||
| 	ret_sym := g.table.get_type_symbol(node.return_type) | ||||
| 	inp_sym := g.table.get_type_symbol(node.receiver_type) | ||||
| 	ret_info := ret_sym.info as table.Array | ||||
| 	ret_elem_type := g.typ(ret_info.elem_type) | ||||
| 	inp_info := inp_sym.info as table.Array | ||||
| 	inp_elem_type := g.typ(inp_info.elem_type) | ||||
| 	if inp_sym.kind != .array { | ||||
| 		verror('map() requires an array') | ||||
| 	} | ||||
| 	g.write('${g.typ(node.left_type)} ${tmp}_orig = ') | ||||
| 	g.expr(node.left) | ||||
| 	g.writeln(';') | ||||
| 	g.write('int ${tmp}_len = ${tmp}_orig.len;') | ||||
| 	g.writeln('$ret_typ $tmp = __new_array(0, ${tmp}_len, sizeof($ret_elem_type));') | ||||
| 	i := g.new_tmp_var() | ||||
| 	g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') | ||||
| 	g.write('\t$inp_elem_type it = (($inp_elem_type*) ${tmp}_orig.data)[$i];') | ||||
| 	g.write('\t$ret_elem_type ti = ') | ||||
| 	expr := node.args[0].expr | ||||
| 	match expr { | ||||
| 		ast.AnonFn { | ||||
| 			g.gen_anon_fn_decl(expr) | ||||
| 			g.write('${expr.decl.name}(it)') | ||||
| 		} | ||||
| 		ast.Ident { | ||||
| 			if expr.kind == .function { | ||||
| 				g.write('${c_name(expr.name)}(it)') | ||||
| 			} else if expr.kind == .variable { | ||||
| 				var_info := expr.var_info() | ||||
| 				sym := g.table.get_type_symbol(var_info.typ) | ||||
| 				if sym.kind == .function { | ||||
| 					g.write('${c_name(expr.name)}(it)') | ||||
| 				} else { | ||||
| 					g.expr(node.args[0].expr) | ||||
| 				} | ||||
| 			} else { | ||||
| 				g.expr(node.args[0].expr) | ||||
| 			} | ||||
| 		} | ||||
| 		else { | ||||
| 			g.expr(node.args[0].expr) | ||||
| 		} | ||||
| 	} | ||||
| 	g.writeln(';') | ||||
| 	g.writeln('\tarray_push(&$tmp, &ti);') | ||||
| 	g.writeln('}') | ||||
| 	g.write(s) | ||||
| 	g.write(tmp) | ||||
| 	g.inside_lambda = false | ||||
| } | ||||
| 
 | ||||
| // `users.sort(a.age < b.age)`
 | ||||
| fn (mut g Gen) gen_array_sort(node ast.CallExpr) { | ||||
| 	// println('filter s="$s"')
 | ||||
| 	rec_sym := g.table.get_type_symbol(node.receiver_type) | ||||
| 	if rec_sym.kind != .array { | ||||
| 		println(node.name) | ||||
| 		println(g.typ(node.receiver_type)) | ||||
| 		// println(rec_sym.kind)
 | ||||
| 		verror('.sort() is an array method') | ||||
| 	} | ||||
| 	info := rec_sym.info as table.Array | ||||
| 	// No arguments means we are sorting an array of builtins (e.g. `numbers.sort()`)
 | ||||
| 	// The type for the comparison fns is the type of the element itself.
 | ||||
| 	mut typ := info.elem_type | ||||
| 	mut is_reverse := false | ||||
| 	// `users.sort(a.age > b.age)`
 | ||||
| 	if node.args.len > 0 { | ||||
| 		// Get the type of the field that's being compared
 | ||||
| 		// `a.age > b.age` => `age int` => int
 | ||||
| 		infix_expr := node.args[0].expr as ast.InfixExpr | ||||
| 		// typ = infix_expr.left_type
 | ||||
| 		is_reverse = infix_expr.op == .gt | ||||
| 	} | ||||
| 	mut compare_fn := '' | ||||
| 	match typ { | ||||
| 		table.int_type { | ||||
| 			compare_fn = 'compare_ints' | ||||
| 		} | ||||
| 		table.u64_type { | ||||
| 			compare_fn = 'compare_u64s' | ||||
| 		} | ||||
| 		table.string_type { | ||||
| 			compare_fn = 'compare_strings' | ||||
| 		} | ||||
| 		table.f64_type { | ||||
| 			compare_fn = 'compare_floats' | ||||
| 		} | ||||
| 		else { | ||||
| 			// Generate a comparison function for a custom type
 | ||||
| 			if node.args.len == 0 { | ||||
| 				verror('usage: .sort(a.field < b.field)') | ||||
| 			} | ||||
| 			// verror('sort(): unhandled type $typ $q.name')
 | ||||
| 			tmp_name := g.new_tmp_var() | ||||
| 			compare_fn = 'compare_${tmp_name}_' + g.typ(typ) | ||||
| 			if is_reverse { | ||||
| 				compare_fn += '_reverse' | ||||
| 			} | ||||
| 			// Register a new custom `compare_xxx` function for qsort()
 | ||||
| 			g.table.register_fn(name: compare_fn, return_type: table.int_type) | ||||
| 			infix_expr := node.args[0].expr as ast.InfixExpr | ||||
| 			styp := g.typ(typ) | ||||
| 			// Variables `a` and `b` are used in the `.sort(a < b)` syntax, so we can reuse them
 | ||||
| 			// when generating the function as long as the args are named the same.
 | ||||
| 			g.definitions.writeln('int $compare_fn ($styp* a, $styp* b) {') | ||||
| 			field_type := g.typ(infix_expr.left_type) | ||||
| 			left_expr_str := g.write_expr_to_string(infix_expr.left).replace_once('.', | ||||
| 				'->') | ||||
| 			right_expr_str := g.write_expr_to_string(infix_expr.right).replace_once('.', | ||||
| 				'->') | ||||
| 			g.definitions.writeln('$field_type a_ = $left_expr_str;') | ||||
| 			g.definitions.writeln('$field_type b_ = $right_expr_str;') | ||||
| 			mut op1, mut op2 := '', '' | ||||
| 			if infix_expr.left_type == table.string_type { | ||||
| 				if is_reverse { | ||||
| 					op1 = 'string_gt(a_, b_)' | ||||
| 					op2 = 'string_lt(a_, b_)' | ||||
| 				} else { | ||||
| 					op1 = 'string_lt(a_, b_)' | ||||
| 					op2 = 'string_gt(a_, b_)' | ||||
| 				} | ||||
| 			} else { | ||||
| 				if is_reverse { | ||||
| 					op1 = 'a_ > b_' | ||||
| 					op2 = 'a_ < b_' | ||||
| 				} else { | ||||
| 					op1 = 'a_ < b_' | ||||
| 					op2 = 'a_ > b_' | ||||
| 				} | ||||
| 			} | ||||
| 			g.definitions.writeln('if ($op1) return -1;') | ||||
| 			g.definitions.writeln('if ($op2) return 1; return 0; }\n') | ||||
| 		} | ||||
| 	} | ||||
| 	if is_reverse && !compare_fn.ends_with('_reverse') { | ||||
| 		compare_fn += '_reverse' | ||||
| 	} | ||||
| 	//
 | ||||
| 	deref := if node.left_type.is_ptr() || node.left_type.is_pointer() { '->' } else { '.' } | ||||
| 	// eprintln('> qsort: pointer $node.left_type | deref: `$deref`')
 | ||||
| 	g.write('qsort(') | ||||
| 	g.expr(node.left) | ||||
| 	g.write('${deref}data, ') | ||||
| 	g.expr(node.left) | ||||
| 	g.write('${deref}len, ') | ||||
| 	g.expr(node.left) | ||||
| 	g.writeln('${deref}element_size, (int (*)(const void *, const void *))&$compare_fn);') | ||||
| } | ||||
| 
 | ||||
| // `nums.filter(it % 2 == 0)`
 | ||||
| fn (mut g Gen) gen_array_filter(node ast.CallExpr) { | ||||
| 	tmp := g.new_tmp_var() | ||||
| 	s := g.go_before_stmt(0) | ||||
| 	// println('filter s="$s"')
 | ||||
| 	sym := g.table.get_type_symbol(node.return_type) | ||||
| 	if sym.kind != .array { | ||||
| 		verror('filter() requires an array') | ||||
| 	} | ||||
| 	info := sym.info as table.Array | ||||
| 	styp := g.typ(node.return_type) | ||||
| 	elem_type_str := g.typ(info.elem_type) | ||||
| 	g.write('${g.typ(node.left_type)} ${tmp}_orig = ') | ||||
| 	g.expr(node.left) | ||||
| 	g.writeln(';') | ||||
| 	g.write('int ${tmp}_len = ${tmp}_orig.len;') | ||||
| 	g.writeln('$styp $tmp = __new_array(0, ${tmp}_len, sizeof($elem_type_str));') | ||||
| 	i := g.new_tmp_var() | ||||
| 	g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') | ||||
| 	g.writeln('  $elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') | ||||
| 	g.write('if (') | ||||
| 	expr := node.args[0].expr | ||||
| 	match expr { | ||||
| 		ast.AnonFn { | ||||
| 			g.gen_anon_fn_decl(expr) | ||||
| 			g.write('${expr.decl.name}(it)') | ||||
| 		} | ||||
| 		ast.Ident { | ||||
| 			if expr.kind == .function { | ||||
| 				g.write('${c_name(expr.name)}(it)') | ||||
| 			} else if expr.kind == .variable { | ||||
| 				var_info := expr.var_info() | ||||
| 				sym_t := g.table.get_type_symbol(var_info.typ) | ||||
| 				if sym_t.kind == .function { | ||||
| 					g.write('${c_name(expr.name)}(it)') | ||||
| 				} else { | ||||
| 					g.expr(node.args[0].expr) | ||||
| 				} | ||||
| 			} else { | ||||
| 				g.expr(node.args[0].expr) | ||||
| 			} | ||||
| 		} | ||||
| 		else { | ||||
| 			g.expr(node.args[0].expr) | ||||
| 		} | ||||
| 	} | ||||
| 	g.writeln(') array_push(&$tmp, &it); \n }') | ||||
| 	g.write(s) | ||||
| 	g.write(' ') | ||||
| 	g.write(tmp) | ||||
| } | ||||
| 
 | ||||
| // `nums.insert(0, 2)` `nums.insert(0, [2,3,4])`
 | ||||
| fn (mut g Gen) gen_array_insert(node ast.CallExpr) { | ||||
| 	left_sym := g.table.get_type_symbol(node.left_type) | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	elem_type_str := g.typ(left_info.elem_type) | ||||
| 	arg2_sym := g.table.get_type_symbol(node.args[1].typ) | ||||
| 	is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type | ||||
| 	if is_arg2_array { | ||||
| 		g.write('array_insert_many(&') | ||||
| 	} else { | ||||
| 		g.write('array_insert(&') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	g.write(', ') | ||||
| 	g.expr(node.args[0].expr) | ||||
| 	if is_arg2_array { | ||||
| 		g.write(', ') | ||||
| 		g.expr(node.args[1].expr) | ||||
| 		g.write('.data, ') | ||||
| 		g.expr(node.args[1].expr) | ||||
| 		g.write('.len)') | ||||
| 	} else { | ||||
| 		g.write(', &($elem_type_str[]){') | ||||
| 		if left_info.elem_type == table.string_type { | ||||
| 			g.write('string_clone(') | ||||
| 		} | ||||
| 		g.expr(node.args[1].expr) | ||||
| 		if left_info.elem_type == table.string_type { | ||||
| 			g.write(')') | ||||
| 		} | ||||
| 		g.write('})') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // `nums.prepend(2)` `nums.prepend([2,3,4])`
 | ||||
| fn (mut g Gen) gen_array_prepend(node ast.CallExpr) { | ||||
| 	left_sym := g.table.get_type_symbol(node.left_type) | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	elem_type_str := g.typ(left_info.elem_type) | ||||
| 	arg_sym := g.table.get_type_symbol(node.args[0].typ) | ||||
| 	is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type | ||||
| 	if is_arg_array { | ||||
| 		g.write('array_prepend_many(&') | ||||
| 	} else { | ||||
| 		g.write('array_prepend(&') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	if is_arg_array { | ||||
| 		g.write(', ') | ||||
| 		g.expr(node.args[0].expr) | ||||
| 		g.write('.data, ') | ||||
| 		g.expr(node.args[0].expr) | ||||
| 		g.write('.len)') | ||||
| 	} else { | ||||
| 		g.write(', &($elem_type_str[]){') | ||||
| 		g.expr(node.args[0].expr) | ||||
| 		g.write('})') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_array_contains_method(left_type table.Type) string { | ||||
| 	mut left_sym := g.table.get_type_symbol(left_type) | ||||
| 	mut left_type_str := g.typ(left_type).replace('*', '') | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	mut elem_type_str := g.typ(left_info.elem_type) | ||||
| 	elem_sym := g.table.get_type_symbol(left_info.elem_type) | ||||
| 	if elem_sym.kind == .function { | ||||
| 		left_type_str = 'array_voidptr' | ||||
| 		elem_type_str = 'voidptr' | ||||
| 	} | ||||
| 	fn_name := '${left_type_str}_contains' | ||||
| 	if !left_sym.has_method('contains') { | ||||
| 		g.type_definitions.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v); // auto') | ||||
| 		mut fn_builder := strings.new_builder(512) | ||||
| 		fn_builder.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v) {') | ||||
| 		fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') | ||||
| 		match elem_sym.kind { | ||||
| 			.string { | ||||
| 				fn_builder.writeln('\t\tif (string_eq((*(string*)array_get(a, i)), v)) {') | ||||
| 			} | ||||
| 			.array { | ||||
| 				ptr_typ := g.gen_array_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.function { | ||||
| 				fn_builder.writeln('\t\tif ((*(voidptr*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 			.map { | ||||
| 				ptr_typ := g.gen_map_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.struct_ { | ||||
| 				ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			else { | ||||
| 				fn_builder.writeln('\t\tif ((*($elem_type_str*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln('\t\t\treturn true;') | ||||
| 		fn_builder.writeln('\t\t}') | ||||
| 		fn_builder.writeln('\t}') | ||||
| 		fn_builder.writeln('\treturn false;') | ||||
| 		fn_builder.writeln('}') | ||||
| 		g.auto_fn_definitions << fn_builder.str() | ||||
| 		left_sym.register_method(&table.Fn{ | ||||
| 			name: 'contains' | ||||
| 			params: [table.Param{ | ||||
| 				typ: left_type | ||||
| 			}, table.Param{ | ||||
| 				typ: left_info.elem_type | ||||
| 			}] | ||||
| 		}) | ||||
| 	} | ||||
| 	return fn_name | ||||
| } | ||||
| 
 | ||||
| // `nums.contains(2)`
 | ||||
| fn (mut g Gen) gen_array_contains(node ast.CallExpr) { | ||||
| 	fn_name := g.gen_array_contains_method(node.left_type) | ||||
| 	g.write('${fn_name}(') | ||||
| 	if node.left_type.is_ptr() { | ||||
| 		g.write('*') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	g.write(', ') | ||||
| 	g.expr(node.args[0].expr) | ||||
| 	g.write(')') | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_array_index_method(left_type table.Type) string { | ||||
| 	mut left_sym := g.table.get_type_symbol(left_type) | ||||
| 	mut left_type_str := g.typ(left_type).replace('*', '') | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	mut elem_type_str := g.typ(left_info.elem_type) | ||||
| 	elem_sym := g.table.get_type_symbol(left_info.elem_type) | ||||
| 	if elem_sym.kind == .function { | ||||
| 		left_type_str = 'array_voidptr' | ||||
| 		elem_type_str = 'voidptr' | ||||
| 	} | ||||
| 	fn_name := '${left_type_str}_index' | ||||
| 	if !left_sym.has_method('index') { | ||||
| 		g.type_definitions.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v); // auto') | ||||
| 		mut fn_builder := strings.new_builder(512) | ||||
| 		fn_builder.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v) {') | ||||
| 		fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') | ||||
| 		match elem_sym.kind { | ||||
| 			.string { | ||||
| 				fn_builder.writeln('\t\tif (string_eq((*(string*)array_get(a, i)), v)) {') | ||||
| 			} | ||||
| 			.array { | ||||
| 				ptr_typ := g.gen_array_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.function { | ||||
| 				fn_builder.writeln('\t\tif ((*(voidptr*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 			.map { | ||||
| 				ptr_typ := g.gen_map_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.struct_ { | ||||
| 				ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			else { | ||||
| 				fn_builder.writeln('\t\tif ((*($elem_type_str*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln('\t\t\treturn i;') | ||||
| 		fn_builder.writeln('\t\t}') | ||||
| 		fn_builder.writeln('\t}') | ||||
| 		fn_builder.writeln('\treturn -1;') | ||||
| 		fn_builder.writeln('}') | ||||
| 		g.auto_fn_definitions << fn_builder.str() | ||||
| 		left_sym.register_method(&table.Fn{ | ||||
| 			name: 'index' | ||||
| 			params: [table.Param{ | ||||
| 				typ: left_type | ||||
| 			}, table.Param{ | ||||
| 				typ: left_info.elem_type | ||||
| 			}] | ||||
| 		}) | ||||
| 	} | ||||
| 	return fn_name | ||||
| } | ||||
| 
 | ||||
| // `nums.index(2)`
 | ||||
| fn (mut g Gen) gen_array_index(node ast.CallExpr) { | ||||
| 	fn_name := g.gen_array_index_method(node.left_type) | ||||
| 	g.write('${fn_name}(') | ||||
| 	if node.left_type.is_ptr() { | ||||
| 		g.write('*') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	g.write(', ') | ||||
| 	g.expr(node.args[0].expr) | ||||
| 	g.write(')') | ||||
| } | ||||
|  | @ -0,0 +1,144 @@ | |||
| // Copyright (c) 2019-2020 Alexander Medvednikov. All rights reserved.
 | ||||
| // Use of this source code is governed by an MIT license that can be found in the LICENSE file.
 | ||||
| module gen | ||||
| 
 | ||||
| import strings | ||||
| import v.table | ||||
| 
 | ||||
| fn (mut g Gen) gen_struct_equality_fn(left table.Type) string { | ||||
| 	left_sym := g.table.get_type_symbol(left) | ||||
| 	info := left_sym.struct_info() | ||||
| 	ptr_typ := g.typ(left).trim('*') | ||||
| 	if ptr_typ in g.struct_fn_definitions { | ||||
| 		return ptr_typ | ||||
| 	} | ||||
| 	g.struct_fn_definitions << ptr_typ | ||||
| 	g.type_definitions.writeln('static bool ${ptr_typ}_struct_eq($ptr_typ a, $ptr_typ b); // auto') | ||||
| 	mut fn_builder := strings.new_builder(512) | ||||
| 	fn_builder.writeln('static bool ${ptr_typ}_struct_eq($ptr_typ a, $ptr_typ b) {') | ||||
| 	for field in info.fields { | ||||
| 		sym := g.table.get_type_symbol(field.typ) | ||||
| 		match sym.kind { | ||||
| 			.string { | ||||
| 				fn_builder.writeln('\tif (string_ne(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.struct_ { | ||||
| 				eq_fn := g.gen_struct_equality_fn(field.typ) | ||||
| 				fn_builder.writeln('\tif (!${eq_fn}_struct_eq(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.array { | ||||
| 				eq_fn := g.gen_array_equality_fn(field.typ) | ||||
| 				fn_builder.writeln('\tif (!${eq_fn}_arr_eq(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.map { | ||||
| 				eq_fn := g.gen_map_equality_fn(field.typ) | ||||
| 				fn_builder.writeln('\tif (!${eq_fn}_map_eq(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.function { | ||||
| 				fn_builder.writeln('\tif (*((voidptr*)(a.$field.name)) != *((voidptr*)(b.$field.name))) {') | ||||
| 			} | ||||
| 			else { | ||||
| 				fn_builder.writeln('\tif (a.$field.name != b.$field.name)) {') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln('\t\treturn false;') | ||||
| 		fn_builder.writeln('\t}') | ||||
| 	} | ||||
| 	fn_builder.writeln('\treturn true;') | ||||
| 	fn_builder.writeln('}') | ||||
| 	g.auto_fn_definitions << fn_builder.str() | ||||
| 	return ptr_typ | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_array_equality_fn(left table.Type) string { | ||||
| 	left_sym := g.table.get_type_symbol(left) | ||||
| 	typ_name := g.typ(left) | ||||
| 	ptr_typ := typ_name[typ_name.index_after('_', 0) + 1..].trim('*') | ||||
| 	elem_sym := g.table.get_type_symbol(left_sym.array_info().elem_type) | ||||
| 	elem_typ := g.typ(left_sym.array_info().elem_type) | ||||
| 	ptr_elem_typ := elem_typ[elem_typ.index_after('_', 0) + 1..] | ||||
| 	if elem_sym.kind == .array { | ||||
| 		// Recursively generate array element comparison function code if array element is array type
 | ||||
| 		g.gen_array_equality_fn(left_sym.array_info().elem_type) | ||||
| 	} | ||||
| 	if ptr_typ in g.array_fn_definitions { | ||||
| 		return ptr_typ | ||||
| 	} | ||||
| 	g.array_fn_definitions << ptr_typ | ||||
| 	g.type_definitions.writeln('static bool ${ptr_typ}_arr_eq(array_$ptr_typ a, array_$ptr_typ b); // auto') | ||||
| 	mut fn_builder := strings.new_builder(512) | ||||
| 	fn_builder.writeln('static bool ${ptr_typ}_arr_eq(array_$ptr_typ a, array_$ptr_typ b) {') | ||||
| 	fn_builder.writeln('\tif (a.len != b.len) {') | ||||
| 	fn_builder.writeln('\t\treturn false;') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	i := g.new_tmp_var() | ||||
| 	fn_builder.writeln('\tfor (int $i = 0; $i < a.len; ++$i) {') | ||||
| 	// compare every pair of elements of the two arrays
 | ||||
| 	match elem_sym.kind { | ||||
| 		.string { fn_builder.writeln('\t\tif (string_ne(*(($ptr_typ*)((byte*)a.data+($i*a.element_size))), *(($ptr_typ*)((byte*)b.data+($i*b.element_size))))) {') } | ||||
| 		.struct_ { fn_builder.writeln('\t\tif (memcmp((byte*)a.data+($i*a.element_size), (byte*)b.data+($i*b.element_size), a.element_size)) {') } | ||||
| 		.array { fn_builder.writeln('\t\tif (!${ptr_elem_typ}_arr_eq((($elem_typ*)a.data)[$i], (($elem_typ*)b.data)[$i])) {') } | ||||
| 		.function { fn_builder.writeln('\t\tif (*((voidptr*)((byte*)a.data+($i*a.element_size))) != *((voidptr*)((byte*)b.data+($i*b.element_size)))) {') } | ||||
| 		else { fn_builder.writeln('\t\tif (*(($ptr_typ*)((byte*)a.data+($i*a.element_size))) != *(($ptr_typ*)((byte*)b.data+($i*b.element_size)))) {') } | ||||
| 	} | ||||
| 	fn_builder.writeln('\t\t\treturn false;') | ||||
| 	fn_builder.writeln('\t\t}') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	fn_builder.writeln('\treturn true;') | ||||
| 	fn_builder.writeln('}') | ||||
| 	g.auto_fn_definitions << fn_builder.str() | ||||
| 	return ptr_typ | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_map_equality_fn(left table.Type) string { | ||||
| 	left_sym := g.table.get_type_symbol(left) | ||||
| 	ptr_typ := g.typ(left).trim('*') | ||||
| 	value_sym := g.table.get_type_symbol(left_sym.map_info().value_type) | ||||
| 	value_typ := g.typ(left_sym.map_info().value_type) | ||||
| 	if value_sym.kind == .map { | ||||
| 		// Recursively generate map element comparison function code if array element is map type
 | ||||
| 		g.gen_map_equality_fn(left_sym.map_info().value_type) | ||||
| 	} | ||||
| 	if ptr_typ in g.map_fn_definitions { | ||||
| 		return ptr_typ | ||||
| 	} | ||||
| 	g.map_fn_definitions << ptr_typ | ||||
| 	g.type_definitions.writeln('static bool ${ptr_typ}_map_eq($ptr_typ a, $ptr_typ b); // auto') | ||||
| 	mut fn_builder := strings.new_builder(512) | ||||
| 	fn_builder.writeln('static bool ${ptr_typ}_map_eq($ptr_typ a, $ptr_typ b) {') | ||||
| 	fn_builder.writeln('\tif (a.len != b.len) {') | ||||
| 	fn_builder.writeln('\t\treturn false;') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	fn_builder.writeln('\tarray_string _keys = map_keys(&a);') | ||||
| 	i := g.new_tmp_var() | ||||
| 	fn_builder.writeln('\tfor (int $i = 0; $i < _keys.len; ++$i) {') | ||||
| 	fn_builder.writeln('\t\tstring k = string_clone( ((string*)_keys.data)[$i]);') | ||||
| 	if value_sym.kind == .function { | ||||
| 		func := value_sym.info as table.FnType | ||||
| 		ret_styp := g.typ(func.func.return_type) | ||||
| 		fn_builder.write('\t\t$ret_styp (*v) (') | ||||
| 		arg_len := func.func.params.len | ||||
| 		for j, arg in func.func.params { | ||||
| 			arg_styp := g.typ(arg.typ) | ||||
| 			fn_builder.write('$arg_styp $arg.name') | ||||
| 			if j < arg_len - 1 { | ||||
| 				fn_builder.write(', ') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln(') = (*(voidptr*)map_get_1(&a, &k, &(voidptr[]){ 0 }));') | ||||
| 	} else { | ||||
| 		fn_builder.writeln('\t\t$value_typ v = (*($value_typ*)map_get_1(&a, &k, &($value_typ[]){ 0 }));') | ||||
| 	} | ||||
| 	match value_sym.kind { | ||||
| 		.string { fn_builder.writeln('\t\tif (!map_exists(b, k) || string_ne((*(string*)map_get_1(&b, &k, &(string[]){_SLIT("")})), v)) {') } | ||||
| 		.function { fn_builder.writeln('\t\tif (!map_exists(b, k) || (*(voidptr*)map_get_1(&b, &k, &(voidptr[]){ 0 })) != v) {') } | ||||
| 		else { fn_builder.writeln('\t\tif (!map_exists(b, k) || (*($value_typ*)map_get_1(&b, &k, &($value_typ[]){ 0 })) != v) {') } | ||||
| 	} | ||||
| 	fn_builder.writeln('\t\t\treturn false;') | ||||
| 	fn_builder.writeln('\t\t}') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	fn_builder.writeln('\treturn true;') | ||||
| 	fn_builder.writeln('}') | ||||
| 	g.auto_fn_definitions << fn_builder.str() | ||||
| 	return ptr_typ | ||||
| } | ||||
|  | @ -4533,144 +4533,6 @@ fn (mut g Gen) assoc(node ast.Assoc) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_struct_equality_fn(left table.Type) string { | ||||
| 	left_sym := g.table.get_type_symbol(left) | ||||
| 	info := left_sym.struct_info() | ||||
| 	ptr_typ := g.typ(left).trim('*') | ||||
| 	if ptr_typ in g.struct_fn_definitions { | ||||
| 		return ptr_typ | ||||
| 	} | ||||
| 	g.struct_fn_definitions << ptr_typ | ||||
| 	g.type_definitions.writeln('static bool ${ptr_typ}_struct_eq($ptr_typ a, $ptr_typ b); // auto') | ||||
| 	mut fn_builder := strings.new_builder(512) | ||||
| 	fn_builder.writeln('static bool ${ptr_typ}_struct_eq($ptr_typ a, $ptr_typ b) {') | ||||
| 	for field in info.fields { | ||||
| 		sym := g.table.get_type_symbol(field.typ) | ||||
| 		match sym.kind { | ||||
| 			.string { | ||||
| 				fn_builder.writeln('\tif (string_ne(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.struct_ { | ||||
| 				eq_fn := g.gen_struct_equality_fn(field.typ) | ||||
| 				fn_builder.writeln('\tif (!${eq_fn}_struct_eq(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.array { | ||||
| 				eq_fn := g.gen_array_equality_fn(field.typ) | ||||
| 				fn_builder.writeln('\tif (!${eq_fn}_arr_eq(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.map { | ||||
| 				eq_fn := g.gen_map_equality_fn(field.typ) | ||||
| 				fn_builder.writeln('\tif (!${eq_fn}_map_eq(a.$field.name, b.$field.name)) {') | ||||
| 			} | ||||
| 			.function { | ||||
| 				fn_builder.writeln('\tif (*((voidptr*)(a.$field.name)) != *((voidptr*)(b.$field.name))) {') | ||||
| 			} | ||||
| 			else { | ||||
| 				fn_builder.writeln('\tif (a.$field.name != b.$field.name)) {') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln('\t\treturn false;') | ||||
| 		fn_builder.writeln('\t}') | ||||
| 	} | ||||
| 	fn_builder.writeln('\treturn true;') | ||||
| 	fn_builder.writeln('}') | ||||
| 	g.auto_fn_definitions << fn_builder.str() | ||||
| 	return ptr_typ | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_array_equality_fn(left table.Type) string { | ||||
| 	left_sym := g.table.get_type_symbol(left) | ||||
| 	typ_name := g.typ(left) | ||||
| 	ptr_typ := typ_name[typ_name.index_after('_', 0) + 1..].trim('*') | ||||
| 	elem_sym := g.table.get_type_symbol(left_sym.array_info().elem_type) | ||||
| 	elem_typ := g.typ(left_sym.array_info().elem_type) | ||||
| 	ptr_elem_typ := elem_typ[elem_typ.index_after('_', 0) + 1..] | ||||
| 	if elem_sym.kind == .array { | ||||
| 		// Recursively generate array element comparison function code if array element is array type
 | ||||
| 		g.gen_array_equality_fn(left_sym.array_info().elem_type) | ||||
| 	} | ||||
| 	if ptr_typ in g.array_fn_definitions { | ||||
| 		return ptr_typ | ||||
| 	} | ||||
| 	g.array_fn_definitions << ptr_typ | ||||
| 	g.type_definitions.writeln('static bool ${ptr_typ}_arr_eq(array_$ptr_typ a, array_$ptr_typ b); // auto') | ||||
| 	mut fn_builder := strings.new_builder(512) | ||||
| 	fn_builder.writeln('static bool ${ptr_typ}_arr_eq(array_$ptr_typ a, array_$ptr_typ b) {') | ||||
| 	fn_builder.writeln('\tif (a.len != b.len) {') | ||||
| 	fn_builder.writeln('\t\treturn false;') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	i := g.new_tmp_var() | ||||
| 	fn_builder.writeln('\tfor (int $i = 0; $i < a.len; ++$i) {') | ||||
| 	// compare every pair of elements of the two arrays
 | ||||
| 	match elem_sym.kind { | ||||
| 		.string { fn_builder.writeln('\t\tif (string_ne(*(($ptr_typ*)((byte*)a.data+($i*a.element_size))), *(($ptr_typ*)((byte*)b.data+($i*b.element_size))))) {') } | ||||
| 		.struct_ { fn_builder.writeln('\t\tif (memcmp((byte*)a.data+($i*a.element_size), (byte*)b.data+($i*b.element_size), a.element_size)) {') } | ||||
| 		.array { fn_builder.writeln('\t\tif (!${ptr_elem_typ}_arr_eq((($elem_typ*)a.data)[$i], (($elem_typ*)b.data)[$i])) {') } | ||||
| 		.function { fn_builder.writeln('\t\tif (*((voidptr*)((byte*)a.data+($i*a.element_size))) != *((voidptr*)((byte*)b.data+($i*b.element_size)))) {') } | ||||
| 		else { fn_builder.writeln('\t\tif (*(($ptr_typ*)((byte*)a.data+($i*a.element_size))) != *(($ptr_typ*)((byte*)b.data+($i*b.element_size)))) {') } | ||||
| 	} | ||||
| 	fn_builder.writeln('\t\t\treturn false;') | ||||
| 	fn_builder.writeln('\t\t}') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	fn_builder.writeln('\treturn true;') | ||||
| 	fn_builder.writeln('}') | ||||
| 	g.auto_fn_definitions << fn_builder.str() | ||||
| 	return ptr_typ | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_map_equality_fn(left table.Type) string { | ||||
| 	left_sym := g.table.get_type_symbol(left) | ||||
| 	ptr_typ := g.typ(left).trim('*') | ||||
| 	value_sym := g.table.get_type_symbol(left_sym.map_info().value_type) | ||||
| 	value_typ := g.typ(left_sym.map_info().value_type) | ||||
| 	if value_sym.kind == .map { | ||||
| 		// Recursively generate map element comparison function code if array element is map type
 | ||||
| 		g.gen_map_equality_fn(left_sym.map_info().value_type) | ||||
| 	} | ||||
| 	if ptr_typ in g.map_fn_definitions { | ||||
| 		return ptr_typ | ||||
| 	} | ||||
| 	g.map_fn_definitions << ptr_typ | ||||
| 	g.type_definitions.writeln('static bool ${ptr_typ}_map_eq($ptr_typ a, $ptr_typ b); // auto') | ||||
| 	mut fn_builder := strings.new_builder(512) | ||||
| 	fn_builder.writeln('static bool ${ptr_typ}_map_eq($ptr_typ a, $ptr_typ b) {') | ||||
| 	fn_builder.writeln('\tif (a.len != b.len) {') | ||||
| 	fn_builder.writeln('\t\treturn false;') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	fn_builder.writeln('\tarray_string _keys = map_keys(&a);') | ||||
| 	i := g.new_tmp_var() | ||||
| 	fn_builder.writeln('\tfor (int $i = 0; $i < _keys.len; ++$i) {') | ||||
| 	fn_builder.writeln('\t\tstring k = string_clone( ((string*)_keys.data)[$i]);') | ||||
| 	if value_sym.kind == .function { | ||||
| 		func := value_sym.info as table.FnType | ||||
| 		ret_styp := g.typ(func.func.return_type) | ||||
| 		fn_builder.write('\t\t$ret_styp (*v) (') | ||||
| 		arg_len := func.func.params.len | ||||
| 		for j, arg in func.func.params { | ||||
| 			arg_styp := g.typ(arg.typ) | ||||
| 			fn_builder.write('$arg_styp $arg.name') | ||||
| 			if j < arg_len - 1 { | ||||
| 				fn_builder.write(', ') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln(') = (*(voidptr*)map_get_1(&a, &k, &(voidptr[]){ 0 }));') | ||||
| 	} else { | ||||
| 		fn_builder.writeln('\t\t$value_typ v = (*($value_typ*)map_get_1(&a, &k, &($value_typ[]){ 0 }));') | ||||
| 	} | ||||
| 	match value_sym.kind { | ||||
| 		.string { fn_builder.writeln('\t\tif (!map_exists(b, k) || string_ne((*(string*)map_get_1(&b, &k, &(string[]){_SLIT("")})), v)) {') } | ||||
| 		.function { fn_builder.writeln('\t\tif (!map_exists(b, k) || (*(voidptr*)map_get_1(&b, &k, &(voidptr[]){ 0 })) != v) {') } | ||||
| 		else { fn_builder.writeln('\t\tif (!map_exists(b, k) || (*($value_typ*)map_get_1(&b, &k, &($value_typ[]){ 0 })) != v) {') } | ||||
| 	} | ||||
| 	fn_builder.writeln('\t\t\treturn false;') | ||||
| 	fn_builder.writeln('\t\t}') | ||||
| 	fn_builder.writeln('\t}') | ||||
| 	fn_builder.writeln('\treturn true;') | ||||
| 	fn_builder.writeln('}') | ||||
| 	g.auto_fn_definitions << fn_builder.str() | ||||
| 	return ptr_typ | ||||
| } | ||||
| 
 | ||||
| fn verror(s string) { | ||||
| 	util.verror('cgen error', s) | ||||
| } | ||||
|  | @ -4932,416 +4794,6 @@ fn (g &Gen) sort_structs(typesa []table.TypeSymbol) []table.TypeSymbol { | |||
| 	return types_sorted | ||||
| } | ||||
| 
 | ||||
| // `nums.map(it % 2 == 0)`
 | ||||
| fn (mut g Gen) gen_array_map(node ast.CallExpr) { | ||||
| 	g.inside_lambda = true | ||||
| 	tmp := g.new_tmp_var() | ||||
| 	s := g.go_before_stmt(0) | ||||
| 	// println('filter s="$s"')
 | ||||
| 	ret_typ := g.typ(node.return_type) | ||||
| 	// inp_typ := g.typ(node.receiver_type)
 | ||||
| 	ret_sym := g.table.get_type_symbol(node.return_type) | ||||
| 	inp_sym := g.table.get_type_symbol(node.receiver_type) | ||||
| 	ret_info := ret_sym.info as table.Array | ||||
| 	ret_elem_type := g.typ(ret_info.elem_type) | ||||
| 	inp_info := inp_sym.info as table.Array | ||||
| 	inp_elem_type := g.typ(inp_info.elem_type) | ||||
| 	if inp_sym.kind != .array { | ||||
| 		verror('map() requires an array') | ||||
| 	} | ||||
| 	g.write('${g.typ(node.left_type)} ${tmp}_orig = ') | ||||
| 	g.expr(node.left) | ||||
| 	g.writeln(';') | ||||
| 	g.write('int ${tmp}_len = ${tmp}_orig.len;') | ||||
| 	g.writeln('$ret_typ $tmp = __new_array(0, ${tmp}_len, sizeof($ret_elem_type));') | ||||
| 	i := g.new_tmp_var() | ||||
| 	g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') | ||||
| 	g.write('\t$inp_elem_type it = (($inp_elem_type*) ${tmp}_orig.data)[$i];') | ||||
| 	g.write('\t$ret_elem_type ti = ') | ||||
| 	expr := node.args[0].expr | ||||
| 	match expr { | ||||
| 		ast.AnonFn { | ||||
| 			g.gen_anon_fn_decl(expr) | ||||
| 			g.write('${expr.decl.name}(it)') | ||||
| 		} | ||||
| 		ast.Ident { | ||||
| 			if expr.kind == .function { | ||||
| 				g.write('${c_name(expr.name)}(it)') | ||||
| 			} else if expr.kind == .variable { | ||||
| 				var_info := expr.var_info() | ||||
| 				sym := g.table.get_type_symbol(var_info.typ) | ||||
| 				if sym.kind == .function { | ||||
| 					g.write('${c_name(expr.name)}(it)') | ||||
| 				} else { | ||||
| 					g.expr(node.args[0].expr) | ||||
| 				} | ||||
| 			} else { | ||||
| 				g.expr(node.args[0].expr) | ||||
| 			} | ||||
| 		} | ||||
| 		else { | ||||
| 			g.expr(node.args[0].expr) | ||||
| 		} | ||||
| 	} | ||||
| 	g.writeln(';') | ||||
| 	g.writeln('\tarray_push(&$tmp, &ti);') | ||||
| 	g.writeln('}') | ||||
| 	g.write(s) | ||||
| 	g.write(tmp) | ||||
| 	g.inside_lambda = false | ||||
| } | ||||
| 
 | ||||
| // `users.sort(a.age < b.age)`
 | ||||
| fn (mut g Gen) gen_array_sort(node ast.CallExpr) { | ||||
| 	// println('filter s="$s"')
 | ||||
| 	rec_sym := g.table.get_type_symbol(node.receiver_type) | ||||
| 	if rec_sym.kind != .array { | ||||
| 		println(node.name) | ||||
| 		println(g.typ(node.receiver_type)) | ||||
| 		// println(rec_sym.kind)
 | ||||
| 		verror('.sort() is an array method') | ||||
| 	} | ||||
| 	info := rec_sym.info as table.Array | ||||
| 	// No arguments means we are sorting an array of builtins (e.g. `numbers.sort()`)
 | ||||
| 	// The type for the comparison fns is the type of the element itself.
 | ||||
| 	mut typ := info.elem_type | ||||
| 	mut is_reverse := false | ||||
| 	// `users.sort(a.age > b.age)`
 | ||||
| 	if node.args.len > 0 { | ||||
| 		// Get the type of the field that's being compared
 | ||||
| 		// `a.age > b.age` => `age int` => int
 | ||||
| 		infix_expr := node.args[0].expr as ast.InfixExpr | ||||
| 		// typ = infix_expr.left_type
 | ||||
| 		is_reverse = infix_expr.op == .gt | ||||
| 	} | ||||
| 	mut compare_fn := '' | ||||
| 	match typ { | ||||
| 		table.int_type { | ||||
| 			compare_fn = 'compare_ints' | ||||
| 		} | ||||
| 		table.u64_type { | ||||
| 			compare_fn = 'compare_u64s' | ||||
| 		} | ||||
| 		table.string_type { | ||||
| 			compare_fn = 'compare_strings' | ||||
| 		} | ||||
| 		table.f64_type { | ||||
| 			compare_fn = 'compare_floats' | ||||
| 		} | ||||
| 		else { | ||||
| 			// Generate a comparison function for a custom type
 | ||||
| 			if node.args.len == 0 { | ||||
| 				verror('usage: .sort(a.field < b.field)') | ||||
| 			} | ||||
| 			// verror('sort(): unhandled type $typ $q.name')
 | ||||
| 			tmp_name := g.new_tmp_var() | ||||
| 			compare_fn = 'compare_${tmp_name}_' + g.typ(typ) | ||||
| 			if is_reverse { | ||||
| 				compare_fn += '_reverse' | ||||
| 			} | ||||
| 			// Register a new custom `compare_xxx` function for qsort()
 | ||||
| 			g.table.register_fn(name: compare_fn, return_type: table.int_type) | ||||
| 			infix_expr := node.args[0].expr as ast.InfixExpr | ||||
| 			styp := g.typ(typ) | ||||
| 			// Variables `a` and `b` are used in the `.sort(a < b)` syntax, so we can reuse them
 | ||||
| 			// when generating the function as long as the args are named the same.
 | ||||
| 			g.definitions.writeln('int $compare_fn ($styp* a, $styp* b) {') | ||||
| 			field_type := g.typ(infix_expr.left_type) | ||||
| 			left_expr_str := g.write_expr_to_string(infix_expr.left).replace_once('.', | ||||
| 				'->') | ||||
| 			right_expr_str := g.write_expr_to_string(infix_expr.right).replace_once('.', | ||||
| 				'->') | ||||
| 			g.definitions.writeln('$field_type a_ = $left_expr_str;') | ||||
| 			g.definitions.writeln('$field_type b_ = $right_expr_str;') | ||||
| 			mut op1, mut op2 := '', '' | ||||
| 			if infix_expr.left_type == table.string_type { | ||||
| 				if is_reverse { | ||||
| 					op1 = 'string_gt(a_, b_)' | ||||
| 					op2 = 'string_lt(a_, b_)' | ||||
| 				} else { | ||||
| 					op1 = 'string_lt(a_, b_)' | ||||
| 					op2 = 'string_gt(a_, b_)' | ||||
| 				} | ||||
| 			} else { | ||||
| 				if is_reverse { | ||||
| 					op1 = 'a_ > b_' | ||||
| 					op2 = 'a_ < b_' | ||||
| 				} else { | ||||
| 					op1 = 'a_ < b_' | ||||
| 					op2 = 'a_ > b_' | ||||
| 				} | ||||
| 			} | ||||
| 			g.definitions.writeln('if ($op1) return -1;') | ||||
| 			g.definitions.writeln('if ($op2) return 1; return 0; }\n') | ||||
| 		} | ||||
| 	} | ||||
| 	if is_reverse && !compare_fn.ends_with('_reverse') { | ||||
| 		compare_fn += '_reverse' | ||||
| 	} | ||||
| 	//
 | ||||
| 	deref := if node.left_type.is_ptr() || node.left_type.is_pointer() { '->' } else { '.' } | ||||
| 	// eprintln('> qsort: pointer $node.left_type | deref: `$deref`')
 | ||||
| 	g.write('qsort(') | ||||
| 	g.expr(node.left) | ||||
| 	g.write('${deref}data, ') | ||||
| 	g.expr(node.left) | ||||
| 	g.write('${deref}len, ') | ||||
| 	g.expr(node.left) | ||||
| 	g.writeln('${deref}element_size, (int (*)(const void *, const void *))&$compare_fn);') | ||||
| } | ||||
| 
 | ||||
| // `nums.filter(it % 2 == 0)`
 | ||||
| fn (mut g Gen) gen_array_filter(node ast.CallExpr) { | ||||
| 	tmp := g.new_tmp_var() | ||||
| 	s := g.go_before_stmt(0) | ||||
| 	// println('filter s="$s"')
 | ||||
| 	sym := g.table.get_type_symbol(node.return_type) | ||||
| 	if sym.kind != .array { | ||||
| 		verror('filter() requires an array') | ||||
| 	} | ||||
| 	info := sym.info as table.Array | ||||
| 	styp := g.typ(node.return_type) | ||||
| 	elem_type_str := g.typ(info.elem_type) | ||||
| 	g.write('${g.typ(node.left_type)} ${tmp}_orig = ') | ||||
| 	g.expr(node.left) | ||||
| 	g.writeln(';') | ||||
| 	g.write('int ${tmp}_len = ${tmp}_orig.len;') | ||||
| 	g.writeln('$styp $tmp = __new_array(0, ${tmp}_len, sizeof($elem_type_str));') | ||||
| 	i := g.new_tmp_var() | ||||
| 	g.writeln('for (int $i = 0; $i < ${tmp}_len; ++$i) {') | ||||
| 	g.writeln('  $elem_type_str it = (($elem_type_str*) ${tmp}_orig.data)[$i];') | ||||
| 	g.write('if (') | ||||
| 	expr := node.args[0].expr | ||||
| 	match expr { | ||||
| 		ast.AnonFn { | ||||
| 			g.gen_anon_fn_decl(expr) | ||||
| 			g.write('${expr.decl.name}(it)') | ||||
| 		} | ||||
| 		ast.Ident { | ||||
| 			if expr.kind == .function { | ||||
| 				g.write('${c_name(expr.name)}(it)') | ||||
| 			} else if expr.kind == .variable { | ||||
| 				var_info := expr.var_info() | ||||
| 				sym_t := g.table.get_type_symbol(var_info.typ) | ||||
| 				if sym_t.kind == .function { | ||||
| 					g.write('${c_name(expr.name)}(it)') | ||||
| 				} else { | ||||
| 					g.expr(node.args[0].expr) | ||||
| 				} | ||||
| 			} else { | ||||
| 				g.expr(node.args[0].expr) | ||||
| 			} | ||||
| 		} | ||||
| 		else { | ||||
| 			g.expr(node.args[0].expr) | ||||
| 		} | ||||
| 	} | ||||
| 	g.writeln(') array_push(&$tmp, &it); \n }') | ||||
| 	g.write(s) | ||||
| 	g.write(' ') | ||||
| 	g.write(tmp) | ||||
| } | ||||
| 
 | ||||
| // `nums.insert(0, 2)` `nums.insert(0, [2,3,4])`
 | ||||
| fn (mut g Gen) gen_array_insert(node ast.CallExpr) { | ||||
| 	left_sym := g.table.get_type_symbol(node.left_type) | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	elem_type_str := g.typ(left_info.elem_type) | ||||
| 	arg2_sym := g.table.get_type_symbol(node.args[1].typ) | ||||
| 	is_arg2_array := arg2_sym.kind == .array && node.args[1].typ == node.left_type | ||||
| 	if is_arg2_array { | ||||
| 		g.write('array_insert_many(&') | ||||
| 	} else { | ||||
| 		g.write('array_insert(&') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	g.write(', ') | ||||
| 	g.expr(node.args[0].expr) | ||||
| 	if is_arg2_array { | ||||
| 		g.write(', ') | ||||
| 		g.expr(node.args[1].expr) | ||||
| 		g.write('.data, ') | ||||
| 		g.expr(node.args[1].expr) | ||||
| 		g.write('.len)') | ||||
| 	} else { | ||||
| 		g.write(', &($elem_type_str[]){') | ||||
| 		if left_info.elem_type == table.string_type { | ||||
| 			g.write('string_clone(') | ||||
| 		} | ||||
| 		g.expr(node.args[1].expr) | ||||
| 		if left_info.elem_type == table.string_type { | ||||
| 			g.write(')') | ||||
| 		} | ||||
| 		g.write('})') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // `nums.prepend(2)` `nums.prepend([2,3,4])`
 | ||||
| fn (mut g Gen) gen_array_prepend(node ast.CallExpr) { | ||||
| 	left_sym := g.table.get_type_symbol(node.left_type) | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	elem_type_str := g.typ(left_info.elem_type) | ||||
| 	arg_sym := g.table.get_type_symbol(node.args[0].typ) | ||||
| 	is_arg_array := arg_sym.kind == .array && node.args[0].typ == node.left_type | ||||
| 	if is_arg_array { | ||||
| 		g.write('array_prepend_many(&') | ||||
| 	} else { | ||||
| 		g.write('array_prepend(&') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	if is_arg_array { | ||||
| 		g.write(', ') | ||||
| 		g.expr(node.args[0].expr) | ||||
| 		g.write('.data, ') | ||||
| 		g.expr(node.args[0].expr) | ||||
| 		g.write('.len)') | ||||
| 	} else { | ||||
| 		g.write(', &($elem_type_str[]){') | ||||
| 		g.expr(node.args[0].expr) | ||||
| 		g.write('})') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_array_contains_method(left_type table.Type) string { | ||||
| 	mut left_sym := g.table.get_type_symbol(left_type) | ||||
| 	mut left_type_str := g.typ(left_type).replace('*', '') | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	mut elem_type_str := g.typ(left_info.elem_type) | ||||
| 	elem_sym := g.table.get_type_symbol(left_info.elem_type) | ||||
| 	if elem_sym.kind == .function { | ||||
| 		left_type_str = 'array_voidptr' | ||||
| 		elem_type_str = 'voidptr' | ||||
| 	} | ||||
| 	fn_name := '${left_type_str}_contains' | ||||
| 	if !left_sym.has_method('contains') { | ||||
| 		g.type_definitions.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v); // auto') | ||||
| 		mut fn_builder := strings.new_builder(512) | ||||
| 		fn_builder.writeln('static bool ${fn_name}($left_type_str a, $elem_type_str v) {') | ||||
| 		fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') | ||||
| 		match elem_sym.kind { | ||||
| 			.string { | ||||
| 				fn_builder.writeln('\t\tif (string_eq((*(string*)array_get(a, i)), v)) {') | ||||
| 			} | ||||
| 			.array { | ||||
| 				ptr_typ := g.gen_array_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.function { | ||||
| 				fn_builder.writeln('\t\tif ((*(voidptr*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 			.map { | ||||
| 				ptr_typ := g.gen_map_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.struct_ { | ||||
| 				ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			else { | ||||
| 				fn_builder.writeln('\t\tif ((*($elem_type_str*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln('\t\t\treturn true;') | ||||
| 		fn_builder.writeln('\t\t}') | ||||
| 		fn_builder.writeln('\t}') | ||||
| 		fn_builder.writeln('\treturn false;') | ||||
| 		fn_builder.writeln('}') | ||||
| 		g.auto_fn_definitions << fn_builder.str() | ||||
| 		left_sym.register_method(&table.Fn{ | ||||
| 			name: 'contains' | ||||
| 			params: [table.Param{ | ||||
| 				typ: left_type | ||||
| 			}, table.Param{ | ||||
| 				typ: left_info.elem_type | ||||
| 			}] | ||||
| 		}) | ||||
| 	} | ||||
| 	return fn_name | ||||
| } | ||||
| 
 | ||||
| // `nums.contains(2)`
 | ||||
| fn (mut g Gen) gen_array_contains(node ast.CallExpr) { | ||||
| 	fn_name := g.gen_array_contains_method(node.left_type) | ||||
| 	g.write('${fn_name}(') | ||||
| 	if node.left_type.is_ptr() { | ||||
| 		g.write('*') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	g.write(', ') | ||||
| 	g.expr(node.args[0].expr) | ||||
| 	g.write(')') | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) gen_array_index_method(left_type table.Type) string { | ||||
| 	mut left_sym := g.table.get_type_symbol(left_type) | ||||
| 	mut left_type_str := g.typ(left_type).replace('*', '') | ||||
| 	left_info := left_sym.info as table.Array | ||||
| 	mut elem_type_str := g.typ(left_info.elem_type) | ||||
| 	elem_sym := g.table.get_type_symbol(left_info.elem_type) | ||||
| 	if elem_sym.kind == .function { | ||||
| 		left_type_str = 'array_voidptr' | ||||
| 		elem_type_str = 'voidptr' | ||||
| 	} | ||||
| 	fn_name := '${left_type_str}_index' | ||||
| 	if !left_sym.has_method('index') { | ||||
| 		g.type_definitions.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v); // auto') | ||||
| 		mut fn_builder := strings.new_builder(512) | ||||
| 		fn_builder.writeln('static int ${fn_name}($left_type_str a, $elem_type_str v) {') | ||||
| 		fn_builder.writeln('\tfor (int i = 0; i < a.len; ++i) {') | ||||
| 		match elem_sym.kind { | ||||
| 			.string { | ||||
| 				fn_builder.writeln('\t\tif (string_eq((*(string*)array_get(a, i)), v)) {') | ||||
| 			} | ||||
| 			.array { | ||||
| 				ptr_typ := g.gen_array_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_arr_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.function { | ||||
| 				fn_builder.writeln('\t\tif ((*(voidptr*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 			.map { | ||||
| 				ptr_typ := g.gen_map_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_map_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			.struct_ { | ||||
| 				ptr_typ := g.gen_struct_equality_fn(left_info.elem_type) | ||||
| 				fn_builder.writeln('\t\tif (${ptr_typ}_struct_eq(*($elem_type_str*)array_get(a, i), v)) {') | ||||
| 			} | ||||
| 			else { | ||||
| 				fn_builder.writeln('\t\tif ((*($elem_type_str*)array_get(a, i)) == v) {') | ||||
| 			} | ||||
| 		} | ||||
| 		fn_builder.writeln('\t\t\treturn i;') | ||||
| 		fn_builder.writeln('\t\t}') | ||||
| 		fn_builder.writeln('\t}') | ||||
| 		fn_builder.writeln('\treturn -1;') | ||||
| 		fn_builder.writeln('}') | ||||
| 		g.auto_fn_definitions << fn_builder.str() | ||||
| 		left_sym.register_method(&table.Fn{ | ||||
| 			name: 'index' | ||||
| 			params: [table.Param{ | ||||
| 				typ: left_type | ||||
| 			}, table.Param{ | ||||
| 				typ: left_info.elem_type | ||||
| 			}] | ||||
| 		}) | ||||
| 	} | ||||
| 	return fn_name | ||||
| } | ||||
| 
 | ||||
| // `nums.index(2)`
 | ||||
| fn (mut g Gen) gen_array_index(node ast.CallExpr) { | ||||
| 	fn_name := g.gen_array_index_method(node.left_type) | ||||
| 	g.write('${fn_name}(') | ||||
| 	if node.left_type.is_ptr() { | ||||
| 		g.write('*') | ||||
| 	} | ||||
| 	g.expr(node.left) | ||||
| 	g.write(', ') | ||||
| 	g.expr(node.args[0].expr) | ||||
| 	g.write(')') | ||||
| } | ||||
| 
 | ||||
| [inline] | ||||
| fn (g &Gen) nth_stmt_pos(n int) int { | ||||
| 	return g.stmt_path_pos[g.stmt_path_pos.len - (1 + n)] | ||||
|  | @ -6096,120 +5548,6 @@ _Interface* I_${cctype}_to_Interface_${interface_name}_ptr($cctype* x) { | |||
| 	return sb.str() | ||||
| } | ||||
| 
 | ||||
| fn (mut g Gen) array_init(it ast.ArrayInit) { | ||||
| 	type_sym := g.table.get_type_symbol(it.typ) | ||||
| 	styp := g.typ(it.typ) | ||||
| 	mut shared_styp := '' // only needed for shared &[]{...}
 | ||||
| 	is_amp := g.is_amp | ||||
| 	g.is_amp = false | ||||
| 	if is_amp { | ||||
| 		g.out.go_back(1) // delete the `&` already generated in `prefix_expr()
 | ||||
| 		if g.is_shared { | ||||
| 			mut shared_typ := it.typ.set_flag(.shared_f) | ||||
| 			shared_styp = g.typ(shared_typ) | ||||
| 			g.writeln('($shared_styp*)memdup(&($shared_styp){.val = ') | ||||
| 		} else { | ||||
| 			g.write('($styp*)memdup(&') // TODO: doesn't work with every compiler
 | ||||
| 		} | ||||
| 	} else { | ||||
| 		if g.is_shared { | ||||
| 			g.writeln('{.val = ($styp*)') | ||||
| 		} | ||||
| 	} | ||||
| 	if type_sym.kind == .array_fixed { | ||||
| 		g.write('{') | ||||
| 		if it.has_val { | ||||
| 			for i, expr in it.exprs { | ||||
| 				g.expr(expr) | ||||
| 				if i != it.exprs.len - 1 { | ||||
| 					g.write(', ') | ||||
| 				} | ||||
| 			} | ||||
| 		} else { | ||||
| 			g.write('0') | ||||
| 		} | ||||
| 		g.write('}') | ||||
| 		return | ||||
| 	} | ||||
| 	elem_type_str := g.typ(it.elem_type) | ||||
| 	if it.exprs.len == 0 { | ||||
| 		elem_sym := g.table.get_type_symbol(it.elem_type) | ||||
| 		is_default_array := elem_sym.kind == .array && it.has_default | ||||
| 		if is_default_array { | ||||
| 			g.write('__new_array_with_array_default(') | ||||
| 		} else { | ||||
| 			g.write('__new_array_with_default(') | ||||
| 		} | ||||
| 		if it.has_len { | ||||
| 			g.expr(it.len_expr) | ||||
| 			g.write(', ') | ||||
| 		} else { | ||||
| 			g.write('0, ') | ||||
| 		} | ||||
| 		if it.has_cap { | ||||
| 			g.expr(it.cap_expr) | ||||
| 			g.write(', ') | ||||
| 		} else { | ||||
| 			g.write('0, ') | ||||
| 		} | ||||
| 		if elem_sym.kind == .function { | ||||
| 			g.write('sizeof(voidptr), ') | ||||
| 		} else { | ||||
| 			g.write('sizeof($elem_type_str), ') | ||||
| 		} | ||||
| 		if is_default_array { | ||||
| 			g.write('($elem_type_str[]){') | ||||
| 			g.expr(it.default_expr) | ||||
| 			g.write('}[0])') | ||||
| 		} else if it.has_default { | ||||
| 			g.write('&($elem_type_str[]){') | ||||
| 			g.expr(it.default_expr) | ||||
| 			g.write('})') | ||||
| 		} else if it.has_len && it.elem_type == table.string_type { | ||||
| 			g.write('&($elem_type_str[]){') | ||||
| 			g.write('_SLIT("")') | ||||
| 			g.write('})') | ||||
| 		} else { | ||||
| 			g.write('0)') | ||||
| 		} | ||||
| 		return | ||||
| 	} | ||||
| 	len := it.exprs.len | ||||
| 	elem_sym := g.table.get_type_symbol(it.elem_type) | ||||
| 	if elem_sym.kind == .function { | ||||
| 		g.write('new_array_from_c_array($len, $len, sizeof(voidptr), _MOV((voidptr[$len]){') | ||||
| 	} else { | ||||
| 		g.write('new_array_from_c_array($len, $len, sizeof($elem_type_str), _MOV(($elem_type_str[$len]){') | ||||
| 	} | ||||
| 	if len > 8 { | ||||
| 		g.writeln('') | ||||
| 		g.write('\t\t') | ||||
| 	} | ||||
| 	for i, expr in it.exprs { | ||||
| 		if it.is_interface { | ||||
| 			// sym := g.table.get_type_symbol(it.expr_types[i])
 | ||||
| 			// isym := g.table.get_type_symbol(it.interface_type)
 | ||||
| 			g.interface_call(it.expr_types[i], it.interface_type) | ||||
| 		} | ||||
| 		g.expr_with_cast(expr, it.expr_types[i], it.elem_type) | ||||
| 		if it.is_interface { | ||||
| 			g.write(')') | ||||
| 		} | ||||
| 		if i != len - 1 { | ||||
| 			g.write(', ') | ||||
| 		} | ||||
| 	} | ||||
| 	g.write('}))') | ||||
| 	if g.is_shared { | ||||
| 		g.write(', .mtx = sync__new_rwmutex()}') | ||||
| 		if is_amp { | ||||
| 			g.write(', sizeof($shared_styp))') | ||||
| 		} | ||||
| 	} else if is_amp { | ||||
| 		g.write(', sizeof($styp))') | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // `ui.foo(button)` =>
 | ||||
| // `ui__foo(I_ui__Button_to_ui__Widget(` ...
 | ||||
| fn (mut g Gen) interface_call(typ table.Type, interface_type table.Type) { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue