The whole thing is kind of a mess.

This commit is contained in:
Simon Forman
2022-09-20 14:22:47 -07:00
parent d080cd20c7
commit 3f4e9d9fe2
40 changed files with 0 additions and 0 deletions
@@ -0,0 +1,7 @@
The `source/gen-*.pl` files are created by `partial_reduce_thun/0`. I'm
just messing around with it at the moment. In theory the reduced forms
would be more efficient, but there's no pressure to improve performance
yet, and I'm not e.g. feeding the output to GNU Prolog to compile to
machine code, eh?
It's just neat to see what the reducer makes of it.
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@@ -0,0 +1,310 @@
% With abs as a definition: abs ::= dup 0 < [] [neg] branch
?- joy(`[abs] ii <=`, [int(A), int(C)], [bool(B)]).
% Eight solutions:
B = false,
A in 0..sup,
A#=<C+ -1,
A+1#=_67378,
C in 1..sup,
C+1#=_67426,
_67426 in 1..sup,
_67378 in 1..sup ;
B = true,
A in 0..sup,
A#>=C,
A+1#=_67204,
C in 0..sup,
C+1#=_67252,
_67252 in 1..sup,
_67204 in 1..sup ;
B = false,
A in inf.. -1,
_67892+A#=0,
A+1#=_67912,
_67892 in 1..sup,
_67892#=<C+ -1,
C in 2..sup,
C+1#=_68008,
_68008 in 1..sup,
_67912 in inf..0 ;
B = true,
A in inf.. -1,
_67724+A#=0,
A+1#=_67744,
_67724 in 1..sup,
_67724#>=C,
C in 0..sup,
C+1#=_67834,
_67834 in 1..sup,
_67744 in inf..0 ;
B = false,
A in 0..sup,
A#=<_67850+ -1,
A+1#=_67870,
_67850 in 1..sup,
_67850+C#=0,
C in inf.. -1,
C+1#=_67966,
_67966 in inf..0,
_67870 in 1..sup ;
B = true,
A in 1..sup,
A#>=_67762,
A+1#=_67780,
_67762 in 1..sup,
_67762+C#=0,
C in inf.. -1,
C+1#=_67876,
_67876 in inf..0,
_67780 in 1..sup ;
B = false,
A in inf.. -1,
_68746+A#=0,
A+1#=_68766,
_68746 in 1..sup,
_68746#=<_68818+ -1,
_68818 in 2..sup,
_68818+C#=0,
C in inf.. -2,
C+1#=_68910,
_68910 in inf..0,
_68766 in inf..0 ;
B = true,
A in inf.. -1,
_68258+A#=0,
A+1#=_68278,
_68258 in 1..sup,
_68258#>=_68326,
_68326 in 1..sup,
_68326+C#=0,
C in inf.. -1,
C+1#=_68416,
_68416 in inf..0,
_68278 in inf..0 ;
false.
% If we add a function rule for it using CLP(FD)...:
func(abs, [int(A)|S], [int(B)|S]) :- B #= abs(A).
?- joy(`[abs] ii <=`, [int(A), int(C)], [bool(B)]).
% We get eighteen solutions! Egad.
B = false,
_7784#=abs(A),
_7784 in 0..sup,
_7784#=<_7836+ -1,
_7836 in 1..sup,
_7836#=abs(C),
C in inf.. -1\/1..sup ;
B = true,
_6512#=abs(A),
_6512 in 0..sup,
_6512#>=_6560,
_6560 in 0..sup,
_6560#=abs(C) ;
B = false,
A in 0..sup,
A#=<_8820+ -1,
A+1#=_8840,
_8820 in 1..sup,
_8820#=abs(C),
C in inf.. -1\/1..sup,
_8840 in 1..sup ;
B = true,
A in 0..sup,
A#>=_7544,
A+1#=_7562,
_7544 in 0..sup,
_7544#=abs(C),
_7562 in 1..sup ;
B = false,
A in inf.. -1,
_9354+A#=0,
A+1#=_9374,
_9354 in 1..sup,
_9354#=<_9426+ -1,
_9426 in 2..sup,
_9426#=abs(C),
C in inf.. -2\/2..sup,
_9374 in inf..0 ;
B = true,
A in inf.. -1,
_8082+A#=0,
A+1#=_8102,
_8082 in 1..sup,
_8082#>=_8150,
_8150 in 0..sup,
_8150#=abs(C),
_8102 in inf..0 ;
B = false,
_7686#=abs(A),
_7686 in 0..sup,
_7686#=<C+ -1,
C in 1..sup,
C+1#=_7782,
_7782 in 1..sup ;
B = true,
_7518#=abs(A),
_7518 in 0..sup,
_7518#>=C,
C in 0..sup,
C+1#=_7608,
_7608 in 1..sup ;
B = false,
A in 0..sup,
A#=<C+ -1,
A+1#=_8742,
C in 1..sup,
C+1#=_8790,
_8790 in 1..sup,
_8742 in 1..sup ;
B = true,
A in 0..sup,
A#>=C,
A+1#=_8568,
C in 0..sup,
C+1#=_8616,
_8616 in 1..sup,
_8568 in 1..sup ;
B = false,
A in inf.. -1,
_9256+A#=0,
A+1#=_9276,
_9256 in 1..sup,
_9256#=<C+ -1,
C in 2..sup,
C+1#=_9372,
_9372 in 1..sup,
_9276 in inf..0 ;
B = true,
A in inf.. -1,
_9088+A#=0,
A+1#=_9108,
_9088 in 1..sup,
_9088#>=C,
C in 0..sup,
C+1#=_9198,
_9198 in 1..sup,
_9108 in inf..0 ;
B = false,
_8178#=abs(A),
_8178 in 0..sup,
_8178#=<_8230+ -1,
_8230 in 1..sup,
_8230+C#=0,
C in inf.. -1,
C+1#=_8322,
_8322 in inf..0 ;
B = true,
A in inf.. -1\/1..sup,
_9272#=abs(A),
_9272 in 1..sup,
_9272#>=_9320,
_9320 in 1..sup,
_9320+C#=0,
C in inf.. -1,
C+1#=_9410,
_9410 in inf..0 ;
B = false,
A in 0..sup,
A#=<_9214+ -1,
A+1#=_9234,
_9214 in 1..sup,
_9214+C#=0,
C in inf.. -1,
C+1#=_9330,
_9330 in inf..0,
_9234 in 1..sup ;
B = true,
A in 1..sup,
A#>=_9126,
A+1#=_9144,
_9126 in 1..sup,
_9126+C#=0,
C in inf.. -1,
C+1#=_9240,
_9240 in inf..0,
_9144 in 1..sup ;
B = false,
A in inf.. -1,
_10110+A#=0,
A+1#=_10130,
_10110 in 1..sup,
_10110#=<_10182+ -1,
_10182 in 2..sup,
_10182+C#=0,
C in inf.. -2,
C+1#=_10274,
_10274 in inf..0,
_10130 in inf..0 ;
B = true,
A in inf.. -1,
_9622+A#=0,
A+1#=_9642,
_9622 in 1..sup,
_9622#>=_9690,
_9690 in 1..sup,
_9690+C#=0,
C in inf.. -1,
C+1#=_9780,
_9780 in inf..0,
_9642 in inf..0 ;
false.
@@ -0,0 +1,4 @@
Talk about minimal basis, Kirby (sp?) has found a basis involving a
combinator he calls 'cake'...
Branch, Loop, Sequence, Parallel.
@@ -0,0 +1,225 @@
___ _ ___ _
| __|_ ____ _ _ __ _ __| |___ / __|___ __| |___
| _|\ \ / _` | ' \| '_ \ / -_) | (__/ _ \/ _` / -_)
|___/_\_\__,_|_|_|_| .__/_\___| \___\___/\__,_\___|
|_|
# On the Square Spiral Example Code
Here is the example of Joy code from the `README` file:
[[[abs]ii <=][[<>][pop !-]||]&&][[!-][[++]][[--]]ifte dip][[pop !-][--][++]ifte]ifte
It might seem unreadable but with a little familiarity it becomes just as
legible as any other notation. Some layout helps:
[ [[abs] ii <=]
[
[<>] [pop !-] ||
] &&
]
[[ !-] [[++]] [[--]] ifte dip]
[[pop !-] [--] [++] ifte ]
ifte
This function accepts two integers on the stack and increments or
decrements one of them such that the new pair of numbers is the next
coordinate pair in a square spiral (like the kind used to construct an
Ulam Spiral).
## Original Form
It's adapted from the [original code on StackOverflow](https://stackoverflow.com/questions/398299/looping-in-a-spiral/31864777#31864777):
> If all you're trying to do is generate the first N points in the spiral
> (without the original problem's constraint of masking to an N x M
> region), the code becomes very simple:
void spiral(const int N)
{
int x = 0;
int y = 0;
for(int i = 0; i < N; ++i)
{
cout << x << '\t' << y << '\n';
if(abs(x) <= abs(y) && (x != y || x >= 0))
x += ((y >= 0) ? 1 : -1);
else
y += ((x >= 0) ? -1 : 1);
}
}
> The trick is that you can compare x and y to determine what side of the
> square you're on, and that tells you what direction to move in.
## Translation to Joy
I'm going to make a function that take two ints (`x` and `y`) and
generates the next pair, we'll turn it into a generator later using the
`x` combinator.
### First Boolean Predicate
We need a function that computes `abs(x) <= abs(y)`, we can use `ii` to
apply `abs` in parallel (eventually) to both values and then compare them
with `<=`:
[abs] ii <=
I've defined two short-circuiting Boolean combinators `&&` and `||` that
each accept two quoted predicate programs, run the first, and
conditionally run the second only if required (to compute the final
Boolean value). They run their predicate arguments `nullary`. Given
those, we can define `x != y || x >= 0` as:
[<>] [pop 0 >=] ||
And `(abs(x) <= abs(y) && (x != y || x >= 0))` as:
[[abs] ii <=] [[<>] [pop 0 >=] ||] &&
It's a little rough, but, as I say, with a little familiarity it becomes
legible.
### The Increment / Decrement Branches
Turning to the branches of the main `if` statement:
x += ((y >= 0) ? 1 : -1);
Rewrite as a hybrid (pseudo-code) `ifte` expression:
[y >= 0] [x += 1] [X -= 1] ifte
Change each C phrase to Joy code:
[0 >=] [[++] dip] [[--] dip] ifte
Factor out the dip from each branch:
[0 >=] [[++]] [[--]] ifte dip
Similar logic applies to the other branch:
y += ((x >= 0) ? -1 : 1);
[x >= 0] [y -= 1] [y += 1] ifte
[pop 0 >=] [--] [++] ifte
## Putting the Pieces Together
We can assemble the three functions we just defined in quotes and give
them them to the `ifte` combinator. With some arrangement to show off
the symmetry of the two branches, we have:
[[[abs] ii <=] [[<>] [pop !-] ||] &&]
[[ !-] [[++]] [[--]] ifte dip]
[[pop !-] [--] [++] ifte ]
ifte
As I was writing this up I realized that, since the `&&` combinator
doesn't consume the stack (below its quoted args), I can unquote the
predicate, swap the branches, and use the `branch` combinator instead of
`ifte`:
[[abs] ii <=] [[<>] [pop !-] ||] &&
[[pop !-] [--] [++] ifte ]
[[ !-] [[++]] [[--]] ifte dip]
branch
## Turning it into a Generator with `x`
It can be used with the x combinator to make a kind of generator for
spiral square coordinates.
We can use `codireco` to make a generator
codireco ::= cons dip rest cons
It will look like this:
[value [F] codireco]
Here's a trace of how it works:
[0 [dup ++] codireco] . x
[0 [dup ++] codireco] . 0 [dup ++] codireco
[0 [dup ++] codireco] 0 . [dup ++] codireco
[0 [dup ++] codireco] 0 [dup ++] . codireco
[0 [dup ++] codireco] 0 [dup ++] . cons dip rest cons
[0 [dup ++] codireco] [0 dup ++] . dip rest cons
. 0 dup ++ [0 [dup ++] codireco] rest cons
0 . dup ++ [0 [dup ++] codireco] rest cons
0 0 . ++ [0 [dup ++] codireco] rest cons
0 1 . [0 [dup ++] codireco] rest cons
0 1 [0 [dup ++] codireco] . rest cons
0 1 [[dup ++] codireco] . cons
0 [1 [dup ++] codireco] .
But first we have to change the `spiral_next` function to work on a
quoted pair of integers, and leave a copy of the pair on the stack.
From:
y x spiral_next
---------------------
y' x'
to:
[x y] [spiral_next] infra
-------------------------------
[x' y']
So our generator is:
[[x y] [dup [spiral_next] infra] codireco]
Or rather:
[[0 0] [dup [spiral_next] infra] codireco]
There is a function `make_generator` that will build the generator for us
out of the value and stepper function:
[0 0] [dup [spiral_next] infra] make_generator
----------------------------------------------------
[[0 0] [dup [spiral_next] infra] codireco]
Here it is in action:
?- joy(`[[0 0] [dup [spiral_next] infra] codireco] x x x x pop`, [], _So),
| joy_terms_to_string(_So, S).
_So = [list([int(-1), int(0)]), list([int(-1), int(1)]), list([int(0), int(1)]), list([int(0), int(0)])],
S = "[-1 0] [-1 1] [0 1] [0 0]" .
Four `x` combinators, four pairs of coordinates.
## Conclusion
So that's an example of Joy code. It's a straightforward translation of
the original. It's a little long for a single definition, you might
break it up like so:
_spn_P ::= [[abs] ii <=] [[<>] [pop !-] ||] &&
_spn_T ::= [ !-] [[++]] [[--]] ifte dip
_spn_E ::= [pop !-] [--] [++] ifte
spiral_next ::= _spn_P [_spn_E] [_spn_T] branch
This way it's easy to see that the function is a branch with two
quasi-symmetrical paths.
We then used this function to make a simple generator of coordinate
pairs, where the next pair in the series can be generated at any time by
using the `x` combinator on the generator (which is just a quoted
expression containing a copy of the current pair and the "stepper
function" to generate the next pair from that.)
@@ -0,0 +1,61 @@
# list-structured memory
[In SICP, section 5.3, "Storage Allocation and Garbage Collection"](https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-33.html#%_sec_5.3):
> In order to simplify the discussion, we will assume that our register
machines can be equipped with a list-structured memory, in which the
basic operations for manipulating list-structured data are primitive.
So they bunt to an abstraction and then implement that abstraction as a
separate problem. Makes sense. I see no reason not to adopt the design
described here.
------------------
# Machine Ints vs BigNums
Already there is a problem in the semantics. SWI Prolog integers can be
larger than machine words, which in the RISC CPU are thirty-two bits.
(GNU Prolog uses machine words for its integers). THe main options are:
1. Implements "BigNums" for Wirth RISC.
2. Adjust the semantics of Thun to reflect the modular arithmetic of
machine words and native machine integer math operations.
3. ... something else.
------------------
# specialized versions of `branch` and `ifte`
THere's another semantic wrinkle with branches and Boolean values.
Namely, the CPU provides the condition and the offset in one instruction
whereas Joy has them separated. I have been thinking about introducing
specialized versions of `branch` as primitives:
=branch
>branch
<branch
<=branch
>=branch
<>branch
Or maybe:
=?
>?
<?
<=?
>=?
<>?
Anyway, it would be pretty easy to detect simple cases of the split
pattern and convert them automatically, but the programmer could use them
directly whenever it made sense.
> [F] [T] branch ==> [F] [T] >branch
Probably specialized versions of `ifte` would be useful as well.
------------------
@@ -0,0 +1,167 @@
# User Guide
There is no use interface as such. At the moment you just load the
`thun.pl` file into SWI Prolog and use some of these "top-level"
predicates to interact with it.
------------------
## `joy/3`
joy(InputString, StackIn, StackOut)
### Evaluation
Accepts a joy expression as a list of codes (in SWI Prolog you can use
backticks to quote a string literal and get codes):
?- joy(`+ *`, [int(2), int(3), int(10)], StackOut).
StackOut = [int(50)] ;
false.
?- joy(`2 3 + 10 *`, StackIn, StackOut).
StackOut = [int(50)|StackIn] ;
false.
### Type Checking
This predicate can also perform type checking:
?- joy(`2 [] +`, StackIn, StackOut).
false.
### Type Inference
And type inference with CLP(FD) constraints on integer operations and
comparisons:
?- joy(`+ *`, StackIn, StackOut).
StackIn = [int(_37782), int(_37792), int(_37802)|_37798],
StackOut = [int(_37824)|_37798],
_37782+_37792#=_37842,
_37842*_37802#=_37824 ;
false.
------------------
## `joy_parse//1`
If you just want to parse a string into a Joy expression use joy_parse//1
DCG, like so:
phrase(joy_parse(Expression), InputString)
Or directly:
joy_parse(Expression, InputString, [])
Example:
?- phrase(joy_parse(Expression), `1 [i]`).
Expression = [int(1), list([symbol(i)])] ;
false.
------------------
## `thun/3`
Once you have a (type-tagged) Joy exression as Prolog data-structure you
can use the `thun/3` predicate to evaluate it:
thun(Expression, InputStack, OutputStack)
------------------
## `sjc/2`
sjc(Name, InputString)
Helper function to see to what Prolog code a given Joy expression would
compile. Give it a name (Prolog atom) and a list of code.
?- sjc(third, `third`).
func(third, [list([_, _, A|_])|B], [A|B]).
true ;
false.
?- sjc(ccons, `ccons`).
func(ccons, [list(C), B, A|D], [list([A, B|C])|D]).
true ;
false.
Compilation captures CLP(FD) constraints:
?- sjc(+*, `+ *`).
func(+*, [int(D), int(E), int(B)|A], [int(C)|A]) :-
maplist(call,
[ clpfd:(F*B#=C),
clpfd:(D+E#=F)
]).
true ;
false.
------------------
## `show_joy_compile/2`
show_joy_compile(Name, Expression)
Same as `sjc/2` but you give it an already-parsed expression.
------------------
## `joy_compile/2`
joy_compile(Name, Expression)
This actually asserts the new function definition into the Prolog rule
database. Use with care.
At some point I'll probably add a build phase that tries to pre-compile
all definitions it can into Prolog rules but for now this is just
experimental.
------------------
## `compiler/4`
compiler(InputString, MachineCode, StackIn, StackOut)
Experimental *and* unfinished, this predicate attempts to build a list of
terms representing machine code for the RISC CPU that Prof. Wirth has
specified for his Project Oberon.
------------------
## `grow//0` & `shrink//0`
These DCGs recursively unfold or fold definitions in a Joy expression:
?- phrase(grow, [symbol(third)], Out).
Out = [symbol(rest), symbol(rest), symbol(first)] ;
Out = [symbol(rest), symbol(rest), symbol(first)] ;
Out = [symbol(rest), symbol(second)] ;
Out = [symbol(third)].
?- phrase(shrink, [symbol(rest), symbol(rest), symbol(first)], Out).
Out = [symbol(rrest), symbol(first)] ;
Out = [symbol(third)] ;
Out = [symbol(rest), symbol(second)] ;
Out = [symbol(rest), symbol(rest), symbol(first)].
They are more a proof-of-concept than useful at the moment. I imagine
that it might be possible to set up some kind of automated search through
all the variations to see if a more efficient form (after compiling with
whatever optimizations) can be found.
------------------
## `joy_terms_to_string/2`
joy_terms_to_string(Expr, String)
Converts a Joy expression into a string (an actual string, not a list of
codes. See the [SWI Prolog manual for more information on strings](https://www.swi-prolog.org/pldoc/man?section=strings).)