summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Sam Atman2025-05-09 12:58:23 -0400
committerGravatar Sam Atman2025-05-15 15:29:56 -0400
commit75dedc6aec86c1a4f43d0d7cd120abf68b5baeb1 (patch)
treeb65d256bb57ae3bc2656d7afec29daaa0e449030
parentUpdate README.md (diff)
downloadzg-75dedc6aec86c1a4f43d0d7cd120abf68b5baeb1.tar.gz
zg-75dedc6aec86c1a4f43d0d7cd120abf68b5baeb1.tar.xz
zg-75dedc6aec86c1a4f43d0d7cd120abf68b5baeb1.zip
Add reverse CodePoint iterator
-rw-r--r--src/code_point.zig68
1 files changed, 67 insertions, 1 deletions
diff --git a/src/code_point.zig b/src/code_point.zig
index fe7ad6e..a319d36 100644
--- a/src/code_point.zig
+++ b/src/code_point.zig
@@ -233,6 +233,45 @@ const class_mask: [12]u8 = .{
233 0, 233 0,
234}; 234};
235 235
236pub const ReverseIterator = struct {
237 bytes: []const u8,
238 i: ?u32,
239
240 pub fn init(str: []const u8) ReverseIterator {
241 var r_iter: ReverseIterator = undefined;
242 r_iter.bytes = str;
243 r_iter.i = if (str.len == 0) 0 else @intCast(str.len - 1);
244 return r_iter;
245 }
246
247 pub fn prev(iter: *ReverseIterator) ?CodePoint {
248 if (iter.i == null) return null;
249 var i_prev = iter.i.?;
250
251 while (i_prev > 0) : (i_prev -= 1) {
252 if (!followbyte(iter.bytes[i_prev])) break;
253 if (i_prev == 0) break;
254 }
255
256 if (i_prev > 0)
257 iter.i = i_prev - 1
258 else
259 iter.i = null;
260
261 return decode(iter.bytes[i_prev..], i_prev);
262 }
263
264 pub fn peek(iter: *ReverseIterator) ?CodePoint {
265 const saved_i = iter.i;
266 defer iter.i = saved_i;
267 return iter.prev();
268 }
269};
270
271inline fn followbyte(b: u8) bool {
272 return 0x80 <= b and b <= 0xbf;
273}
274
236test "decode" { 275test "decode" {
237 const bytes = "🌩️"; 276 const bytes = "🌩️";
238 const res = decode(bytes, 0); 277 const res = decode(bytes, 0);
@@ -246,7 +285,7 @@ test "decode" {
246 } 285 }
247} 286}
248 287
249test "peek" { 288test Iterator {
250 var iter = Iterator{ .bytes = "Hi" }; 289 var iter = Iterator{ .bytes = "Hi" };
251 290
252 try expectEqual(@as(u21, 'H'), iter.next().?.code); 291 try expectEqual(@as(u21, 'H'), iter.next().?.code);
@@ -346,6 +385,33 @@ test "truncation" {
346 } 385 }
347} 386}
348 387
388test ReverseIterator {
389 {
390 var r_iter: ReverseIterator = .init("ABC");
391 try testing.expectEqual(@as(u21, 'C'), r_iter.prev().?.code);
392 try testing.expectEqual(@as(u21, 'B'), r_iter.peek().?.code);
393 try testing.expectEqual(@as(u21, 'B'), r_iter.prev().?.code);
394 try testing.expectEqual(@as(u21, 'A'), r_iter.prev().?.code);
395 try testing.expectEqual(@as(?CodePoint, null), r_iter.peek());
396 try testing.expectEqual(@as(?CodePoint, null), r_iter.prev());
397 try testing.expectEqual(@as(?CodePoint, null), r_iter.prev());
398 }
399 {
400 var r_iter: ReverseIterator = .init("∅δq🦾ă");
401 try testing.expectEqual(@as(u21, 'ă'), r_iter.prev().?.code);
402 try testing.expectEqual(@as(u21, '🦾'), r_iter.prev().?.code);
403 try testing.expectEqual(@as(u21, 'q'), r_iter.prev().?.code);
404 try testing.expectEqual(@as(u21, 'δ'), r_iter.peek().?.code);
405 try testing.expectEqual(@as(u21, 'δ'), r_iter.prev().?.code);
406 try testing.expectEqual(@as(u21, '∅'), r_iter.peek().?.code);
407 try testing.expectEqual(@as(u21, '∅'), r_iter.peek().?.code);
408 try testing.expectEqual(@as(u21, '∅'), r_iter.prev().?.code);
409 try testing.expectEqual(@as(?CodePoint, null), r_iter.peek());
410 try testing.expectEqual(@as(?CodePoint, null), r_iter.prev());
411 try testing.expectEqual(@as(?CodePoint, null), r_iter.prev());
412 }
413}
414
349const std = @import("std"); 415const std = @import("std");
350const testing = std.testing; 416const testing = std.testing;
351const expect = testing.expect; 417const expect = testing.expect;