OSDN Git Service

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