|
The idea of demos is explained below;
a menu at top column right indexes the actual topic specific demos.
story
This demo department collects pages about using þ to implement specific kinds of tasks, presented as a story with occasional bits using fictional developers, featuring Wil as the author persona. The actual source code for þ is developed in this way, interleaved with docs explaining what code does. A main idea of demos is simply trying code involved, to see if the results suit desired behavior, with an empirical focus.
warning
To some folks, perhaps none of the demos qualify for the term demo as it is most often used by industry folks: as a word meaning "exercise the user interface of software." Little code you find here has a user interface, unless you count debug printing. Local use of demo means "show more and define less." Design in a vacuum is second class. Ways to get specific desired effects have priority. So þ demos pursue code as exploring "suppose you want to do xyz..." Notably absent is a unified view of the world besides internal consistency and practical technique which assumes there is no need to ever look at how anyone else does things. As a side effect, code can be grasped locally with a near horizon.
organization
You can see the menu at the top of the right column, with mirrored nav links in the far right sidebar under the image. Both these appear on all demo pages. The todo page describes state of completion for all demo pages, as well as overview for order and relation of topics — Wil offers remarks on topic choice and relevance. Toy language related demos under mu appear under toy, which serves the same role as this page, but for toy language specific code. The þ runtime includes language support without clear boundary for one purpose and another. (Names of þ classes reserve some primitive symbol conventions for VM runtime purposes; but these are usually consistent with other uses. For example, the letter p means pointer most often — even for iterators which resemble pointers; but yp is the peg primitive pointer type for slots in gc memory.)
example
Here's an example demo featuring a comment figure shown on the style page. This demo explains what the type yv means in the course of showing how to intersect two of them: yv yv::vintersect(const yv& kv) const { // k: const «
/// \brief find & return intersection this yv and kv
yv empty(0, 0); // empty: nothing in common
if (!v_n || !kv.v_n) // either yv is empty?
return empty;
const yv* lo; // yv at low address
const yv* hi; // yv high address
if (v_p > kv.v_p) { // this is high?
hi = this;
lo = &kv;
}
else { // this yv is low
hi = &kv;
lo = this;
}
const u8* a = lo->v_p; // lowest address of either
const u8* b = a + lo->v_n; // one byte past low end
const u8* c = hi->v_p; // beginning of higher yv
const u8* d = c + hi->v_n; // one byte past high end
if ( c >= b ) // hi starts after lo? no overlap?
return empty;
// c must be < b, and c >= a (of c and a, c is max);
// so any intersection of this and kv starts with c.
// +-----------+
// <-- 0? -->|<-- d-c -->|
// +-----------+
// ^c ^d
// +---------------+ (maybe a==c)
// |<---- b-a ---->|
// +---------------+ (maybe b < d)
// ^a ^b
// +---------------------------+ (maybe a==c)
// |<----------- b-a --------->|
// +---------------------------+ (maybe d < b)
// ^a ^b
const u8* end = (b < d)? b : d; // lowest yv end
yv overlap(c, end - c); // common to this and kv
return overlap;
}
test
Wil's yv::vintersect() test is just main() returning the value from a test method counting errors, so zero is success. int main (int argc, char * const* argv) {
fprintf(stderr, "mu: sizeof(yv)=%u\n", sizeof(yv));
return yv_vintersect_test();
}
First Wil writes a test case that fails on purpose, to see what an error case looks like in output: using namespace mu; // yv is in mu namespace
static int
fail_vintersect(const yv& actual, const yv& left,
const yv& right, const yv& expect) {
fprintf(stderr,
"FAIL: '%.*s'.vintersect('%.*s')='%.*s' not '%.*s'\n",
(int) left.v_n, (const char*) left.v_p,
(int) right.v_n, (const char*) right.v_p,
(int) actual.v_n, (const char*) actual.v_p,
(int) expect.v_n, (const char*) expect.v_p);
return 1;
}
int yv_vintersect_test() {
int fails = 0;
const char* digits = "0123456789";
yv empty(0, 0); // nothing
yv v3456(digits+3, 4); // "3456"
yv v34(digits+3, 2); // "34"
yv v789(digits+7, 3); // "789"
yv v; // value of intersection
v = v3456.vintersect(v789);
if (!v.veq(v34)) // not first half of second?
fails += fail_vintersect(v, v3456, v789, v34);
return fails;
}
When this executes, the following output appears: mu: sizeof(yv)=8
FAIL: '3456'.vintersect('789')='' not '34'
complete
Wil's final yv_vintersect_test() looks like this: int yv_vintersect_test() {
int fails = 0;
const char* digits = "0123456789";
yv empty(0, 0); // zero length sequence
yv v01234(digits+0, 5); // first five
yv v56789(digits+5, 5); // second five
yv v3456(digits+3, 4); // "3456"
yv v34(digits+3, 2); // "34"
yv v123(digits+1, 3); // "123"
yv v789(digits+7, 3); // "789"
yv v; // value of intersection
v = v123.vintersect(v123);
if (!v.veq(v123)) // not identity?
fails += fail_vintersect(v, v123, v123, v123);
v = v123.vintersect(v789);
if (!v.veq(empty)) // not empty?
fails += fail_vintersect(v, v123, v789, empty);
v = v789.vintersect(v123);
if (!v.veq(empty)) // not empty?
fails += fail_vintersect(v, v789, v123, empty);
v = v01234.vintersect(v56789);
if (!v.veq(empty)) // not empty?
fails += fail_vintersect(v, v01234, v56789, empty);
v = v56789.vintersect(v01234);
if (!v.veq(empty)) // not empty?
fails += fail_vintersect(v, v56789, v01234, empty);
v = v56789.vintersect(empty);
if (!v.veq(empty)) // not empty?
fails += fail_vintersect(v, v56789, empty, empty);
v = empty.vintersect(v01234);
if (!v.veq(empty)) // not empty?
fails += fail_vintersect(v, empty, v01234, empty);
v = v123.vintersect(v01234);
if (!v.veq(v123)) // not middle?
fails += fail_vintersect(v, v123, v01234, v123);
v = v01234.vintersect(v123);
if (!v.veq(v123)) // not middle?
fails += fail_vintersect(v, v01234, v123, v123);
v = v01234.vintersect(v3456);
if (!v.veq(v34)) // not last half of first?
fails += fail_vintersect(v, v01234, v3456, v34);
v = v3456.vintersect(v01234);
if (!v.veq(v34)) // not last half of second?
fails += fail_vintersect(v, v3456, v01234, v34);
return fails;
}
|
A submenu for demos appears below, letting you
go to the page on the given topic written as a demo (as this page
defines the idea).
menu
thorn: todo, names, fd, iovec, assert, log, run, hex, crc, buf, in, out, quote, escape, compare, file, deck, cow, arc, blob, tree, slice, rand, time, stat, hash, heap, node, primes, page, book, pile, stack, atomic, lock, mutex, thread, map, meter, list, iter, ctype (mu: toy, peg, imm, tag, box, symbol, token, number, bigint, class, method, reader, writer, eval, env, vm, gc, world, pcode, compiler, asm, lathe, lisp, smalltalk, design, weight, jar, card, harp, debug, profile) Some demos are stubs: todo is a demo guide. See toy for mu updates on language pages; names introduces naming schemes.
sample
Besides the menu, all the rest of this page is a sample demo. Instead of explaining demos or filling the page with drivel, an actual demo seemed more in the spirit of showing first.
vintersect
Here we explain the meaning of yv — a vector of bytes like an iovec — by showing how we can find the intersection of any two ranges of memory (assuming a 32-bit address space here). See code in the left column for reference. Note u8 and n32 are defined on the types page. typedef uint8_t u8; // stdint.h
typedef uint32_t n32; // stdint.h
The yv api used in the left column is this: struct yv { // vector of bytes (like iovec)
u8* v_p; // byte pointer
n32 v_n; // length in bytes
yv() : v_p(0), v_n(0) { }
yv(const void* p, n32 n) : v_p((u8*) p), v_n(n) { }
yv vintersect(const yv& alt) const;
static int vtest_intersect();
};
Now let's comment from Wil's view. Wil calls yv a run of bytes. (A run is a contiguous sequence of items.) Wil wants to intersect two runs when (for example) generating a slice from a yv run given an offset and length. On yv::vintersect()'s first line, Wil didn't use yv's empty constructor though it would have worked. Wil thinks passing explicit values is better for readability. If either this or input alt has zero length, the intersection is empty. Otherwise Wil renames the two yv instances as the earlier lo or later hi instance, depending on which begins first in memory. If start of later run (c) begins at or after end of earlier run (b) there's no overlap and the result is empty. Otherwise Wil figures a non-empty intersection must exist and start with c (the later run's start). The end of the intersection is the end of either run, whichever comes first. And length is distance from end to start.
equality
To test yv::vintersect(), Wil compares result values with expected values, so a boolean test for equality is needed. This can be done several ways, but let's pick an easy one. This version of yv equality assumes address v_p can be used if v_n is nonzero and you compare instances, implying access is okay. We assume yv's api is a union of all methods we use. struct yv { // vector of bytes (like iovec)
// ...
bool veq(const yv& v) const { // true iff identical content // «
n32 n = v.v_n;
return n == v_n && (!n || 0 == ::memcmp(v_p, v.v_p, n));
}
bool operator==(yv const& v) const { return this->veq(v); }
bool operator!=(yv const& v) const { return !this->veq(v); }
}; // yv
General yv comparison occurs in another demo. This is enough to test for equality of octet sequences.
printing
Another demo features Wil's yv hexdump code with ascii annotation. This demo doesn't need that — just simple printing. The test for intersection works on any bytes, so Wil simply uses "%.*s" format in fprintf() to print strings of known size which are not necessarily nil terminated. (Note Wil often uses fprintf() for minimal code above the standard C library. The fewer moving parts the better.)
namespace
Wil puts most code in a C++ namespace, so all þ code is current inside the mu namespace, but the demo doesn't show this. And Wil uses the following directive to avoid any need to write mu::yv instead of simply yv as shown. using namespace mu;
But it's best to think of mu::yv as the actual class name.
gdb
Wil likes to walk through test executables under gdb to see pieces of memory contain values he expects. In his way, Wil can step through the intersection code as many times as he needs to feel code is bullet proof for his purposes.
scripts
Demos far down the road will include interactive testing, so Wil can play with code interfaces without first committing to a specific static code path to run under gdb. But those demos will first require the demos building a toy language for interactive tests. (Dynamic scripting languages like Python also work for that purpose, though it would involve layers of code Wil has little control over. And it goes against the grain of an aim to build our own toy language here after basic stuff is demonstrated.)
empty
To be consistent, all the tests compare two runs with veq(), even when a simpler test for is-empty could be done. This makes it clear what to pass into a function reporting failure. But this might be a good place to show a conversion to boolean for yv using an operator, because it creates an opportunity to discuss failings of operators due to ambiguous interpretation. struct yv { // vector of bytes (like iovec)
// ...
void vclear() { v_p = 0; v_n = 0; }
bool vempty() const { return 0==v_n || 0==v_p; }
operator bool() const { return v_n && v_p; }
}; // yv
The earlier definition of veq() considers all yv instances with zero length v_n equivalent: so v_p doesn't matter when v_n is zero. But the definition of vempty() and operator bool() look at v_p as well. Why is that? It depends on what you mean by "empty." With respect to yv, Wil wants empty to mean "don't try to use the bytes referenced" either because there are none, or because the pointer is nil. But this makes veq() and vempty() at odds. But Wil never wants to compare bytes located at address zero, so the difference in behavior doesn't arise. Sometimes Wil has a use for zero-sized yv instances using specific non-nil v_p values. For example, when parsing content, a zero-sized yv can still point to where text might be located if present, even when actual length is zero (say, the value of an empty URL query parameter in an HTTP header). So this means one "empty" yv is not identical to every other, since an empty sequence can occur at different points in memory; yv just describes memory spans, not content.
syntax
Dex noticed Wil used const yv& everywhere in the demo, and never yv const&, so this was his big chance to imply Wil didn't know the alternative by bringing it up first. "I think veq(yv const&) is better style," Dex observed. "I also use that syntax more myself," Wil nodded. "But many folks haven't seen it and think it looks weird." "But now's your chance to push a style," Dex urged. "Shut up, Dex," Ira warned. |
demos « Þ
+ todo + names + fd + iovec + assert + log + run + hex + crc + buf + in + out + quote + escape + compare + file + deck + cow + arc + blob + tree + slice + rand + time + stat + hash + heap + node + primes + page + book + pile + stack + atomic + lock + mutex + thread + map + meter + list + iter + ctype |