OSDN Git Service

Loop and while combinators.
[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 @sec_unary_math
707 @SimpleFunctionWrapper
708 def succ(S):
709   '''Increment TOS.'''
710   (tos, stack) = S
711   return tos + 1, stack
712
713
714 @inscribe
715 @sec_unary_math
716 @SimpleFunctionWrapper
717 def pred(S):
718   '''Decrement TOS.'''
719   (tos, stack) = S
720   return tos - 1, stack
721
722
723 @inscribe
724 @SimpleFunctionWrapper
725 def pm(stack):
726   '''
727   Plus or minus
728   ::
729
730        a b pm
731     -------------
732        a+b a-b
733
734   '''
735   a, (b, stack) = stack
736   p, m, = b + a, b - a
737   return m, (p, stack)
738
739
740 def floor(n):
741   return int(math.floor(n))
742
743 floor.__doc__ = math.floor.__doc__
744
745
746 @inscribe
747 @SimpleFunctionWrapper
748 def divmod_(S):
749   '''
750   divmod(x, y) -> (quotient, remainder)
751
752   Return the tuple (x//y, x%y).  Invariant: div*y + mod == x.
753   '''
754   a, (b, stack) = S
755   d, m = divmod(a, b)
756   return d, (m, stack)
757
758
759 def sqrt(a):
760   '''
761   Return the square root of the number a.
762   Negative numbers return complex roots.
763   '''
764   try:
765     r = math.sqrt(a)
766   except ValueError:
767     assert a < 0, repr(a)
768     r = math.sqrt(-a) * 1j
769   return r
770
771
772 #def execute(S):
773 #  (text, stack) = S
774 #  if isinstance(text, str):
775 #    return run(text, stack)
776 #  return stack
777
778
779 @inscribe
780 @SimpleFunctionWrapper
781 def id_(stack):
782   '''The identity function.'''
783   return stack
784
785
786 @inscribe
787 @SimpleFunctionWrapper
788 def void(stack):
789   '''True if the form on TOS is void otherwise False.'''
790   form, stack = stack
791   return _void(form), stack
792
793
794 def _void(form):
795   return any(not _void(i) for i in iter_stack(form))
796
797
798
799 ##  transpose
800 ##  sign
801 ##  take
802
803
804 @inscribe
805 @FunctionWrapper
806 def words(stack, expression, dictionary):
807   '''Print all the words in alphabetical order.'''
808   print(' '.join(sorted(dictionary)))
809   return stack, expression, dictionary
810
811
812 @inscribe
813 @FunctionWrapper
814 def sharing(stack, expression, dictionary):
815   '''Print redistribution information.'''
816   print("You may convey verbatim copies of the Program's source code as"
817         ' you receive it, in any medium, provided that you conspicuously'
818         ' and appropriately publish on each copy an appropriate copyright'
819         ' notice; keep intact all notices stating that this License and'
820         ' any non-permissive terms added in accord with section 7 apply'
821         ' to the code; keep intact all notices of the absence of any'
822         ' warranty; and give all recipients a copy of this License along'
823         ' with the Program.'
824         ' You should have received a copy of the GNU General Public License'
825         ' along with Thun.  If not see <http://www.gnu.org/licenses/>.')
826   return stack, expression, dictionary
827
828
829 @inscribe
830 @FunctionWrapper
831 def warranty(stack, expression, dictionary):
832   '''Print warranty information.'''
833   print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
834         ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
835         ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
836         ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
837         ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
838         ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
839         ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
840         ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
841         ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
842   return stack, expression, dictionary
843
844
845 # def simple_manual(stack):
846 #   '''
847 #   Print words and help for each word.
848 #   '''
849 #   for name, f in sorted(FUNCTIONS.items()):
850 #     d = getdoc(f)
851 #     boxline = '+%s+' % ('-' * (len(name) + 2))
852 #     print('\n'.join((
853 #       boxline,
854 #       '| %s |' % (name,),
855 #       boxline,
856 #       d if d else '   ...',
857 #       '',
858 #       '--' * 40,
859 #       '',
860 #       )))
861 #   return stack
862
863
864 @inscribe
865 @FunctionWrapper
866 def help_(S, expression, dictionary):
867   '''Accepts a quoted symbol on the top of the stack and prints its docs.'''
868   ((symbol, _), stack) = S
869   word = dictionary[symbol]
870   print(getdoc(word))
871   return stack, expression, dictionary
872
873
874 #
875 # § Combinators
876 #
877
878
879 # Several combinators depend on other words in their definitions,
880 # we use symbols to prevent hard-coding these, so in theory, you
881 # could change the word in the dictionary to use different semantics.
882 S_choice = Symbol('choice')
883 S_first = Symbol('first')
884 S_getitem = Symbol('getitem')
885 S_genrec = Symbol('genrec')
886 S_loop = Symbol('loop')
887 S_i = Symbol('i')
888 S_ifte = Symbol('ifte')
889 S_infra = Symbol('infra')
890 S_pop = Symbol('pop')
891 S_step = Symbol('step')
892 S_times = Symbol('times')
893 S_swaack = Symbol('swaack')
894 S_truthy = Symbol('truthy')
895
896
897 @inscribe
898 @combinator_effect(_COMB_NUMS(), s1)
899 @FunctionWrapper
900 def i(stack, expression, dictionary):
901   '''
902   The i combinator expects a quoted program on the stack and unpacks it
903   onto the pending expression for evaluation.
904   ::
905
906        [Q] i
907     -----------
908         Q
909
910   '''
911   quote, stack = stack
912   return stack, concat(quote, expression), dictionary
913
914
915 @inscribe
916 @combinator_effect(_COMB_NUMS(), s1)
917 @FunctionWrapper
918 def x(stack, expression, dictionary):
919   '''
920   ::
921
922     x == dup i
923
924     ... [Q] x = ... [Q] dup i
925     ... [Q] x = ... [Q] [Q] i
926     ... [Q] x = ... [Q]  Q
927
928   '''
929   quote, _ = stack
930   return stack, concat(quote, expression), dictionary
931
932
933 @inscribe
934 @combinator_effect(_COMB_NUMS(), s7, s6)
935 @FunctionWrapper
936 def b(stack, expression, dictionary):
937   '''
938   ::
939
940     b == [i] dip i
941
942     ... [P] [Q] b == ... [P] i [Q] i
943     ... [P] [Q] b == ... P Q
944
945   '''
946   q, (p, (stack)) = stack
947   return stack, concat(p, concat(q, expression)), dictionary
948
949
950 @inscribe
951 @combinator_effect(_COMB_NUMS(), a1, s1)
952 @FunctionWrapper
953 def dupdip(stack, expression, dictionary):
954   '''
955   ::
956
957     [F] dupdip == dup [F] dip
958
959     ... a [F] dupdip
960     ... a dup [F] dip
961     ... a a   [F] dip
962     ... a F a
963
964   '''
965   F, stack = stack
966   a = stack[0]
967   return stack, concat(F, (a,  expression)), dictionary
968
969
970 @inscribe
971 @combinator_effect(_COMB_NUMS(), s7, s6)
972 @FunctionWrapper
973 def infra(stack, expression, dictionary):
974   '''
975   Accept a quoted program and a list on the stack and run the program
976   with the list as its stack.
977   ::
978
979        ... [a b c] [Q] . infra
980     -----------------------------
981        c b a . Q [...] swaack
982
983   '''
984   (quote, (aggregate, stack)) = stack
985   return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary
986
987
988 @inscribe
989 #@combinator_effect(_COMB_NUMS(), s7, s6, s5, s4)
990 @FunctionWrapper
991 def genrec(stack, expression, dictionary):
992   '''
993   General Recursion Combinator.
994   ::
995
996                           [if] [then] [rec1] [rec2] genrec
997     ---------------------------------------------------------------------
998        [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
999
1000   From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
1001   "The genrec combinator takes four program parameters in addition to
1002   whatever data parameters it needs. Fourth from the top is an if-part,
1003   followed by a then-part. If the if-part yields true, then the then-part
1004   is executed and the combinator terminates. The other two parameters are
1005   the rec1-part and the rec2-part. If the if-part yields false, the
1006   rec1-part is executed. Following that the four program parameters and
1007   the combinator are again pushed onto the stack bundled up in a quoted
1008   form. Then the rec2-part is executed, where it will find the bundled
1009   form. Typically it will then execute the bundled form, either with i or
1010   with app2, or some other combinator."
1011
1012   The way to design one of these is to fix your base case [then] and the
1013   test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
1014   a quotation of the whole function.
1015
1016   For example, given a (general recursive) function 'F':
1017   ::
1018
1019       F == [I] [T] [R1] [R2] genrec
1020
1021   If the [I] if-part fails you must derive R1 and R2 from:
1022   ::
1023
1024       ... R1 [F] R2
1025
1026   Just set the stack arguments in front, and figure out what R1 and R2
1027   have to do to apply the quoted [F] in the proper way.  In effect, the
1028   genrec combinator turns into an ifte combinator with a quoted copy of
1029   the original definition in the else-part:
1030   ::
1031
1032       F == [I] [T] [R1]   [R2] genrec
1033         == [I] [T] [R1 [F] R2] ifte
1034
1035   Primitive recursive functions are those where R2 == i.
1036   ::
1037
1038       P == [I] [T] [R] primrec
1039         == [I] [T] [R [P] i] ifte
1040         == [I] [T] [R P] ifte
1041
1042   '''
1043   (rec2, (rec1, stack)) = stack
1044   (then, (if_, _)) = stack
1045   F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
1046   else_ = concat(rec1, (F, rec2))
1047   return (else_, stack), (S_ifte, expression), dictionary
1048
1049
1050 @inscribe
1051 @combinator_effect(_COMB_NUMS(), s7, s6)
1052 @FunctionWrapper
1053 def map_(S, expression, dictionary):
1054   '''
1055   Run the quoted program on TOS on the items in the list under it, push a
1056   new list with the results (in place of the program and original list.
1057   '''
1058 #  (quote, (aggregate, stack)) = S
1059 #  results = list_to_stack([
1060 #    joy((term, stack), quote, dictionary)[0][0]
1061 #    for term in iter_stack(aggregate)
1062 #    ])
1063 #  return (results, stack), expression, dictionary
1064   (quote, (aggregate, stack)) = S
1065   if not aggregate:
1066     return (aggregate, stack), expression, dictionary
1067   batch = ()
1068   for term in iter_stack(aggregate):
1069     s = term, stack
1070     batch = (s, (quote, (S_infra, (S_first, batch))))
1071   stack = (batch, ((), stack))
1072   return stack, (S_infra, expression), dictionary
1073
1074
1075 #def cleave(S, expression, dictionary):
1076 #  '''
1077 #  The cleave combinator expects two quotations, and below that an item X.
1078 #  It first executes [P], with X on top, and saves the top result element.
1079 #  Then it executes [Q], again with X, and saves the top result.
1080 #  Finally it restores the stack to what it was below X and pushes the two
1081 #  results P(X) and Q(X).
1082 #  '''
1083 #  (Q, (P, (x, stack))) = S
1084 #  p = joy((x, stack), P, dictionary)[0][0]
1085 #  q = joy((x, stack), Q, dictionary)[0][0]
1086 #  return (q, (p, stack)), expression, dictionary
1087
1088
1089 def branch_true(stack, expression, dictionary):
1090   # pylint: disable=unused-variable
1091   (then, (else_, (flag, stack))) = stack
1092   return stack, concat(then, expression), dictionary
1093
1094
1095 def branch_false(stack, expression, dictionary):
1096   # pylint: disable=unused-variable
1097   (then, (else_, (flag, stack))) = stack
1098   return stack, concat(else_, expression), dictionary
1099
1100
1101 @inscribe
1102 @poly_combinator_effect(_COMB_NUMS(), [branch_true, branch_false], b1, s7, s6)
1103 @FunctionWrapper
1104 def branch(stack, expression, dictionary):
1105   '''
1106   Use a Boolean value to select one of two quoted programs to run.
1107
1108   ::
1109
1110       branch == roll< choice i
1111
1112   ::
1113
1114         False [F] [T] branch
1115      --------------------------
1116                F
1117
1118         True [F] [T] branch
1119      -------------------------
1120                   T
1121
1122   '''
1123   (then, (else_, (flag, stack))) = stack
1124   return stack, concat(then if flag else else_, expression), dictionary
1125
1126
1127 #FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
1128
1129
1130 ##@inscribe
1131 ##@FunctionWrapper
1132 ##def ifte(stack, expression, dictionary):
1133 ##  '''
1134 ##  If-Then-Else Combinator
1135 ##  ::
1136 ##
1137 ##                  ... [if] [then] [else] ifte
1138 ##       ---------------------------------------------------
1139 ##          ... [[else] [then]] [...] [if] infra select i
1140 ##
1141 ##
1142 ##
1143 ##
1144 ##                ... [if] [then] [else] ifte
1145 ##       -------------------------------------------------------
1146 ##          ... [else] [then] [...] [if] infra first choice i
1147 ##
1148 ##
1149 ##  Has the effect of grabbing a copy of the stack on which to run the
1150 ##  if-part using infra.
1151 ##  '''
1152 ##  (else_, (then, (if_, stack))) = stack
1153 ##  expression = (S_infra, (S_first, (S_choice, (S_i, expression))))
1154 ##  stack = (if_, (stack, (then, (else_, stack))))
1155 ##  return stack, expression, dictionary
1156
1157
1158 @inscribe
1159 @FunctionWrapper
1160 def cond(stack, expression, dictionary):
1161   '''
1162   This combinator works like a case statement.  It expects a single quote
1163   on the stack that must contain zero or more condition quotes and a 
1164   default quote.  Each condition clause should contain a quoted predicate
1165   followed by the function expression to run if that predicate returns
1166   true.  If no predicates return true the default function runs.
1167
1168   It works by rewriting into a chain of nested `ifte` expressions, e.g.::
1169
1170             [[[B0] T0] [[B1] T1] [D]] cond
1171       -----------------------------------------
1172          [B0] [T0] [[B1] [T1] [D] ifte] ifte
1173
1174   '''
1175   conditions, stack = stack
1176   if conditions:
1177     expression = _cond(conditions, expression)
1178     try:
1179       # Attempt to preload the args to first ifte.
1180       (P, (T, (E, expression))) = expression
1181     except ValueError:
1182       # If, for any reason, the argument to cond should happen to contain
1183       # only the default clause then this optimization will fail.
1184       pass
1185     else:
1186       stack = (E, (T, (P, stack)))
1187   return stack, expression, dictionary
1188
1189
1190 def _cond(conditions, expression):
1191   (clause, rest) = conditions
1192   if not rest:  # clause is [D]
1193     return clause
1194   P, T = clause
1195   return (P, (T, (_cond(rest, ()), (S_ifte, expression))))
1196
1197
1198 @inscribe
1199 @combinator_effect(_COMB_NUMS(), a1, s1)
1200 @FunctionWrapper
1201 def dip(stack, expression, dictionary):
1202   '''
1203   The dip combinator expects a quoted program on the stack and below it
1204   some item, it hoists the item into the expression and runs the program
1205   on the rest of the stack.
1206   ::
1207
1208        ... x [Q] dip
1209     -------------------
1210          ... Q x
1211
1212   '''
1213   (quote, (x, stack)) = stack
1214   expression = (x, expression)
1215   return stack, concat(quote, expression), dictionary
1216
1217
1218 @inscribe
1219 @combinator_effect(_COMB_NUMS(), a2, a1, s1)
1220 @FunctionWrapper
1221 def dipd(S, expression, dictionary):
1222   '''
1223   Like dip but expects two items.
1224   ::
1225
1226        ... y x [Q] dip
1227     ---------------------
1228          ... Q y x
1229
1230   '''
1231   (quote, (x, (y, stack))) = S
1232   expression = (y, (x, expression))
1233   return stack, concat(quote, expression), dictionary
1234
1235
1236 @inscribe
1237 @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1)
1238 @FunctionWrapper
1239 def dipdd(S, expression, dictionary):
1240   '''
1241   Like dip but expects three items.
1242   ::
1243
1244        ... z y x [Q] dip
1245     -----------------------
1246          ... Q z y x
1247
1248   '''
1249   (quote, (x, (y, (z, stack)))) = S
1250   expression = (z, (y, (x, expression)))
1251   return stack, concat(quote, expression), dictionary
1252
1253
1254 @inscribe
1255 @combinator_effect(_COMB_NUMS(), a1, s1)
1256 @FunctionWrapper
1257 def app1(S, expression, dictionary):
1258   '''
1259   Given a quoted program on TOS and anything as the second stack item run
1260   the program and replace the two args with the first result of the
1261   program.
1262   ::
1263
1264               ... x [Q] . app1
1265      -----------------------------------
1266         ... [x ...] [Q] . infra first
1267   '''
1268   (quote, (x, stack)) = S
1269   stack = (quote, ((x, stack), stack))
1270   expression = (S_infra, (S_first, expression))
1271   return stack, expression, dictionary
1272
1273
1274 @inscribe
1275 @combinator_effect(_COMB_NUMS(), a2, a1, s1)
1276 @FunctionWrapper
1277 def app2(S, expression, dictionary):
1278   '''Like app1 with two items.
1279   ::
1280
1281             ... y x [Q] . app2
1282      -----------------------------------
1283         ... [y ...] [Q] . infra first
1284             [x ...] [Q]   infra first
1285
1286   '''
1287   (quote, (x, (y, stack))) = S
1288   expression = (S_infra, (S_first,
1289     ((x, stack), (quote, (S_infra, (S_first,
1290       expression))))))
1291   stack = (quote, ((y, stack), stack))
1292   return stack, expression, dictionary
1293
1294
1295 @inscribe
1296 @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1)
1297 @FunctionWrapper
1298 def app3(S, expression, dictionary):
1299   '''Like app1 with three items.
1300   ::
1301
1302             ... z y x [Q] . app3
1303      -----------------------------------
1304         ... [z ...] [Q] . infra first
1305             [y ...] [Q]   infra first
1306             [x ...] [Q]   infra first
1307
1308   '''
1309   (quote, (x, (y, (z, stack)))) = S
1310   expression = (S_infra, (S_first,
1311     ((y, stack), (quote, (S_infra, (S_first,
1312     ((x, stack), (quote, (S_infra, (S_first,
1313       expression))))))))))
1314   stack = (quote, ((z, stack), stack))
1315   return stack, expression, dictionary
1316
1317
1318 @inscribe
1319 @combinator_effect(_COMB_NUMS(), s7, s6)
1320 @FunctionWrapper
1321 def step(S, expression, dictionary):
1322   '''
1323   Run a quoted program on each item in a sequence.
1324   ::
1325
1326           ... [] [Q] . step
1327        -----------------------
1328                  ... .
1329
1330
1331          ... [a] [Q] . step
1332       ------------------------
1333                ... a . Q
1334
1335
1336        ... [a b c] [Q] . step
1337     ----------------------------------------
1338                  ... a . Q [b c] [Q] step
1339
1340   The step combinator executes the quotation on each member of the list
1341   on top of the stack.
1342   '''
1343   (quote, (aggregate, stack)) = S
1344   if not aggregate:
1345     return stack, expression, dictionary
1346   head, tail = aggregate
1347   stack = quote, (head, stack)
1348   if tail:
1349     expression = tail, (quote, (S_step, expression))
1350   expression = S_i, expression
1351   return stack, expression, dictionary
1352
1353
1354 @inscribe
1355 @combinator_effect(_COMB_NUMS(), i1, s6)
1356 @FunctionWrapper
1357 def times(stack, expression, dictionary):
1358   '''
1359   times == [-- dip] cons [swap] infra [0 >] swap while pop
1360   ::
1361
1362        ... n [Q] . times
1363     ---------------------  w/ n <= 0
1364              ... .
1365
1366
1367        ... 1 [Q] . times
1368     ---------------------------------
1369              ... . Q
1370
1371
1372        ... n [Q] . times
1373     ---------------------------------  w/ n > 1
1374              ... . Q (n - 1) [Q] times
1375
1376   '''
1377   # times == [-- dip] cons [swap] infra [0 >] swap while pop
1378   (quote, (n, stack)) = stack
1379   if n <= 0:
1380     return stack, expression, dictionary
1381   n -= 1
1382   if n:
1383     expression = n, (quote, (S_times, expression))
1384   expression = concat(quote, expression)
1385   return stack, expression, dictionary
1386
1387
1388 # The current definition above works like this:
1389
1390 #             [P] [Q] while
1391 # --------------------------------------
1392 #    [P] nullary [Q [P] nullary] loop
1393
1394 #   while == [pop i not] [popop] [dudipd] primrec
1395
1396 #def while_(S, expression, dictionary):
1397 #  '''[if] [body] while'''
1398 #  (body, (if_, stack)) = S
1399 #  while joy(stack, if_, dictionary)[0][0]:
1400 #    stack = joy(stack, body, dictionary)[0]
1401 #  return stack, expression, dictionary
1402
1403
1404 def loop_true(stack, expression, dictionary):
1405     quote, (flag, stack) = stack  # pylint: disable=unused-variable
1406     return stack, concat(quote, (S_pop, expression)), dictionary
1407
1408 def loop_two_true(stack, expression, dictionary):
1409     quote, (flag, stack) = stack  # pylint: disable=unused-variable
1410     return stack, concat(quote, (S_pop, concat(quote, (S_pop, expression)))), dictionary
1411
1412 def loop_false(stack, expression, dictionary):
1413     quote, (flag, stack) = stack  # pylint: disable=unused-variable
1414     return stack, expression, dictionary
1415
1416
1417 @inscribe
1418 @poly_combinator_effect(_COMB_NUMS(), [loop_two_true, loop_true, loop_false], b1, s6)
1419 @FunctionWrapper
1420 def loop(stack, expression, dictionary):
1421   '''
1422   Basic loop combinator.
1423   ::
1424
1425        ... True [Q] loop
1426     -----------------------
1427           ... Q [Q] loop
1428
1429        ... False [Q] loop
1430     ------------------------
1431               ...
1432
1433   '''
1434   quote, (flag, stack) = stack
1435   if flag:
1436     expression = concat(quote, (quote, (S_loop, expression)))
1437   return stack, expression, dictionary
1438
1439
1440 @inscribe
1441 @combinator_effect(_COMB_NUMS(), a1, a2, s6, s7, s8)
1442 @FunctionWrapper
1443 def cmp_(stack, expression, dictionary):
1444   '''
1445   cmp takes two values and three quoted programs on the stack and runs
1446   one of the three depending on the results of comparing the two values:
1447   ::
1448
1449            a b [G] [E] [L] cmp
1450         ------------------------- a > b
1451                 G
1452
1453            a b [G] [E] [L] cmp
1454         ------------------------- a = b
1455                     E
1456
1457            a b [G] [E] [L] cmp
1458         ------------------------- a < b
1459                         L
1460   '''
1461   L, (E, (G, (b, (a, stack)))) = stack
1462   expression = concat(G if a > b else L if a < b else E, expression)
1463   return stack, expression, dictionary
1464
1465
1466 #  FunctionWrapper(cleave),
1467 #  FunctionWrapper(while_),
1468
1469
1470 for F in (
1471
1472   #divmod_ = pm = __(n2, n1), __(n4, n3)
1473
1474   sec_binary_cmp(BinaryBuiltinWrapper(operator.eq)),
1475   sec_binary_cmp(BinaryBuiltinWrapper(operator.ge)),
1476   sec_binary_cmp(BinaryBuiltinWrapper(operator.gt)),
1477   sec_binary_cmp(BinaryBuiltinWrapper(operator.le)),
1478   sec_binary_cmp(BinaryBuiltinWrapper(operator.lt)),
1479   sec_binary_cmp(BinaryBuiltinWrapper(operator.ne)),
1480
1481   sec_binary_ints(BinaryBuiltinWrapper(operator.xor)),
1482   sec_binary_ints(BinaryBuiltinWrapper(operator.lshift)),
1483   sec_binary_ints(BinaryBuiltinWrapper(operator.rshift)),
1484
1485   sec_binary_logic(BinaryBuiltinWrapper(operator.and_)),
1486   sec_binary_logic(BinaryBuiltinWrapper(operator.or_)),
1487
1488   sec_binary_math(BinaryBuiltinWrapper(operator.add)),
1489   sec_binary_math(BinaryBuiltinWrapper(operator.floordiv)),
1490   sec_binary_math(BinaryBuiltinWrapper(operator.mod)),
1491   sec_binary_math(BinaryBuiltinWrapper(operator.mul)),
1492   sec_binary_math(BinaryBuiltinWrapper(operator.pow)),
1493   sec_binary_math(BinaryBuiltinWrapper(operator.sub)),
1494   sec_binary_math(BinaryBuiltinWrapper(operator.truediv)),
1495
1496   sec_unary_logic(UnaryBuiltinWrapper(bool)),
1497   sec_unary_logic(UnaryBuiltinWrapper(operator.not_)),
1498
1499   sec_unary_math(UnaryBuiltinWrapper(abs)),
1500   sec_unary_math(UnaryBuiltinWrapper(operator.neg)),
1501   sec_unary_math(UnaryBuiltinWrapper(sqrt)),
1502
1503   stack_effect(n1)(i1)(UnaryBuiltinWrapper(floor)),
1504   ):
1505   inscribe(F)
1506 del F  # Otherwise Sphinx autodoc will pick it up.
1507
1508
1509 YIN_STACK_EFFECTS = yin_functions()
1510
1511 # Load the auto-generated primitives into the dictionary.
1512 _functions.update(YIN_STACK_EFFECTS)
1513 # exec '''
1514
1515 # eh = compose(dup, bool)
1516 # sqr = compose(dup, mul)
1517 # of = compose(swap, at)
1518
1519 # ''' in dict(compose=compose), _functions
1520 for name in sorted(_functions):
1521   sec = _functions[name]
1522   F = FUNCTIONS[name] = SymbolJoyType(name, [sec], _SYM_NUMS())
1523   if name in YIN_STACK_EFFECTS:
1524     _log.info('Setting stack effect for Yin function %s := %s', F.name, doc_from_stack_effect(*sec))
1525
1526 for name, primitive in getmembers(genlib, isfunction):
1527   inscribe(SimpleFunctionWrapper(primitive))
1528
1529
1530 add_aliases(_dictionary, ALIASES)
1531 add_aliases(_functions, ALIASES)
1532 add_aliases(FUNCTIONS, ALIASES)
1533
1534
1535 DefinitionWrapper.add_definitions(definitions, _dictionary)
1536
1537
1538 EXPECTATIONS = dict(
1539   ifte=(s7, (s6, (s5, s4))),
1540   nullary=(s7, s6),
1541   run=(s7, s6),
1542 )
1543 EXPECTATIONS['while'] = (s7, (s6, s5))
1544
1545
1546 for name in '''
1547   dinfrirst
1548   nullary
1549   ifte
1550   run
1551   dupdipd codireco
1552   while
1553   '''.split():
1554   C = _dictionary[name]
1555   expect = EXPECTATIONS.get(name)
1556   if expect:
1557     sec = doc_from_stack_effect(expect)
1558     _log.info('Setting stack EXPECT for combinator %s := %s', C.name, sec)
1559   else:
1560     _log.info('combinator %s', C.name)
1561   FUNCTIONS[name] = CombinatorJoyType(name, [C], _COMB_NUMS(), expect)
1562
1563
1564 for name in ('''
1565   of quoted enstacken ?
1566   unary binary ternary
1567   sqr unquoted
1568   '''.split()):
1569   of_ = _dictionary[name]
1570   secs = infer_expression(of_.body)
1571   assert len(secs) == 1, repr(secs)
1572   _log.info(
1573     'Setting stack effect for definition %s := %s',
1574     name,
1575     doc_from_stack_effect(*secs[0]),
1576     )
1577   FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS())
1578
1579
1580 #sec_Ns_math(_dictionary['product'])
1581
1582 ##  product == 1 swap [*] step
1583 ##  flatten == [] swap [concat] step
1584 ##  disenstacken == ? [uncons ?] loop pop
1585 ##  pam == [i] map
1586 ##  size == 0 swap [pop ++] step
1587 ##  fork == [i] app2
1588 ##  cleave == fork [popd] dip
1589 ##  average == [sum 1.0 *] [size] cleave /
1590 ##  gcd == 1 [tuck modulus dup 0 >] loop pop
1591 ##  least_fraction == dup [gcd] infra [div] concat map
1592 ##  *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
1593 ##  *fraction0 == concat [[swap] dip * [*] dip] infra
1594 ##  down_to_zero == [0 >] [dup --] while
1595 ##  range_to_zero == unit [down_to_zero] infra
1596 ##  anamorphism == [pop []] swap [dip swons] genrec
1597 ##  range == [0 <=] [1 - dup] anamorphism
1598 ##  while == swap [nullary] cons dup dipd concat loop
1599 ##  dupdipd == dup dipd
1600 ##  primrec == [i] genrec
1601 ##  step_zero == 0 roll> step
1602 ##  codireco == cons dip rest cons
1603 ##  make_generator == [codireco] ccons
1604 ##  ifte == [nullary not] dipd branch