summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--html/index.md19
-rw-r--r--html/notes/macros.md151
-rw-r--r--html/notes/sr.md368
-rw-r--r--src/libzisp.zig30
-rw-r--r--src/libzisp/gc.zig13
-rw-r--r--src/libzisp/io/parser.zig199
-rw-r--r--src/libzisp/io/unparser.zig101
-rw-r--r--src/libzisp/value.zig43
-rw-r--r--src/libzisp/value/boole.zig5
-rw-r--r--src/libzisp/value/char.zig2
-rw-r--r--src/libzisp/value/eof.zig2
-rw-r--r--src/libzisp/value/istr.zig3
-rw-r--r--src/libzisp/value/misc.zig8
-rw-r--r--src/libzisp/value/nil.zig2
-rw-r--r--src/libzisp/value/pair.zig2
-rw-r--r--src/libzisp/value/ptr.zig54
-rw-r--r--src/libzisp/value/rune.zig13
-rw-r--r--src/libzisp/value/sstr.zig10
18 files changed, 813 insertions, 212 deletions
diff --git a/html/index.md b/html/index.md
index 37565f1..e7c5ff2 100644
--- a/html/index.md
+++ b/html/index.md
@@ -6,9 +6,17 @@ been invented today, and had it been designed with pragmatic use as a
primary concern in its design.
This language doesn't actually exist yet. You are merely reading the
-ramblings of a madman.
+ramblings of a madman. A little bit of code is here already though:
-* [Compilation is execution](notes/compilation.html)
+[Zisp on GitHub](https://github.com/TaylanUB/zisp/)
+
+Some of the following articles are quite insightful. Others are VERY
+rambly; you've been warned.
+
+Some are outdated with regards to the actual implementation of Zisp,
+because writing the code often gives you yet another perspective.
+
+* [Compilation is execution](notes/compile.html)
* [Everything can be serialized](notes/serialize.html)
* [Symbols are strings](notes/symbols.html)
* [Stop the "cons" madness!](notes/cons.html)
@@ -22,7 +30,6 @@ ramblings of a madman.
* [Object-oriented programming](notes/oop.html)
* [Equality and equivalence semantics](notes/equal.html)
* [NaN-packing](notes/nan.html)
-
-Temporary source repo before I set up my own git server:
-
-[Zisp on GitHub](https://github.com/TaylanUB/zisp/)
+* [Reader? Decoder? I barely know 'er!](notes/reader.html)
+* [Does the decoder implement macros?](notes/macros.html)
+* [Better syntax-rules?](notes/sr.html)
diff --git a/html/notes/macros.md b/html/notes/macros.md
new file mode 100644
index 0000000..3169c49
--- /dev/null
+++ b/html/notes/macros.md
@@ -0,0 +1,151 @@
+# Does the decoder implement macros?
+
+I've written about the [parser/decoder dualism](reader.html) in a
+previous article. Long story short, the parser takes care of syntax
+sugar, like turning `#(...)` into `(#HASH ...)`, and the decoder takes
+care of turning that into a vector or whatever.
+
+Now, since the job of the decoder seems superficially quite similar to
+that of a macro expander, I've been agonizing for the past two days or
+so whether it *is* the macro expander.
+
+(Warning: This post is probably going to be very rambly, as I'm trying
+to gather my thoughts by writing it.)
+
+On one hand, sure:
+
+ (define-syntax #HASH
+ (syntax-rules ()
+ (#HASH <element> ...)
+ (vector '<element> ...)))
+
+Or something like that. You know what I mean? I mean, in Scheme you
+can't return a vector from a macro, but in Zisp the idea is that you
+can very well do that if you want, because why not.
+
+It's very much possible that I will eventually realize that this is a
+bad idea in some way, but we'll see. So far I really like the idea of
+a macro just returning objects, like a procedure, rather than having
+to return a syntax object that has a binding to that procedure.
+
+This may be similar to John Shutt's "vau calculus" from his language
+Kernel. Maybe Zisp will even end up being an implementation of the
+vau calculus. But I don't know; I've never fully grokked the vau
+calculus, so if I end up implementing it, it will be by accident.
+
+In any case, I want the user to be able to bind transformers to runes,
+and doing so feels like it's pretty much the same thing as defining a
+macro, so maybe the decoder should also be the macro expander.
+
+But then there's an issue with quoting. Consider the following:
+
+ (define stuff '(foo #(0 1 2)))
+
+In Zisp, this would first of all be parsed into:
+
+ (define stuff (#QUOTE foo (#HASH 0 1 2)))
+
+Now, if #QUOTE didn't decode its operand, we'd end up seeing #HASH in
+the result, never creating the vector we meant to create.
+
+But if #QUOTE calls decode on its operand, and the decoder is also the
+macro expander, whoops:
+
+ (let-syntax ((foo (syntax-rules () ((_ x) (bar x)))))
+ '(foo #(0 1 2)))
+
+ ;; => (bar #(0 1 2))
+
+I mean... MAYBE that should happen, actually?! Probably not, though.
+What Scheme does isn't gospel; Zisp isn't Scheme and it will do some
+things differently, but we *probably* don't want anything inside a
+quoted expression to be macro expanded. Probably.
+
+The thought that I might actually want that to happen sent me down a
+whole rabbit whole, and made me question "runes" altogether. If they
+just make the decoder invoke a predefined macro, well, why not ditch
+runes and have the parser emit macro calls?
+
+So instead of:
+
+ #(x y z) -> (#HASH x y z)
+
+(Which is then "decoded" into a vector...) Why not just:
+
+ #(x y z) -> (VECTOR x y z)
+
+And then `VECTOR` is, I don't know, a macro in the standard library I
+guess. If the decoder is the macro expander, then sure, it will know
+about the standard library; it will have a full-blown environment that
+it uses to macro expand, to look up macro names.
+
+But no, I think this conflates everything too much. Even just on the
+level of comprehensibility of code containing literals, I think it's
+good for there to be something that you just know will turn into an
+object of some type, no matter what; that's what a literal is.
+
+(In Zisp, it's not the reader that immediately turns the literal into
+an object of the correct type, but the decoder still runs before the
+evaluator so it's almost the same.)
+
+Then again, maybe this intuition just comes from having worked with
+Scheme for such a long time, and maybe it's not good. Perhaps it's
+more elegant if everything is a macro. Don't pile feature on top of
+feature, remember?
+
+Booleans, by the way, would just be identifier syntax then. Just
+`true` and `false` without the hash sign. In Zisp, you can't shadow
+identifiers anyway, so now they're like keywords in other languages,
+also a bit like `t` and `nil` in CL and Elisp.
+
+IF we are fine with the quote issue described above, then I *think*
+everything being a macro would be the right thing to do. Although
+I've said the decoder could be used for things other than code, like
+for configuration files containing user-defined data types, you could
+still do that by defining macros and calling the macro expander on the
+config file.
+
+It's just that you would either not be able to have stuff like vectors
+in a quoted list (you'd just get a list like `(VECTOR ...)` in it if
+you tried), or you'd have to be expanding any macros encountered
+within the quoted list. Either both, or neither.
+
+Not getting a choice, you say... That's not very expressive. That
+seems like a limitation in the language. Remember: remove the
+limitations that make additional features seem necessary.
+
+Next thing we will have two variants of quote: One which quotes for
+real, and one that expands macros. Or maybe some mechanism to mark
+macros as being meant to be run inside a quote or not, but then we
+re-invented runes in a different way.
+
+Which brings me back to runes, and how `#QUOTE` could handle them,
+even if the decoder is the macro expander.
+
+Encountering `#QUOTE` could tell the decoder that while decoding the
+operand, it should only honor runes, not macros bound to identifiers.
+
+That would probably be a fine way to solve the quote problem, should
+the decoder also be the macro expander: Macros are bound to runes or
+identifiers, and the rune-bound macros are those that are expanded
+even inside a quote.
+
+I think that would be the same as having completely separate decode
+and macro-expand phases.
+
+(The reason we would want them merged, by the way, is that it would
+presumably prevent duplication of code, since what they do is so
+similar.)
+
+It's possible that I'm agonizing for no reason at all because maybe
+the decoder cannot be the macro expander anyway.
+
+We will see.
+
+For now, I think it's best to proceed by implementing the decoder, and
+once I've come to the macro expander I can see if it makes sense to
+merge the two or not.
+
+But I'll probably keep runes one way or another, since they're a nice
+way of marking things that should be processed "no matter what" such
+that they can function as object literals within code.
diff --git a/html/notes/sr.md b/html/notes/sr.md
new file mode 100644
index 0000000..0fa9e06
--- /dev/null
+++ b/html/notes/sr.md
@@ -0,0 +1,368 @@
+# Better syntax-rules?
+
+Yesterday, someone on IRC asked for help in improving the following
+syntax-rules (s-r) macro:
+
+```scheme
+
+(define-syntax alist-let*
+ (syntax-rules ()
+
+ ;; uses subpattern to avoid fender
+ ;; alist-expr is evaluated only once
+ ((_ alist-expr ((key alias) ...) body body* ...)
+ (let ((alist alist-expr))
+ (let ((alias (assq-ref alist 'key)) ...)
+ body body* ...)))
+
+ ((_ alist-expr (key ...) body body* ...)
+ (let ((alist alist-expr))
+ (let ((key (assq-ref alist 'key)) ...)
+ body body* ...)))
+
+))
+
+;; Example uses:
+
+(define alist '((foo . 1) (bar . 2)))
+
+(alist-let alist (foo bar)
+ (+ foo bar)) ;=> 3
+
+(alist-let alist ((foo x) (bar y))
+ (+ x y)) ;=> 3
+
+;; Problem: Can't mix plain key with (key alias) forms:
+
+(alist-let alist ((foo x) bar)
+ (+ x bar)) ;ERROR
+
+```
+
+How do we make it accept a mix of plain keys and `(key alias)` pairs?
+Oh boy, it's more difficult than you may think if you're new to s-r
+macros. Basically, there's no "obvious" solution, and all we have is
+various hacks we can apply.
+
+Let's look at two fairly straightforward hacks, and their problems.
+
+## Option 1
+
+```scheme
+
+;; Solution 1: Internal helper patterns using a dummy constant.
+
+(define-syntax alist-let*
+ (syntax-rules ()
+
+ ((_ "1" alist ((key alias) rest ...) body body* ...)
+ (let ((alias (assq-ref alist 'key)))
+ (alist-let* "1" alist (rest ...) body body* ...)))
+
+ ((_ "1" alist (key rest ...) body body* ...)
+ (let ((key (assq-ref alist 'key)))
+ (alist-let* "1" alist (rest ...) body body* ...)))
+
+ ((_ "1" alist () body body* ...)
+ (begin body body* ...))
+
+ ;; dispatch, ensuring alist-expr only eval'd once
+ ((_ <alist> <bindings> <body> <body*> ...)
+ (let ((alist <alist>))
+ (alist-let* "1" alist <bindings> <body> <body*> ...)))
+
+))
+
+```
+
+(I've switched to my `<foo>` notation for pattern variables in the
+"dispatcher" part. Don't let it distract you. I strongly endorse
+that convention for s-r pattern variables, to make it clear that
+they're like "empty slots" where *any* expression can match, but
+that's a topic for another day.)
+
+What the solution above does, is "dispatch" actual uses of the macro,
+which obviously won't have the string literal `"1"` in first position,
+onto internal sub-macros, which can call each other recursively, so
+each layer only handles either a stand-alone `key` or a `(key alias)`
+couple.
+
+There's some nuances to this implementation. First, if you're not
+familiar with s-r macros, you may mistakenly worry that this solution
+could mask a programmer error: What if we accidentally call the macro
+with a variable bound to the string "1"? Would this lead to a very
+annoying bug that's hard to find? No; remember that syntax-rules
+patterns match *unevaluated* operands, so the internal sub-patterns
+are only triggered by the appearance of a literal string constant of
+`"1"` in the first position; a mistake that would be very apparent in
+code you're reading, and is extremely unlikely to occur by accident.
+
+As for a real pitfall of this implementation: The dispatcher pattern
+*must* be in the final position; otherwise it will actually catch our
+recursive calls starting with `"1"` and bind that string literal to
+the `alist` pattern variable! (Kind of the "reverse" of the fake
+problem described in the previous paragraph, in a sense?) If the
+dispatcher pattern is in the first position, it will keep calling
+itself with an increasing number of `"1"`s at the start, in an
+infinite loop, until you forcibly stop it or it crashes.
+
+As a side note, this brings me to a general s-r pitfall, that applies
+to the original implementation as well in this case: Since patterns
+are matched top to bottom, a simple `key` pattern variable *could*
+actually match the form `(key alias)`, so you have to make sure that
+the pattern for matching those key-alias couples comes before the one
+matching plain keys.
+
+Oh, and by the way, if you're questioning whether we even need those
+internal helper patterns at all: Yes, it's the only way to ensure the
+initial `<alist>` expression is only evaluated once, in an outermost
+`let` wrapping everything.
+
+Let's summarize the issues we've faced:
+
+1. It's easy to forget that pattern variables can match arbitrary
+ expressions, not just identifiers, and there's no way to say it
+ should only match identifiers.
+
+2. When an arbitrary expression is matched by the pattern variable,
+ using it means repeating that expression every time, unless you
+ explicitly use `let` to take care of that, which may require
+ dispatching to another pattern immediately if you wanted to use
+ recursive patterns.
+
+3. You may accidentally put a more generic pattern first, causing it
+ to match an input that was meant to be matched by a subsequent
+ pattern with more deeper destructuring.
+
+It may be interesting trying to solve 3 by specifying some way of
+measuring the "specificity" of a pattern, and saying that those with
+the highest specificity match first, but that may prove difficult.
+Besides, solving 1 would basically solve 3 anyway.
+
+Racket has syntax-parse, which solves the first problem through an
+incredibly sophisticated specification of "syntax patterns" that take
+the place of the humble generic pattern variable of syntax-rules.
+It's cool and all, but the charm of s-r is the simplicity. Can't we
+use some of the ideas of syntax-parse patterns and add them to s-r?
+
+In Racket, there's the concept of "syntax classes," and a pattern can
+be a variable with `:syntax-class-id` appended to its name, which is
+how you make it only match inputs of that syntax class, such as for
+example, only identifiers. Trying to find out what syntax class ids
+are supported may send you down a rabbit hole of how you can actually
+define your own syntax classes, but that just seems to be a weak spot
+of the Racket online documentation; looking a bit closer, you should
+find the list of built-in classes that are supported. They are just
+called "library" syntax classes for some reason:
+
+[Library Syntax Classes and Literal Sets -- Racket Documentation](https://docs.racket-lang.org/syntax/Library_Syntax_Classes_and_Literal_Sets.html)
+
+It would be great if there were classes for atoms (anything that's not
+a list) and lists, though; then we could do this:
+
+```scheme
+
+(define-syntax alist-let*
+ (syntax-rules ()
+
+ ((_ <alist>:list bindings body body* ...)
+ (let ((alist <alist>))
+ (alist-let* alist bindings body body* ...)))
+
+ ((_ alist (key:id ...) body body* ...)
+ (let ((key (assq-ref alist 'key)) ...)
+ body body* ...))
+
+ ((_ alist ((key:atom alias:id) ...) body body* ...)
+ (let ((alias (assq-ref alist 'key)) ...)
+ body body* ...))
+
+))
+
+```
+
+(The key could also be a non-symbol immediate value, like a fixnum,
+boolean, etc.; anything that `assq-ref` can compare via `eq?`. One
+could also just not quote the key, and instead let it be an arbitrary
+expression, which would probably make for a more useful macro, but
+that's a different topic.)
+
+Isn't that really neat? But let's go one step further. I believe
+this strategy of binding an expression via `let` to ensure it's only
+evaluated once is probably so common that it warrants a shortcut:
+
+```scheme
+
+(define-syntax alist-let*
+ (syntax-rules ()
+
+ ((_ alist:bind (key:id ...) body body* ...)
+ (let ((key (assq-ref alist 'key)) ...)
+ body body* ...))
+
+ ((_ alist:bind ((key:atom alias:id) ...) body body* ...)
+ (let ((alias (assq-ref alist 'key)) ...)
+ body body* ...))
+
+))
+
+```
+
+The idea here is: All pattern variables marked with `:bind` are first
+collected, and if there is at least one that is not an identifier,
+then the whole template (the part that produces the output of the s-r
+macro) is wrapped in a `let` which binds those expressions to the name
+of the pattern variable, and uses of that pattern variable within the
+template refer to that binding.
+
+I'm not entirely sure yet if this is an ingenious idea, or a hacky fix
+for just one arbitrary issue you can face while using syntax-rules,
+but I suspect it's a common enough pattern to make it desirable.
+
+## Option 2
+
+I said there were various hacks to solve the original problem; here's
+the second variant. It's actually almost the same thing, but we put
+the helper patterns into a separate macro.
+
+```scheme
+
+;; Solution 2: Separate helper macro
+
+(define-syntax alist-let*
+ (syntax-rules ()
+
+ ;; dispatch, ensuring alist-expr only eval'd once
+ ((_ <alist> <bindings> <body> <body*> ...)
+ (let ((alist <alist>))
+ (%alist-let-helper alist <bindings> <body> <body*> ...)))
+
+))
+
+(define-syntax %alist-let-helper
+ (syntax-rules ()
+
+ ;; basically do here what the internal helpers did in solution 1,
+ ;; but without the need for the "1" string literal hack
+
+))
+
+```
+
+That's cleaner in terms of the patterns we have to write, but we had
+to define a second top-level macro, which feels wrong. It should be
+properly encapsulated as part of the first.
+
+This is where another improvement to s-r could come in handy, and
+that's not making it evaluate to a syntax transformer (i.e., lambda)
+directly, but rather making it more like syntax-case in that regard.
+However, the additional lambda wrapping always really annoyed me, so
+the following syntax may be desirable.
+
+```scheme
+
+(define-syntax (alist-let* . s)
+
+ (define-syntax (helper . s)
+ (syntax-rules s ()
+ ((alist ((key alias) rest ...) body body* ...)
+ (let ((alias (assq-ref alist 'key)))
+ (alist-let* "1" alist (rest ...) body body* ...)))
+
+ ((alist (key rest ...) body body* ...)
+ (let ((key (assq-ref alist 'key)))
+ (alist-let* "1" alist (rest ...) body body* ...)))
+
+ ((alist () body body* ...)
+ (begin body body* ...))
+ ))
+
+ (syntax-rules s ()
+ ((<alist> <bindings> <body> <body*> ...)
+ (let ((alist <alist>))
+ (helper alist <bindings> <body> <body*> ...)))))
+
+```
+
+That looks a bit confusing at first sight, but we can actually do
+something a lot better now, since we already get one stand-alone
+pattern at the start, which fits our intention perfectly here:
+
+```scheme
+
+(define-syntax (alist-let* <alist> <bindings> <body> <body*> ...)
+
+ (define-syntax (helper . s)
+ (syntax-rules s ()
+ ((alist ((key alias) rest ...) body body* ...)
+ (let ((alias (assq-ref alist 'key)))
+ (alist-let* "1" alist (rest ...) body body* ...)))
+
+ ((alist (key rest ...) body body* ...)
+ (let ((key (assq-ref alist 'key)))
+ (alist-let* "1" alist (rest ...) body body* ...)))
+
+ ((alist () body body* ...)
+ (begin body body* ...))
+ ))
+
+ #'(let ((alist <alist>))
+ (helper alist <bindings> <body> <body*> ...)))
+
+```
+
+To be honest, I don't like this solution nearly as much as the first,
+and I now realize that there wouldn't be much point in keeping s-r if
+it's going to be so close to syntax-case. (The only difference, at
+this point, would be that s-r implicitly puts `#'` in front of the
+templates. That's literally all it would do, if I'm not mistaken.)
+
+## Or just implement syntax-parse?
+
+Racket can actually give you the implicit lambda when you want it, by
+offering `syntax-parser` as an alternative to `syntax-parse`:
+
+```scheme
+
+;; The following two are equivalent.
+
+(define-syntax foo
+ (lambda (s)
+ (syntax-parse s ...)))
+
+(define-syntax foo
+ (syntax-parser ...))
+
+```
+
+(At least, I'm pretty sure that's how it's supposed to work; the docs
+just bind the result of `syntax-parser` to an identifier via `define`
+and call it as a procedure to showcase it, for whatever reason.)
+
+Yes, syntax-parse is a lot more complex than syntax-rules, but to be
+honest it seems mainly the fault of the documentation that it doesn't
+showcase the simplest ways of using it, which look essentially the
+same as using syntax-rules, so it's not clear why s-r should stay if
+you have syntax-parse.
+
+Maybe I would just make one change, which is to allow the following
+syntax and thus make the additional `syntax-parser` unnecessary:
+
+```scheme
+
+(define-syntax (foo s)
+ (syntax-parse s ...))
+
+```
+
+Note that this is different from my previous idea of making the first
+operand to `define-syntax` a pattern. The only thing I don't like
+about this variant is that there will never be more than one argument,
+but maybe that's fine?
+
+In any case, I guess the only innovation I came up with here is the
+special `:bind` syntax class id, assuming there isn't already a
+similar thing in Racket or elsewhere.
+
+Oh and this made me realize I should add `foo:bar` as reader syntax to
+Zisp, turning it into `(#COLON foo . bar)` or such.
diff --git a/src/libzisp.zig b/src/libzisp.zig
index 79a54b4..b2c8283 100644
--- a/src/libzisp.zig
+++ b/src/libzisp.zig
@@ -5,13 +5,13 @@ const std = @import("std");
const builtin = @import("builtin");
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 ShortString = value.ShortString;
pub const Value = value.Value;
+pub const Hval = value.Hval;
test "double" {
const d1: f64 = 0.123456789;
@@ -46,12 +46,12 @@ test "fixnum" {
test "ptr" {
const ptr = value.ptr;
- const val: [*]gc.Bucket = @ptrFromInt(256);
- const tag = ptr.Tag.string;
+ const val: [*]Hval = @ptrFromInt(256);
+ const tag = ptr.Tag.istr;
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.checkZispTag(p, tag));
try std.testing.expect(ptr.checkStrong(p));
const pv, const pt = ptr.unpack(p);
@@ -60,7 +60,7 @@ test "ptr" {
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.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)));
@@ -258,7 +258,7 @@ test "parse" {
test "parse2" {
const val = io.parser.parse(
\\ ;; Testing some crazy datum comments
- \\ ##;"bar"#;([x #"y"]{##`,'z})"foo"
+ \\ #;"bar"#;([x #"y"]{##`,'z}) #"foo"
\\ ;; end
);
@@ -299,8 +299,20 @@ test "parse4" {
}
test "unparse" {
- try std.testing.expectEqualStrings(
- "#foo",
- io.unparser.unparse(io.parser.parse("#foo")),
+ const unparse = io.unparser.unparse;
+
+ var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
+ var out: std.ArrayList(u8) = .init(gpa.allocator());
+
+ const w = out.writer();
+ const v = io.parser.parse("#foo");
+ try unparse(w, v);
+ try std.testing.expectEqualStrings("#foo", try out.toOwnedSlice());
+}
+
+test "unparse2" {
+ try io.unparser.unparse(
+ std.io.getStdErr().writer(),
+ io.parser.parse("#{foo bar['x]}"),
);
}
diff --git a/src/libzisp/gc.zig b/src/libzisp/gc.zig
index 819fa0b..6704102 100644
--- a/src/libzisp/gc.zig
+++ b/src/libzisp/gc.zig
@@ -1,15 +1,12 @@
const std = @import("std");
-const Value = @import("value.zig").Value;
+const value = @import("value.zig");
+
+const Hval = value.Hval;
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");
+pub fn alloc(count: usize) []Hval {
+ return gpa.alloc(Hval, count) catch @panic("OOM");
}
diff --git a/src/libzisp/io/parser.zig b/src/libzisp/io/parser.zig
index 1e61385..6449431 100644
--- a/src/libzisp/io/parser.zig
+++ b/src/libzisp/io/parser.zig
@@ -25,17 +25,19 @@
//
// The following table summarizes the other supported transformations:
//
-// [...] -> (#SQUARE ...) #datum -> (#HASH . datum)
+// #datum -> (#HASH . datum) #rune(...) -> (#rune ...)
//
-// {...} -> (#BRACE ...) #rune(...) -> (#rune ...)
+// [...] -> (#SQUARE ...) dat1dat2 -> (#JOIN dat1 . dat2)
//
-// #<...> -> (#ANGLE ...) dat1dat2 -> (#JOIN dat1 . dat2)
+// {...} -> (#BRACE ...) dat1.dat2 -> (#DOT dat1 . dat2)
//
-// 'datum -> (#QUOTE . datum) dat1.dat2 -> (#DOT dat1 . dat2)
+// 'datum -> (#QUOTE . datum) dat1:dat2 -> (#COLON dat1 . dat2)
//
-// `datum -> (#GRAVE . datum) #n#=datum -> (#LABEL n . datum)
+// `datum -> (#GRAVE . datum) dat1|dat2 -> (#PIPE dat1 . dat2)
//
-// ,datum -> (#COMMA . datum) #n# -> (#LABEL . n)
+// ,datum -> (#COMMA . datum) #n#=datum -> (#LABEL n . datum)
+//
+// #n# -> (#LABEL . n)
//
// Notes:
//
@@ -64,10 +66,6 @@
// The parser will see this as an attempt to use an 8-letter rune name, and
// raise an error, since rune names are limited to 6 characters.
//
-// * The #<...> form is a special case; the less-than and greater-than symbols
-// are not otherwise treated as brackets; e.g., <a b c d> is actually four
-// strings: "<a", "b", "c", "d>".
-//
// Syntax sugar can combine arbitrarily; some examples follow:
//
// #{...} -> (#HASH #BRACE ...)
@@ -105,7 +103,7 @@
//
// #{x} -> (#HASH (#BRACE (x))) #{x} -> (#HASH #BRACE x)
//
-// foo(x y) -> (#JOIN foo (x y)) foo(bar) -> (#JOIN foo x y)
+// foo(x y) -> (#JOIN foo (x y)) foo(x y) -> (#JOIN foo x y)
//
//
// === Decoder ===
@@ -249,11 +247,11 @@ const State = struct {
parent: ?*State = null,
retval: Value = undefined,
- // To store accumulated context, such as list elements.
+ // To store a value for context, such as a list of accumulated elements.
context: Value = undefined,
- // To remember what kind of list we're in: () [] {}
- opening_bracket: u8 = undefined,
+ // To store a character for context, such as the type of opening bracket.
+ char_context: u8 = undefined,
fn eof(s: *State) bool {
return s.top.pos >= s.top.input.len;
@@ -264,6 +262,7 @@ const State = struct {
}
fn skip(s: *State) void {
+ // std.debug.print("{c}\n", .{s.top.input[s.top.pos]});
s.top.pos += 1;
}
@@ -284,13 +283,10 @@ const State = struct {
// Consumes whitespace and line comments.
fn consumeBlanks(s: *State) void {
while (!s.eof()) {
- if (s.isWhitespace()) {
- s.skip();
- } else if (s.peek() == ';') {
- s.skip();
- s.consumeLineComment();
- } else {
- return;
+ switch (s.peek()) {
+ '\t', '\n', ' ' => s.skip(),
+ ';' => s.consumeLineComment(),
+ else => return,
}
}
}
@@ -360,11 +356,10 @@ fn readShortString(
const Fn = enum {
start_parse,
start_datum,
- end_dotted_datum,
- end_joined_datum,
- end_datum_label,
+ end_join_datum,
+ end_label_datum,
end_hash_datum,
- end_quote,
+ end_quote_datum,
continue_list,
finish_improper_list,
end_improper_list,
@@ -380,19 +375,21 @@ pub fn parse(input: []const u8) Value {
var top = TopState{ .alloc = alloc, .input = input };
var s0 = State{ .top = &top };
var s = &s0;
- while (true) s = switch (s.next) {
- .start_parse => startParse(s),
- .start_datum => startDatum(s),
- .end_dotted_datum => endDottedDatum(s),
- .end_joined_datum => endJoinedDatum(s),
- .end_datum_label => endDatumLabel(s),
- .end_hash_datum => endHashDatum(s),
- .end_quote => endQuote(s),
- .continue_list => continueList(s),
- .finish_improper_list => finishImproperList(s),
- .end_improper_list => endImproperList(s),
- .perform_return => s.performReturn() orelse return s.retval,
- };
+ while (true) {
+ // std.debug.print("{}\n", .{s.next});
+ s = switch (s.next) {
+ .start_parse => startParse(s),
+ .start_datum => startDatum(s),
+ .end_join_datum => endJoinedDatum(s),
+ .end_label_datum => endLabelDatum(s),
+ .end_hash_datum => endHashDatum(s),
+ .end_quote_datum => endQuoteDatum(s),
+ .continue_list => continueList(s),
+ .finish_improper_list => finishImproperList(s),
+ .end_improper_list => endImproperList(s),
+ .perform_return => s.performReturn() orelse return s.retval,
+ };
+ }
}
fn startParse(s: *State) *State {
@@ -441,11 +438,8 @@ fn startDatum(s: *State) *State {
fn endDatum(s: *State, d: Value) *State {
//
- // We're at the end of a datum; check for dot and join notations:
- //
- // DATUM|.DATUM2
- //
- // DATUM|DATUM2
+ // We're at the end of a datum; check for the various ways data can be
+ // joined together, like DATUM|DATUM or DATUM|.DATUM etc.
//
if (isEndOfDatum(s)) {
@@ -453,28 +447,32 @@ fn endDatum(s: *State, d: Value) *State {
return s.returnDatum(d);
}
- s.context = d;
+ // There's a stupid special-case we have to handle here, where a datum
+ // comment may fool us into thinking there's something to join: foo|#;bar
- if (s.peek() == '.') {
- s.skip();
- return s.recurParse(.start_datum, .end_dotted_datum);
+ const c = s.peek();
+ switch (c) {
+ '.', ':', '|' => s.skip(),
+ '#' => if (checkTrailingDatumComment(s)) {
+ return s.returnDatum(d);
+ },
+ else => {},
}
-
- return s.recurParse(.start_datum, .end_joined_datum);
-}
-
-fn endDottedDatum(s: *State) *State {
- const rune = value.rune.pack("DOT");
- const first = s.context;
- const second = s.retval;
- return endDatum(s, value.pair.cons(rune, value.pair.cons(first, second)));
+ s.context = d;
+ s.char_context = c;
+ return s.recurParse(.start_datum, .end_join_datum);
}
-fn endJoinedDatum(s: *State) *State {
- const rune = value.rune.pack("JOIN");
- const first = s.context;
- const second = s.retval;
- return endDatum(s, value.pair.cons(rune, value.pair.cons(first, second)));
+fn checkTrailingDatumComment(s: *State) bool {
+ const pos = s.pos();
+ s.skip();
+ if (s.eof()) {
+ // Error, but let it be handled later.
+ return false;
+ }
+ const c = s.peek();
+ s.resetPos(pos);
+ return c == ';';
}
fn isEndOfDatum(s: *State) bool {
@@ -484,20 +482,29 @@ fn isEndOfDatum(s: *State) bool {
};
}
+fn endJoinedDatum(s: *State) *State {
+ const rune = value.rune.pack(switch (s.char_context) {
+ '.' => "DOT",
+ ':' => "COLON",
+ '|' => "PIPE",
+ else => "JOIN",
+ });
+ const joined = value.pair.cons(s.context, s.retval);
+ return endDatum(s, value.pair.cons(rune, joined));
+}
+
fn handleHash(s: *State) *State {
s.skip();
//
// We just consumed a hash. Possibilities include:
//
- // #|foo ;rune
- //
- // #n#=DATUM ;datum with numeric label
+ // #|foo ;rune
//
- // #n# ;reference to datum label
+ // #|n#[=DATUM] ;datum label, with or without datum
//
- // #|;DATUM ;datum comment
+ // #|;DATUM ;datum comment
//
- // #|DATUM ;hash-datum
+ // #|DATUM ;hash-datum
//
if (s.eof()) {
@@ -507,29 +514,17 @@ fn handleHash(s: *State) *State {
return err(s, "whitespace after hash");
}
- // Is it a rune? #foo
switch (s.peek()) {
'a'...'z', 'A'...'Z' => return handleRune(s),
- else => {},
- }
-
- // Is it a datum label / reference?
- switch (s.peek()) {
'0'...'9' => return handleDatumLabel(s),
- else => {},
- }
-
- // Is it a datum comment? #;DATUM
- if (s.peek() == ';') {
- s.skip();
- // Don't change s.next in this case. Just let the parser try to redo
- // what it was doing as soon as the commented-out datum has been read.
- return s.recurParse(.start_datum, s.next);
+ ';' => {
+ s.skip();
+ // Don't change s.next in this case. Just let the parser redo what
+ // it was doing as soon as the commented-out datum has been read.
+ return s.recurParse(.start_datum, s.next);
+ },
+ else => return s.recurParse(.start_datum, .end_hash_datum),
}
-
- // Otherwise, it must be a hash-datum. #DATUM
-
- return s.recurParse(.start_datum, .end_hash_datum);
}
fn handleRune(s: *State) *State {
@@ -560,7 +555,7 @@ fn handleDatumLabel(s: *State) *State {
if (s.eof() or s.isWhitespace()) {
const rune = value.rune.pack("LABEL");
- return s.returnDatum(value.pair.cons(rune, n));
+ return endDatum(s, value.pair.cons(rune, n));
}
if (s.getc() != '=') {
@@ -568,22 +563,22 @@ fn handleDatumLabel(s: *State) *State {
}
s.context = n;
- return s.recurParse(.start_datum, .end_datum_label);
+ return s.recurParse(.start_datum, .end_label_datum);
}
fn readDatumLabel(s: *State) ?Value {
return readShortString(s, std.ascii.isDigit, value.sstr.pack);
}
-fn endDatumLabel(s: *State) *State {
+fn endLabelDatum(s: *State) *State {
const rune = value.rune.pack("LABEL");
const payload = value.pair.cons(s.context, s.retval);
- return s.returnDatum(value.pair.cons(rune, payload));
+ return endDatum(s, value.pair.cons(rune, payload));
}
fn endHashDatum(s: *State) *State {
const rune = value.rune.pack("HASH");
- return s.returnDatum(value.pair.cons(rune, s.retval));
+ return endDatum(s, value.pair.cons(rune, s.retval));
}
fn startQuotedString(s: *State) *State {
@@ -591,7 +586,7 @@ fn startQuotedString(s: *State) *State {
s.skip();
const str = readQuotedString(s) catch return err(s, "unclosed string");
- return s.returnDatum(str);
+ return endDatum(s, str);
}
// RQS = Read Quoted String
@@ -611,7 +606,7 @@ fn readQuotedSstr(s: *State) !?Value {
const c = s.getc();
if (c == '"') {
// ok, return what we accumulated
- return value.sstr.packLiteral(buf[0..i]);
+ return value.sstr.packQuoted(buf[0..i]);
}
if (i == 6) {
// failed; reset and bail out
@@ -637,7 +632,7 @@ fn startBareString(s: *State) *State {
fn readBareSstr(s: *State) ?*State {
const sp = s.pos();
if (readShortString(s, isSstrChar, value.sstr.pack)) |sstr| {
- return s.returnDatum(sstr);
+ return endDatum(s, sstr);
} else {
s.resetPos(sp);
return null;
@@ -666,11 +661,11 @@ fn startQuote(s: *State) *State {
',' => "COMMA",
else => unreachable,
});
- return s.recurParse(.start_datum, .end_quote);
+ return s.recurParse(.start_datum, .end_quote_datum);
}
-fn endQuote(s: *State) *State {
- return s.returnDatum(value.pair.cons(s.context, s.retval));
+fn endQuoteDatum(s: *State) *State {
+ return endDatum(s, value.pair.cons(s.context, s.retval));
}
// List processing is, unsurprisingly, the most complicated, and it's made even
@@ -689,7 +684,7 @@ fn startList(s: *State) *State {
}
s.context = value.nil.nil;
- s.opening_bracket = open;
+ s.char_context = open;
return if (isEndOfList(s))
endList(s)
else
@@ -704,19 +699,19 @@ fn isEndOfList(s: *State) bool {
}
fn endList(s: *State) *State {
- const open = s.opening_bracket;
+ const open = s.char_context;
const char = s.getc();
if (open == '(' and char == ')') {
- return s.returnDatum(s.context);
+ return endDatum(s, s.context);
}
if (open == '[' and char == ']') {
const rune = value.rune.pack("SQUARE");
- return s.returnDatum(value.pair.cons(rune, s.context));
+ return endDatum(s, value.pair.cons(rune, s.context));
}
if (open == '{' and char == '}') {
const rune = value.rune.pack("BRACE");
- return s.returnDatum(value.pair.cons(rune, s.context));
+ return endDatum(s, value.pair.cons(rune, s.context));
}
return err(s, "wrong closing bracket for list");
diff --git a/src/libzisp/io/unparser.zig b/src/libzisp/io/unparser.zig
index d835924..c25e918 100644
--- a/src/libzisp/io/unparser.zig
+++ b/src/libzisp/io/unparser.zig
@@ -3,19 +3,100 @@ const std = @import("std");
const value = @import("../value.zig");
const ShortString = value.ShortString;
+const OtherTag = value.OtherTag;
const Value = value.Value;
+const Hval = value.Hval;
-// const State = struct {
+pub fn unparse(w: anytype, v: Value) anyerror!void {
+ try if (value.double.check(v))
+ unparseDouble(w, v)
+ else if (value.fixnum.check(v))
+ unparseFixnum(w, v)
+ else if (value.ptr.checkZisp(v))
+ unparseHeap(w, v)
+ else
+ unparseOther(w, v);
+}
+
+fn unparseDouble(w: anytype, v: Value) !void {
+ _ = w;
+ _ = v;
+ @panic("not implemented");
+}
+
+fn unparseFixnum(w: anytype, v: Value) !void {
+ _ = w;
+ _ = v;
+ @panic("not implemented");
+}
-// }
+fn unparseHeap(w: anytype, v: Value) !void {
+ const p, const t = value.ptr.unpack(v);
+ try switch (t) {
+ .pair => unparsePair(w, p),
+ .istr => @panic("not implemented"),
+ .proc => @panic("not implemented"),
+ };
+}
+
+fn unparseOther(w: anytype, v: Value) !void {
+ try switch (v.other.tag) {
+ .rune => unparseRune(w, v),
+ .sstr => unparseSstr(w, v),
+ .qstr => unparseQstr(w, v),
+ .char => unparseChar(w, v),
+ .misc => unparseMisc(w, v),
+ };
+}
+
+fn unparseRune(w: anytype, v: Value) !void {
+ const name = value.rune.unpack(v);
+ try w.writeByte('#');
+ try w.writeAll(name.constSlice());
+}
-pub fn unparse(v: Value) []u8 {
- var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init;
- var out: std.ArrayList(u8) = .init(gpa.allocator());
- if (value.rune.check(v)) {
- const name = value.rune.unpack(v);
- out.append('#') catch @panic("");
- out.appendSlice(name.slice()) catch @panic("");
+fn unparseSstr(w: anytype, v: Value) !void {
+ const str = value.sstr.unpack(v);
+ try w.writeAll(str.constSlice());
+}
+
+fn unparseQstr(w: anytype, v: Value) !void {
+ const str = value.sstr.unpack(v);
+ try w.writeByte('"');
+ try w.writeAll(str.constSlice());
+ try w.writeByte('"');
+}
+
+fn unparseChar(w: anytype, v: Value) !void {
+ var buf: [4]u8 = undefined;
+ const len = try std.unicode.utf8Encode(v.char.value, &buf);
+ try w.writeAll(buf[0..len]);
+}
+
+fn unparseMisc(w: anytype, v: Value) !void {
+ try switch (v.misc.value) {
+ .f => w.writeAll("#f"),
+ .t => w.writeAll("#t"),
+ .nil => w.writeAll("()"),
+ .eof => w.writeAll("#eof"),
+ .undef => w.writeAll("#undef"),
+ };
+}
+
+fn unparsePair(w: anytype, p: [*]Hval) !void {
+ const vs: *[2]Value = @ptrCast(p);
+ try w.writeByte('(');
+ try unparse(w, vs[0]);
+ var cdr = vs[1];
+ while (value.pair.check(cdr)) : (cdr = value.pair.cdr(cdr)) {
+ try w.writeByte(' ');
+ try unparse(w, value.pair.car(cdr));
+ }
+ if (!value.nil.check(cdr)) {
+ try w.writeByte(' ');
+ try w.writeByte('.');
+ try w.writeByte(' ');
+ try unparse(w, cdr);
}
- return out.toOwnedSlice() catch @panic("");
+ try w.writeByte(')');
}
diff --git a/src/libzisp/value.zig b/src/libzisp/value.zig
index 273c659..fbe7dbe 100644
--- a/src/libzisp/value.zig
+++ b/src/libzisp/value.zig
@@ -165,7 +165,9 @@ const FILL = 0x7ff;
// Used when dealing with runes and short strings.
pub const ShortString = std.BoundedArray(u8, 6);
-pub const OtherTag = enum(u3) { rune, sstr, sstr_lit, char, misc };
+pub const OtherTag = enum(u3) { rune, sstr, qstr, char, misc };
+
+pub const MiscValue = enum(u8) { f, t, nil, eof, undef = 255 };
/// Represents a Zisp value/object.
pub const Value = packed union {
@@ -266,7 +268,7 @@ pub const Value = packed union {
/// For initializing and reading misc values aka singletons.
misc: packed struct {
- value: u8,
+ value: MiscValue,
_reserved: u40 = 0,
_tag: OtherTag = .misc,
_is_ptr: bool = false,
@@ -274,11 +276,9 @@ pub const Value = packed union {
_is_fixnum: bool = false,
},
- const Self = @This();
-
/// Hexdumps the value.
- pub fn dump(self: Self) void {
- std.debug.dumpHex(std.mem.asBytes(&self));
+ pub inline fn dump(v: Value) void {
+ std.debug.dumpHex(std.mem.asBytes(&v));
}
// The following aren't type predicates per se, but rather determine which
@@ -286,32 +286,37 @@ pub const Value = packed union {
// 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;
+ pub inline fn isDouble(v: Value) bool {
+ return v.ieee.exp != FILL or v.ieee.rest == 0;
}
/// Checks for a non-double Zisp value packed into a NaN.
- pub fn isPacked(self: Self) bool {
- return !self.isDouble();
+ pub inline fn isPacked(v: Value) bool {
+ return !v.isDouble();
}
/// Checks for a fixnum.
- pub fn isFixnum(self: Self) bool {
- return self.isPacked() and self.ieee.sign;
+ pub inline fn isFixnum(v: Value) bool {
+ return v.isPacked() and v.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;
+ pub inline fn isPtr(v: Value) bool {
+ return v.isPacked() and !v.ieee.sign and v.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 inline fn isOther(v: Value) bool {
+ return v.isPacked() and !v.ieee.sign and !v.ieee.quiet;
}
- /// Checks for any "other" type of value.
- pub fn isOther(self: Self, tag: OtherTag) bool {
- return self._isOther() and self.other.tag == tag;
+ /// Checks for an other type of value based on tag.
+ pub inline fn isOtherTag(v: Value, tag: OtherTag) bool {
+ return v.isOther() and v.other.tag == tag;
}
};
+
+/// A "heap value" that could be a Value or object header.
+pub const Hval = packed union {
+ value: Value,
+};
diff --git a/src/libzisp/value/boole.zig b/src/libzisp/value/boole.zig
index 623dbc2..2e94e4d 100644
--- a/src/libzisp/value/boole.zig
+++ b/src/libzisp/value/boole.zig
@@ -1,8 +1,7 @@
const Value = @import("../value.zig").Value;
-const misc = @import("misc.zig");
-pub const f = misc.f;
-pub const t = misc.t;
+pub const f = Value{ .misc = .{ .value = .f } };
+pub const t = Value{ .misc = .{ .value = .t } };
// Zig API
diff --git a/src/libzisp/value/char.zig b/src/libzisp/value/char.zig
index eb4bbc9..09a3034 100644
--- a/src/libzisp/value/char.zig
+++ b/src/libzisp/value/char.zig
@@ -5,7 +5,7 @@ const Value = value.Value;
// Zig API
pub fn check(v: Value) bool {
- return v.isOther(.char);
+ return v.isOtherTag(.char);
}
pub fn assert(v: Value) void {
diff --git a/src/libzisp/value/eof.zig b/src/libzisp/value/eof.zig
index 367a86c..4b16669 100644
--- a/src/libzisp/value/eof.zig
+++ b/src/libzisp/value/eof.zig
@@ -2,7 +2,7 @@ const value = @import("../value.zig");
const Value = value.Value;
-pub const eof = @import("misc.zig").eof;
+pub const eof = Value{ .misc = .{ .value = .eof } };
// Zig API
diff --git a/src/libzisp/value/istr.zig b/src/libzisp/value/istr.zig
new file mode 100644
index 0000000..5937531
--- /dev/null
+++ b/src/libzisp/value/istr.zig
@@ -0,0 +1,3 @@
+const std = @import("std");
+
+const value = @import("../value.zig");
diff --git a/src/libzisp/value/misc.zig b/src/libzisp/value/misc.zig
deleted file mode 100644
index 30cbf84..0000000
--- a/src/libzisp/value/misc.zig
+++ /dev/null
@@ -1,8 +0,0 @@
-const Value = @import("../value.zig").Value;
-
-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 14bd800..f95ecad 100644
--- a/src/libzisp/value/nil.zig
+++ b/src/libzisp/value/nil.zig
@@ -2,7 +2,7 @@ const value = @import("../value.zig");
const Value = value.Value;
-pub const nil = @import("misc.zig").nil;
+pub const nil = Value{ .misc = .{ .value = .nil } };
// Zig API
diff --git a/src/libzisp/value/pair.zig b/src/libzisp/value/pair.zig
index 541a5f5..1c34096 100644
--- a/src/libzisp/value/pair.zig
+++ b/src/libzisp/value/pair.zig
@@ -9,7 +9,7 @@ const Value = value.Value;
// Zig API
pub fn check(v: Value) bool {
- return ptr.checkZisp(v, .pair);
+ return ptr.checkZispTag(v, .pair);
}
pub fn assert(v: Value) void {
diff --git a/src/libzisp/value/ptr.zig b/src/libzisp/value/ptr.zig
index e1fadf2..115cc2d 100644
--- a/src/libzisp/value/ptr.zig
+++ b/src/libzisp/value/ptr.zig
@@ -2,8 +2,8 @@ const std = @import("std");
const value = @import("../value.zig");
const gc = @import("../gc.zig");
-const Bucket = gc.Bucket;
const Value = value.Value;
+const Hval = value.Hval;
// Zig API
@@ -42,19 +42,19 @@ pub fn unpackForeign(v: Value) u50 {
// Zisp Pointers
-fn _checkZisp(v: Value) bool {
+pub fn checkZisp(v: Value) bool {
return check(v) and !v.ptr.is_foreign;
}
-fn _assertZisp(v: Value) void {
- if (!_checkZisp(v)) {
+pub 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.zptr.is_weak;
+ return checkZisp(v) and v.zptr.is_weak;
}
pub fn assertWeak(v: Value) void {
@@ -64,19 +64,19 @@ pub fn assertWeak(v: Value) void {
}
}
-pub fn checkZisp(v: Value, tag: Tag) bool {
- return _checkZisp(v) and unpack(v).@"1" == tag;
+pub fn checkZispTag(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)) {
+pub fn assertZispTag(v: Value, tag: Tag) void {
+ if (!checkZispTag(v, tag)) {
v.dump();
@panic("not zisp pointer or wrong tag");
}
}
pub fn checkStrong(v: Value) bool {
- return _checkZisp(v) and !v.zptr.is_weak;
+ return checkZisp(v) and !v.zptr.is_weak;
}
pub fn assertStrong(v: Value) void {
@@ -86,24 +86,24 @@ pub fn assertStrong(v: Value) void {
}
}
-pub fn packZisp(ptr: [*]Bucket, tag: Tag, is_weak: bool) Value {
+pub fn packZisp(ptr: [*]Hval, tag: Tag, is_weak: bool) Value {
return .{ .zptr = .{
.tagged_value = tagPtr(ptr, tag),
.is_weak = is_weak,
} };
}
-pub fn pack(ptr: [*]Bucket, tag: Tag) Value {
+pub fn pack(ptr: [*]Hval, tag: Tag) Value {
return packZisp(ptr, tag, false);
}
-pub fn packWeak(ptr: [*]Bucket, tag: Tag) Value {
+pub fn packWeak(ptr: [*]Hval, tag: Tag) Value {
return packZisp(ptr, tag, true);
}
// Unpacks weak as well; no need for a separate fn.
-pub fn unpack(v: Value) struct { [*]Bucket, Tag } {
- _assertZisp(v);
+pub fn unpack(v: Value) struct { [*]Hval, Tag } {
+ assertZisp(v);
return untagPtr(v.zptr.tagged_value);
}
@@ -117,37 +117,27 @@ pub fn isWeakNull(v: Value) bool {
return v.zptr.tagged_value == 0;
}
-fn tagPtr(ptr: [*]Bucket, tag: Tag) u48 {
+fn tagPtr(ptr: [*]Hval, tag: Tag) u48 {
const int: usize = @intFromPtr(ptr);
const untagged: u48 = @intCast(int);
return untagged | @intFromEnum(tag);
}
-fn untagPtr(tagged: u48) struct { [*]Bucket, Tag } {
+fn untagPtr(tagged: u48) struct { [*]Hval, Tag } {
const untagged: u48 = tagged & 0xfffffffffff8;
- const ptr: [*]Bucket = @ptrFromInt(untagged);
+ const ptr: [*]Hval = @ptrFromInt(untagged);
const int: u3 = @truncate(tagged);
const tag: Tag = @enumFromInt(int);
return .{ ptr, tag };
}
pub const Tag = enum(u3) {
- /// 0. Strings / Symbols
- string,
- /// 1. Bignums / Ratnums
- number,
- /// 2. Pairs ([2]Value)
+ /// *[2]Value
pair,
- /// 3. Collections: Vector, table, etc.
- coll,
- /// 4. OOP: Classes, instances, etc.
- oop,
- /// 5. String buffers
- text,
- /// 6. Procedures
+ /// Interned string (symbol)
+ istr,
+ /// Procedure
proc,
- /// 7. Others
- other,
};
// Zisp API
diff --git a/src/libzisp/value/rune.zig b/src/libzisp/value/rune.zig
index 3a4dc61..a6152b1 100644
--- a/src/libzisp/value/rune.zig
+++ b/src/libzisp/value/rune.zig
@@ -8,7 +8,7 @@ const Value = value.Value;
// Zig API
pub fn check(v: Value) bool {
- return v.isOther(.rune);
+ return v.isOtherTag(.rune);
}
pub fn assert(v: Value) void {
@@ -50,15 +50,12 @@ pub fn pack(s: []const u8) Value {
}
pub fn unpack(v: Value) ShortString {
- var s = ShortString{ .buffer = @bitCast(v.sstr.string) };
+ assert(v);
+ const s: [6]u8 = @bitCast(v.rune.name);
inline for (0..6) |i| {
- if (s.buffer[i] == 0) {
- s.len = i;
- return s;
- }
+ if (s[i] == 0) return .{ .buffer = s, .len = i };
}
- s.len = 6;
- return s;
+ return .{ .buffer = s, .len = 6 };
}
// Zisp API
diff --git a/src/libzisp/value/sstr.zig b/src/libzisp/value/sstr.zig
index 1c9812e..b02fd3d 100644
--- a/src/libzisp/value/sstr.zig
+++ b/src/libzisp/value/sstr.zig
@@ -9,7 +9,7 @@ const Value = value.Value;
// Zig API
pub fn check(v: Value) bool {
- return v.isOther(.sstr) or v.isOther(.sstr_lit);
+ return v.isOtherTag(.sstr) or v.isOtherTag(.qstr);
}
pub fn assert(v: Value) void {
@@ -19,6 +19,10 @@ pub fn assert(v: Value) void {
}
}
+pub fn checkQuoted(v: Value) bool {
+ return v.isOtherTag(.qstr);
+}
+
// For now, ignore encoding, just treat it as []u8.
pub fn isValidSstr(s: []const u8) bool {
@@ -50,8 +54,8 @@ pub fn pack(s: []const u8) Value {
return _pack(s, .sstr);
}
-pub fn packLiteral(s: []const u8) Value {
- return _pack(s, .sstr_lit);
+pub fn packQuoted(s: []const u8) Value {
+ return _pack(s, .qstr);
}
fn _pack(s: []const u8, tag: OtherTag) Value {