OSDN Git Service

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