OSDN Git Service

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