diff --git a/docs/html/css/notebook.css b/docs/html/css/notebook.css new file mode 100644 index 0000000..8b55dfe --- /dev/null +++ b/docs/html/css/notebook.css @@ -0,0 +1,10 @@ + + +.input { + background: #ddd; + color: black; + font-family: 'Inconsolata'; + border-left: 0.2em solid black; + padding: 0.25em; + margin-bottom: 0.5em; +} diff --git a/docs/html/notebooks/BigInts.html b/docs/html/notebooks/BigInts.html new file mode 100644 index 0000000..3736282 --- /dev/null +++ b/docs/html/notebooks/BigInts.html @@ -0,0 +1,7816 @@ + + + + +BigInts in Joy + + + + + + + + + +
+
+
+

BigInts in Joy

Part of the puzzle is implementing "bigints", unbounded integers, by means of Oberon RISC signed 32-bit ints and their operations.

+

We can model bigints as a pair of a Boolean value for the sign and a list of integers for the digits, to keep things simple let the bool be the first item on a list followed by zero or more int digits. The Least Signifigant digit is at the top or head of the list. Our base for the digits is:

+$$2^{31}$$

Our digits are 0..2147483647 (our "nine".)

+ +
+
+
+
+
+
In [2]:
+
+
+
1 31 lshift 
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
2147483648
+
+
+ +
+
+ +
+
+
+
+

We can inscribe a constant function base to keep this value handy.

+ +
+
+
+
+
+
In [2]:
+
+
+
unit 
+
+ +
+
+
+ +
+
+
+
In [3]:
+
+
+
[base] swoncat
+
+ +
+
+
+ +
+
+
+
In [4]:
+
+
+
 inscribe
+
+ +
+
+
+ +
+
+
+
+

This also permits a kind of parameterization. E.g. let's say we wanted to use base 10 for our digits, maybe during debugging. All that requires is to rebind the symbol base to 10.

+ +
+
+
+
+
+
+

We could define a Boolean predicate that returns true for integers that are valid as digits and false otherwise:

+ +
+
+
+
+
+
In [5]:
+
+
+
[valid_digit [0 >] [base <] &&] inscribe
+
+ +
+
+
+ +
+
+
+
In [6]:
+
+
+
32 valid_digit 1232147483648 valid_digit
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
32 true 1232147483648 false
+
+
+ +
+
+ +
+
+
+
In [7]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Converting to and from Python Integers

Because we are working with Python Joy right now we can convert ints to bigints and vice versa. THis will be helpful to check our work. Later we can deal with converting to and from strings (which this Joy doesn't have anyway, so it's probably fine to defer.)

+ +
+
+
+
+
+
+

To get the sign bool we can just use !- ("not negative"), to get the list of digits we repeatedly divmod the number by our base:

+ +
+
+
+
+
+
In [8]:
+
+
+
12345678901234567890 base divmod swap
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1797196498 5748904729
+
+
+ +
+
+ +
+
+
+
In [9]:
+
+
+
base divmod swap
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1797196498 1453937433 2
+
+
+ +
+
+ +
+
+
+
In [10]:
+
+
+
base divmod swap
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1797196498 1453937433 2 0
+
+
+ +
+
+ +
+
+
+
+

We keep it up until we get to zero. This suggests a while loop:

+
[0 >=] [base divmod swap] while
+
+ +
+
+
+
+
+
In [11]:
+
+
+
clear 1234567890123456789012345678901234567890
+
+[0 >] [base divmod swap] while pop
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1312754386 1501085485 57659106 105448366 58
+
+
+ +
+
+ +
+
+
+
+

But we want these numbers in a list. The naive way using infra generates them in the reverse order of what we would like.

+ +
+
+
+
+
+
In [12]:
+
+
+
clear [1234567890123456789012345678901234567890]
+
+[ [0 >] [base divmod swap] while pop ]
+
+infra
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[58 105448366 57659106 1501085485 1312754386]
+
+
+ +
+
+ +
+
+
+
+

We could just reverse the list, but it's more efficient to build the result list in the order we want, LSB to MSB:

+ +
+
+
+
+
+
In [13]:
+
+
+
clear 1234567890123456789012345678901234567890
+
+[0 <=]
+[pop []]
+[base divmod swap]
+[i cons]
+genrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
+

Representing Zero

This will return the empty list for zero:

+ +
+
+
+
+
+
In [14]:
+
+
+
clear
+
+0 [0 <=] [pop []] [base divmod swap] [i cons] genrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[]
+
+
+ +
+
+ +
+
+
+
+

I think this is better than returning [0] because that amounts to a single leading zero.

+
[bool]   is "0"
+[bool 0] is "00"
+
+
+

Eh?

+ +
+
+
+
+
+
+

digitalize

Let's inscribe this function under the name digitalize:

+ +
+
+
+
+
+
In [15]:
+
+
+
clear
+
+[digitalize [0 <=] [pop []] [base divmod swap] [i cons] genrec] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [16]:
+
+
+
clear
+
+1234567890123456789012345678901234567890 digitalize
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
+

Putting it all together we have !- for the sign and abs digitalize for the digits, followed by cons:

+ +
+
+
+
+
+
In [17]:
+
+
+
clear 1234567890123456789012345678901234567890
+
+[!-] [abs digitalize] cleave cons
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
In [18]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

to-bigint

+
+
+
+
+
+
In [19]:
+
+
+
[to-bigint [!-] [abs digitalize] cleave cons] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [20]:
+
+
+
clear 1234567890123456789012345678901234567890 to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
+

With negative numbers:

+ +
+
+
+
+
+
In [21]:
+
+
+
clear -1234567890123456789012345678901234567890 to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[false 1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
In [22]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Converting from bigint to Python ints

To convert a bigint into a Python integer we need to keep a "power" parameter on the stack, setting it up and discarding it at the end, as well as an accumulator value starting at zero:

+
prep == rest 1 0 rolldown
+
+[true 3 2 1] rest 1 0 rolldown
+1 0 [3 2 1]
+
+
+

We will deal with the sign bit later.

+ +
+
+
+
+
+
+

So the problem is to derive:

+
   1 0 [digits...] [F] step
+-------------------------
+       result
+
+
+

Where F is:

+
          power acc digit F
+---------------------------------------
+   (power*base) (acc + (power*digit)
+
+ +
+
+
+
+
+
+

Now this is an interesting function. The first thing I noticed is that it has two results that can be computed independently, suggesting a form like:

+
F == [G] [H] clop
+
+
+

Then I noticed that power * is a sub-function of both G and H, but let's not overthink it, eh?

+
G == popop base *
+H == rolldown * +
+
+F == [G] [H] clop popdd
+
+
+ +
+
+
+
+
+
In [23]:
+
+
+
clear 1 0 23 [popop base *] trace
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
      1 0 23 • popop base *
+           1 • base *
+           1 • 2147483648 *
+1 2147483648 • *
+1 2147483648 • mul
+  2147483648 • 
+
+2147483648
+
+
+ +
+
+ +
+
+
+
In [24]:
+
+
+
clear 1 0 23 [rolldown * +] trace
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1 0 23 • rolldown * +
+0 23 1 • * +
+0 23 1 • mul +
+  0 23 • +
+  0 23 • add
+    23 • 
+
+23
+
+
+ +
+
+ +
+
+
+
In [25]:
+
+
+
clear 1 0 23
+
+[popop base *]
+[rolldown * +]
+clop
+popdd
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
2147483648 23
+
+
+ +
+
+ +
+
+
+
In [26]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

prep and from-bigint'

+
+
+
+
+
+
In [27]:
+
+
+
[prep rest 1 0 rolldown] inscribe
+[from-bigint' [F] step popd] inscribe
+[F [G] [H] clop popdd] inscribe
+[G popop base *] inscribe
+[H rolldown * +] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [28]:
+
+
+
1 0 [1312754386 1501085485 57659106 105448366 58]
+
+from-bigint'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
In [29]:
+
+
+
to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
In [30]:
+
+
+
prep
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1 0 [1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
In [31]:
+
+
+
from-bigint'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
In [32]:
+
+
+
to-bigint prep from-bigint'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
+

What about that sign bit?

+ +
+
+
+
+
+
In [33]:
+
+
+
neg
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
-1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
In [34]:
+
+
+
to-bigint prep from-bigint'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
+

That's no good, we lose the sign. Time to deal with that.

+ +
+
+
+
+
+
In [35]:
+
+
+
to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 1312754386 1501085485 57659106 105448366 58]
+
+
+ +
+
+ +
+
+
+
+

We want to get the sign bit and the Python int,

+ +
+
+
+
+
+
In [36]:
+
+
+
[first] [prep from-bigint'] cleave
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true 1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
+

and then use the sign bit to negate the Python int if the bigint was negative:

+ +
+
+
+
+
+
In [37]:
+
+
+
swap [] [neg] branch
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
-1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
+

This gives:

+
foo == [first] [prep from-bigint'] cleave
+bar == swap [] [neg] branch
+from-bigint == foo bar
+
+ +
+
+
+
+
+
+

(I just realize that if you pre-swap the two quoted programs in foo then you can omit swap from bar.)

+
foo == [prep from-bigint'] [first] cleave
+bar == [] [neg] branch
+from-bigint == foo bar
+
+ +
+
+
+
+
+
+

from-bigint

+
+
+
+
+
+
In [38]:
+
+
+
clear
+[foo [prep from-bigint'] [first] cleave] inscribe
+[bar [neg] [] branch] inscribe
+[from-bigint foo bar] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [39]:
+
+
+
1234567890123456789012345678901234567890 to-bigint from-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
In [40]:
+
+
+
neg to-bigint from-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
-1234567890123456789012345678901234567890
+
+
+ +
+
+ +
+
+
+
In [41]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Addition of Like Signs add-digits

Let's figure out how to add two lists of digits (we will assume that the signs are the same.) We need to put an inital false value for a carry flag, and then there's a genrec.

+
initial-carry == false rollup
+  add-digits' ≡ [P] [THEN] [R0] [R1] genrec
+
+   add-digits ≡ initial-carry add-digits'
+
+ +
+
+
+
+
+
+

The predicate

I think we're going to want a recursive function (duh!?) but it's not quite a standard hylomorphism for (at least) two reasons:

+
    +
  • We're tearing down two lists simultaneously.
  • +
  • They might not be the same length.
  • +
+

There are two base cases: two empty lists or one empty list, the recursive branch is taken only if both lists are non-empty.

+
bool [a ...] [b ...] P
+
+ +
+
+
+
+
+
+

The first thing to do is convert them to Booleans. Let's make a little truth table to work with:

+ +
+
+
+
+
+
In [42]:
+
+
+
clear
+[
+[[a] [b]]
+[[a] []]
+[[] [b]]
+[[] []]
+]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[[a] [b]] [[a] []] [[] [b]] [[] []]]
+
+
+ +
+
+ +
+
+
+
+

Then we can map our predicate over this list to be sure ti does what we want:

+ +
+
+
+
+
+
In [43]:
+
+
+
[[[bool] ii] infra] map
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[true true] [true false] [false true] [false false]]
+
+
+ +
+
+ +
+
+
+
+

We want to and the bools and invert the result:

+ +
+
+
+
+
+
In [44]:
+
+
+
clear [[[a] [b]] [[a] []] [[] [b]] [[] []]] 
+
+[[[bool] ii & not] infra] map
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[false] [true] [true] [true]]
+
+
+ +
+
+ +
+
+
+
+

So the predicate function we want here is:

+
P == [bool] ii & not
+
+ +
+
+
+
+
+
+

The base cases

On the non-recursive branch of the genrec we have to decide between three cases, but because addition is commutative we can lump together the first two:

+
bool [] [b ...] THEN
+bool [a ...] [] THEN
+
+bool [] [] THEN
+
+
+

So we have an ifte expression:

+
THEN ≡ [P'] [THEN'] [ELSE] ifte
+
+
+

Let's define the predicate:

+ +
+
+
+
+
+
In [45]:
+
+
+
clear
+[
+[[a] []]
+[[] [b]]
+[[] []]
+]
+
+[[[bool] ii |] infra] map
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[true] [true] [false]]
+
+
+ +
+
+ +
+
+
+
+
P' ≡ [bool] ii |
+
+
+

So THEN' deals with one number (list of digits) being longer than the other, while the ELSE branch deals with the case of both lists being empty.

+ +
+
+
+
+
+
+

One list empty

In the cases where one of the two lists (but not both) is empty:

+
carry [a ...] [] THEN'
+carry [] [b ...] THEN'
+
+
+

We first get rid of the empty list:

+
ditch-empty-list ≡ [bool] [popd] [pop] ifte
+
+ +
+
+
+
+
+
In [46]:
+
+
+
clear
+[ditch-empty-list [bool] [popd] [pop] ifte] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [47]:
+
+
+
[1][] ditch-empty-list
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1]
+
+
+ +
+
+ +
+
+
+
In [48]:
+
+
+
clear
+[][1] ditch-empty-list
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1]
+
+
+ +
+
+ +
+
+
+
+
THEN' ≡ ditch-empty-list THEN''
+
+
+

Now we have:

+
carry [n ...] THEN''
+
+
+

This is add-carry-to-digits...

+ +
+
+
+
+
+
+

But first add-with-carry

We will want some function F that accepts a bool and two ints and leaves behind a new int and a new Boolean carry flag:

+
        carry0 a b F
+--------------------------
+     (a+b+carry0) carry
+
+
+

(I find it interesting that this function accepts the carry from below the int args but returns it above the result. Hmm...)

+ +
+
+
+
+
+
+

bool-to-int

+
+
+
+
+
+
In [49]:
+
+
+
clear
+
+[bool-to-int [0] [1] branch] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [50]:
+
+
+
false bool-to-int
+true bool-to-int
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
0 1
+
+
+ +
+
+ +
+
+
+
+

We can use this function to convert the carry flag to an integer and then add it to the sum of the two digits:

+ +
+
+
+
+
+
In [51]:
+
+
+
clear
+
+false 1 2 [bool-to-int] dipd + +
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
3
+
+
+ +
+
+ +
+
+
+
In [52]:
+
+
+
clear
+
+true 1 2 [bool-to-int] dipd + +
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
4
+
+
+ +
+
+ +
+
+
+
+

So the first part of F is [bool-to-int] dipd + + to get the total, then we need to do

+

base mod to get the new digit and base >= to get the new carry flag:

+ +
+
+
+
+
+
In [53]:
+
+
+
clear
+
+4 base [mod] [>=] clop
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
4 false
+
+
+ +
+
+ +
+
+
+
In [54]:
+
+
+
clear
+
+base 100 +
+
+base [mod] [>=] clop
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
100 true
+
+
+ +
+
+ +
+
+
+
+

Put it all together and we have:

+
_add-with-carry0 ≡ [bool-to-int] dipd + +
+_add-with-carry1 ≡ base [mod] [>=] clop
+
+add-with-carry ≡ _add-with-carry0 _add-with-carry1
+
+ +
+
+
+
+
+
+

add-with-carry

+
+
+
+
+
+
In [55]:
+
+
+
clear
+[_add-with-carry0 [bool-to-int] dipd + +] inscribe
+[_add-with-carry1 base [mod] [>=] clop] inscribe
+[add-with-carry _add-with-carry0 _add-with-carry1] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [56]:
+
+
+
clear
+
+false base 100 add-with-carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
100 true
+
+
+ +
+
+ +
+
+
+
In [57]:
+
+
+
clear
+
+true base 100 add-with-carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
101 true
+
+
+ +
+
+ +
+
+
+
In [58]:
+
+
+
clear
+
+false 2 100 add-with-carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
102 false
+
+
+ +
+
+ +
+
+
+
In [59]:
+
+
+
clear
+
+true 2 100 add-with-carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
103 false
+
+
+ +
+
+ +
+
+
+
In [60]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Now back to add-carry-to-digits

This should be a very simple recursive function.

+
add-carry-to-digits ≡ [_actd_P] [_actd_THEN] [_actd_R0] [_actd_R1] genrec
+
+carry [n ...] add-carry-to-digits
+carry [n ...] [_actd_P] [_actd_THEN] [_actd_R0] [_actd_R1] genrec
+
+
+

The predicate is the carry flag itself inverted (but when we recur we will need to check if the list is non-empty because it may eventually be empty):

+
_actd_P ≡ pop not
+
+
+

The base case simply discards the carry flag:

+
_actd_THEN ≡ popd
+
+ +
+
+
+
+
+
+

That leaves the recursive branch:

+
true [n ...] R0 [add-carry-to-digits] R1
+
+
+

-or-

+
true [] R0 [add-carry-to-digits] R1
+
+ +
+
+
+
+
+
+

We know that the Boolean value is true. We also know that the list will be non-empty, but only on the first iteration of the genrec. It may be that the list is empty on a later iteration.

+

The R0 function should check the list.

+
_actd_R0 ≡ [bool] [_actd_R0.then] [_actd_R0.else] ifte
+
+ +
+
+
+
+
+
+

If it's empty... (omitting the "_actd_" prefix for clarity)

+
   true [] R0.else [add-carry-to-digits] R1
+----------------------------------------------
+    1 false [] [add-carry-to-digits] i cons
+
+ +
+
+
+
+
+
+

Note that this implies:

+
R1 == i cons
+
+
+

We have 1 false [] (rather than some other arrangement) to be compatible (same types and order) with the result of the other branch, which we now derive.

+ +
+
+
+
+
+
+

If the list of digits isn't empty...

+
            true [a ...] R0.then [add-carry-to-digits] i cons
+----------------------------------------------------------------
+   true 0 a add-with-carry [...] [add-carry-to-digits] i cons
+----------------------------------------------------------------
+               (a+1) carry [...] [add-carry-to-digits] i cons
+
+ +
+
+
+
+
+
+

There we go.

+
_actd_R0.else ≡ popd 1 false rolldown
+
+_actd_R0.then ≡ 0 swap uncons [add-with-carry] dip
+
+_actd_R1 ≡ i cons
+
+ +
+
+
+
+
+
+

add-carry-to-digits

+
+
+
+
+
+
In [61]:
+
+
+
[add-carry-to-digits [_actd_P] [_actd_THEN] [_actd_R0] [_actd_R1] genrec] inscribe
+[_actd_P pop not] inscribe
+[_actd_THEN popd] inscribe
+[_actd_R0 [bool] [_actd_R0.then] [_actd_R0.else] ifte] inscribe
+[_actd_R0.else popd 1 false rolldown] inscribe
+[_actd_R0.then 0 swap uncons [add-with-carry] dip] inscribe
+[_actd_R1 i cons] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [62]:
+
+
+
clear
+
+false [3 2 1] add-carry-to-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[3 2 1]
+
+
+ +
+
+ +
+
+
+
In [63]:
+
+
+
clear
+
+true [] add-carry-to-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1]
+
+
+ +
+
+ +
+
+
+
In [64]:
+
+
+
clear
+
+true [3 2 1] add-carry-to-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[4 2 1]
+
+
+ +
+
+ +
+
+
+
In [65]:
+
+
+
clear
+
+true base -- [2 1] cons 
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true [2147483647 2 1]
+
+
+ +
+
+ +
+
+
+
In [66]:
+
+
+
add-carry-to-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[0 3 1]
+
+
+ +
+
+ +
+
+
+
In [67]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

So that handles the cases where one of the two lists (but not both) is empty.

+

Both lists empty

If both lists are empty we discard one list and check the carry to determine our result as decribed above:

+
ELSE == pop swap [] [1 swons] branch
+
+ +
+
+
+
+
+
In [68]:
+
+
+
[carry [] [1 swons] branch] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [69]:
+
+
+
clear
+
+true [] [] pop swap carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1]
+
+
+ +
+
+ +
+
+
+
In [70]:
+
+
+
clear
+
+false [] [] pop swap carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[]
+
+
+ +
+
+ +
+
+
+
In [71]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

The story so far...

+
add-digits == initial-carry add-digits'
+
+add-digits' == [P] [THEN] [R0] [R1] genrec
+
+initial-carry == false rollup
+
+P == [bool] ii & not
+
+THEN == [P'] [THEN'] [ELSE] ifte
+
+P' == [bool] ii |
+
+THEN' == ditch-empty-list add-carry-to-digits
+
+carry == [] [1 swons] branch
+
+ELSE == pop swap carry
+
+
+

We just need to knock out those recursive functions R0 and R1 and we're done.

+ +
+
+
+
+
+
+

And recur...

bool [a ...] [b ...] R0 [add-digits'] R1
+
+ +
+
+
+
+
+
+

First we will want to uncons the digits

+ +
+
+
+
+
+
In [72]:
+
+
+
clear
+
+false [1 2 3] [4 5 6] [uncons] ii swapd
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false 1 4 [2 3] [5 6]
+
+
+ +
+
+ +
+
+
+
+

uncons-two

We could call this uncons-two:

+ +
+
+
+
+
+
In [73]:
+
+
+
clear
+
+[uncons-two [uncons] ii swapd] inscribe
+
+[1 2 3] [4 5 6] uncons-two
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1 4 [2 3] [5 6]
+
+
+ +
+
+ +
+
+
+
In [74]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+
bool a b [...] [...] R0' [add-digits'] R1
+
+
+

It's at this point that we'll want to employ the add-with-carry function:

+
bool a b [...] [...] [add-with-carry] dipd R0'' [add-digits'] R1
+
+bool a b add-with-carry [...] [...] R0'' [add-digits'] R1
+
+(a+b) bool [...] [...] R0'' [add-digits'] R1
+
+
+

If we postulate a cons in our R1 function...

+
(a+b) bool [...] [...] R0'' [add-digits'] i cons
+
+
+

Then it seems like we're done? R0'' is nothing?

+
R0 ≡ uncons-two [add-with-carry] dipd
+
+R1 ≡ i cons
+
+ +
+
+
+
+
+
+

add-digits

+
+
+
+
+
+
In [75]:
+
+
+
[add-digits initial-carry add-digits'] inscribe
+[add-digits' [P] [THEN] [R0] [R1] genrec] inscribe
+[initial-carry false rollup] inscribe
+[P [bool] ii & not] inscribe
+[THEN [P'] [THEN'] [ELSE] ifte] inscribe
+[P' [bool] ii |] inscribe
+[THEN' ditch-empty-list add-carry-to-digits] inscribe
+[ELSE pop swap [] [1 swons] branch] inscribe
+[R0 uncons-two [add-with-carry] dipd] inscribe
+[R1 i cons] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [76]:
+
+
+
[3 2 1] [1 1 1]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[3 2 1] [1 1 1]
+
+
+ +
+
+ +
+
+
+
In [77]:
+
+
+
add-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[4 3 2]
+
+
+ +
+
+ +
+
+
+
In [78]:
+
+
+
base -- unit
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[4 3 2] [2147483647]
+
+
+ +
+
+ +
+
+
+
In [79]:
+
+
+
add-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[3 4 2]
+
+
+ +
+
+ +
+
+
+
In [80]:
+
+
+
base -- dup dup unit ccons
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[3 4 2] [2147483647 2147483647 2147483647]
+
+
+ +
+
+ +
+
+
+
In [81]:
+
+
+
add-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[2 4 2 1]
+
+
+ +
+
+ +
+
+
+
+

243 + 999 =

+ +
+
+
+
+
+
In [82]:
+
+
+
clear 243 999 +
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
1242
+
+
+ +
+
+ +
+
+
+
+

add-bigints

There is one more thing we have to do to use this: we have to deal with the signs.

+
add-bigints 
+[[first] ii xor not]                 # are they the same sign?
+[[uncons] dip rest add-digits cons]  # add the digits and set the sign.
+[neg-bigint sub-bigints]             # adding unlikes is actually subtraction.
+ifte
+
+
+

But we haven't implemented neg-bigint or sub-bigints yet...

+ +
+
+
+
+
+
In [83]:
+
+
+
clear
+
+123 to-bigint 456 to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 123] [true 456]
+
+
+ +
+
+ +
+
+
+
In [84]:
+
+
+
[first] ii xor not
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [85]:
+
+
+
clear
+
+123 to-bigint 456 to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 123] [true 456]
+
+
+ +
+
+ +
+
+
+
In [86]:
+
+
+
[uncons] dip
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true [123] [true 456]
+
+
+ +
+
+ +
+
+
+
In [87]:
+
+
+
rest 
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true [123] [456]
+
+
+ +
+
+ +
+
+
+
In [88]:
+
+
+
add-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true [579]
+
+
+ +
+
+ +
+
+
+
In [89]:
+
+
+
 cons
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 579]
+
+
+ +
+
+ +
+
+
+
+

add-bigints

+
+
+
+
+
+
In [90]:
+
+
+
[same-sign [first] ii xor not] inscribe
+[add-like-bigints [uncons] dip rest add-digits cons] inscribe
+[add-bigints [same-sign] [add-like-bigints] [1 0 /] ifte] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 579]
+
+
+ +
+
+ +
+
+
+
In [91]:
+
+
+
base -- to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 579] [true 2147483647]
+
+
+ +
+
+ +
+
+
+
In [92]:
+
+
+
add-bigints
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 578 1]
+
+
+ +
+
+ +
+
+
+
In [93]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Subtraction of Like Signs sub-digits

Subtraction is similar to addition in that it's a simple recursive algorithm that works digit-by-digit. It has the same four cases as well, we can reuse P and P'.

+
initial-carry == false rollup
+  sub-digits' == [P] [sub.THEN] [sub.R0] [sub.R1] genrec
+   sub-digits == initial-carry add-digits'
+     sub.THEN == [P'] [sub.THEN'] [sub.ELSE] ifte
+
+ +
+
+
+
+
+
+

Refactoring For The Win

We noted above that the algorithm for subtraction is similar to that for addition. Maybe we can reuse more than just P and P'? In fact, I think we could refactor (prematurely, two cases is one too few) something like this?

+
             [sub.THEN'] [sub.ELSE] [sub.R0] [sub.R1] foo
+---------------------------------------------------------------------
+   [P] [[P'] [sub.THEN'] [sub.ELSE] ifte] [sub.R0] [sub.R1] genrec
+
+ +
+
+
+
+
+
+

or just

+
             [THEN] [ELSE] [R0] [R1] foo
+----------------------------------------------------
+   [P] [[P'] [THEN] [ELSE] ifte] [R0] [R1] genrec
+
+
+

eh?

+ +
+
+
+
+
+
+

foo is something like:

+
F == [ifte] ccons [P'] swons
+G == [F] dipdd
+
+[THEN] [ELSE] [R0] [R1] [F] dipdd foo'
+[THEN] [ELSE] F [R0] [R1] foo'
+[THEN] [ELSE] [ifte] ccons [P'] swons [R0] [R1] foo'
+[[THEN] [ELSE] ifte] [P'] swons [R0] [R1] foo'
+[[P'] [THEN] [ELSE] ifte] [R0] [R1] foo'
+
+ +
+
+
+
+
+
+

That leaves [P]...

+
F == [ifte] ccons [P'] swons [P] swap
+G == [F] dipdd
+
+[THEN] [ELSE] [ifte] ccons [P'] swons [P] swap [R0] [R1] foo'
+[[THEN] [ELSE] ifte] [P'] swons [P] swap [R0] [R1] foo'
+[[P'] [THEN] [ELSE] ifte] [P] swap [R0] [R1] foo'
+[P] [[P'] [THEN] [ELSE] ifte] [R0] [R1] genrec
+
+ +
+
+
+
+
+
+

Ergo:

+
  F == [ifte] ccons [P'] swons [P] swap
+foo == [F] dipdd genrec
+combine-two-lists == [i cons] foo
+
+ +
+
+
+
+
+
+

-and-

+
add-digits' == [one-empty-list]
+               [both-empty]
+               [both-full]
+               combine-two-lists
+
+one-empty-list == ditch-empty-list add-carry-to-digits
+both-empty == pop swap carry
+both-full == uncons-two [add-with-carry] dipd
+
+
+

This illustrates how refactoring creates denser yet more readable code.

+ +
+
+
+
+
+
+

But this doesn't go quite far enough, I think.

+
R0 == uncons-two [add-with-carry] dipd
+
+ +
+
+
+
+
+
+

I think R0 will pretty much always do:

+
uncons-two [combine-two-values] dipd
+
+ +
+
+
+
+
+
+

And so it should be refactored further to something like:

+
         [F] R0
+-------------------------
+   uncons-two [F] dipd
+
+ +
+
+
+
+
+
+

And then add-digits' becomes just:

+
add-digits' == [one-empty-list]
+               [both-empty]
+               [add-with-carry]
+               combine-two-lists
+
+ +
+
+
+
+
+
+

If we factor ditch-empty-list out of one-empty-list, and pop from both-empty:

+
add-digits' == [add-carry-to-digits]
+               [swap carry]
+               [add-with-carry]
+               combine-two-lists
+
+ +
+
+
+
+
+
+

Let's figure out the new form.

+
   [ONE-EMPTY] [BOTH-EMPTY] [COMBINE-VALUES] foo
+---------------------------------------------------
+   [P]
+   [
+       [P']
+       [ditch-empty-list ONE-EMPTY]
+       [pop BOTH-EMPTY]
+       ifte
+   ]
+   [uncons-two [COMBINE-VALUES] dipd]
+   [i cons] genrec
+
+
+

eh?

+ +
+
+
+
+
+
+

Let's not over think it.

+
   [ONE-EMPTY] [ditch-empty-list] swoncat [BOTH-EMPTY] [pop] swoncat [COMBINE-VALUES] 
+   [ditch-empty-list ONE-EMPTY] [pop BOTH-EMPTY] [COMBINE-VALUES]
+
+ +
+
+
+
+
+
+

With:

+
   [C] [A] [B] sandwich
+--------------------------
+        [A [C] B]
+
+ +
+
+
+
+
+
In [94]:
+
+
+
[sandwich swap [cons] dip swoncat] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [95]:
+
+
+
clear [B] [A] [C]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[B] [A] [C]
+
+
+ +
+
+ +
+
+
+
In [96]:
+
+
+
sandwich
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[A [B] C]
+
+
+ +
+
+ +
+
+
+
+

So to get from

+
[A] [B] [C]
+
+
+

to:

+
[ditch-empty-list A] [pop B] [uncons-two [C] dipd]
+
+
+

we use:

+
[[[ditch-empty-list] swoncat] dip [pop] swoncat] dip [uncons-two] [dipd] sandwich
+
+
+

It's gnarly, but simple:

+ +
+
+
+
+
+
In [97]:
+
+
+
clear
+[_foo0.0 [[ditch-empty-list] swoncat] dip] inscribe
+[_foo0.1 [pop] swoncat] inscribe
+[_foo0.3 [_foo0.0 _foo0.1] dip] inscribe
+[_foo0.4 [uncons-two] [dipd] sandwich] inscribe
+[_foo0 _foo0.3 _foo0.4] inscribe
+
+[_foo1 [
+  [ifte] ccons
+  [P'] swons 
+  [P] swap 
+  ] dip
+] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [98]:
+
+
+
[A] [B] [C] _foo0
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[ditch-empty-list A] [pop B] [uncons-two [C] dipd]
+
+
+ +
+
+ +
+
+
+
In [99]:
+
+
+
_foo1
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[P] [[P'] [ditch-empty-list A] [pop B] ifte] [uncons-two [C] dipd]
+
+
+ +
+
+ +
+
+
+
In [100]:
+
+
+
clear
+[add-carry-to-digits]
+[swap carry]
+[add-with-carry]
+_foo0 _foo1
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd]
+
+
+ +
+
+ +
+
+
+
+

Compare the above with what we wanted:

+
[P]
+[
+   [P']
+   [ditch-empty-list ONE-EMPTY]
+   [pop BOTH-EMPTY]
+   ifte
+]
+[uncons-two [COMBINE-VALUES] dipd]
+
+
+

Allwe need to do is add:

+
[i cons] genrec
+
+ +
+
+
+
+
+
In [101]:
+
+
+
clear
+
+[3 2 1] [6 5 4] initial-carry
+
+[add-carry-to-digits]
+[swap carry]
+[add-with-carry]
+_foo0 _foo1
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false [3 2 1] [6 5 4] [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd]
+
+
+ +
+
+ +
+
+
+
In [102]:
+
+
+
[i cons] genrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[9 7 5]
+
+
+ +
+
+ +
+
+
+
In [103]:
+
+
+
clear
+[build-two-list-combiner _foo0 _foo1 [i cons]] inscribe
+[combine-two-lists [add-carry-to-digits] [swap carry] [add-with-carry] build-two-list-combiner] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [104]:
+
+
+
clear
+
+[3 2 1] [6 5 4] initial-carry
+combine-two-lists
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false [3 2 1] [6 5 4] [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons]
+
+
+ +
+
+ +
+
+
+
In [105]:
+
+
+
genrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[9 7 5]
+
+
+ +
+
+ +
+
+
+
In [106]:
+
+
+
[base 10] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[9 7 5]
+
+
+ +
+
+ +
+
+
+
In [107]:
+
+
+
clear
+
+123456 to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 6 5 4 3 2 1]
+
+
+ +
+
+ +
+
+
+
In [108]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

So that's nice.

+

In order to avoid the overhead of rebuilding the whole thing each time we could pre-compute the function and store it in the dictionary.

+ +
+
+
+
+
+
In [109]:
+
+
+
[add-carry-to-digits]
+[swap carry]
+[add-with-carry]
+build-two-list-combiner
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons]
+
+
+ +
+
+ +
+
+
+
+

Now grab the definition, add the genrec and symbol (name) and inscribe it:

+ +
+
+
+
+
+
In [110]:
+
+
+
[genrec] ccons ccons [add-digits'] swoncat
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[add-digits' [P] [[P'] [ditch-empty-list add-carry-to-digits] [pop swap carry] ifte] [uncons-two [add-with-carry] dipd] [i cons] genrec]
+
+
+ +
+
+ +
+
+
+
In [111]:
+
+
+
 inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out...

+ +
+
+
+
+
+
In [112]:
+
+
+
false [3 2 1] [4 3 2] add-digits'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[7 5 3]
+
+
+ +
+
+ +
+
+
+
In [113]:
+
+
+
false swap base -- unit
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false [7 5 3] [9]
+
+
+ +
+
+ +
+
+
+
In [114]:
+
+
+
add-digits'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[6 6 3]
+
+
+ +
+
+ +
+
+
+
In [115]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Demonstrate add-bigints

+
+
+
+
+
+
In [116]:
+
+
+
1234 999 [to-bigint] ii 
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 4 3 2 1] [true 9 9 9]
+
+
+ +
+
+ +
+
+
+
In [117]:
+
+
+
add-bigints
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 3 3 2 2]
+
+
+ +
+
+ +
+
+
+
In [118]:
+
+
+
from-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
2233
+
+
+ +
+
+ +
+
+
+
In [119]:
+
+
+
1234 999 +
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
2233 2233
+
+
+ +
+
+ +
+
+
+
In [120]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Subtracting

Okay, we're almost ready to implement subtraction, but there's a wrinkle! When we subtract a smaller (absolute) value from a larger (absolute) value there's no problem:

+
10 - 5 = 5
+
+
+

But I don't know the algorithm to subtract a larger number from a smaller one:

+
5 - 10 = ???
+
+
+

The answer is -5, of course, but what's the algorithm? How to make the computer figure that out? We make use of the simple algebraic identity:

+
a - b = -(b - a)
+
+
+

So if we want to subtract a larger number a from a smaller one b we can instead subtract the smaller from the larger and invert the sign:

+
5 - 10 = -(10 - 5)
+
+
+

To do this we need a function gt-digits that will tell us which of two digit lists represents the larger integer.

+ +
+
+
+
+
+
+

gt-digits

+
+
+
+
+
+
+

I just realized I don't have a list length function yet!

+ +
+
+
+
+
+
In [121]:
+
+
+
[length [pop ++] step_zero] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [122]:
+
+
+
clear
+[] length
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
0
+
+
+ +
+
+ +
+
+
+
In [123]:
+
+
+
clear
+[this is a list] length
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
4
+
+
+ +
+
+ +
+
+
+
In [124]:
+
+
+
clear
+[1 2 3] [4 5] over over [length] app2
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[1 2 3] [4 5] 3 2
+
+
+ +
+
+ +
+
+
+
In [125]:
+
+
+
[swap][6][7]cmp
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[4 5] [1 2 3]
+
+
+ +
+
+ +
+
+
+
+

what about a function that iterates through two lists until one or the other ends, or they end at the same time (same length) and we walk back through comparing the digits?

+ +
+
+
+
+
+
In [126]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [127]:
+
+
+
[1 2 3] [4 5 6] [bool] ii &
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [128]:
+
+
+
clear
+[1 2 3] [4 5 6]
+[[bool] ii | not]
+[pop]
+[uncons-two]
+[i [unit cons] dip cons]
+genrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[1 4] [2 5] [3 6]]
+
+
+ +
+
+ +
+
+
+
In [129]:
+
+
+
clear
+[1 2 3] [4 5 6]
+[[bool] ii | not]
+[pop]
+[uncons-two]
+[i [unit cons] dip cons]
+genrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[1 4] [2 5] [3 6]]
+
+
+ +
+
+ +
+
+
+
In [130]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

So I guess that's zip?

+

But we want something a little different.

+

It's a weird function: compare lengths, if they are the same length then compare contents pairwise from the end.

+

if the first list is empty and the second list isn't then the whole function should return false

+

if the first list is non-empty and the second list is empty then the whole function should return true

+

if both lists are non-empty we uncons some digits for later comparison? Where to put them? Leave them on the stack? What about short-circuits?

+

if both lists are empty we start comparing uncons'd pairs until we find an un-equal pair or run out of pairs.

+

if we run out of pairs before we find an unequal pair then the function returns true (the numbers are identical, we should try to shortcut the actual subtraction here, but let's just get it working first, eh?)

+

if we find an unequal pair we return a>b and discard the rest of the pairs. Or maybe this all happens in some sort of infra first situation?

+ +
+
+
+
+
+
+

So the predicate will be [bool] ii & not, if one list is longer than the other we are done. +We postulate a third list to contain the pairs:

+
[] [3 2 1] [4 5 6] [P] [BASE] [R0] [R1] genrec
+
+
+

The recursive branch seems simpler to figure out:

+
[] [3 2 1] [4 5 6] R0 [F] R1
+
+uncons-two [unit cons swons] dipd [F] i
+
+[] [3 2 1] [4 5 6] [P] [BASE] [uncons-two [unit cons swons] dipd] tailrec
+
+ +
+
+
+
+
+
In [131]:
+
+
+
 [xR1 uncons-two [unit cons swons] dipd] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [132]:
+
+
+
clear
+
+[] [3 2 1] [4 5 6]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[] [3 2 1] [4 5 6]
+
+
+ +
+
+ +
+
+
+
In [133]:
+
+
+
xR1 xR1 xR1
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[1 6] [2 5] [3 4]] [] []
+
+
+ +
+
+ +
+
+
+
In [134]:
+
+
+
clear
+[xP [bool] ii & not] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [135]:
+
+
+
clear
+
+[] [3 2 1] [5 4] [xP] [] [xR1] tailrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[2 4] [3 5]] [1] []
+
+
+ +
+
+ +
+
+
+
In [136]:
+
+
+
clear
+
+[] [3 2] [4 5 1] [xP] [] [xR1] tailrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[2 5] [3 4]] [] [1]
+
+
+ +
+
+ +
+
+
+
In [137]:
+
+
+
clear
+
+[] [3 2 1] [5 4 3] [xP] [] [xR1] tailrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[[1 3] [2 4] [3 5]] [] []
+
+
+ +
+
+ +
+
+
+
+

Now comes the tricky part, that base case:

+

we have three lists. The first is a possibly-empty list of pairs to compare.

+

The second two are the tails of the original lists.

+

If the top list is non-empty then the second list must be empty so the whole function should return true

+

If the top list is empty and the second list isn't then the whole function should return false

+

If both lists are empty we start comparing uncons'd pairs until we find an un-equal pair or run out of pairs.

+
[bool]  # if the first list is non-empty
+[popop pop true]
+[
+    [pop bool]  # the second list is non-empty (the first list is empty)
+    [popop pop false]
+    [
+        # both lists are empty
+        popop
+        compare-pairs
+    ]
+    ifte
+]
+ifte
+
+
+ +
+
+
+
+
+
In [138]:
+
+
+
clear
+[][][1]
+
+[bool]
+[popop pop true]
+[
+    [pop bool]
+    [popop pop false]
+    [popop 23 swons]
+    ifte
+]
+ifte
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [139]:
+
+
+
clear
+[][1][]
+
+[bool]
+[popop pop true]
+[
+    [pop bool]
+    [popop pop false]
+    [popop 23 swons]
+    ifte
+]
+ifte
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false
+
+
+ +
+
+ +
+
+
+
In [140]:
+
+
+
clear
+[1][][]
+
+[bool]
+[popop pop true]
+[
+    [pop bool]
+    [popop pop false]
+    [popop 23 swons]
+    ifte
+]
+ifte
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[23 1]
+
+
+ +
+
+ +
+
+
+
+

compare-pairs

This should be a pretty simple recursive function

+
[P] [THEN] [R0] [R1] genrec
+
+
+

If the list is empty we return false

+
P == bool not
+THEN == pop false
+
+
+

On the recursive branch we have an ifte expression:

+
            pairs R0 [compare-pairs] R1
+---------------------------------------------------
+   pairs [P.rec] [THEN.rec] [compare-pairs] ifte
+
+
+

We must compare the pair from the top of the list:

+
P.rec == first [>] infrst
+
+ +
+
+
+
+
+
In [141]:
+
+
+
clear
+
+[[1 3] [2 4] [3 5]] first [>] infrst
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [142]:
+
+
+
clear
+
+[[1 3] [2 4] [3 5]] [[>] infrst] map
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true true true]
+
+
+ +
+
+ +
+
+
+
+
THEN.rec == pop true
+
+ +
+
+
+
+
+
In [143]:
+
+
+
clear
+
+[compare-pairs
+  [bool not]
+  [pop false]
+  [
+    [first [>] infrst]
+    [pop true]
+  ]
+  [ifte]
+  genrec
+] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [144]:
+
+
+
clear [[1 3] [2 4] [3 5]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [145]:
+
+
+
clear [[1 3] [3 3] [3 5]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
+

Whoops! I forgot to remove the already-checked pair from the list of pairs! (Later on I discover that the logic is inverted here: >= not < d'oh!)

+ +
+
+
+
+
+
In [146]:
+
+
+
clear
+
+[compare-pairs
+  [bool not]
+  [pop false]
+  [
+    [first [>=] infrst]
+    [pop true]
+  ]
+  [[rest] swoncat ifte]
+  genrec
+] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

This is clunky and inefficient but it works.

+ +
+
+
+
+
+
In [147]:
+
+
+
clear [[1 0] [2 2] [3 3]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [148]:
+
+
+
clear [[1 1] [2 2] [3 3]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [149]:
+
+
+
clear [[1 2] [2 2] [3 3]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [150]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [151]:
+
+
+
clear [[1 1] [2 1] [3 3]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [152]:
+
+
+
clear [[1 1] [2 2] [3 3]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [153]:
+
+
+
clear [[1 1] [2 3] [3 3]] compare-pairs
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [ ]:
+
+
+
 
+
+ +
+
+
+ +
+
+
+
In [154]:
+
+
+
clear
+[[1 1] [2 1] [3 3]] [] []
+
+[bool]
+[popop pop true]
+[
+    [pop bool]
+    [popop pop false]
+    [popop compare-pairs]
+    ifte
+]
+ifte
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [155]:
+
+
+
[BASE
+  [bool]
+  [popop pop true]
+  [
+    [pop bool]
+    [popop pop false]
+    [popop compare-pairs]
+    ifte
+  ]
+  ifte
+] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [156]:
+
+
+
clear
+
+[] [3 2 1] [4 5 6]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[] [3 2 1] [4 5 6]
+
+
+ +
+
+ +
+
+
+
In [157]:
+
+
+
[xP] [BASE] [xR1] tailrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [158]:
+
+
+
clear
+
+[] [3 2 1] [4 5 6] swap
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[] [4 5 6] [3 2 1]
+
+
+ +
+
+ +
+
+
+
In [159]:
+
+
+
[xP] [BASE] [xR1] tailrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false
+
+
+ +
+
+ +
+
+
+
In [160]:
+
+
+
clear
+
+[] [3 2 1] dup
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[] [3 2 1] [3 2 1]
+
+
+ +
+
+ +
+
+
+
In [161]:
+
+
+
[xP] [BASE] [xR1] tailrec
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [162]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [163]:
+
+
+
[gt-bigint <<{} [xP] [BASE] [xR1] tailrec] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [164]:
+
+
+
clear [3 2 1] [4 5 6] gt-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [165]:
+
+
+
clear [3 2 1] [4 5 6] swap gt-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false
+
+
+ +
+
+ +
+
+
+
In [166]:
+
+
+
clear [3 2 1] dup gt-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true
+
+
+ +
+
+ +
+
+
+
In [ ]:
+
+
+
 
+
+ +
+
+
+ +
+
+
+
In [167]:
+
+
+
clear [3 2 1] [4 5 6] [gt-bigint] [swap] [] ifte
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[4 5 6] [3 2 1]
+
+
+ +
+
+ +
+
+
+
In [168]:
+
+
+
clear [4 5 6] [3 2 1] [gt-bigint] [swap] [] ifte
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[4 5 6] [3 2 1]
+
+
+ +
+
+ +
+
+
+
+

And so it goes.

+

Now we can subtract, we just have to remember to invert the sign bit if we swap the digit lists.

+

Maybe something like:

+
check-gt == [gt-bigint] [swap true rollup] [false rollup] ifte
+
+
+

To keep the decision around as a Boolean flag? We can xor it with the sign bit?

+ +
+
+
+
+
+
In [169]:
+
+
+
clear
+[check-gt [gt-bigint] [swap [not] dipd] [] ifte] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [170]:
+
+
+
false [4 5 6] [3 2 1]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false [4 5 6] [3 2 1]
+
+
+ +
+
+ +
+
+
+
In [171]:
+
+
+
check-gt
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false [4 5 6] [3 2 1]
+
+
+ +
+
+ +
+
+
+
In [172]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Subtraction, at last...

So now that we can compare digit lists to see if one is larger than the other we can subtract (inverting the sign if necessary) much like we did addition:

+
sub-bigints == [same-sign] [sub-like-bigints] [1 0 /] ifte
+
+sub-like-bigints == [uncons] dip rest   sub-digits cons
+                                      ^
+                                      |
+
+
+

At this point we would have the sign bit then the two digit lists.

+
sign [c b a] [z y x]
+
+ +
+
+
+
+
+
+

We want to use check-gt here:

+
sign [c b a] [z y x] check-gt
+sign swapped? [c b a] [z y x] check-gt
+
+ +
+
+
+
+
+
+

It seems we should just flip the sign bit if we swap, eh?

+
check-gt == [gt-bigint] [swap [not] dipd] [] ifte
+
+ +
+
+
+
+
+
+

Now we subtract the digits:

+
sign [c b a] [z y x] sub-digits cons
+
+ +
+
+
+
+
+
+

So:

+
sub-like-bigints == [uncons] dip rest check-gt sub-digits cons
+
+sub-digits == initial-carry sub-digits'
+
+sub-digits' ==
+    [sub-carry-from-digits]
+    [swap sub-carry]
+    [sub-with-carry]
+    build-two-list-combiner
+    genrec
+
+
+

We just need to define the pieces.

+ +
+
+
+
+
+
+

sub-with-carry

We know we will never be subtracting a larger (absolute) number from a smaller (absolute) number (they might be equal) so the carry flag will never be true at the end of a digit list subtraction.

+ +
+
+
+
+
+
+
   carry a b sub-with-carry
+------------------------------
+     (a-b-carry)  new-carry
+
+_sub-with-carry0 ≡ [bool-to-int] dipd - -
+_sub-with-carry1 ≡ [base + base mod] [0 <] clop
+
+sub-with-carry ≡ _sub-with-carry0 _sub-with-carry1
+
+ +
+
+
+
+
+
In [173]:
+
+
+
[_sub-with-carry0 rolldown bool-to-int [-] ii] inscribe
+[_sub-with-carry1 [base + base mod] [0 <] cleave] inscribe
+[sub-with-carry _sub-with-carry0 _sub-with-carry1] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [174]:
+
+
+
clear false 3 base --
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false 3 9
+
+
+ +
+
+ +
+
+
+
In [175]:
+
+
+
sub-with-carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
4 true
+
+
+ +
+
+ +
+
+
+
In [176]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

sub-carry-from-digits

Should be easy to make modeled on add-carry-to-digits, another very simple recursive function. The predicate, base case, and R1 are the same:

+
carry [n ...] sub-carry-from-digits
+carry [n ...] [pop not] [popd] [_scfd_R0] [i cons] genrec
+
+ +
+
+
+
+
+
+

That leaves the recursive branch:

+
true [n ...] _scfd_R0 [sub-carry-from-digits] i cons
+
+
+

-or-

+
true [] _scfd_R0 [sub-carry-from-digits] i cons
+
+
+

Except that this should should never happen when subtracting, because we already made sure that we're only ever subtracting a number less than or equal to the, uh, number we are subtracting from (TODO rewrite this trainwreck of a sentence).

+
         true [a ...] _scfd_R0 [sub-carry-from-digits] i cons
+----------------------------------------------------------------
+ true 0 a add-with-carry [...] [sub-carry-from-digits] i cons
+------------------------------------------------------------------
+             (a+1) carry [...] [sub-carry-from-digits] i cons
+
+ +
+
+
+
+
+
+
true [a ...] _scfd_R0
+true [a ...] 0 swap uncons [sub-with-carry] dip
+true 0 [a ...] uncons [sub-with-carry] dip
+true 0 a [...] [sub-with-carry] dip
+true 0 a sub-with-carry [...]
+
+_scfd_R0 == 0 swap uncons [sub-with-carry] dip
+
+ +
+
+
+
+
+
+

But there's a problem! This winds up subtracting a from 0 rather than the other way around:

+
_scfd_R0 == uncons 0 swap [sub-with-carry] dip
+
+ +
+
+
+
+
+
In [177]:
+
+
+
[sub-carry-from-digits
+  [pop not]
+  [popd]
+  [_scfd_R0]
+  [i cons]
+  genrec
+] inscribe
+[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Try it out:

+ +
+
+
+
+
+
In [178]:
+
+
+
clear
+
+false [3 2 1] sub-carry-from-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[3 2 1]
+
+
+ +
+
+ +
+
+
+
In [179]:
+
+
+
clear
+
+true [0 1] sub-carry-from-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[9 0]
+
+
+ +
+
+ +
+
+
+
In [180]:
+
+
+
clear
+
+true [3 2 1] sub-carry-from-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[2 2 1]
+
+
+ +
+
+ +
+
+
+
In [181]:
+
+
+
clear
+
+true [0 0 1] sub-carry-from-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[9 9 0]
+
+
+ +
+
+ +
+
+
+
In [182]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

But what about those leading zeroes?

+

We could use a version of cons that refuses to put 0 onto an empty list?

+
cons-but-not-leading-zeroes == [[bool] ii | not] [popd] [cons] ifte
+
+ +
+
+
+
+
+
In [183]:
+
+
+
[cons-but-not-leading-zeroes [[bool] ii | not] [popd] [cons] ifte] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [184]:
+
+
+
[sub-carry-from-digits
+  [pop not]
+  [popd]
+  [_scfd_R0]
+  [i cons-but-not-leading-zeroes]
+  genrec
+] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [185]:
+
+
+
[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [186]:
+
+
+
clear
+
+true [0 0 1] sub-carry-from-digits
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[9 9]
+
+
+ +
+
+ +
+
+
+
In [187]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

sub-carry

sub-carry == pop
+
+ +
+
+
+
+
+
In [188]:
+
+
+
[sub-like-bigints [uncons] dip rest check-gt sub-digits cons] inscribe
+[sub-digits initial-carry sub-digits'] inscribe
+[sub-digits'
+    [sub-carry-from-digits]
+    [swap pop]
+    [sub-with-carry]
+    build-two-list-combiner
+    genrec
+] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [189]:
+
+
+
clear
+true [3 2 1] [6 5 4]
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
true [3 2 1] [6 5 4]
+
+
+ +
+
+ +
+
+
+
In [190]:
+
+
+
check-gt initial-carry
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false false [6 5 4] [3 2 1]
+
+
+ +
+
+ +
+
+
+
In [191]:
+
+
+
sub-digits'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
false [3 3 3]
+
+
+ +
+
+ +
+
+
+
In [192]:
+
+
+
clear
+12345 to-bigint 109 to-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 5 4 3 2 1] [true 9 0 1]
+
+
+ +
+
+ +
+
+
+
In [193]:
+
+
+
sub-like-bigints
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
[true 6 3 2 2 1]
+
+
+ +
+
+ +
+
+
+
In [194]:
+
+
+
from-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
12236
+
+
+ +
+
+ +
+
+
+
In [195]:
+
+
+
clear
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

neg-bigint

+
+
+
+
+
+
In [196]:
+
+
+
[neg-bigint [not] infra] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
In [197]:
+
+
+
123 
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
123
+
+
+ +
+
+ +
+
+
+
In [198]:
+
+
+
to-bigint neg-bigint from-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
-123
+
+
+ +
+
+ +
+
+
+
In [199]:
+
+
+
to-bigint neg-bigint from-bigint
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+
123
+
+
+ +
+
+ +
+
+
+
In [200]:
+
+
+
clear
+[sub-bigints [same-sign] [sub-like-bigints] [neg-bigint add-like-bigints] ifte] inscribe
+[add-bigints [same-sign] [add-like-bigints] [neg-bigint sub-like-bigints] ifte] inscribe
+
+ +
+
+
+ +
+
+ + +
+ +
+ + +
+

+
+
+ +
+
+ +
+
+
+
+

Multiplication

+
+
+
+
+
+
In [ ]:
+
+
+
 
+
+ +
+
+
+ +
+
+
+
+

Appendix: Source Code

clear
+[base 2147483648]
+[ditch-empty-list [bool] [popd] [pop] ifte]
+[bool-to-int [0] [1] branch]
+[uncons-two [uncons] ii swapd]
+[sandwich swap [cons] dip swoncat]
+
+[digitalize [0 <=] [pop []] [base divmod swap] [i cons] genrec]
+[to-bigint [!-] [abs digitalize] cleave cons]
+
+[prep rest 1 0 rolldown]
+[from-bigint' [next-digit] step popd]
+[next-digit [increase-power] [accumulate-digit] clop popdd]
+[increase-power popop base *]
+[accumulate-digit rolldown * +]
+
+[sign-int [first] [prep from-bigint'] cleave]
+[neg-if-necessary swap [neg] [] branch]
+[from-bigint sign-int neg-if-necessary]
+
+[add-with-carry _add-with-carry0 _add-with-carry1]
+[_add-with-carry0 [bool-to-int] dipd + +]
+[_add-with-carry1 base [mod] [>=] clop]
+
+[add-carry-to-digits [pop not] [popd] [_actd_R0] [i cons] genrec]
+[_actd_R0 [bool] [_actd_R0.then] [_actd_R0.else] ifte]
+[_actd_R0.else popd 1 false rolldown]
+[_actd_R0.then 0 swap uncons [add-with-carry] dip]
+
+[add-digits initial-carry add-digits']
+[initial-carry false rollup]
+
+[add-digits' [P] [THEN] [R0] [R1] genrec]
+[P [bool] ii & not]
+[THEN [P'] [THEN'] [ELSE] ifte]
+[R0 uncons-two [add-with-carry] dipd]
+[R1 i cons]
+[P' [bool] ii |]
+[THEN' ditch-empty-list add-carry-to-digits]
+[ELSE pop swap [] [1 swons] branch]
+
+[same-sign [first] ii xor not]
+[add-like-bigints [uncons] dip rest add-digits cons]
+[add-bigints [same-sign] [add-like-bigints] [neg-bigint sub-like-bigints] ifte]
+
+[build-two-list-combiner _btlc0 _btlc1 [i cons]]
+[_btlc0.0 [[ditch-empty-list] swoncat] dip]
+[_btlc0.1 [pop] swoncat]
+[_btlc0.3 [_btlc0.0 _btlc0.1] dip]
+[_btlc0.4 [uncons-two] [dipd] sandwich]
+[_btlc0 _btlc0.3 _btlc0.4]
+[_btlc1 [[ifte] ccons [P'] swons [P] swap] dip]
+
+[carry [] [1 swons] branch]
+
+[compare-pairs [bool not] [pop false] [[first [>=] infrst] [pop true]] [[rest] swoncat ifte] genrec]
+[xR1 uncons-two [unit cons swons] dipd]
+[xP [bool] ii & not]
+[BASE [bool] [popop pop true] [[pop bool] [popop pop false] [popop compare-pairs] ifte] ifte]
+[gt-bigint <<{} [xP] [BASE] [xR1] tailrec]
+[check-gt [gt-bigint] [swap [not] dipd] [] ifte]
+
+[sub-carry pop]
+
+[sub-carry-from-digits [pop not] [popd] [_scfd_R0] [i cons-but-not-leading-zeroes] genrec] inscribe
+[_scfd_R0 uncons 0 swap [sub-with-carry] dip] inscribe
+[cons-but-not-leading-zeroes [P'] [cons] [popd] ifte]
+
+[sub-with-carry _sub-with-carry0 _sub-with-carry1]
+[_sub-with-carry0 rolldown bool-to-int [-] ii]
+[_sub-with-carry1 [base + base mod] [0 <] cleave]
+
+[sub-like-bigints [uncons] dip rest check-gt sub-digits cons]
+[sub-digits initial-carry sub-digits']
+
+enstacken [inscribe] step
+
+[add-carry-to-digits]
+[swap carry]
+[add-with-carry]
+build-two-list-combiner
+[genrec] ccons ccons
+[add-digits'] swoncat
+inscribe
+
+[sub-carry-from-digits]
+[swap sub-carry]
+[sub-with-carry]
+build-two-list-combiner
+[genrec] ccons ccons
+[sub-digits'] swoncat
+inscribe
+
+ +
+
+
+
+
+
+

notes

So far I have three formats for Joy source:

+
    +
  • def.txt is a list of definitions (UTF-8), one per line, with no special marks.
  • +
  • foo ≡ bar baz... lines in the joy.py embedded definition text, because why not? (Sometimes I use == instead of mostly because some tools can't handle the Unicode glyph. Like converting this notebook to PDF via LaTeX just omitted them.)
  • +
  • [name body] inscribe Joy source code that literally defines new words in the dictionary at runtime. A text of those commands can be fed to the interpreter to customize it without any special processing (like the other two formats require.)
  • +
+

So far I prefer the def.txt style but that makes it tricky to embed them automatically into the joy.py file.

+

Refactoring

We have i cons but that's pretty tight already, eh?

+

However, [i cons] genrec is an interesting combinator. It's almost tailrec with that i combinator for the recursion, but then cons means it's a list-builder (an anamorphism if you go for that sort of thing.)

+
simple-list-builder == [i cons] genrec
+
+
+

And maybe:

+
boolii == [bool] ii
+
+   both? == boolii &
+ one-of? == boolii |
+
+ +
+
+
+ + diff --git a/docs/notebooks/BigInts.ipynb b/docs/notebooks/BigInts.ipynb index e07fb71..20e7c91 100644 --- a/docs/notebooks/BigInts.ipynb +++ b/docs/notebooks/BigInts.ipynb @@ -18,12 +18,20 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "id": "08a49b81", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2147483648" + ] + } + ], "source": [ - "1 31 << " + "1 31 lshift " ] }, { diff --git a/docs/notebooks/Makefile b/docs/notebooks/Makefile index 73a1857..f949930 100644 --- a/docs/notebooks/Makefile +++ b/docs/notebooks/Makefile @@ -14,10 +14,11 @@ index.html: build_index.py $(docs_html) python build_index.py > index.html $(docs_html): %.html : %.ipynb - jupyter nbconvert --to html --template lab $< + jupyter nbconvert --to html --template basic $< mov: $(docs_html) index.html mv -v $? ../html/notebooks/ # markdown Functor-Reference.md | tidy5 -utf8 --add-meta-charset yes > ../../html/FR.html +# --HTMLExporter.embed_images=True \ No newline at end of file diff --git a/docs/notebooks/jupyter_kernel/joy.py b/docs/notebooks/jupyter_kernel/joy.py new file mode 100755 index 0000000..a98adca --- /dev/null +++ b/docs/notebooks/jupyter_kernel/joy.py @@ -0,0 +1,2683 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright © 2022 - 2023 Simon Forman +# +# This file is part of Thun +# +# Thun is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Thun is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Thun. If not see . +# +''' +████████╗██╗ ██╗██╗ ██╗███╗ ██╗ +╚══██╔══╝██║ ██║██║ ██║████╗ ██║ + ██║ ███████║██║ ██║██╔██╗ ██║ + ██║ ██╔══██║██║ ██║██║╚██╗██║ + ██║ ██║ ██║╚██████╔╝██║ ╚████║ + ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + +Thun v. 0.5.1 - http://joypy.osdn.io/ + +This script implements an interpreter for a dialect of Joy. +''' +from functools import wraps +from inspect import getdoc +from sys import stderr +from traceback import print_exc +import operator + + +DEFS = '''\ +eq [false] [true] [false] cmp +gt [true] [false] [false] cmp +lt [false] [false] [true] cmp +neq [true] [false] [true] cmp +le [false] [true] [true] cmp +ge [true] [true] [false] cmp +-- 1 - +? dup bool +and nulco [nullary [false]] dip branch +++ 1 + +or nulco [nullary] dip [true] branch +!- 0 >= +<{} [] swap +<<{} [] rollup +abs dup 0 < [] [neg] branch +anamorphism [pop []] swap [dip swons] genrec +app1 grba infrst +app2 [grba swap grba swap] dip [infrst] cons ii +app3 3 appN +appN [grabN] codi map reverse disenstacken +at drop first +average [sum] [size] cleave / +b [i] dip i +binary unary popd +ccccons ccons ccons +ccons cons cons +clear [] swaack pop +cleave fork popdd +clop cleave popdd +cmp [[>] swap] dipd [ifte] ccons [=] swons ifte +codi cons dip +codireco codi reco +dinfrirst dip infrst +dipd [dip] codi +disenstacken swaack pop +divmod [/] [%] clop +down_to_zero [0 >] [dup --] while +drop [rest] times +dupd [dup] dip +dupdd [dup] dipd +dupdip dupd dip +dupdipd dup dipd +enstacken stack [clear] dip +first uncons pop +flatten <{} [concat] step +fork [i] app2 +fourth rest third +gcd true [tuck mod dup 0 >] loop pop +genrec [[genrec] ccccons] nullary swons concat ifte +grabN <{} [cons] times +grba [stack popd] dip +hypot [sqr] ii + sqrt +ifte [nullary] dipd swap branch +ii [dip] dupdip i +infra swons swaack [i] dip swaack +infrst infra first +make_generator [codireco] ccons +mod % +neg 0 swap - +not [true] [false] branch +nulco [nullary] cons +null [] concat bool not +nullary [stack] dinfrirst +of swap at +pam [i] map +pm [+] [-] clop +popd [pop] dip +popdd [pop] dipd +popop pop pop +popopop pop popop +popopd [popop] dip +popopdd [popop] dipd +product 1 swap [*] step +quoted [unit] dip +range [0 <=] [-- dup] anamorphism +range_to_zero unit [down_to_zero] infra +reco rest cons +rest uncons popd +reverse <{} shunt +roll> swap swapd +roll< swapd swap +rollup roll> +rolldown roll< +rrest rest rest +run <{} infra +second rest first +shift uncons [swons] dip +shunt [swons] step +size [pop ++] step_zero +small dup null [rest null] [pop true] branch +spiral_next [[[abs] ii <=] [[<>] [pop !-] or] and] [[!-] [[++]] [[--]] ifte dip] [[pop !-] [--] [++] ifte] ifte +split_at [drop] [take] clop +split_list [take reverse] [drop] clop +sqr dup mul +stackd [stack] dip +step_zero 0 roll> step +stuncons stack uncons +sum [+] step_zero +swapd [swap] dip +swons swap cons +swoncat swap concat +tailrec [i] genrec +take <<{} [shift] times pop +ternary binary popd +third rest second +tuck dup swapd +unary nullary popd +uncons [first] [rest] cleave +unit [] cons +unquoted [i] dip +unstack [[] swaack] dip swoncat swaack pop +unswons uncons swap +while swap nulco dupdipd concat loop +x dup i +step [_step0] x +_step0 _step1 [popopop] [_stept] branch +_step1 [?] dipd roll< +_stept [uncons] dipd [dupdipd] dip x +times [_times0] x +_times0 _times1 [popopop] [_timest] branch +_times1 [dup 0 >] dipd roll< +_timest [[--] dip dupdipd] dip x +map [_map0] cons [[] [_map?] [_mape]] dip tailrec +_map? pop bool not +_mape popd reverse +_map0 [_map1] dipd _map2 +_map1 stackd shift +_map2 [infrst] cons dipd roll< swons +_\/_ [not not] [not] branch +/\ [not not] ii [pop false] [] branch +\/ [not not] ii [] [pop true] branch'''.splitlines() + + + +''' +██╗███╗ ██╗████████╗███████╗██████╗ ██████╗ ██████╗ ███████╗████████╗███████╗██████╗ +██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝██╔══██╗ +██║██╔██╗ ██║ ██║ █████╗ ██████╔╝██████╔╝██████╔╝█████╗ ██║ █████╗ ██████╔╝ +██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗██╔═══╝ ██╔══██╗██╔══╝ ██║ ██╔══╝ ██╔══██╗ +██║██║ ╚████║ ██║ ███████╗██║ ██║██║ ██║ ██║███████╗ ██║ ███████╗██║ ██║ +╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ +Interpreter +''' + + +def joy(stack, expression, dictionary): + ''' + Evaluate a Joy expression on a stack. + + This function iterates through a sequence of terms. + Literals are put onto the stack and Symbols are + looked up in the dictionary and the functions they + denote are executed. + + :param stack stack: The stack. + :param stack expression: The expression to evaluate. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + + :rtype: (stack, dictionary) + + ''' + expr = push_quote(expression) # We keep a stack-of-stacks, see below. + while expr: + #print( + # f'{stack_to_string(stack)} • {expr_to_string(expr)}' + # ) + term, expr = next_term(expr) + if isinstance(term, Symbol): + try: + func = dictionary[term] + except KeyError: + raise UnknownSymbolError(term) from None + stack, expr, dictionary = func(stack, expr, dictionary) + else: + stack = term, stack + return stack, dictionary + + +class UnknownSymbolError(KeyError): + pass + + +''' +███████╗████████╗ █████╗ ██████╗██╗ ██╗ +██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝ +███████╗ ██║ ███████║██║ █████╔╝ +╚════██║ ██║ ██╔══██║██║ ██╔═██╗ +███████║ ██║ ██║ ██║╚██████╗██║ ██╗ +╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ +Stack +''' + + +class StackUnderflowError(Exception): + pass + + +def list_to_stack(el, stack=()): + ''' + Convert a Python list (or other sequence) to a Joy stack:: + + [1, 2, 3] -> (1, (2, (3, ()))) + + :param list el: A Python list or other sequence (iterators and generators + won't work because ``reversed()`` is called on ``el``.) + :param stack stack: A stack, optional, defaults to the empty stack. This + allows for concatinating Python lists (or other sequence objects) + onto an existing Joy stack. + :rtype: stack + + ''' + for item in reversed(el): + stack = item, stack + return stack + + +def iter_stack(stack): + ''' + Iterate through the items on the stack. + + :param stack stack: A stack. + :rtype: iterator + ''' + while stack: + item, stack = stack + yield item + + +def concat(quote, expression): + ''' + Concatinate quote onto expression. + + In joy [1 2] [3 4] would become [1 2 3 4]. + + :param stack quote: A stack. + :param stack expression: A stack. + :rtype: stack + ''' + isnt_stack(quote) + isnt_stack(expression) + return list_to_stack(list(iter_stack(quote)), expression) + + ## return (quote[0], concat(quote[1], expression)) if quote else expression + # :raises RuntimeError: if quote is larger than sys.getrecursionlimit(). + # This is faster implementation but it would trigger + # RuntimeError: maximum recursion depth exceeded + # on quotes longer than sys.getrecursionlimit(). + + +def get_n_items(n, stack): + ''' + Return n items and remainder of stack. + Raise StackUnderflowError if there are fewer than n items on the stack. + ''' + assert n > 0, repr(n) + temp = [] + while n > 0: + n -= 1 + try: + item, stack = stack + except ValueError: + raise StackUnderflowError( + 'Not enough values on stack.' + ) from None + temp.append(item) + temp.append(stack) + return tuple(temp) + + +def reversed_stack(stack): + ''' + Return list_reverseiterator object for a stack. + ''' + return reversed(list(iter_stack(stack))) + + +''' +███████╗██╗ ██╗██████╗ ██████╗ ███████╗███████╗███████╗██╗ ██████╗ ███╗ ██╗ +██╔════╝╚██╗██╔╝██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██║██╔═══██╗████╗ ██║ +█████╗ ╚███╔╝ ██████╔╝██████╔╝█████╗ ███████╗███████╗██║██║ ██║██╔██╗ ██║ +██╔══╝ ██╔██╗ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║██║██║ ██║██║╚██╗██║ +███████╗██╔╝ ██╗██║ ██║ ██║███████╗███████║███████║██║╚██████╔╝██║ ╚████║ +╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ +Expression + +As elegant as it is to model the expression as a stack, it's not very +efficient, as concatenating definitions and other quoted programs to +the expression is a common and expensive operation. + +Instead, let's keep a stack of sub-expressions, reading from them +one-by-one, and prepending new sub-expressions to the stack rather than +concatenating them. +''' + + +def push_quote(quote, expression=()): + ''' + Put the quoted program onto the stack-of-stacks. + ''' + return (quote, expression) if quote else expression + + +def next_term(expression): + ''' + Return the next term from the expression and the new expression. + Raises ValueError if called on an empty expression. + ''' + (item, quote), expression = expression + return item, push_quote(quote, expression) + + +''' +██████╗ █████╗ ██████╗ ███████╗███████╗██████╗ +██╔══██╗██╔══██╗██╔══██╗██╔════╝██╔════╝██╔══██╗ +██████╔╝███████║██████╔╝███████╗█████╗ ██████╔╝ +██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝ ██╔══██╗ +██║ ██║ ██║██║ ██║███████║███████╗██║ ██║ +╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ +Parser + +There is a single function for converting text to joy expressions +as well as a Symbol class and an Exception type. The Symbol +string class is used by the interpreter to recognize literals by +the fact that they are not Symbol objects. + +A crude grammar:: + + joy := * + term := | 'true' | 'false' | '[' ']' | + +A Joy expression is a sequence of zero or more terms. A term is a +literal value (integer, Boolean, or quoted Joy expression) or a function symbol. +Function symbols are sequences of non-blanks and cannot contain square +brackets. Terms must be separated by blanks, which can be omitted +around square brackets. +''' + + +JOY_BOOL_LITERALS = _F, _T = 'false', 'true' + + +class ParseError(ValueError): + ''' + Raised when there is a error while parsing text. + ''' + + +class Symbol(str): + ''' + A string class that represents Joy function names. + ''' + + __repr__ = str.__str__ + + +def text_to_expression(text): + ''' + Convert a string to a Joy expression. + + When supplied with a string this function returns a Python datastructure + that represents the Joy datastructure described by the text expression. + Any unbalanced square brackets will raise a ParseError. + + :param str text: Text to convert. + :rtype: stack + :raises ParseError: if the parse fails. + ''' + frame = [] + stack = [] + + for tok in text.replace('[', ' [ ').replace(']', ' ] ').split(): + + if tok == '[': + stack.append(frame) + frame = [] + continue + + if tok == ']': + thing = list_to_stack(frame) + try: + frame = stack.pop() + except IndexError: + raise ParseError('Extra closing bracket.') from None + elif tok == _T: + thing = True + elif tok == _F: + thing = False + else: + try: + thing = int(tok) + except ValueError: + thing = Symbol(tok) + + frame.append(thing) + + if stack: + raise ParseError('Unclosed bracket.') + + return list_to_stack(frame) + + +''' +██████╗ ██████╗ ██╗███╗ ██╗████████╗███████╗██████╗ +██╔══██╗██╔══██╗██║████╗ ██║╚══██╔══╝██╔════╝██╔══██╗ +██████╔╝██████╔╝██║██╔██╗ ██║ ██║ █████╗ ██████╔╝ +██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗ +██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║ +╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ +Printer +''' + + +def stack_to_string(stack): + ''' + Return a "pretty print" string for a stack. + + The items are written right-to-left:: + + (top, (second, ...)) -> '... second top' + + :param stack stack: A stack. + :rtype: str + ''' + return _stack_to_string(stack, reversed_stack) + + +def expression_to_string(expression): + ''' + Return a "pretty print" string for a expression. + (For historical reasons this function works on a single quote + not a stack-of-stacks.) + + The items are written left-to-right:: + + (top, (second, ...)) -> 'top second ...' + + :param stack expression: A stack. + :rtype: str + ''' + return _stack_to_string(expression, iter_stack) + + +def expr_to_string(expr): + ''' + Return a "pretty print" string for a stack-of-stacks expression. + ''' + return ' '.join(map(expression_to_string, iter_stack(expr))) + + +def _stack_to_string(stack, iterator): + isnt_stack(stack) + if not stack: # shortcut + return '' + return ' '.join(map(_s, iterator(stack))) + + +def _s(thing): + return ( + '[%s]' % expression_to_string(thing) + if isinstance(thing, tuple) + else JOY_BOOL_LITERALS[thing] + if isinstance(thing, bool) + else repr(thing) + ) + + +''' +██████╗ ███████╗██████╗ ██╗ +██╔══██╗██╔════╝██╔══██╗██║ +██████╔╝█████╗ ██████╔╝██║ +██╔══██╗██╔══╝ ██╔═══╝ ██║ +██║ ██║███████╗██║ ███████╗ +╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ + +Read-Evaluate-Print Loop + +''' + + +def hack_error_message(exception): + ''' + Some of the Python exception messages (such as when you attempt to + shift a number by a negative amount of bits) are used as Joy error + messages. They should start with a capital letter and end with a + period. This function takes care of that. + ''' + message = str(exception) + if message[0].islower(): + message = message[0].swapcase() + message[1:] + if '.' != message[-1]: + message += '.' + print(message, file=stderr) + + +def repl(stack=(), dictionary=None): + ''' + Read-Evaluate-Print Loop + + Accept input and run it on the stack, loop. + + :param stack stack: The stack. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :rtype: stack + + ''' + if dictionary is None: + dictionary = {} + try: + while True: + try: + text = input('joy? ') + except (EOFError, KeyboardInterrupt): + break + try: + stack, dictionary = run(text, stack, dictionary) + except UnknownSymbolError as sym: + print('Unknown:', sym, file=stderr) + except SystemExit as e: + raise SystemExit from e + except Exception as e: + hack_error_message(e) + print(stack_to_string(stack)) + except SystemExit as e: + raise SystemExit from e + except: + print_exc() + print() + return stack + + +def run(text, stack, dictionary): + ''' + Return the stack resulting from running the Joy code text on the stack. + + :param str text: Joy code. + :param stack stack: The stack. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :rtype: (stack, (), dictionary) + + ''' + return joy(stack, text_to_expression(text), dictionary) + + +def interp(stack=(), dictionary=None): + ''' + Simple REPL with no extra output, suitable for use in scripts. + ''' + if dictionary is None: + dictionary = {} + try: + while True: + try: + text = input() + except (EOFError, KeyboardInterrupt): + break + try: + stack, dictionary = run(text, stack, dictionary) + except UnknownSymbolError as sym: + print('Unknown:', sym, file=stderr) + except ( + StackUnderflowError, + NotABoolError, + NotAListError, + NotAnIntError, + ) as e: + print(e, file=stderr) + except SystemExit as e: + raise SystemExit from e + except Exception as e: + hack_error_message(e) + print(stack_to_string(stack)) + except SystemExit as e: + raise SystemExit from e + except: + print_exc() + return stack + + +''' +██████╗ ██╗ ██████╗████████╗██╗ ██████╗ ███╗ ██╗ █████╗ ██████╗ ██╗ ██╗ +██╔══██╗██║██╔════╝╚══██╔══╝██║██╔═══██╗████╗ ██║██╔══██╗██╔══██╗╚██╗ ██╔╝ +██║ ██║██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████║██████╔╝ ╚████╔╝ +██║ ██║██║██║ ██║ ██║██║ ██║██║╚██╗██║██╔══██║██╔══██╗ ╚██╔╝ +██████╔╝██║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║██║ ██║██║ ██║ ██║ +╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ +Dictionary +''' + + +# This is the main dict we're building. +_dictionary = {} + + +def inscribe(function, d=_dictionary): + ''' + A decorator to inscribe functions into the default dictionary. + ''' + name = function.__name__ + if name.endswith('_'): + name = name[:-1] + d[name] = function + return function + + +def initialize(): + ''' + Return a dictionary of Joy functions for use with joy(). + ''' + return _dictionary.copy() + + +def SimpleFunctionWrapper(f): + ''' + Wrap functions that take and return just a stack. + ''' + + @wraps(f) + def SimpleFunctionWrapper_inner(stack, expr, dictionary): + return f(stack), expr, dictionary + + return SimpleFunctionWrapper_inner + + +@inscribe +def words(stack, expression, dictionary): + ''' + Put a list of all the words in alphabetical order onto the stack. + ''' + w = () + for name in reversed(sorted(dictionary)): + if name.startswith('_'): + continue + w = (Symbol(name), ()), w + return (w, stack), expression, dictionary + + +HELP_TEMPLATE = '''\ + +==== Help on %s ==== + +%s + +---- end ( %s ) +''' + + +@inscribe +def help_(stack, expression, dictionary): + ''' + Accepts a quoted symbol on the top of the stack and prints its docs. + ''' + ((symbol, _), stack) = stack + word = dictionary[symbol] + print(HELP_TEMPLATE % (symbol, getdoc(word), symbol)) + return stack, expression, dictionary + + +''' + ██████╗ ██████╗ ███╗ ███╗██████╗ ██╗███╗ ██╗ █████╗ ████████╗ ██████╗ ██████╗ ███████╗ +██╔════╝██╔═══██╗████╗ ████║██╔══██╗██║████╗ ██║██╔══██╗╚══██╔══╝██╔═══██╗██╔══██╗██╔════╝ +██║ ██║ ██║██╔████╔██║██████╔╝██║██╔██╗ ██║███████║ ██║ ██║ ██║██████╔╝███████╗ +██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║ +╚██████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████║ + ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ +Combinators +''' + + +@inscribe +def branch(stack, expr, dictionary): + ''' + Use a Boolean value to select one of two quoted programs to run. + + :: + + branch == roll< choice i + + :: + + False [F] [T] branch + -------------------------- + F + + True [F] [T] branch + ------------------------- + T + + ''' + then, else_, flag, stack = get_n_items(3, stack) + isnt_stack(then) + isnt_stack(else_) + isnt_bool(flag) + expr = push_quote((then if flag else else_), expr) + return stack, expr, dictionary + + +@inscribe +def dip(stack, expr, dictionary): + ''' + The dip combinator expects a quoted program on the stack and below it + some item, it hoists the item into the expression and runs the program + on the rest of the stack. + :: + + ... x [Q] dip + ------------------- + ... Q x + + ''' + quote, x, stack = get_n_items(2, stack) + isnt_stack(quote) + expr = push_quote((x, ()), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def i(stack, expr, dictionary): + ''' + The i combinator expects a quoted program on the stack and unpacks it + onto the pending expression for evaluation. + :: + + [Q] i + ----------- + Q + + ''' + quote, stack = get_n_items(1, stack) + isnt_stack(quote) + return stack, push_quote(quote, expr), dictionary + + +S_loop = Symbol('loop') + + +@inscribe +def loop(stack, expr, dictionary): + ''' + Basic loop combinator. + :: + + ... True [Q] loop + ----------------------- + ... Q [Q] loop + + ... False [Q] loop + ------------------------ + ... + + ''' + quote, stack = get_n_items(1, stack) + isnt_stack(quote) + flag, stack = get_n_items(1, stack) + isnt_bool(flag) + if flag: + expr = push_quote((quote, (S_loop, ())), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def quit(stack, expr, dictionary): + ''' + Stop the interpreter. + ''' + raise SystemExit + + +''' + ██████╗ ██████╗ ██████╗ ███████╗ ██╗ ██╗ ██████╗ ██████╗ ██████╗ ███████╗ +██╔════╝██╔═══██╗██╔══██╗██╔════╝ ██║ ██║██╔═══██╗██╔══██╗██╔══██╗██╔════╝ +██║ ██║ ██║██████╔╝█████╗ ██║ █╗ ██║██║ ██║██████╔╝██║ ██║███████╗ +██║ ██║ ██║██╔══██╗██╔══╝ ██║███╗██║██║ ██║██╔══██╗██║ ██║╚════██║ +╚██████╗╚██████╔╝██║ ██║███████╗ ╚███╔███╔╝╚██████╔╝██║ ██║██████╔╝███████║ + ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ +Core Words +''' + + +@inscribe +@SimpleFunctionWrapper +def clear(stack): + ''' + Clear everything from the stack. + :: + + clear == stack [pop stack] loop + + ... clear + --------------- + + ''' + return () + + +@inscribe +@SimpleFunctionWrapper +def concat_(stack): + ''' + Concatinate the two lists on the top of the stack. + :: + + [a b c] [d e f] concat + ---------------------------- + [a b c d e f] + + ''' + tos, second, stack = get_n_items(2, stack) + return concat(second, tos), stack + + +@inscribe +@SimpleFunctionWrapper +def cons(stack): + ''' + Given an item and a list, append the item to the list to make a new list. + :: + + a [...] cons + ------------------ + [a ...] + + Cons is a venerable old function from Lisp + ( https://en.wikipedia.org/wiki/Cons#Lists ). + Its inverse operation is uncons. + ''' + s0, stack = get_n_items(1, stack) + isnt_stack(s0) + a1, stack = get_n_items(1, stack) + return ((a1, s0), stack) + + +@inscribe +@SimpleFunctionWrapper +def dup(stack): + ''' + "Dup"licate the top item on the stack. + :: + + a dup + ----------- + a a + + ''' + a1, stack = get_n_items(1, stack) + return a1, (a1, stack) + + +@inscribe +@SimpleFunctionWrapper +def first(stack): + ''' + Replace a list with its first item. + + [a ...] + -------------- + a + + ''' + s0, stack = get_n_items(1, stack) + isnt_stack(s0) + a1, _ = get_n_items(1, s0) + return a1, stack + + +@inscribe +@SimpleFunctionWrapper +def pop(stack): + ''' + Pop the top item from the stack and discard it. + + a pop + ----------- + + ''' + try: + _, stack = stack + except ValueError: + raise StackUnderflowError('Cannot pop empty stack.') from None + return stack + + +@inscribe +@SimpleFunctionWrapper +def rest(stack): + ''' + Replace a list with its tail. + + [a b c] rest + ------------------ + [b c] + + ''' + s0, stack = get_n_items(1, stack) + isnt_stack(s0) + try: + _, s1 = s0 + except ValueError: + raise StackUnderflowError( + 'Cannot take rest of empty list.' + ) from None + return s1, stack + + +@inscribe +@SimpleFunctionWrapper +def stack(stack): + ''' + Put the stack onto the stack. + + ... c b a stack + --------------------------- + ... c b a [a b c ...] + + ''' + return stack, stack + + +@inscribe +@SimpleFunctionWrapper +def swaack(stack): + ''' + Swap stack. Take a list from the top of the stack, replace the stack + with the list, and put the old stack onto it. + + 1 2 3 [4 5 6] swaack + -------------------------- + 6 5 4 [3 2 1] + + ''' + s1, s0 = get_n_items(1, stack) + isnt_stack(s1) + return s0, s1 + + +@inscribe +@SimpleFunctionWrapper +def swap(stack): + ''' + Swap the top two items on the stack. + + a b swap + -------------- + b a + + ''' + a2, a1, stack = get_n_items(2, stack) + return (a1, (a2, stack)) + + +def BinaryLogicWrapper(f): + ''' + Wrap functions that take two numbers and return a single result. + ''' + + @wraps(f) + def BinaryLogicWrapper_inner(stack, expression, dictionary): + a, b, stack = get_n_items(2, stack) + isnt_bool(a) + isnt_bool(b) + result = f(b, a) + return (result, stack), expression, dictionary + + return BinaryLogicWrapper_inner + + +def BinaryMathWrapper(func): + ''' + Wrap functions that take two numbers and return a single result. + ''' + + @wraps(func) + def BinaryMathWrapper_inner(stack, expression, dictionary): + a, b, stack = get_n_items(2, stack) + isnt_int(a) + isnt_int(b) + result = func(b, a) + return (result, stack), expression, dictionary + + return BinaryMathWrapper_inner + + +def UnaryLogicWrapper(f): + ''' + Wrap functions that take one argument and return a single result. + ''' + + @wraps(f) + def UnaryLogicWrapper_inner(stack, expression, dictionary): + a, stack = get_n_items(1, stack) + isnt_bool(a) + result = f(a) + return (result, stack), expression, dictionary + + return UnaryLogicWrapper_inner + + +def UnaryMathWrapper(f): + ''' + Wrap functions that take one argument and return a single result. + ''' + + @wraps(f) + def UnaryMathWrapper_inner(stack, expression, dictionary): + a, stack = get_n_items(1, stack) + isnt_int(a) + result = f(a) + return (result, stack), expression, dictionary + + return UnaryMathWrapper_inner + + +def UnaryWrapper(f): + ''' + Wrap functions that take one argument and return a single result. + ''' + + @wraps(f) + def UnaryWrapper_inner(stack, expression, dictionary): + a, stack = get_n_items(1, stack) + result = f(a) + return (result, stack), expression, dictionary + + return UnaryWrapper_inner + + +for F in ( + ## ██████╗ ██████╗ ███╗ ███╗██████╗ █████╗ ██████╗ ██╗███████╗██╗ ██████╗ ███╗ ██╗ + ##██╔════╝██╔═══██╗████╗ ████║██╔══██╗██╔══██╗██╔══██╗██║██╔════╝██║██╔═══██╗████╗ ██║ + ##██║ ██║ ██║██╔████╔██║██████╔╝███████║██████╔╝██║███████╗██║██║ ██║██╔██╗ ██║ + ##██║ ██║ ██║██║╚██╔╝██║██╔═══╝ ██╔══██║██╔══██╗██║╚════██║██║██║ ██║██║╚██╗██║ + ##╚██████╗╚██████╔╝██║ ╚═╝ ██║██║ ██║ ██║██║ ██║██║███████║██║╚██████╔╝██║ ╚████║ + ## ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ + BinaryMathWrapper(operator.eq), + BinaryMathWrapper(operator.ge), + BinaryMathWrapper(operator.gt), + BinaryMathWrapper(operator.le), + BinaryMathWrapper(operator.lt), + BinaryMathWrapper(operator.ne), + ##██╗ ██████╗ ██████╗ ██╗ ██████╗ + ##██║ ██╔═══██╗██╔════╝ ██║██╔════╝ + ##██║ ██║ ██║██║ ███╗██║██║ + ##██║ ██║ ██║██║ ██║██║██║ + ##███████╗╚██████╔╝╚██████╔╝██║╚██████╗ + ##╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝ + UnaryWrapper(bool), # Convert any value to Boolean. + # (The only polymorphic function.) + BinaryLogicWrapper(operator.xor), + BinaryLogicWrapper(operator.and_), + BinaryLogicWrapper(operator.or_), + UnaryLogicWrapper(operator.not_), + ##███╗ ███╗ █████╗ ████████╗██╗ ██╗ + ##████╗ ████║██╔══██╗╚══██╔══╝██║ ██║ + ##██╔████╔██║███████║ ██║ ███████║ + ##██║╚██╔╝██║██╔══██║ ██║ ██╔══██║ + ##██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║ + ##╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ + BinaryMathWrapper(operator.lshift), + BinaryMathWrapper(operator.rshift), + BinaryMathWrapper(operator.add), + BinaryMathWrapper(operator.floordiv), + BinaryMathWrapper(operator.mod), + BinaryMathWrapper(operator.mul), + BinaryMathWrapper(operator.pow), + BinaryMathWrapper(operator.sub), + UnaryMathWrapper(abs), + UnaryMathWrapper(operator.neg), +): + inscribe(F) + + +for alias, name in ( + ('+', 'add'), + ('-', 'sub'), + ('/', 'floordiv'), + ('%', 'mod'), + ('*', 'mul'), + ('>', 'gt'), + ('<', 'lt'), + ('>=', 'ge'), + ('<=', 'le'), + ('!=', 'ne'), + ('<>', 'ne'), + ('=', 'eq'), + ): + try: + _dictionary[alias] = _dictionary[name] + except KeyError: + pass + + +''' +██████╗ ███████╗███████╗██╗███╗ ██╗██╗████████╗██╗ ██████╗ ███╗ ██╗███████╗ +██╔══██╗██╔════╝██╔════╝██║████╗ ██║██║╚══██╔══╝██║██╔═══██╗████╗ ██║██╔════╝ +██║ ██║█████╗ █████╗ ██║██╔██╗ ██║██║ ██║ ██║██║ ██║██╔██╗ ██║███████╗ +██║ ██║██╔══╝ ██╔══╝ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ +██████╔╝███████╗██║ ██║██║ ╚████║██║ ██║ ██║╚██████╔╝██║ ╚████║███████║ +╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ +Definitions +''' + + +class Def(object): + ''' + Definitions are given by equations: + + name foo bar baz ... + + When a definition symbol is evaluated its body expression is put onto + the pending expression. + ''' + + # tribar = '\u2261' # '≡' + + def __init__(self, name, body): + self.__doc__ = f'{name} {expression_to_string(body)}' + self.__name__ = name + self.body = body + + def __call__(self, stack, expr, dictionary): + return stack, push_quote(self.body, expr), dictionary + + @classmethod + def load_definitions(class_, stream, dictionary): + ''' + Given an iterable of lines (strings) and a dictionary put + definitions into the dictionary. + ''' + for line in stream: + name, body = text_to_expression(line) + if name not in dictionary: + # filthy hack + if name.endswith('_'): + name = name + '_' + # See, I want to define some Python functions and use inscribe() + # as a decorator and get the Joy symbol from the name of the + # Python function. But some Joy names are the same as some + # Python names, so to differentiate them I decided on a convention + # of putting an underscore after the Python function name and + # stripping it off in inscribe(). But now that there's a definition + # that ends with an underscore ('_\/_' logical Boolean xor) it's + # getting stripped off (to make '_\/'.) So, rather than deal with + # all that in a reasonable way, I'm just going to hack it here and + # add an extra underscore for inscribe() to pick off. + # As I say, it's a filthy hack, but it works, and it took less time + # to write than this note explaining it. :) + inscribe(class_(name, body), dictionary) + + +''' +████████╗██╗ ██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗███████╗ +╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔════╝ ██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝██╔════╝ + ██║ ╚████╔╝ ██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ███████╗ + ██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ╚════██║ + ██║ ██║ ██║ ███████╗ ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗███████║ + ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ +Type Checks + +Simple guard functions, for type inference see the Prolog versions. +''' + + +class NotAListError(Exception): + ''' + Raised when a stack is expected but not received. + ''' + + +class NotAnIntError(Exception): + pass + + +class NotABoolError(Exception): + pass + + +def isnt_int(i): + ''' + Raise NotAnIntError if i isn't an integer. + (Booleans are not integers in Joy.) + ''' + if not isinstance(i, int) or isinstance(i, bool): + raise NotAnIntError('Not an integer.') + return i + + +def isnt_bool(b): + ''' + Raise NotABoolError if b isn't a Boolean. + ''' + if not isinstance(b, bool): + raise NotABoolError('Not a Boolean value.') + return b + + +def isnt_stack(el): + ''' + Raise NotAListError if el isn't a stack/quote/list. + ''' + if not isinstance(el, tuple): + raise NotAListError('Not a list.') + return el + + +# Put these into the dictionary so users can, uh, use them. +# Not as decorators because we want to use the unwrapped +# functions in our python code. +inscribe(UnaryWrapper(isnt_int)) +inscribe(UnaryWrapper(isnt_bool)) +inscribe(UnaryWrapper(isnt_stack)) + + +''' +███████╗██╗ ██╗████████╗██████╗ █████╗ +██╔════╝╚██╗██╔╝╚══██╔══╝██╔══██╗██╔══██╗ +█████╗ ╚███╔╝ ██║ ██████╔╝███████║ +██╔══╝ ██╔██╗ ██║ ██╔══██╗██╔══██║ +███████╗██╔╝ ██╗ ██║ ██║ ██║██║ ██║ +╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ +''' + + +@inscribe +def trace(stack, expr, dictionary): + '''Evaluate a Joy expression on a stack and print a trace. + + This function is just like the `i` combinator but it also prints a + trace of the evaluation + + :param stack stack: The stack. + :param stack expression: The expression to evaluate. + :param dict dictionary: A ``dict`` mapping names to Joy functions. + :rtype: (stack, (), dictionary) + + ''' + quote, stack = get_n_items(1, stack) + isnt_stack(quote) + history = [] + append = history.append + local_expr = push_quote(quote) + try: + while local_expr: + if len(history) > 1000: + break + append((stack, local_expr)) + term, local_expr = next_term(local_expr) + if isinstance(term, Symbol): + try: + func = dictionary[term] + except KeyError: + print(trace_to_string(history)) + raise UnknownSymbolError(term) from None + stack, local_expr, dictionary = func(stack, local_expr, dictionary) + else: + stack = term, stack + except: + print(trace_to_string(history)) + raise + append((stack, local_expr)) + print(trace_to_string(history)) + return stack, expr, dictionary + + +def trace_to_string(history): + max_stack_length = 0 + lines = [] + for stack, expression in history: + stack = stack_to_string(stack) + expression = expr_to_string(expression) + length = len(stack) + max_stack_length = max(max_stack_length, length) + lines.append((length, stack, expression)) + return '\n'.join( + # Prefix spaces to line up '•'s. + (' ' * (max_stack_length - length) + f'{stack} • {expression}') + for i, (length, stack, expression) in enumerate(lines) + ) + + +S_swaack = Symbol('swaack') +S_genrec = Symbol('genrec') +S_ifte = Symbol('ifte') +S_infra = Symbol('infra') +S_first = Symbol('first') +S_primrec = Symbol('primrec') +S_choice = Symbol('choice') +S_i = Symbol('i') +S_cond = Symbol('cond') +S_step = Symbol('step') +S_times = Symbol('times') + +_ifte_ = (S_infra, (S_first, (S_choice, (S_i, ())))) + + +def dnd(stack, from_index, to_index): + ''' + Given a stack and two indices return a rearranged stack. + First remove the item at from_index and then insert it at to_index, + the second index is relative to the stack after removal of the item + at from_index. + + This function reuses all of the items and as much of the stack as it + can. It's meant to be used by remote clients to support drag-n-drop + rearranging of the stack from e.g. the StackListbox. + ''' + assert 0 <= from_index + assert 0 <= to_index + if from_index == to_index: + return stack + head, n = [], from_index + while True: + item, stack = stack + n -= 1 + if n < 0: + break + head.append(item) + assert len(head) == from_index + # now we have two cases: + diff = from_index - to_index + if diff < 0: + # from < to + # so the destination index is still in the stack + while diff: + h, stack = stack + head.append(h) + diff += 1 + else: + # from > to + # so the destination is in the head list + while diff: + stack = head.pop(), stack + diff -= 1 + stack = item, stack + while head: + stack = head.pop(), stack + return stack + + +def pick(stack, n): + ''' + Return the nth item on the stack. + + :param stack stack: A stack. + :param int n: An index into the stack. + :raises ValueError: if ``n`` is less than zero. + :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. + :rtype: whatever + ''' + if n < 0: + raise ValueError + while True: + try: + item, stack = stack + except ValueError: + raise IndexError + n -= 1 + if n < 0: + break + return item + + +@inscribe +def inscribe_(stack, expression, dictionary): + ''' + Create a new Joy function definition in the Joy dictionary. A + definition is given as a quote with a name followed by a Joy + expression. for example: + + [sqr dup mul] inscribe + + ''' + (name, body), stack = stack + inscribe(Def(name, body), dictionary) + return stack, expression, dictionary + + +@inscribe +@SimpleFunctionWrapper +def getitem(stack): + ''' + :: + + getitem == drop first + + Expects an integer and a quote on the stack and returns the item at the + nth position in the quote counting from 0. + :: + + [a b c d] 0 getitem + ------------------------- + a + + ''' + n, (Q, stack) = stack + return pick(Q, n), stack + + +@inscribe +@SimpleFunctionWrapper +def drop(stack): + ''' + :: + + drop == [rest] times + + Expects an integer and a quote on the stack and returns the quote with + n items removed off the top. + :: + + [a b c d] 2 drop + ---------------------- + [c d] + + ''' + n, (Q, stack) = stack + while n > 0: + try: + _, Q = Q + except ValueError: + raise StackUnderflowError + n -= 1 + return Q, stack + + +@inscribe +@SimpleFunctionWrapper +def take(stack): + ''' + Expects an integer and a quote on the stack and returns the quote with + just the top n items in reverse order (because that's easier and you can + use reverse if needed.) + :: + + [a b c d] 2 take + ---------------------- + [b a] + + ''' + n, (Q, stack) = stack + x = () + while n > 0: + try: + item, Q = Q + except ValueError: + raise StackUnderflowError + x = item, x + n -= 1 + return x, stack + + +@inscribe +def gcd2(stack, expression, dictionary): + '''Compiled GCD function.''' + (v1, (v2, stack)) = stack + tos = True + while tos: + v3 = v2 % v1 + tos = v3 > 0 + (v1, (v2, stack)) = (v3, (v1, stack)) + return (v2, stack), expression, dictionary + + +@inscribe +@SimpleFunctionWrapper +def choice(stack): + ''' + Use a Boolean value to select one of two items. + :: + + A B false choice + ---------------------- + A + + + A B true choice + --------------------- + B + + ''' + (if_, (then, (else_, stack))) = stack + assert isinstance(if_, bool), repr(if_) + return then if if_ else else_, stack + + +@inscribe +@SimpleFunctionWrapper +def select(stack): + ''' + Use a Boolean value to select one of two items from a sequence. + :: + + [A B] false select + ------------------------ + A + + + [A B] true select + ----------------------- + B + + The sequence can contain more than two items but not fewer. + Currently Python semantics are used to evaluate the "truthiness" of the + Boolean value (so empty string, zero, etc. are counted as false, etc.) + ''' + (flag, (choices, stack)) = stack + (else_, (then, _)) = choices + return then if flag else else_, stack + + +@inscribe +@SimpleFunctionWrapper +def max_(S): + '''Given a list find the maximum.''' + tos, stack = S + return max(iter_stack(tos)), stack + + +@inscribe +@SimpleFunctionWrapper +def min_(S): + '''Given a list find the minimum.''' + tos, stack = S + return min(iter_stack(tos)), stack + + +@inscribe +@SimpleFunctionWrapper +def sum_(S): + ''' + Given a quoted sequence of numbers return the sum. + :: + + sum == 0 swap [+] step + + ''' + tos, stack = S + return sum(iter_stack(tos)), stack + + +@inscribe +@SimpleFunctionWrapper +def remove(S): + ''' + Expects an item on the stack and a quote under it and removes that item + from the the quote. The item is only removed once. If the list is + empty or the item isn't in the list then the list is unchanged. + :: + + [1 2 3 1] 1 remove + ------------------------ + [2 3 1] + + ''' + (item, (quote, stack)) = S + return _remove(item, quote), stack + + +def _remove(item, quote): + try: head, tail = quote + except ValueError: return quote + return tail if head == item else (head, _remove(item, tail)) + + +@inscribe +@SimpleFunctionWrapper +def unique(S): + '''Given a list remove duplicate items.''' + tos, stack = S + I = list(iter_stack(tos)) + return list_to_stack(sorted(set(I), key=I.index)), stack + + +@inscribe +@SimpleFunctionWrapper +def sort_(S): + '''Given a list return it sorted.''' + tos, stack = S + return list_to_stack(sorted(iter_stack(tos))), stack + + +@inscribe +@SimpleFunctionWrapper +def disenstacken(stack): + ''' + The disenstacken operator expects a list on top of the stack and makes that + the stack discarding the rest of the stack. + ''' + return stack[0] + + +@inscribe +@SimpleFunctionWrapper +def reverse(S): + ''' + Reverse the list on the top of the stack. + :: + + reverse == [] swap shunt + ''' + (tos, stack) = S + res = () + for term in iter_stack(tos): + res = term, res + return res, stack + + +@inscribe +@SimpleFunctionWrapper +def shunt(stack): + ''' + Like concat but reverses the top list into the second. + :: + + shunt == [swons] step == reverse swap concat + + [a b c] [d e f] shunt + --------------------------- + [f e d a b c] + + ''' + (tos, (second, stack)) = stack + while tos: + term, tos = tos + second = term, second + return second, stack + + +@inscribe +@SimpleFunctionWrapper +def zip_(S): + ''' + Replace the two lists on the top of the stack with a list of the pairs + from each list. The smallest list sets the length of the result list. + ''' + (tos, (second, stack)) = S + accumulator = [ + (a, (b, ())) + for a, b in zip(iter_stack(tos), iter_stack(second)) + ] + return list_to_stack(accumulator), stack + + +@inscribe +@SimpleFunctionWrapper +def succ(S): + '''Increment TOS.''' + (tos, stack) = S + return tos + 1, stack + + +@inscribe +@SimpleFunctionWrapper +def pred(S): + '''Decrement TOS.''' + (tos, stack) = S + return tos - 1, stack + + +@inscribe +@SimpleFunctionWrapper +def pm(stack): + ''' + Plus or minus + :: + + a b pm + ------------- + a+b a-b + + ''' + a, (b, stack) = stack + p, m, = b + a, b - a + return m, (p, stack) + + +@inscribe +@SimpleFunctionWrapper +def divmod_(S): + ''' + Similarly to pm ("Plus or minus") this function computes + both the + :: + + a b divmod + --------------------- + a b div a b mod + --------------------- + q r + + Where: q * b + r == a + + ''' + y, (x, stack) = S + q, r = divmod(x, y) + return r, (q, stack) + + +@inscribe +def sharing(stack, expression, dictionary): + '''Print redistribution information.''' + print("You may convey verbatim copies of the Program's source code as" + ' you receive it, in any medium, provided that you conspicuously' + ' and appropriately publish on each copy an appropriate copyright' + ' notice; keep intact all notices stating that this License and' + ' any non-permissive terms added in accord with section 7 apply' + ' to the code; keep intact all notices of the absence of any' + ' warranty; and give all recipients a copy of this License along' + ' with the Program.' + ' You should have received a copy of the GNU General Public License' + ' along with Thun. If not see .') + return stack, expression, dictionary + + +@inscribe +def warranty(stack, expression, dictionary): + '''Print warranty information.''' + print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY' + ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE' + ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM' + ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR' + ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES' + ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE' + ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS' + ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE' + ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.') + return stack, expression, dictionary + + +@inscribe +def x(stack, expr, dictionary): + ''' + :: + + x == dup i + + ... [Q] x = ... [Q] dup i + ... [Q] x = ... [Q] [Q] i + ... [Q] x = ... [Q] Q + + ''' + quote, _ = stack + isnt_stack(quote) + return stack, push_quote(quote, expr), dictionary + + +@inscribe +def b(stack, expr, dictionary): + ''' + :: + + b == [i] dip i + + ... [P] [Q] b == ... [P] i [Q] i + ... [P] [Q] b == ... P Q + + ''' + q, (p, (stack)) = stack + isnt_stack(q) + isnt_stack(p) + expr = push_quote(q, expr) + expr = push_quote(p, expr) + return stack, expr, dictionary + + +@inscribe +def ii(stack, expr, dictionary): + ''' + :: + + ... a [Q] ii + ------------------ + ... Q a Q + + ''' + quote, (a, stack) = stack + isnt_stack(quote) + expr = push_quote((a, quote), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def dupdip(stack, expr, dictionary): + ''' + :: + + [F] dupdip == dup [F] dip + + ... a [F] dupdip + ... a dup [F] dip + ... a a [F] dip + ... a F a + + ''' + quote, stack = stack + isnt_stack(quote) + a = stack[0] + expr = push_quote((a, ()), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def infra(stack, expr, dictionary): + ''' + Accept a quoted program and a list on the stack and run the program + with the list as its stack. Does not affect the rest of the stack. + :: + + ... [a b c] [Q] . infra + ----------------------------- + c b a . Q [...] swaack + + ''' + quote, aggregate, stack = get_n_items(2, stack) + isnt_stack(quote) + isnt_stack(aggregate) + expr = push_quote((stack, (S_swaack, ())), expr) + expr = push_quote(quote, expr) + return aggregate, expr, dictionary + + +@inscribe +def genrec(stack, expr, dictionary): + ''' + General Recursion Combinator. + :: + + [if] [then] [rec1] [rec2] genrec + --------------------------------------------------------------------- + [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte + + From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun: + "The genrec combinator takes four program parameters in addition to + whatever data parameters it needs. Fourth from the top is an if-part, + followed by a then-part. If the if-part yields true, then the then-part + is executed and the combinator terminates. The other two parameters are + the rec1-part and the rec2-part. If the if-part yields false, the + rec1-part is executed. Following that the four program parameters and + the combinator are again pushed onto the stack bundled up in a quoted + form. Then the rec2-part is executed, where it will find the bundled + form. Typically it will then execute the bundled form, either with i or + with app2, or some other combinator." + + The way to design one of these is to fix your base case [then] and the + test [if], and then treat rec1 and rec2 as an else-part "sandwiching" + a quotation of the whole function. + + For example, given a (general recursive) function 'F': + :: + + F == [I] [T] [R1] [R2] genrec + + If the [I] if-part fails you must derive R1 and R2 from: + :: + + ... R1 [F] R2 + + Just set the stack arguments in front, and figure out what R1 and R2 + have to do to apply the quoted [F] in the proper way. In effect, the + genrec combinator turns into an ifte combinator with a quoted copy of + the original definition in the else-part: + :: + + F == [I] [T] [R1] [R2] genrec + == [I] [T] [R1 [F] R2] ifte + + Primitive recursive functions are those where R2 == i. + :: + + P == [I] [T] [R] tailrec + == [I] [T] [R [P] i] ifte + == [I] [T] [R P] ifte + + ''' + rec2, rec1, then, if_, stack = get_n_items(4, stack) + isnt_stack(if_) + isnt_stack(then) + isnt_stack(rec1) + isnt_stack(rec2) + F = (if_, (then, (rec1, (rec2, (S_genrec, ()))))) + else_ = concat(rec1, (F, rec2)) + stack = (else_, (then, (if_, stack))) + expr = push_quote((S_ifte, ()), expr) + return stack, expr, dictionary + + +@inscribe +def map_(stack, expr, dictionary): + ''' + Run the quoted program on TOS on the items in the list under it, push a + new list with the results in place of the program and original list. + ''' + quote, aggregate, stack = get_n_items(2, stack) + isnt_stack(quote) + isnt_stack(aggregate) + if not aggregate: + return (aggregate, stack), expr, dictionary + batch = () + for term in iter_stack(aggregate): + s = term, stack + batch = (s, (quote, (S_infra, (S_first, batch)))) + stack = (batch, ((), stack)) + expr = push_quote((S_infra, ()), expr) + return stack, expr, dictionary + + +@inscribe +def primrec(stack, expr, dictionary): + ''' + From the "Overview of the language JOY": + + > The primrec combinator expects two quoted programs in addition to a + data parameter. For an integer data parameter it works like this: If + the data parameter is zero, then the first quotation has to produce + the value to be returned. If the data parameter is positive then the + second has to combine the data parameter with the result of applying + the function to its predecessor.:: + + 5 [1] [*] primrec + + > Then primrec tests whether the top element on the stack (initially + the 5) is equal to zero. If it is, it pops it off and executes one of + the quotations, the [1] which leaves 1 on the stack as the result. + Otherwise it pushes a decremented copy of the top element and + recurses. On the way back from the recursion it uses the other + quotation, [*], to multiply what is now a factorial on top of the + stack by the second element on the stack.:: + + n [Base] [Recur] primrec + + 0 [Base] [Recur] primrec + ------------------------------ + Base + + n [Base] [Recur] primrec + ------------------------------------------ n > 0 + n (n-1) [Base] [Recur] primrec Recur + + ''' + recur, base, n, stack = get_n_items(3, stack) + isnt_stack(recur) + isnt_stack(base) + if n <= 0: + expr = push_quote(base, expr) + else: + expr = push_quote(recur, expr) + expr = push_quote((S_primrec, ()), expr) + stack = recur, (base, (n - 1, (n, stack))) + return stack, expr, dictionary + + +@inscribe +def ifte(stack, expr, dictionary): + ''' + If-Then-Else Combinator + + + ... [if] [then] [else] ifte + ------------------------------------------------------- + ... [else] [then] [...] [if] infra first choice i + + + Has the effect of grabbing a copy of the stack on which to run the + if-part using infra. + ''' + else_, then, if_, stack = get_n_items(3, stack) + expr = push_quote(_ifte_, expr) + stack = (if_, (stack, (then, (else_, stack)))) + return stack, expr, dictionary + + +@inscribe +def cond(stack, expr, dictionary): + ''' + This combinator works like a case statement. It expects a single quote + on the stack that must contain zero or more condition quotes and a + default quote. Each condition clause should contain a quoted predicate + followed by the function expression to run if that predicate returns + true. If no predicates return true the default function runs. + + It works by rewriting into a chain of nested `ifte` expressions, e.g.:: + + [[D]] cond + ---------------- + D + + [[[IF] THEN] [D]] cond + ---------------------------- (with single condition, same as ifte) + [IF] [THEN] [D] ifte + + + [[[IF] THEN] ...] cond + ----------------------------------- (multiple conditions) + [IF] [THEN] [[...] cond] ifte + + + The middle case isn't actually implemented. It's implied by the + base case and the "multiple conditions" case. + ''' + conditions, stack = get_n_items(1, stack) + isnt_stack(conditions) + if not conditions: + raise StackUnderflowError('cond without default clause') + + condition_clause, conditions = conditions + isnt_stack(condition_clause) + + if not conditions: # This is the default clause, run it. + expr = push_quote(condition_clause, expr) + else: + if_, then = get_n_items(1, condition_clause) + isnt_stack(if_) + else_ = (conditions, (S_cond, ())) + stack = (else_, (then, (if_, stack))) + expr = push_quote((S_ifte, ()), expr) + + return stack, expr, dictionary + + +@inscribe +def dipd(stack, expr, dictionary): + ''' + The dipd combinator is like dip but expects two items. + + ... y x [Q] dipd + ---------------------- + ... Q y x + + ''' + quote, x, y, stack = get_n_items(3, stack) + isnt_stack(quote) + expr = push_quote((y, (x, ())), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def dipdd(stack, expr, dictionary): + ''' + The dipdd combinator is like dip but expects three items. + + ... y x z [Q] dipdd + ------------------------- + ... Q y x z + + ''' + quote, x, y, z, stack = get_n_items(3, stack) + isnt_stack(quote) + expr = push_quote((z, (y, (x, ()))), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def cmp_(stack, expr, dictionary): + ''' + cmp takes two values and three quoted programs on the stack and runs + one of the three depending on the results of comparing the two values: + :: + + a b [G] [E] [L] cmp + ------------------------- a > b + G + + a b [G] [E] [L] cmp + ------------------------- a = b + E + + a b [G] [E] [L] cmp + ------------------------- a < b + L + ''' + L, E, G, b, a, stack = get_n_items(5, stack) + isnt_stack(L) + isnt_stack(E) + isnt_stack(G) + isnt_int(b) + isnt_int(a) + expr = push_quote(G if a > b else L if a < b else E, expr) + return stack, expr, dictionary + + +@inscribe +def step(stack, expr, dictionary): + ''' + Run a quoted program on each item in a sequence. + :: + + ... [] [Q] step + --------------------- + ... + + + ... [a] [Q] step + ---------------------- + ... a Q + + + ... [a b c] [Q] step + ---------------------------- + ... a Q [b c] [Q] step + + The step combinator executes the quotation on each member of the list + on top of the stack. + ''' + quote, aggregate, stack = get_n_items(2, stack) + isnt_stack(quote) + isnt_stack(aggregate) + if not aggregate: + return stack, expr, dictionary + + head, tail = aggregate + stack = head, stack + if tail: + expr = push_quote((tail, (quote, (S_step, ()))), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def times(stack, expr, dictionary): + ''' + times == [-- dip] cons [swap] infra [0 >] swap while pop + :: + + ... n [Q] times + --------------------- w/ n <= 0 + ... + + + ... 1 [Q] times + --------------------- + ... Q + + + ... n [Q] times + --------------------------- w/ n > 1 + ... Q (n-1) [Q] times + + ''' + # times == [-- dip] cons [swap] infra [0 >] swap while pop + quote, n, stack = get_n_items(2, stack) + isnt_stack(quote) + isnt_int(n) + if n <= 0: + return stack, expr, dictionary + n -= 1 + if n: + expr = push_quote((n, (quote, (S_times, ()))), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +@SimpleFunctionWrapper +def _Tree_add_Ee(stack): + """ + :: + + ([a4 a5 ...1] a3 a2 a1 -- [a2 a3 ...1]) + + """ + (a1, (a2, (a3, ((a4, (a5, s1)), s2)))) = stack + return ((a2, (a3, s1)), s2) + + +@inscribe +@SimpleFunctionWrapper +def _Tree_delete_R0(stack): + """ + :: + + ([a2 ...1] a1 -- [a2 ...1] a2 a1 a1) + + """ + (a1, ((a2, s1), s2)) = stack + return (a1, (a1, (a2, ((a2, s1), s2)))) + + +@inscribe +@SimpleFunctionWrapper +def _Tree_delete_clear_stuff(stack): + """ + :: + + (a3 a2 [a1 ...1] -- [...1]) + + """ + ((a1, s1), (a2, (a3, s2))) = stack + return (s1, s2) + + +@inscribe +@SimpleFunctionWrapper +def _Tree_get_E(stack): + """ + :: + + ([a3 a4 ...1] a2 a1 -- a4) + + """ + (a1, (a2, ((a3, (a4, s1)), s2))) = stack + return (a4, s2) + + +@inscribe +@SimpleFunctionWrapper +def ccons(stack): + """ + :: + + (a2 a1 [...1] -- [a2 a1 ...1]) + + """ + (s1, (a1, (a2, s2))) = stack + return ((a2, (a1, s1)), s2) + + +##def cons(stack): +## """ +## :: +## +## (a1 [...0] -- [a1 ...0]) +## +## """ +## try: s0, stack = stack +## except ValueError: raise StackUnderflowError('Not enough values on stack.') +## if not isinstance(s0, tuple): raise NotAListError('Not a list.') +## try: a1, s23 = stack +## except ValueError: raise StackUnderflowError('Not enough values on stack.') +## return ((a1, s0), s23) + + +##def dup(stack): +## """ +## :: +## +## (a1 -- a1 a1) +## +## """ +## (a1, s23) = stack +## return (a1, (a1, s23)) + + +@inscribe +@SimpleFunctionWrapper +def dupd(stack): + """ + :: + + (a2 a1 -- a2 a2 a1) + + """ + (a1, (a2, s23)) = stack + return (a1, (a2, (a2, s23))) + + +@inscribe +@SimpleFunctionWrapper +def dupdd(stack): + """ + :: + + (a3 a2 a1 -- a3 a3 a2 a1) + + """ + (a1, (a2, (a3, s23))) = stack + return (a1, (a2, (a3, (a3, s23)))) + + +##def first(stack): +## """ +## :: +## +## ([a1 ...1] -- a1) +## +## """ +## ((a1, s1), s23) = stack +## return (a1, s23) + + +@inscribe +@SimpleFunctionWrapper +def first_two(stack): + """ + :: + + ([a1 a2 ...1] -- a1 a2) + + """ + ((a1, (a2, s1)), s2) = stack + return (a2, (a1, s2)) + + +@inscribe +@SimpleFunctionWrapper +def fourth(stack): + """ + :: + + ([a1 a2 a3 a4 ...1] -- a4) + + """ + ((a1, (a2, (a3, (a4, s1)))), s2) = stack + return (a4, s2) + + +@inscribe +@SimpleFunctionWrapper +def over(stack): + """ + :: + + (a2 a1 -- a2 a1 a2) + + """ + (a1, (a2, s23)) = stack + return (a2, (a1, (a2, s23))) + + +##def pop(stack): +## """ +## :: +## +## (a1 --) +## +## """ +## try: +## (a1, s23) = stack +## except ValueError: +## raise StackUnderflowError('Cannot pop empty stack.') +## return s23 + + +@inscribe +@SimpleFunctionWrapper +def popd(stack): + """ + :: + + (a2 a1 -- a1) + + """ + (a1, (a2, s23)) = stack + return (a1, s23) + + +@inscribe +@SimpleFunctionWrapper +def popdd(stack): + """ + :: + + (a3 a2 a1 -- a2 a1) + + """ + (a1, (a2, (a3, s23))) = stack + return (a1, (a2, s23)) + + +@inscribe +@SimpleFunctionWrapper +def popop(stack): + """ + :: + + (a2 a1 --) + + """ + (a1, (a2, s23)) = stack + return s23 + + +@inscribe +@SimpleFunctionWrapper +def popopd(stack): + """ + :: + + (a3 a2 a1 -- a1) + + """ + (a1, (a2, (a3, s23))) = stack + return (a1, s23) + + +@inscribe +@SimpleFunctionWrapper +def popopdd(stack): + """ + :: + + (a4 a3 a2 a1 -- a2 a1) + + """ + (a1, (a2, (a3, (a4, s23)))) = stack + return (a1, (a2, s23)) + + +##def rest(stack): +## """ +## :: +## +## ([a1 ...0] -- [...0]) +## +## """ +## try: +## s0, stack = stack +## except ValueError: +## raise StackUnderflowError +## if not isinstance(s0, tuple): +## raise NotAListError('Not a list.') +## try: +## _, s1 = s0 +## except ValueError: +## raise StackUnderflowError('Cannot take rest of empty list.') +## return (s1, stack) + + +@inscribe +@SimpleFunctionWrapper +def rolldown(stack): + """ + :: + + (a1 a2 a3 -- a2 a3 a1) + + """ + (a3, (a2, (a1, s23))) = stack + return (a1, (a3, (a2, s23))) + + +@inscribe +@SimpleFunctionWrapper +def rollup(stack): + """ + :: + + (a1 a2 a3 -- a3 a1 a2) + + """ + (a3, (a2, (a1, s23))) = stack + return (a2, (a1, (a3, s23))) + + +@inscribe +@SimpleFunctionWrapper +def rrest(stack): + """ + :: + + ([a1 a2 ...1] -- [...1]) + + """ + ((a1, (a2, s1)), s2) = stack + return (s1, s2) + + +@inscribe +@SimpleFunctionWrapper +def second(stack): + """ + :: + + ([a1 a2 ...1] -- a2) + + """ + ((a1, (a2, s1)), s2) = stack + return (a2, s2) + + +@inscribe +@SimpleFunctionWrapper +def stack(stack): + """ + :: + + (... -- ... [...]) + + """ + s0 = stack + return (s0, s0) + + +@inscribe +@SimpleFunctionWrapper +def stuncons(stack): + """ + :: + + (... a1 -- ... a1 a1 [...]) + + """ + (a1, s1) = stack + return (s1, (a1, (a1, s1))) + + +@inscribe +@SimpleFunctionWrapper +def stununcons(stack): + """ + :: + + (... a2 a1 -- ... a2 a1 a1 a2 [...]) + + """ + (a1, (a2, s1)) = stack + return (s1, (a2, (a1, (a1, (a2, s1))))) + + +##def swaack(stack): +## """ +## :: +## +## ([...1] -- [...0]) +## +## """ +## try: +## (s1, s0) = stack +## except ValueError: +## raise StackUnderflowError('Not enough values on stack.') +## if not isinstance(s1, tuple): +## raise NotAListError('Not a list.') +## return (s0, s1) + + +##def swap(stack): +## """ +## :: +## +## (a1 a2 -- a2 a1) +## +## """ +## try: +## (a2, (a1, s23)) = stack +## except ValueError: +## raise StackUnderflowError('Not enough values on stack.') +## return (a1, (a2, s23)) + + +@inscribe +@SimpleFunctionWrapper +def swons(stack): + """ + :: + + ([...1] a1 -- [a1 ...1]) + + """ + (a1, (s1, s2)) = stack + return ((a1, s1), s2) + + +@inscribe +@SimpleFunctionWrapper +def third(stack): + """ + :: + + ([a1 a2 a3 ...1] -- a3) + + """ + ((a1, (a2, (a3, s1))), s2) = stack + return (a3, s2) + + +@inscribe +@SimpleFunctionWrapper +def tuck(stack): + """ + :: + + (a2 a1 -- a1 a2 a1) + + """ + (a1, (a2, s23)) = stack + return (a1, (a2, (a1, s23))) + + +@inscribe +@SimpleFunctionWrapper +def uncons(stack): + """ + :: + + ([a1 ...0] -- a1 [...0]) + + """ + ((a1, s0), s23) = stack + return (s0, (a1, s23)) + + +@inscribe +@SimpleFunctionWrapper +def unit(stack): + """ + :: + + (a1 -- [a1 ]) + + """ + (a1, s23) = stack + return ((a1, ()), s23) + + +@inscribe +@SimpleFunctionWrapper +def unswons(stack): + """ + :: + + ([a1 ...1] -- [...1] a1) + + """ + ((a1, s1), s2) = stack + return (a1, (s1, s2)) + + +def default_defs(dictionary): + Def.load_definitions(DEFS, dictionary) + + +if __name__ == '__main__': + import sys + + J = interp if '-q' in sys.argv else repl + dictionary = initialize() + default_defs(dictionary) + try: + stack = J(dictionary=dictionary) + except SystemExit: + pass +## jcode = "5 10 [>][++][*]ifte" +## jcode = '1 2 [[+]] cond' +## jcode = '1 2 [[[>] -] [[<] +] [*]] cond' +## jcode = '2 1 [[[>] -] [[<] +] [*]] cond' +## jcode = '3 3 [[[>] -] [[<] +] [*]] cond' +## jcode = '3 dup [dup mul] times' +## jcode = '0 [1 2 3] [add] step' +## stack, _ = run(jcode, (), dictionary) + ##print(stack_to_string(stack)) diff --git a/implementations/Python/joy.py b/implementations/Python/joy.py index f136a44..9acd7d1 100755 --- a/implementations/Python/joy.py +++ b/implementations/Python/joy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- # -# Copyright © 2022 Simon Forman +# Copyright © 2022 - 2023 Simon Forman # # This file is part of Thun # @@ -266,6 +266,7 @@ def reversed_stack(stack): ██╔══╝ ██╔██╗ ██╔═══╝ ██╔══██╗██╔══╝ ╚════██║╚════██║██║██║ ██║██║╚██╗██║ ███████╗██╔╝ ██╗██║ ██║ ██║███████╗███████║███████║██║╚██████╔╝██║ ╚████║ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚═══╝ +Expression As elegant as it is to model the expression as a stack, it's not very efficient, as concatenating definitions and other quoted programs to @@ -300,6 +301,7 @@ def next_term(expression): ██╔═══╝ ██╔══██║██╔══██╗╚════██║██╔══╝ ██╔══██╗ ██║ ██║ ██║██║ ██║███████║███████╗██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═╝ +Parser There is a single function for converting text to joy expressions as well as a Symbol class and an Exception type. The Symbol @@ -389,6 +391,7 @@ def text_to_expression(text): ██╔═══╝ ██╔══██╗██║██║╚██╗██║ ██║ ██╔══╝ ██╔══██╗ ██║ ██║ ██║██║██║ ╚████║ ██║ ███████╗██║ ██║ ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝ +Printer ''' @@ -565,6 +568,7 @@ def interp(stack=(), dictionary=None): ██║ ██║██║██║ ██║ ██║██║ ██║██║╚██╗██║██╔══██║██╔══██╗ ╚██╔╝ ██████╔╝██║╚██████╗ ██║ ██║╚██████╔╝██║ ╚████║██║ ██║██║ ██║ ██║ ╚═════╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ +Dictionary ''' @@ -643,6 +647,7 @@ def help_(stack, expression, dictionary): ██║ ██║ ██║██║╚██╔╝██║██╔══██╗██║██║╚██╗██║██╔══██║ ██║ ██║ ██║██╔══██╗╚════██║ ╚██████╗╚██████╔╝██║ ╚═╝ ██║██████╔╝██║██║ ╚████║██║ ██║ ██║ ╚██████╔╝██║ ██║███████║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ +Combinators ''' @@ -754,6 +759,7 @@ def quit(stack, expr, dictionary): ██║ ██║ ██║██╔══██╗██╔══╝ ██║███╗██║██║ ██║██╔══██╗██║ ██║╚════██║ ╚██████╗╚██████╔╝██║ ██║███████╗ ╚███╔███╔╝╚██████╔╝██║ ██║██████╔╝███████║ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚══╝╚══╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ ╚══════╝ +Core Words ''' @@ -1076,6 +1082,7 @@ for alias, name in ( ██║ ██║██╔══╝ ██╔══╝ ██║██║╚██╗██║██║ ██║ ██║██║ ██║██║╚██╗██║╚════██║ ██████╔╝███████╗██║ ██║██║ ╚████║██║ ██║ ██║╚██████╔╝██║ ╚████║███████║ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚══════╝ +Definitions ''' @@ -1133,6 +1140,9 @@ class Def(object): ██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ╚════██║ ██║ ██║ ██║ ███████╗ ╚██████╗██║ ██║███████╗╚██████╗██║ ██╗███████║ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ +Type Checks + +Simple guard functions, for type inference see the Prolog versions. '''