OSDN Git Service

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