https://gitlab.com/datasha.re/zeyn is not the first time Zeyn has been implemented.
Years ago (probably around 2022), I first started implementing Zeyn. Back then, the inspiration for starting it were a few things.
There's not a lot of Postscript interpreters with usable licensing.
There's Ghostscript. Ghostscript is available under one of two licenses: A commercial license or AGPL. AGPL requires source code sharing.
There's Poppler. I believe, it cannot render Postscript directly, instead it can convert Postscript to PDF, then render that. Furthermore, it's only available under GPL licenses that require source code sharing.
Even though I think most all source code should be shared with most everybody, I don't think licenses should require it. Room for nuance should be left.
I especially eschew "infectious" licenses. Licenses that come to affect not just its users, but users of its users and further, with the same magnitude.
When I use such infections licenses in my projects, I bar some people from using them.
There is Xpost. Had I known (more) about Xpost back then, maybe I would have stopped thinking about other reasons to make Zeyn and stuck to Postscript. Xpost licensing is BSD-3-Clause, which is way better.
Postscript lacks (partial) transparency and blend mode support.
PDF introduces those, but PDF is not a programming language. PDF is not really source code. Parts of PDFs, content streams, may use Postscript-like language, using single letter command names instead of English language ones.
In PDF:
1 1 0 rg
10 10 m
50 50 l
10 50 l
h
f
Equivalent PostScript:
1 1 0 setrgbcolor
10 10 moveto
50 50 lineto
10 50 lineto
closepath
fill
Furthermore, in PDF this program has to be referenced somewhere in the document structure. Usually as a "content stream". Referenced in a "dictionary". All this introduces further weird and unreadable syntax.
A blend mode can only be set by using syntax outside of the content streams. By setting a key in a dictionary.
I wanted to see if the C library I created, cutilib, worked.
Cutilib uses interfaces like this:
struct io_Writer {
void *obj;
unsigned (*write)(void *obj, void const *buf, unsigned n, struct Err *err);
};
struct io_Writer w = getWriter();
struct Err err = {0};
w.write(w.obj, "hello", 5, &err)
if (err.obj != 0) {
// write failed
}
Interfaces like this are used for everything
instead of using the C library directly.
E.g. memory allocation is handled with another interface:
struct mem_Allocator.
I did not want to rely on Syscalls to implement basic core business logic, which, in theory, should make that code more portable.
Errors are handled by passing an out parameter to functions.
All this is inspired by programming in Go.
I wanted to do something with the Farbfeld image format.
Farbfeld is close to the simplest possible image format.
farbfeld is a lossless image format which is easy to parse, pipe and compress. It has the following format:
╔════════╤═════════════════════════════════════════════════════════╗ ║ Bytes │ Description ║ ╠════════╪═════════════════════════════════════════════════════════╣ ║ 8 │ "farbfeld" magic value ║ ╟────────┼─────────────────────────────────────────────────────────╢ ║ 4 │ 32-Bit BE unsigned integer (width) ║ ╟────────┼─────────────────────────────────────────────────────────╢ ║ 4 │ 32-Bit BE unsigned integer (height) ║ ╟────────┼─────────────────────────────────────────────────────────╢ ║ [2222] │ 4x16-Bit BE unsigned integers [RGBA] / pixel, row-major ║ ╚════════╧═════════════════════════════════════════════════════════╝
Zeyn was the first project I made that used it. Since then I have implemented it multiple times in multiple languages. The hardest part about it is remembering what direction bytes go when they represent big endian integers.
I don't think it ever took me more than an hour implementing conversion to and from farbfeld.
I did not find an easy way to draw triangles produced by small3dlib to a canvas that can then be output as farbfeld.
Drawing triangles was the first shape, right after pixels, implemented in Zeyn, even before there was a parser.
Note:
When writing this I checked again
and you don't seem to have to supply a triangle function to small3dlib.
Instead you must supply a pixel function, S3L_PIXEL_FUNCTION.
I am unsure if this has always been the case.
If yes, I don't know why should've created Zeyn for that use case.
Drawing pixels to farbfeld is very easy.
Nina Paleys animations look amazing.
E.g. Death of the Firstborn Egyptians.
Some of the clips seemed to me, at the time, like they would rely on special blend modes.
SVG did not seem powerful enough to render some of the images in her animations. At least not without a lot of "code duplication". Like the recursive transformed objects. E.g. tree branches, leafs, segments of snakes.
I enjoyed typing graphics in Postscript way more than typing graphics in SVG.
Having a turing complete programming language seems useful for drawing fractals and custom blend modes.
The Morph transition of Powerpoint is pretty cool. I wanted to be able to create something similar.
I lost the code.
So I decided to implement it again. This time on a public Git repo.
There are a few differences from my previous implementation that I remember.
Previously, the scope and stack were not native values, but implemented separately. This time, the scope is just a native dictionary, and the stack is just a native array. This is gonna make implementing things like like getting the whole stack as an array, getting the scope as a dictionary, or providing a dictionary as the scope for a command much easier.
Previously, the space after a string was mandatory.
This was just an arbitrary choice.
This time, I figured that not putting a space after a string
might be nice for reprs and "from string" constructor functions.
E.g. (1 2 3)arr.
Or for comments, e.g. (This is a comment)drop.
Previously, I did also anticipate that I might want a different representation for numbers at some point.
I anticipated some bignum approach for integers of arbitrary size and floats of arbitrary precision.
But the implementation relied on IEEE754 floats
(normal C floats, I think I used long double)
from the beginning.
This time, I implemented my own way of representing numbers very early. They're not arbitrary precision and may not be the final representation. They're fractions consisting of two integers, a numerator and a denominator.
Previously, I started implementing graphics early.
Even before implementing the parser.
This time, I implemented the parser and basic language functionality first.