diff options
| -rw-r--r-- | html/index.md | 19 | ||||
| -rw-r--r-- | html/notes/macros.md | 151 | ||||
| -rw-r--r-- | html/notes/sr.md | 368 | ||||
| -rw-r--r-- | src/libzisp.zig | 30 | ||||
| -rw-r--r-- | src/libzisp/gc.zig | 13 | ||||
| -rw-r--r-- | src/libzisp/io/parser.zig | 199 | ||||
| -rw-r--r-- | src/libzisp/io/unparser.zig | 101 | ||||
| -rw-r--r-- | src/libzisp/value.zig | 43 | ||||
| -rw-r--r-- | src/libzisp/value/boole.zig | 5 | ||||
| -rw-r--r-- | src/libzisp/value/char.zig | 2 | ||||
| -rw-r--r-- | src/libzisp/value/eof.zig | 2 | ||||
| -rw-r--r-- | src/libzisp/value/istr.zig | 3 | ||||
| -rw-r--r-- | src/libzisp/value/misc.zig | 8 | ||||
| -rw-r--r-- | src/libzisp/value/nil.zig | 2 | ||||
| -rw-r--r-- | src/libzisp/value/pair.zig | 2 | ||||
| -rw-r--r-- | src/libzisp/value/ptr.zig | 54 | ||||
| -rw-r--r-- | src/libzisp/value/rune.zig | 13 | ||||
| -rw-r--r-- | src/libzisp/value/sstr.zig | 10 |
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 { |
