1 # -*- coding: utf-8 -*-
3 # Copyright © 2014, 2015, 2017, 2018 Simon Forman
5 # This file is part of Thun
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.
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.
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/>.
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()
26 from __future__ import print_function
27 from builtins import map, object, range, zip
28 from logging import getLogger
30 _log = getLogger(__name__)
31 _log.info('Loading library.')
33 from inspect import getdoc
34 from functools import wraps
35 from itertools import count
36 from inspect import getmembers, isfunction
39 from .parser import text_to_expression, Symbol
40 from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat
42 if sys.version_info.major < 3:
43 from .utils.brutal_hackery import rename_code_object
45 rename_code_object = lambda _: lambda f: f
47 from .utils import generated_library as genlib
48 from .utils.types import (
70 poly_combinator_effect,
71 doc_from_stack_effect,
75 _SYM_NUMS = lambda c=count(): next(c)
76 _COMB_NUMS = lambda c=count(): next(c)
80 A = a0, a1, a2, a3, a4, a5, a6, a7, a8, a9 = list(map(AnyJoyType, _R))
81 B = b0, b1, b2, b3, b4, b5, b6, b7, b8, b9 = list(map(BooleanJoyType, _R))
82 N = n0, n1, n2, n3, n4, n5, n6, n7, n8, n9 = list(map(NumberJoyType, _R))
83 S = s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 = list(map(StackJoyType, _R))
84 F = f0, f1, f2, f3, f4, f5, f6, f7, f8, f9 = list(map(FloatJoyType, _R))
85 I = i0, i1, i2, i3, i4, i5, i6, i7, i8, i9 = list(map(IntJoyType, _R))
86 T = t0, t1, t2, t3, t4, t5, t6, t7, t8, t9 = list(map(TextJoyType, _R))
89 _R = list(range(1, 11))
90 As = list(map(AnyStarJoyType, _R))
91 Ns = list(map(NumberStarJoyType, _R))
92 Ss = list(map(StackStarJoyType, _R))
95 sec0 = stack_effect(t1)()
96 sec1 = stack_effect(s0, i1)(s1)
97 sec2 = stack_effect(s0, i1)(a1)
98 sec_binary_cmp = stack_effect(n1, n2)(b1)
99 sec_binary_ints = stack_effect(i1, i2)(i3)
100 sec_binary_logic = stack_effect(b1, b2)(b3)
101 sec_binary_math = stack_effect(n1, n2)(n3)
102 sec_unary_logic = stack_effect(a1)(b1)
103 sec_unary_math = stack_effect(n1)(n2)
104 sec_Ns_math = stack_effect((Ns[1], s1),)(n0)
109 def inscribe(function):
110 '''A decorator to inscribe functions into the default dictionary.'''
111 _dictionary[function.name] = function
116 '''Return a dictionary of Joy functions for use with joy().'''
117 return _dictionary.copy()
123 ('bool', ['truthy']),
125 ('floordiv', ['/floor', '//']),
126 ('floor', ['round']),
128 ('mod', ['%', 'rem', 'remainder', 'modulus']),
131 ('getitem', ['pick', 'at']),
136 ('ne', ['<>', '!=']),
142 ('rolldown', ['roll<']),
143 ('rollup', ['roll>']),
149 def add_aliases(D, A):
151 Given a dict and a iterable of (name, [alias, ...]) pairs, create
152 additional entries in the dict mapping each alias to the named function
153 if it's in the dict. Aliases for functions not in the dict are ignored.
155 for name, aliases in A:
160 for alias in aliases:
166 Return a dict of named stack effects.
168 "Yin" functions are those that only rearrange items in stacks and
169 can be defined completely by their stack effects. This means they
170 can be auto-compiled.
172 # pylint: disable=unused-variable
173 cons = ef(a1, s0)((a1, s0))
174 ccons = compose(cons, cons)
176 dupd = ef(a2, a1)(a2, a2, a1)
177 dupdd = ef(a3, a2, a1)(a3, a3, a2, a1)
178 first = ef((a1, s1),)(a1,)
179 over = ef(a2, a1)(a2, a1, a2)
181 popd = ef(a2, a1,)(a1)
182 popdd = ef(a3, a2, a1,)(a2, a1,)
183 popop = ef(a2, a1,)()
184 popopd = ef(a3, a2, a1,)(a1)
185 popopdd = ef(a4, a3, a2, a1,)(a2, a1)
186 rest = ef((a1, s0),)(s0,)
187 rolldown = ef(a1, a2, a3)(a2, a3, a1)
188 rollup = ef(a1, a2, a3)(a3, a1, a2)
189 rrest = compose(rest, rest)
190 second = compose(rest, first)
192 swaack = (s1, s0), (s0, s1)
193 swap = ef(a1, a2)(a2, a1)
194 swons = compose(swap, cons)
195 third = compose(rest, second)
196 tuck = ef(a2, a1)(a1, a2, a1)
197 uncons = ef((a1, s0),)(a1, s0)
198 unswons = compose(uncons, swap)
199 stuncons = compose(stack, uncons)
200 stununcons = compose(stack, uncons, uncons)
201 unit = ef(a1)((a1, ()))
203 first_two = compose(uncons, uncons, pop)
204 fourth = compose(rest, third)
206 _Tree_add_Ee = compose(pop, swap, rolldown, rrest, ccons)
207 _Tree_get_E = compose(popop, second)
208 _Tree_delete_clear_stuff = compose(rollup, popop, rest)
209 _Tree_delete_R0 = compose(over, first, swap, dup)
217 product == 1 swap [*] step
218 flatten == [] swap [concat] step
221 enstacken == stack [clear] dip
223 disenstacken == ? [uncons ?] loop pop
224 dinfrirst == dip infra first
225 nullary == [stack] dinfrirst
226 unary == nullary popd
227 binary == nullary [popop] dip
228 ternary == unary [popop] dip
232 size == 0 swap [pop ++] step
234 cleave == fork [popd] dip
235 average == [sum 1.0 *] [size] cleave /
236 gcd == 1 [tuck modulus dup 0 >] loop pop
237 least_fraction == dup [gcd] infra [div] concat map
238 *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
239 *fraction0 == concat [[swap] dip * [*] dip] infra
240 down_to_zero == [0 >] [dup --] while
241 range_to_zero == unit [down_to_zero] infra
242 anamorphism == [pop []] swap [dip swons] genrec
243 range == [0 <=] [1 - dup] anamorphism
244 while == swap [nullary] cons dup dipd concat loop
246 primrec == [i] genrec
247 step_zero == 0 roll> step
248 codireco == cons dip rest cons
249 make_generator == [codireco] ccons
250 ifte == [nullary not] dipd branch
254 # ifte == [nullary] dipd swap branch
255 # genrec == [[genrec] cons cons cons cons] nullary swons concat ifte
257 # Another definition for while. FWIW
258 # while == over [[i] dip nullary] ccons [nullary] dip loop
262 ##second == rest first
263 ##third == rest rest first
265 ##swoncat == swap concat
268 ##z-down == [] swap uncons swap
269 ##z-up == swons swap shunt
270 ##z-right == [swons] cons dip uncons swap
271 ##z-left == swons [uncons swap] dip swap
274 ##divisor == popop 2 *
276 ##radical == swap dup * rollup * 4 * - sqrt
279 ##q0 == [[divisor] [minusb] [radical]] pam
280 ##q1 == [[root1] [root2]] pam
281 ##quadratic == [q0] ternary i [q1] ternary
285 ##PE1.1 == + dup [+] dip
286 ##PE1.2 == dup [3 & PE1.1] dip 2 >>
287 ##PE1.3 == 14811 swap [PE1.2] times pop
288 ##PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop
290 #PE1.2 == [PE1.1] step
291 #PE1 == 0 0 66 [[3 2 1 3 1 2 3] PE1.2] times [3 2 1 3] PE1.2 pop
295 def FunctionWrapper(f):
296 '''Set name attribute.'''
298 raise ValueError('Function %s must have doc string.' % f.__name__)
299 f.name = f.__name__.rstrip('_') # Don't shadow builtins.
303 def SimpleFunctionWrapper(f):
305 Wrap functions that take and return just a stack.
309 @rename_code_object(f.__name__)
310 def inner(stack, expression, dictionary):
311 return f(stack), expression, dictionary
315 def BinaryBuiltinWrapper(f):
317 Wrap functions that take two arguments and return a single result.
321 @rename_code_object(f.__name__)
322 def inner(stack, expression, dictionary):
323 (a, (b, stack)) = stack
325 return (result, stack), expression, dictionary
329 def UnaryBuiltinWrapper(f):
331 Wrap functions that take one argument and return a single result.
335 @rename_code_object(f.__name__)
336 def inner(stack, expression, dictionary):
339 return (result, stack), expression, dictionary
343 class DefinitionWrapper(object):
345 Provide implementation of defined functions, and some helper methods.
348 def __init__(self, name, body_text, doc=None):
349 self.name = self.__name__ = name
350 self.body = text_to_expression(body_text)
351 self._body = tuple(iter_stack(self.body))
352 self.__doc__ = doc or body_text
353 self._compiled = None
355 def __call__(self, stack, expression, dictionary):
357 return self._compiled(stack, expression, dictionary) # pylint: disable=E1102
358 expression = list_to_stack(self._body, expression)
359 return stack, expression, dictionary
362 def parse_definition(class_, defi):
364 Given some text describing a Joy function definition parse it and
365 return a DefinitionWrapper.
367 name, proper, body_text = (n.strip() for n in defi.partition('=='))
369 raise ValueError('Definition %r failed' % (defi,))
370 return class_(name, body_text)
373 def add_definitions(class_, defs, dictionary):
375 Scan multi-line string defs for definitions and add them to the
378 for definition in _text_to_defs(defs):
379 class_.add_def(definition, dictionary)
382 def add_def(class_, definition, dictionary, fail_fails=False):
384 Add the definition to the dictionary.
386 F = class_.parse_definition(definition)
387 _log.info('Adding definition %s := %s', F.name, expression_to_string(F.body))
388 dictionary[F.name] = F
391 def load_definitions(class_, filename, dictionary):
392 with open(filename) as f:
393 lines = [line for line in f if '==' in line]
395 class_.add_def(line, dictionary)
398 def _text_to_defs(text):
399 return (line.strip() for line in text.splitlines() if '==' in line)
410 def inscribe_(stack, expression, dictionary):
412 Create a new Joy function definition in the Joy dictionary. A
413 definition is given as a string with a name followed by a double
414 equal sign then one or more Joy functions, the body. for example:
418 If you want the definition to persist over restarts, enter it into
419 the definitions.txt resource.
421 definition, stack = stack
422 DefinitionWrapper.add_def(definition, dictionary, fail_fails=True)
423 return stack, expression, dictionary
427 @SimpleFunctionWrapper
429 '''Parse the string on the stack to a Joy expression.'''
431 expression = text_to_expression(text)
432 return expression, stack
436 @SimpleFunctionWrapper
438 '''Attempt to infer the stack effect of a Joy expression.'''
440 effects = infer_expression(E)
441 e = list_to_stack([(fi, (fo, ())) for fi, fo in effects])
447 @SimpleFunctionWrapper
452 getitem == drop first
454 Expects an integer and a quote on the stack and returns the item at the
455 nth position in the quote counting from 0.
459 -------------------------
463 n, (Q, stack) = stack
464 return pick(Q, n), stack
469 @SimpleFunctionWrapper
476 Expects an integer and a quote on the stack and returns the quote with
477 n items removed off the top.
481 ----------------------
485 n, (Q, stack) = stack
497 @SimpleFunctionWrapper
500 Expects an integer and a quote on the stack and returns the quote with
501 just the top n items in reverse order (because that's easier and you can
502 use reverse if needed.)
506 ----------------------
510 n, (Q, stack) = stack
523 @SimpleFunctionWrapper
526 Use a Boolean value to select one of two items.
530 ----------------------
535 ---------------------
538 Currently Python semantics are used to evaluate the "truthiness" of the
539 Boolean value (so empty string, zero, etc. are counted as false, etc.)
541 (if_, (then, (else_, stack))) = stack
542 return then if if_ else else_, stack
546 @SimpleFunctionWrapper
549 Use a Boolean value to select one of two items from a sequence.
553 ------------------------
558 -----------------------
561 The sequence can contain more than two items but not fewer.
562 Currently Python semantics are used to evaluate the "truthiness" of the
563 Boolean value (so empty string, zero, etc. are counted as false, etc.)
565 (flag, (choices, stack)) = stack
566 (else_, (then, _)) = choices
567 return then if flag else else_, stack
572 @SimpleFunctionWrapper
574 '''Given a list find the maximum.'''
576 return max(iter_stack(tos)), stack
581 @SimpleFunctionWrapper
583 '''Given a list find the minimum.'''
585 return min(iter_stack(tos)), stack
590 @SimpleFunctionWrapper
592 '''Given a quoted sequence of numbers return the sum.
594 sum == 0 swap [+] step
597 return sum(iter_stack(tos)), stack
601 @SimpleFunctionWrapper
604 Expects an item on the stack and a quote under it and removes that item
605 from the the quote. The item is only removed once.
609 ------------------------
613 (tos, (second, stack)) = S
614 l = list(iter_stack(second))
616 return list_to_stack(l), stack
620 @SimpleFunctionWrapper
622 '''Given a list remove duplicate items.'''
624 I = list(iter_stack(tos))
625 return list_to_stack(sorted(set(I), key=I.index)), stack
629 @SimpleFunctionWrapper
631 '''Given a list return it sorted.'''
633 return list_to_stack(sorted(iter_stack(tos))), stack
636 _functions['clear'] = s0, s1
638 @SimpleFunctionWrapper
640 '''Clear everything from the stack.
643 clear == stack [pop stack] loop
653 @SimpleFunctionWrapper
656 The unstack operator expects a list on top of the stack and makes that
657 the stack discarding the rest of the stack.
663 @SimpleFunctionWrapper
665 '''Reverse the list on the top of the stack.
668 reverse == [] swap shunt
672 for term in iter_stack(tos):
678 @combinator_effect(_COMB_NUMS(), s7, s6)
679 @SimpleFunctionWrapper
681 '''Concatinate the two lists on the top of the stack.
684 [a b c] [d e f] concat
685 ----------------------------
689 (tos, (second, stack)) = S
690 return concat(second, tos), stack
694 @SimpleFunctionWrapper
696 '''Like concat but reverses the top list into the second.
699 shunt == [swons] step == reverse swap concat
701 [a b c] [d e f] shunt
702 ---------------------------
706 (tos, (second, stack)) = stack
709 second = term, second
714 @SimpleFunctionWrapper
717 Replace the two lists on the top of the stack with a list of the pairs
718 from each list. The smallest list sets the length of the result list.
720 (tos, (second, stack)) = S
723 for a, b in zip(iter_stack(tos), iter_stack(second))
725 return list_to_stack(accumulator), stack
730 @SimpleFunctionWrapper
734 return tos + 1, stack
739 @SimpleFunctionWrapper
743 return tos - 1, stack
747 @SimpleFunctionWrapper
758 a, (b, stack) = stack
764 return int(math.floor(n))
766 floor.__doc__ = math.floor.__doc__
770 @SimpleFunctionWrapper
773 divmod(x, y) -> (quotient, remainder)
775 Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
784 Return the square root of the number a.
785 Negative numbers return complex roots.
790 assert a < 0, repr(a)
791 r = math.sqrt(-a) * 1j
797 # if isinstance(text, str):
798 # return run(text, stack)
803 @SimpleFunctionWrapper
805 '''The identity function.'''
810 @SimpleFunctionWrapper
812 '''True if the form on TOS is void otherwise False.'''
814 return _void(form), stack
818 return any(not _void(i) for i in iter_stack(form))
829 def words(stack, expression, dictionary):
830 '''Print all the words in alphabetical order.'''
831 print(' '.join(sorted(dictionary)))
832 return stack, expression, dictionary
837 def sharing(stack, expression, dictionary):
838 '''Print redistribution information.'''
839 print("You may convey verbatim copies of the Program's source code as"
840 ' you receive it, in any medium, provided that you conspicuously'
841 ' and appropriately publish on each copy an appropriate copyright'
842 ' notice; keep intact all notices stating that this License and'
843 ' any non-permissive terms added in accord with section 7 apply'
844 ' to the code; keep intact all notices of the absence of any'
845 ' warranty; and give all recipients a copy of this License along'
847 ' You should have received a copy of the GNU General Public License'
848 ' along with Thun. If not see <http://www.gnu.org/licenses/>.')
849 return stack, expression, dictionary
854 def warranty(stack, expression, dictionary):
855 '''Print warranty information.'''
856 print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
857 ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
858 ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
859 ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
860 ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
861 ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
862 ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
863 ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
864 ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
865 return stack, expression, dictionary
868 # def simple_manual(stack):
870 # Print words and help for each word.
872 # for name, f in sorted(FUNCTIONS.items()):
874 # boxline = '+%s+' % ('-' * (len(name) + 2))
877 # '| %s |' % (name,),
879 # d if d else ' ...',
889 def help_(S, expression, dictionary):
890 '''Accepts a quoted symbol on the top of the stack and prints its docs.'''
891 ((symbol, _), stack) = S
892 word = dictionary[symbol]
894 return stack, expression, dictionary
902 # Several combinators depend on other words in their definitions,
903 # we use symbols to prevent hard-coding these, so in theory, you
904 # could change the word in the dictionary to use different semantics.
905 S_choice = Symbol('choice')
906 S_first = Symbol('first')
907 S_getitem = Symbol('getitem')
908 S_genrec = Symbol('genrec')
909 S_loop = Symbol('loop')
911 S_ifte = Symbol('ifte')
912 S_infra = Symbol('infra')
913 S_pop = Symbol('pop')
914 S_step = Symbol('step')
915 S_times = Symbol('times')
916 S_swaack = Symbol('swaack')
920 @combinator_effect(_COMB_NUMS(), s1)
922 def i(stack, expression, dictionary):
924 The i combinator expects a quoted program on the stack and unpacks it
925 onto the pending expression for evaluation.
934 return stack, concat(quote, expression), dictionary
938 @combinator_effect(_COMB_NUMS(), s1)
940 def x(stack, expression, dictionary):
946 ... [Q] x = ... [Q] dup i
947 ... [Q] x = ... [Q] [Q] i
948 ... [Q] x = ... [Q] Q
952 return stack, concat(quote, expression), dictionary
956 @combinator_effect(_COMB_NUMS(), s7, s6)
958 def b(stack, expression, dictionary):
964 ... [P] [Q] b == ... [P] i [Q] i
965 ... [P] [Q] b == ... P Q
968 q, (p, (stack)) = stack
969 return stack, concat(p, concat(q, expression)), dictionary
973 @combinator_effect(_COMB_NUMS(), a1, s1)
975 def dupdip(stack, expression, dictionary):
979 [F] dupdip == dup [F] dip
989 return stack, concat(F, (a, expression)), dictionary
993 @combinator_effect(_COMB_NUMS(), s7, s6)
995 def infra(stack, expression, dictionary):
997 Accept a quoted program and a list on the stack and run the program
998 with the list as its stack. Does not affect the rest of the stack.
1001 ... [a b c] [Q] . infra
1002 -----------------------------
1003 c b a . Q [...] swaack
1006 (quote, (aggregate, stack)) = stack
1007 return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary
1011 #@combinator_effect(_COMB_NUMS(), s7, s6, s5, s4)
1013 def genrec(stack, expression, dictionary):
1015 General Recursion Combinator.
1018 [if] [then] [rec1] [rec2] genrec
1019 ---------------------------------------------------------------------
1020 [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
1022 From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
1023 "The genrec combinator takes four program parameters in addition to
1024 whatever data parameters it needs. Fourth from the top is an if-part,
1025 followed by a then-part. If the if-part yields true, then the then-part
1026 is executed and the combinator terminates. The other two parameters are
1027 the rec1-part and the rec2-part. If the if-part yields false, the
1028 rec1-part is executed. Following that the four program parameters and
1029 the combinator are again pushed onto the stack bundled up in a quoted
1030 form. Then the rec2-part is executed, where it will find the bundled
1031 form. Typically it will then execute the bundled form, either with i or
1032 with app2, or some other combinator."
1034 The way to design one of these is to fix your base case [then] and the
1035 test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
1036 a quotation of the whole function.
1038 For example, given a (general recursive) function 'F':
1041 F == [I] [T] [R1] [R2] genrec
1043 If the [I] if-part fails you must derive R1 and R2 from:
1048 Just set the stack arguments in front, and figure out what R1 and R2
1049 have to do to apply the quoted [F] in the proper way. In effect, the
1050 genrec combinator turns into an ifte combinator with a quoted copy of
1051 the original definition in the else-part:
1054 F == [I] [T] [R1] [R2] genrec
1055 == [I] [T] [R1 [F] R2] ifte
1057 Primitive recursive functions are those where R2 == i.
1060 P == [I] [T] [R] primrec
1061 == [I] [T] [R [P] i] ifte
1062 == [I] [T] [R P] ifte
1065 (rec2, (rec1, stack)) = stack
1066 (then, (if_, _)) = stack
1067 F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
1068 else_ = concat(rec1, (F, rec2))
1069 return (else_, stack), (S_ifte, expression), dictionary
1073 @combinator_effect(_COMB_NUMS(), s7, s6)
1075 def map_(S, expression, dictionary):
1077 Run the quoted program on TOS on the items in the list under it, push a
1078 new list with the results in place of the program and original list.
1080 # (quote, (aggregate, stack)) = S
1081 # results = list_to_stack([
1082 # joy((term, stack), quote, dictionary)[0][0]
1083 # for term in iter_stack(aggregate)
1085 # return (results, stack), expression, dictionary
1086 (quote, (aggregate, stack)) = S
1088 return (aggregate, stack), expression, dictionary
1090 for term in iter_stack(aggregate):
1092 batch = (s, (quote, (S_infra, (S_first, batch))))
1093 stack = (batch, ((), stack))
1094 return stack, (S_infra, expression), dictionary
1097 #def cleave(S, expression, dictionary):
1099 # The cleave combinator expects two quotations, and below that an item X.
1100 # It first executes [P], with X on top, and saves the top result element.
1101 # Then it executes [Q], again with X, and saves the top result.
1102 # Finally it restores the stack to what it was below X and pushes the two
1103 # results P(X) and Q(X).
1105 # (Q, (P, (x, stack))) = S
1106 # p = joy((x, stack), P, dictionary)[0][0]
1107 # q = joy((x, stack), Q, dictionary)[0][0]
1108 # return (q, (p, stack)), expression, dictionary
1111 def branch_true(stack, expression, dictionary):
1112 # pylint: disable=unused-variable
1113 (then, (else_, (flag, stack))) = stack
1114 return stack, concat(then, expression), dictionary
1117 def branch_false(stack, expression, dictionary):
1118 # pylint: disable=unused-variable
1119 (then, (else_, (flag, stack))) = stack
1120 return stack, concat(else_, expression), dictionary
1124 @poly_combinator_effect(_COMB_NUMS(), [branch_true, branch_false], b1, s7, s6)
1126 def branch(stack, expression, dictionary):
1128 Use a Boolean value to select one of two quoted programs to run.
1132 branch == roll< choice i
1136 False [F] [T] branch
1137 --------------------------
1141 -------------------------
1145 (then, (else_, (flag, stack))) = stack
1146 return stack, concat(then if flag else else_, expression), dictionary
1149 #FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
1154 ##def ifte(stack, expression, dictionary):
1156 ## If-Then-Else Combinator
1159 ## ... [if] [then] [else] ifte
1160 ## ---------------------------------------------------
1161 ## ... [[else] [then]] [...] [if] infra select i
1166 ## ... [if] [then] [else] ifte
1167 ## -------------------------------------------------------
1168 ## ... [else] [then] [...] [if] infra first choice i
1171 ## Has the effect of grabbing a copy of the stack on which to run the
1172 ## if-part using infra.
1174 ## (else_, (then, (if_, stack))) = stack
1175 ## expression = (S_infra, (S_first, (S_choice, (S_i, expression))))
1176 ## stack = (if_, (stack, (then, (else_, stack))))
1177 ## return stack, expression, dictionary
1182 def cond(stack, expression, dictionary):
1184 This combinator works like a case statement. It expects a single quote
1185 on the stack that must contain zero or more condition quotes and a
1186 default quote. Each condition clause should contain a quoted predicate
1187 followed by the function expression to run if that predicate returns
1188 true. If no predicates return true the default function runs.
1190 It works by rewriting into a chain of nested `ifte` expressions, e.g.::
1192 [[[B0] T0] [[B1] T1] [D]] cond
1193 -----------------------------------------
1194 [B0] [T0] [[B1] [T1] [D] ifte] ifte
1197 conditions, stack = stack
1199 expression = _cond(conditions, expression)
1201 # Attempt to preload the args to first ifte.
1202 (P, (T, (E, expression))) = expression
1204 # If, for any reason, the argument to cond should happen to contain
1205 # only the default clause then this optimization will fail.
1208 stack = (E, (T, (P, stack)))
1209 return stack, expression, dictionary
1212 def _cond(conditions, expression):
1213 (clause, rest) = conditions
1214 if not rest: # clause is [D]
1217 return (P, (T, (_cond(rest, ()), (S_ifte, expression))))
1221 @combinator_effect(_COMB_NUMS(), a1, s1)
1223 def dip(stack, expression, dictionary):
1225 The dip combinator expects a quoted program on the stack and below it
1226 some item, it hoists the item into the expression and runs the program
1227 on the rest of the stack.
1235 (quote, (x, stack)) = stack
1236 expression = (x, expression)
1237 return stack, concat(quote, expression), dictionary
1241 @combinator_effect(_COMB_NUMS(), a2, a1, s1)
1243 def dipd(S, expression, dictionary):
1245 Like dip but expects two items.
1249 ---------------------
1253 (quote, (x, (y, stack))) = S
1254 expression = (y, (x, expression))
1255 return stack, concat(quote, expression), dictionary
1259 @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1)
1261 def dipdd(S, expression, dictionary):
1263 Like dip but expects three items.
1267 -----------------------
1271 (quote, (x, (y, (z, stack)))) = S
1272 expression = (z, (y, (x, expression)))
1273 return stack, concat(quote, expression), dictionary
1277 @combinator_effect(_COMB_NUMS(), a1, s1)
1279 def app1(S, expression, dictionary):
1281 Given a quoted program on TOS and anything as the second stack item run
1282 the program and replace the two args with the first result of the
1287 -----------------------------------
1288 ... [x ...] [Q] . infra first
1290 (quote, (x, stack)) = S
1291 stack = (quote, ((x, stack), stack))
1292 expression = (S_infra, (S_first, expression))
1293 return stack, expression, dictionary
1297 @combinator_effect(_COMB_NUMS(), a2, a1, s1)
1299 def app2(S, expression, dictionary):
1300 '''Like app1 with two items.
1304 -----------------------------------
1305 ... [y ...] [Q] . infra first
1306 [x ...] [Q] infra first
1309 (quote, (x, (y, stack))) = S
1310 expression = (S_infra, (S_first,
1311 ((x, stack), (quote, (S_infra, (S_first,
1313 stack = (quote, ((y, stack), stack))
1314 return stack, expression, dictionary
1318 @combinator_effect(_COMB_NUMS(), a3, a2, a1, s1)
1320 def app3(S, expression, dictionary):
1321 '''Like app1 with three items.
1324 ... z y x [Q] . app3
1325 -----------------------------------
1326 ... [z ...] [Q] . infra first
1327 [y ...] [Q] infra first
1328 [x ...] [Q] infra first
1331 (quote, (x, (y, (z, stack)))) = S
1332 expression = (S_infra, (S_first,
1333 ((y, stack), (quote, (S_infra, (S_first,
1334 ((x, stack), (quote, (S_infra, (S_first,
1335 expression))))))))))
1336 stack = (quote, ((z, stack), stack))
1337 return stack, expression, dictionary
1341 @combinator_effect(_COMB_NUMS(), s7, s6)
1343 def step(S, expression, dictionary):
1345 Run a quoted program on each item in a sequence.
1349 -----------------------
1354 ------------------------
1358 ... [a b c] [Q] . step
1359 ----------------------------------------
1360 ... a . Q [b c] [Q] step
1362 The step combinator executes the quotation on each member of the list
1363 on top of the stack.
1365 (quote, (aggregate, stack)) = S
1367 return stack, expression, dictionary
1368 head, tail = aggregate
1369 stack = quote, (head, stack)
1371 expression = tail, (quote, (S_step, expression))
1372 expression = S_i, expression
1373 return stack, expression, dictionary
1377 @combinator_effect(_COMB_NUMS(), i1, s6)
1379 def times(stack, expression, dictionary):
1381 times == [-- dip] cons [swap] infra [0 >] swap while pop
1385 --------------------- w/ n <= 0
1390 ---------------------------------
1395 --------------------------------- w/ n > 1
1396 ... . Q (n - 1) [Q] times
1399 # times == [-- dip] cons [swap] infra [0 >] swap while pop
1400 (quote, (n, stack)) = stack
1402 return stack, expression, dictionary
1405 expression = n, (quote, (S_times, expression))
1406 expression = concat(quote, expression)
1407 return stack, expression, dictionary
1410 # The current definition above works like this:
1413 # --------------------------------------
1414 # [P] nullary [Q [P] nullary] loop
1416 # while == [pop i not] [popop] [dudipd] primrec
1418 #def while_(S, expression, dictionary):
1419 # '''[if] [body] while'''
1420 # (body, (if_, stack)) = S
1421 # while joy(stack, if_, dictionary)[0][0]:
1422 # stack = joy(stack, body, dictionary)[0]
1423 # return stack, expression, dictionary
1426 def loop_true(stack, expression, dictionary):
1427 quote, (flag, stack) = stack # pylint: disable=unused-variable
1428 return stack, concat(quote, (S_pop, expression)), dictionary
1430 def loop_two_true(stack, expression, dictionary):
1431 quote, (flag, stack) = stack # pylint: disable=unused-variable
1432 return stack, concat(quote, (S_pop, concat(quote, (S_pop, expression)))), dictionary
1434 def loop_false(stack, expression, dictionary):
1435 quote, (flag, stack) = stack # pylint: disable=unused-variable
1436 return stack, expression, dictionary
1440 @poly_combinator_effect(_COMB_NUMS(), [loop_two_true, loop_true, loop_false], b1, s6)
1442 def loop(stack, expression, dictionary):
1444 Basic loop combinator.
1448 -----------------------
1452 ------------------------
1456 quote, (flag, stack) = stack
1458 expression = concat(quote, (quote, (S_loop, expression)))
1459 return stack, expression, dictionary
1463 @combinator_effect(_COMB_NUMS(), a1, a2, s6, s7, s8)
1465 def cmp_(stack, expression, dictionary):
1467 cmp takes two values and three quoted programs on the stack and runs
1468 one of the three depending on the results of comparing the two values:
1472 ------------------------- a > b
1476 ------------------------- a = b
1480 ------------------------- a < b
1483 L, (E, (G, (b, (a, stack)))) = stack
1484 expression = concat(G if a > b else L if a < b else E, expression)
1485 return stack, expression, dictionary
1488 # FunctionWrapper(cleave),
1489 # FunctionWrapper(while_),
1494 #divmod_ = pm = __(n2, n1), __(n4, n3)
1496 sec_binary_cmp(BinaryBuiltinWrapper(operator.eq)),
1497 sec_binary_cmp(BinaryBuiltinWrapper(operator.ge)),
1498 sec_binary_cmp(BinaryBuiltinWrapper(operator.gt)),
1499 sec_binary_cmp(BinaryBuiltinWrapper(operator.le)),
1500 sec_binary_cmp(BinaryBuiltinWrapper(operator.lt)),
1501 sec_binary_cmp(BinaryBuiltinWrapper(operator.ne)),
1503 sec_binary_ints(BinaryBuiltinWrapper(operator.xor)),
1504 sec_binary_ints(BinaryBuiltinWrapper(operator.lshift)),
1505 sec_binary_ints(BinaryBuiltinWrapper(operator.rshift)),
1507 sec_binary_logic(BinaryBuiltinWrapper(operator.and_)),
1508 sec_binary_logic(BinaryBuiltinWrapper(operator.or_)),
1510 sec_binary_math(BinaryBuiltinWrapper(operator.add)),
1511 sec_binary_math(BinaryBuiltinWrapper(operator.floordiv)),
1512 sec_binary_math(BinaryBuiltinWrapper(operator.mod)),
1513 sec_binary_math(BinaryBuiltinWrapper(operator.mul)),
1514 sec_binary_math(BinaryBuiltinWrapper(operator.pow)),
1515 sec_binary_math(BinaryBuiltinWrapper(operator.sub)),
1516 sec_binary_math(BinaryBuiltinWrapper(operator.truediv)),
1518 sec_unary_logic(UnaryBuiltinWrapper(bool)),
1519 sec_unary_logic(UnaryBuiltinWrapper(operator.not_)),
1521 sec_unary_math(UnaryBuiltinWrapper(abs)),
1522 sec_unary_math(UnaryBuiltinWrapper(operator.neg)),
1523 sec_unary_math(UnaryBuiltinWrapper(sqrt)),
1525 stack_effect(n1)(i1)(UnaryBuiltinWrapper(floor)),
1528 del F # Otherwise Sphinx autodoc will pick it up.
1531 YIN_STACK_EFFECTS = yin_functions()
1532 add_aliases(YIN_STACK_EFFECTS, ALIASES)
1534 # Load the auto-generated primitives into the dictionary.
1535 _functions.update(YIN_STACK_EFFECTS)
1538 # eh = compose(dup, bool)
1539 # sqr = compose(dup, mul)
1540 # of = compose(swap, at)
1542 # ''' in dict(compose=compose), _functions
1543 for name in sorted(_functions):
1544 sec = _functions[name]
1545 F = FUNCTIONS[name] = SymbolJoyType(name, [sec], _SYM_NUMS())
1546 if name in YIN_STACK_EFFECTS:
1547 _log.info('Setting stack effect for Yin function %s := %s', F.name, doc_from_stack_effect(*sec))
1549 for name, primitive in getmembers(genlib, isfunction):
1550 inscribe(SimpleFunctionWrapper(primitive))
1553 add_aliases(_dictionary, ALIASES)
1554 add_aliases(_functions, ALIASES)
1555 add_aliases(FUNCTIONS, ALIASES)
1558 DefinitionWrapper.add_definitions(definitions, _dictionary)
1561 EXPECTATIONS = dict(
1562 ifte=(s7, (s6, (s5, s4))),
1566 EXPECTATIONS['while'] = (s7, (s6, s5))
1577 C = _dictionary[name]
1578 expect = EXPECTATIONS.get(name)
1580 sec = doc_from_stack_effect(expect)
1581 _log.info('Setting stack EXPECT for combinator %s := %s', C.name, sec)
1583 _log.info('combinator %s', C.name)
1584 FUNCTIONS[name] = CombinatorJoyType(name, [C], _COMB_NUMS(), expect)
1588 of quoted enstacken ?
1589 unary binary ternary
1592 of_ = _dictionary[name]
1593 secs = infer_expression(of_.body)
1594 assert len(secs) == 1, repr(secs)
1596 'Setting stack effect for definition %s := %s',
1598 doc_from_stack_effect(*secs[0]),
1600 FUNCTIONS[name] = SymbolJoyType(name, infer_expression(of_.body), _SYM_NUMS())
1603 #sec_Ns_math(_dictionary['product'])
1605 ## product == 1 swap [*] step
1606 ## flatten == [] swap [concat] step
1607 ## disenstacken == ? [uncons ?] loop pop
1609 ## size == 0 swap [pop ++] step
1611 ## cleave == fork [popd] dip
1612 ## average == [sum 1.0 *] [size] cleave /
1613 ## gcd == 1 [tuck modulus dup 0 >] loop pop
1614 ## least_fraction == dup [gcd] infra [div] concat map
1615 ## *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
1616 ## *fraction0 == concat [[swap] dip * [*] dip] infra
1617 ## down_to_zero == [0 >] [dup --] while
1618 ## range_to_zero == unit [down_to_zero] infra
1619 ## anamorphism == [pop []] swap [dip swons] genrec
1620 ## range == [0 <=] [1 - dup] anamorphism
1621 ## while == swap [nullary] cons dup dipd concat loop
1622 ## dupdipd == dup dipd
1623 ## primrec == [i] genrec
1624 ## step_zero == 0 roll> step
1625 ## codireco == cons dip rest cons
1626 ## make_generator == [codireco] ccons
1627 ## ifte == [nullary not] dipd branch