const std = @import( "std" ); pub const Stream = struct { src :[]const u8, off :usize = 0, /// initialize a Stream object pub fn init( src :[]const u8 ) Stream { return .{ .src = src }; } /// is no more content available ? pub fn done( stream :*Stream ) bool { return stream.off == stream.src.len; } /// is more content available ? pub fn more( stream :*Stream ) bool { return !stream.done(); } /// get next char /// null if eos /// does not increment pub fn peek( stream :*Stream ) ?u8 { return if( stream.done() ) null else stream.src[ stream.off ]; } /// peek and compare /// does not increment pub fn next( stream :*Stream, n :u8 ) bool { const p = stream.peek() orelse return false; return p == n; } /// get the next char /// error on eos /// increments stream pub fn step( stream :*Stream ) !u8 { if(!( stream.off < stream.src.len )) { return error.end_of_stream; } defer { stream.off += 1; } return stream.src[ stream.off ]; } /// if to_consume matches what is next in stream /// then increment that far. /// if not, error on not equal /// XXX in hindsight, this is a poor api /// should probably return boolean /// or take a single character /// ... /// all 9 usages are 1 char; `try` or `catch unreachable` pub fn consume( stream :*Stream, to_consume :[]const u8 ) !void { const len = to_consume.len; const s = stream.slice( len ) catch { return error.not_equal; }; if( !std.mem.eql( u8, s, to_consume ) ) { return error.not_equal; } stream.off += len; } fn slice( stream :*Stream, len :usize ) ![]const u8 { if( stream.src.len - stream.off < len ) { return error.end_of_stream; } return stream.src[ stream.off.. ][ 0..len ]; } }; const tst = std.testing; test "slice" { // ASDF i get booleans backwards so often... damn var ao = Stream.init( "alpha omega" ); try tst.expectEqualStrings( "", try ao.slice( 0 ) ); try tst.expectEqualStrings( "a", try ao.slice( 1 ) ); try tst.expectEqualStrings( "alph", try ao.slice( 4 ) ); try tst.expectEqualStrings( "alpha o", try ao.slice( 7 ) ); try tst.expectEqualStrings( "alpha omeg", try ao.slice( 10 ) ); try tst.expectEqualStrings( "alpha omega", try ao.slice( 11 ) ); } test "abc" { var abc = Stream.init( "abc" ); try tst.expect( abc.more() ); try tst.expectEqual( abc.peek().?, 'a' ); try tst.expect( abc.next( 'a' ) ); try tst.expectEqual( try abc.step(), 'a' ); try tst.expect( abc.more() ); // std.debug.print( "here\n", .{} ); // std.debug.print( "{any}\n", .{ abc } ); // std.debug.print( "there\n", .{} ); try abc.consume( "bc" ); try tst.expect( abc.done() ); } test "ijk" { var ijk = Stream.init( "ijk" ); try tst.expect( ijk.more() ); while( ijk.more() ) { try tst.expect( ijk.more() ); _ = try ijk.step(); } try tst.expect( ijk.done() ); } test "xyz" { var xyz = Stream.init( "xyz" ); try tst.expect( xyz.more() ); try xyz.consume( "xyz" ); try tst.expect( xyz.done() ); }