const builtin = @import("builtin"); const std = @import("std"); const testing = std.testing; pub const gc = @import("libzisp/gc.zig"); pub const io = @import("libzisp/io.zig"); pub const lib = @import("libzisp/lib.zig"); pub const value = @import("libzisp/value.zig"); pub const Hval = gc.Hval; pub const ShortString = value.ShortString; pub const Value = value.Value; fn benchmark(name: []const u8, iters: usize, func: fn () anyerror!void) !void { var timer = try std.time.Timer.start(); for (0..iters) |i| { _ = i; try func(); } const ns: f64 = @floatFromInt(timer.lap()); const secs = ns / 1_000_000_000; std.debug.print( "bench {s} x {}: {d:.3}s\n", .{ name, iters, secs }, ); } test "double" { const d1: f64 = 0.123456789; const d2: f64 = -0.987654321; const v1 = value.double.pack(d1); const v2 = value.double.pack(d2); const v3 = value.double.add(v1, v2); const result = value.double.unpack(v3); try std.testing.expect(value.double.check(v1)); try std.testing.expect(value.double.check(v2)); try std.testing.expect(value.double.check(v3)); try std.testing.expectEqual(d1 + d2, result); } test "fixnum" { const int1: i64 = 123456789; const int2: i64 = -987654321; const v1 = value.fixnum.pack(int1); const v2 = value.fixnum.pack(int2); const v3 = value.fixnum.add(v1, v2); const result = value.fixnum.unpack(v3); try std.testing.expect(value.fixnum.check(v1)); try std.testing.expect(value.fixnum.check(v2)); try std.testing.expect(value.fixnum.check(v3)); try std.testing.expectEqual(int1 + int2, result); } test "ptr" { const ptr = value.ptr; const val: *Hval = @ptrFromInt(256); const tag = ptr.Tag.pair; const p = ptr.pack(val, tag); try std.testing.expect(ptr.check(p)); try std.testing.expect(ptr.checkZispTag(p, tag)); try std.testing.expect(ptr.checkStrong(p)); const pv, const pt = ptr.unpack(p); try std.testing.expectEqual(val, pv); try std.testing.expectEqual(tag, pt); var w = ptr.makeWeak(p); try std.testing.expect(ptr.check(w)); try std.testing.expect(ptr.checkZispTag(w, tag)); try std.testing.expect(ptr.checkWeak(w)); try std.testing.expectEqual(true, value.boole.unpack(ptr.predWeak(w))); try std.testing.expectEqual(false, value.boole.unpack(ptr.predWeakNull(w))); const wv, const wt = ptr.unpack(w); try std.testing.expectEqual(val, wv); try std.testing.expectEqual(tag, wt); const wv2, const wt2 = ptr.unpack(ptr.getWeak(w)); try std.testing.expectEqual(val, wv2); try std.testing.expectEqual(tag, wt2); ptr.setWeakNull(&w); try std.testing.expect(ptr.check(w)); try std.testing.expect(ptr.checkWeak(w)); try std.testing.expect(ptr.isWeakNull(w)); try std.testing.expectEqual(true, value.boole.unpack(ptr.predWeak(w))); try std.testing.expectEqual(true, value.boole.unpack(ptr.predWeakNull(w))); try std.testing.expectEqual(false, value.boole.unpack(ptr.getWeak(w))); } test "fptr" { const ptr = value.ptr; const int1: u50 = 0; const int2: u50 = std.math.maxInt(u50); const f1 = ptr.packForeign(int1); try std.testing.expect(ptr.checkForeign(f1)); try std.testing.expectEqual(int1, ptr.unpackForeign(f1)); const f2 = ptr.packForeign(int2); try std.testing.expect(ptr.checkForeign(f2)); try std.testing.expectEqual(int2, ptr.unpackForeign(f2)); } test "rune" { const r = value.rune.pack("test"); try std.testing.expect(value.rune.check(r)); const s = value.rune.unpack(r); try std.testing.expectEqualStrings("test", s.slice()); } const SstrImpl = struct { SstrPack, SstrUnpack }; const SstrPack = *const fn ([]const u8) Value; const SstrUnpack = *const fn (Value) ShortString; test "sstr" { const impls = [_]SstrImpl{ .{ value.sstr.pack, value.sstr.unpack }, // .{ value.sstr.pack1, value.sstr.unpack1 }, // .{ value.sstr.pack2, value.sstr.unpack2 }, // .{ value.sstr.pack3, value.sstr.unpack3 }, // .{ value.sstr.pack4, value.sstr.unpack4 }, }; for (impls) |impl| { try testSstr(impl); } if (impls.len > 1) { const iters = switch (@import("builtin").mode) { .Debug, .ReleaseSmall => 10_000_000, .ReleaseSafe => 100_000_000, .ReleaseFast => 1_000_000_000, }; std.debug.print("Benchmarking sstr with {} iters.\n", .{iters}); inline for (impls, 0..) |impl, i| { try benchmarkSstr(impl, i, iters); } } } fn testSstr(impl: SstrImpl) !void { const pack, const unpack = impl; const ss1 = pack("1"); const ss2 = pack("123"); const ss3 = pack("123456"); const s1 = unpack(ss1); const s2 = unpack(ss2); const s3 = unpack(ss3); try std.testing.expect(value.sstr.check(ss1)); try std.testing.expect(value.sstr.check(ss2)); try std.testing.expect(value.sstr.check(ss3)); try std.testing.expectEqualStrings("1", s1.slice()); try std.testing.expectEqualStrings("123", s2.slice()); try std.testing.expectEqualStrings("123456", s3.slice()); } fn benchmarkSstr(impl: SstrImpl, id: usize, iters: usize) !void { const pack, const unpack = impl; var timer = try std.time.Timer.start(); var ns: f64 = undefined; var secs: f64 = undefined; var ss1: Value = undefined; var ss2: Value = undefined; var ss3: Value = undefined; for (0..iters) |_i| { _ = _i; ss1 = pack("1"); ss2 = pack("123"); ss3 = pack("123456"); } ns = @floatFromInt(timer.lap()); secs = ns / 1_000_000_000; std.debug.print("pack{}: {d:.3}s\t", .{ id, secs }); for (0..iters) |_i| { _ = _i; std.mem.doNotOptimizeAway(unpack(ss1)); std.mem.doNotOptimizeAway(unpack(ss2)); std.mem.doNotOptimizeAway(unpack(ss3)); } ns = @floatFromInt(timer.lap()); secs = ns / 1_000_000_000; std.debug.print("unpack{}: {d:.3}s\n", .{ id, secs }); } test "char" { const c1 = value.char.pack('\x00'); try std.testing.expect(value.char.check(c1)); try std.testing.expectEqual('\x00', value.char.unpack(c1)); const c2 = value.char.pack('😀'); try std.testing.expect(value.char.check(c2)); try std.testing.expectEqual('😀', value.char.unpack(c2)); } test "misc" { const f = value.boole.pack(false); try std.testing.expect(value.boole.check(f)); try std.testing.expectEqual(false, value.boole.unpack(f)); try std.testing.expect(value.boole.unpack(value.boole.pred(f))); const t = value.boole.pack(true); try std.testing.expect(value.boole.check(t)); try std.testing.expectEqual(true, value.boole.unpack(t)); try std.testing.expect(value.boole.unpack(value.boole.pred(t))); const nil = value.nil.get(); try std.testing.expect(value.nil.check(nil)); try std.testing.expect(value.boole.unpack(value.nil.pred(nil))); const eof = value.eof.get(); try std.testing.expect(value.eof.check(eof)); try std.testing.expect(value.boole.unpack(value.eof.pred(eof))); } test "pair" { const v1 = value.fixnum.pack(1); const v2 = value.fixnum.pack(2); const v3 = value.fixnum.pack(3); const v4 = value.fixnum.pack(4); const p = value.pair.cons(v1, v2); try std.testing.expect(value.pair.check(p)); try std.testing.expect(value.boole.unpack(value.pair.pred(p))); const car = value.pair.car(p); const cdr = value.pair.cdr(p); try std.testing.expectEqual(1, value.fixnum.unpack(car)); try std.testing.expectEqual(2, value.fixnum.unpack(cdr)); value.pair.setcar(p, v3); value.pair.setcdr(p, v4); const car2 = value.pair.car(p); const cdr2 = value.pair.cdr(p); try std.testing.expectEqual(3, value.fixnum.unpack(car2)); try std.testing.expectEqual(4, value.fixnum.unpack(cdr2)); } test "istr" { const istr = value.istr; const fx = value.fixnum; const s1 = "foo bar baz"; const v1 = istr.intern(s1, false); const v1_len: usize = @intCast(fx.unpack(istr.len(v1))); try std.testing.expectEqualStrings(s1, istr.getHeader(v1).bytes()); try std.testing.expectEqual(s1.len, v1_len); const file = try std.fs.cwd().openFile("test-data/string.txt", .{}); defer file.close(); var s2_buf: [4096]u8 = undefined; const s2_len = try file.readAll(&s2_buf); var s2: []u8 = s2_buf[0..s2_len]; const v2 = istr.intern(s2, false); const v2_len: usize = @intCast(fx.unpack(istr.len(v2))); var s2_orig_buf: [4096]u8 = undefined; @memcpy(&s2_orig_buf, &s2_buf); const s2_orig = s2_orig_buf[0..s2_len]; s2[0] = s2[0] +% 1; try std.testing.expectEqualStrings(s2_orig, istr.getHeader(v2).bytes()); try std.testing.expectEqual(s2_len, v2_len); } fn parseString(str: []const u8) Value { var fbs = std.io.fixedBufferStream(str); return io.parser.parse(fbs.reader().any()); } test "parse" { const val = parseString("\"foo\""); try std.testing.expect(value.sstr.check(val)); const s = value.sstr.unpack(val); try std.testing.expectEqualStrings("foo", s.slice()); } test "parse2" { const val = parseString( \\ ;; Testing some crazy datum comments \\ ;~"bar"([x #"y"]{##`,'z}) #"foo" \\ ;; end ); const r = value.rune.unpack(value.pair.car(val)); try std.testing.expectEqualStrings("HASH", r.slice()); const s = value.pair.cdr(val); try std.testing.expect(value.sstr.check(s)); const f = value.sstr.unpack(s); try std.testing.expectEqualStrings("foo", f.slice()); } test "parse3" { const val = parseString( \\(foo ;~x ;~(x y) ;~x #bar [#x #"baz"] 'bat) ); const car = value.pair.car; const cdr = value.pair.cdr; const e1 = car(val); const e2 = car(cdr(val)); const e3 = car(cdr(cdr(val))); const e4 = car(cdr(cdr(cdr(val)))); try std.testing.expect(value.sstr.check(e1)); try std.testing.expect(value.rune.check(e2)); try std.testing.expect(value.pair.check(e3)); try std.testing.expect(value.pair.check(e4)); } test "parse4" { const val = parseString("(foo . ;~x bar ;~y)"); const s = value.sstr.unpack(value.pair.car(val)); try std.testing.expectEqualStrings("foo", s.slice()); const f = value.sstr.unpack(value.pair.cdr(val)); try std.testing.expectEqualStrings("bar", f.slice()); } test "unparse" { var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; var out: std.ArrayList(u8) = .init(gpa.allocator()); const w = out.writer(); const v = parseString("#foo"); try io.unparser.unparse(w, v); try std.testing.expectEqualStrings("#foo", try out.toOwnedSlice()); } test "unparse2" { var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; var out: std.ArrayList(u8) = .init(gpa.allocator()); const w = out.writer(); const v = parseString("#{foo bar['x]}"); try io.unparser.unparse(w, v); try std.testing.expectEqualStrings( "(#HASH #BRACE foo (#JOIN bar #SQUARE (#QUOTE . x)))", try out.toOwnedSlice(), ); } fn writeParseResult(str: []const u8) !void { const w = std.io.getStdErr().writer(); const v = parseString(str); try io.unparser.unparse(w, v); try w.writeByte('\n'); } test "unparse3" { try writeParseResult("#{foo bar['x](y)(z)}"); } test "unparse4" { try writeParseResult("(foo ;~bar)"); } test "unparse5" { try writeParseResult("(;~foo foo ;~bar . ;~bar bar ;~bar)"); } test "unparse6" { try writeParseResult("(foo bar ... baz bat.(qux))"); } test "unparse7" { try writeParseResult("#`(#,(->keyword (syntax->datum #'sym)) . in)"); } fn parseBench(path: []const u8, iters: usize) !void { const file = try std.fs.cwd().openFile(path, .{}); defer file.close(); var fb_alloc: std.mem.Allocator = undefined; var stack_sfa: io.parser.DefaultStackSfa = undefined; var chars_sfa: io.parser.DefaultCharsSfa = undefined; var parser = try io.parser.default(&fb_alloc, &stack_sfa, &chars_sfa); defer parser.deinit(); defer io.parser.deinit(&fb_alloc); var timer = try std.time.Timer.start(); for (0..iters) |i| { _ = i; var br = std.io.bufferedReader(file.reader()); const reader = br.reader().any(); // const reader = file.reader().any(); var v: Value = undefined; while (true) { // std.debug.print("hihi {s}\n", .{path}); v = parser.run(reader) catch |e| { std.debug.print("\nfile pos: {}\n", .{ try file.getPos(), }); return e; }; // try io.unparser.unparse(std.io.getStdOut().writer().any(), v); if (value.eof.check(v)) { break; } } try file.seekTo(0); } const ns: f64 = @floatFromInt(timer.lap()); const secs = ns / 1_000_000_000; std.debug.print( "parse {s} x {}: {d:.3}s\n", .{ path, iters, secs }, ); } test "parse bench" { try parseBench("test-data/parser-test-1.scm", 200); try parseBench("test-data/parser-test-2.scm", 800); try parseBench("test-data/parser-torture.scm", 1); }