OSDN Git Service

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