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 from inspect import getdoc
31 from functools import wraps
32 from itertools import count
33 from inspect import getmembers, isfunction
36 from .parser import text_to_expression, Symbol
37 from .utils.stack import expression_to_string, list_to_stack, iter_stack, pick, concat
38 from .utils import generated_library as genlib
51 # This is the main dict we're building.
55 def inscribe(function):
56 '''A decorator to inscribe functions into the default dictionary.'''
57 _dictionary[function.name] = function
62 '''Return a dictionary of Joy functions for use with joy().'''
63 return _dictionary.copy()
71 ('floordiv', ['/floor', '//']),
73 ('truediv', ['/', 'div']),
74 ('mod', ['%', 'rem', 'remainder', 'modulus']),
77 ('getitem', ['pick', 'at']),
88 ('rolldown', ['roll<']),
89 ('rollup', ['roll>']),
95 def add_aliases(D, A):
97 Given a dict and a iterable of (name, [alias, ...]) pairs, create
98 additional entries in the dict mapping each alias to the named function
99 if it's in the dict. Aliases for functions not in the dict are ignored.
101 for name, aliases in A:
106 for alias in aliases:
112 *fraction [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
113 *fraction0 concat [[swap] dip * [*] dip] infra
114 anamorphism [pop []] swap [dip swons] genrec
115 average [sum 1.0 *] [size] cleave /
116 binary nullary [popop] dip
117 cleave fork [popd] dip
118 codireco cons dip rest cons
119 dinfrirst dip infra first
120 unstack ? [uncons ?] loop pop
121 down_to_zero [0 >] [dup --] while
123 enstacken stack [clear] dip
124 flatten [] swap [concat] step
126 gcd 1 [tuck modulus dup 0 >] loop pop
127 ifte [nullary not] dipd branch
129 least_fraction dup [gcd] infra [div] concat map
130 make_generator [codireco] ccons
131 nullary [stack] dinfrirst
135 product 1 swap [*] step
137 range [0 <=] [1 - dup] anamorphism
138 range_to_zero unit [down_to_zero] infra
140 size 0 swap [pop ++] step
142 step_zero 0 roll> step
144 ternary unary [popop] dip
147 while swap [nullary] cons dup dipd concat loop
151 # ifte == [nullary] dipd swap branch
152 # genrec == [[genrec] cons cons cons cons] nullary swons concat ifte
154 # Another definition for while. FWIW
155 # while == over [[i] dip nullary] ccons [nullary] dip loop
159 ##second == rest first
160 ##third == rest rest first
164 ##z-down == [] swap uncons swap
165 ##z-up == swons swap shunt
166 ##z-right == [swons] cons dip uncons swap
167 ##z-left == swons [uncons swap] dip swap
170 ##divisor == popop 2 *
172 ##radical == swap dup * rollup * 4 * - sqrt
175 ##q0 == [[divisor] [minusb] [radical]] pam
176 ##q1 == [[root1] [root2]] pam
177 ##quadratic == [q0] ternary i [q1] ternary
181 ##PE1.1 == + dup [+] dip
182 ##PE1.2 == dup [3 & PE1.1] dip 2 >>
183 ##PE1.3 == 14811 swap [PE1.2] times pop
184 ##PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop
186 #PE1.2 == [PE1.1] step
187 #PE1 == 0 0 66 [[3 2 1 3 1 2 3] PE1.2] times [3 2 1 3] PE1.2 pop
191 def FunctionWrapper(f):
192 '''Set name attribute.'''
194 raise ValueError('Function %s must have doc string.' % f.__name__)
195 f.name = f.__name__.rstrip('_') # Don't shadow builtins.
199 def SimpleFunctionWrapper(f):
201 Wrap functions that take and return just a stack.
205 def inner(stack, expression, dictionary):
206 return f(stack), expression, dictionary
210 def BinaryBuiltinWrapper(f):
212 Wrap functions that take two arguments and return a single result.
216 def inner(stack, expression, dictionary):
217 (a, (b, stack)) = stack
219 return (result, stack), expression, dictionary
223 def UnaryBuiltinWrapper(f):
225 Wrap functions that take one argument and return a single result.
229 def inner(stack, expression, dictionary):
232 return (result, stack), expression, dictionary
236 class DefinitionWrapper(object):
238 Provide implementation of defined functions, and some helper methods.
241 def __init__(self, name, body_text, doc=None):
242 self.name = self.__name__ = name
243 self.body = text_to_expression(body_text)
244 self._body = tuple(iter_stack(self.body))
245 self.__doc__ = doc or body_text
246 self._compiled = None
248 def __call__(self, stack, expression, dictionary):
250 return self._compiled(stack, expression, dictionary) # pylint: disable=E1102
251 expression = list_to_stack(self._body, expression)
252 return stack, expression, dictionary
255 def parse_definition(class_, defi):
257 Given some text describing a Joy function definition parse it and
258 return a DefinitionWrapper.
260 return class_(*(n.strip() for n in defi.split(None, 1)))
263 def add_definitions(class_, defs, dictionary):
265 Scan multi-line string defs for definitions and add them to the
268 for definition in _text_to_defs(defs):
269 class_.add_def(definition, dictionary)
272 def add_def(class_, definition, dictionary, fail_fails=False):
274 Add the definition to the dictionary.
276 F = class_.parse_definition(definition)
277 dictionary[F.name] = F
280 def load_definitions(class_, filename, dictionary):
281 with open(filename) as f:
282 lines = [line for line in f if '==' in line]
284 class_.add_def(line, dictionary)
287 def _text_to_defs(text):
290 for line in text.splitlines()
291 if not line.startswith('#')
302 def inscribe_(stack, expression, dictionary):
304 Create a new Joy function definition in the Joy dictionary. A
305 definition is given as a string with a name followed by a double
306 equal sign then one or more Joy functions, the body. for example:
310 If you want the definition to persist over restarts, enter it into
311 the definitions.txt resource.
313 definition, stack = stack
314 DefinitionWrapper.add_def(definition, dictionary, fail_fails=True)
315 return stack, expression, dictionary
319 @SimpleFunctionWrapper
321 '''Parse the string on the stack to a Joy expression.'''
323 expression = text_to_expression(text)
324 return expression, stack
328 # @SimpleFunctionWrapper
330 # '''Attempt to infer the stack effect of a Joy expression.'''
332 # effects = infer_expression(E)
333 # e = list_to_stack([(fi, (fo, ())) for fi, fo in effects])
338 @SimpleFunctionWrapper
343 getitem == drop first
345 Expects an integer and a quote on the stack and returns the item at the
346 nth position in the quote counting from 0.
350 -------------------------
354 n, (Q, stack) = stack
355 return pick(Q, n), stack
359 @SimpleFunctionWrapper
366 Expects an integer and a quote on the stack and returns the quote with
367 n items removed off the top.
371 ----------------------
375 n, (Q, stack) = stack
386 @SimpleFunctionWrapper
389 Expects an integer and a quote on the stack and returns the quote with
390 just the top n items in reverse order (because that's easier and you can
391 use reverse if needed.)
395 ----------------------
399 n, (Q, stack) = stack
412 @SimpleFunctionWrapper
415 Use a Boolean value to select one of two items.
419 ----------------------
424 ---------------------
427 Currently Python semantics are used to evaluate the "truthiness" of the
428 Boolean value (so empty string, zero, etc. are counted as false, etc.)
430 (if_, (then, (else_, stack))) = stack
431 return then if if_ else else_, stack
435 @SimpleFunctionWrapper
438 Use a Boolean value to select one of two items from a sequence.
442 ------------------------
447 -----------------------
450 The sequence can contain more than two items but not fewer.
451 Currently Python semantics are used to evaluate the "truthiness" of the
452 Boolean value (so empty string, zero, etc. are counted as false, etc.)
454 (flag, (choices, stack)) = stack
455 (else_, (then, _)) = choices
456 return then if flag else else_, stack
460 @SimpleFunctionWrapper
462 '''Given a list find the maximum.'''
464 return max(iter_stack(tos)), stack
468 @SimpleFunctionWrapper
470 '''Given a list find the minimum.'''
472 return min(iter_stack(tos)), stack
476 @SimpleFunctionWrapper
478 '''Given a quoted sequence of numbers return the sum.
480 sum == 0 swap [+] step
483 return sum(iter_stack(tos)), stack
487 @SimpleFunctionWrapper
490 Expects an item on the stack and a quote under it and removes that item
491 from the the quote. The item is only removed once.
495 ------------------------
499 (tos, (second, stack)) = S
500 l = list(iter_stack(second))
502 return list_to_stack(l), stack
506 @SimpleFunctionWrapper
508 '''Given a list remove duplicate items.'''
510 I = list(iter_stack(tos))
511 return list_to_stack(sorted(set(I), key=I.index)), stack
515 @SimpleFunctionWrapper
517 '''Given a list return it sorted.'''
519 return list_to_stack(sorted(iter_stack(tos))), stack
523 @SimpleFunctionWrapper
525 '''Clear everything from the stack.
528 clear == stack [pop stack] loop
538 @SimpleFunctionWrapper
539 def disenstacken(stack):
541 The disenstacken operator expects a list on top of the stack and makes that
542 the stack discarding the rest of the stack.
548 @SimpleFunctionWrapper
550 '''Reverse the list on the top of the stack.
553 reverse == [] swap shunt
557 for term in iter_stack(tos):
563 @SimpleFunctionWrapper
565 '''Concatinate the two lists on the top of the stack.
568 [a b c] [d e f] concat
569 ----------------------------
573 (tos, (second, stack)) = S
574 return concat(second, tos), stack
578 @SimpleFunctionWrapper
580 '''Like concat but reverses the top list into the second.
583 shunt == [swons] step == reverse swap concat
585 [a b c] [d e f] shunt
586 ---------------------------
590 (tos, (second, stack)) = stack
593 second = term, second
598 @SimpleFunctionWrapper
601 Replace the two lists on the top of the stack with a list of the pairs
602 from each list. The smallest list sets the length of the result list.
604 (tos, (second, stack)) = S
607 for a, b in zip(iter_stack(tos), iter_stack(second))
609 return list_to_stack(accumulator), stack
613 @SimpleFunctionWrapper
617 return tos + 1, stack
621 @SimpleFunctionWrapper
625 return tos - 1, stack
629 @SimpleFunctionWrapper
640 a, (b, stack) = stack
646 return int(math.floor(n))
648 floor.__doc__ = math.floor.__doc__
652 @SimpleFunctionWrapper
655 divmod(x, y) -> (quotient, remainder)
657 Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
666 Return the square root of the number a.
667 Negative numbers return complex roots.
672 assert a < 0, repr(a)
673 r = math.sqrt(-a) * 1j
679 # if isinstance(text, str):
680 # return run(text, stack)
685 @SimpleFunctionWrapper
687 '''The identity function.'''
692 @SimpleFunctionWrapper
694 '''True if the form on TOS is void otherwise False.'''
696 return _void(form), stack
700 return any(not _void(i) for i in iter_stack(form))
711 def words(stack, expression, dictionary):
712 '''Print all the words in alphabetical order.'''
713 print(' '.join(sorted(dictionary)))
714 return stack, expression, dictionary
719 def sharing(stack, expression, dictionary):
720 '''Print redistribution information.'''
721 print("You may convey verbatim copies of the Program's source code as"
722 ' you receive it, in any medium, provided that you conspicuously'
723 ' and appropriately publish on each copy an appropriate copyright'
724 ' notice; keep intact all notices stating that this License and'
725 ' any non-permissive terms added in accord with section 7 apply'
726 ' to the code; keep intact all notices of the absence of any'
727 ' warranty; and give all recipients a copy of this License along'
729 ' You should have received a copy of the GNU General Public License'
730 ' along with Thun. If not see <http://www.gnu.org/licenses/>.')
731 return stack, expression, dictionary
736 def warranty(stack, expression, dictionary):
737 '''Print warranty information.'''
738 print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
739 ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
740 ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
741 ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
742 ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
743 ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
744 ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
745 ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
746 ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
747 return stack, expression, dictionary
750 # def simple_manual(stack):
752 # Print words and help for each word.
754 # for name, f in sorted(FUNCTIONS.items()):
756 # boxline = '+%s+' % ('-' * (len(name) + 2))
759 # '| %s |' % (name,),
761 # d if d else ' ...',
771 def help_(S, expression, dictionary):
772 '''Accepts a quoted symbol on the top of the stack and prints its docs.'''
773 ((symbol, _), stack) = S
774 word = dictionary[symbol]
775 print(HELP_TEMPLATE % (symbol, getdoc(word), symbol))
776 return stack, expression, dictionary
784 # Several combinators depend on other words in their definitions,
785 # we use symbols to prevent hard-coding these, so in theory, you
786 # could change the word in the dictionary to use different semantics.
787 S_choice = Symbol('choice')
788 S_first = Symbol('first')
789 S_genrec = Symbol('genrec')
790 S_getitem = Symbol('getitem')
792 S_ifte = Symbol('ifte')
793 S_infra = Symbol('infra')
794 S_loop = Symbol('loop')
795 S_pop = Symbol('pop')
796 S_primrec = Symbol('primrec')
797 S_step = Symbol('step')
798 S_swaack = Symbol('swaack')
799 S_times = Symbol('times')
804 def i(stack, expression, dictionary):
806 The i combinator expects a quoted program on the stack and unpacks it
807 onto the pending expression for evaluation.
816 return stack, concat(quote, expression), dictionary
821 def x(stack, expression, dictionary):
827 ... [Q] x = ... [Q] dup i
828 ... [Q] x = ... [Q] [Q] i
829 ... [Q] x = ... [Q] Q
833 return stack, concat(quote, expression), dictionary
838 def b(stack, expression, dictionary):
844 ... [P] [Q] b == ... [P] i [Q] i
845 ... [P] [Q] b == ... P Q
848 q, (p, (stack)) = stack
849 return stack, concat(p, concat(q, expression)), dictionary
854 def dupdip(stack, expression, dictionary):
858 [F] dupdip == dup [F] dip
868 return stack, concat(F, (a, expression)), dictionary
873 def infra(stack, expression, dictionary):
875 Accept a quoted program and a list on the stack and run the program
876 with the list as its stack. Does not affect the rest of the stack.
879 ... [a b c] [Q] . infra
880 -----------------------------
881 c b a . Q [...] swaack
884 (quote, (aggregate, stack)) = stack
885 return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary
889 #@combinator_effect(_COMB_NUMS(), s7, s6, s5, s4)
891 def genrec(stack, expression, dictionary):
893 General Recursion Combinator.
896 [if] [then] [rec1] [rec2] genrec
897 ---------------------------------------------------------------------
898 [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
900 From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
901 "The genrec combinator takes four program parameters in addition to
902 whatever data parameters it needs. Fourth from the top is an if-part,
903 followed by a then-part. If the if-part yields true, then the then-part
904 is executed and the combinator terminates. The other two parameters are
905 the rec1-part and the rec2-part. If the if-part yields false, the
906 rec1-part is executed. Following that the four program parameters and
907 the combinator are again pushed onto the stack bundled up in a quoted
908 form. Then the rec2-part is executed, where it will find the bundled
909 form. Typically it will then execute the bundled form, either with i or
910 with app2, or some other combinator."
912 The way to design one of these is to fix your base case [then] and the
913 test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
914 a quotation of the whole function.
916 For example, given a (general recursive) function 'F':
919 F == [I] [T] [R1] [R2] genrec
921 If the [I] if-part fails you must derive R1 and R2 from:
926 Just set the stack arguments in front, and figure out what R1 and R2
927 have to do to apply the quoted [F] in the proper way. In effect, the
928 genrec combinator turns into an ifte combinator with a quoted copy of
929 the original definition in the else-part:
932 F == [I] [T] [R1] [R2] genrec
933 == [I] [T] [R1 [F] R2] ifte
935 Primitive recursive functions are those where R2 == i.
938 P == [I] [T] [R] tailrec
939 == [I] [T] [R [P] i] ifte
940 == [I] [T] [R P] ifte
943 (rec2, (rec1, stack)) = stack
944 (then, (if_, _)) = stack
945 F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
946 else_ = concat(rec1, (F, rec2))
947 return (else_, stack), (S_ifte, expression), dictionary
952 def map_(S, expression, dictionary):
954 Run the quoted program on TOS on the items in the list under it, push a
955 new list with the results in place of the program and original list.
957 # (quote, (aggregate, stack)) = S
958 # results = list_to_stack([
959 # joy((term, stack), quote, dictionary)[0][0]
960 # for term in iter_stack(aggregate)
962 # return (results, stack), expression, dictionary
963 (quote, (aggregate, stack)) = S
965 return (aggregate, stack), expression, dictionary
967 for term in iter_stack(aggregate):
969 batch = (s, (quote, (S_infra, (S_first, batch))))
970 stack = (batch, ((), stack))
971 return stack, (S_infra, expression), dictionary
976 def primrec(stack, expression, dictionary):
978 From the "Overview of the language JOY":
980 > The primrec combinator expects two quoted programs in addition to a
981 data parameter. For an integer data parameter it works like this: If
982 the data parameter is zero, then the first quotation has to produce
983 the value to be returned. If the data parameter is positive then the
984 second has to combine the data parameter with the result of applying
985 the function to its predecessor.
989 > Then primrec tests whether the top element on the stack (initially
990 the 5) is equal to zero. If it is, it pops it off and executes one of
991 the quotations, the [1] which leaves 1 on the stack as the result.
992 Otherwise it pushes a decremented copy of the top element and
993 recurses. On the way back from the recursion it uses the other
994 quotation, [*], to multiply what is now a factorial on top of the
995 stack by the second element on the stack.
997 n [Base] [Recur] primrec
999 0 [Base] [Recur] primrec
1000 ------------------------------
1003 n [Base] [Recur] primrec
1004 ------------------------------------------ n > 0
1005 n (n-1) [Base] [Recur] primrec Recur
1008 recur, (base, (n, stack)) = stack
1010 expression = concat(base, expression)
1012 expression = S_primrec, concat(recur, expression)
1013 stack = recur, (base, (n - 1, (n, stack)))
1014 return stack, expression, dictionary
1017 #def cleave(S, expression, dictionary):
1019 # The cleave combinator expects two quotations, and below that an item X.
1020 # It first executes [P], with X on top, and saves the top result element.
1021 # Then it executes [Q], again with X, and saves the top result.
1022 # Finally it restores the stack to what it was below X and pushes the two
1023 # results P(X) and Q(X).
1025 # (Q, (P, (x, stack))) = S
1026 # p = joy((x, stack), P, dictionary)[0][0]
1027 # q = joy((x, stack), Q, dictionary)[0][0]
1028 # return (q, (p, stack)), expression, dictionary
1031 def branch_true(stack, expression, dictionary):
1032 # pylint: disable=unused-variable
1033 (then, (else_, (flag, stack))) = stack
1034 return stack, concat(then, expression), dictionary
1037 def branch_false(stack, expression, dictionary):
1038 # pylint: disable=unused-variable
1039 (then, (else_, (flag, stack))) = stack
1040 return stack, concat(else_, expression), dictionary
1045 def branch(stack, expression, dictionary):
1047 Use a Boolean value to select one of two quoted programs to run.
1051 branch == roll< choice i
1055 False [F] [T] branch
1056 --------------------------
1060 -------------------------
1064 (then, (else_, (flag, stack))) = stack
1065 return stack, concat(then if flag else else_, expression), dictionary
1068 #FUNCTIONS['branch'] = CombinatorJoyType('branch', [branch_true, branch_false], 100)
1073 ##def ifte(stack, expression, dictionary):
1075 ## If-Then-Else Combinator
1078 ## ... [if] [then] [else] ifte
1079 ## ---------------------------------------------------
1080 ## ... [[else] [then]] [...] [if] infra select i
1085 ## ... [if] [then] [else] ifte
1086 ## -------------------------------------------------------
1087 ## ... [else] [then] [...] [if] infra first choice i
1090 ## Has the effect of grabbing a copy of the stack on which to run the
1091 ## if-part using infra.
1093 ## (else_, (then, (if_, stack))) = stack
1094 ## expression = (S_infra, (S_first, (S_choice, (S_i, expression))))
1095 ## stack = (if_, (stack, (then, (else_, stack))))
1096 ## return stack, expression, dictionary
1101 def cond(stack, expression, dictionary):
1103 This combinator works like a case statement. It expects a single quote
1104 on the stack that must contain zero or more condition quotes and a
1105 default quote. Each condition clause should contain a quoted predicate
1106 followed by the function expression to run if that predicate returns
1107 true. If no predicates return true the default function runs.
1109 It works by rewriting into a chain of nested `ifte` expressions, e.g.::
1111 [[[B0] T0] [[B1] T1] [D]] cond
1112 -----------------------------------------
1113 [B0] [T0] [[B1] [T1] [D] ifte] ifte
1116 conditions, stack = stack
1118 expression = _cond(conditions, expression)
1120 # Attempt to preload the args to first ifte.
1121 (P, (T, (E, expression))) = expression
1123 # If, for any reason, the argument to cond should happen to contain
1124 # only the default clause then this optimization will fail.
1127 stack = (E, (T, (P, stack)))
1128 return stack, expression, dictionary
1131 def _cond(conditions, expression):
1132 (clause, rest) = conditions
1133 if not rest: # clause is [D]
1136 return (P, (T, (_cond(rest, ()), (S_ifte, expression))))
1141 def dip(stack, expression, dictionary):
1143 The dip combinator expects a quoted program on the stack and below it
1144 some item, it hoists the item into the expression and runs the program
1145 on the rest of the stack.
1153 (quote, (x, stack)) = stack
1154 expression = (x, expression)
1155 return stack, concat(quote, expression), dictionary
1160 def dipd(S, expression, dictionary):
1162 Like dip but expects two items.
1166 ---------------------
1170 (quote, (x, (y, stack))) = S
1171 expression = (y, (x, expression))
1172 return stack, concat(quote, expression), dictionary
1177 def dipdd(S, expression, dictionary):
1179 Like dip but expects three items.
1183 -----------------------
1187 (quote, (x, (y, (z, stack)))) = S
1188 expression = (z, (y, (x, expression)))
1189 return stack, concat(quote, expression), dictionary
1194 def app1(S, expression, dictionary):
1196 Given a quoted program on TOS and anything as the second stack item run
1197 the program and replace the two args with the first result of the
1202 -----------------------------------
1203 ... [x ...] [Q] . infra first
1205 (quote, (x, stack)) = S
1206 stack = (quote, ((x, stack), stack))
1207 expression = (S_infra, (S_first, expression))
1208 return stack, expression, dictionary
1213 def app2(S, expression, dictionary):
1214 '''Like app1 with two items.
1218 -----------------------------------
1219 ... [y ...] [Q] . infra first
1220 [x ...] [Q] infra first
1223 (quote, (x, (y, stack))) = S
1224 expression = (S_infra, (S_first,
1225 ((x, stack), (quote, (S_infra, (S_first,
1227 stack = (quote, ((y, stack), stack))
1228 return stack, expression, dictionary
1233 def app3(S, expression, dictionary):
1234 '''Like app1 with three items.
1237 ... z y x [Q] . app3
1238 -----------------------------------
1239 ... [z ...] [Q] . infra first
1240 [y ...] [Q] infra first
1241 [x ...] [Q] infra first
1244 (quote, (x, (y, (z, stack)))) = S
1245 expression = (S_infra, (S_first,
1246 ((y, stack), (quote, (S_infra, (S_first,
1247 ((x, stack), (quote, (S_infra, (S_first,
1248 expression))))))))))
1249 stack = (quote, ((z, stack), stack))
1250 return stack, expression, dictionary
1255 def step(S, expression, dictionary):
1257 Run a quoted program on each item in a sequence.
1261 -----------------------
1266 ------------------------
1270 ... [a b c] [Q] . step
1271 ----------------------------------------
1272 ... a . Q [b c] [Q] step
1274 The step combinator executes the quotation on each member of the list
1275 on top of the stack.
1277 (quote, (aggregate, stack)) = S
1279 return stack, expression, dictionary
1280 head, tail = aggregate
1281 stack = quote, (head, stack)
1283 expression = tail, (quote, (S_step, expression))
1284 expression = S_i, expression
1285 return stack, expression, dictionary
1290 def times(stack, expression, dictionary):
1292 times == [-- dip] cons [swap] infra [0 >] swap while pop
1296 --------------------- w/ n <= 0
1301 ---------------------------------
1306 --------------------------------- w/ n > 1
1307 ... . Q (n - 1) [Q] times
1310 # times == [-- dip] cons [swap] infra [0 >] swap while pop
1311 (quote, (n, stack)) = stack
1313 return stack, expression, dictionary
1316 expression = n, (quote, (S_times, expression))
1317 expression = concat(quote, expression)
1318 return stack, expression, dictionary
1321 # The current definition above works like this:
1324 # --------------------------------------
1325 # [P] nullary [Q [P] nullary] loop
1327 # while == [pop i not] [popop] [dudipd] tailrec
1329 #def while_(S, expression, dictionary):
1330 # '''[if] [body] while'''
1331 # (body, (if_, stack)) = S
1332 # while joy(stack, if_, dictionary)[0][0]:
1333 # stack = joy(stack, body, dictionary)[0]
1334 # return stack, expression, dictionary
1339 def loop(stack, expression, dictionary):
1341 Basic loop combinator.
1345 -----------------------
1349 ------------------------
1353 quote, (flag, stack) = stack
1355 expression = concat(quote, (quote, (S_loop, expression)))
1356 return stack, expression, dictionary
1361 def cmp_(stack, expression, dictionary):
1363 cmp takes two values and three quoted programs on the stack and runs
1364 one of the three depending on the results of comparing the two values:
1368 ------------------------- a > b
1372 ------------------------- a = b
1376 ------------------------- a < b
1379 L, (E, (G, (b, (a, stack)))) = stack
1380 expression = concat(G if a > b else L if a < b else E, expression)
1381 return stack, expression, dictionary
1384 # FunctionWrapper(cleave),
1385 # FunctionWrapper(while_),
1390 #divmod_ = pm = __(n2, n1), __(n4, n3)
1392 BinaryBuiltinWrapper(operator.eq),
1393 BinaryBuiltinWrapper(operator.ge),
1394 BinaryBuiltinWrapper(operator.gt),
1395 BinaryBuiltinWrapper(operator.le),
1396 BinaryBuiltinWrapper(operator.lt),
1397 BinaryBuiltinWrapper(operator.ne),
1399 BinaryBuiltinWrapper(operator.xor),
1400 BinaryBuiltinWrapper(operator.lshift),
1401 BinaryBuiltinWrapper(operator.rshift),
1403 BinaryBuiltinWrapper(operator.and_),
1404 BinaryBuiltinWrapper(operator.or_),
1406 BinaryBuiltinWrapper(operator.add),
1407 BinaryBuiltinWrapper(operator.floordiv),
1408 BinaryBuiltinWrapper(operator.mod),
1409 BinaryBuiltinWrapper(operator.mul),
1410 BinaryBuiltinWrapper(operator.pow),
1411 BinaryBuiltinWrapper(operator.sub),
1412 BinaryBuiltinWrapper(operator.truediv),
1414 UnaryBuiltinWrapper(bool),
1415 UnaryBuiltinWrapper(operator.not_),
1417 UnaryBuiltinWrapper(abs),
1418 UnaryBuiltinWrapper(operator.neg),
1419 UnaryBuiltinWrapper(sqrt),
1421 UnaryBuiltinWrapper(floor),
1424 del F # Otherwise Sphinx autodoc will pick it up.
1427 for name, primitive in getmembers(genlib, isfunction):
1428 inscribe(SimpleFunctionWrapper(primitive))
1431 add_aliases(_dictionary, ALIASES)
1434 DefinitionWrapper.add_definitions(definitions, _dictionary)
1437 ## product == 1 swap [*] step
1438 ## flatten == [] swap [concat] step
1440 ## size == 0 swap [pop ++] step
1442 ## cleave == fork [popd] dip
1443 ## average == [sum 1.0 *] [size] cleave /
1444 ## gcd == 1 [tuck modulus dup 0 >] loop pop
1445 ## least_fraction == dup [gcd] infra [div] concat map
1446 ## *fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
1447 ## *fraction0 == concat [[swap] dip * [*] dip] infra
1448 ## down_to_zero == [0 >] [dup --] while
1449 ## range_to_zero == unit [down_to_zero] infra
1450 ## anamorphism == [pop []] swap [dip swons] genrec
1451 ## range == [0 <=] [1 - dup] anamorphism
1452 ## while == swap [nullary] cons dup dipd concat loop
1453 ## dupdipd == dup dipd
1454 ## tailrec == [i] genrec
1455 ## step_zero == 0 roll> step
1456 ## codireco == cons dip rest cons
1457 ## make_generator == [codireco] ccons
1458 ## ifte == [nullary not] dipd branch