A high performance Protein Databank parser written in Zig.
0

Configure Feed

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

chore: update to Zig v0.16.0

Ethan Holz (Apr 28, 2026, 9:37 PM -0600) cd730956 eb0b2efe

+78 -66
+1 -1
flake.nix
··· 19 19 flake-utils.lib.eachSystem systems ( 20 20 system: let 21 21 pkgs = nixpkgs.legacyPackages.${system}; 22 - zig = zig-overlay.packages.${system}."0.15.2"; 22 + zig = zig-overlay.packages.${system}."0.16.0"; 23 23 in rec { 24 24 formatter = nixpkgs.legacyPackages.${system}.nixpkgs-fmt; 25 25 packages.default = packages.sonic-pdb-parser;
+9 -9
src/fasta-lib.zig
··· 67 67 } 68 68 69 69 pub fn pdbToFasta(allocator: std.mem.Allocator, lines: []const u8) ![]const u8 { 70 - var builder: std.ArrayList(u8) = .empty; 71 - errdefer builder.deinit(allocator); 72 - const writer = builder.writer(allocator); 70 + var builder = std.Io.Writer.Allocating.init(allocator); 71 + errdefer builder.deinit(); 72 + const writer = &builder.writer; 73 73 var prevChainID: u8 = 0; 74 74 var recordNumber: u32 = 0; 75 75 var input = std.mem.tokenizeScalar(u8, lines, '\n'); ··· 78 78 if (line.len < 6) continue; 79 79 const tag_int = std.mem.readInt(u48, line[0..6], .little); 80 80 if (tag_int == end) break; 81 - const tag = std.meta.intToEnum(records.RecordType, tag_int) catch continue; 81 + const tag = records.recordTypeFromInt(tag_int) orelse continue; 82 82 var record = try Record.parse(line, tag, recordNumber, allocator); 83 83 defer record.free(allocator); 84 84 if (record == .endmdl) break; 85 85 recordNumber = record.serial(); 86 86 try handleRecord(writer, record, &prevChainID); 87 87 } 88 - return try builder.toOwnedSlice(allocator); 88 + return try builder.toOwnedSlice(); 89 89 } 90 90 91 91 // Handles the conversion of a list of records to a fasta file 92 92 pub fn recordsToFasta(allocator: std.mem.Allocator, input: std.ArrayList(Record)) ![]const u8 { 93 - var builder: std.ArrayList(u8) = .empty; 94 - errdefer builder.deinit(allocator); 95 - const writer = builder.writer(allocator); 93 + var builder = std.Io.Writer.Allocating.init(allocator); 94 + errdefer builder.deinit(); 95 + const writer = &builder.writer; 96 96 var prevChainID: u8 = 0; 97 97 for (input.items) |record| { 98 98 if (record == .endmdl) break; 99 99 try handleRecord(writer, record, &prevChainID); 100 100 } 101 - return try builder.toOwnedSlice(allocator); 101 + return try builder.toOwnedSlice(); 102 102 } 103 103 104 104 // test "recordsToFasta" {
+20 -18
src/fasta.zig
··· 8 8 fileName: []const u8 = "", 9 9 output: ?[]const u8 = null, 10 10 11 - pub fn parseArgs(argsList: [][:0]u8) !Args { 11 + pub fn parseArgs(argsList: []const [:0]const u8) !Args { 12 12 var args = Args{}; 13 13 for (argsList, 0..) |arg, idx| { 14 14 if (strings.equals(arg, "-f")) { ··· 19 19 } 20 20 if (strings.equals(arg, "-h")) { 21 21 std.debug.print("Usage: pdb2fasta -f <file> -o <output>\n", .{}); 22 - std.posix.exit(0); 22 + std.process.exit(0); 23 23 } 24 24 } 25 25 if (strings.equals(args.fileName, "")) { 26 26 std.debug.print("No file specified, please provide a file\n", .{}); 27 - std.posix.exit(1); 27 + std.process.exit(1); 28 28 } 29 29 return args; 30 30 } 31 31 }; 32 32 33 - pub fn main() !void { 34 - const allocator = std.heap.page_allocator; 35 - const args = try std.process.argsAlloc(allocator); 36 - defer std.process.argsFree(allocator, args); 33 + pub fn main(init: std.process.Init) !void { 34 + const gpa = init.gpa; 35 + // const allocator = std.heap.page_allocator; 36 + const args = try init.minimal.args.toSlice(init.arena.allocator()); 37 + // const args = try std.process.argsAlloc(allocator); 38 + // defer std.process.argsFree(allocator, args); 37 39 const parsedArgs = try Args.parseArgs(args); 38 40 39 - const file = try std.fs.cwd().openFile(parsedArgs.fileName, .{}); 40 - defer file.close(); 41 + const file = try std.Io.Dir.cwd().openFile(init.io, parsedArgs.fileName, .{}); 42 + defer file.close(init.io); 41 43 var read_buffer: [4096]u8 = undefined; 42 - var file_reader = file.reader(&read_buffer); 43 - const atoms = try file_reader.interface.allocRemaining(allocator, .unlimited); 44 - defer allocator.free(atoms); 44 + var file_reader = file.reader(init.io, &read_buffer); 45 + const atoms = try file_reader.interface.allocRemaining(gpa, .unlimited); 46 + defer gpa.free(atoms); 45 47 46 - const converted = try fasta.pdbToFasta(allocator, atoms); 47 - defer allocator.free(converted); 48 + const converted = try fasta.pdbToFasta(gpa, atoms); 49 + defer gpa.free(converted); 48 50 var stdout_buffer: [4096]u8 = undefined; 49 - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); 51 + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &stdout_buffer); 50 52 try stdout_writer.interface.writeAll(converted); 51 53 try stdout_writer.interface.flush(); 52 54 if (parsedArgs.output != null) { 53 - const fastaPath = try std.fs.cwd().createFile(parsedArgs.output.?, .{}); 54 - defer fastaPath.close(); 55 + const fastaPath = try std.Io.Dir.cwd().createFile(init.io, parsedArgs.output.?, .{}); 56 + defer fastaPath.close(init.io); 55 57 var write_buffer: [4096]u8 = undefined; 56 - var fasta_writer = fastaPath.writerStreaming(&write_buffer); 58 + var fasta_writer = fastaPath.writerStreaming(init.io, &write_buffer); 57 59 try fasta_writer.interface.writeAll(converted); 58 60 try fasta_writer.interface.flush(); 59 61 }
+29 -23
src/main.zig
··· 22 22 output: string = "times.csv", 23 23 json: bool = false, 24 24 25 - pub fn parseArgs(argsList: [][:0]u8) !Args { 25 + pub fn parseArgs(argsList: []const [:0]const u8) !Args { 26 26 var args = Args{}; 27 27 for (argsList, 0..) |arg, idx| { 28 28 if (strings.equals(arg, "-r")) { ··· 37 37 if (strings.equals(arg, "--json")) args.json = true; 38 38 if (strings.equals(arg, "-h")) { 39 39 std.debug.print("Usage: exe -r <runs> -f <file> -o <output>\n", .{}); 40 - std.posix.exit(0); 40 + std.process.exit(0); 41 41 } 42 42 } 43 43 if (strings.equals(args.fileName, "")) { 44 44 std.debug.print("No file specified, please provide a file\n", .{}); 45 - std.posix.exit(1); 45 + std.process.exit(1); 46 46 } 47 47 return args; 48 48 } 49 49 }; 50 50 51 - pub fn main() !void { 52 - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 53 - defer _ = gpa.deinit(); 54 - const allocator = gpa.allocator(); 55 - const args = try std.process.argsAlloc(allocator); 56 - defer std.process.argsFree(allocator, args); 51 + pub fn main(init: std.process.Init) !void { 52 + // var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 53 + const allocator = init.gpa; 54 + // defer _ = gpa.deinit(); 55 + // const allocator = gpa.allocator(); 56 + // const args = try std.process.argsAlloc(allocator); 57 + // defer std.process.argsFree(allocator, args); 58 + var args_arena = std.heap.ArenaAllocator.init(allocator); 59 + defer args_arena.deinit(); 60 + const args = try init.minimal.args.toSlice(args_arena.allocator()); 57 61 const parsedArgs = try Args.parseArgs(args); 58 62 59 63 var arena = std.heap.ArenaAllocator.init(allocator); 60 64 defer arena.deinit(); 61 65 const arenaAllocator = arena.allocator(); 62 66 63 - const file = try fs.cwd().openFile(parsedArgs.fileName, .{}); 64 - defer file.close(); 67 + const file = try std.Io.Dir.cwd().openFile(init.io, parsedArgs.fileName, .{}); 68 + defer file.close(init.io); 65 69 66 70 if (parsedArgs.runs == 1) { 67 71 var read_buffer: [4096]u8 = undefined; 68 - var file_reader = file.reader(&read_buffer); 72 + var file_reader = file.reader(init.io, &read_buffer); 69 73 70 74 var pdb = try PDB.init(arenaAllocator); 71 75 defer pdb.deinit(); 72 76 try pdb.read(&file_reader.interface); 73 77 74 78 var stdout_buffer: [4096]u8 = undefined; 75 - var stdout_writer = std.fs.File.stdout().writerStreaming(&stdout_buffer); 79 + var stdout_writer = std.Io.File.stdout().writerStreaming(init.io, &stdout_buffer); 76 80 if (parsedArgs.json) { 77 81 try pdb.writeJson(&stdout_writer.interface); 78 82 try stdout_writer.interface.writeByte('\n'); 83 + try stdout_writer.flush(); 79 84 } else { 80 - try stdout_writer.interface.print("{f}\n", .{pdb}); 85 + try stdout_writer.interface.print("{f}", .{pdb}); 86 + try stdout_writer.flush(); 81 87 } 82 88 try stdout_writer.interface.flush(); 83 89 return; 84 90 } 85 91 86 - var timer = try std.time.Timer.start(); 92 + var timer_start = std.Io.Clock.awake.now(init.io); 87 93 var times = try std.ArrayList(u64).initCapacity(allocator, @intCast(parsedArgs.runs)); 88 94 defer times.deinit(allocator); 89 95 var sum: u64 = 0; 90 - const csv = try fs.cwd().createFile(parsedArgs.output, .{}); 91 - defer csv.close(); 92 - try RunRecord.writeCSVHeader(csv); 96 + const csv = try std.Io.Dir.cwd().createFile(init.io, parsedArgs.output, .{}); 97 + defer csv.close(init.io); 98 + try RunRecord.writeCSVHeader(init.io, csv); 93 99 94 100 // var arena = std.heap.ArenaAllocator.init(allocator); 95 101 // defer arena.deinit(); ··· 98 104 for (0..parsedArgs.runs) |i| { 99 105 defer { 100 106 _ = arena.reset(.retain_capacity); 101 - file.seekTo(0) catch @panic("file error"); 107 + init.io.vtable.fileSeekTo(init.io.userdata, file, 0) catch @panic("file error"); 102 108 } 103 - timer.reset(); 109 + timer_start = std.Io.Clock.awake.now(init.io); 104 110 var read_buffer: [4096]u8 = undefined; 105 - var file_reader = file.reader(&read_buffer); 111 + var file_reader = file.reader(init.io, &read_buffer); 106 112 var pdb = try PDB.init(arenaAllocator); 107 113 try pdb.read(&file_reader.interface); 108 - const elapsed = timer.read(); 114 + const elapsed: u64 = @intCast(timer_start.untilNow(init.io, .awake).toNanoseconds()); 109 115 try times.append(allocator, elapsed); 110 116 var runRecord: RunRecord = RunRecord{ .run = i + 1, .time = elapsed, .file = parsedArgs.fileName }; 111 - try runRecord.writeCSVLine(csv); 117 + try runRecord.writeCSVLine(init.io, csv); 112 118 sum += elapsed; 113 119 std.debug.print("Run {} Complete\n", .{i}); 114 120 }
+19 -15
src/records.zig
··· 68 68 var recordNumber: u32 = 0; 69 69 const end = std.mem.readInt(u48, "END ", .little); 70 70 while (true) { 71 - const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { 72 - error.EndOfStream => break, 73 - else => |e| return e, 74 - }; 71 + const line = (try reader.takeDelimiter('\n')) orelse break; 75 72 if (line.len < 6) continue; 76 73 const tag_int = std.mem.readInt(u48, line[0..6], .little); 77 74 if (tag_int == end) break; 78 - const tag = std.meta.intToEnum(RecordType, tag_int) catch continue; 75 + const tag = recordTypeFromInt(tag_int) orelse continue; 79 76 if (tag == .endmdl) { 80 77 continue; 81 78 } ··· 108 105 // Some of the CHARMM files are longer 109 106 const end = std.mem.readInt(u48, "END ", .little); 110 107 while (true) { 111 - const line = reader.takeDelimiterExclusive('\n') catch |err| switch (err) { 112 - error.EndOfStream => break, 113 - else => |e| return e, 114 - }; 108 + const line = (try reader.takeDelimiter('\n')) orelse break; 115 109 if (line.len < 6) continue; 116 110 const tag_int = std.mem.readInt(u48, line[0..6], .little); 117 111 if (tag_int == end) break; ··· 138 132 file: string, 139 133 140 134 // there is no need to allocate here. you can print directly to the file. 141 - pub fn writeCSVLine(self: *RunRecord, file: std.fs.File) !void { 135 + pub fn writeCSVLine(self: *RunRecord, io: std.Io, file: std.Io.File) !void { 142 136 var buffer: [1024]u8 = undefined; 143 - var file_writer = file.writerStreaming(&buffer); 137 + var file_writer = file.writerStreaming(io, &buffer); 144 138 try file_writer.interface.print("{d},{d},{s}\n", .{ self.run, self.time, self.file }); 145 139 try file_writer.interface.flush(); 146 140 } 147 141 148 - pub fn writeCSVHeader(file: std.fs.File) !void { 142 + pub fn writeCSVHeader(io: std.Io, file: std.Io.File) !void { 149 143 // const fields = @typeInfo(RunRecord).Struct.fields; 150 144 const fields = @typeInfo(RunRecord).@"struct".fields; 151 145 const len = fields.len; 146 + var buffer: [1024]u8 = undefined; 147 + var file_writer = file.writerStreaming(io, &buffer); 152 148 inline for (fields, 0..) |field, idx| { 153 149 std.debug.print("{s}\n", .{field.name}); 154 - _ = try file.write(field.name); 150 + try file_writer.interface.writeAll(field.name); 155 151 if (idx == len - 1) { 156 152 break; 157 153 } 158 - _ = try file.write(","); 154 + try file_writer.interface.writeByte(','); 159 155 } 160 - _ = try file.write("\n"); 156 + try file_writer.interface.writeByte('\n'); 157 + try file_writer.interface.flush(); 161 158 } 162 159 }; 160 + 161 + pub fn recordTypeFromInt(value: u48) ?RecordType { 162 + inline for (std.meta.tags(RecordType)) |tag| { 163 + if (@intFromEnum(tag) == value) return tag; 164 + } 165 + return null; 166 + } 163 167 164 168 // zig fmt: off 165 169 /// An enum derived of possible records in a PDB file