├── .DS_Store ├── .gitignore ├── README.md ├── build.zig ├── examples └── file.txt └── src └── main.zig /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EclesioMeloJunior/zig-grep/251c455feb1432bac81f00001d5a53aca0a4dc3f/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | zig-cache/ 2 | zig-out -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-grep 2 | 3 | - To execute this program make sure you have installed zig at version 0.9.1 4 | - You should type `zig build run -- {pattern_here}` then it will search at `examples/file.txt` for the pattern you provided. 5 | 6 | ex: 7 | 8 | ``` 9 | zig build run -- ${text_to_find} ${absolute_file_path} 10 | ``` 11 | 12 | - [x] Provide the file as an CLI parameter 13 | - You should provide the file in the absolute format, for example: `/User/some_user_name/Documents/file.txt` 14 | - [ ] Ignore case sensitivy 15 | - [ ] Increase test coverage 16 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | pub fn build(b: *std.build.Builder) void { 4 | // Standard target options allows the person running `zig build` to choose 5 | // what target to build for. Here we do not override the defaults, which 6 | // means any target is allowed, and the default is native. Other options 7 | // for restricting supported target set are available. 8 | const target = b.standardTargetOptions(.{}); 9 | 10 | // Standard optimization options allow the person running `zig build` to select 11 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not 12 | // set a preferred release mode, allowing the user to decide how to optimize. 13 | const optimize = b.standardOptimizeOption(.{}); 14 | 15 | const exe = b.addExecutable(.{ 16 | .name = "zig-grep", 17 | .root_source_file = .{ .path = "src/main.zig" }, 18 | .target = target, 19 | .optimize = optimize, 20 | }); 21 | 22 | const run_cmd = std.build.addRunArtifact(b, exe); 23 | run_cmd.step.dependOn(b.getInstallStep()); 24 | 25 | const run_step = b.step("run", "Run the app"); 26 | run_step.dependOn(&run_cmd.step); 27 | } 28 | -------------------------------------------------------------------------------- /examples/file.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum non velit quis velit aliquam aliquet id eu lorem. Suspendisse a orci justo. Quisque nec dolor a est imperdiet vulputate. Aliquam ac tempus dui, nec malesuada ex. Nam lacus lacus, malesuada varius mattis in, malesuada vitae ligula. Mauris vitae dapibus nibh, ac interdum velit. Proin vel nulla sed felis sollicitudin scelerisque vel sit amet tortor. Donec ac nunc lacus. Mauris ac maximus tortor, ut euismod nunc. Donec a mi blandit, ullamcorper augue sit amet, sodales nulla. 2 | 3 | Aliquam egestas mi a metus aliquam malesuada. In nec mi vitae neque ultricies laoreet. Sed ut nisi arcu. Cras purus justo, interdum in lacus in, cursus vulputate quam. Vestibulum et lectus quis ex sodales posuere quis id nunc. Nulla nibh nisi, molestie sed nulla a, rutrum porttitor augue. Mauris mi nisl, aliquam quis sodales vitae, bibendum eu lectus. Maecenas a blandit dolor. In eu vehicula ex, id sollicitudin nulla. 4 | 5 | Quisque lacinia risus risus, a interdum sapien sagittis id. Aliquam viverra luctus mattis. Donec in auctor quam. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vivamus at mollis eros. Sed rhoncus euismod varius. Praesent vulputate sollicitudin felis. Nam purus tortor, accumsan non imperdiet at, porttitor sit amet lorem. Nulla at bibendum purus, non accumsan libero. Nullam facilisis pulvinar congue. Vestibulum facilisis eu lorem non rutrum. Etiam iaculis tortor at bibendum tempus. Ut dui libero, gravida sit amet felis eu, elementum ornare est. Aliquam nisi purus, accumsan sed elit sed, consequat faucibus risus. Donec ex risus, sodales porta consectetur et, lobortis quis risus. 6 | 7 | Curabitur venenatis lacus at maximus vulputate. Suspendisse pulvinar volutpat orci, vitae tempor lorem egestas quis. Morbi quis mollis risus. Quisque dignissim ultrices quam vitae dictum. Donec a volutpat mi. Aliquam sed suscipit tortor. Quisque eget lobortis ex. Curabitur id nisl volutpat, consectetur neque vitae, scelerisque tortor. Proin non magna pulvinar, scelerisque neque at, lobortis arcu. Vestibulum nulla risus, vehicula id ornare eu, interdum non tortor. Suspendisse sit amet metus faucibus, consectetur dolor quis, malesuada lectus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque rhoncus risus lorem, ut molestie nisi ullamcorper eu. 8 | 9 | Vestibulum feugiat at sem eu semper. In arcu nulla, malesuada eget vulputate ut, luctus ut tellus. Nam vel accumsan mauris. Proin faucibus odio a ex luctus, eget convallis mauris sollicitudin. Quisque vel nibh semper, fringilla risus finibus, faucibus magna. Vivamus viverra diam sapien, nec pulvinar tellus luctus eget. Curabitur malesuada malesuada tellus eu aliquet. Nulla facilisi. Integer sit amet purus ultricies, rutrum turpis vel, lacinia diam. Donec odio velit, blandit sit amet mi nec, pulvinar eleifend elit. Etiam eros orci, volutpat ut convallis quis, mattis a nisl. Suspendisse dictum elit sed ante posuere, at hendrerit est blandit. Nulla hendrerit pulvinar nunc, in volutpat ipsum laoreet in. Nulla quis tincidunt mauris. -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fs = std.fs; 3 | const heap = std.heap; 4 | const ascii = std.ascii; 5 | const process = std.process; 6 | const expect = std.testing.expect; 7 | 8 | pub fn main() !void { 9 | var args_iterator = process.args(); 10 | defer args_iterator.deinit(); 11 | // we should skip the executable path 12 | // if true the next argument will be the pattern otherwise 13 | // there is no next argument so we should terminate 14 | if (!args_iterator.skip()) { 15 | return; 16 | } 17 | 18 | var arena = heap.ArenaAllocator.init(heap.page_allocator); 19 | defer arena.deinit(); 20 | const arena_alloc = arena.allocator(); 21 | 22 | var pattern: []const u8 = undefined; 23 | if (args_iterator.next()) |cli_pattern| { 24 | pattern = cli_pattern; 25 | } else { 26 | return; 27 | } 28 | 29 | var file_path: []const u8 = undefined; 30 | if (args_iterator.next()) |cli_file_path| { 31 | file_path = cli_file_path; 32 | } else { 33 | return; 34 | } 35 | 36 | var lps_table = try arena_alloc.alloc(usize, pattern.len); 37 | compute_lps(pattern, lps_table); 38 | 39 | const file = try fs.openFileAbsolute(file_path, fs.File.OpenFlags{ 40 | .mode = fs.File.OpenMode.read_only, 41 | }); 42 | defer file.close(); 43 | 44 | var file_metadata = try file.metadata(); 45 | var file_size = file_metadata.size(); 46 | 47 | const max_size: usize = 1024; 48 | var fixed_buffer: [max_size]u8 = undefined; 49 | 50 | var fixed_size_alloc = heap.FixedBufferAllocator.init(&fixed_buffer); 51 | const allocator = fixed_size_alloc.allocator(); 52 | 53 | var offset: usize = 0; 54 | while (true) { 55 | const amount_to_read = file_size - offset; 56 | 57 | var buf: []u8 = undefined; 58 | if (amount_to_read >= max_size) { 59 | buf = try allocator.alloc(u8, max_size); 60 | } else { 61 | buf = try allocator.alloc(u8, amount_to_read); 62 | } 63 | 64 | var bytes_read = try file.pread(buf, offset); 65 | if (bytes_read == 0) { 66 | allocator.free(buf); 67 | break; 68 | } 69 | 70 | if ((bytes_read + offset) < file_size and !ascii.isWhitespace(buf[buf.len - 1])) { 71 | while (!ascii.isWhitespace(buf[bytes_read - 1])) { 72 | bytes_read -= 1; 73 | } 74 | 75 | buf = try allocator.realloc(buf, bytes_read); 76 | } 77 | 78 | offset += bytes_read; 79 | try grep(buf, pattern, lps_table); 80 | 81 | allocator.free(buf); 82 | } 83 | } 84 | 85 | fn compute_lps(pattern: []const u8, lps_table: []usize) void { 86 | lps_table[0] = 0; 87 | 88 | // lets start from index 1 as index 0 89 | // of LPS slice already contains a value 90 | var i: usize = 1; 91 | 92 | // lenght of previous longest proper 93 | // prefix that is also a suffix 94 | var longest_lps: usize = 0; 95 | 96 | while (i < pattern.len) { 97 | if (pattern[longest_lps] == pattern[i]) { 98 | longest_lps += 1; 99 | lps_table[i] = longest_lps; 100 | i += 1; 101 | } else { 102 | if (longest_lps > 0) { 103 | longest_lps = lps_table[longest_lps - 1]; 104 | } else { 105 | lps_table[i] = 0; 106 | i += 1; 107 | } 108 | } 109 | } 110 | } 111 | 112 | fn grep(text_buf: []u8, pattern: []const u8, lps_table: []const usize) !void { 113 | var pattern_idx: usize = 0; 114 | var text_idx: usize = 0; 115 | 116 | while (text_idx < text_buf.len) { 117 | if (pattern[pattern_idx] == text_buf[text_idx]) { 118 | pattern_idx += 1; 119 | text_idx += 1; 120 | 121 | // we found a match 122 | if (pattern_idx == pattern.len) { 123 | pattern_idx = 0; 124 | 125 | var foward: usize = text_idx; 126 | while (foward < text_buf.len) { 127 | if (text_buf[foward] == '\n' or ascii.isWhitespace(text_buf[foward])) { 128 | break; 129 | } 130 | 131 | foward += 1; 132 | } 133 | 134 | // start from the begining of the text that matches the pattern 135 | var backward: usize = text_idx - pattern.len; 136 | while (backward > 0) { 137 | if (text_buf[backward] == '\n' or ascii.isWhitespace(text_buf[backward])) { 138 | // since we are in a new line or a space character we should 139 | // remove it, so we increase the backward by one getting back 140 | // to the place after the space 141 | backward += 1; 142 | break; 143 | } 144 | 145 | backward -= 1; 146 | } 147 | 148 | std.debug.print("{s}\n", .{text_buf[backward..foward]}); 149 | } 150 | } else if (pattern_idx > 0) { 151 | pattern_idx = lps_table[pattern_idx - 1]; 152 | } else { 153 | pattern_idx = 0; 154 | text_idx += 1; 155 | } 156 | } 157 | } 158 | 159 | // TODO: test case sensitivity 160 | test "compute LPS" { 161 | const Test = struct { 162 | pattern: []const u8, 163 | expected: []const usize, 164 | }; 165 | 166 | const tests = [_]Test{ 167 | .{ 168 | .pattern = "ABABD", 169 | .expected = &.{ 0, 0, 1, 2, 0 }, 170 | }, 171 | 172 | .{ 173 | .pattern = "AADAFG", 174 | .expected = &.{ 0, 1, 0, 1, 0, 0 }, 175 | }, 176 | 177 | .{ 178 | .pattern = "abc", 179 | .expected = &.{ 0, 0, 0 }, 180 | }, 181 | }; 182 | 183 | for (tests) |t| { 184 | var lps_table: []usize = undefined; 185 | lps_table = try std.testing.allocator.alloc(usize, t.pattern.len); 186 | compute_lps(t.pattern, lps_table); 187 | 188 | try expect(std.mem.eql(usize, lps_table, t.expected)); 189 | std.testing.allocator.free(lps_table); 190 | } 191 | } 192 | --------------------------------------------------------------------------------