diff options
-rw-r--r-- | docs/nock/nock-for-everyday-coders-1.md | 589 | ||||
-rw-r--r-- | docs/nock/nock-for-everyday-coders-2.md | 334 | ||||
-rw-r--r-- | docs/nock/nock_implementations.md (renamed from docs/nock_implementations.md) | 0 | ||||
-rw-r--r-- | vere/build.zig | 10 | ||||
-rw-r--r-- | vere/pkg/noun/nock.c | 69 | ||||
-rw-r--r-- | vere/pkg/noun/vortex.c | 6 | ||||
-rw-r--r-- | vere/pkg/vere/solid_boot_test.c | 121 | ||||
-rw-r--r-- | vere/pkg/vere/test_bisect_lifecycle.c | 74 | ||||
-rw-r--r-- | vere/pkg/vere/test_op8.c | 49 | ||||
-rw-r--r-- | vere/pkg/vere/test_opcodes.c | 78 | ||||
-rw-r--r-- | vere/pkg/vere/test_simple_lifecycle.c | 42 | ||||
-rw-r--r-- | zod/.urb/log/0i0/data.mdb | bin | 12832768 -> 12832768 bytes | |||
-rw-r--r-- | zod/.urb/log/0i0/lock.mdb | bin | 8192 -> 8192 bytes | |||
-rw-r--r-- | zod/.urb/log/0i0/vere.txt | 2 | ||||
-rw-r--r-- | zod/.vere.lock | 2 |
15 files changed, 1337 insertions, 39 deletions
diff --git a/docs/nock/nock-for-everyday-coders-1.md b/docs/nock/nock-for-everyday-coders-1.md new file mode 100644 index 0000000..694d4d8 --- /dev/null +++ b/docs/nock/nock-for-everyday-coders-1.md @@ -0,0 +1,589 @@ +Nock for Everyday Coders, Part 1: Basic Functions and Opcodes +By ~timluc-miptev + +Series Outline +Part 1: Basic Functions and Opcodes +Part 2: The Rest of Nock and Some Real-World Code +Interlude: Loose Ends and FAQ +Part 3: Design Patterns and Real Programs +Table of Contents +Intro +Getting Started and Confusing Points +Nock's Simplest Functions, 0 and 1 +4, the Incrementing Function +Cell-Maker (aka the Distribution Rule) +3 and 5, the "Is This a Cell?" and "Equality Test" Functions +2, the "Subject-Altering" Function +Summary and First Hoon Connections +End of Part 1 +tldr;/Intro +For Whom? +Nock is not super complex, and most normal programmers can learn the basics of it rapidly. The mental model you gain from Nock turns out to be very useful in learning Hoon and understanding Urbit. + +The Urbit docs generally suggest not worrying about Nock, but it's very simple and small, so I think most normal programmers will feel more comfortable in Hoon if they learn its basics. + +By "normal programmer", I mean: a person of somewhat above-average IQ (programming is a pretty self-selecting profession) who works relatively high up the modern tech stack, abstracted from low-level stuff. His/her day job likely consists of a lot of React/JS/SQL/Rails/Java etc, and he/she is not really familiar with assembly/OS stuff. That describes me and most programmers I know. + +Why Is This Necessary? +The official Nock docs on the Urbit site are accurate, thorough, and well-explained. However, they take a little while to get to the practical examples, and probably lose a fair number of people along the way. I'm not trying to be original; I'm just a translator. + +If I say something here and you're like "wait, isn't that already in the Nock docs?", the answer is yes, it is; I'm just changing the order for clarity/teaching purposes, in ways that were helpful to me when I learned. + +Goals by the End of the Series +to explain Nock clearly in terms most programmers will relate to +impart a feeling of confidence with very basic Nock +give a knowledge of Nock’s idioms and big "wins" so that they carry over to Hoon learning +Getting Started--Confusing Points +When people first look at Nock, they see the definition page, which, tbh, is fairly intimidating. + +I'm talking about lines like this in the spec: + +/[(a + a) b] /[2 /[a b]] +The problem is, the programmer already probably knows that Nock code he's seen looks more like the below--just lists of numbers, with no symbols: + +[6 + [5 [0 6] [1 0]] + [0 7] + [9 4 [[0 2] [2 [0 6] [0 5]] [4 0 7]]] +] +So what gives? Which one is the "real" Nock? + +Phases of Nock +We are looking at two different things in the examples above: + +Pseudocode for how to interpret +Nock code to be interpreted (the lists of numbers) +The symbols and spec are pseudocode, not real Nock code. They could just as easily be written in English, and they will never be written down as actual Nock code and given to an interpreter. They represent what an interpreter should do to turn Nock code into interpreter instructions. + +The lists of numbers are the actual Nock code. This is what you feed to an interpreter to get some result. + +What Is a Nock Interpreter? +An interpreter can be a computer program, or it can be a human manually expanding Nock code into results. In both cases, the program and human have to know the Nock pseudocode in order to do the right thing with incoming Nock code. + +So a Nock interpreter is any entity that takes Nock code as input, and gives a noun as output. A noun can be: + +:: a number +782 +:: a cell (pair with two elements) +[782 9872] +:: each element can itself be a pair +[782 [9872 89728]] +:: the above can be written as +[782 9872 89728] +How to Run Nock Code +We will be expanding Nock pseudocode manually in the examples that follow, in effect acting as our own interpreter. + +If we want to check that we're getting the right results from our manual interpretation, the Urbit dojo has a Hoon function that runs Nock code (i.e. a Nock interpreter). To use it: + +Start up a dojo session (see here for how to create a fake ship) +At the prompt, we can execute Nock with .*(NOCK_SUBJECT, NOCK_FORMULA) +Subject, Formula? +Let's keep this simple: + +subject = an argument to a function +formula = the function +That's it. We'll see below how this works, going really slowly with examples. + +Evaluating Our First Nock Code +OK, so the interpreter takes two arguments, a "subject" and a "formula". Both are nouns (a number or a cell). Let's run some insanely simple Nock code in the Dojo: + +~zod:dojo> .*(42 [0 1]) +42 +In the above, 42 is our subject. [0 1] is our formula. + +Formulas are always cells, and the first element of the cell is a number that you can think of as the name of the function. + +In this case, our function name is 0, which is the memory slot function. It is always followed by 1 number, in this case 1, which is the number of the memory slot to fetch in the subject. + +Whenever we look at Nock code, we want to ask: + +What is the subject (function argument)? In this case, it's 42. +What is the formula (function)? In this case, it's [0 1]. +What value does that formula (function) produce when called on this subject (argument)? In this case, the return value is 42. +Why is the return value 42? How does this formula work? + +Nock's Simplest Functions, 0 and 1 +The two most basic Nock functions are 0 and 1. The goal here is to get strong intuitions of what they do, how they handle edge cases, and how this relates to the Nock spec/pseudocode. + +Prerequisite +Note: Before getting started, you should understand how Nock/Hoon handle addresses in binary trees. I have put 3 resources below + +If you already understand why memory slot 5 of + +[['apple' %pie] [0b1101 0xdad]] +is %pie, you are good to go and can skip this explanation. + +Binary Tree Explanations +official Hoon documents on binary trees +the Tree Addressing sectiion +My explanation: Every noun in Nock can be thought of as a tree, which means we can give an exact number to access any position in the tree. This means that, no matter how big our subject (argument) is, we can yank a value out of any part of it. + +How do you say which slot number you want from the tree? + +1 is the tree root +The head of every node n is 2n +the tail is 2n+1 +Let's take an example tree to illustrate. In Nock cell form, the tree is: + +[[4 5] [6 14 15]] +Drawn as ASCII art (this is not real Nock code), the tree looks like: + + 1 + 2 3 +4 5 6 7 + 14 15 +So + +1 is the address of the whole tree, [[4 5] [6 14 15]] +2 is the address of the left branch, [4 5] +3 is the address of the right branch, [6 14 15] +15 is the value 15 +Let's play around now in the dojo Nock interpreter so that we can confirm this. In each example, our subject (argument) will be the tree [[4 5] [6 14 15]]. + +:: formula (function) [0 1]: get the whole tree +~zod:dojo> .*([[4 5] [6 14 15]] [0 1]) +[[4 5] 6 14 15] + +:: formula (function) [0 2]: get the left branch +~zod:dojo> .*([[4 5] [6 14 15]] [0 2]) +[4 5] + +:: formula (function) [0 7]: get the subtree in slot 7 +~zod:dojo> .*([[4 5] [6 14 15]] [0 7]) +[14 15] +0, the "Memory Slot" Function +The pseudocode for the 0 opcode is as follows: + +*[a 0 b] /[b a] +/[b a] is pseudocode. In English, it means "a is a binary tree. Get the memory slot numbered b. + +So if a were the tree [9 10], and b were 1, we'd get the memory slot 1 in the tree [9 10], which is just the tree itself. + +Written in pseudocode, that's /[1 [9 10]]. We can also do /[2 [9 10]], which grabs memory slot 2, aka 9. + +Let's look at some examples. I've put them first in Dojo form so that you can see how they run in that interpreter, and then I show the "human interpreter" in pseudocode below that. + +Example 1 +:: get memory slot 1 +~zod:dojo> .*([50 51] [0 1]) +[50 51] +:: PSEUDOCODE -- replaces the Dojo's () with [] +*[[50 51] [0 1]] +:: *[a 0 b] -> a = [50 51], b = 1 +/[1 [50 51]] +[50 51] +Example 2 +~zod:dojo> .*([50 51] [0 2]) +50 +:: PSEUDOCODE -- replaces the Dojo's () with [] +*[[50 51] [0 2]] +:: *[a 0 b] -> a = [50 51], b = 2 +/[2 [50 51]] +50 +Example 3 -- A Crash! +~zod:dojo> .*([50 51] [0 [0 1]]) +ford: %ride failed to execute: +:: PSEUDOCODE +*[[50 51] [0 [0 1]]] +/[[0 1] [50 51]] +:: can't evaluate this--a memory slot must be a number like 2, not a cell like [0 1] +CRASH +Summary of 0 +0 is everywhere in Nock, because "get something from a memory slot" really means "store stuff in a place and get it whenever I want," which is another name for creating variables. These memory slot numbers take the place of variable names. + +If you're familiar with assembly or C, they're conceptually similar to memory pointers or registers in how Nock uses them. Keep in mind though, Nock is functional/immutable, so it doesn't update memory locations: it creates copies of data structures with altered values. + +We've also seen that the memory slot function can't take just anything as the memory slot to fetch: it must receive an atom (number). + +1, the "Quoter" Function +This is another really simple function. You can think of it as a "quoter": it just returns any value passed to it exactly as it is. It ignores the subject, and just quotes the value after it. Let's look at a couple examples. + +~zod:dojo> .*([20 30] [1 67]) +67 +:: [1 2 587] is same as [1 [2 [587]]] +~zod:dojo> .*([20 30] [1 [2 [587]) +[2 587] +~zod:dojo> +It doesn't matter how much information is after the 1: 1 is a dumb function that just returns it all. + +The pseudocode for 1 is: + +*[a 1 b] b +In English, this means: "ignore the subject a, and just return everything after the 1 exactly as it is. + +Let's look at our first example, .*([20 30] [1 67]). The subject a is [20 30], so we ignore that. What's after the 1? 67, so we return that. + +In the second example, "everything after the 1" is longer, but the same rule applies: the Nock interpreter just returns it exactly as it is, after stripping out unnecessary brackets. + +4, the Incrementing Function +0 and 1 are simple functions that don't have any nested behavior. Now we're going to move to a function that does have nested children. + +Here are code examples of opcode 4, and then we'll look at its pseudocode and break down the examples. + +:: example 1 +~zod:dojo> .*(50 [4 0 1]) +51 + +::example 2 +~zod:dojo> .*(50 [4 4 0 1]) +52 + +:: example 3 +~zod:dojo> .*([100 150] [4 4 0 3]) +152 + +:: example 4 +~zod:dojo> .*(50 [4 1 98]) +99 + +::example 5 +~zod:dojo> .*(50 [4 1 [0 2]]) +ford: %ride failed to execute: +Here's 4's pseudocode, juxtaposed with that of 0 and 1: + +:: 4 +*[a 4 b] +*[a b] + +:: 0 +*[a 0 b] /[b a] + +:: 1 +*[a 1 b] b +In English, this says "when we have subject a, function 4, and Nock code b, first evaluate [a b] as [subject formula], and then add 1 to the result." + +If we contrast with 0 and 1, we see that the right side has a * symbol. This symbol means "evaluate the expression again in the Nock interpreter." 0 and 1 did not have this symbol, and that's why they couldn't evaluate nested Nock expressions. + +Example 1 Breakdown +.*(50 [4 0 1]) +Let's start by translating the dojo's .*(subject formula) to pseudocode of the form *[a b], and then expand it line by line: + +:: change to pseudocode +*[50 [4 0 1]] +:: move the 4 to the outside as + ++*[50 [0 1]] +:: expand the 0 opcode to the memory slot operator "/" ++/[1 50] +:: grabs the memory slot ++(50) +:: evaluate +51 +The [a b] part of +*[a b] expands to: + +[50 [0 1]] +OK, this we know how to handle! It's just our 0 function, and it wants the value in memory slot 1 of the subject. That's 50. + +So now we that *[a b] = 50, and we just have to add 1 to it (the + in +*[a b]). That is 51, which is exactly what the interpreter gave us. + +Example 2 Breakdown +This one is similar, we just have an extra 4. We again start by translating the dojo's .*(subject formula) to pseudocode, and then expand + +*[50 [4 4 0 1]] +:: the first 4 moves outside as a '+' ++*[50 [4 0 1]] +:: 2nd 4 becomes a '+'...OK, we have the 0 function again! +++*[50 [0 1]] +++/[1 50] +++(50) ++(51) +52 +Example 3 Breakdown +Here we again see lots of 4s applied consecutively, and we also see how we can yank values out of a more complicated subject and manipulate them. Notice how the subject is a cell, not an atom. The rest is the same as in Example 2. + +a (the subject) = [100 150] +*[[100 150] [4 4 0 3]] ++*[[100 150] [4 0 3]] +:: Now we've extracted all the increments ("+" signs) +:: So we just grab the value at memory slot 3 ([0 3] command) +++*[[100 150] [0 3]] +++/[3 [100 150]] +++(150) ++(151) +152 +Example 4 Breakdown +In the below example, we see how we can use the quote/constant function 1 to generate the value 98 and increment it. We ignore the subject 50 completely. + +*[50 [4 1 98]] +:: formula is the "quoter" function: [1 98] ++*[50 [1 98]] ++(98) +99 +Example 5 Breakdown +Just as opcode 0 had values it couldn't handle (non-atoms), so opcode 4 needs the nested value inside it to evaluate to an atom. + +*[50 [4 1 [0 2]]] +:: OK cool, the nested value is a 1 opcode, let's see what happens ++*[50 [1 [0 2]] +:: 1 ignores the subject (50) and just returns [0 2] ++([0 2]) +:: oh crap, [0 2] isn't an atom, how can we increment it??? We can't so we crash +ford: %ride failed to execute: +Summary +In these examples, we've seen that function 4 can be called as many times in a row as we want. At the end of those calls, it always ends up incrementing a number that either is yanked from the subject (memory slot function 0) or quoted as it is (quote function 1). + +the Cell-Maker (aka the Distribution Rule) +The Nock interpreter is allowed to return nouns, which are atoms (positive numbers) or cells (pairs of nouns). What have our functions/opcodes been returning so far? + +0: atoms or cells, depending what's in the memory slot that we yoink +1: atoms or cells, depending on what we quote +4: just atoms +But what if my subject was [51 67 89], and I wanted to increment every value and return that as [52 68 90]? How can I do that when it's a cell, and 4 only seems able to return atoms? + +The answer is something that the Nock docs call the "Distribution Rule" or "implicit cons" (hello, fellow LISPers!), but that I find easiest to think of as the "Cell-Maker Rule." + +A Quick Detour into Nock Formulas +We haven't talked much yet about what values are legal to feed into the Nock interpreter (the .*(subject formula) function in the dojo). So far, we've only been using formulas that start with numbers (our functions/opcodes 0/1/4). + +Let's now fully solidify our understanding of what's a legal formula by taking a quick look at the 3 possible cases (the format is`.(subject formula)`*): + +:: formula is a cell starting with an atom--works; we knew this +~zod:dojo> .*(50 [0 1]) +50 + +:: formula is just an atom (0)--error, not valid +~zod:dojo> .*(50 0) +ford: %ride failed to execute: + +:: now we do: .*(subject [cell1 cell2]) +:: so...formula is a cell that starts with a cell...wtf, this works? +~zod:dojo> .*(50 [[0 1] [1 203]]) +[50 203] +The Cell-Maker in All His Glory +So apparently a formula cell can start with a cell. The Cell-Maker rule is (from the Nock docs): + +*[subject [formula-x formula-y]] +=> [*[subject formula-x] *[subject formula-y]] +In our example above, formula-x is [0 1], and formula-y is [1 203]. They each evaluate individually against the subject, and the end result is a cell. + +Using Our New Powers +We can make as many cells in a row as we want! + +~zod:dojo> .*(50 [[0 1] [1 203] [0 1] [1 19] [1 76]]) +[50 203 50 19 76] +We can put any operation inside each cell! + +:: [19 20] is our subject, just to be clear +:: the rest is formulas you've seen before +~zod:dojo> .*([19 20] [[0 1] [1 76] [4 4 0 3]]) +[[19 20] 76 22] +If we take the returned collection [[19 20] 76 22] in order, we can write in English how they connect to our collection of formulas that we passed: + +[19 20]: [0 1], get memory slot 1 +76: [1 76], return the quoted value 76 +22: [4 4 0 3], increment twice the value in memory slot 3 (20) +So we can pass one small subject ([19 20]) and make an arbitrarily long collection of values from it, using any functions we want. Cell-Maker ftw! + +3 and 5, the "Is This a Cell?" and "Equality Test" Functions +Now we come to functions/opcodes 3 and 5, which are pretty straightforward after we've seen how 4 and the Cell-Maker work. Functions 3 and 5, like 4, allow nested evaluation. Let's put all their pseudocode definitions together to compare: + +:: function/opcode 3 +*[a 3 b] ?*[a b] +:: function/opcode 4 +*[a 4 b] +*[a b] +:: function/opcode 5 +*[a 5 b c] =[*[a b] *[a c]] +First of all, notice how the right side of all these "equations" has the evaluation operator, *. This means that these functions can have nested formulas, since they keep evaluating all the way down. + +There are some new pseudocode symbols here that we need to translate into English. We already know +: "increment the value after this". Now we also see: + +?: "check whether the value after this is a cell. Return 0 if yes, 1 if no" +=: "first run the function in b with subject a as the argument, and same for the function in c. If the results are equal, return 0, if not, return 1. +Dojo + Pseudocode Examples of 3, the "Is-This-A-Cell?" Function +Example 1 -- not a cell + +~zod:dojo> .*(50 [3 0 1]) +1 +:: PSEUDOCODE OF THE ABOVE +*[50 [3 0 1]] +?*[50 [0 1]] +::get memory slot 1 of the subject +?(50) +:: is 50 a cell? No, so return 1 +1 +Example 2 -- yes, this is a cell + +~zod:dojo> .*([50 51] [3 0 1]) +0 +:: PSEUDOCODE OF THE ABOVE +*[[50 51] [3 0 1]] +?*[[50 51] [0 1]] +::get memory slot 1 of the subject: [50 51] +?([50 51]) +:: is [50 51] a cell? Yes, so return 0 +0 +Example 3 -- nested evaluation with one of the other 3/4/5 functions/opcodes + +~zod:dojo> .*([50 51] [4 4 3 0 1]) +2 +:: PSEUDOCODE OF THE ABOVE +*[[50 51] [4 4 3 0 1]] ++*[[50 51] [4 3 0 1]] +:: whatever comes out of the 3 function, we're gonna increment twice +++*[[50 51] [3 0 1]] +:: down to just fetching memory slot 1 +++?*[[50 51] [0 1]] +++?([50 51]) +:: is [50 51] a cell? Yes, so return 0 +++(0) ++(1) +2 +Example 4 -- check whether multiple things are cells using Cell-Maker + +~zod:dojo> .*([[50 51] 52] [[3 0 2] [3 0 3]]) +[0 1] +*[[[50 51] 52] [[3 0 2] [3 0 3]]] +?[*[[50 51] 52] [0 2]] + *[[50 51] [0 3]]] +:: yank memory slots 2 and 3 +?*[[50 51] 52] +:: first is a cell, second is not +[0 1] +Dojo + Pseudocode Examples of 5, the "Equals" Function +Because 5 compares the results of 2 formulas, it always makes 2 inner evaluations of the subject. It's similar to Cell-Maker in this way. + +Example 1 -- compare two equal values + +~zod:dojo> .*([50 51] [5 [0 2] [0 2]]) +0 +:: PSEUDOCODE +*[[50 51] [5 [0 2] [0 2]]] +:: factor out the = +=[*[[50 51] [0 2]] *[[50 51] [0 2]]] +:: get memory slot 2 twice +=(50 50) +0 +Example 2 -- compare two unequal values + +~zod:dojo> .*([50 51] [5 [0 2] [0 3]]) +1 +:: PSEUDOCODE +*[[50 51] [5 [0 2] [0 3]]] +:: factor out the = +=[*[[50 51] [0 2]] *[[50 51] [0 3]]] +:: get memory slot 2 and memory slot 3 +=(50 51) +1 +Example 3 -- compare two values, one of which comes from a nested function + +~zod:dojo> .*([50 51] [5 [4 0 2] [0 3]]) +0 +:: PSEUDOCODE +*[[50 51] [5 [4 0 2] [0 3]]] +:: factor out the = +=[*[[50 51] [4 0 2]] + *[[50 51] [0 3]]] +:: factor out the + +=[+*[[50 51] [0 2]] + *[[50 51] [0 3]]] +:: get memory slots 2 and 3 +=[+50 51] +:: evaluate the + +=([51 51]) +0 +Example 4 -- 5 knows how to compare cells, not just atoms. Erotic. + +~zod:dojo> .*([99 99] [5 [1 [99 99]] [0 1]]) +0 +:: PSEUDOCODE +*[[99 99] [5 [1 [99 99]] [0 1]]] +=[*[[99 99] [1 [99 99]]] + *[[99 99] [0 1]]] +:: first cell is the result of quoter function (ignores subject) +:: second cell is the result of fetching memory slot 1 in the subject +=[[99 99] [99 99]] +:: true +0 +2, the "Subject-Altering" and "Stored Procedure" Function +In all our examples so far, the subject has been defined at the start when we call the interpreter, and never changes. But what if we want a different subject? + +Motivation, or "Why Would I Want a Different Subject?" +A different subject? Why would we want that? Here's an easy example. Say you found the following piece of Nock code on the interwebz: + +[8 [1 0] 8 [1 6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1] +This is the code for a Nock function that expects a subject that is an atom, and decrements that subject by 1. You could actually enter it in the Dojo right now: + +~zod:dojo> .*(100 [8 [1 0] 8 [1 6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1]) +99 +It works! You do not have to understand the code right now; just try entering different numbers instead of 100 to see the program working. + +But now imagine that I have a Nock program with a different subject + +~zod:dojo> .*([50 51] some-formula) +And somewhere inside some-formula, I want to decrement the number 51. I can't pass [50 51] as the subject to my decrementer code above though, because that's a cell, and it expects just an atom (a number). + +Subject Altering to the Rescue +The function/opcode 2 is designd to handle this problem for us. Here's the pseudocode, and then I'll explain what the pseudocode means. + +:: PSEUDOCODE +*[a 2 b c] *[*[a b] *[a c]] +2 expects 2 formulas after the subject a: b and c. With those, it: + +runs formula b against the subject to set up a new environment/subject derived from the subject +runs formula c against the subject to prepare a 2nd function. +run that 2nd function against the new environment/subject from step (1) +Note that the pseudocode for 2 has nested *s. + +*[*[a b] *[a c]] +The 2 inner *s run steps (1) and (2), and the outer one, around the whole expression, runs step (3). + +Examples +Example 1 — change the subject, have step (2) just call a constant value +~zod:dojo> .*([50 51] [2 [0 3] [1 [4 0 1]]]) +52 +:: PSEUDOCODE +*[[50 51] [2 [0 3] [1 [4 0 1]]]] +:: separate b and c to each run against the subject (steps 1 and 2) +*[*[[50 51] [0 3]] *[[50 51] [1 [4 0 1]]]] +:: after steps 1 and 2, we have a new subject, 51! +:: note how we're back in normal *[subject formula] form +*[51 [4 0 1]] +:: apply the 4 function as we're used to ++*[51 [0 1]] +:: grab 51 from memory slot 1 ++(51) +52 +Example 2 — grab a block of code from the subject in step (2), then run it in step (3). +Think of this as grabbing a "stored procedure" from the subject. + +~zod:dojo> .*([[4 0 1] 51] [2 [0 3] [0 2]]]) +52 +:: PSEUDOCODE, subject is [[4 0 1] 51] +*[[[4 0 1] 51] [2 [0 3] [0 2]]]] +*[*[[[4 0 1] 51] [0 3] + *[[[4 0 1] 51][0 2]] +:: step 1 gets memory slot 3, step 2 grabs memory slot 2 +*[51 [4 0 1]] +:: looks like a normal 4 opcode to me! ++*[51 [0 1]] +:: grab memory slot 1 ++(51) +52 +Example 3: Back to Our Motivating Case +Remember our decrementing block of code that we couldn't use when the subject was [50 51], instead of just an atom? Opcode 2 makes handling that issue a piece of cake. + +We simply use 2 to transform our subject into an atom, and use 1 to quote the decrement block of code before it evaluates in step (3). + +~zod:dojo> .*([50 51] [2 [0 2] [1 [8 [1 0] 8 [1 6 [5 [0 7] 4 0 6] [0 6] 9 2 [0 2] [4 0 6] 0 7] 9 2 0 1]]]) +49 +:: PSEUDOCODE (subject is [50 51]) +:: I substitute "decrement-formula" for that block of Nock code for clarity +*[[50 51] [2 [0 2] [1 decrement-formula]]] +*[*[[50 51] [0 2]] *[[50 51] [1 decrement-formula]]] +:: 1, the quoting function, just returns the decrement-formula, like in Example 1 +*[50 decrement-formula] +::decrement-formula has the atom subject that it wants now! +49 +Summary and First Hoon Connections +For those who know a bit of Hoon, Example 2 above is similar-ish to calling an arm that produces a gate, and then running the gate. Most of Hoon runs this type of stored-procedure + subject-altering Nock, and it all uses opcode 2 at its base. + +And for those who know Hoon, [[4 0 1] 51] should already be looking a lot like [battery payload]...that's not a coincidence. + +We're starting to see the first glimpses of how Hoon's cores, arms and subjects/subject mutations flow out of the fundamental structure of Nock. + +We also see how Hoon/Nock lend themselves well to throwing around chunks of code, and adjusting the subject as necessary to create the correct subject/environment against which to run that code. + +End of Part 1 +You now have seen all the fundamental functions/opcodes in Nock. In Part 2, I'll introduce the remaining functions, which no longer need pseudocode: we have enough scaffolding now to build the rest of Nock from Nock itself. Instead, these new opcodes will just be shortcuts/macros/code expansions of opcodes 1-5. + +If you already feel like you're comfortable with Nock now, you can skip my Part 2, and jump directly to the section of the Nock Explanation titled Sugar to see the rest of the opcodes explained. + +We'll also start to connect Nock to Hoon, and see how most of the fundamental (and slightly weird) features of Hoon flow directly from Nock's structure, and make a lot more sense in combination with it. diff --git a/docs/nock/nock-for-everyday-coders-2.md b/docs/nock/nock-for-everyday-coders-2.md new file mode 100644 index 0000000..bc6dc37 --- /dev/null +++ b/docs/nock/nock-for-everyday-coders-2.md @@ -0,0 +1,334 @@ +Nock for Everyday Coders, Part 2 +The Rest of Nock and Some Real-World Code +By ~timluc-miptev + +Series Outline +Part 1: Basic Functions and Opcodes +Part 2: The Rest of Nock and Some Real-World Code +Interlude: Loose Ends and FAQ +Part 3: Design Patterns and Real Programs +A word of encouragement: we're done with the hard part now. Every Nock function we learn in this section will be built from pieces in Part 1. + +None of these new functions are necessary to make Nock work. All of them except Nock 10 and 11 are syntatic sugar that is part of the Nock definition, and must be built into every correct Nock implementation. If you have seen macros or code expansions in other languages, that's another word for what's happening here. + +Nock 10 makes it easier to replace a memory value somewhere in a tree, and Nock 11 allows passing hints to the interpreter. + +One point of clarity: this syntactic sugar/code-expansion system is not extensible. This means that you can't invent your own Nock opcodes and still have that language be Nock; you're making a higher-level language on top of Nock at that point. + +In fact, that's not a bad way to think of Hoon: it's a higher-level language that adds missing syntatic sugar/macros and human-readable names to Nock. (That's not the whole story, but it's a decent mental peg to initially hang Hoon on.) + +Nock is intentionally very, very small, such that you can always walk through and analyze what is happening in a block of code if you know Nock's opcodes. + +Table of Contents +An Opening Note about the 1 Function +6, "If/Else" +7, the "Composition" Opcode +8, the "Variable Adder" Opcode +9, Create a Core and Run One of Its Arms +10, Replace a Memory Slot +Real Nock Code +An Opening Note about the 1 Function +You're going to see the 1 opcode (the "Quoter") appear in a lot of examples below for a simple reason: the 6-9 opcodes expect formulas in a lot of places, not just atoms. And, as we've seen already, formulas have to be cells. + +Whenever a formula is required, but you really just want to return a number, you use the quoter function. + +Example 1: Increment the Number 5 +:: we just want to run 4 on the number 5, but 4 expects a formula after it, so we use [1 5] +~zod:dojo> .*(0 [4 1 5]) +6 +Example 2: Compare a Memory Slot to a Number +:: we grab memory slot 2 +:: then it has to be compared to the result of a formula +:: so we just use the formula [1 23] to return 23 +~zod:dojo> .*([23 45] [5 [0 2] [1 23]]) +0 +6, "If/Else" +We're into the "Sugar" part of Nock now. This means that all the functions in Part 2 will use only * and Nock code in their pseudocode. For example, here is the code for 6, the "If/Else" function. + +*[a 6 b c d] *[a *[[c d] 0 *[[2 3] 0 *[a 4 4 b]]]] +English Definition (Really Simple) +The English definition of what's happening is simple/boring (refer to the left side of the definition above): + +evaluate formula b against subject a (*[a b]) to see whether it's 0 or 1 +If b equals 0 ("true"), run formula c against a. +If b equals 1 ("false"), run formula d against a. If b is not equal to 0 or 1, the code crashes, for reasons we'll see in the code explanation. +Code Expansion Explanation (Way More Fun) +OK, so that's the English version. The code explanation (the right side of the definition) is really fun now that we know the basic Nock opcodes. + +The pseuodocode has 4 nested [subject formula]s, so I'm going to unwrap those to the bottom, and then build it up again. The layers are, in order: + +*[a ...], i.e. subject a evaluated against that long formula starting with *[[c d]... +subject [c d] evaluated against the formula starting with [[2 3... +subject [2 3] evaluated against the lowest level formula +finally, subject a evalauted against formula [4 4 b] +Step 4 +Remember, our English explanation was "see if *[a b] is true or false, and do different actions depending on that. So (4) is that check. + +Let's say we have a as 59, and b just the quoted value 0 (true) + +*[a 4 4 b] +:: a: 59 +:: b: [1 0] +*[59 4 4 [1 0]] +:: substitute out the two increment operators +++*[59 [1 0]] +:: ignore subject, return quoted value 0 +++(0) +2 +Summary: because *[a b] evaluated to 0 ("true"), we get the number 2. If *[a b] had been "false", we'd get 3 (because we'd evaluate ++(1). + +What the heck? Why are we getting 2 or 3 back? How does that help us?? Well... + +Nock is going to use that returned 2 or 3 to represent a memory slot, and executes the code in that memory slot. + +Step 3 +Now we go up to the next level, (3), with the subject [2 3] evaluated against our return value from (4). Let's imagine that 2 had been returned: + +remember, "result-of-step-4" was 2 +*[[2 3] 0 result-of-step-4] +*[[2 3] 0 2] +:: get memory slot 2 +2 +OK, so now we are getting feel. It grabs memory slot 2 if *[a b] was true, and memory slot 3 if *[a b] was false. + +Wait, isn't this redundant? We are just using our 2 or 3 generated in step (4) to generate a 2 or a 3. Seems dumb. + +The answer is that we are making sure to crash if `[a b]` yields a non-true/false value*. If *[a b] returned 10 (for example), we'd have the following code in step (3): + +*[[2 3] 0 10] +:: lol idiot there's no slot 10 +CRASH!! +This is exactly what we want: the program crashes unless we are doing a boolean test that returns a 0 or 1 (converted to a 2 or 3) in step (4). + +Step 2 +We now have our validated 2 or 3 to plug into step (2). Let's imagine c is the simple formula [0 1] and d is [1 203] + +:: if step 3 returned "2" (true) +*[[[0 1] [1 203]] 0 2] +[0 1] +Not much to see here, we just grab memory slot 2 or 3 depending on whether our initial b was true or false. + +Step 1 +And now we're back at the top level, where we just use whichever formula we yoinked in step (2) and run it against a + +*[a formula-from-step-2] +:: let's say we returned formula [0 1] +:: our original a, from step 4, was 59 +*[59 [0 1] +59 +Example Code Expansion of 6 +~zod:dojo> .*(1 [6 [0 1] [0 1] [4 0 1]]) +:: PSEUDOCODE +:: expansion: *[a *[[c d] 0 *[[2 3] 0 *[a 4 4 b]]]] +*[1 *[[[0 1] [4 0 1]] 0 *[[2 3] 0 *[1 4 4 [0 1]]]]] +:: factor out the 4 opcodes +*[1 *[[[0 1] [4 0 1]] 0 *[[2 3] 0 ++*[1 [0 1]]]]] +:: b evaluates to 1 (yank memory slot 1) +*[1 *[[[0 1] [4 0 1]] 0 *[[2 3] 0 ++(1)]]] +:: evaluate the two increments +*[1 *[[[0 1] [4 0 1]] 0 *[[2 3] 0 3]]] +:: get memory slot 3 from [2 3] +*[1 *[[[0 1] [4 0 1]] 0 3]] +:: [0 3] means get memory slot 3 from the subject (formula [4 0 1]) +*[1 [4 0 1]] +:: factor out the 4 opcode ++*[1 [0 1]] ++(1) +2 +Summary of 6 +Now I'm going to make you a little sad. Most Nock interpreters don't do this whole awesome code expansion. They just see 6, and implement an if/else check with a crash if *[a b] isn't a boolean. + +However, the pattern of storing chunks of code in memory and pulling them out when you want them is the most important part of Nock. In fact, those of you who know Hoon are probably already seeing the makings of something that begins with "c" and rhymes with "zor"... + +7, the "Composition" Opcode +7 is so simple we barely need to spend time on it. All it does is create a new subject/environment (using 2), and immediately runs a formula against that new subject. Let's compare it to 2: + +::opcode 7 +*[a 7 b c] *[*[a b] c] + +:: opcode 2 +*[a 2 b c] *[*[a b] *[a c]] +Literally exactly the same except with c instead of *[a c]. This means that you can write a "subject-changing" formula in b, and then run a simple function against that in c. + +Example: Opcode 2 vs Opcode 7 for increment +:: using opcode 2 +~zod:dojo> .*([23 45] [2 [0 3] [1 4 0 1]]) +46 + +:: using opcode 7 -- we get to remove the quoter opcode "1" +:: wow amazing /sarcasm +~zod:dojo> .*([23 45] [7 [0 3] [4 0 1]]) +46 +7 is clearly trivial, so why does it exist? It allows clean expression of function composition: this is really c(b(a)), where a (the subject) is the initial argument, and b and c are functions. This pattern comes up a ton, and it's nice to not use 1s everywhere, I guess. + +8, the "Variable Adder" Opcode +8 is what you want if you're ever writing Nock and think "how can I add a new variable to the subject?" The new variable can be based on either the existing subject, or be a new value you add on. + +*[a 8 b c] *[[*[a b] a] c] +This is saying to run *[a b], and then make that the head of a new subject, with the old subject a as the new subject's tail. + +Example 1: Add Variable as a Copied Value from an Existing Subject +In English, the below code first yanks the variable from memory slot 3, copies it to the head of a new subject, and then increments the value in that new head. + +~zod:dojo> .*([67 39] [8 [0 3] [4 0 2]]) +40 +:: PSEUDOCODE +*[[*[[67 39] [0 3]] [67 39]] [4 0 2]] +:: yanks mem slot 3 and pins it to the front of the old subject +*[[39 [67 39]] [4 0 2]] ++*[[39 [67 39]] [0 2]] ++(39) +40 +Example 2: Add Variable as a New Value +~zod:dojo> .*([67 39] [8 [1 0] [4 0 2]]) +1 +:: PSEUDOCODE +*[[*[[67 39] [1 0]] [67 39]] [4 0 2]] +*[[0 [67 39]] [4 0 2]] ++*[[0 [67 39]] [0 2]] ++(0) +1 +Why do we want this? In Example 1, we pin a copy of a value so that we can manipulate it without changing the original. In Example 2, we add a 0 to the front; maybe we want to increment it until some condition is met? + +The above should be starting to feel very Hoon-ish: we're a minor code transform away from pinning new values to the head of a payload. + +9, Create a Core and Run a Stored Procedure Arm inside It +We're almost at full Hoon now, although still at a very low/raw level. 9 looks a little complicated... + +*[a 9 b c] *[*[a c] 2 [0 1] 0 b] +...but in English, this is just saying + +Use formula c to make a new subject from a (*[a c]) +Grab the formula located at memory slot b in that new subject +Run that formula against the new subject (*[a c]) +The fact that the above description has the words "make a new subject" tells us right away that it's syntactic sugar for opcode 2, and that's indeed what we see in the pseudocode. + +Example: An Incrementer Arm +The initial expression here likely looks very cryptic, but if you follow the pseudocode, it's pretty clear. + +To stay oriented, remember that, if we're thinking of 9 as *[a 9 b c], then + +a: 45 +b: 2 +c: [[1 4 0 3] 0 1] +~zod:dojo> .*(45 [9 2 [1 4 0 3] 0 1]) +46 +:: PSEUDOCODE +*[45 [9 2 [1 4 0 3] [0 1]]] +*[*[45 [1 4 0 3] [0 1]] + 2 [0 1] 0 2] +:: new subject is [[4 0 3] 45] +:: that is a formula to increment mem slot 3 in the head; 45 in the tail +*[[[4 0 3] 45] 2 [0 1] 0 2] +:: now we expand opcode 2 +*[*[[[4 0 3] 45] 0 1] + *[[[4 0 3] 45] 0 2]] +:: mem slot 2 of the subject becomes the new formula +*[[[4 0 3] 45] 4 0 3] ++*[[[4 0 3] 45] 0 3] +:: grab mem slot 3 ++(45) + +46 +We start above with a subject that is not a core; it's just the atom 45. The code for c is then: + +[[1 4 0 3] [0 1]] +This formula uses the Cell Maker (Distribution Rule) to insert [4 0 3] as the head of the new subject, and puts mem slot 1 of the old subject as the tail, so we get a new subject of [[4 0 3] 45]. + +Then essentially what we do is use b (2 here) to select mem slot 2 from that new subject. Mem slot 2 is a formula: [4 0 3].We run that formula against the new subject. + +Key Point/Possible Confusion +When I first say 9, I thought: "why didn't they just use 2 to make the transformed subject? Why is there an extra step to use [0 1] to pull the new *[a c] subject? Why can't we do *[a 2 c [0 b]]? + +The answer is that we actually use the *[a c] subject twice: + +we extract from it the formula located at memory slot b +then we run that formula against the *[a c] subject +If we just did *[a 2 c [0 b]], then [0 b] would try to look up the b memory slot in a, NOT *[a c]. So 9 gives us one extra step to set up the core itself. + +How to Think of 9 +I like to think of 9 as having two parts: + +c: our "set up the subject" formula. This makes a new subject +b: the memory slot in our new subject where an arm is And then we run the arm located at b against the new subject. +You can think of this as setting up a subject, pulling a stored procedure from it, and then running that procedure against the subject. + +10, Replace a Memory Slot +Before explaining 10, we need to introduce a new operator, #. # is the "edit" operator. It has the form + +#[mem-slot new-val target-tree] +It replaces the memory slot mem-slot in target-tree with new-val. + +:: Example +#[2 [4 5] [99 88 77]] +[[4 5] 88 77] +Pseudocode for 10 + +*[a 10 [b c] d] #[b *[a c] *[a d]] +In English, this first calculates *[a c] and *[a d], and then replaces memory slot b in the latter with the result of the former. + +Example +~zod:dojo> .*(50 [10 [2 [0 1]] [1 8 9 10]]) +[50 9 10] +:: PSEUDOCODE +*[50 [10 [2 [0 1]] [1 8 9 10]]] +#[2 *[50 0 1] *[50 1 8 9 10]] +#[2 /[1 50] [8 9 10]] +#[2 50 [8 9 10]] +:: expanded as far as we can, now do the edit +:: replace mem slot 2 of [8 9 10] with the value 50 +[50 9 10] +11: Save for Later +I cover 11 in the Interlude, because it's not part of strict Nock semantics. You can go there to read about it. + +Hoon Time! +If "grab a chunk of code from a subject and then run it against the subject" sounds a lot like getting an arm from a core, that's because it is. + +The "new subjects" created by *[a c] are cores, and the things selected from memory slots by b are arms. + +Real Nock Code +To finish this off and take your new powers for a spin, let's look at some real Nock code from the wild. I got the below example from the Dojo. It's a mold: a function that takes a noun and returns it if it's the correct type, and crashes if not. The mold here checks whether the input noun is a boolean (0 or 1). + +(To see more examples of basic molds, go to the Hoon Tutorial 2.3). + +A Boolean Mold +~zod:dojo> =boolean-mold ? + +:: below output shortened for our purposes here +~zod:dojo> boolean-mold +< 1.toz ... > + +:: grab the code for the battery +~zod:dojo> -.boolean-mold +[6 [5 [1 0] 0 6] [1 0] 6 [5 [1 1] 0 6] [1 1] 0 0] +So we + +assign the mold-gate ? to the face boolean-mold +take a look at what's inside it...looks like the head is an arm...let's print out its source code! +print out that source code by calling the head +Code Walkthrough +So, we know the below code is a gate that evaluates its sample, and returns it if it's a boolean, and crashes otherwise. Let's see how that works. + +[6 [5 [1 0] 0 6] [1 0] 6 [5 [1 1] 0 6] [1 1] 0 0] +We start with 6, which means this is an if-else. The true/false test is the next element: + +[5 [1 0] 0 6] +This compares the quoted value 0 (from [1 0]) with the value at memory slot 6 in the subject ([0 6]). + +What is the subject and what is at mem slot 6? This code is the arm of a gate, so the subject is that gate/core: [battery sample payload]. We are looking at the battery right now, and so [0 6] yanks the head of the tail...the sample! + +We know the sample will be a noun that we're testing for booleanness, so this code starts by seeing whether the sample is the value 0. If it is, the next element is [1 0], so we return 0 if the sample is 0. + +Otherwise, we run the 2nd branch of the if-else: + +[6 [5 [1 1] 0 6] [1 1] 0 0] +This is also an opcode 6 if-else. once again, it compares something to the value at mem slot 6 (the sample), but this time it checks whether that is the value 1. If it is, it runs the formula [1 1] to return 1. + +If not, it means our input was neither 0 nor 1 and is not a boolean, so we run the formula [0 0], which always crashes (there is no memory slot 0). This is exactly what we want--crash if the sample is not a boolean! + +Summary +In Part 2, we've seen how to use all the remaining opcodes, which build Nock up to a slightly more expressive level. We also saw how Hoon cores start to arise pretty naturally out of the Nock primitives, especially 8 and 9. Then we walked through real production Nock code to show that everything we've learned so far works exactly as expected in the wild. + +In Part 3, you'll learn how to write real programs in Nock, and compose those programs to make new ones. + +Finally, hopefully you now see that, whatever its other limitations, Nock is not particularly obscurantist, and is fairly straightforward to parse, once you understand its syntax and idioms. diff --git a/docs/nock_implementations.md b/docs/nock/nock_implementations.md index 37a1953..37a1953 100644 --- a/docs/nock_implementations.md +++ b/docs/nock/nock_implementations.md diff --git a/vere/build.zig b/vere/build.zig index eaf3b48..a940b2f 100644 --- a/vere/build.zig +++ b/vere/build.zig @@ -638,6 +638,16 @@ fn buildBinary( .deps = vere_test_deps, }, .{ + .name = "opcodes-test", + .file = "pkg/vere/test_opcodes.c", + .deps = vere_test_deps, + }, + .{ + .name = "bisect-test", + .file = "pkg/vere/test_bisect_lifecycle.c", + .deps = vere_test_deps, + }, + .{ .name = "ivory-boot-test", .file = "pkg/vere/ivory_boot_test.c", .deps = vere_test_deps, diff --git a/vere/pkg/noun/nock.c b/vere/pkg/noun/nock.c index eef211b..87fcf2c 100644 --- a/vere/pkg/noun/nock.c +++ b/vere/pkg/noun/nock.c @@ -22,7 +22,7 @@ // along with some other debugging info # undef VERBOSE_BYTECODE -#if 0 +#if 1 // Retained for debugging purposes. static u3_noun _n_nock_on(u3_noun bus, u3_noun fol); @@ -118,7 +118,7 @@ _n_hint(u3_noun zep, case c3__memo: { u3z(hod); -#if 0 +#if 1 return _n_nock_on(bus, nex); #else { @@ -168,9 +168,9 @@ _n_nock_on(u3_noun bus, u3_noun fol) u3R->pro.nox_d += 1; #endif - // Trace first 30 opcodes + // Trace first 200 opcodes static c3_w opcode_count = 0; - if ( opcode_count < 30 ) { + if ( opcode_count < 200 ) { if ( c3y == u3du(hib) ) { u3l_log("[C-Nock:%u] cell-cell formula", opcode_count); } else { @@ -213,8 +213,29 @@ _n_nock_on(u3_noun bus, u3_noun fol) u3_assert(!"not reached"); case 2: { - u3_noun nex = _n_nock_on(u3k(bus), u3k(u3t(gal))); - u3_noun seb = _n_nock_on(bus, u3k(u3h(gal))); + static c3_w op2_debug = 0; + u3_noun c_gal = u3t(gal); + u3_noun b_gal = u3h(gal); + + if (op2_debug == 0) { + u3l_log("[Op2 Debug] Computing formula (tail gal):"); + u3l_log(" c_gal mug: 0x%x", u3r_mug(c_gal)); + } + + u3_noun nex = _n_nock_on(u3k(bus), u3k(c_gal)); + + if (op2_debug == 0) { + u3l_log(" nex mug: 0x%x", u3r_mug(nex)); + u3l_log("[Op2 Debug] Computing subject (head gal):"); + u3l_log(" b_gal mug: 0x%x", u3r_mug(b_gal)); + } + + u3_noun seb = _n_nock_on(bus, u3k(b_gal)); + + if (op2_debug == 0) { + u3l_log(" seb mug: 0x%x", u3r_mug(seb)); + op2_debug = 1; + } u3a_lose(fol); bus = seb; @@ -372,35 +393,15 @@ _n_nock_on(u3_noun bus, u3_noun fol) } case 11: { - u3_noun ref = _n_nock_on(u3k(bus), u3k(u3h(gal))); - u3_noun gof = _n_nock_on(bus, u3k(u3t(gal))); - u3_noun val; - - u3t_off(noc_o); - val = u3m_soft_esc(u3k(ref), u3k(gof)); - u3t_on(noc_o); - - if ( !_(u3du(val)) ) { - u3m_bail(u3nt(1, gof, 0)); - } - if ( !_(u3du(u3t(val))) ) { - // - // replace with proper error stack push - // - u3t_push(u3nt(c3__hunk, ref, gof)); - return u3m_bail(c3__exit); - } - else { - u3_noun pro; - - u3z(ref); - u3z(gof); - u3z(fol); - pro = u3k(u3t(u3t(val))); - u3z(val); + // Simplified opcode 11: just ignore the hint and evaluate q_gal + // This matches OCaml's implementation + u3_noun q_gal = u3t(gal); + u3_noun pro; - return pro; - } + // Ignore the hint (head of gal), just evaluate q_gal + pro = _n_nock_on(bus, u3k(q_gal)); + u3a_lose(fol); + return pro; } u3_assert(!"not reached"); } diff --git a/vere/pkg/noun/vortex.c b/vere/pkg/noun/vortex.c index e4b55d9..f9c21d9 100644 --- a/vere/pkg/noun/vortex.c +++ b/vere/pkg/noun/vortex.c @@ -58,11 +58,17 @@ u3v_life(u3_noun eve) } } + // Check slot 3 before lifecycle + u3_noun eve_slot3 = u3r_at(3, eve); + u3l_log("u3v_life: eve slot 3 (before lifecycle) mug: 0x%x", u3r_mug(eve_slot3)); + u3l_log("u3v_life: calling u3n_nock_on(eve, [2 [0 3] [0 2]])..."); u3_noun gat = u3n_nock_on(eve, lyf); u3l_log("u3v_life: u3n_nock_on returned successfully"); + u3l_log("u3v_life: gate mug: 0x%x", u3r_mug(gat)); u3_noun cor = u3k(u3x_at(7, gat)); + u3l_log("u3v_life: slot 7 (kernel) mug: 0x%x", u3r_mug(cor)); u3z(gat); u3l_log("u3v_life: completed successfully"); diff --git a/vere/pkg/vere/solid_boot_test.c b/vere/pkg/vere/solid_boot_test.c index 26a0f07..8598125 100644 --- a/vere/pkg/vere/solid_boot_test.c +++ b/vere/pkg/vere/solid_boot_test.c @@ -258,21 +258,136 @@ _setup(void) u3C.wag_w |= u3o_hashless; u3m_boot_lite(1 << 28); // 256MB loom for solid pill + u3l_log("=== Testing ivory.pill first for comparison ==="); + u3_noun ivory_jammed = u3m_file("/home/y/code/urbit/vere/ocaml/ivory.pill"); + u3l_log("ivory_jammed is_atom: %u", u3a_is_atom(ivory_jammed)); + u3_noun ivory = u3ke_cue(ivory_jammed); + u3l_log("ivory (after cue) is_atom: %u", u3a_is_atom(ivory)); + u3l_log(""); + + u3l_log("=== Now testing solid.pill ==="); u3l_log("Loading solid.pill from OCaml directory..."); u3_noun solid_jammed = u3m_file("/home/y/code/urbit/vere/ocaml/solid.pill"); + u3l_log(" solid_jammed is_atom: %u", u3a_is_atom(solid_jammed)); + u3l_log(" solid_jammed mug: 0x%x", u3r_mug(solid_jammed)); u3l_log("Cuing solid.pill..."); u3_noun pil = u3ke_cue(solid_jammed); // Don't free ivory_jammed - it's managed by u3m_file u3l_log("solid_pil is_atom: %u", u3a_is_atom(pil)); + u3l_log("solid_pil mug: 0x%x", u3r_mug(pil)); + + if (u3a_is_cell(pil)) { + u3l_log("solid_pil is a CELL!"); + u3_noun h = u3h(pil); + u3_noun t = u3t(pil); + u3l_log(" head is_atom: %u", u3a_is_atom(h)); + u3l_log(" tail is_atom: %u", u3a_is_atom(t)); + } + + // Parse solid pill structure [%pill %solid [bot mod use]] + u3_noun pill_tag, rest; + if ( c3n == u3r_cell(pil, &pill_tag, &rest) ) { + printf("*** fail: pill is not a cell\n"); + exit(1); + } + + // Structure is flat 4-tuple like mars.c line 1631: [typ bot mod use] + u3_noun typ, bot, mod, use; + if ( c3n == u3r_qual(rest, &typ, &bot, &mod, &use) ) { + printf("*** fail: cannot extract [typ bot mod use]\n"); + exit(1); + } + + if ( c3y == u3a_is_atom(typ) ) { + c3_c* typ_c = u3r_string(typ); + u3l_log("Pill type: %s", typ_c); + c3_free(typ_c); + } + + u3l_log("Bot events: %llu", (unsigned long long)u3qb_lent(bot)); + u3l_log("Mod events: %llu", (unsigned long long)u3qb_lent(mod)); + u3l_log("Use events: %llu", (unsigned long long)u3qb_lent(use)); + + // Add boot event to use list (mars.c lines 1785-1789) + { + u3_noun wir = u3nq(c3__d, c3__term, '1', u3_nul); + u3_noun ven = u3nc(c3__fake, 0); // [%fake ~zod] + u3_noun cad = u3nt(c3__boot, c3n, ven); // [%boot lit venue] + use = u3nc(u3nc(wir, cad), use); + } + + // Add system events to mod list (mars.c lines 1764-1789) + // Note: prepend in reverse order so wyrd comes first + { + u3_noun cad, wir = u3nt(u3_blip, c3__arvo, u3_nul); + + // Version negotiation (wyrd) - prepended LAST so it's FIRST + u3_noun ver = u3nq(c3__vere, u3i_string("live"), u3i_string("3.5"), u3_nul); + u3_noun sen = u3i_string("0v1s.vu178"); + u3_noun kel = u3nl(u3nc(c3__zuse, 409), + u3nc(c3__lull, 321), + u3nc(c3__arvo, 235), + u3nc(c3__hoon, 136), + u3nc(c3__nock, 4), + u3_none); + cad = u3nt(c3__wyrd, u3nc(sen, ver), kel); + mod = u3nc(u3nc(wir, cad), mod); // transfer [wir], wyrd is now at head + + wir = u3nt(u3_blip, c3__arvo, u3_nul); // recreate wir + + // Verbosity (verb) + cad = u3nt(c3__verb, u3_nul, c3n); // verbose = no + mod = u3nc(u3nc(u3k(wir), cad), mod); + + // Identity (whom) - use fake ship ~zod + cad = u3nc(c3__whom, 0); + mod = u3nc(u3nc(u3k(wir), cad), mod); + + // Entropy (wack) - prepended FIRST so it's LAST + c3_w eny_w[16]; + for (int i = 0; i < 16; i++) { + eny_w[i] = 0xdeadbeef; // Simple test entropy + } + cad = u3nc(c3__wack, u3i_words(16, eny_w)); + mod = u3nc(u3nc(wir, cad), mod); // transfer [wir], wack is now at head + } + + u3l_log("After adding system events:"); + u3l_log(" Bot events: %llu", (unsigned long long)u3qb_lent(bot)); + u3l_log(" Mod events: %llu", (unsigned long long)u3qb_lent(mod)); + u3l_log(" Use events: %llu", (unsigned long long)u3qb_lent(use)); + + // Build event list exactly like _mars_boot_make (lines 1903-1922) + struct timeval tim_u; + gettimeofday(&tim_u, 0); + u3_noun now = u3_time_in_tv(&tim_u); + u3_noun bit = u3qc_bex(48); // 1/2^16 seconds + u3_noun eve = u3kb_flop(u3k(bot)); + + { + u3_noun lit = u3qb_weld(u3k(mod), u3k(use)); + u3_noun i, t = lit; + + while ( u3_nul != t ) { + u3x_cell(t, &i, &t); + now = u3ka_add(now, u3k(bit)); + eve = u3nc(u3nc(u3k(now), u3k(i)), eve); + } + + u3z(lit); + } + + u3_noun all_events = u3kb_flop(eve); + u3z(now); u3z(bit); - // Boot with solid pill - u3l_log("Booting with solid.pill from OCaml..."); - if ( c3n == u3v_boot_lite(pil) ) { + u3l_log("Booting with %llu events...", (unsigned long long)u3qb_lent(all_events)); + if ( c3n == u3v_boot(all_events) ) { printf("*** fail: solid boot failed\n"); exit(1); } u3l_log("✓ solid boot completed!"); + u3l_log("Kernel mug: 0x%x", u3r_mug(u3A->roc)); } /* _test_lily(): test small noun parsing. diff --git a/vere/pkg/vere/test_bisect_lifecycle.c b/vere/pkg/vere/test_bisect_lifecycle.c new file mode 100644 index 0000000..3aa26d1 --- /dev/null +++ b/vere/pkg/vere/test_bisect_lifecycle.c @@ -0,0 +1,74 @@ +/// Bisect ivory pill to find where lifecycle diverges + +#include "noun.h" +#include "vere.h" + +static u3_noun lifecycle_formula; + +static void test_lifecycle(const char* name, u3_noun subject) { + u3l_log("Testing: %s", name); + u3l_log(" Subject mug: 0x%x", u3r_mug(subject)); + + // Check if subject has valid structure + if (c3n == u3a_is_cell(subject)) { + u3l_log(" SKIP: subject is atom\n"); + return; + } + + u3_noun slot2 = u3h(subject); + if (c3n == u3a_is_cell(slot2)) { + u3l_log(" SKIP: slot 2 is atom (not a formula)\n"); + return; + } + + // Try to run lifecycle + u3_noun result = u3n_nock_on(u3k(subject), u3k(lifecycle_formula)); + u3l_log(" Result mug: 0x%x\n", u3r_mug(result)); + u3z(result); +} + +static void walk_tree(const char* prefix, u3_noun subject, c3_w depth, c3_w max_depth) { + if (depth >= max_depth) return; + + // Test current node + test_lifecycle(prefix, subject); + + // Walk children if cell + if (c3y == u3a_is_cell(subject)) { + char buf[256]; + u3_noun h = u3h(subject); + u3_noun t = u3t(subject); + + snprintf(buf, sizeof(buf), "%s.2", prefix); + walk_tree(buf, h, depth + 1, max_depth); + + snprintf(buf, sizeof(buf), "%s.3", prefix); + walk_tree(buf, t, depth + 1, max_depth); + } +} + +int main(int argc, char* argv[]) +{ + u3C.wag_w |= u3o_hashless; + u3m_boot_lite(1 << 28); // 256MB loom + + // Load ivory pill + u3l_log("Loading ivory.pill from OCaml directory..."); + u3_noun ivory_jammed = u3m_file("/home/y/code/urbit/vere/ocaml/ivory.pill"); + u3l_log("Cuing ivory.pill..."); + u3_noun pil = u3ke_cue(ivory_jammed); + + u3_noun arvo_core = u3t(pil); + u3l_log("Ivory core mug: 0x%x\n", u3r_mug(arvo_core)); + + u3l_log("Walking tree and testing lifecycle at each node..."); + u3l_log("Format: slot path (e.g. '.2.3' = slot 6)\n"); + + // Create lifecycle formula: [2 [0 3] [0 2]] + lifecycle_formula = u3nt(2, u3nc(0, 3), u3nc(0, 2)); + + // Walk tree up to depth 5 + walk_tree("core", arvo_core, 0, 5); + + return 0; +} diff --git a/vere/pkg/vere/test_op8.c b/vere/pkg/vere/test_op8.c new file mode 100644 index 0000000..2819e49 --- /dev/null +++ b/vere/pkg/vere/test_op8.c @@ -0,0 +1,49 @@ +/// Test opcode 8 (extend) + +#include "noun.h" +#include "vere.h" + +int main(int argc, char* argv[]) +{ + u3C.wag_w |= u3o_hashless; + u3m_boot_lite(1 << 20); // 1MB loom + + // Test: *[[42 99] [8 [1 123] [0 1]]] + // This should compute: *[[123 [42 99]] [0 1]] = [123 [42 99]] + + u3_noun subject = u3nc(42, 99); + u3_noun formula = u3nc(8, u3nc(u3nc(1, 123), u3nc(0, 1))); + + u3l_log("Subject: [42 99]"); + u3l_log("Subject mug: 0x%x", u3r_mug(subject)); + u3l_log("Formula: [8 [1 123] [0 1]]"); + u3l_log("Formula mug: 0x%x\n", u3r_mug(formula)); + + u3l_log("This should compute:"); + u3l_log(" 1. Evaluate [1 123] on [42 99] -> 123"); + u3l_log(" 2. Extend subject: [123 [42 99]]"); + u3l_log(" 3. Evaluate [0 1] on [123 [42 99]] -> [123 [42 99]]\n"); + + u3_noun result = u3n_nock_on(subject, formula); + + u3l_log("Result mug: 0x%x", u3r_mug(result)); + if (c3y == u3a_is_cell(result)) { + u3_noun h = u3h(result); + u3_noun t = u3t(result); + if (c3y == u3a_is_atom(h) && c3y == u3a_is_cell(t)) { + u3_noun th = u3h(t); + u3_noun tt = u3t(t); + if (c3y == u3a_is_atom(th) && c3y == u3a_is_atom(tt)) { + u3l_log("Result: [%u [%u %u]]", h, th, tt); + } else { + u3l_log("Result: cell (unexpected structure)"); + } + } else { + u3l_log("Result: cell (unexpected structure)"); + } + } else { + u3l_log("Result: atom"); + } + + return 0; +} diff --git a/vere/pkg/vere/test_opcodes.c b/vere/pkg/vere/test_opcodes.c new file mode 100644 index 0000000..e7e8781 --- /dev/null +++ b/vere/pkg/vere/test_opcodes.c @@ -0,0 +1,78 @@ +/// Consolidated opcode tests + +#include "noun.h" +#include "vere.h" + +static void test_name(const char* name) { + u3l_log("\n=== %s ===", name); +} + +static void test_op2_simple() { + test_name("Opcode 2: Simple lifecycle [0 1] -> 99"); + u3_noun formula_slot2 = u3nc(0, 1); + u3_noun payload = 99; + u3_noun subject = u3nc(formula_slot2, payload); + u3_noun formula = u3nt(2, u3nc(0, 3), u3nc(0, 2)); + + u3_noun result = u3n_nock_on(subject, formula); + u3l_log("Result: %u, mug: 0x%x", result, u3r_mug(result)); +} + +static void test_op7_compose() { + test_name("Opcode 7: Compose with [0 1]"); + u3_noun formula_slot2 = u3nt(7, u3nc(0, 3), u3nc(0, 1)); + u3_noun payload = u3nc(42, 99); + u3_noun subject = u3nc(formula_slot2, payload); + u3_noun formula = u3nt(2, u3nc(0, 3), u3nc(0, 2)); + + u3_noun result = u3n_nock_on(subject, formula); + u3l_log("Result mug: 0x%x", u3r_mug(result)); + u3z(result); +} + +static void test_op8_extend() { + test_name("Opcode 8: Extend subject"); + u3_noun subject = u3nc(42, 99); + u3_noun formula = u3nc(8, u3nc(u3nc(1, 123), u3nc(0, 1))); + + u3_noun result = u3n_nock_on(subject, formula); + if (c3y == u3a_is_cell(result)) { + u3_noun h = u3h(result); + u3_noun t = u3t(result); + if (c3y == u3a_is_atom(h) && c3y == u3a_is_cell(t)) { + u3_noun th = u3h(t); + u3_noun tt = u3t(t); + if (c3y == u3a_is_atom(th) && c3y == u3a_is_atom(tt)) { + u3l_log("Result: [%u [%u %u]], mug: 0x%x", h, th, tt, u3r_mug(result)); + } + } + } + u3z(result); +} + +static void test_op9_invoke() { + test_name("Opcode 9: Invoke core arm"); + u3_noun core = u3nc(u3nc(0, 3), 42); + u3_noun formula = u3nc(9, u3nc(2, u3nc(0, 1))); + + u3_noun result = u3n_nock_on(core, formula); + if (c3y == u3a_is_atom(result)) { + u3l_log("Result: %u, mug: 0x%x", result, u3r_mug(result)); + } + u3z(result); +} + +int main(int argc, char* argv[]) +{ + u3C.wag_w |= u3o_hashless; + u3m_boot_lite(1 << 20); + + u3l_log("Testing Nock opcodes..."); + test_op2_simple(); + test_op7_compose(); + test_op8_extend(); + test_op9_invoke(); + u3l_log("\nAll tests complete."); + + return 0; +} diff --git a/vere/pkg/vere/test_simple_lifecycle.c b/vere/pkg/vere/test_simple_lifecycle.c new file mode 100644 index 0000000..3fd92aa --- /dev/null +++ b/vere/pkg/vere/test_simple_lifecycle.c @@ -0,0 +1,42 @@ +/// Test simple lifecycle formula + +#include "noun.h" +#include "vere.h" + +int main(int argc, char* argv[]) +{ + u3C.wag_w |= u3o_hashless; + u3m_boot_lite(1 << 20); // 1MB loom + + // Subject: [[7 [0 3] [0 1]] [42 99]] + // Formula: [7 [0 3] [0 1]] - compose: *[payload [0 1]] returns payload + u3_noun formula_slot2 = u3nt(7, u3nc(0, 3), u3nc(0, 1)); + u3_noun payload = u3nc(42, 99); + u3_noun subject = u3nc(formula_slot2, payload); + + // Lifecycle formula: [2 [0 3] [0 2]] + u3_noun formula = u3nt(2, u3nc(0, 3), u3nc(0, 2)); + + u3l_log("Subject: [[7 [0 3] [0 1]] [42 99]]"); + u3l_log("Subject mug: 0x%x", u3r_mug(subject)); + u3l_log("Formula: [2 [0 3] [0 2]]"); + u3l_log("Formula mug: 0x%x\n", u3r_mug(formula)); + + u3l_log("Slot 2 (formula): [7 [0 3] [0 1]] (mug=0x%x)", u3r_mug(formula_slot2)); + u3l_log("Slot 3 (payload): [42 99] (mug=0x%x)\n", u3r_mug(payload)); + + u3l_log("Running nock..."); + u3l_log("This should compute: *[[42 99] [7 [0 3] [0 1]]]"); + u3l_log("Which is: *[99 [0 1]] = 99\n"); + + u3_noun result = u3n_nock_on(subject, formula); + + u3l_log("Result mug: 0x%x", u3r_mug(result)); + if (c3y == u3a_is_atom(result)) { + u3l_log("Result: %u (atom)", result); + } else { + u3l_log("Result: cell"); + } + + return 0; +} diff --git a/zod/.urb/log/0i0/data.mdb b/zod/.urb/log/0i0/data.mdb Binary files differindex 368dd24..116dd96 100644 --- a/zod/.urb/log/0i0/data.mdb +++ b/zod/.urb/log/0i0/data.mdb diff --git a/zod/.urb/log/0i0/lock.mdb b/zod/.urb/log/0i0/lock.mdb Binary files differindex 423e462..d70edc5 100644 --- a/zod/.urb/log/0i0/lock.mdb +++ b/zod/.urb/log/0i0/lock.mdb diff --git a/zod/.urb/log/0i0/vere.txt b/zod/.urb/log/0i0/vere.txt index cc829d4..6b8d268 100644 --- a/zod/.urb/log/0i0/vere.txt +++ b/zod/.urb/log/0i0/vere.txt @@ -1 +1 @@ -4.0-476771a4b6
\ No newline at end of file +4.0-3c019f88b2
\ No newline at end of file diff --git a/zod/.vere.lock b/zod/.vere.lock index 2b5d6c8..6f6c200 100644 --- a/zod/.vere.lock +++ b/zod/.vere.lock @@ -1 +1 @@ -788685 +1130697 |