cgen: make `dump(x)` use a single write call, fix memleaks for autogenerated .str() methods of nested structs (#12529)
							parent
							
								
									f1dd0e3355
								
							
						
					
					
						commit
						258d0d6df7
					
				| 
						 | 
					@ -798,9 +798,19 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name stri
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	fn_builder.writeln('\tstring indents = string_repeat(_SLIT("    "), indent_count);')
 | 
						fn_builder.writeln('\tstring indents = string_repeat(_SLIT("    "), indent_count);')
 | 
				
			||||||
	fn_builder.writeln('\tstring res = str_intp( ${info.fields.len * 4 + 3}, _MOV((StrIntpData[]){')
 | 
					 | 
				
			||||||
	fn_builder.writeln('\t\t{_SLIT("$clean_struct_v_type_name{\\n"), 0, {.d_c=0}},')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mut fn_body_surrounder := util.new_surrounder(info.fields.len)
 | 
				
			||||||
 | 
						mut fn_body := strings.new_builder(info.fields.len * 256)
 | 
				
			||||||
 | 
						defer {
 | 
				
			||||||
 | 
							fn_builder.write_string(fn_body_surrounder.before())
 | 
				
			||||||
 | 
							fn_builder << fn_body
 | 
				
			||||||
 | 
							fn_builder.write_string(fn_body_surrounder.after())
 | 
				
			||||||
 | 
							fn_builder.writeln('\tstring_free(&indents);')
 | 
				
			||||||
 | 
							fn_builder.writeln('\treturn res;')
 | 
				
			||||||
 | 
							fn_builder.writeln('}')
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						fn_body.writeln('\tstring res = str_intp( ${info.fields.len * 4 + 3}, _MOV((StrIntpData[]){')
 | 
				
			||||||
 | 
						fn_body.writeln('\t\t{_SLIT("$clean_struct_v_type_name{\\n"), 0, {.d_c=0}},')
 | 
				
			||||||
	for i, field in info.fields {
 | 
						for i, field in info.fields {
 | 
				
			||||||
		mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' }
 | 
							mut ptr_amp := if field.typ.is_ptr() { '&' } else { '' }
 | 
				
			||||||
		base_fmt := g.type_to_fmt1(g.unwrap_generic(field.typ))
 | 
							base_fmt := g.type_to_fmt1(g.unwrap_generic(field.typ))
 | 
				
			||||||
| 
						 | 
					@ -818,9 +828,9 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name stri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// first fields doesn't need \n
 | 
							// first fields doesn't need \n
 | 
				
			||||||
		if i == 0 {
 | 
							if i == 0 {
 | 
				
			||||||
			fn_builder.write_string('\t\t{_SLIT0, $c.si_s_code, {.d_s=indents}}, {_SLIT("    $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ')
 | 
								fn_body.write_string('\t\t{_SLIT0, $c.si_s_code, {.d_s=indents}}, {_SLIT("    $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ')
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			fn_builder.write_string('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("    $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ')
 | 
								fn_body.write_string('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("    $field.name: $ptr_amp$prefix"), 0, {.d_c=0}}, ')
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// custom methods management
 | 
							// custom methods management
 | 
				
			||||||
| 
						 | 
					@ -835,71 +845,79 @@ fn (mut g Gen) gen_str_for_struct(info ast.Struct, styp string, str_fn_name stri
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// manage the fact hat with float we use always the g representation
 | 
							// manage the fact hat with float we use always the g representation
 | 
				
			||||||
		if sym.kind !in [.f32, .f64] {
 | 
							if sym.kind !in [.f32, .f64] {
 | 
				
			||||||
			fn_builder.write_string('{_SLIT("$quote_str"), ${int(base_fmt)}, {.${data_str(base_fmt)}=')
 | 
								fn_body.write_string('{_SLIT("$quote_str"), ${int(base_fmt)}, {.${data_str(base_fmt)}=')
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex()
 | 
								g_fmt := '0x' + (u32(base_fmt) | u32(0x7F) << 9).hex()
 | 
				
			||||||
			fn_builder.write_string('{_SLIT("$quote_str"), $g_fmt, {.${data_str(base_fmt)}=')
 | 
								fn_body.write_string('{_SLIT("$quote_str"), $g_fmt, {.${data_str(base_fmt)}=')
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mut func := struct_auto_str_func1(sym, field.typ, field_styp_fn_name, field.name,
 | 
							mut funcprefix := ''
 | 
				
			||||||
			sym_has_str_method, str_method_expects_ptr)
 | 
							mut func, mut caller_should_free := struct_auto_str_func1(sym, field.typ, field_styp_fn_name,
 | 
				
			||||||
 | 
								field.name, sym_has_str_method, str_method_expects_ptr)
 | 
				
			||||||
		if field.typ in ast.cptr_types {
 | 
							if field.typ in ast.cptr_types {
 | 
				
			||||||
			func = '(voidptr) it.$field.name'
 | 
								func = '(voidptr) it.$field.name'
 | 
				
			||||||
 | 
								caller_should_free = false
 | 
				
			||||||
		} else if field.typ.is_ptr() {
 | 
							} else if field.typ.is_ptr() {
 | 
				
			||||||
			// reference types can be "nil"
 | 
								// reference types can be "nil"
 | 
				
			||||||
			fn_builder.write_string('isnil(it.${c_name(field.name)})')
 | 
								funcprefix += 'isnil(it.${c_name(field.name)})'
 | 
				
			||||||
			fn_builder.write_string(' ? _SLIT("nil") : ')
 | 
								funcprefix += ' ? _SLIT("nil") : '
 | 
				
			||||||
			// struct, floats and ints have a special case through the _str function
 | 
								// struct, floats and ints have a special case through the _str function
 | 
				
			||||||
			if sym.kind != .struct_ && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() {
 | 
								if sym.kind != .struct_ && !field.typ.is_int_valptr() && !field.typ.is_float_valptr() {
 | 
				
			||||||
				fn_builder.write_string('*')
 | 
									funcprefix += '*'
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// handle circular ref type of struct to the struct itself
 | 
							// handle circular ref type of struct to the struct itself
 | 
				
			||||||
		if styp == field_styp {
 | 
							if styp == field_styp {
 | 
				
			||||||
			fn_builder.write_string('_SLIT("<circular>")')
 | 
								fn_body.write_string('${funcprefix}_SLIT("<circular>")')
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			// manage C charptr
 | 
								// manage C charptr
 | 
				
			||||||
			if field.typ in ast.charptr_types {
 | 
								if field.typ in ast.charptr_types {
 | 
				
			||||||
				fn_builder.write_string('tos2((byteptr)$func)')
 | 
									fn_body.write_string('tos2((byteptr)$func)')
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				if field.typ.is_ptr() && sym.kind == .struct_ {
 | 
									if field.typ.is_ptr() && sym.kind == .struct_ {
 | 
				
			||||||
					fn_builder.write_string('(indent_count > 25) ? _SLIT("<probably circular>") : ')
 | 
										funcprefix += '(indent_count > 25) ? _SLIT("<probably circular>") : '
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									// eprintln('>>> caller_should_free: ${caller_should_free:6s} | funcprefix: $funcprefix | func: $func')
 | 
				
			||||||
 | 
									if caller_should_free {
 | 
				
			||||||
 | 
										tmpvar := g.new_tmp_var()
 | 
				
			||||||
 | 
										fn_body_surrounder.add('\tstring $tmpvar = $funcprefix$func;', '\tstring_free(&$tmpvar);')
 | 
				
			||||||
 | 
										fn_body.write_string(tmpvar)
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										fn_body.write_string(funcprefix)
 | 
				
			||||||
 | 
										fn_body.write_string(func)
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				fn_builder.write_string(func)
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn_builder.writeln('}}, {_SLIT("$quote_str"), 0, {.d_c=0}},')
 | 
							fn_body.writeln('}}, {_SLIT("$quote_str"), 0, {.d_c=0}},')
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	fn_builder.writeln('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("}"), 0, {.d_c=0}},')
 | 
						fn_body.writeln('\t\t{_SLIT("\\n"), $c.si_s_code, {.d_s=indents}}, {_SLIT("}"), 0, {.d_c=0}},')
 | 
				
			||||||
	fn_builder.writeln('\t}));')
 | 
						fn_body.writeln('\t}));')
 | 
				
			||||||
	fn_builder.writeln('\tstring_free(&indents);')
 | 
					 | 
				
			||||||
	fn_builder.writeln('\treturn res;')
 | 
					 | 
				
			||||||
	fn_builder.writeln('}')
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn struct_auto_str_func1(sym &ast.TypeSymbol, field_type ast.Type, fn_name string, field_name string, has_custom_str bool, expects_ptr bool) string {
 | 
					fn struct_auto_str_func1(sym &ast.TypeSymbol, field_type ast.Type, fn_name string, field_name string, has_custom_str bool, expects_ptr bool) (string, bool) {
 | 
				
			||||||
	deref, _ := deref_kind(expects_ptr, field_type.is_ptr(), field_type)
 | 
						deref, _ := deref_kind(expects_ptr, field_type.is_ptr(), field_type)
 | 
				
			||||||
	if sym.kind == .enum_ {
 | 
						if sym.kind == .enum_ {
 | 
				
			||||||
		return '${fn_name}(${deref}it.${c_name(field_name)})'
 | 
							return '${fn_name}(${deref}it.${c_name(field_name)})', true
 | 
				
			||||||
	} else if should_use_indent_func(sym.kind) {
 | 
						} else if should_use_indent_func(sym.kind) {
 | 
				
			||||||
		obj := 'it.${c_name(field_name)}'
 | 
							obj := 'it.${c_name(field_name)}'
 | 
				
			||||||
		if has_custom_str {
 | 
							if has_custom_str {
 | 
				
			||||||
			return '${fn_name}($deref$obj)'
 | 
								return '${fn_name}($deref$obj)', true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return 'indent_${fn_name}($deref$obj, indent_count + 1)'
 | 
							return 'indent_${fn_name}($deref$obj, indent_count + 1)', true
 | 
				
			||||||
	} else if sym.kind in [.array, .array_fixed, .map, .sum_type] {
 | 
						} else if sym.kind in [.array, .array_fixed, .map, .sum_type] {
 | 
				
			||||||
		if has_custom_str {
 | 
							if has_custom_str {
 | 
				
			||||||
			return '${fn_name}(${deref}it.${c_name(field_name)})'
 | 
								return '${fn_name}(${deref}it.${c_name(field_name)})', true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return 'indent_${fn_name}(${deref}it.${c_name(field_name)}, indent_count + 1)'
 | 
							return 'indent_${fn_name}(${deref}it.${c_name(field_name)}, indent_count + 1)', true
 | 
				
			||||||
	} else if sym.kind == .function {
 | 
						} else if sym.kind == .function {
 | 
				
			||||||
		return '${fn_name}()'
 | 
							return '${fn_name}()', true
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		if sym.kind == .chan {
 | 
							if sym.kind == .chan {
 | 
				
			||||||
			return '${fn_name}(${deref}it.${c_name(field_name)})'
 | 
								return '${fn_name}(${deref}it.${c_name(field_name)})', true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		mut method_str := 'it.${c_name(field_name)}'
 | 
							mut method_str := 'it.${c_name(field_name)}'
 | 
				
			||||||
 | 
							mut caller_should_free := false
 | 
				
			||||||
		if sym.kind == .bool {
 | 
							if sym.kind == .bool {
 | 
				
			||||||
			method_str += ' ? _SLIT("true") : _SLIT("false")'
 | 
								method_str += ' ? _SLIT("true") : _SLIT("false")'
 | 
				
			||||||
		} else if (field_type.is_int_valptr() || field_type.is_float_valptr())
 | 
							} else if (field_type.is_int_valptr() || field_type.is_float_valptr())
 | 
				
			||||||
| 
						 | 
					@ -908,18 +926,18 @@ fn struct_auto_str_func1(sym &ast.TypeSymbol, field_type ast.Type, fn_name strin
 | 
				
			||||||
			if sym.kind == .f32 {
 | 
								if sym.kind == .f32 {
 | 
				
			||||||
				return 'str_intp(1, _MOV((StrIntpData[]){
 | 
									return 'str_intp(1, _MOV((StrIntpData[]){
 | 
				
			||||||
					{_SLIT0, $si_g32_code, {.d_f32 = *$method_str }}
 | 
										{_SLIT0, $si_g32_code, {.d_f32 = *$method_str }}
 | 
				
			||||||
				}))'
 | 
									}))', true
 | 
				
			||||||
			} else if sym.kind == .f64 {
 | 
								} else if sym.kind == .f64 {
 | 
				
			||||||
				return 'str_intp(1, _MOV((StrIntpData[]){
 | 
									return 'str_intp(1, _MOV((StrIntpData[]){
 | 
				
			||||||
					{_SLIT0, $si_g64_code, {.d_f64 = *$method_str }}
 | 
										{_SLIT0, $si_g64_code, {.d_f64 = *$method_str }}
 | 
				
			||||||
				}))'
 | 
									}))', true
 | 
				
			||||||
			} else if sym.kind == .u64 {
 | 
								} else if sym.kind == .u64 {
 | 
				
			||||||
				fmt_type := StrIntpType.si_u64
 | 
									fmt_type := StrIntpType.si_u64
 | 
				
			||||||
				return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_u64 = *$method_str }}}))'
 | 
									return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_u64 = *$method_str }}}))', true
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			fmt_type := StrIntpType.si_i32
 | 
								fmt_type := StrIntpType.si_i32
 | 
				
			||||||
			return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *$method_str }}}))'
 | 
								return 'str_intp(1, _MOV((StrIntpData[]){{_SLIT0, ${u32(fmt_type) | 0xfe00}, {.d_i32 = *$method_str }}}))', true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return method_str
 | 
							return method_str, caller_should_free
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
module c
 | 
					module c
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import v.ast
 | 
					import v.ast
 | 
				
			||||||
 | 
					import v.util
 | 
				
			||||||
import strings
 | 
					import strings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut g Gen) dump_expr(node ast.DumpExpr) {
 | 
					fn (mut g Gen) dump_expr(node ast.DumpExpr) {
 | 
				
			||||||
| 
						 | 
					@ -43,25 +44,43 @@ fn (mut g Gen) dump_expr_definitions() {
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			continue
 | 
								continue
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		dump_fns.writeln('\teprint(${ctoslit('[')});')
 | 
							mut surrounder := util.new_surrounder(3)
 | 
				
			||||||
		dump_fns.writeln('\teprint(fpath);')
 | 
							surrounder.add('string sline = int_str(line);', 'string_free(&sline);')
 | 
				
			||||||
		dump_fns.writeln('\teprint(${ctoslit(':')});')
 | 
							surrounder.add('string value = ${to_string_fn_name}(${deref}dump_arg);', 'string_free(&value);')
 | 
				
			||||||
		dump_fns.writeln('\teprint(int_str(line));')
 | 
							surrounder.add('
 | 
				
			||||||
		dump_fns.writeln('\teprint(${ctoslit('] ')});')
 | 
					string res;
 | 
				
			||||||
		// dump_fns.writeln('\t/* dump_type: $dump_type | to_string_fn_name: $to_string_fn_name | is_ptr: $is_ptr | ptr_asterisk: $ptr_asterisk | dump_fn_name: $dump_fn_name | cnam: $cname */')
 | 
					strings__Builder sb = strings__new_builder(256);
 | 
				
			||||||
		dump_fns.writeln('\teprint(sexpr);')
 | 
					',
 | 
				
			||||||
		dump_fns.writeln('\teprint(${ctoslit(': ')});')
 | 
								'
 | 
				
			||||||
 | 
					res = strings__Builder_str(&sb);
 | 
				
			||||||
 | 
					eprint(res);
 | 
				
			||||||
 | 
					string_free(&res);
 | 
				
			||||||
 | 
					strings__Builder_free(&sb);
 | 
				
			||||||
 | 
					')
 | 
				
			||||||
 | 
							dump_fns.writeln(surrounder.before())
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, '[');")
 | 
				
			||||||
 | 
							dump_fns.writeln('\tstrings__Builder_write_string(&sb, fpath);')
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, ':');")
 | 
				
			||||||
 | 
							dump_fns.writeln('\tstrings__Builder_write_string(&sb, sline);')
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, ']');")
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, ' ');")
 | 
				
			||||||
 | 
							dump_fns.writeln('\tstrings__Builder_write_string(&sb, sexpr);')
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, ':');")
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, ' ');")
 | 
				
			||||||
		if is_ptr {
 | 
							if is_ptr {
 | 
				
			||||||
			dump_fns.writeln('\teprint(${ctoslit('&')});')
 | 
								dump_fns.writeln("\tstrings__Builder_write_rune(&sb, '&');")
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		dump_fns.writeln('\teprintln(${to_string_fn_name}(${deref}dump_arg));')
 | 
							dump_fns.writeln('\tstrings__Builder_write_string(&sb, value);')
 | 
				
			||||||
 | 
							dump_fns.writeln("\tstrings__Builder_write_rune(&sb, '\\n');")
 | 
				
			||||||
 | 
							dump_fns.writeln(surrounder.after())
 | 
				
			||||||
		dump_fns.writeln('\treturn dump_arg;')
 | 
							dump_fns.writeln('\treturn dump_arg;')
 | 
				
			||||||
		dump_fns.writeln('}')
 | 
							dump_fns.writeln('}')
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	for tdef, _ in dump_typedefs {
 | 
						for tdef, _ in dump_typedefs {
 | 
				
			||||||
		g.definitions.writeln(tdef)
 | 
							g.definitions.writeln(tdef)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	g.definitions.writeln(dump_fns.str())
 | 
						defs := dump_fns.str()
 | 
				
			||||||
 | 
						g.definitions.writeln(defs)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn (mut g Gen) writeln_fn_header(s string, mut sb strings.Builder) bool {
 | 
					fn (mut g Gen) writeln_fn_header(s string, mut sb strings.Builder) bool {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					struct NestedInner {
 | 
				
			||||||
 | 
						x int
 | 
				
			||||||
 | 
						y int
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Inner {
 | 
				
			||||||
 | 
						x           int
 | 
				
			||||||
 | 
						y           int
 | 
				
			||||||
 | 
						nestedinner NestedInner
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Outer {
 | 
				
			||||||
 | 
						x     int
 | 
				
			||||||
 | 
						y     int
 | 
				
			||||||
 | 
						inner Inner
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn main() {
 | 
				
			||||||
 | 
						a := Outer{123, 456, Inner{789, 999, NestedInner{111, 222}}}
 | 
				
			||||||
 | 
						dump(a)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,70 @@
 | 
				
			||||||
 | 
					module util
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import strings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[noinit]
 | 
				
			||||||
 | 
					pub struct Surrounder {
 | 
				
			||||||
 | 
					pub mut:
 | 
				
			||||||
 | 
						befores []string
 | 
				
			||||||
 | 
						afters  []string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn new_surrounder(expected_length int) Surrounder {
 | 
				
			||||||
 | 
						return Surrounder{
 | 
				
			||||||
 | 
							befores: []string{cap: expected_length}
 | 
				
			||||||
 | 
							afters: []string{cap: expected_length}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn (mut s Surrounder) add(before string, after string) {
 | 
				
			||||||
 | 
						s.befores << before
 | 
				
			||||||
 | 
						s.afters << after
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[manualfree]
 | 
				
			||||||
 | 
					pub fn (s &Surrounder) before() string {
 | 
				
			||||||
 | 
						len := s.befores.len
 | 
				
			||||||
 | 
						if len > 0 {
 | 
				
			||||||
 | 
							mut res := strings.new_builder(len * 100)
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								unsafe { res.free() }
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for i := 0; i < len; i++ {
 | 
				
			||||||
 | 
								x := &s.befores[i]
 | 
				
			||||||
 | 
								if x.len > 0 {
 | 
				
			||||||
 | 
									res.writeln(x)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret := res.str()
 | 
				
			||||||
 | 
							return ret
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ''
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[manualfree]
 | 
				
			||||||
 | 
					pub fn (s &Surrounder) after() string {
 | 
				
			||||||
 | 
						len := s.afters.len
 | 
				
			||||||
 | 
						if len > 0 {
 | 
				
			||||||
 | 
							mut res := strings.new_builder(len * 100)
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								unsafe { res.free() }
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for i := len - 1; i >= 0; i-- {
 | 
				
			||||||
 | 
								x := &s.afters[i]
 | 
				
			||||||
 | 
								if x.len > 0 {
 | 
				
			||||||
 | 
									res.writeln(x)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ret := res.str()
 | 
				
			||||||
 | 
							return ret
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ''
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[unsafe]
 | 
				
			||||||
 | 
					pub fn (mut s Surrounder) free() {
 | 
				
			||||||
 | 
						unsafe {
 | 
				
			||||||
 | 
							s.befores.free()
 | 
				
			||||||
 | 
							s.afters.free()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue