A poolable string builder (aka string buffer) for Zig
0

Configure Feed

Select the types of activity you want to include in your feed.

initial commit

Karl Seguin (Jun 24, 2023, 5:54 PM +0800) 00835730

+851
+2
.gitignore
··· 1 + zig-out/ 2 + zig-cache/
+373
LICENSE
··· 1 + Mozilla Public License Version 2.0 2 + ================================== 3 + 4 + 1. Definitions 5 + -------------- 6 + 7 + 1.1. "Contributor" 8 + means each individual or legal entity that creates, contributes to 9 + the creation of, or owns Covered Software. 10 + 11 + 1.2. "Contributor Version" 12 + means the combination of the Contributions of others (if any) used 13 + by a Contributor and that particular Contributor's Contribution. 14 + 15 + 1.3. "Contribution" 16 + means Covered Software of a particular Contributor. 17 + 18 + 1.4. "Covered Software" 19 + means Source Code Form to which the initial Contributor has attached 20 + the notice in Exhibit A, the Executable Form of such Source Code 21 + Form, and Modifications of such Source Code Form, in each case 22 + including portions thereof. 23 + 24 + 1.5. "Incompatible With Secondary Licenses" 25 + means 26 + 27 + (a) that the initial Contributor has attached the notice described 28 + in Exhibit B to the Covered Software; or 29 + 30 + (b) that the Covered Software was made available under the terms of 31 + version 1.1 or earlier of the License, but not also under the 32 + terms of a Secondary License. 33 + 34 + 1.6. "Executable Form" 35 + means any form of the work other than Source Code Form. 36 + 37 + 1.7. "Larger Work" 38 + means a work that combines Covered Software with other material, in 39 + a separate file or files, that is not Covered Software. 40 + 41 + 1.8. "License" 42 + means this document. 43 + 44 + 1.9. "Licensable" 45 + means having the right to grant, to the maximum extent possible, 46 + whether at the time of the initial grant or subsequently, any and 47 + all of the rights conveyed by this License. 48 + 49 + 1.10. "Modifications" 50 + means any of the following: 51 + 52 + (a) any file in Source Code Form that results from an addition to, 53 + deletion from, or modification of the contents of Covered 54 + Software; or 55 + 56 + (b) any new file in Source Code Form that contains any Covered 57 + Software. 58 + 59 + 1.11. "Patent Claims" of a Contributor 60 + means any patent claim(s), including without limitation, method, 61 + process, and apparatus claims, in any patent Licensable by such 62 + Contributor that would be infringed, but for the grant of the 63 + License, by the making, using, selling, offering for sale, having 64 + made, import, or transfer of either its Contributions or its 65 + Contributor Version. 66 + 67 + 1.12. "Secondary License" 68 + means either the GNU General Public License, Version 2.0, the GNU 69 + Lesser General Public License, Version 2.1, the GNU Affero General 70 + Public License, Version 3.0, or any later versions of those 71 + licenses. 72 + 73 + 1.13. "Source Code Form" 74 + means the form of the work preferred for making modifications. 75 + 76 + 1.14. "You" (or "Your") 77 + means an individual or a legal entity exercising rights under this 78 + License. For legal entities, "You" includes any entity that 79 + controls, is controlled by, or is under common control with You. For 80 + purposes of this definition, "control" means (a) the power, direct 81 + or indirect, to cause the direction or management of such entity, 82 + whether by contract or otherwise, or (b) ownership of more than 83 + fifty percent (50%) of the outstanding shares or beneficial 84 + ownership of such entity. 85 + 86 + 2. License Grants and Conditions 87 + -------------------------------- 88 + 89 + 2.1. Grants 90 + 91 + Each Contributor hereby grants You a world-wide, royalty-free, 92 + non-exclusive license: 93 + 94 + (a) under intellectual property rights (other than patent or trademark) 95 + Licensable by such Contributor to use, reproduce, make available, 96 + modify, display, perform, distribute, and otherwise exploit its 97 + Contributions, either on an unmodified basis, with Modifications, or 98 + as part of a Larger Work; and 99 + 100 + (b) under Patent Claims of such Contributor to make, use, sell, offer 101 + for sale, have made, import, and otherwise transfer either its 102 + Contributions or its Contributor Version. 103 + 104 + 2.2. Effective Date 105 + 106 + The licenses granted in Section 2.1 with respect to any Contribution 107 + become effective for each Contribution on the date the Contributor first 108 + distributes such Contribution. 109 + 110 + 2.3. Limitations on Grant Scope 111 + 112 + The licenses granted in this Section 2 are the only rights granted under 113 + this License. No additional rights or licenses will be implied from the 114 + distribution or licensing of Covered Software under this License. 115 + Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 + Contributor: 117 + 118 + (a) for any code that a Contributor has removed from Covered Software; 119 + or 120 + 121 + (b) for infringements caused by: (i) Your and any other third party's 122 + modifications of Covered Software, or (ii) the combination of its 123 + Contributions with other software (except as part of its Contributor 124 + Version); or 125 + 126 + (c) under Patent Claims infringed by Covered Software in the absence of 127 + its Contributions. 128 + 129 + This License does not grant any rights in the trademarks, service marks, 130 + or logos of any Contributor (except as may be necessary to comply with 131 + the notice requirements in Section 3.4). 132 + 133 + 2.4. Subsequent Licenses 134 + 135 + No Contributor makes additional grants as a result of Your choice to 136 + distribute the Covered Software under a subsequent version of this 137 + License (see Section 10.2) or under the terms of a Secondary License (if 138 + permitted under the terms of Section 3.3). 139 + 140 + 2.5. Representation 141 + 142 + Each Contributor represents that the Contributor believes its 143 + Contributions are its original creation(s) or it has sufficient rights 144 + to grant the rights to its Contributions conveyed by this License. 145 + 146 + 2.6. Fair Use 147 + 148 + This License is not intended to limit any rights You have under 149 + applicable copyright doctrines of fair use, fair dealing, or other 150 + equivalents. 151 + 152 + 2.7. Conditions 153 + 154 + Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 + in Section 2.1. 156 + 157 + 3. Responsibilities 158 + ------------------- 159 + 160 + 3.1. Distribution of Source Form 161 + 162 + All distribution of Covered Software in Source Code Form, including any 163 + Modifications that You create or to which You contribute, must be under 164 + the terms of this License. You must inform recipients that the Source 165 + Code Form of the Covered Software is governed by the terms of this 166 + License, and how they can obtain a copy of this License. You may not 167 + attempt to alter or restrict the recipients' rights in the Source Code 168 + Form. 169 + 170 + 3.2. Distribution of Executable Form 171 + 172 + If You distribute Covered Software in Executable Form then: 173 + 174 + (a) such Covered Software must also be made available in Source Code 175 + Form, as described in Section 3.1, and You must inform recipients of 176 + the Executable Form how they can obtain a copy of such Source Code 177 + Form by reasonable means in a timely manner, at a charge no more 178 + than the cost of distribution to the recipient; and 179 + 180 + (b) You may distribute such Executable Form under the terms of this 181 + License, or sublicense it under different terms, provided that the 182 + license for the Executable Form does not attempt to limit or alter 183 + the recipients' rights in the Source Code Form under this License. 184 + 185 + 3.3. Distribution of a Larger Work 186 + 187 + You may create and distribute a Larger Work under terms of Your choice, 188 + provided that You also comply with the requirements of this License for 189 + the Covered Software. If the Larger Work is a combination of Covered 190 + Software with a work governed by one or more Secondary Licenses, and the 191 + Covered Software is not Incompatible With Secondary Licenses, this 192 + License permits You to additionally distribute such Covered Software 193 + under the terms of such Secondary License(s), so that the recipient of 194 + the Larger Work may, at their option, further distribute the Covered 195 + Software under the terms of either this License or such Secondary 196 + License(s). 197 + 198 + 3.4. Notices 199 + 200 + You may not remove or alter the substance of any license notices 201 + (including copyright notices, patent notices, disclaimers of warranty, 202 + or limitations of liability) contained within the Source Code Form of 203 + the Covered Software, except that You may alter any license notices to 204 + the extent required to remedy known factual inaccuracies. 205 + 206 + 3.5. Application of Additional Terms 207 + 208 + You may choose to offer, and to charge a fee for, warranty, support, 209 + indemnity or liability obligations to one or more recipients of Covered 210 + Software. However, You may do so only on Your own behalf, and not on 211 + behalf of any Contributor. You must make it absolutely clear that any 212 + such warranty, support, indemnity, or liability obligation is offered by 213 + You alone, and You hereby agree to indemnify every Contributor for any 214 + liability incurred by such Contributor as a result of warranty, support, 215 + indemnity or liability terms You offer. You may include additional 216 + disclaimers of warranty and limitations of liability specific to any 217 + jurisdiction. 218 + 219 + 4. Inability to Comply Due to Statute or Regulation 220 + --------------------------------------------------- 221 + 222 + If it is impossible for You to comply with any of the terms of this 223 + License with respect to some or all of the Covered Software due to 224 + statute, judicial order, or regulation then You must: (a) comply with 225 + the terms of this License to the maximum extent possible; and (b) 226 + describe the limitations and the code they affect. Such description must 227 + be placed in a text file included with all distributions of the Covered 228 + Software under this License. Except to the extent prohibited by statute 229 + or regulation, such description must be sufficiently detailed for a 230 + recipient of ordinary skill to be able to understand it. 231 + 232 + 5. Termination 233 + -------------- 234 + 235 + 5.1. The rights granted under this License will terminate automatically 236 + if You fail to comply with any of its terms. However, if You become 237 + compliant, then the rights granted under this License from a particular 238 + Contributor are reinstated (a) provisionally, unless and until such 239 + Contributor explicitly and finally terminates Your grants, and (b) on an 240 + ongoing basis, if such Contributor fails to notify You of the 241 + non-compliance by some reasonable means prior to 60 days after You have 242 + come back into compliance. Moreover, Your grants from a particular 243 + Contributor are reinstated on an ongoing basis if such Contributor 244 + notifies You of the non-compliance by some reasonable means, this is the 245 + first time You have received notice of non-compliance with this License 246 + from such Contributor, and You become compliant prior to 30 days after 247 + Your receipt of the notice. 248 + 249 + 5.2. If You initiate litigation against any entity by asserting a patent 250 + infringement claim (excluding declaratory judgment actions, 251 + counter-claims, and cross-claims) alleging that a Contributor Version 252 + directly or indirectly infringes any patent, then the rights granted to 253 + You by any and all Contributors for the Covered Software under Section 254 + 2.1 of this License shall terminate. 255 + 256 + 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 + end user license agreements (excluding distributors and resellers) which 258 + have been validly granted by You or Your distributors under this License 259 + prior to termination shall survive termination. 260 + 261 + ************************************************************************ 262 + * * 263 + * 6. Disclaimer of Warranty * 264 + * ------------------------- * 265 + * * 266 + * Covered Software is provided under this License on an "as is" * 267 + * basis, without warranty of any kind, either expressed, implied, or * 268 + * statutory, including, without limitation, warranties that the * 269 + * Covered Software is free of defects, merchantable, fit for a * 270 + * particular purpose or non-infringing. The entire risk as to the * 271 + * quality and performance of the Covered Software is with You. * 272 + * Should any Covered Software prove defective in any respect, You * 273 + * (not any Contributor) assume the cost of any necessary servicing, * 274 + * repair, or correction. This disclaimer of warranty constitutes an * 275 + * essential part of this License. No use of any Covered Software is * 276 + * authorized under this License except under this disclaimer. * 277 + * * 278 + ************************************************************************ 279 + 280 + ************************************************************************ 281 + * * 282 + * 7. Limitation of Liability * 283 + * -------------------------- * 284 + * * 285 + * Under no circumstances and under no legal theory, whether tort * 286 + * (including negligence), contract, or otherwise, shall any * 287 + * Contributor, or anyone who distributes Covered Software as * 288 + * permitted above, be liable to You for any direct, indirect, * 289 + * special, incidental, or consequential damages of any character * 290 + * including, without limitation, damages for lost profits, loss of * 291 + * goodwill, work stoppage, computer failure or malfunction, or any * 292 + * and all other commercial damages or losses, even if such party * 293 + * shall have been informed of the possibility of such damages. This * 294 + * limitation of liability shall not apply to liability for death or * 295 + * personal injury resulting from such party's negligence to the * 296 + * extent applicable law prohibits such limitation. Some * 297 + * jurisdictions do not allow the exclusion or limitation of * 298 + * incidental or consequential damages, so this exclusion and * 299 + * limitation may not apply to You. * 300 + * * 301 + ************************************************************************ 302 + 303 + 8. Litigation 304 + ------------- 305 + 306 + Any litigation relating to this License may be brought only in the 307 + courts of a jurisdiction where the defendant maintains its principal 308 + place of business and such litigation shall be governed by laws of that 309 + jurisdiction, without reference to its conflict-of-law provisions. 310 + Nothing in this Section shall prevent a party's ability to bring 311 + cross-claims or counter-claims. 312 + 313 + 9. Miscellaneous 314 + ---------------- 315 + 316 + This License represents the complete agreement concerning the subject 317 + matter hereof. If any provision of this License is held to be 318 + unenforceable, such provision shall be reformed only to the extent 319 + necessary to make it enforceable. Any law or regulation which provides 320 + that the language of a contract shall be construed against the drafter 321 + shall not be used to construe this License against a Contributor. 322 + 323 + 10. Versions of the License 324 + --------------------------- 325 + 326 + 10.1. New Versions 327 + 328 + Mozilla Foundation is the license steward. Except as provided in Section 329 + 10.3, no one other than the license steward has the right to modify or 330 + publish new versions of this License. Each version will be given a 331 + distinguishing version number. 332 + 333 + 10.2. Effect of New Versions 334 + 335 + You may distribute the Covered Software under the terms of the version 336 + of the License under which You originally received the Covered Software, 337 + or under the terms of any subsequent version published by the license 338 + steward. 339 + 340 + 10.3. Modified Versions 341 + 342 + If you create software not governed by this License, and you want to 343 + create a new license for such software, you may create and use a 344 + modified version of this License if you rename the license and remove 345 + any references to the name of the license steward (except to note that 346 + such modified license differs from this License). 347 + 348 + 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 + Licenses 350 + 351 + If You choose to distribute Source Code Form that is Incompatible With 352 + Secondary Licenses under the terms of this version of the License, the 353 + notice described in Exhibit B of this License must be attached. 354 + 355 + Exhibit A - Source Code Form License Notice 356 + ------------------------------------------- 357 + 358 + This Source Code Form is subject to the terms of the Mozilla Public 359 + License, v. 2.0. If a copy of the MPL was not distributed with this 360 + file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 + 362 + If it is not possible or desirable to put the notice in a particular 363 + file, then You may include the notice in a location (such as a LICENSE 364 + file in a relevant directory) where a recipient would be likely to look 365 + for such a notice. 366 + 367 + You may add additional accurate notices of copyright ownership. 368 + 369 + Exhibit B - "Incompatible With Secondary Licenses" Notice 370 + --------------------------------------------------------- 371 + 372 + This Source Code Form is "Incompatible With Secondary Licenses", as 373 + defined by the Mozilla Public License, v. 2.0.
+3
Makefile
··· 1 + .PHONY: t 2 + t: 3 + zig build test --summary all -freference-trace
+21
build.zig
··· 1 + const std = @import("std"); 2 + 3 + pub fn build(b: *std.Build) !void { 4 + const target = b.standardTargetOptions(.{}); 5 + const optimize = b.standardOptimizeOption(.{}); 6 + 7 + _ = b.addModule("string_builder", .{ 8 + .source_file = .{ .path = "src/string_builder.zig" }, 9 + }); 10 + 11 + const lib_test = b.addTest(.{ 12 + .root_source_file = .{ .path = "src/string_builder.zig" }, 13 + .target = target, 14 + .optimize = optimize, 15 + }); 16 + const run_test = b.addRunArtifact(lib_test); 17 + run_test.has_side_effects = true; 18 + 19 + const test_step = b.step("test", "Run tests"); 20 + test_step.dependOn(&run_test.step); 21 + }
+40
readme.md
··· 1 + # StringBuilder For Zig 2 + 3 + Behaves a lot like a `std.ArrayList(u8)` but with a cleaner interface and pooling capabilities. 4 + 5 + ```zig 6 + const StringBuilder = @import("string_builder").StringBuilder; 7 + 8 + // Starts off with a static buffer of 100 bytes 9 + // If you go over this, memory will be dynamically allocated 10 + // It's like calling ensureTotalCapacity on an ArrrayList, but 11 + // those 100 bytes (aka the static portion of the buffer) are 12 + // re-used when pooling is used 13 + 14 + var sb = try new StringBuilder(allocator, 100); 15 + 16 + try sb.writeByte('o'); 17 + try sb.write('ver 9000!1'); 18 + sb.truncate(1); 19 + 20 + sb.len(); // 10 21 + sb.string(); // "over 9000!" 22 + ``` 23 + 24 + You can call `sb.writer()` to get an `std.io.Writer`. 25 + ``` 26 + 27 + ## Pooling 28 + 29 + ```zig 30 + const Pool = @import("string_builder").Pool; 31 + 32 + 33 + // Creates pool of 100 StringBuilders, each configured with a static buffer 34 + // of 10000 bytes 35 + var pool = try Pool.init(allocator, 100, 10000); 36 + var sb = try pool.acquire(); 37 + defer pool.release(sb); 38 + ``` 39 + 40 + The `Pool` is thread-safe. The `StringBuilder` is not thread safe.
+138
src/pool.zig
··· 1 + const std = @import("std"); 2 + const builtin = @import("builtin"); 3 + 4 + const StringBuilder = @import("string_builder.zig").StringBuilder; 5 + 6 + const Mutex = std.Thread.Mutex; 7 + const Allocator = std.mem.Allocator; 8 + 9 + pub const Pool = struct { 10 + mutex: Mutex, 11 + available: usize, 12 + allocator: Allocator, 13 + builder_size: usize, 14 + builders: []*StringBuilder, 15 + 16 + pub fn init(allocator: Allocator, pool_size: u16, builder_size: usize) !Pool { 17 + const builders = try allocator.alloc(*StringBuilder, pool_size); 18 + 19 + for (0..pool_size) |i| { 20 + var sb = try allocator.create(StringBuilder); 21 + sb.* = try StringBuilder.init(allocator, builder_size); 22 + builders[i] = sb; 23 + } 24 + 25 + return Pool{ 26 + .mutex = Mutex{}, 27 + .builders = builders, 28 + .allocator = allocator, 29 + .available = pool_size, 30 + .builder_size = builder_size 31 + }; 32 + } 33 + 34 + pub fn deinit(self: *Pool) void { 35 + const allocator = self.allocator; 36 + for (self.builders) |sb| { 37 + sb.deinit(); 38 + allocator.destroy(sb); 39 + } 40 + allocator.free(self.builders); 41 + } 42 + 43 + pub fn acquire(self: *Pool) !*StringBuilder { 44 + self.mutex.lock(); 45 + 46 + const builders = self.builders; 47 + const available = self.available; 48 + if (available == 0) { 49 + // dont hold the lock over factory 50 + self.mutex.unlock(); 51 + const allocator = self.allocator; 52 + 53 + const sb = try allocator.create(StringBuilder); 54 + sb.* = try StringBuilder.init(allocator, self.builder_size); 55 + if (comptime builtin.is_test) sb.buf[0] = 0; 56 + return sb; 57 + } 58 + const index = available - 1; 59 + const sb = builders[index]; 60 + self.available = index; 61 + self.mutex.unlock(); 62 + return sb; 63 + } 64 + 65 + pub fn release(self: *Pool, sb: *StringBuilder) void { 66 + sb.reset(); 67 + self.mutex.lock(); 68 + 69 + var builders = self.builders; 70 + const available = self.available; 71 + if (available == builders.len) { 72 + self.mutex.unlock(); 73 + const allocator = self.allocator; 74 + sb.deinit(); 75 + allocator.destroy(sb); 76 + return; 77 + } 78 + builders[available] = sb; 79 + self.available = available + 1; 80 + self.mutex.unlock(); 81 + } 82 + }; 83 + 84 + const t = @import("t.zig"); 85 + test "pool: acquire and release" { 86 + var p = try Pool.init(t.allocator, 2, 100); 87 + defer p.deinit(); 88 + 89 + var sb1a = p.acquire() catch unreachable; 90 + var sb2a = p.acquire() catch unreachable; 91 + var sb3a = p.acquire() catch unreachable; // this should be dynamically generated 92 + 93 + try t.expectEqual(false, sb1a == sb2a); 94 + try t.expectEqual(false, sb2a == sb3a); 95 + 96 + p.release(sb1a); 97 + 98 + var sb1b = p.acquire() catch unreachable; 99 + try t.expectEqual(true, sb1a == sb1b); 100 + 101 + p.release(sb3a); 102 + p.release(sb2a); 103 + p.release(sb1b); 104 + } 105 + 106 + test "pool: threadsafety" { 107 + var p = try Pool.init(t.allocator, 4, 20); 108 + defer p.deinit(); 109 + 110 + // initialize this to 0 since we're asserting that it's 0 111 + for (p.builders) |sb| { 112 + sb.buf[0] = 0; 113 + } 114 + 115 + const t1 = try std.Thread.spawn(.{}, testPool, .{&p}); 116 + const t2 = try std.Thread.spawn(.{}, testPool, .{&p}); 117 + const t3 = try std.Thread.spawn(.{}, testPool, .{&p}); 118 + const t4 = try std.Thread.spawn(.{}, testPool, .{&p}); 119 + const t5 = try std.Thread.spawn(.{}, testPool, .{&p}); 120 + 121 + t1.join(); t2.join(); t3.join(); t4.join(); t5.join(); 122 + } 123 + 124 + fn testPool(p: *Pool) void { 125 + var r = t.getRandom(); 126 + const random = r.random(); 127 + 128 + for (0..5000) |_| { 129 + var sb = p.acquire() catch unreachable; 130 + // no other thread should have set this to 255 131 + std.debug.assert(sb.buf[0] == 0); 132 + 133 + sb.buf[0] = 255; 134 + std.time.sleep(random.uintAtMost(u32, 100000)); 135 + sb.buf[0] = 0; 136 + p.release(sb); 137 + } 138 + }
+258
src/string_builder.zig
··· 1 + const std = @import("std"); 2 + 3 + const Allocator = std.mem.Allocator; 4 + pub const Pool = @import("pool.zig").Pool; 5 + 6 + pub const StringBuilder = struct { 7 + allocator: Allocator, 8 + 9 + // where in buf we currently are 10 + pos: usize, 11 + 12 + // fixed size, created on startup 13 + static: []u8, 14 + 15 + // created when we try to write more than static.len 16 + dynamic: ?[]u8, 17 + 18 + // points to either static or dynamic, 19 + buf: []u8, 20 + 21 + pub fn init(allocator: Allocator, size: usize) !StringBuilder { 22 + const static = try allocator.alloc(u8, size); 23 + return .{ 24 + .pos = 0, 25 + .dynamic = null, 26 + .buf = static, 27 + .static = static, 28 + .allocator = allocator, 29 + }; 30 + } 31 + 32 + pub fn deinit(self: StringBuilder) void { 33 + const allocator = self.allocator; 34 + allocator.free(self.static); 35 + if (self.dynamic) |dyn| { 36 + allocator.free(dyn); 37 + } 38 + } 39 + 40 + pub fn reset(self: *StringBuilder) void { 41 + self.pos = 0; 42 + if (self.dynamic) |dyn| { 43 + self.allocator.free(dyn); 44 + self.dynamic = null; 45 + self.buf = self.static; 46 + } 47 + } 48 + 49 + pub fn len(self: StringBuilder) usize { 50 + return self.pos; 51 + } 52 + 53 + pub fn string(self: StringBuilder) []const u8 { 54 + return self.buf[0..self.pos]; 55 + } 56 + 57 + pub fn truncate(self: *StringBuilder, n: usize) void { 58 + const pos = self.pos; 59 + if (n >= pos) { 60 + self.pos = 0; 61 + return; 62 + } 63 + self.pos = pos - n; 64 + } 65 + 66 + pub fn writeByte(self: *StringBuilder, b: u8) !void { 67 + try self.ensureUnusedCapacity(1); 68 + self.writeByteAssumeCapacity(b); 69 + } 70 + 71 + pub fn writeByteAssumeCapacity(self: *StringBuilder, b: u8) void { 72 + const pos = self.pos; 73 + self.buf[pos] = b; 74 + self.pos = pos + 1; 75 + } 76 + 77 + pub fn writeByteNTimes(self: *StringBuilder, b: u8, n: usize) !void { 78 + try self.ensureUnusedCapacity(n); 79 + const pos = self.pos; 80 + const buf = self.buf; 81 + for (0..n) |offset| { 82 + buf[pos+offset] = b; 83 + } 84 + self.pos = pos + n; 85 + } 86 + 87 + pub fn write(self: *StringBuilder, data: []const u8) !void { 88 + try self.ensureUnusedCapacity(data.len); 89 + return self.writeAssumeCapacity(data); 90 + } 91 + 92 + pub fn writeAssumeCapacity(self: *StringBuilder, data: []const u8) void { 93 + const pos = self.pos; 94 + const end_pos = pos + data.len; 95 + std.mem.copyForwards(u8, self.buf[pos..end_pos], data); 96 + self.pos = end_pos; 97 + } 98 + 99 + pub fn ensureUnusedCapacity(self: *StringBuilder, n: usize) !void { 100 + return self.ensureTotalCapacity(self.pos + n); 101 + } 102 + 103 + pub fn ensureTotalCapacity(self: *StringBuilder, required_capacity: usize) !void { 104 + const buf = self.buf; 105 + if (required_capacity <= buf.len) { 106 + return; 107 + } 108 + 109 + // from std.ArrayList 110 + var new_capacity = self.buf.len; 111 + while (true) { 112 + new_capacity +|= new_capacity / 2 + 8; 113 + if (new_capacity >= required_capacity) break; 114 + } 115 + 116 + const allocator = self.allocator; 117 + if (buf.ptr == self.static.ptr or !allocator.resize(buf, new_capacity)) { 118 + const new_buffer = try allocator.alloc(u8, new_capacity); 119 + std.mem.copyForwards(u8, new_buffer[0..buf.len], buf); 120 + 121 + if (self.dynamic) |dyn| { 122 + allocator.free(dyn); 123 + } 124 + 125 + self.buf = new_buffer; 126 + self.dynamic = new_buffer; 127 + } else { 128 + const new_buffer = buf.ptr[0..new_capacity]; 129 + self.buf = new_buffer; 130 + self.dynamic = new_buffer; 131 + } 132 + } 133 + 134 + pub fn writer(self: *StringBuilder) Writer.IOWriter { 135 + return .{.context = Writer.init(self)}; 136 + } 137 + 138 + pub const Writer = struct { 139 + sb: *StringBuilder, 140 + 141 + pub const Error = Allocator.Error; 142 + pub const IOWriter = std.io.Writer(Writer, error{OutOfMemory}, Writer.write); 143 + 144 + fn init(sb: *StringBuilder) Writer { 145 + return .{.sb = sb}; 146 + } 147 + 148 + pub fn write(self: Writer, data: []const u8) Allocator.Error!usize { 149 + try self.sb.write(data); 150 + return data.len; 151 + } 152 + }; 153 + }; 154 + 155 + const t = @import("t.zig"); 156 + test { 157 + std.testing.refAllDecls(@This()); 158 + } 159 + 160 + test "growth" { 161 + var sb = try StringBuilder.init(t.allocator, 10); 162 + defer sb.deinit(); 163 + 164 + // we reset at the end of the loop, and things should work the exact same 165 + // after a reset 166 + for (0..5) |_| { 167 + try t.expectEqual(0, sb.len()); 168 + try sb.writeByte('o'); 169 + try t.expectEqual(1, sb.len()); 170 + try t.expectString("o", sb.string()); 171 + try t.expectEqual(true, sb.dynamic == null); 172 + 173 + // stays in static 174 + try sb.write("ver 9000!"); 175 + try t.expectEqual(10, sb.len()); 176 + try t.expectString("over 9000!", sb.string()); 177 + try t.expectEqual(true, sb.dynamic == null); 178 + 179 + // grows into dynamic 180 + try sb.write("!!!"); 181 + try t.expectEqual(13, sb.len()); 182 + try t.expectString("over 9000!!!!", sb.string()); 183 + try t.expectEqual(false, sb.dynamic == null); 184 + 185 + 186 + try sb.write("If you were to run this code, you'd almost certainly see a segmentation fault (aka, segfault). We create a Response which involves creating an ArenaAllocator and from that, an Allocator. This allocator is then used to format our string. For the purpose of this example, we create a 2nd response and immediately free it. We need this for the same reason that warning1 in our first example printed an almost ok value: we want to re-initialize the memory in our init function stack."); 187 + try t.expectEqual(492, sb.len()); 188 + try t.expectString("over 9000!!!!If you were to run this code, you'd almost certainly see a segmentation fault (aka, segfault). We create a Response which involves creating an ArenaAllocator and from that, an Allocator. This allocator is then used to format our string. For the purpose of this example, we create a 2nd response and immediately free it. We need this for the same reason that warning1 in our first example printed an almost ok value: we want to re-initialize the memory in our init function stack.", sb.string()); 189 + 190 + sb.reset(); 191 + } 192 + } 193 + 194 + test "truncate" { 195 + var sb = try StringBuilder.init(t.allocator, 10); 196 + defer sb.deinit(); 197 + 198 + sb.truncate(100); 199 + try t.expectEqual(0, sb.len()); 200 + 201 + try sb.write("hello world!1"); 202 + 203 + sb.truncate(0); 204 + try t.expectEqual(13, sb.len()); 205 + try t.expectString("hello world!1", sb.string()); 206 + 207 + sb.truncate(1); 208 + try t.expectEqual(12, sb.len()); 209 + try t.expectString("hello world!", sb.string()); 210 + 211 + sb.truncate(5); 212 + try t.expectEqual(7, sb.len()); 213 + try t.expectString("hello w", sb.string()); 214 + } 215 + 216 + test "fuzz" { 217 + var control = std.ArrayList(u8).init(t.allocator); 218 + defer control.deinit(); 219 + 220 + var r = t.getRandom(); 221 + const random = r.random(); 222 + 223 + var arena = std.heap.ArenaAllocator.init(t.allocator); 224 + defer arena.deinit(); 225 + 226 + const aa = arena.allocator(); 227 + 228 + for (1..100) |_| { 229 + var sb = try StringBuilder.init(t.allocator, random.uintAtMost(u16, 1000) + 1); 230 + defer sb.deinit(); 231 + 232 + for (1..100) |_| { 233 + const input = testString(aa, random); 234 + try sb.write(input); 235 + try control.appendSlice(input); 236 + try t.expectString(control.items, sb.string()); 237 + } 238 + sb.reset(); 239 + control.clearRetainingCapacity(); 240 + _ = arena.reset(.free_all); 241 + } 242 + } 243 + 244 + test "writer" { 245 + var sb = try StringBuilder.init(t.allocator, 10); 246 + defer sb.deinit(); 247 + 248 + try std.json.stringify(.{.over = 9000, .spice = "must flow", .ok = true}, .{}, sb.writer()); 249 + try t.expectString("{\"over\":9000,\"spice\":\"must flow\",\"ok\":true}", sb.string()); 250 + } 251 + 252 + fn testString(allocator: Allocator, random: std.rand.Random) []const u8 { 253 + var s = allocator.alloc(u8, random.uintAtMost(u8, 100) + 1) catch unreachable; 254 + for (0..s.len) |i| { 255 + s[i] = random.uintAtMost(u8, 90) + 32; 256 + } 257 + return s; 258 + }
+16
src/t.zig
··· 1 + const std = @import("std"); 2 + pub const allocator = std.testing.allocator; 3 + 4 + // std.testing.expectEqual won't coerce expected to actual, which is a problem 5 + // when expected is frequently a comptime. 6 + // https://github.com/ziglang/zig/issues/4437 7 + pub fn expectEqual(expected: anytype, actual: anytype) !void { 8 + try std.testing.expectEqual(@as(@TypeOf(actual), expected), actual); 9 + } 10 + pub const expectString = std.testing.expectEqualStrings; 11 + 12 + pub fn getRandom() std.rand.DefaultPrng { 13 + var seed: u64 = undefined; 14 + std.os.getrandom(std.mem.asBytes(&seed)) catch unreachable; 15 + return std.rand.DefaultPrng.init(seed); 16 + }