1 # -*- coding: utf-8 -*-
3 # Copyright © 2014-2020 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 inspect import getdoc, getmembers, isfunction
27 from functools import wraps
28 from itertools import count
31 from .parser import text_to_expression, Symbol
32 from .utils import generated_library as genlib
33 from .utils.errors import (
38 from .utils.stack import (
57 # This is the main dict we're building.
61 def inscribe(function, d=_dictionary):
62 '''A decorator to inscribe functions into the default dictionary.'''
63 d[function.name] = function
68 '''Return a dictionary of Joy functions for use with joy().'''
69 return _dictionary.copy()
77 ('floordiv', ['/floor', '//', '/', 'div']),
78 ('mod', ['%', 'rem', 'remainder', 'modulus']),
81 ('getitem', ['pick', 'at']),
92 ('rolldown', ['roll<']),
93 ('rollup', ['roll>']),
99 def add_aliases(D, A):
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.
105 for name, aliases in A:
110 for alias in aliases:
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
127 enstacken == stack [clear] dip
128 flatten == [] swap [concat] step
130 gcd == 1 [tuck modulus dup 0 >] loop pop
131 ifte == [nullary not] dipd branch
133 least_fraction == dup [gcd] infra [div] concat map
134 make_generator == [codireco] ccons
135 nullary == [stack] dinfrirst
138 tailrec == [i] genrec
139 product == 1 swap [*] step
141 range == [0 <=] [1 - dup] anamorphism
142 range_to_zero == unit [down_to_zero] infra
144 size == 0 swap [pop ++] step
146 step_zero == 0 roll> step
147 swoncat == swap concat
148 tailrec == [i] genrec
149 ternary == unary [popop] dip
150 unary == nullary popd
152 while == swap [nullary] cons dup dipd concat loop
156 # ifte == [nullary] dipd swap branch
157 # genrec == [[genrec] cons cons cons cons] nullary swons concat ifte
159 # Another definition for while. FWIW
160 # while == over [[i] dip nullary] ccons [nullary] dip loop
164 ##second == rest first
165 ##third == rest rest first
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
175 ##divisor == popop 2 *
177 ##radical == swap dup * rollup * 4 * - sqrt
180 ##q0 == [[divisor] [minusb] [radical]] pam
181 ##q1 == [[root1] [root2]] pam
182 ##quadratic == [q0] ternary i [q1] ternary
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
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
196 def FunctionWrapper(f):
197 '''Set name attribute.'''
199 raise ValueError('Function %s must have doc string.' % f.__name__)
200 f.name = f.__name__.rstrip('_') # Don't shadow builtins.
204 def SimpleFunctionWrapper(f):
206 Wrap functions that take and return just a stack.
210 def inner(stack, expression, dictionary):
211 return f(stack), expression, dictionary
215 def BinaryBuiltinWrapper(f):
217 Wrap functions that take two arguments and return a single result.
221 def inner(stack, expression, dictionary):
223 (a, (b, stack)) = stack
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)
232 ## raise NotAnIntError
234 return (result, stack), expression, dictionary
238 def UnaryBuiltinWrapper(f):
240 Wrap functions that take one argument and return a single result.
244 def inner(stack, expression, dictionary):
247 return (result, stack), expression, dictionary
251 class DefinitionWrapper(object):
253 Provide implementation of defined functions, and some helper methods.
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
263 def __call__(self, stack, expression, dictionary):
265 return self._compiled(stack, expression, dictionary) # pylint: disable=E1102
266 expression = list_to_stack(self._body, expression)
267 return stack, expression, dictionary
270 def parse_definition(class_, defi):
272 Given some text describing a Joy function definition parse it and
273 return a DefinitionWrapper.
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 '=='.
282 name, part, body = defi.partition('==')
284 return class_(name.strip(), body.strip())
285 raise ValueError("No '==' in definition text %r" % (defi,))
287 # return class_(*(n.strip() for n in defi.split(None, 1)))
290 def add_definitions(class_, defs, dictionary):
292 Scan multi-line string defs for definitions and add them to the
295 for definition in _text_to_defs(defs):
296 class_.add_def(definition, dictionary)
299 def add_def(class_, definition, dictionary, fail_fails=False):
301 Add the definition to the dictionary.
303 F = class_.parse_definition(definition)
304 dictionary[F.name] = F
307 def load_definitions(class_, filename, dictionary):
308 with open(filename) as f:
309 lines = [line for line in f if '==' in line]
311 class_.add_def(line, dictionary)
314 class Def(DefinitionWrapper):
316 Definitions created by inscribe.
319 def __init__(self, name, body):
322 self._body = tuple(iter_stack(body))
323 self.__doc__ = expression_to_string(body)
324 self._compiled = None
327 def load_definitions(class_, stream, dictionary):
329 if line.lstrip().startswith('#'):
331 name, body = text_to_expression(line)
332 inscribe(class_(name, body), dictionary)
335 def _text_to_defs(text):
338 for line in text.splitlines()
340 and not line.startswith('#')
352 def inscribe_(stack, expression, dictionary):
354 Create a new Joy function definition in the Joy dictionary. A
355 definition is given as a quote with a name followed by a Joy
356 expression. for example:
358 [sqr dup mul] inscribe
361 (name, body), stack = stack
362 inscribe(Def(name, body), dictionary)
363 return stack, expression, dictionary
367 @SimpleFunctionWrapper
369 '''Parse the string on the stack to a Joy expression.'''
371 expression = text_to_expression(text)
372 return expression, stack
376 # @SimpleFunctionWrapper
378 # '''Attempt to infer the stack effect of a Joy expression.'''
380 # effects = infer_expression(E)
381 # e = list_to_stack([(fi, (fo, ())) for fi, fo in effects])
386 @SimpleFunctionWrapper
391 getitem == drop first
393 Expects an integer and a quote on the stack and returns the item at the
394 nth position in the quote counting from 0.
398 -------------------------
402 n, (Q, stack) = stack
403 return pick(Q, n), stack
407 @SimpleFunctionWrapper
414 Expects an integer and a quote on the stack and returns the quote with
415 n items removed off the top.
419 ----------------------
423 n, (Q, stack) = stack
434 @SimpleFunctionWrapper
437 Expects an integer and a quote on the stack and returns the quote with
438 just the top n items in reverse order (because that's easier and you can
439 use reverse if needed.)
443 ----------------------
447 n, (Q, stack) = stack
461 def gcd2(stack, expression, dictionary):
462 '''Compiled GCD function.'''
463 (v1, (v2, stack)) = stack
468 (v1, (v2, stack)) = (v3, (v1, stack))
469 return (v2, stack), expression, dictionary
473 @SimpleFunctionWrapper
476 Use a Boolean value to select one of two items.
480 ----------------------
485 ---------------------
488 Currently Python semantics are used to evaluate the "truthiness" of the
489 Boolean value (so empty string, zero, etc. are counted as false, etc.)
491 (if_, (then, (else_, stack))) = stack
492 return then if if_ else else_, stack
496 @SimpleFunctionWrapper
499 Use a Boolean value to select one of two items from a sequence.
503 ------------------------
508 -----------------------
511 The sequence can contain more than two items but not fewer.
512 Currently Python semantics are used to evaluate the "truthiness" of the
513 Boolean value (so empty string, zero, etc. are counted as false, etc.)
515 (flag, (choices, stack)) = stack
516 (else_, (then, _)) = choices
517 return then if flag else else_, stack
521 @SimpleFunctionWrapper
523 '''Given a list find the maximum.'''
525 return max(iter_stack(tos)), stack
529 @SimpleFunctionWrapper
531 '''Given a list find the minimum.'''
533 return min(iter_stack(tos)), stack
537 @SimpleFunctionWrapper
540 Given a quoted sequence of numbers return the sum.
543 sum == 0 swap [+] step
547 return sum(iter_stack(tos)), stack
551 @SimpleFunctionWrapper
554 Expects an item on the stack and a quote under it and removes that item
555 from the the quote. The item is only removed once.
559 ------------------------
563 (tos, (second, stack)) = S
564 l = list(iter_stack(second))
566 return list_to_stack(l), stack
570 @SimpleFunctionWrapper
572 '''Given a list remove duplicate items.'''
574 I = list(iter_stack(tos))
575 return list_to_stack(sorted(set(I), key=I.index)), stack
579 @SimpleFunctionWrapper
581 '''Given a list return it sorted.'''
583 return list_to_stack(sorted(iter_stack(tos))), stack
587 @SimpleFunctionWrapper
589 '''Clear everything from the stack.
592 clear == stack [pop stack] loop
602 @SimpleFunctionWrapper
603 def disenstacken(stack):
605 The disenstacken operator expects a list on top of the stack and makes that
606 the stack discarding the rest of the stack.
612 @SimpleFunctionWrapper
615 Reverse the list on the top of the stack.
618 reverse == [] swap shunt
622 for term in iter_stack(tos):
628 @SimpleFunctionWrapper
631 Concatinate the two lists on the top of the stack.
634 [a b c] [d e f] concat
635 ----------------------------
639 (tos, (second, stack)) = S
640 return concat(second, tos), stack
644 @SimpleFunctionWrapper
647 Like concat but reverses the top list into the second.
650 shunt == [swons] step == reverse swap concat
652 [a b c] [d e f] shunt
653 ---------------------------
657 (tos, (second, stack)) = stack
660 second = term, second
665 @SimpleFunctionWrapper
668 Replace the two lists on the top of the stack with a list of the pairs
669 from each list. The smallest list sets the length of the result list.
671 (tos, (second, stack)) = S
674 for a, b in zip(iter_stack(tos), iter_stack(second))
676 return list_to_stack(accumulator), stack
680 @SimpleFunctionWrapper
684 return tos + 1, stack
688 @SimpleFunctionWrapper
692 return tos - 1, stack
696 @SimpleFunctionWrapper
707 a, (b, stack) = stack
713 return int(math.floor(n))
715 floor.__doc__ = math.floor.__doc__
719 @SimpleFunctionWrapper
722 divmod(x, y) -> (quotient, remainder)
724 Return the tuple (x//y, x%y). Invariant: q * y + r == x.
733 Return the square root of the number a.
734 Negative numbers return complex roots.
739 assert a < 0, repr(a)
740 r = math.sqrt(-a) * 1j
746 # if isinstance(text, str):
747 # return run(text, stack)
752 @SimpleFunctionWrapper
754 '''The identity function.'''
759 @SimpleFunctionWrapper
761 '''True if the form on TOS is void otherwise False.'''
763 return _void(form), stack
767 return any(not _void(i) for i in iter_stack(form))
778 def words(stack, expression, dictionary):
779 '''Print all the words in alphabetical order.'''
780 print(' '.join(sorted(dictionary)))
781 return stack, expression, dictionary
786 def sharing(stack, expression, dictionary):
787 '''Print redistribution information.'''
788 print("You may convey verbatim copies of the Program's source code as"
789 ' you receive it, in any medium, provided that you conspicuously'
790 ' and appropriately publish on each copy an appropriate copyright'
791 ' notice; keep intact all notices stating that this License and'
792 ' any non-permissive terms added in accord with section 7 apply'
793 ' to the code; keep intact all notices of the absence of any'
794 ' warranty; and give all recipients a copy of this License along'
796 ' You should have received a copy of the GNU General Public License'
797 ' along with Thun. If not see <http://www.gnu.org/licenses/>.')
798 return stack, expression, dictionary
803 def warranty(stack, expression, dictionary):
804 '''Print warranty information.'''
805 print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
806 ' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
807 ' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
808 ' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
809 ' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
810 ' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
811 ' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
812 ' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
813 ' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
814 return stack, expression, dictionary
817 # def simple_manual(stack):
819 # Print words and help for each word.
821 # for name, f in sorted(FUNCTIONS.items()):
823 # boxline = '+%s+' % ('-' * (len(name) + 2))
826 # '| %s |' % (name,),
828 # d if d else ' ...',
838 def help_(S, expression, dictionary):
839 '''Accepts a quoted symbol on the top of the stack and prints its docs.'''
840 ((symbol, _), stack) = S
841 word = dictionary[symbol]
842 print(HELP_TEMPLATE % (symbol, getdoc(word), symbol))
843 return stack, expression, dictionary
851 # Several combinators depend on other words in their definitions,
852 # we use symbols to prevent hard-coding these, so in theory, you
853 # could change the word in the dictionary to use different semantics.
854 S_choice = Symbol('choice')
855 S_first = Symbol('first')
856 S_genrec = Symbol('genrec')
857 S_getitem = Symbol('getitem')
859 S_ifte = Symbol('ifte')
860 S_infra = Symbol('infra')
861 S_loop = Symbol('loop')
862 S_pop = Symbol('pop')
863 S_primrec = Symbol('primrec')
864 S_step = Symbol('step')
865 S_swaack = Symbol('swaack')
866 S_times = Symbol('times')
871 def i(stack, expression, dictionary):
873 The i combinator expects a quoted program on the stack and unpacks it
874 onto the pending expression for evaluation.
885 raise StackUnderflowError('Not enough values on stack.')
886 return stack, concat(quote, expression), dictionary
891 def x(stack, expression, dictionary):
897 ... [Q] x = ... [Q] dup i
898 ... [Q] x = ... [Q] [Q] i
899 ... [Q] x = ... [Q] Q
903 return stack, concat(quote, expression), dictionary
908 def b(stack, expression, dictionary):
914 ... [P] [Q] b == ... [P] i [Q] i
915 ... [P] [Q] b == ... P Q
918 q, (p, (stack)) = stack
919 return stack, concat(p, concat(q, expression)), dictionary
924 def dupdip(stack, expression, dictionary):
928 [F] dupdip == dup [F] dip
938 return stack, concat(F, (a, expression)), dictionary
943 def infra(stack, expression, dictionary):
945 Accept a quoted program and a list on the stack and run the program
946 with the list as its stack. Does not affect the rest of the stack.
949 ... [a b c] [Q] . infra
950 -----------------------------
951 c b a . Q [...] swaack
954 (quote, (aggregate, stack)) = stack
955 return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary
960 def genrec(stack, expression, dictionary):
962 General Recursion Combinator.
965 [if] [then] [rec1] [rec2] genrec
966 ---------------------------------------------------------------------
967 [if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
969 From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
970 "The genrec combinator takes four program parameters in addition to
971 whatever data parameters it needs. Fourth from the top is an if-part,
972 followed by a then-part. If the if-part yields true, then the then-part
973 is executed and the combinator terminates. The other two parameters are
974 the rec1-part and the rec2-part. If the if-part yields false, the
975 rec1-part is executed. Following that the four program parameters and
976 the combinator are again pushed onto the stack bundled up in a quoted
977 form. Then the rec2-part is executed, where it will find the bundled
978 form. Typically it will then execute the bundled form, either with i or
979 with app2, or some other combinator."
981 The way to design one of these is to fix your base case [then] and the
982 test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
983 a quotation of the whole function.
985 For example, given a (general recursive) function 'F':
988 F == [I] [T] [R1] [R2] genrec
990 If the [I] if-part fails you must derive R1 and R2 from:
995 Just set the stack arguments in front, and figure out what R1 and R2
996 have to do to apply the quoted [F] in the proper way. In effect, the
997 genrec combinator turns into an ifte combinator with a quoted copy of
998 the original definition in the else-part:
1001 F == [I] [T] [R1] [R2] genrec
1002 == [I] [T] [R1 [F] R2] ifte
1004 Primitive recursive functions are those where R2 == i.
1007 P == [I] [T] [R] tailrec
1008 == [I] [T] [R [P] i] ifte
1009 == [I] [T] [R P] ifte
1012 (rec2, (rec1, stack)) = stack
1013 (then, (if_, _)) = stack
1014 F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
1015 else_ = concat(rec1, (F, rec2))
1016 return (else_, stack), (S_ifte, expression), dictionary
1021 def map_(S, expression, dictionary):
1023 Run the quoted program on TOS on the items in the list under it, push a
1024 new list with the results in place of the program and original list.
1026 # (quote, (aggregate, stack)) = S
1027 # results = list_to_stack([
1028 # joy((term, stack), quote, dictionary)[0][0]
1029 # for term in iter_stack(aggregate)
1031 # return (results, stack), expression, dictionary
1032 (quote, (aggregate, stack)) = S
1034 return (aggregate, stack), expression, dictionary
1036 for term in iter_stack(aggregate):
1038 batch = (s, (quote, (S_infra, (S_first, batch))))
1039 stack = (batch, ((), stack))
1040 return stack, (S_infra, expression), dictionary
1045 def primrec(stack, expression, dictionary):
1047 From the "Overview of the language JOY":
1049 > The primrec combinator expects two quoted programs in addition to a
1050 data parameter. For an integer data parameter it works like this: If
1051 the data parameter is zero, then the first quotation has to produce
1052 the value to be returned. If the data parameter is positive then the
1053 second has to combine the data parameter with the result of applying
1054 the function to its predecessor.::
1058 > Then primrec tests whether the top element on the stack (initially
1059 the 5) is equal to zero. If it is, it pops it off and executes one of
1060 the quotations, the [1] which leaves 1 on the stack as the result.
1061 Otherwise it pushes a decremented copy of the top element and
1062 recurses. On the way back from the recursion it uses the other
1063 quotation, [*], to multiply what is now a factorial on top of the
1064 stack by the second element on the stack.::
1066 n [Base] [Recur] primrec
1068 0 [Base] [Recur] primrec
1069 ------------------------------
1072 n [Base] [Recur] primrec
1073 ------------------------------------------ n > 0
1074 n (n-1) [Base] [Recur] primrec Recur
1077 recur, (base, (n, stack)) = stack
1079 expression = concat(base, expression)
1081 expression = S_primrec, concat(recur, expression)
1082 stack = recur, (base, (n - 1, (n, stack)))
1083 return stack, expression, dictionary
1086 #def cleave(S, expression, dictionary):
1088 # The cleave combinator expects two quotations, and below that an item X.
1089 # It first executes [P], with X on top, and saves the top result element.
1090 # Then it executes [Q], again with X, and saves the top result.
1091 # Finally it restores the stack to what it was below X and pushes the two
1092 # results P(X) and Q(X).
1094 # (Q, (P, (x, stack))) = S
1095 # p = joy((x, stack), P, dictionary)[0][0]
1096 # q = joy((x, stack), Q, dictionary)[0][0]
1097 # return (q, (p, stack)), expression, dictionary
1102 def branch(stack, expression, dictionary):
1104 Use a Boolean value to select one of two quoted programs to run.
1108 branch == roll< choice i
1112 False [F] [T] branch
1113 --------------------------
1117 -------------------------
1121 (then, (else_, (flag, stack))) = stack
1122 return stack, concat(then if flag else else_, expression), dictionary
1127 ##def ifte(stack, expression, dictionary):
1129 ## If-Then-Else Combinator
1132 ## ... [if] [then] [else] ifte
1133 ## ---------------------------------------------------
1134 ## ... [[else] [then]] [...] [if] infra select i
1139 ## ... [if] [then] [else] ifte
1140 ## -------------------------------------------------------
1141 ## ... [else] [then] [...] [if] infra first choice i
1144 ## Has the effect of grabbing a copy of the stack on which to run the
1145 ## if-part using infra.
1147 ## (else_, (then, (if_, stack))) = stack
1148 ## expression = (S_infra, (S_first, (S_choice, (S_i, expression))))
1149 ## stack = (if_, (stack, (then, (else_, stack))))
1150 ## return stack, expression, dictionary
1155 def cond(stack, expression, dictionary):
1157 This combinator works like a case statement. It expects a single quote
1158 on the stack that must contain zero or more condition quotes and a
1159 default quote. Each condition clause should contain a quoted predicate
1160 followed by the function expression to run if that predicate returns
1161 true. If no predicates return true the default function runs.
1163 It works by rewriting into a chain of nested `ifte` expressions, e.g.::
1165 [[[B0] T0] [[B1] T1] [D]] cond
1166 -----------------------------------------
1167 [B0] [T0] [[B1] [T1] [D] ifte] ifte
1170 conditions, stack = stack
1172 expression = _cond(conditions, expression)
1174 # Attempt to preload the args to first ifte.
1175 (P, (T, (E, expression))) = expression
1177 # If, for any reason, the argument to cond should happen to contain
1178 # only the default clause then this optimization will fail.
1181 stack = (E, (T, (P, stack)))
1182 return stack, expression, dictionary
1185 def _cond(conditions, expression):
1186 (clause, rest) = conditions
1187 if not rest: # clause is [D]
1190 return (P, (T, (_cond(rest, ()), (S_ifte, expression))))
1195 def dip(stack, expression, dictionary):
1197 The dip combinator expects a quoted program on the stack and below it
1198 some item, it hoists the item into the expression and runs the program
1199 on the rest of the stack.
1208 (quote, (x, stack)) = stack
1210 raise StackUnderflowError('Not enough values on stack.')
1211 expression = (x, expression)
1212 return stack, concat(quote, expression), dictionary
1217 def dipd(S, expression, dictionary):
1219 Like dip but expects two items.
1223 ---------------------
1227 (quote, (x, (y, stack))) = S
1228 expression = (y, (x, expression))
1229 return stack, concat(quote, expression), dictionary
1234 def dipdd(S, expression, dictionary):
1236 Like dip but expects three items.
1240 -----------------------
1244 (quote, (x, (y, (z, stack)))) = S
1245 expression = (z, (y, (x, expression)))
1246 return stack, concat(quote, expression), dictionary
1251 def app1(S, expression, dictionary):
1253 Given a quoted program on TOS and anything as the second stack item run
1254 the program and replace the two args with the first result of the
1259 -----------------------------------
1260 ... [x ...] [Q] . infra first
1263 (quote, (x, stack)) = S
1264 stack = (quote, ((x, stack), stack))
1265 expression = (S_infra, (S_first, expression))
1266 return stack, expression, dictionary
1271 def app2(S, expression, dictionary):
1272 '''Like app1 with two items.
1276 -----------------------------------
1277 ... [y ...] [Q] . infra first
1278 [x ...] [Q] infra first
1281 (quote, (x, (y, stack))) = S
1282 expression = (S_infra, (S_first,
1283 ((x, stack), (quote, (S_infra, (S_first,
1285 stack = (quote, ((y, stack), stack))
1286 return stack, expression, dictionary
1291 def app3(S, expression, dictionary):
1292 '''Like app1 with three items.
1295 ... z y x [Q] . app3
1296 -----------------------------------
1297 ... [z ...] [Q] . infra first
1298 [y ...] [Q] infra first
1299 [x ...] [Q] infra first
1302 (quote, (x, (y, (z, stack)))) = S
1303 expression = (S_infra, (S_first,
1304 ((y, stack), (quote, (S_infra, (S_first,
1305 ((x, stack), (quote, (S_infra, (S_first,
1306 expression))))))))))
1307 stack = (quote, ((z, stack), stack))
1308 return stack, expression, dictionary
1313 def step(S, expression, dictionary):
1315 Run a quoted program on each item in a sequence.
1319 -----------------------
1324 ------------------------
1328 ... [a b c] [Q] . step
1329 ----------------------------------------
1330 ... a . Q [b c] [Q] step
1332 The step combinator executes the quotation on each member of the list
1333 on top of the stack.
1335 (quote, (aggregate, stack)) = S
1337 return stack, expression, dictionary
1338 head, tail = aggregate
1339 stack = quote, (head, stack)
1341 expression = tail, (quote, (S_step, expression))
1342 expression = S_i, expression
1343 return stack, expression, dictionary
1348 def times(stack, expression, dictionary):
1350 times == [-- dip] cons [swap] infra [0 >] swap while pop
1354 --------------------- w/ n <= 0
1359 -----------------------
1364 ------------------------------------- w/ n > 1
1365 ... . Q (n - 1) [Q] times
1368 # times == [-- dip] cons [swap] infra [0 >] swap while pop
1369 (quote, (n, stack)) = stack
1371 return stack, expression, dictionary
1374 expression = n, (quote, (S_times, expression))
1375 expression = concat(quote, expression)
1376 return stack, expression, dictionary
1379 # The current definition above works like this:
1382 # --------------------------------------
1383 # [P] nullary [Q [P] nullary] loop
1385 # while == [pop i not] [popop] [dudipd] tailrec
1387 #def while_(S, expression, dictionary):
1388 # '''[if] [body] while'''
1389 # (body, (if_, stack)) = S
1390 # while joy(stack, if_, dictionary)[0][0]:
1391 # stack = joy(stack, body, dictionary)[0]
1392 # return stack, expression, dictionary
1397 def loop(stack, expression, dictionary):
1399 Basic loop combinator.
1403 -----------------------
1407 ------------------------
1412 quote, stack = stack
1414 raise StackUnderflowError('Not enough values on stack.')
1415 if not isinstance(quote, tuple):
1416 raise NotAListError('Loop body not a list.')
1418 (flag, stack) = stack
1420 raise StackUnderflowError('Not enough values on stack.')
1422 expression = concat(quote, (quote, (S_loop, expression)))
1423 return stack, expression, dictionary
1428 def cmp_(stack, expression, dictionary):
1430 cmp takes two values and three quoted programs on the stack and runs
1431 one of the three depending on the results of comparing the two values:
1435 ------------------------- a > b
1439 ------------------------- a = b
1443 ------------------------- a < b
1446 L, (E, (G, (b, (a, stack)))) = stack
1447 expression = concat(G if a > b else L if a < b else E, expression)
1448 return stack, expression, dictionary
1451 # FunctionWrapper(cleave),
1452 # FunctionWrapper(while_),
1457 #divmod_ = pm = __(n2, n1), __(n4, n3)
1459 BinaryBuiltinWrapper(operator.eq),
1460 BinaryBuiltinWrapper(operator.ge),
1461 BinaryBuiltinWrapper(operator.gt),
1462 BinaryBuiltinWrapper(operator.le),
1463 BinaryBuiltinWrapper(operator.lt),
1464 BinaryBuiltinWrapper(operator.ne),
1466 BinaryBuiltinWrapper(operator.xor),
1467 BinaryBuiltinWrapper(operator.lshift),
1468 BinaryBuiltinWrapper(operator.rshift),
1470 BinaryBuiltinWrapper(operator.and_),
1471 BinaryBuiltinWrapper(operator.or_),
1473 BinaryBuiltinWrapper(operator.add),
1474 BinaryBuiltinWrapper(operator.floordiv),
1475 BinaryBuiltinWrapper(operator.mod),
1476 BinaryBuiltinWrapper(operator.mul),
1477 BinaryBuiltinWrapper(operator.pow),
1478 BinaryBuiltinWrapper(operator.sub),
1479 ## BinaryBuiltinWrapper(operator.truediv),
1481 UnaryBuiltinWrapper(bool),
1482 UnaryBuiltinWrapper(operator.not_),
1484 UnaryBuiltinWrapper(abs),
1485 UnaryBuiltinWrapper(operator.neg),
1486 UnaryBuiltinWrapper(sqrt),
1488 UnaryBuiltinWrapper(floor),
1489 UnaryBuiltinWrapper(round),
1492 del F # Otherwise Sphinx autodoc will pick it up.
1495 for name, primitive in getmembers(genlib, isfunction):
1496 inscribe(SimpleFunctionWrapper(primitive))
1499 add_aliases(_dictionary, ALIASES)
1502 DefinitionWrapper.add_definitions(definitions, _dictionary)