OSDN Git Service

Working to get types right.
[joypy/Thun.git] / joy / library.py
1 # -*- coding: utf-8 -*-
2 #
3 #    Copyright © 2014, 2015, 2017, 2018 Simon Forman
4 #
5 #    This file is part of Thun
6 #
7 #    Thun is free software: you can redistribute it and/or modify
8 #    it under the terms of the GNU General Public License as published by
9 #    the Free Software Foundation, either version 3 of the License, or
10 #    (at your option) any later version.
11 #
12 #    Thun is distributed in the hope that it will be useful,
13 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 #    GNU General Public License for more details.
16 #
17 #    You should have received a copy of the GNU General Public License
18 #    along with Thun.  If not see <http://www.gnu.org/licenses/>. 
19 #
20 '''
21 This module contains the Joy function infrastructure and a library of
22 functions.  Its main export is a Python function initialize() that
23 returns a dictionary of Joy functions suitable for use with the joy()
24 function.
25 '''
26 from logging import getLogger
27
28 _log = getLogger(__name__)
29 _log.info('Loading library.')
30
31 from inspect import getdoc
32 from functools import wraps
33 from itertools import count
34 from inspect import getmembers, isfunction
35 import operator, math
36
37 from .parser import text_to_expression, Symbol
38 from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat
39 from .utils.brutal_hackery import rename_code_object
40
41 from .utils import generated_library as genlib
42 from .utils.types import (
43   compose,
44   ef,
45   stack_effect,
46   AnyJoyType,
47   AnyStarJoyType,
48   BooleanJoyType,
49   NumberJoyType,
50   NumberStarJoyType,
51   StackJoyType,
52   StackStarJoyType,
53   FloatJoyType,
54   IntJoyType,
55   SymbolJoyType,
56   CombinatorJoyType,
57   TextJoyType,
58   _functions,
59   FUNCTIONS,
60   infer,
61   infer_expression,
62   JoyTypeError,
63   combinator_effect,
64   poly_combinator_effect,
65   doc_from_stack_effect,
66   )
67   
68   
69 _SYM_NUMS = count().next
70 _COMB_NUMS = count().next
71
72
73 _R = range(10)
74 A = a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = map(AnyJoyType, _R)
75 B = b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = map(BooleanJoyType, _R)
76 N = n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = map(NumberJoyType, _R)
77 S = s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = map(StackJoyType, _R)
78 F = f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = map(FloatJoyType, _R)
79 I = i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = map(IntJoyType, _R)
80 T = t0, t1, t2, t3, t4, t5, t6, t7, t8, t9 = map(TextJoyType, _R)
81
82
83 _R = range(1, 11)
84 As = map(AnyStarJoyType, _R)
85 Ns = map(NumberStarJoyType, _R)
86 Ss = map(StackStarJoyType, _R)
87
88
89 sec0 = stack_effect(t1)()
90 sec1 = stack_effect(s0, i1)(s1)
91 sec2 = stack_effect(s0, i1)(a1)
92 sec_binary_cmp = stack_effect(n1, n2)(b1)
93 sec_binary_ints = stack_effect(i1, i2)(i3)
94 sec_binary_logic = stack_effect(b1, b2)(b3)
95 sec_binary_math = stack_effect(n1, n2)(n3)
96 sec_unary_logic = stack_effect(a1)(b1)
97 sec_unary_math = stack_effect(n1)(n2)
98 sec_Ns_math = stack_effect((Ns[1], s1),)(n0)
99
100 _dictionary = {}
101
102
103 def inscribe(function):
104   '''A decorator to inscribe functions into the default dictionary.'''
105   _dictionary[function.name] = function
106   return function
107
108
109 def initialize():
110   '''Return a dictionary of Joy functions for use with joy().'''
111   return _dictionary.copy()
112
113
114 ALIASES = (
115   ('add', ['+']),
116   ('and', ['&']),
117   ('bool', ['truthy']),
118   ('mul', ['*']),
119   ('floordiv', ['/floor', '//']),
120   ('floor', ['round']),
121   ('truediv', ['/']),
122   ('mod', ['%', 'rem', 'remainder', 'modulus']),
123   ('eq', ['=']),
124   ('ge', ['>=']),
125   ('getitem', ['pick', 'at']),
126   ('gt', ['>']),
127   ('le', ['<=']),
128   ('lshift', ['<<']),
129   ('lt', ['<']),
130   ('ne', ['<>', '!=']),
131   ('rshift', ['>>']),
132   ('sub', ['-']),
133   ('xor', ['^']),
134   ('succ', ['++']),
135   ('pred', ['--']),
136   ('rolldown', ['roll<']),
137   ('rollup', ['roll>']),
138   ('eh', ['?']),
139   ('id', [u'•']),
140   )
141
142
143 def add_aliases(D, A):
144   '''
145   Given a dict and a iterable of (name, [alias, ...]) pairs, create
146   additional entries in the dict mapping each alias to the named function
147   if it's in the dict.  Aliases for functions not in the dict are ignored.
148   '''
149   for name, aliases in A:
150     try:
151       F = D[name]
152     except KeyError:
153       continue
154     for alias in aliases:
155       D[alias] = F
156
157
158 def yin_functions():
159   '''
160   Return a dict of named stack effects.
161
162   "Yin" functions are those that only rearrange items in stacks and
163   can be defined completely by their stack effects.  This means they
164   can be auto-compiled.
165   '''
166   # pylint: disable=unused-variable
167   cons = ef(a1, s0)((a1, s0))
168   ccons = compose(cons, cons)
169   dup = ef(a1)(a1, a1)
170   dupd = ef(a2, a1)(a2, a2, a1)
171   dupdd = ef(a3, a2, a1)(a3, a3, a2, a1)
172   first = ef((a1, s1),)(a1,)
173   over = ef(a2, a1)(a2, a1, a2)
174   pop = ef(a1)()
175   popd = ef(a2, a1,)(a1)
176   popdd = ef(a3, a2, a1,)(a2, a1,)
177   popop = ef(a2, a1,)()
178   popopd = ef(a3, a2, a1,)(a1)
179   popopdd = ef(a4, a3, a2, a1,)(a2, a1)
180   rest = ef((a1, s0),)(s0,)
181   rolldown = ef(a1, a2, a3)(a2, a3, a1)
182   rollup = ef(a1, a2, a3)(a3, a1, a2)
183   rrest = compose(rest, rest)
184   second = compose(rest, first)
185   stack = s0, (s0, s0)
186   swaack = (s1, s0), (s0, s1)
187   swap = ef(a1, a2)(a2, a1)
188   swons = compose(swap, cons)
189   third = compose(rest, second)
190   tuck = ef(a2, a1)(a1, a2, a1)
191   uncons = ef((a1, s0),)(a1, s0)
192   unswons = compose(uncons, swap)
193   stuncons = compose(stack, uncons)
194   stununcons = compose(stack, uncons, uncons)
195   unit = ef(a1)((a1, ()))
196
197   first_two = compose(uncons, uncons, pop)
198   fourth = compose(rest, third)
199
200   _Tree_add_Ee = compose(pop, swap, rolldown, rrest, ccons)
201   _Tree_get_E = compose(popop, second)
202   _Tree_delete_clear_stuff = compose(rollup, popop, rest)
203   _Tree_delete_R0 = compose(over, first, swap, dup)
204
205   return locals()
206
207
208 definitions = ('''\
209 of == swap at
210 product == 1 swap [*] step
211 flatten == [] swap [concat] step
212 quoted == [unit] dip
213 unquoted == [i] dip
214 enstacken == stack [clear] dip
215 ? == dup truthy
216 disenstacken == ? [uncons ?] loop pop
217 dinfrirst == dip infra first
218 nullary == [stack] dinfrirst
219 unary == nullary popd
220 binary == nullary [popop] dip
221 ternary == unary [popop] dip
222 pam == [i] map
223 run == [] swap infra
224 sqr == dup mul
225 size == 0 swap [pop ++] step
226 fork == [i] app2
227 cleave == fork [popd] dip
228 average == [sum 1.0 *] [size] cleave /
229 gcd == 1 [tuck modulus dup 0 >] loop pop
230 least_fraction == dup [gcd] infra [div] concat map
231 *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
232 *fraction0 == concat [[swap] dip * [*] dip] infra
233 down_to_zero == [0 >] [dup --] while
234 range_to_zero == unit [down_to_zero] infra
235 anamorphism == [pop []] swap [dip swons] genrec
236 range == [0 <=] [1 - dup] anamorphism
237 while == swap [nullary] cons dup dipd concat loop
238 dupdipd == dup dipd
239 primrec == [i] genrec
240 step_zero == 0 roll> step
241 codireco == cons dip rest cons
242 make_generator == [codireco] ccons
243 ifte == [nullary not] dipd branch
244 '''
245 #
246
247 # ifte == [nullary] dipd swap branch
248 # genrec == [[genrec] cons cons cons cons] nullary swons concat ifte
249
250 # Another definition for while. FWIW
251 # while == over [[i] dip nullary] ccons [nullary] dip loop
252
253 ##ccons == cons cons
254 ##unit == [] cons
255 ##second == rest first
256 ##third == rest rest first
257 ##swons == swap cons
258 ##swoncat == swap concat
259
260 ##Zipper
261 ##z-down == [] swap uncons swap
262 ##z-up == swons swap shunt
263 ##z-right == [swons] cons dip uncons swap
264 ##z-left == swons [uncons swap] dip swap
265
266 ##Quadratic Formula
267 ##divisor == popop 2 *
268 ##minusb == pop neg
269 ##radical == swap dup * rollup * 4 * - sqrt
270 ##root1 == + swap /
271 ##root2 == - swap /
272 ##q0 == [[divisor] [minusb] [radical]] pam
273 ##q1 == [[root1] [root2]] pam
274 ##quadratic == [q0] ternary i [q1] ternary
275
276 # Project Euler
277 ##'''\
278 ##PE1.1 == + dup [+] dip
279 ##PE1.2 == dup [3 & PE1.1] dip 2 >>
280 ##PE1.3 == 14811 swap [PE1.2] times pop
281 ##PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop
282 ##'''
283 #PE1.2 == [PE1.1] step
284 #PE1 == 0 0 66 [[3 2 1 3 1 2 3] PE1.2] times [3 2 1 3] PE1.2 pop
285 )
286
287
288 def FunctionWrapper(f):
289   '''Set name attribute.'''
290   if not f.__doc__:
291     raise ValueError('Function %s must have doc string.' % f.__name__)
292   f.name = f.__name__.rstrip('_')  # Don't shadow builtins.
293   return f
294
295
296 def SimpleFunctionWrapper(f):
297   '''
298   Wrap functions that take and return just a stack.
299   '''
300   @FunctionWrapper
301   @wraps(f)
302   @rename_code_object(f.__name__)
303   def inner(stack, expression, dictionary):
304     return f(stack), expression, dictionary
305   return inner
306
307
308 def BinaryBuiltinWrapper(f):
309   '''
310   Wrap functions that take two arguments and return a single result.
311   '''
312   @FunctionWrapper
313   @wraps(f)
314   @rename_code_object(f.__name__)
315   def inner(stack, expression, dictionary):
316     (a, (b, stack)) = stack
317     result = f(b, a)
318     return (result, stack), expression, dictionary
319   return inner
320
321
322 def UnaryBuiltinWrapper(f):
323   '''
324   Wrap functions that take one argument and return a single result.
325   '''
326   @FunctionWrapper
327   @wraps(f)
328   @rename_code_object(f.__name__)
329   def inner(stack, expression, dictionary):
330     (a, stack) = stack
331     result = f(a)
332     return (result, stack), expression, dictionary
333   return inner
334
335
336 class DefinitionWrapper(object):
337   '''
338   Provide implementation of defined functions, and some helper methods.
339   '''
340
341   def __init__(self, name, body_text, doc=None):
342     self.name = self.__name__ = name
343     self.body = text_to_expression(body_text)
344     self._body = tuple(iter_stack(self.body))
345     self.__doc__ = doc or body_text
346     self._compiled = None
347
348   def __call__(self, stack, expression, dictionary):
349     if self._compiled:
350       return self._compiled(stack, expression, dictionary)  # pylint: disable=E1102
351     expression = list_to_stack(self._body, expression)
352     return stack, expression, dictionary
353
354   @classmethod
355   def parse_definition(class_, defi):
356     '''
357     Given some text describing a Joy function definition parse it and
358     return a DefinitionWrapper.
359     '''
360     name, proper, body_text = (n.strip() for n in defi.partition('=='))
361     if not proper:
362       raise ValueError('Definition %r failed' % (defi,))
363     return class_(name, body_text)
364
365   @classmethod
366   def add_definitions(class_, defs, dictionary):
367     '''
368     Scan multi-line string defs for definitions and add them to the
369     dictionary.
370     '''
371     for definition in _text_to_defs(defs):
372       class_.add_def(definition, dictionary)
373
374   @classmethod
375   def add_def(class_, definition, dictionary, fail_fails=False):
376     '''
377     Add the definition to the dictionary.
378     '''
379     F = class_.parse_definition(definition)
380     _log.info('Adding definition %s := %s', F.name, expression_to_string(F.body))
381     dictionary[F.name] = F
382
383
384 def _text_to_defs(text):
385   return (line.strip() for line in text.splitlines() if '==' in line)
386
387
388 #
389 # Functions
390 #
391
392
393 @inscribe
394 @sec0
395 @FunctionWrapper
396 def inscribe_(stack, expression, dictionary):
397   '''
398   Create a new Joy function definition in the Joy dictionary.  A
399   definition is given as a string with a name followed by a double
400   equal sign then one or more Joy functions, the body. for example:
401
402       sqr == dup mul
403
404   If you want the definition to persist over restarts, enter it into
405   the definitions.txt resource.
406   '''
407   definition, stack = stack
408   DefinitionWrapper.add_def(definition, dictionary, fail_fails=True)
409   return stack, expression, dictionary
410
411
412 @inscribe
413 @SimpleFunctionWrapper
414 def parse(stack):
415   '''Parse the string on the stack to a Joy expression.'''
416   text, stack = stack
417   expression = text_to_expression(text)
418   return expression, stack
419
420
421 @inscribe
422 @sec2
423 @SimpleFunctionWrapper
424 def getitem(stack):
425   '''
426   ::
427
428     getitem == drop first
429
430   Expects an integer and a quote on the stack and returns the item at the
431   nth position in the quote counting from 0.
432   ::
433
434        [a b c d] 0 getitem
435     -------------------------
436                 a
437
438   '''
439   n, (Q, stack) = stack
440   return pick(Q, n), stack
441
442
443 @inscribe
444 @sec1
445 @SimpleFunctionWrapper
446 def drop(stack):
447   '''
448   ::
449
450     drop == [rest] times
451
452   Expects an integer and a quote on the stack and returns the quote with
453   n items removed off the top.
454   ::
455
456        [a b c d] 2 drop
457     ----------------------
458            [c d]
459
460   '''
461   n, (Q, stack) = stack
462   while n > 0:
463     try:
464       _, Q = Q
465     except ValueError:
466       raise IndexError
467     n -= 1
468   return Q, stack
469
470
471 @inscribe
472 @sec1
473 @SimpleFunctionWrapper
474 def take(stack):
475   '''
476   Expects an integer and a quote on the stack and returns the quote with
477   just the top n items in reverse order (because that's easier and you can
478   use reverse if needed.)
479   ::
480
481        [a b c d] 2 take
482     ----------------------
483            [b a]
484
485   '''
486   n, (Q, stack) = stack
487   x = ()
488   while n > 0:
489     try:
490       item, Q = Q
491     except ValueError:
492       raise IndexError
493     x = item, x
494     n -= 1
495   return x, stack
496
497
498 @inscribe
499 @SimpleFunctionWrapper
500 def choice(stack):
501   '''
502   Use a Boolean value to select one of two items.
503   ::
504
505         A B False choice
506      ----------------------
507                A
508
509
510         A B True choice
511      ---------------------
512                B
513
514   Currently Python semantics are used to evaluate the "truthiness" of the
515   Boolean value (so empty string, zero, etc. are counted as false, etc.)
516   '''
517   (if_, (then, (else_, stack))) = stack
518   return then if if_ else else_, stack
519
520
521 @inscribe
522 @SimpleFunctionWrapper
523 def select(stack):
524   '''
525   Use a Boolean value to select one of two items from a sequence.
526   ::
527
528         [A B] False select
529      ------------------------
530                 A
531
532
533         [A B] True select
534      -----------------------
535                B
536
537   The sequence can contain more than two items but not fewer.
538   Currently Python semantics are used to evaluate the "truthiness" of the
539   Boolean value (so empty string, zero, etc. are counted as false, etc.)
540   '''
541   (flag, (choices, stack)) = stack
542   (else_, (then, _)) = choices
543   return then if flag else else_, stack
544
545
546 @inscribe
547 @sec_Ns_math
548 @SimpleFunctionWrapper
549 def max_(S):
550   '''Given a list find the maximum.'''
551   tos, stack = S
552   return max(iter_stack(tos)), stack
553
554
555 @inscribe
556 @sec_Ns_math
557 @SimpleFunctionWrapper
558 def min_(S):
559   '''Given a list find the minimum.'''
560   tos, stack = S
561   return min(iter_stack(tos)), stack
562
563
564 @inscribe
565 @sec_Ns_math
566 @SimpleFunctionWrapper
567 def sum_(S):
568   '''Given a quoted sequence of numbers return the sum.
569
570   sum == 0 swap [+] step
571   '''
572   tos, stack = S
573   return sum(iter_stack(tos)), stack
574
575
576 @inscribe
577 @SimpleFunctionWrapper
578 def remove(S):
579   '''
580   Expects an item on the stack and a quote under it and removes that item
581   from the the quote.  The item is only removed once.
582   ::
583
584        [1 2 3 1] 1 remove
585     ------------------------
586             [2 3 1]
587
588   '''
589   (tos, (second, stack)) = S
590   l = list(iter_stack(second))
591   l.remove(tos)
592   return list_to_stack(l), stack
593
594
595 @inscribe
596 @SimpleFunctionWrapper
597 def unique(S):
598   '''Given a list remove duplicate items.'''
599   tos, stack = S
600   I = list(iter_stack(tos))
601   list_to_stack(sorted(set(I), key=I.index))
602   return list_to_stack(sorted(set(I), key=I.index)), stack
603
604
605 @inscribe
606 @SimpleFunctionWrapper
607 def sort_(S):
608   '''Given a list return it sorted.'''
609   tos, stack = S
610   return list_to_stack(sorted(iter_stack(tos))), stack
611
612
613 _functions['clear'] = s0, s1
614 @inscribe
615 @SimpleFunctionWrapper
616 def clear(stack):
617   '''Clear everything from the stack.
618   ::
619
620     clear == stack [pop stack] loop
621
622        ... clear
623     ---------------
624
625   '''
626   return ()
627
628
629 @inscribe
630 @SimpleFunctionWrapper
631 def unstack(stack):
632   '''
633   The unstack operator expects a list on top of the stack and makes that
634   the stack discarding the rest of the stack.
635   '''
636   return stack[0]
637
638
639 @inscribe
640 @SimpleFunctionWrapper
641 def reverse(S):
642   '''Reverse the list on the top of the stack.
643   ::
644
645     reverse == [] swap shunt
646   '''
647   (tos, stack) = S
648   res = ()
649   for term in iter_stack(tos):
650     res = term, res
651   return res, stack
652
653
654 @inscribe
655 @combinator_effect(_COMB_NUMS(), s7, s6)
656 @SimpleFunctionWrapper
657 def concat_(S):
658   '''Concatinate the two lists on the top of the stack.
659   ::
660
661        [a b c] [d e f] concat
662     ----------------------------
663            [a b c d e f]
664
665 '''
666   (tos, (second, stack)) = S
667   return concat(second, tos), stack
668
669
670 @inscribe
671 @SimpleFunctionWrapper
672 def shunt(stack):
673   '''Like concat but reverses the top list into the second.
674   ::
675
676     shunt == [swons] step == reverse swap concat
677
678        [a b c] [d e f] shunt
679     ---------------------------
680          [f e d a b c] 
681
682   '''
683   (tos, (second, stack)) = stack
684   while tos:
685     term, tos = tos
686     second = term, second
687   return second, stack
688
689
690 @inscribe
691 @SimpleFunctionWrapper
692 def zip_(S):
693   '''
694   Replace the two lists on the top of the stack with a list of the pairs
695   from each list.  The smallest list sets the length of the result list.
696   '''
697   (tos, (second, stack)) = S
698   accumulator = [
699     (a, (b, ()))
700     for a, b in zip(iter_stack(tos), iter_stack(second))
701     ]
702   return list_to_stack(accumulator), stack
703
704
705 @inscribe
706 @SimpleFunctionWrapper
707 def succ(S):
708   '''Increment TOS.'''
709   (tos, stack) = S
710   return tos + 1, stack
711
712
713 @inscribe
714 @SimpleFunctionWrapper
715 def pred(S):
716   '''Decrement TOS.'''
717   (tos, stack) = S
718   return tos - 1, stack
719
720
721 @inscribe
722 @SimpleFunctionWrapper
723 def pm(stack):
724   '''
725   Plus or minus
726   ::
727
728        a b pm
729     -------------
730        a+b a-b
731
732   '''
733   a, (b, stack) = stack
734   p, m, = b + a, b - a
735   return m, (p, stack)
736
737
738 def floor(n):
739   return int(math.floor(n))
740
741 floor.__doc__ = math.floor.__doc__
742
743
744 @inscribe
745 @SimpleFunctionWrapper
746 def divmod_(S):
747   '''
748   divmod(x, y) -> (quotient, remainder)
749
750   Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.
751   '''
752   a, (b, stack) = S
753   d, m = divmod(a, b)
754   return d, (m, stack)
755
756
757 def sqrt(a):
758   '''
759   Return the square root of the number a.
760   Negative numbers return complex roots.
761   '''
762   try:
763     r = math.sqrt(a)
764   except ValueError:
765     assert a < 0, repr(a)
766     r = math.sqrt(-a) * 1j
767   return r
768
769
770 #def execute(S):
771 #  (text, stack) = S
772 #  if isinstance(text, str):
773 #    return run(text, stack)
774 #  return stack
775
776
777 @inscribe
778 @SimpleFunctionWrapper
779 def id_(stack):
780   '''The identity function.'''
781   return stack
782
783
784 @inscribe
785 @SimpleFunctionWrapper
786 def void(stack):
787   '''True if the form on TOS is void otherwise False.'''
788   form, stack = stack
789   return _void(form), stack
790
791
792 def _void(form):
793   return any(not _void(i) for i in iter_stack(form))
794
795
796
797 ##  transpose
798 ##  sign
799 ##  take
800
801
802 @inscribe
803 @FunctionWrapper
804 def words(stack, expression, dictionary):
805   '''Print all the words in alphabetical order.'''
806   print(' '.join(sorted(dictionary)))
807   return stack, expression, dictionary
808
809
810 @inscribe
811 @FunctionWrapper
812 def sharing(stack, expression, dictionary):
813   '''Print redistribution information.'''
814   print("You may convey verbatim copies of the Program's source code as"
815         ' you receive it, in any medium, provided that you conspicuously'
816         ' and appropriately publish on each copy an appropriate copyright'
817         ' notice; keep intact all notices stating that this License and'
818         ' any non-permissive terms added in accord with section 7 apply'
819         ' to the code; keep intact all notices of the absence of any'
820         ' warranty; and give all recipients a copy of this License along'
821         ' with the Program.'
822         ' You should have received a copy of the GNU General Public License'
823         ' along with Thun.  If not see <http://www.gnu.org/licenses/>.')
824   return stack, expression, dictionary
825
826
827 @inscribe
828 @FunctionWrapper
829 def warranty(stack, expression, dictionary):
830   '''Print warranty information.'''
831   print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
832         ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
833         ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
834         ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
835         ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
836         ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
837         ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
838         ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
839         ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
840   return stack, expression, dictionary
841
842
843 # def simple_manual(stack):
844 #   '''
845 #   Print words and help for each word.
846 #   '''
847 #   for name, f in sorted(FUNCTIONS.items()):
848 #     d = getdoc(f)
849 #     boxline = '+%s+' % ('-' * (len(name) + 2))
850 #     print('\n'.join((
851 #       boxline,
852 #       '| %s |' % (name,),
853 #       boxline,
854 #       d if d else '   ...',
855 #       '',
856 #       '--' * 40,
857 #       '',
858 #       )))
859 #   return stack
860
861
862 @inscribe
863 @FunctionWrapper
864 def help_(S, expression, dictionary):
865   '''Accepts a quoted symbol on the top of the stack and prints its docs.'''
866   ((symbol, _), stack) = S
867   word = dictionary[symbol]
868   print(getdoc(word))
869   return stack, expression, dictionary
870
871
872 #
873 # § Combinators
874 #
875
876
877 # Several combinators depend on other words in their definitions,
878 # we use symbols to prevent hard-coding these, so in theory, you
879 # could change the word in the dictionary to use different semantics.
880 S_choice = Symbol('choice')
881 S_first = Symbol('first')
882 S_getitem = Symbol('getitem')
883 S_genrec = Symbol('genrec')
884 S_loop = Symbol('loop')
885 S_i = Symbol('i')
886 S_ifte = Symbol('ifte')
887 S_infra = Symbol('infra')
888 S_step = Symbol('step')
889 S_times = Symbol('times')
890 S_swaack = Symbol('swaack')
891 S_truthy = Symbol('truthy')
892
893
894 @inscribe
895 @combinator_effect(_COMB_NUMS(), s1)
896 @FunctionWrapper
897 def i(stack, expression, dictionary):
898   '''
899   The i combinator expects a quoted program on the stack and unpacks it
900   onto the pending expression for evaluation.
901   ::
902
903        [Q] i
904     -----------
905         Q
906
907   '''
908   quote, stack = stack
909   return stack, concat(quote, expression), dictionary
910
911
912 @inscribe
913 @combinator_effect(_COMB_NUMS(), s1)
914 @FunctionWrapper
915 def x(stack, expression, dictionary):
916   '''
917   ::
918
919     x == dup i
920
921     ... [Q] x = ... [Q] dup i
922     ... [Q] x = ... [Q] [Q] i
923     ... [Q] x = ... [Q]  Q
924
925   '''
926   quote, _ = stack
927   return stack, concat(quote, expression), dictionary
928
929
930 @inscribe
931 @combinator_effect(_COMB_NUMS(), s7, s6)
932 @FunctionWrapper
933 def b(stack, expression, dictionary):
934   '''
935   ::
936
937     b == [i] dip i
938
939     ... [P] [Q] b == ... [P] i [Q] i
940     ... [P] [Q] b == ... P Q
941
942   '''
943   q, (p, (stack)) = stack
944   return stack, concat(p, concat(q, expression)), dictionary
945
946
947 @inscribe
948 @combinator_effect(_COMB_NUMS(), a1, s1)
949 @FunctionWrapper
950 def dupdip(stack, expression, dictionary):
951   '''
952   ::
953
954     [F] dupdip == dup [F] dip
955
956     ... a [F] dupdip
957     ... a dup [F] dip
958     ... a a   [F] dip
959     ... a F a
960
961   '''
962   F, stack = stack
963   a = stack[0]
964   return stack, concat(F, (a,  expression)), dictionary
965
966
967 @inscribe
968 @combinator_effect(_COMB_NUMS(), s7, s6)
969 @FunctionWrapper
970 def infra(stack, expression, dictionary):
971   '''
972   Accept a quoted program and a list on the stack and run the program
973   with the list as its stack.
974   ::
975
976        ... [a b c] [Q] . infra
977     -----------------------------
978        c b a . Q [...] swaack
979
980   '''
981   (quote, (aggregate, stack)) = stack
982   return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary
983
984
985 @inscribe
986 #@combinator_effect(_COMB_NUMS(), s7, s6, s5, s4)
987 @FunctionWrapper
988 def genrec(stack, expression, dictionary):
989   '''
990   General Recursion Combinator.
991   ::
992
993                           [if] [then] [rec1] [rec2] genrec
994     ---------------------------------------------------------------------
995        [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
996
997   From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
998   "The genrec combinator takes four program parameters in addition to
999   whatever data parameters it needs. Fourth from the top is an if-part,
1000   followed by a then-part. If the if-part yields true, then the then-part
1001   is executed and the combinator terminates. The other two parameters are
1002   the rec1-part and the rec2-part. If the if-part yields false, the
1003   rec1-part is executed. Following that the four program parameters and
1004   the combinator are again pushed onto the stack bundled up in a quoted
1005   form. Then the rec2-part is executed, where it will find the bundled
1006   form. Typically it will then execute the bundled form, either with i or
1007   with app2, or some other combinator."
1008
1009   The way to design one of these is to fix your base case [then] and the
1010   test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
1011   a quotation of the whole function.
1012
1013   For example, given a (general recursive) function 'F':
1014   ::
1015
1016       F == [I] [T] [R1] [R2] genrec
1017
1018   If the [I] if-part fails you must derive R1 and R2 from:
1019   ::
1020
1021       ... R1 [F] R2
1022
1023   Just set the stack arguments in front, and figure out what R1 and R2
1024   have to do to apply the quoted [F] in the proper way.  In effect, the
1025   genrec combinator turns into an ifte combinator with a quoted copy of
1026   the original definition in the else-part:
1027   ::
1028
1029       F == [I] [T] [R1]   [R2] genrec
1030         == [I] [T] [R1 [F] R2] ifte
1031
1032   Primitive recursive functions are those where R2 == i.
1033   ::
1034
1035       P == [I] [T] [R] primrec
1036         == [I] [T] [R [P] i] ifte
1037         == [I] [T] [R P] ifte
1038
1039   '''
1040   (rec2, (rec1, stack)) = stack
1041   (then, (if_, _)) = stack
1042   F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
1043   else_ = concat(rec1, (F, rec2))
1044   return (else_, stack), (S_ifte, expression), dictionary
1045
1046
1047 @inscribe
1048 @combinator_effect(_COMB_NUMS(), s7, s6)
1049 @FunctionWrapper
1050 def map_(S, expression, dictionary):
1051   '''
1052   Run the quoted program on TOS on the items in the list under it, push a
1053   new list with the results (in place of the program and original list.
1054   '''
1055 #  (quote, (aggregate, stack)) = S
1056 #  results = list_to_stack([
1057 #    joy((term, stack), quote, dictionary)[0][0]
1058 #    for term in iter_stack(aggregate)
1059 #    ])
1060 #  return (results, stack), expression, dictionary
1061   (quote, (aggregate, stack)) = S
1062   if not aggregate:
1063     return (aggregate, stack), expression, dictionary
1064   batch = ()
1065   for term in iter_stack(aggregate):
1066     s = term, stack
1067     batch = (s, (quote, (S_infra, (S_first, batch))))
1068   stack = (batch, ((), stack))
1069   return stack, (S_infra, expression), dictionary
1070
1071
1072 #def cleave(S, expression, dictionary):
1073 #  '''
1074 #  The cleave combinator expects two quotations, and below that an item X.
1075 #  It first executes [P], with X on top, and saves the top result element.
1076 #  Then it executes [Q], again with X, and saves the top result.
1077 #  Finally it restores the stack to what it was below X and pushes the two
1078 #  results P(X) and Q(X).
1079 #  '''
1080 #  (Q, (P, (x, stack))) = S
1081 #  p = joy((x, stack), P, dictionary)[0][0]
1082 #  q = joy((x, stack), Q, dictionary)[0][0]
1083 #  return (q, (p, stack)), expression, dictionary
1084
1085
1086 def branch_true(stack, expression, dictionary):
1087   # pylint: disable=unused-variable
1088   (then, (else_, (flag, stack))) = stack
1089   return stack, concat(then, expression), dictionary
1090
1091
1092 def branch_false(stack, expression, dictionary):
1093   # pylint: disable=unused-variable
1094   (then, (else_, (flag, stack))) = stack
1095   return stack, concat(else_, expression), dictionary
1096
1097
1098 @inscribe
1099 @poly_combinator_effect(_COMB_NUMS(), [branch_true, branch_false], b1, s7, s6)
1100 @FunctionWrapper
1101 def branch(stack, expression, dictionary):
1102   '''
1103   Use a Boolean value to select one of two quoted programs to run.
1104
1105   ::
1106
1107       branch == roll< choice i
1108
1109   ::
1110
1111         False [F] [T] branch
1112      --------------------------
1113                F
1114
1115         True [F] [T] branch
1116      -------------------------
1117                   T
1118
1119   '''
1120   (then, (else_, (flag, stack))) = stack
1121   return stack, concat(then if flag else else_, expression), dictionary
1122
1123
1124 #FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
1125
1126
1127 ##@inscribe
1128 ##@FunctionWrapper
1129 ##def ifte(stack, expression, dictionary):
1130 ##  '''
1131 ##  If-Then-Else Combinator
1132 ##  ::
1133 ##
1134 ##                  ... [if] [then] [else] ifte
1135 ##       ---------------------------------------------------
1136 ##          ... [[else] [then]] [...] [if] infra select i
1137 ##
1138 ##
1139 ##
1140 ##
1141 ##                ... [if] [then] [else] ifte
1142 ##       -------------------------------------------------------
1143 ##          ... [else] [then] [...] [if] infra first choice i
1144 ##
1145 ##
1146 ##  Has the effect of grabbing a copy of the stack on which to run the
1147 ##  if-part using infra.
1148 ##  '''
1149 ##  (else_, (then, (if_, stack))) = stack
1150 ##  expression = (S_infra, (S_first, (S_choice, (S_i, expression))))
1151 ##  stack = (if_, (stack, (then, (else_, stack))))
1152 ##  return stack, expression, dictionary
1153
1154
1155 @inscribe
1156 @FunctionWrapper
1157 def cond(stack, expression, dictionary):
1158   '''
1159   This combinator works like a case statement.  It expects a single quote
1160   on the stack that must contain zero or more condition quotes and a 
1161   default quote.  Each condition clause should contain a quoted predicate
1162   followed by the function expression to run if that predicate returns
1163   true.  If no predicates return true the default function runs.
1164
1165   It works by rewriting into a chain of nested `ifte` expressions, e.g.::
1166
1167             [[[B0] T0] [[B1] T1] [D]] cond
1168       -----------------------------------------
1169          [B0] [T0] [[B1] [T1] [D] ifte] ifte
1170
1171   '''
1172   conditions, stack = stack
1173   if conditions:
1174     expression = _cond(conditions, expression)
1175     try:
1176       # Attempt to preload the args to first ifte.
1177       (P, (T, (E, expression))) = expression
1178     except ValueError:
1179       # If, for any reason, the argument to cond should happen to contain
1180       # only the default clause then this optimization will fail.
1181       pass
1182     else:
1183       stack = (E, (T, (P, stack)))
1184   return stack, expression, dictionary
1185
1186
1187 def _cond(conditions, expression):
1188   (clause, rest) = conditions
1189   if not rest:  # clause is [D]
1190     return clause
1191   P, T = clause
1192   return (P, (T, (_cond(rest, ()), (S_ifte, expression))))
1193
1194
1195 @inscribe
1196 @combinator_effect(_COMB_NUMS(), a1, s1)
1197 @FunctionWrapper
1198 def dip(stack, expression, dictionary):
1199   '''
1200   The dip combinator expects a quoted program on the stack and below it
1201   some item, it hoists the item into the expression and runs the program
1202   on the rest of the stack.
1203   ::
1204
1205        ... x [Q] dip
1206     -------------------
1207          ... Q x
1208
1209   '''
1210   (quote, (x, stack)) = stack
1211   expression = (x, expression)
1212   return stack, concat(quote, expression), dictionary
1213
1214
1215 @inscribe
1216 @combinator_effect(_COMB_NUMS(), a2, a1, s1)
1217 @FunctionWrapper
1218 def dipd(S, expression, dictionary):
1219   '''
1220   Like dip but expects two items.
1221   ::
1222
1223        ... y x [Q] dip
1224     ---------------------
1225          ... Q y x
1226
1227   '''
1228   (quote, (x, (y, stack))) = S
1229   expression = (y, (x, expression))
1230   return stack, concat(quote, expression), dictionary
1231
1232
1233 @inscribe
1234 @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1)
1235 @FunctionWrapper
1236 def dipdd(S, expression, dictionary):
1237   '''
1238   Like dip but expects three items.
1239   ::
1240
1241        ... z y x [Q] dip
1242     -----------------------
1243          ... Q z y x
1244
1245   '''
1246   (quote, (x, (y, (z, stack)))) = S
1247   expression = (z, (y, (x, expression)))
1248   return stack, concat(quote, expression), dictionary
1249
1250
1251 @inscribe
1252 @combinator_effect(_COMB_NUMS(), a1, s1)
1253 @FunctionWrapper
1254 def app1(S, expression, dictionary):
1255   '''
1256   Given a quoted program on TOS and anything as the second stack item run
1257   the program and replace the two args with the first result of the
1258   program.
1259   ::
1260
1261               ... x [Q] . app1
1262      -----------------------------------
1263         ... [x ...] [Q] . infra first
1264   '''
1265   (quote, (x, stack)) = S
1266   stack = (quote, ((x, stack), stack))
1267   expression = (S_infra, (S_first, expression))
1268   return stack, expression, dictionary
1269
1270
1271 @inscribe
1272 @combinator_effect(_COMB_NUMS(), a2, a1, s1)
1273 @FunctionWrapper
1274 def app2(S, expression, dictionary):
1275   '''Like app1 with two items.
1276   ::
1277
1278             ... y x [Q] . app2
1279      -----------------------------------
1280         ... [y ...] [Q] . infra first
1281             [x ...] [Q]   infra first
1282
1283   '''
1284   (quote, (x, (y, stack))) = S
1285   expression = (S_infra, (S_first,
1286     ((x, stack), (quote, (S_infra, (S_first,
1287       expression))))))
1288   stack = (quote, ((y, stack), stack))
1289   return stack, expression, dictionary
1290
1291
1292 @inscribe
1293 @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1)
1294 @FunctionWrapper
1295 def app3(S, expression, dictionary):
1296   '''Like app1 with three items.
1297   ::
1298
1299             ... z y x [Q] . app3
1300      -----------------------------------
1301         ... [z ...] [Q] . infra first
1302             [y ...] [Q]   infra first
1303             [x ...] [Q]   infra first
1304
1305   '''
1306   (quote, (x, (y, (z, stack)))) = S
1307   expression = (S_infra, (S_first,
1308     ((y, stack), (quote, (S_infra, (S_first,
1309     ((x, stack), (quote, (S_infra, (S_first,
1310       expression))))))))))
1311   stack = (quote, ((z, stack), stack))
1312   return stack, expression, dictionary
1313
1314
1315 @inscribe
1316 @combinator_effect(_COMB_NUMS(), s7, s6)
1317 @FunctionWrapper
1318 def step(S, expression, dictionary):
1319   '''
1320   Run a quoted program on each item in a sequence.
1321   ::
1322
1323           ... [] [Q] . step
1324        -----------------------
1325                  ... .
1326
1327
1328          ... [a] [Q] . step
1329       ------------------------
1330                ... a . Q
1331
1332
1333        ... [a b c] [Q] . step
1334     ----------------------------------------
1335                  ... a . Q [b c] [Q] step
1336
1337   The step combinator executes the quotation on each member of the list
1338   on top of the stack.
1339   '''
1340   (quote, (aggregate, stack)) = S
1341   if not aggregate:
1342     return stack, expression, dictionary
1343   head, tail = aggregate
1344   stack = quote, (head, stack)
1345   if tail:
1346     expression = tail, (quote, (S_step, expression))
1347   expression = S_i, expression
1348   return stack, expression, dictionary
1349
1350
1351 @inscribe
1352 @combinator_effect(_COMB_NUMS(), i1, s6)
1353 @FunctionWrapper
1354 def times(stack, expression, dictionary):
1355   '''
1356   times == [-- dip] cons [swap] infra [0 >] swap while pop
1357   ::
1358
1359        ... n [Q] . times
1360     ---------------------  w/ n <= 0
1361              ... .
1362
1363
1364        ... 1 [Q] . times
1365     ---------------------------------
1366              ... . Q
1367
1368
1369        ... n [Q] . times
1370     ---------------------------------  w/ n > 1
1371              ... . Q (n - 1) [Q] times
1372
1373   '''
1374   # times == [-- dip] cons [swap] infra [0 >] swap while pop
1375   (quote, (n, stack)) = stack
1376   if n <= 0:
1377     return stack, expression, dictionary
1378   n -= 1
1379   if n:
1380     expression = n, (quote, (S_times, expression))
1381   expression = concat(quote, expression)
1382   return stack, expression, dictionary
1383
1384
1385 # The current definition above works like this:
1386
1387 #             [P] [Q] while
1388 # --------------------------------------
1389 #    [P] nullary [Q [P] nullary] loop
1390
1391 #   while == [pop i not] [popop] [dudipd] primrec
1392
1393 #def while_(S, expression, dictionary):
1394 #  '''[if] [body] while'''
1395 #  (body, (if_, stack)) = S
1396 #  while joy(stack, if_, dictionary)[0][0]:
1397 #    stack = joy(stack, body, dictionary)[0]
1398 #  return stack, expression, dictionary
1399
1400
1401 @inscribe
1402 #@combinator_effect(_COMB_NUMS(), b1, s6)
1403 @FunctionWrapper
1404 def loop(stack, expression, dictionary):
1405   '''
1406   Basic loop combinator.
1407   ::
1408
1409        ... True [Q] loop
1410     -----------------------
1411           ... Q [Q] loop
1412
1413        ... False [Q] loop
1414     ------------------------
1415               ...
1416
1417   '''
1418   quote, (flag, stack) = stack
1419   if flag:
1420     expression = concat(quote, (quote, (S_loop, expression)))
1421   return stack, expression, dictionary
1422
1423
1424 @inscribe
1425 @combinator_effect(_COMB_NUMS(), a1, a2, s6, s7, s8)
1426 @FunctionWrapper
1427 def cmp_(stack, expression, dictionary):
1428   '''
1429   cmp takes two values and three quoted programs on the stack and runs
1430   one of the three depending on the results of comparing the two values:
1431   ::
1432
1433            a b [G] [E] [L] cmp
1434         ------------------------- a > b
1435                 G
1436
1437            a b [G] [E] [L] cmp
1438         ------------------------- a = b
1439                     E
1440
1441            a b [G] [E] [L] cmp
1442         ------------------------- a < b
1443                         L
1444   '''
1445   L, (E, (G, (b, (a, stack)))) = stack
1446   expression = concat(G if a > b else L if a < b else E, expression)
1447   return stack, expression, dictionary
1448
1449
1450 #  FunctionWrapper(cleave),
1451 #  FunctionWrapper(while_),
1452
1453
1454 for F in (
1455
1456   #divmod_ = pm = __(n2, n1), __(n4, n3)
1457
1458   sec_binary_cmp(BinaryBuiltinWrapper(operator.eq)),
1459   sec_binary_cmp(BinaryBuiltinWrapper(operator.ge)),
1460   sec_binary_cmp(BinaryBuiltinWrapper(operator.gt)),
1461   sec_binary_cmp(BinaryBuiltinWrapper(operator.le)),
1462   sec_binary_cmp(BinaryBuiltinWrapper(operator.lt)),
1463   sec_binary_cmp(BinaryBuiltinWrapper(operator.ne)),
1464
1465   sec_binary_ints(BinaryBuiltinWrapper(operator.xor)),
1466   sec_binary_ints(BinaryBuiltinWrapper(operator.lshift)),
1467   sec_binary_ints(BinaryBuiltinWrapper(operator.rshift)),
1468
1469   sec_binary_logic(BinaryBuiltinWrapper(operator.and_)),
1470   sec_binary_logic(BinaryBuiltinWrapper(operator.or_)),
1471
1472   sec_binary_math(BinaryBuiltinWrapper(operator.add)),
1473   sec_binary_math(BinaryBuiltinWrapper(operator.floordiv)),
1474   sec_binary_math(BinaryBuiltinWrapper(operator.mod)),
1475   sec_binary_math(BinaryBuiltinWrapper(operator.mul)),
1476   sec_binary_math(BinaryBuiltinWrapper(operator.pow)),
1477   sec_binary_math(BinaryBuiltinWrapper(operator.sub)),
1478   sec_binary_math(BinaryBuiltinWrapper(operator.truediv)),
1479
1480   sec_unary_logic(UnaryBuiltinWrapper(bool)),
1481   sec_unary_logic(UnaryBuiltinWrapper(operator.not_)),
1482
1483   sec_unary_math(UnaryBuiltinWrapper(abs)),
1484   sec_unary_math(UnaryBuiltinWrapper(operator.neg)),
1485   sec_unary_math(UnaryBuiltinWrapper(sqrt)),
1486
1487   stack_effect(n1)(i1)(UnaryBuiltinWrapper(floor)),
1488   ):
1489   inscribe(F)
1490 del F  # Otherwise Sphinx autodoc will pick it up.
1491
1492
1493 YIN_STACK_EFFECTS = yin_functions()
1494
1495 # Load the auto-generated primitives into the dictionary.
1496 _functions.update(YIN_STACK_EFFECTS)
1497 # exec '''
1498
1499 # eh = compose(dup, bool)
1500 # sqr = compose(dup, mul)
1501 # of = compose(swap, at)
1502
1503 # ''' in dict(compose=compose), _functions
1504 for name in sorted(_functions):
1505   sec = _functions[name]
1506   F = FUNCTIONS[name] = SymbolJoyType(name, [sec], _SYM_NUMS())
1507   if name in YIN_STACK_EFFECTS:
1508     _log.info('Setting stack effect for Yin function %s := %s', F.name, doc_from_stack_effect(*sec))
1509
1510 for name, primitive in getmembers(genlib, isfunction):
1511   inscribe(SimpleFunctionWrapper(primitive))
1512
1513
1514 add_aliases(_dictionary, ALIASES)
1515 add_aliases(_functions, ALIASES)
1516 add_aliases(FUNCTIONS, ALIASES)
1517
1518
1519 DefinitionWrapper.add_definitions(definitions, _dictionary)
1520
1521
1522 for name in ('''
1523 of quoted enstacken ? dinfrirst
1524 '''.split()):
1525   of_ = _dictionary[name]
1526   secs = infer_expression(of_.body)
1527   assert len(secs) == 1, repr(secs)
1528   _log.info(
1529     'Setting stack effect for definition %s := %s',
1530     name,
1531     doc_from_stack_effect(*secs[0]),
1532     )
1533   FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS())
1534
1535
1536 for name in ('''
1537 of quoted enstacken ? dinfrirst
1538 '''.split()):
1539   of_ = _dictionary[name]
1540   
1541   assert len(secs) == 1, repr(secs)
1542   _log.info(
1543     'Setting stack effect for definition %s := %s',
1544     name,
1545     doc_from_stack_effect(*secs[0]),
1546     )
1547   FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS())
1548
1549
1550 #sec_Ns_math(_dictionary['product'])
1551
1552 ##  product == 1 swap [*] step
1553 ##  flatten == [] swap [concat] step
1554 ##  quoted == [unit] dip
1555 ##  unquoted == [i] dip
1556 ##  enstacken == stack [clear] dip
1557 ##  ? == dup truthy
1558 ##  disenstacken == ? [uncons ?] loop pop
1559 ##  dinfrirst == dip infra first
1560 ##  nullary == [stack] dinfrirst
1561 ##  unary == nullary popd
1562 ##  binary == nullary [popop] dip
1563 ##  ternary == unary [popop] dip
1564 ##  pam == [i] map
1565 ##  run == [] swap infra
1566 ##  sqr == dup mul
1567 ##  size == 0 swap [pop ++] step
1568 ##  fork == [i] app2
1569 ##  cleave == fork [popd] dip
1570 ##  average == [sum 1.0 *] [size] cleave /
1571 ##  gcd == 1 [tuck modulus dup 0 >] loop pop
1572 ##  least_fraction == dup [gcd] infra [div] concat map
1573 ##  *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
1574 ##  *fraction0 == concat [[swap] dip * [*] dip] infra
1575 ##  down_to_zero == [0 >] [dup --] while
1576 ##  range_to_zero == unit [down_to_zero] infra
1577 ##  anamorphism == [pop []] swap [dip swons] genrec
1578 ##  range == [0 <=] [1 - dup] anamorphism
1579 ##  while == swap [nullary] cons dup dipd concat loop
1580 ##  dupdipd == dup dipd
1581 ##  primrec == [i] genrec
1582 ##  step_zero == 0 roll> step
1583 ##  codireco == cons dip rest cons
1584 ##  make_generator == [codireco] ccons
1585 ##  ifte == [nullary not] dipd branch