| 1 | import strings |
| 2 | |
| 3 | type MyInt = int |
| 4 | |
| 5 | const maxn = 100000 |
| 6 | |
| 7 | fn test_sb() { |
| 8 | mut sb := strings.new_builder(100) |
| 9 | sb.write_string('hi') |
| 10 | sb.write_string('!') |
| 11 | sb.write_string('hello') |
| 12 | assert sb.len == 8 |
| 13 | sb_end := sb.str() |
| 14 | assert sb_end == 'hi!hello' |
| 15 | assert sb.len == 0 |
| 16 | /// |
| 17 | sb = strings.new_builder(10) |
| 18 | sb.write_string('a') |
| 19 | sb.write_string('b') |
| 20 | assert sb.len == 2 |
| 21 | assert sb.str() == 'ab' |
| 22 | // Test interpolation optimization |
| 23 | sb = strings.new_builder(10) |
| 24 | x := 10 |
| 25 | y := MyInt(20) |
| 26 | sb.writeln('x = ${x} y = ${y}') |
| 27 | res := sb.str() |
| 28 | assert res[res.len - 1] == `\n` |
| 29 | println('"${res}"') |
| 30 | assert res.trim_space() == 'x = 10 y = 20' |
| 31 | |
| 32 | sb = strings.new_builder(10) |
| 33 | sb.write_string('x = ${x} y = ${y}') |
| 34 | assert sb.str() == 'x = 10 y = 20' |
| 35 | //$if !windows { |
| 36 | sb = strings.new_builder(10) |
| 37 | sb.write_string('123456') |
| 38 | last_2 := sb.cut_last(2) |
| 39 | assert last_2 == '56' |
| 40 | final_sb := sb.str() |
| 41 | assert final_sb == '1234' |
| 42 | //} |
| 43 | sb.clear() |
| 44 | assert sb.str() == '' |
| 45 | } |
| 46 | |
| 47 | fn test_big_sb() { |
| 48 | mut sb := strings.new_builder(100) |
| 49 | mut sb2 := strings.new_builder(10000) |
| 50 | for i in 0 .. maxn { |
| 51 | sb.writeln(i.str()) |
| 52 | sb2.write_string('+') |
| 53 | } |
| 54 | s := sb.str() |
| 55 | lines := s.split_into_lines() |
| 56 | assert lines.len == maxn |
| 57 | assert lines[0] == '0' |
| 58 | assert lines[1] == '1' |
| 59 | assert lines[777] == '777' |
| 60 | assert lines[98765] == '98765' |
| 61 | println(sb2.len) |
| 62 | assert sb2.len == maxn |
| 63 | } |
| 64 | |
| 65 | fn test_byte_write() { |
| 66 | mut sb := strings.new_builder(100) |
| 67 | temp_str := 'byte testing' |
| 68 | mut count := 0 |
| 69 | for word in temp_str { |
| 70 | sb.write_u8(word) |
| 71 | count++ |
| 72 | assert count == sb.len |
| 73 | } |
| 74 | sb_final := sb.str() |
| 75 | assert sb_final == temp_str |
| 76 | } |
| 77 | |
| 78 | fn test_strings_builder_reuse() { |
| 79 | mut sb := strings.new_builder(256) |
| 80 | sb.write_string('world') |
| 81 | assert sb.str() == 'world' |
| 82 | sb.write_string('hello') |
| 83 | assert sb.str() == 'hello' |
| 84 | } |
| 85 | |
| 86 | fn test_cut_to() { |
| 87 | mut sb := strings.new_builder(16) |
| 88 | sb.write_string('hello') |
| 89 | assert sb.cut_to(3) == 'lo' |
| 90 | assert sb.len == 3 |
| 91 | assert sb.cut_to(3) == '' |
| 92 | assert sb.len == 3 |
| 93 | assert sb.cut_to(0) == 'hel' |
| 94 | assert sb.cut_to(32) == '' |
| 95 | assert sb.len == 0 |
| 96 | } |
| 97 | |
| 98 | fn test_write_rune() { |
| 99 | mut sb := strings.new_builder(10) |
| 100 | sb.write_rune(`h`) |
| 101 | sb.write_rune(`e`) |
| 102 | sb.write_rune(`l`) |
| 103 | sb.write_rune(`l`) |
| 104 | sb.write_rune(`o`) |
| 105 | x := sb.str() |
| 106 | assert x == 'hello' |
| 107 | } |
| 108 | |
| 109 | fn test_write_runes() { |
| 110 | mut sb := strings.new_builder(20) |
| 111 | sb.write_runes([`h`, `e`, `l`, `l`, `o`]) |
| 112 | sb.write_rune(` `) |
| 113 | sb.write_runes([`w`, `o`, `r`, `l`, `d`]) |
| 114 | x := sb.str() |
| 115 | assert x == 'hello world' |
| 116 | } |
| 117 | |
| 118 | fn test_ensure_cap() { |
| 119 | mut sb := strings.new_builder(0) |
| 120 | assert sb.cap == 0 |
| 121 | sb.ensure_cap(10) |
| 122 | assert sb.cap >= 10 |
| 123 | old_cap := sb.cap |
| 124 | sb.ensure_cap(10) |
| 125 | assert sb.cap == old_cap |
| 126 | sb.ensure_cap(15) |
| 127 | assert sb.cap >= 15 |
| 128 | old_cap2 := sb.cap |
| 129 | sb.ensure_cap(10) |
| 130 | assert sb.cap == old_cap2 |
| 131 | sb.ensure_cap(-1) |
| 132 | assert sb.cap == old_cap2 |
| 133 | } |
| 134 | |
| 135 | fn test_drain_builder() { |
| 136 | mut sb := strings.new_builder(0) |
| 137 | mut target_sb := strings.new_builder(0) |
| 138 | assert sb.cap == 0 |
| 139 | assert target_sb.cap == 0 |
| 140 | |
| 141 | sb.write_string('abc') |
| 142 | assert sb.len == 3 |
| 143 | |
| 144 | target_sb.drain_builder(mut sb, 0) |
| 145 | assert sb.len == 0 |
| 146 | assert target_sb.len == 3 |
| 147 | assert target_sb.str() == 'abc' |
| 148 | } |
| 149 | |
| 150 | @[manualfree] |
| 151 | fn sb_i64_str(n i64) string { |
| 152 | mut sb := strings.new_builder(24) |
| 153 | defer { |
| 154 | unsafe { sb.free() } |
| 155 | } |
| 156 | sb.write_decimal(n) |
| 157 | return sb.str() |
| 158 | } |
| 159 | |
| 160 | fn test_write_decimal() { |
| 161 | assert sb_i64_str(0) == '0' |
| 162 | assert sb_i64_str(1) == '1' |
| 163 | assert sb_i64_str(-1) == '-1' |
| 164 | assert sb_i64_str(1001) == '1001' |
| 165 | assert sb_i64_str(-1001) == '-1001' |
| 166 | assert sb_i64_str(1234567890) == '1234567890' |
| 167 | assert sb_i64_str(-1234567890) == '-1234567890' |
| 168 | assert sb_i64_str(9223372036854775807) == '9223372036854775807' |
| 169 | assert sb_i64_str(-9223372036854775807) == '-9223372036854775807' |
| 170 | assert sb_i64_str(min_i64) == '-9223372036854775808' |
| 171 | // runtime `min_i64` (parsed, not the constant), whose negation overflows i64: |
| 172 | assert sb_i64_str('-9223372036854775808'.i64()) == '-9223372036854775808' |
| 173 | } |
| 174 | |
| 175 | @[manualfree] |
| 176 | fn sb_u64_str(n u64) string { |
| 177 | mut sb := strings.new_builder(24) |
| 178 | defer { |
| 179 | unsafe { sb.free() } |
| 180 | } |
| 181 | sb.write_u_decimal(n) |
| 182 | return sb.str() |
| 183 | } |
| 184 | |
| 185 | fn test_write_u_decimal() { |
| 186 | assert sb_u64_str(0) == '0' |
| 187 | assert sb_u64_str(1) == '1' |
| 188 | assert sb_u64_str(1001) == '1001' |
| 189 | assert sb_u64_str(1234567890) == '1234567890' |
| 190 | assert sb_u64_str(u64(9223372036854775807)) == '9223372036854775807' |
| 191 | // values above max_i64, which write_decimal(i64) cannot represent: |
| 192 | assert sb_u64_str(u64(9223372036854775807) + 1) == '9223372036854775808' |
| 193 | assert sb_u64_str(max_u64) == '18446744073709551615' |
| 194 | } |
| 195 | |
| 196 | fn test_grow_len() { |
| 197 | mut sb := strings.new_builder(10) |
| 198 | assert sb.len == 0 |
| 199 | assert sb.cap == 10 |
| 200 | |
| 201 | sb.write_string('0123456789') |
| 202 | assert sb.len == 10 |
| 203 | |
| 204 | unsafe { sb.grow_len(-5) } |
| 205 | assert sb.len == 10 |
| 206 | assert sb.cap == 10 |
| 207 | |
| 208 | unsafe { sb.grow_len(10) } |
| 209 | assert sb.len == 20 |
| 210 | assert sb.cap == 20 |
| 211 | |
| 212 | sb.ensure_cap(35) |
| 213 | assert sb.len == 20 |
| 214 | assert sb.cap >= 35 |
| 215 | cap_after_ensure := sb.cap |
| 216 | |
| 217 | unsafe { sb.grow_len(5) } |
| 218 | assert sb.len == 25 |
| 219 | assert sb.cap == cap_after_ensure |
| 220 | } |
| 221 | |
| 222 | fn test_write_repeated_rune() { |
| 223 | mut sb := strings.new_builder(20) |
| 224 | sb.write_repeated_rune(`h`, 5) |
| 225 | sb.write_repeated_rune(`w`, 5) |
| 226 | sb.write_repeated_rune(`√`, 5) |
| 227 | sb.write_rune(` `) |
| 228 | x := sb.str() |
| 229 | assert x == 'hhhhhwwwww√√√√√ ' |
| 230 | } |
| 231 | |
| 232 | struct IndentTest { |
| 233 | param strings.IndentParam |
| 234 | input string |
| 235 | output string |
| 236 | } |
| 237 | |
| 238 | // vfmt off |
| 239 | const indent_test_data = [ |
| 240 | IndentTest{ |
| 241 | input: 'User1 { |
| 242 | name: "John" |
| 243 | settings: { |
| 244 | theme: "dark" |
| 245 | language: "en" |
| 246 | } |
| 247 | }' |
| 248 | output: 'User1 { |
| 249 | name: "John" |
| 250 | settings: { |
| 251 | theme: "dark" |
| 252 | language: "en" |
| 253 | } |
| 254 | }' |
| 255 | }, |
| 256 | IndentTest{ |
| 257 | input: 'User2{name:"John" settings:{theme:"dark" language:"en" hobbies:["reading","sports"]}}' |
| 258 | output: 'User2{ |
| 259 | name:"John" settings:{ |
| 260 | theme:"dark" language:"en" hobbies:["reading","sports"] |
| 261 | } |
| 262 | }' |
| 263 | }, |
| 264 | IndentTest{ |
| 265 | input: 'message {text: "Hello {world}!" count: 5 nested: {data: "Test {inner}"}}' |
| 266 | output: 'message { |
| 267 | text: "Hello {world}!" count: 5 nested: { |
| 268 | data: "Test {inner}" |
| 269 | } |
| 270 | }' |
| 271 | }, |
| 272 | IndentTest{ |
| 273 | input: 'if x > 0 {println("Positive") for i in 0..x {println(i) if i % 2 == 0 {println("even")}}} else {println("Not positive")}' |
| 274 | output: 'if x > 0 { |
| 275 | println("Positive") for i in 0..x { |
| 276 | println(i) if i % 2 == 0 { |
| 277 | println("even") |
| 278 | } |
| 279 | } |
| 280 | } else { |
| 281 | println("Not positive") |
| 282 | }' |
| 283 | }, |
| 284 | IndentTest{ |
| 285 | input: 'config{database:{host:"localhost" port:5432} server:{port:8080 routes:{api:"/api" static:"/static"}} log:{level:"info"}}' |
| 286 | output: 'config{ |
| 287 | database:{ |
| 288 | host:"localhost" port:5432 |
| 289 | } server:{ |
| 290 | port:8080 routes:{ |
| 291 | api:"/api" static:"/static" |
| 292 | } |
| 293 | } log:{ |
| 294 | level:"info" |
| 295 | } |
| 296 | }' |
| 297 | }, |
| 298 | IndentTest{ |
| 299 | input: "MyS{ |
| 300 | a: 0 |
| 301 | b: 0 |
| 302 | son: Son{ |
| 303 | k: '' |
| 304 | m: '' |
| 305 | } |
| 306 | } |
| 307 | " |
| 308 | output: "MyS{ |
| 309 | a: 0 |
| 310 | b: 0 |
| 311 | son: Son{ |
| 312 | k: '' |
| 313 | m: '' |
| 314 | } |
| 315 | } |
| 316 | " |
| 317 | }, |
| 318 | IndentTest{ |
| 319 | input: 'config {message: "He said: \\"Hello World!\\"" code: "if (x) { return \\"value\\"; }"}' |
| 320 | output: 'config { |
| 321 | message: "He said: \\"Hello World!\\"" code: "if (x) { return \\"value\\"; }" |
| 322 | }' |
| 323 | }, |
| 324 | IndentTest { |
| 325 | input : 'data {text: "String with {curly braces} and \\"quotes\\"" nested: {value: "Inner \\"quoted\\" string"}}' |
| 326 | output : 'data { |
| 327 | text: "String with {curly braces} and \\"quotes\\"" nested: { |
| 328 | value: "Inner \\"quoted\\" string" |
| 329 | } |
| 330 | }' |
| 331 | }, |
| 332 | IndentTest { |
| 333 | input : 'escape {backslash: "Path: C:\\\\Users\\\\Test" newline: "Line1\\nLine2" tab: "Column1\\tColumn2"}' |
| 334 | output : 'escape { |
| 335 | backslash: "Path: C:\\\\Users\\\\Test" newline: "Line1\\nLine2" tab: "Column1\\tColumn2" |
| 336 | }' |
| 337 | }, |
| 338 | IndentTest { |
| 339 | input : 'nested {outer: "Outer \\"quote\\" with {braces}" inner: {value: "Inner string with \\\\backslash and \\"quotes\\""}}' |
| 340 | output : 'nested { |
| 341 | outer: "Outer \\"quote\\" with {braces}" inner: { |
| 342 | value: "Inner string with \\\\backslash and \\"quotes\\"" |
| 343 | } |
| 344 | }' |
| 345 | }, |
| 346 | IndentTest { |
| 347 | input : 'complex {str1: "\\"Escaped quotes\\"" str2: "Backslash: \\\\" str3: "Mixed: \\"quoted\\" and \\\\backslash" regex: "Pattern: \\\\d+\\\\.\\\\d+"}' |
| 348 | output : 'complex { |
| 349 | str1: "\\"Escaped quotes\\"" str2: "Backslash: \\\\" str3: "Mixed: \\"quoted\\" and \\\\backslash" regex: "Pattern: \\\\d+\\\\.\\\\d+" |
| 350 | }' |
| 351 | }, |
| 352 | IndentTest { |
| 353 | input : 'json {data: "{\\"name\\": \\"John\\", \\"age\\": 30, \\"city\\": \\"New York\\"}"}' |
| 354 | output : 'json { |
| 355 | data: "{\\"name\\": \\"John\\", \\"age\\": 30, \\"city\\": \\"New York\\"}" |
| 356 | }' |
| 357 | }, |
| 358 | IndentTest { |
| 359 | input : 'code {function: "fn test() { if (x) { return \\\"value\\\"; } }" error: "Error: \\\"File not found\\\" at line 10"}' |
| 360 | output : 'code { |
| 361 | function: "fn test() { if (x) { return \\\"value\\\"; } }" error: "Error: \\\"File not found\\\" at line 10" |
| 362 | }' |
| 363 | }, |
| 364 | IndentTest { |
| 365 | input : 'multiline {message: "Line 1\\nLine 2\\nLine 3 with {braces}\\nLine 4 with \\\"quotes\\\""}' |
| 366 | output : 'multiline { |
| 367 | message: "Line 1\\nLine 2\\nLine 3 with {braces}\\nLine 4 with \\\"quotes\\\"" |
| 368 | }' |
| 369 | }, |
| 370 | IndentTest { |
| 371 | input : 'extreme {str: "\\\\\\\\\\\\\\"Quadruple escaped\\\\\\\\\\\\\\""}' |
| 372 | output : 'extreme { |
| 373 | str: "\\\\\\\\\\\\\\"Quadruple escaped\\\\\\\\\\\\\\"" |
| 374 | }' |
| 375 | }, |
| 376 | IndentTest{ |
| 377 | param: strings.IndentParam { |
| 378 | block_start: `[` |
| 379 | block_end: `]` |
| 380 | } |
| 381 | input: 'message [text: "Hello {world}!" count: 5 nested: [data: "Test {inner}"]]' |
| 382 | output: 'message [ |
| 383 | text: "Hello {world}!" count: 5 nested: [ |
| 384 | data: "Test {inner}" |
| 385 | ] |
| 386 | ]' |
| 387 | }, |
| 388 | IndentTest{ |
| 389 | param: strings.IndentParam { |
| 390 | block_start: `[` |
| 391 | block_end: `]` |
| 392 | indent_char: `#` |
| 393 | } |
| 394 | input: 'message [text: "Hello {world}!" count: 5 nested: [data: "Test {inner}"]]' |
| 395 | output: 'message [ |
| 396 | ####text: "Hello {world}!" count: 5 nested: [ |
| 397 | ########data: "Test {inner}" |
| 398 | ####] |
| 399 | ]' |
| 400 | }, |
| 401 | IndentTest{ |
| 402 | param: strings.IndentParam { |
| 403 | block_start: `[` |
| 404 | block_end: `]` |
| 405 | indent_char: `\t` |
| 406 | indent_count: 2 |
| 407 | } |
| 408 | input: 'message [text: "Hello {world}!" count: 5 nested: [data: "Test {inner}"]]' |
| 409 | output: 'message [ |
| 410 | \t\ttext: "Hello {world}!" count: 5 nested: [ |
| 411 | \t\t\t\tdata: "Test {inner}" |
| 412 | \t\t] |
| 413 | ]' |
| 414 | }, |
| 415 | IndentTest{ |
| 416 | param: strings.IndentParam { |
| 417 | block_start: `[` |
| 418 | block_end: `]` |
| 419 | indent_char: `#` |
| 420 | indent_count: 1 |
| 421 | starting_level: 2 |
| 422 | } |
| 423 | input: 'message [text: "Hello {world}!" count: 5 nested: [data: "Test {inner}"]]' |
| 424 | output: '##message [ |
| 425 | ###text: "Hello {world}!" count: 5 nested: [ |
| 426 | ####data: "Test {inner}" |
| 427 | ###] |
| 428 | ##]' |
| 429 | }, |
| 430 | ] |
| 431 | |
| 432 | // vfmt on |
| 433 | |
| 434 | fn test_indent() { |
| 435 | for t in indent_test_data { |
| 436 | mut sb := strings.new_builder(256) |
| 437 | sb.indent(t.input, t.param) |
| 438 | assert sb.str() == t.output |
| 439 | } |
| 440 | } |
| 441 | |