OSDN Git Service

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