summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTaylan Kammer <taylan.kammer@gmail.com>2025-02-18 22:48:57 +0100
committerTaylan Kammer <taylan.kammer@gmail.com>2025-02-18 22:48:57 +0100
commit4d0db1a1065f18d879b3ff90da6ecb14e9e1ae31 (patch)
tree7c5c275e7f3dae7bf96377560269b5a1bfa1fb99
parent2384a31c42f480c961785bcf0520bb0688b8e028 (diff)
update
-rw-r--r--html/notes/symbols.md49
-rw-r--r--src/libzisp.zig101
-rw-r--r--src/libzisp/gc.zig15
-rw-r--r--src/libzisp/read.zig105
-rw-r--r--src/libzisp/value.zig161
-rw-r--r--src/libzisp/value/boole.zig2
-rw-r--r--src/libzisp/value/char.zig21
-rw-r--r--src/libzisp/value/double.zig7
-rw-r--r--src/libzisp/value/eof.zig9
-rw-r--r--src/libzisp/value/fixnum.zig7
-rw-r--r--src/libzisp/value/misc.zig2
-rw-r--r--src/libzisp/value/nil.zig9
-rw-r--r--src/libzisp/value/pair.zig57
-rw-r--r--src/libzisp/value/ptr.zig156
-rw-r--r--src/libzisp/value/sstr.zig24
15 files changed, 554 insertions, 171 deletions
diff --git a/html/notes/symbols.md b/html/notes/symbols.md
index 8aa666f..aa3c448 100644
--- a/html/notes/symbols.md
+++ b/html/notes/symbols.md
@@ -17,3 +17,52 @@ and bare symbols will just be reader syntax for a string constant.
Instead of `string->symbol` we will have `string-intern` which
basically does the same thing. Dynamically generated strings that
aren't passed to this function will be uninterned.
+
+## But but but
+
+(Late addition because I didn't even notice this problem at first.
+How embarrassing!)
+
+But if symbols and strings are the same thing at the reader level,
+then how on earth would you have a string literal in source code,
+without it being evaluated as a variable?
+
+ (display "bar") ;should this look up the variable 'bar'?!
+ (display bar) ;should this display the string 'bar'?!
+
+There's actually a simple solution.
+
+The syntax `"string"`, with double-quotes and nothing else, becomes
+reader syntax akin to the apostrophe, and expands to:
+
+ (quote #"string")
+
+And `#"string"` is the real syntax for string literals, which are
+always treated as identifiers by the evaluator.
+
+Bare identifiers like `foo` instead directly become `#"foo"`, without
+the wrapping `(quote ...)`, and are thus evaluated.
+
+This also means that manually writing `#"string"` in your source code
+allows that to be used as an identifier regardless of whether it has
+illegal characters in it, essentially doing what `|string|` does in
+R7RS-small.
+
+Let's sum it up; here's the reader transformations:
+
+ foo -> #"foo"
+ "foo" -> (quote #"foo")
+ "foo bar" -> (quote #"foo bar")
+ #"foo bar" -> #"foo bar"
+
+Some pseudo-code based on Scheme:
+
+ (let ((#"all your" "base ")
+ (#"are belong" "to us"))
+ (display
+ (string-append #"all your" #"are belong")))
+
+That prints: "base to us"
+
+I'm not married to the syntax `#"string"` and may end up using the
+simpler `|foo|` in the end. It doesn't really matter.
diff --git a/src/libzisp.zig b/src/libzisp.zig
index c7ead3b..f5ad6af 100644
--- a/src/libzisp.zig
+++ b/src/libzisp.zig
@@ -6,6 +6,11 @@ const builtin = @import("builtin");
const testing = std.testing;
pub const value = @import("libzisp/value.zig");
+pub const read = @import("libzisp/read.zig");
+pub const gc = @import("libzisp/gc.zig");
+
+pub const Value = value.Value;
+pub const Bucket = gc.Bucket;
test "double" {
const d1: f64 = 0.123456789;
@@ -18,6 +23,7 @@ test "double" {
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);
}
@@ -32,27 +38,54 @@ test "fixnum" {
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 p: *anyopaque = @ptrFromInt(256);
-
- const p1 = value.ptr.pack(p, value.ptr.Tag.string);
- try std.testing.expect(value.ptr.check(p1));
- try std.testing.expect(value.ptr.checkZisp(p1));
- try std.testing.expect(value.ptr.checkNormal(p1));
- const p1v, const p1t = value.ptr.unpack(p1);
- try std.testing.expectEqual(p, p1v);
- try std.testing.expectEqual(value.ptr.Tag.string, p1t);
-
- const p2 = value.ptr.makeWeak(p1);
- try std.testing.expect(value.ptr.check(p2));
- try std.testing.expect(value.ptr.checkZisp(p2));
- try std.testing.expect(value.ptr.checkWeak(p2));
- const p2v, const p2t = value.ptr.unpack(p1);
- try std.testing.expectEqual(p, p2v);
- try std.testing.expectEqual(value.ptr.Tag.string, p2t);
+ const ptr = value.ptr;
+
+ const val: [*]Bucket = @ptrFromInt(256);
+ const tag = ptr.Tag.string;
+
+ const f = ptr.packForeign(val);
+ try std.testing.expect(ptr.checkForeign(f));
+ try std.testing.expectEqual(
+ @intFromPtr(val),
+ @intFromPtr(ptr.unpackForeign(f)),
+ );
+
+ const p = ptr.pack(val, tag);
+ try std.testing.expect(ptr.check(p));
+ try std.testing.expect(ptr.checkZisp(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.checkZisp(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 "sstr" {
@@ -93,6 +126,8 @@ test "sstr" {
var secs: f64 = undefined;
const iters = 1;
+ // const iters = 10_000_000; // standard
+ // const iters = 1_000_000_000; // ReleaseFast
if (iters > 1) {
for (0..iters) |_i| {
_ = _i;
@@ -150,3 +185,35 @@ test "misc" {
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 "read" {
+ const val = read.read("\"foo\"");
+ const s, const l = value.sstr.unpack(value.pair.car(value.pair.cdr(val)));
+ try std.testing.expectEqualStrings("foo", s[0..l]);
+ try std.testing.expectEqual(3, l);
+}
diff --git a/src/libzisp/gc.zig b/src/libzisp/gc.zig
new file mode 100644
index 0000000..819fa0b
--- /dev/null
+++ b/src/libzisp/gc.zig
@@ -0,0 +1,15 @@
+const std = @import("std");
+
+const Value = @import("value.zig").Value;
+
+var _gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
+const gpa = _gpa.allocator();
+
+pub const Bucket = packed union {
+ bits: u64,
+ value: Value,
+};
+
+pub fn alloc(count: usize) []Bucket {
+ return gpa.alloc(Bucket, count) catch @panic("OOM");
+}
diff --git a/src/libzisp/read.zig b/src/libzisp/read.zig
new file mode 100644
index 0000000..9ef9891
--- /dev/null
+++ b/src/libzisp/read.zig
@@ -0,0 +1,105 @@
+const std = @import("std");
+
+const gc = @import("gc.zig");
+const value = @import("value.zig");
+
+const Value = value.Value;
+
+const State = struct {
+ alloc: std.mem.Allocator,
+ input: []const u8,
+ pos: usize = 0,
+
+ next: enum {
+ start,
+
+ list,
+ list_end,
+
+ err,
+
+ done,
+ } = .start,
+
+ retval: Value = value.eof.eof,
+
+ parent: ?*State = null,
+};
+
+pub fn read(input: []const u8) Value {
+ var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
+ var top = State{ .alloc = gpa.allocator(), .input = input };
+ var s = &top;
+ while (s.pos <= s.input.len) : (s.pos += 1) {
+ s = switch (s.next) {
+ .start => start(s),
+
+ .list => list(s),
+ .list_end => list(s),
+
+ .err => err(s),
+
+ .done => ret: {
+ if (s.parent) |parent| {
+ s.alloc.destroy(s);
+ break :ret parent;
+ } else {
+ return s.retval;
+ }
+ },
+ };
+ }
+ unreachable;
+}
+
+fn start(s: *State) *State {
+ switch (s.input[s.pos]) {
+ 0...8 => s.next = .err,
+
+ '\t', '\n' => {},
+
+ 11...31 => s.next = .err,
+
+ ' ' => {},
+
+ '!' => s.next = .err,
+
+ '"' => quotedString(s),
+
+ else => s.next = .err,
+ }
+ return s;
+}
+
+fn quotedString(s: *State) void {
+ var buf: [6]u8 = .{0} ** 6;
+ const len = readString(&buf, s);
+ s.retval = value.pair.cons(
+ value.sstr.pack("quote"),
+ value.pair.cons(
+ value.sstr.pack(buf[0..len]),
+ value.nil.nil,
+ ),
+ );
+ s.next = .done;
+}
+
+fn readString(buf: []u8, s: *State) usize {
+ s.pos += 1; // skip opening quote
+ for (s.input[s.pos..], 0..) |c, i| {
+ if (c == '"') {
+ s.pos += i;
+ return i;
+ }
+ buf[i] = c;
+ }
+ unreachable;
+}
+
+fn list(s: *State) *State {
+ return s;
+}
+
+fn err(s: *State) *State {
+ return s;
+}
diff --git a/src/libzisp/value.zig b/src/libzisp/value.zig
index da67af7..dd9df3c 100644
--- a/src/libzisp/value.zig
+++ b/src/libzisp/value.zig
@@ -58,28 +58,26 @@
//
// === Pointers ===
//
-// Pointers are further subdivided as follows based on the remaining 51 bits:
+// Pointers are further subdivided as follows based on the remaining 51 bits,
+// with the first three bits used as a sort of tag:
//
-// MSb = 1 :: Foreign Pointer (or a "special 50-bit fixnum")
+// 000 :: Pointer to Zisp heap object (string, vector, etc.)
//
-// MSb = 0, SSb = 0 :: Pointer to heap object (string, vector, etc.)
+// 001 :: Weak pointer to Zisp heap object
//
-// MSb = 0, SSb = 1 :: Weak pointer to heap object
+// 01. :: Undefined
//
-// (SSb = Second-most significant bit)
-//
-// This means regular pointers to the Zisp heap are 49 bits. Of these, we only
-// really need 45, since 64-bit platforms are in practice limited to 48-bit
-// addresses, and allocations happen at 8-byte boundaries, meaning the least
-// significant 3 bit are always 0. Thus, we are able to store 4-bit tags in
-// those 49-bit pointers alongside the actual, multiple-of-8, 48-bit address.
+// 1.. :: Undefined
//
-// Note that foreign pointers avoid stepping on any forbidden value, thanks to
-// bit 51 being set.
+// This means pointers to the Zisp heap are 48 bits. Of those, we only really
+// need 45, since 64-bit platforms are in practice limited to 48-bit addresses,
+// and allocations happen at 8-byte boundaries, meaning the least significant 3
+// bit are always 0. Thus, we are able to store yet another 3-bit tag in those
+// 48-bit pointers alongside the actual, multiple-of-8, 48-bit address.
//
// The forbidden value 3, Positive cqNaN, is avoided thanks to the fact that a
// regular Zisp heap pointer can never be null. Weak pointers, which can be
-// null, avoid stepping on that forbidden value thanks to bit 50 being set.
+// null, avoid stepping on that forbidden value thanks to bit 49 being set.
//
//
// === Other values ===
@@ -88,7 +86,7 @@
//
// 000 :: Undefined
//
-// 001 :: Small string
+// 001 :: Short string
//
// 010 :: Unicode code point
//
@@ -106,14 +104,16 @@
//
// There may also be uninterned strings on the heap that are also as short but
// ended up on the heap due to being uninterned. Calling intern on them will
-// return the equivalent small string.
+// return the equivalent short string.
//
// Unicode code points need a maximum of 21 bits, yet we have 48 available.
// This may be exploited for a future extension.
//
// Similarly, it's extremely unlikely that we will ever need more than a few
// dozen singleton values (false, true, null, and so on). As such, this range
-// of bit patterns may be subdivided further in the future.
+// of bit patterns may be subdivided in the future. Right now, only the lowest
+// 8 bits are allowed to be set, with the other 40 being reserved, so there's a
+// limit of 256 singleton values that can be defined.
//
// And on top of all that we still have a 48-bit and a 50-bit range left!
//
@@ -142,72 +142,109 @@ pub const boole = @import("value/boole.zig");
pub const nil = @import("value/nil.zig");
pub const eof = @import("value/eof.zig");
+pub const pair = @import("value/pair.zig");
+
/// To fill up the u11 exponent part of a NaN.
const FILL = 0x7ff;
/// Represents a Zisp value/object.
pub const Value = packed union {
+ /// To get the value as a regular double.
double: f64,
+
+ /// To get an agnostic value for direct comparison with == i.e. eq?.
bits: u64,
- nan: packed struct {
+ // Some of the structs below are just for inspection, whereas others are to
+ // initialize a new value of that category as well as read it that way.
+
+ /// Inspection through the lens of the general IEEE 754 double layout.
+ ieee: packed struct {
rest: u51,
- quiet: u1,
- exp: u11 = FILL,
- sign: u1,
+ quiet: bool,
+ exp: u11,
+ sign: bool,
},
+ /// For initializing and reading fixnums.
fixnum: packed struct {
code: u51,
negative: bool,
_: u11 = FILL,
- is_fixnum: bool = true,
+ _is_fixnum: bool = true,
},
+ /// Inspection through the lens of the ptr category.
ptr: packed struct {
- // if foreign, we don't actually use value and is_weak
- value: u49,
- weak: bool = false,
- foreign: bool = false,
- is_ptr: bool = true,
- _: u11 = FILL,
- _fixnum: bool = false,
+ _value: u48,
+ is_weak: bool,
+ _unused: bool,
+ is_foreign: bool,
+ _is_ptr: bool,
+ _: u11,
+ _is_fixnum: bool,
},
+ /// For initializing and reading foreign pointers.
fptr: packed struct {
value: u50,
- _foreign: bool = true,
- _ptr: bool = true,
+ _is_foreign: bool = true,
+ _is_ptr: bool = true,
_: u11 = FILL,
- _fixnum: bool = false,
+ _is_fixnum: bool = false,
+ },
+
+ /// For initializing and reading Zisp heap pointers.
+ zptr: packed struct {
+ tagged_value: u48,
+ is_weak: bool = false,
+ _unused: bool = false,
+ _is_foreign: bool = false,
+ _is_ptr: bool = true,
+ _: u11 = FILL,
+ _is_fixnum: bool = false,
+ },
+
+ /// Inspection as an other (non-fixnum, non-pointer) packed value.
+ other: packed struct {
+ _value: u48,
+ tag: OtherTag,
+ _is_ptr: bool,
+ _: u11,
+ _is_ifxnum: bool,
},
+ /// For initializing and reading short strings.
sstr: packed struct {
- // packed struct cannot contain array
- value: u48,
- tag: Tag = .sstr,
- ptr: bool = false,
+ // actually [6]u8 but packed struct cannot contain arrays
+ string: u48,
+ _tag: OtherTag = .sstr,
+ _is_ptr: bool = false,
_: u11 = FILL,
- fixnum: bool = false,
+ _is_fixnum: bool = false,
},
+ /// For initializing and reading characters.
char: packed struct {
- value: u48,
- tag: Tag = .char,
- ptr: bool = false,
+ char: u21,
+ _reserved: u27 = 0,
+ _tag: OtherTag = .char,
+ _is_ptr: bool = false,
_: u11 = FILL,
- fixnum: bool = false,
+ _is_fixnum: bool = false,
},
+ /// For initializing and reading misc values aka singletons.
misc: packed struct {
- value: u48,
- tag: Tag = .misc,
- ptr: bool = false,
+ value: u8,
+ _reserved: u40 = 0,
+ _tag: OtherTag = .misc,
+ _is_ptr: bool = false,
_: u11 = FILL,
- fixnum: bool = false,
+ _is_fixnum: bool = false,
},
- const Tag = enum(u3) { sstr = 1, char = 2, misc = 3 };
+ const OtherTag = enum(u3) { sstr = 1, char = 2, misc = 3 };
const Self = @This();
@@ -216,8 +253,36 @@ pub const Value = packed union {
std.debug.dumpHex(std.mem.asBytes(&self));
}
- /// Checks for a Zisp value (non-double) packed into a NaN.
+ // The following aren't type predicates per se, but rather determine which
+ // general category the value is in. The exceptions are fixnum and double,
+ // since those aren't sub-categorized into further types.
+
+ /// Checks for a Zisp double, including: +nan.0, -nan.0, +inf.0, -inf.0
+ pub fn isDouble(self: Self) bool {
+ return self.ieee.exp != FILL or self.ieee.rest == 0;
+ }
+
+ /// Checks for a non-double Zisp value packed into a NaN.
pub fn isPacked(self: Self) bool {
- return self.nan.exp == FILL and self.nan.rest != 0;
+ return !self.isDouble();
+ }
+
+ /// Checks for a fixnum.
+ pub fn isFixnum(self: Self) bool {
+ return self.isPacked() and self.ieee.sign;
+ }
+
+ /// Checks for any kind of pointer.
+ pub fn isPtr(self: Self) bool {
+ return self.isPacked() and !self.ieee.sign and self.ieee.quiet;
+ }
+
+ /// Checks for a non-double, non-fixnum, non-pointer Zisp value.
+ fn _isOther(self: Self) bool {
+ return self.isPacked() and !self.ieee.sign and !self.ieee.quiet;
+ }
+
+ pub fn isOther(self: Self, tag: OtherTag) bool {
+ return self._isOther() and self.other.tag == tag;
}
};
diff --git a/src/libzisp/value/boole.zig b/src/libzisp/value/boole.zig
index 0af7e22..623dbc2 100644
--- a/src/libzisp/value/boole.zig
+++ b/src/libzisp/value/boole.zig
@@ -30,5 +30,5 @@ pub fn unpack(v: Value) bool {
// Zisp API
pub fn pred(v: Value) Value {
- return if (check(v)) t else f;
+ return pack(check(v));
}
diff --git a/src/libzisp/value/char.zig b/src/libzisp/value/char.zig
index 6a38f0d..98bb26f 100644
--- a/src/libzisp/value/char.zig
+++ b/src/libzisp/value/char.zig
@@ -1,10 +1,11 @@
-const Value = @import("../value.zig").Value;
+const value = @import("../value.zig");
+
+const Value = value.Value;
+
+// Zig API
pub fn check(v: Value) bool {
- return v.isPacked() and
- !v.char.fixnum and
- !v.char.ptr and
- v.char.tag == .char;
+ return v.isOther(.char);
}
pub fn assert(v: Value) void {
@@ -15,10 +16,16 @@ pub fn assert(v: Value) void {
}
pub fn pack(c: u21) Value {
- return .{ .char = .{ .value = c } };
+ return .{ .char = .{ .char = c } };
}
pub fn unpack(v: Value) u21 {
assert(v);
- return @truncate(v.char.value);
+ return @truncate(v.char.char);
+}
+
+// Zisp API
+
+pub fn pred(v: Value) Value {
+ return value.boole.pack(check(v));
}
diff --git a/src/libzisp/value/double.zig b/src/libzisp/value/double.zig
index 5c98324..5cfe6ee 100644
--- a/src/libzisp/value/double.zig
+++ b/src/libzisp/value/double.zig
@@ -1,10 +1,11 @@
-const Value = @import("../value.zig").Value;
+const value = @import("../value.zig");
+const Value = value.Value;
// Zig API
/// Checks for a Zisp double (double, +inf, -inf, or canonical NaN).
pub fn check(v: Value) bool {
- return !v.isPacked();
+ return v.isDouble();
}
/// Asserts check().
@@ -27,7 +28,7 @@ pub fn unpack(v: Value) f64 {
// Zisp API
pub fn pred(v: Value) Value {
- return Value.boole.pack(check(v));
+ return value.boole.pack(check(v));
}
pub fn add(v1: Value, v2: Value) Value {
diff --git a/src/libzisp/value/eof.zig b/src/libzisp/value/eof.zig
index 34ab35d..367a86c 100644
--- a/src/libzisp/value/eof.zig
+++ b/src/libzisp/value/eof.zig
@@ -1,7 +1,8 @@
-const Value = @import("../value.zig").Value;
-const misc = @import("misc.zig");
+const value = @import("../value.zig");
-pub const eof = misc.eof;
+const Value = value.Value;
+
+pub const eof = @import("misc.zig").eof;
// Zig API
@@ -23,5 +24,5 @@ pub fn get() Value {
}
pub fn pred(v: Value) Value {
- return if (check(v)) misc.t else misc.f;
+ return value.boole.pack(check(v));
}
diff --git a/src/libzisp/value/fixnum.zig b/src/libzisp/value/fixnum.zig
index 6d26a9c..888dd3a 100644
--- a/src/libzisp/value/fixnum.zig
+++ b/src/libzisp/value/fixnum.zig
@@ -1,12 +1,13 @@
const std = @import("std");
+const value = @import("../value.zig");
-const Value = @import("../value.zig").Value;
+const Value = value.Value;
// Zig API
/// Checks for a Zisp fixnum.
pub fn check(v: Value) bool {
- return v.isPacked() and v.fixnum.is_fixnum;
+ return v.isFixnum();
}
/// Asserts check().
@@ -77,7 +78,7 @@ pub fn unpack(v: Value) i64 {
// Zisp API
pub fn pred(v: Value) Value {
- return Value.boole.pack(check(v));
+ return value.boole.pack(check(v));
}
pub fn add(v1: Value, v2: Value) Value {
diff --git a/src/libzisp/value/misc.zig b/src/libzisp/value/misc.zig
index 793c60e..30cbf84 100644
--- a/src/libzisp/value/misc.zig
+++ b/src/libzisp/value/misc.zig
@@ -4,3 +4,5 @@ pub const f = Value{ .misc = .{ .value = 0 } };
pub const t = Value{ .misc = .{ .value = 1 } };
pub const nil = Value{ .misc = .{ .value = 2 } };
pub const eof = Value{ .misc = .{ .value = 3 } };
+
+pub const undef = Value{ .misc = .{ .value = 255 } };
diff --git a/src/libzisp/value/nil.zig b/src/libzisp/value/nil.zig
index 1b1a51e..14bd800 100644
--- a/src/libzisp/value/nil.zig
+++ b/src/libzisp/value/nil.zig
@@ -1,7 +1,8 @@
-const Value = @import("../value.zig").Value;
-const misc = @import("misc.zig");
+const value = @import("../value.zig");
-pub const nil = misc.nil;
+const Value = value.Value;
+
+pub const nil = @import("misc.zig").nil;
// Zig API
@@ -23,5 +24,5 @@ pub fn get() Value {
}
pub fn pred(v: Value) Value {
- return if (check(v)) misc.t else misc.f;
+ return value.boole.pack(check(v));
}
diff --git a/src/libzisp/value/pair.zig b/src/libzisp/value/pair.zig
new file mode 100644
index 0000000..541a5f5
--- /dev/null
+++ b/src/libzisp/value/pair.zig
@@ -0,0 +1,57 @@
+const std = @import("std");
+const value = @import("../value.zig");
+const gc = @import("../gc.zig");
+
+const ptr = @import("ptr.zig");
+
+const Value = value.Value;
+
+// Zig API
+
+pub fn check(v: Value) bool {
+ return ptr.checkZisp(v, .pair);
+}
+
+pub fn assert(v: Value) void {
+ if (!check(v)) {
+ v.dump();
+ @panic("not pair");
+ }
+}
+
+// Zisp API
+
+pub fn pred(v: Value) Value {
+ return value.boole.pack(check(v));
+}
+
+pub fn cons(v1: Value, v2: Value) Value {
+ const mem = gc.alloc(2);
+ mem[0] = .{ .value = v1 };
+ mem[1] = .{ .value = v2 };
+ return ptr.pack(mem.ptr, .pair);
+}
+
+fn getMem(v: Value) *[2]Value {
+ return @ptrCast(ptr.unpack(v).@"0");
+}
+
+pub fn car(v: Value) Value {
+ assert(v);
+ return getMem(v)[0];
+}
+
+pub fn cdr(v: Value) Value {
+ assert(v);
+ return getMem(v)[1];
+}
+
+pub fn setcar(v: Value, new: Value) void {
+ assert(v);
+ getMem(v)[0] = new;
+}
+
+pub fn setcdr(v: Value, new: Value) void {
+ assert(v);
+ getMem(v)[1] = new;
+}
diff --git a/src/libzisp/value/ptr.zig b/src/libzisp/value/ptr.zig
index 6a3a6c4..fe13af5 100644
--- a/src/libzisp/value/ptr.zig
+++ b/src/libzisp/value/ptr.zig
@@ -1,11 +1,14 @@
const std = @import("std");
+const value = @import("../value.zig");
+const gc = @import("../gc.zig");
-const Value = @import("../value.zig").Value;
+const Bucket = gc.Bucket;
+const Value = value.Value;
// Zig API
pub fn check(v: Value) bool {
- return v.isPacked() and v.ptr.is_ptr;
+ return v.isPtr();
}
pub fn assert(v: Value) void {
@@ -18,7 +21,7 @@ pub fn assert(v: Value) void {
// Foreign Pointers
pub fn checkForeign(v: Value) bool {
- return check(v) and v.ptr.foreign;
+ return check(v) and v.ptr.is_foreign;
}
pub fn assertForeign(v: Value) void {
@@ -28,151 +31,166 @@ pub fn assertForeign(v: Value) void {
}
}
-pub fn packForeign(int: u50) Value {
- return .{ .fptr = .{int} };
+pub fn checkForeignRange(ptr: *anyopaque) bool {
+ const int = @intFromPtr(ptr);
+ return int <= std.math.maxInt(u50);
}
-pub fn unpackForeign(v: Value) u64 {
+fn assertForeignRange(ptr: *anyopaque) void {
+ if (!checkForeignRange(ptr)) {
+ std.debug.print("foreign pointer out of range: {}\n", .{ptr});
+ @panic("foreign pointer out of range");
+ }
+}
+
+pub fn packForeign(ptr: *anyopaque) Value {
+ assertForeignRange(ptr);
+ const int: u50 = @intCast(@intFromPtr(ptr));
+ return .{ .fptr = .{ .value = int } };
+}
+
+pub fn unpackForeign(v: Value) *anyopaque {
assertForeign(v);
- return v.ptr.value.foreign;
+ return @ptrFromInt(v.fptr.value);
}
// Zisp Pointers
-pub fn checkZisp(v: Value) bool {
- return check(v) and !v.ptr.foreign;
+fn _checkZisp(v: Value) bool {
+ return check(v) and !v.ptr.is_foreign;
}
-pub fn assertZisp(v: Value) void {
- if (!checkZisp(v)) {
+fn _assertZisp(v: Value) void {
+ if (!_checkZisp(v)) {
v.dump();
@panic("not zisp pointer");
}
}
pub fn checkWeak(v: Value) bool {
- return checkZisp(v) and v.ptr.weak;
+ return _checkZisp(v) and v.zptr.is_weak;
}
pub fn assertWeak(v: Value) void {
if (!checkWeak(v)) {
v.dump();
- @panic("not weak zisp pointer");
+ @panic("not zisp weak pointer");
+ }
+}
+
+pub fn checkZisp(v: Value, tag: Tag) bool {
+ return _checkZisp(v) and unpack(v).@"1" == tag;
+}
+
+pub fn assertZisp(v: Value, tag: Tag) void {
+ if (!checkZisp(v, tag)) {
+ v.dump();
+ @panic("not zisp pointer or wrong tag");
}
}
-pub fn checkNormal(v: Value) bool {
- return checkZisp(v) and !v.ptr.weak;
+pub fn checkStrong(v: Value) bool {
+ return _checkZisp(v) and !v.zptr.is_weak;
}
-pub fn assertNormal(v: Value) void {
- if (!checkNormal(v)) {
+pub fn assertStrong(v: Value) void {
+ if (!checkStrong(v)) {
v.dump();
- @panic("not normal zisp pointer");
+ @panic("not zisp strong pointer");
}
}
-pub fn packZisp(ptr: *anyopaque, tag: Tag, weak: bool) Value {
- return .{ .ptr = .{
- .value = tagPtr(ptr, tag),
- .weak = weak,
+pub fn packZisp(ptr: [*]Bucket, tag: Tag, is_weak: bool) Value {
+ return .{ .zptr = .{
+ .tagged_value = tagPtr(ptr, tag),
+ .is_weak = is_weak,
} };
}
-pub fn pack(ptr: *anyopaque, tag: Tag) Value {
+pub fn pack(ptr: [*]Bucket, tag: Tag) Value {
return packZisp(ptr, tag, false);
}
-pub fn packWeak(ptr: *anyopaque, tag: Tag) Value {
+pub fn packWeak(ptr: [*]Bucket, tag: Tag) Value {
return packZisp(ptr, tag, true);
}
// Unpacks weak as well; no need for a separate fn.
-pub fn unpack(v: Value) PtrAndTag {
- assertZisp(v);
- return untagPtr(v.ptr.value);
+pub fn unpack(v: Value) struct { [*]Bucket, Tag } {
+ _assertZisp(v);
+ return untagPtr(v.zptr.tagged_value);
}
-// Weak pointers may be null.
-pub fn isNull(v: Value) bool {
- assertWeak(v);
- const ptr, _ = untagPtr(v.ptr.value);
- return @intFromPtr(ptr) == 0;
+pub fn setWeakNull(v: *Value) void {
+ assertWeak(v.*);
+ v.zptr.tagged_value = 0;
}
-pub fn tagPtr(ptr: *anyopaque, tag: Tag) u49 {
- const int: u64 = @intFromPtr(ptr);
- const untagged: u49 = @truncate(int);
- return untagged << 1 | @intFromEnum(tag);
+pub fn isWeakNull(v: Value) bool {
+ assertWeak(v);
+ return v.zptr.tagged_value == 0;
}
-pub const PtrAndTag = struct { *anyopaque, Tag };
+fn tagPtr(ptr: [*]Bucket, tag: Tag) u48 {
+ const int: usize = @intFromPtr(ptr);
+ const untagged: u48 = @intCast(int);
+ return untagged | @intFromEnum(tag);
+}
-pub fn untagPtr(tagged: u49) PtrAndTag {
- const untagged: u49 = tagged >> 1 & 0xfffffffffff0;
- const ptr: *anyopaque = @ptrFromInt(untagged);
- const int: u4 = @truncate(tagged);
+fn untagPtr(tagged: u48) struct { [*]Bucket, Tag } {
+ const untagged: u48 = tagged & 0xfffffffffff8;
+ const ptr: [*]Bucket = @ptrFromInt(untagged);
+ const int: u3 = @truncate(tagged);
const tag: Tag = @enumFromInt(int);
return .{ ptr, tag };
}
-pub const Tag = enum(u4) {
+pub const Tag = enum(u3) {
/// 0. Strings / Symbols
string,
/// 1. Bignums / Ratnums
number,
/// 2. Pairs ([2]Value)
pair,
- /// 3. Vector, bytevector, etc.
- array,
- /// 4. Ordered hash table
- table,
- /// 5. String buffer
+ /// 3. Collections: Vector, table, etc.
+ coll,
+ /// 4. OOP: Classes, instances, etc.
+ oop,
+ /// 5. String buffers
text,
- /// 6. Class, interface, etc.
- role,
- /// 7. Instance, basically
- actor,
- /// 8. I/O Port
- port,
- /// 9. Procedure
+ /// 6. Procedures
proc,
- /// 10. Continuation
- cont,
- /// Other
- other = 15,
+ /// 7. Others
+ other,
};
// Zisp API
pub fn predForeign(v: Value) Value {
- return Value.boole.pack(checkForeign(v));
+ return value.boole.pack(checkForeign(v));
}
pub fn makeWeak(v: Value) Value {
- assertNormal(v);
+ assertStrong(v);
var copy = v;
- copy.ptr.weak = true;
+ copy.zptr.is_weak = true;
return copy;
}
pub fn predWeak(v: Value) Value {
- const isWeak = checkWeak(v);
- return Value.boole.pack(isWeak);
+ return value.boole.pack(checkWeak(v));
}
pub fn predWeakNull(v: Value) Value {
- assertWeak(v);
- return Value.boole.pack(v.ptr.weak);
+ return value.boole.pack(isWeakNull(v));
}
pub fn getWeak(v: Value) Value {
- assertWeak(v);
- if (isNull(v)) {
- return Value.boole.pack(false);
+ if (isWeakNull(v)) {
+ return value.boole.f;
} else {
var copy = v;
- copy.ptr.weak = false;
+ copy.zptr.is_weak = false;
return copy;
}
}
diff --git a/src/libzisp/value/sstr.zig b/src/libzisp/value/sstr.zig
index 55b3f8b..896b8d7 100644
--- a/src/libzisp/value/sstr.zig
+++ b/src/libzisp/value/sstr.zig
@@ -5,10 +5,7 @@ const Value = @import("../value.zig").Value;
// Zig API
pub fn check(v: Value) bool {
- return v.isPacked() and
- !v.sstr.fixnum and
- !v.sstr.ptr and
- v.sstr.tag == .sstr;
+ return v.isOther(.sstr);
}
pub fn assert(v: Value) void {
@@ -41,25 +38,22 @@ fn assertValidSstr(s: []const u8) void {
// Different ways of doing the following have been tested, including manual
// shifting and bit masking, but memcpy always wins easily according to our
-// micro-benchmarks, both under ReleaseSafe and under ReleaseFast.
+// micro-benchmarks, under both ReleaseSafe and ReleaseFast.
pub fn pack(s: []const u8) Value {
assertValidSstr(s);
- var v = Value{ .sstr = .{ .value = 0 } };
- const dest: [*]u8 = @ptrCast(&v.sstr.value);
+ var v = Value{ .sstr = .{ .string = 0 } };
+ const dest: [*]u8 = @ptrCast(&v.sstr.string);
@memcpy(dest, s);
return v;
}
-// It's tempting to inline for here to eliminate the if statement or prevent
-// need of @truncate but all alternatives were a little slower.
-
pub fn unpack(v: Value) struct { [6]u8, u3 } {
- var s: [6]u8 = undefined;
- const src: *const [6]u8 = @ptrCast(&v.sstr.value);
- @memcpy(&s, src);
- for (0..6) |i| {
- if (s[i] == 0) return .{ s, @intCast(i) };
+ const s: [6]u8 = @bitCast(v.sstr.string);
+ inline for (0..6) |i| {
+ if (s[i] == 0) return .{ s, i };
}
return .{ s, 6 };
}
+
+// No Zisp API for sstr specifically, since it's a string. See string.zig.