OSDN Git Service

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