+# -*- coding: utf-8 -*-
+#
+# Copyright © 2014, 2015, 2017, 2018 Simon Forman
+#
+# This file is part of Thun
+#
+# Thun is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Thun is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Thun. If not see <http://www.gnu.org/licenses/>.
+#
+'''
+This module contains the Joy function infrastructure and a library of
+functions. Its main export is a Python function initialize() that
+returns a dictionary of Joy functions suitable for use with the joy()
+function.
+'''
+from inspect import getdoc
+from functools import wraps
+import operator, math
+
+from .parser import text_to_expression, Symbol
+from .utils.stack import list_to_stack, iter_stack, pick, pushback
+from .utils.brutal_hackery import rename_code_object
+
+
+_dictionary = {}
+
+
+[docs]def inscribe(function):
+
'''A decorator to inscribe functions into the default dictionary.'''
+
_dictionary[function.name] = function
+
return function
+
+
+[docs]def initialize():
+
'''Return a dictionary of Joy functions for use with joy().'''
+
return _dictionary.copy()
+
+
+ALIASES = (
+ ('add', ['+']),
+ ('and', ['&']),
+ ('bool', ['truthy']),
+ ('mul', ['*']),
+ ('truediv', ['/']),
+ ('mod', ['%', 'rem', 'remainder', 'modulus']),
+ ('eq', ['=']),
+ ('ge', ['>=']),
+ ('getitem', ['pick', 'at']),
+ ('gt', ['>']),
+ ('le', ['<=']),
+ ('lshift', ['<<']),
+ ('lt', ['<']),
+ ('ne', ['<>', '!=']),
+ ('rshift', ['>>']),
+ ('sub', ['-']),
+ ('xor', ['^']),
+ ('succ', ['++']),
+ ('pred', ['--']),
+ ('rolldown', ['roll<']),
+ ('rollup', ['roll>']),
+ ('id', ['•']),
+ )
+
+
+[docs]def add_aliases(D, A):
+
'''
+
Given a dict and a iterable of (name, [alias, ...]) pairs, create
+
additional entries in the dict mapping each alias to the named function
+
if it's in the dict. Aliases for functions not in the dict are ignored.
+
'''
+
for name, aliases in A:
+
try:
+
F = D[name]
+
except KeyError:
+
continue
+
for alias in aliases:
+
D[alias] = F
+
+
+definitions = ('''\
+second == rest first
+third == rest rest first
+of == swap at
+product == 1 swap [*] step
+swons == swap cons
+swoncat == swap concat
+flatten == [] swap [concat] step
+unit == [] cons
+quoted == [unit] dip
+unquoted == [i] dip
+enstacken == stack [clear] dip
+disenstacken == ? [uncons ?] loop pop
+? == dup truthy
+dinfrirst == dip infra first
+nullary == [stack] dinfrirst
+unary == [stack [pop] dip] dinfrirst
+binary == [stack [popop] dip] dinfrirst
+ternary == [stack [popop pop] dip] dinfrirst
+pam == [i] map
+run == [] swap infra
+sqr == dup mul
+size == 0 swap [pop ++] step
+cleave == [i] app2 [popd] dip
+average == [sum 1.0 *] [size] cleave /
+gcd == 1 [tuck modulus dup 0 >] loop pop
+least_fraction == dup [gcd] infra [div] concat map
+*fraction == [uncons] dip uncons [swap] dip concat [*] infra [*] dip cons
+*fraction0 == concat [[swap] dip * [*] dip] infra
+down_to_zero == [0 >] [dup --] while
+range_to_zero == unit [down_to_zero] infra
+anamorphism == [pop []] swap [dip swons] genrec
+range == [0 <=] [1 - dup] anamorphism
+while == swap [nullary] cons dup dipd concat loop
+dudipd == dup dipd
+primrec == [i] genrec
+step_zero == 0 roll> step
+'''
+
+##Zipper
+##z-down == [] swap uncons swap
+##z-up == swons swap shunt
+##z-right == [swons] cons dip uncons swap
+##z-left == swons [uncons swap] dip swap
+
+##Quadratic Formula
+##divisor == popop 2 *
+##minusb == pop neg
+##radical == swap dup * rollup * 4 * - sqrt
+##root1 == + swap /
+##root2 == - swap /
+##q0 == [[divisor] [minusb] [radical]] pam
+##q1 == [[root1] [root2]] pam
+##quadratic == [q0] ternary i [q1] ternary
+
+# Project Euler
+##'''\
+##PE1.1 == + dup [+] dip
+##PE1.2 == dup [3 & PE1.1] dip 2 >>
+##PE1.3 == 14811 swap [PE1.2] times pop
+##PE1 == 0 0 66 [7 PE1.3] times 4 PE1.3 pop
+##'''
+#PE1.2 == [PE1.1] step
+#PE1 == 0 0 66 [[3 2 1 3 1 2 3] PE1.2] times [3 2 1 3] PE1.2 pop
+)
+
+
+[docs]def FunctionWrapper(f):
+
'''Set name attribute.'''
+
if not f.__doc__:
+
raise ValueError('Function %s must have doc string.' % f.__name__)
+
f.name = f.__name__.rstrip('_') # Don't shadow builtins.
+
return f
+
+
+[docs]def SimpleFunctionWrapper(f):
+
'''
+
Wrap functions that take and return just a stack.
+
'''
+
@FunctionWrapper
+
@wraps(f)
+
@rename_code_object(f.__name__)
+
def inner(stack, expression, dictionary):
+
return f(stack), expression, dictionary
+
return inner
+
+
+[docs]def BinaryBuiltinWrapper(f):
+
'''
+
Wrap functions that take two arguments and return a single result.
+
'''
+
@FunctionWrapper
+
@wraps(f)
+
@rename_code_object(f.__name__)
+
def inner(stack, expression, dictionary):
+
(a, (b, stack)) = stack
+
result = f(b, a)
+
return (result, stack), expression, dictionary
+
return inner
+
+
+[docs]def UnaryBuiltinWrapper(f):
+
'''
+
Wrap functions that take one argument and return a single result.
+
'''
+
@FunctionWrapper
+
@wraps(f)
+
@rename_code_object(f.__name__)
+
def inner(stack, expression, dictionary):
+
(a, stack) = stack
+
result = f(a)
+
return (result, stack), expression, dictionary
+
return inner
+
+
+[docs]class DefinitionWrapper(object):
+
'''
+
Provide implementation of defined functions, and some helper methods.
+
'''
+
+
def __init__(self, name, body_text, doc=None):
+
self.name = self.__name__ = name
+
self.body = text_to_expression(body_text)
+
self._body = tuple(iter_stack(self.body))
+
self.__doc__ = doc or body_text
+
+
def __call__(self, stack, expression, dictionary):
+
expression = list_to_stack(self._body, expression)
+
return stack, expression, dictionary
+
+
[docs] @classmethod
+
def parse_definition(class_, defi):
+
'''
+
Given some text describing a Joy function definition parse it and
+
return a DefinitionWrapper.
+
'''
+
name, proper, body_text = (n.strip() for n in defi.partition('=='))
+
if not proper:
+
raise ValueError('Definition %r failed' % (defi,))
+
return class_(name, body_text)
+
+
[docs] @classmethod
+
def add_definitions(class_, defs, dictionary):
+
'''
+
Scan multi-line string defs for definitions and add them to the
+
dictionary.
+
'''
+
for definition in _text_to_defs(defs):
+
class_.add_def(definition, dictionary)
+
+
[docs] @classmethod
+
def add_def(class_, definition, dictionary):
+
'''
+
Add the definition to the dictionary.
+
'''
+
F = class_.parse_definition(definition)
+
dictionary[F.name] = F
+
+
+def _text_to_defs(text):
+ return (line.strip() for line in text.splitlines() if '==' in line)
+
+
+#
+# Functions
+#
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def parse(stack):
+
'''Parse the string on the stack to a Joy expression.'''
+
text, stack = stack
+
expression = text_to_expression(text)
+
return expression, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def first(stack):
+
'''
+
::
+
+
first == uncons pop
+
+
'''
+
((head, tail), stack) = stack
+
return head, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def rest(stack):
+
'''
+
::
+
+
rest == uncons popd
+
+
'''
+
((head, tail), stack) = stack
+
return tail, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def getitem(stack):
+
'''
+
::
+
+
getitem == drop first
+
+
Expects an integer and a quote on the stack and returns the item at the
+
nth position in the quote counting from 0.
+
::
+
+
[a b c d] 0 getitem
+
-------------------------
+
a
+
+
'''
+
n, (Q, stack) = stack
+
return pick(Q, n), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def drop(stack):
+
'''
+
::
+
+
drop == [rest] times
+
+
Expects an integer and a quote on the stack and returns the quote with
+
n items removed off the top.
+
::
+
+
[a b c d] 2 drop
+
----------------------
+
[c d]
+
+
'''
+
n, (Q, stack) = stack
+
while n > 0:
+
try:
+
_, Q = Q
+
except ValueError:
+
raise IndexError
+
n -= 1
+
return Q, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def take(stack):
+
'''
+
Expects an integer and a quote on the stack and returns the quote with
+
just the top n items in reverse order (because that's easier and you can
+
use reverse if needed.)
+
::
+
+
[a b c d] 2 take
+
----------------------
+
[b a]
+
+
'''
+
n, (Q, stack) = stack
+
x = ()
+
while n > 0:
+
try:
+
item, Q = Q
+
except ValueError:
+
raise IndexError
+
x = item, x
+
n -= 1
+
return x, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def choice(stack):
+
'''
+
Use a Boolean value to select one of two items.
+
::
+
+
A B False choice
+
----------------------
+
A
+
+
+
A B True choice
+
---------------------
+
B
+
+
Currently Python semantics are used to evaluate the "truthiness" of the
+
Boolean value (so empty string, zero, etc. are counted as false, etc.)
+
'''
+
(if_, (then, (else_, stack))) = stack
+
return then if if_ else else_, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def select(stack):
+
'''
+
Use a Boolean value to select one of two items from a sequence.
+
::
+
+
[A B] False select
+
------------------------
+
A
+
+
+
[A B] True select
+
-----------------------
+
B
+
+
The sequence can contain more than two items but not fewer.
+
Currently Python semantics are used to evaluate the "truthiness" of the
+
Boolean value (so empty string, zero, etc. are counted as false, etc.)
+
'''
+
(flag, (choices, stack)) = stack
+
(else_, (then, _)) = choices
+
return then if flag else else_, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def max_(S):
+
'''Given a list find the maximum.'''
+
tos, stack = S
+
return max(iter_stack(tos)), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def min_(S):
+
'''Given a list find the minimum.'''
+
tos, stack = S
+
return min(iter_stack(tos)), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def sum_(S):
+
'''Given a quoted sequence of numbers return the sum.
+
+
sum == 0 swap [+] step
+
'''
+
tos, stack = S
+
return sum(iter_stack(tos)), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def remove(S):
+
'''
+
Expects an item on the stack and a quote under it and removes that item
+
from the the quote. The item is only removed once.
+
::
+
+
[1 2 3 1] 1 remove
+
------------------------
+
[2 3 1]
+
+
'''
+
(tos, (second, stack)) = S
+
l = list(iter_stack(second))
+
l.remove(tos)
+
return list_to_stack(l), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def unique(S):
+
'''Given a list remove duplicate items.'''
+
tos, stack = S
+
I = list(iter_stack(tos))
+
list_to_stack(sorted(set(I), key=I.index))
+
return list_to_stack(sorted(set(I), key=I.index)), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def sort_(S):
+
'''Given a list return it sorted.'''
+
tos, stack = S
+
return list_to_stack(sorted(iter_stack(tos))), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def cons(S):
+
'''
+
The cons operator expects a list on top of the stack and the potential
+
member below. The effect is to add the potential member into the
+
aggregate.
+
'''
+
(tos, (second, stack)) = S
+
return (second, tos), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def uncons(S):
+
'''
+
Inverse of cons, removes an item from the top of the list on the stack
+
and places it under the remaining list.
+
'''
+
(tos, stack) = S
+
item, tos = tos
+
return tos, (item, stack)
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def clear(stack):
+
'''Clear everything from the stack.
+
::
+
+
... clear
+
---------------
+
+
'''
+
return ()
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def dup(S):
+
'''Duplicate the top item on the stack.'''
+
(tos, stack) = S
+
return tos, (tos, stack)
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def over(S):
+
'''
+
Copy the second item down on the stack to the top of the stack.
+
::
+
+
a b over
+
--------------
+
a b a
+
+
'''
+
second = S[1][0]
+
return second, S
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def tuck(S):
+
'''
+
Copy the item at TOS under the second item of the stack.
+
::
+
+
a b tuck
+
--------------
+
b a b
+
+
'''
+
(tos, (second, stack)) = S
+
return tos, (second, (tos, stack))
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def swap(S):
+
'''Swap the top two items on stack.'''
+
(tos, (second, stack)) = S
+
return second, (tos, stack)
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def swaack(stack):
+
'''swap stack'''
+
old_stack, stack = stack
+
return stack, old_stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def stack_(stack):
+
'''
+
The stack operator pushes onto the stack a list containing all the
+
elements of the stack.
+
'''
+
return stack, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def unstack(stack):
+
'''
+
The unstack operator expects a list on top of the stack and makes that
+
the stack discarding the rest of the stack.
+
'''
+
return stack[0]
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def pop(stack):
+
'''Pop and discard the top item from the stack.'''
+
return stack[1]
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def popd(stack):
+
'''Pop and discard the second item from the stack.'''
+
(tos, (_, stack)) = stack
+
return tos, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def popdd(stack):
+
'''Pop and discard the third item from the stack.'''
+
(tos, (second, (_, stack))) = stack
+
return tos, (second, stack)
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def popop(stack):
+
'''Pop and discard the first and second items from the stack.'''
+
return stack[1][1]
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def dupd(S):
+
'''Duplicate the second item on the stack.'''
+
(tos, (second, stack)) = S
+
return tos, (second, (second, stack))
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def reverse(S):
+
'''Reverse the list on the top of the stack.
+
::
+
+
reverse == [] swap shunt
+
'''
+
(tos, stack) = S
+
res = ()
+
for term in iter_stack(tos):
+
res = term, res
+
return res, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def concat(S):
+
'''Concatinate the two lists on the top of the stack.'''
+
(tos, (second, stack)) = S
+
for term in reversed(list(iter_stack(second))):
+
tos = term, tos
+
return tos, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def shunt(stack):
+
'''Like concat but reverses the top list into the second.
+
::
+
+
shunt == [swons] step
+
+
'''
+
(tos, (second, stack)) = stack
+
while tos:
+
term, tos = tos
+
second = term, second
+
return second, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def zip_(S):
+
'''
+
Replace the two lists on the top of the stack with a list of the pairs
+
from each list. The smallest list sets the length of the result list.
+
'''
+
(tos, (second, stack)) = S
+
accumulator = [
+
(a, (b, ()))
+
for a, b in zip(iter_stack(tos), iter_stack(second))
+
]
+
return list_to_stack(accumulator), stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def succ(S):
+
'''Increment TOS.'''
+
(tos, stack) = S
+
return tos + 1, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def pred(S):
+
'''Decrement TOS.'''
+
(tos, stack) = S
+
return tos - 1, stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def pm(stack):
+
'''
+
Plus or minus
+
::
+
+
a b pm
+
-------------
+
a+b a-b
+
+
'''
+
a, (b, stack) = stack
+
p, m, = b + a, b - a
+
return m, (p, stack)
+
+
+[docs]def floor(n):
+
return int(math.floor(n))
+
+floor.__doc__ = math.floor.__doc__
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def divmod_(S):
+
'''
+
divmod(x, y) -> (quotient, remainder)
+
+
Return the tuple (x//y, x%y). Invariant: div*y + mod == x.
+
'''
+
a, (b, stack) = S
+
d, m = divmod(a, b)
+
return d, (m, stack)
+
+
+[docs]def sqrt(a):
+
'''
+
Return the square root of the number a.
+
Negative numbers return complex roots.
+
'''
+
try:
+
r = math.sqrt(a)
+
except ValueError:
+
assert a < 0, repr(a)
+
r = math.sqrt(-a) * 1j
+
return r
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def rollup(S):
+
'''
+
::
+
+
a b c
+
-----------
+
b c a
+
+
'''
+
(a, (b, (c, stack))) = S
+
return b, (c, (a, stack))
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def rolldown(S):
+
'''
+
::
+
+
a b c
+
-----------
+
c a b
+
+
'''
+
(a, (b, (c, stack))) = S
+
return c, (a, (b, stack))
+
+
+#def execute(S):
+# (text, stack) = S
+# if isinstance(text, str):
+# return run(text, stack)
+# return stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def id_(stack):
+
'''The identity function.'''
+
return stack
+
+
+[docs]@inscribe
+
@SimpleFunctionWrapper
+
def void(stack):
+
'''True if the form on TOS is void otherwise False.'''
+
form, stack = stack
+
return _void(form), stack
+
+
+def _void(form):
+ return any(not _void(i) for i in iter_stack(form))
+
+
+
+## transpose
+## sign
+## take
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def words(stack, expression, dictionary):
+
'''Print all the words in alphabetical order.'''
+
print(' '.join(sorted(dictionary)))
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def sharing(stack, expression, dictionary):
+
'''Print redistribution information.'''
+
print("You may convey verbatim copies of the Program's source code as"
+
' you receive it, in any medium, provided that you conspicuously'
+
' and appropriately publish on each copy an appropriate copyright'
+
' notice; keep intact all notices stating that this License and'
+
' any non-permissive terms added in accord with section 7 apply'
+
' to the code; keep intact all notices of the absence of any'
+
' warranty; and give all recipients a copy of this License along'
+
' with the Program.'
+
' You should have received a copy of the GNU General Public License'
+
' along with Thun. If not see <http://www.gnu.org/licenses/>.')
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def warranty(stack, expression, dictionary):
+
'''Print warranty information.'''
+
print('THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY'
+
' APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE'
+
' COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM'
+
' "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR'
+
' IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES'
+
' OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE'
+
' ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS'
+
' WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE'
+
' COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.')
+
return stack, expression, dictionary
+
+
+# def simple_manual(stack):
+# '''
+# Print words and help for each word.
+# '''
+# for name, f in sorted(FUNCTIONS.items()):
+# d = getdoc(f)
+# boxline = '+%s+' % ('-' * (len(name) + 2))
+# print('\n'.join((
+# boxline,
+# '| %s |' % (name,),
+# boxline,
+# d if d else ' ...',
+# '',
+# '--' * 40,
+# '',
+# )))
+# return stack
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def help_(S, expression, dictionary):
+
'''Accepts a quoted symbol on the top of the stack and prints its docs.'''
+
((symbol, _), stack) = S
+
word = dictionary[symbol]
+
print(getdoc(word))
+
return stack, expression, dictionary
+
+
+#
+# § Combinators
+#
+
+
+# Several combinators depend on other words in their definitions,
+# we use symbols to prevent hard-coding these, so in theory, you
+# could change the word in the dictionary to use different semantics.
+S_choice = Symbol('choice')
+S_first = Symbol('first')
+S_getitem = Symbol('getitem')
+S_genrec = Symbol('genrec')
+S_loop = Symbol('loop')
+S_i = Symbol('i')
+S_ifte = Symbol('ifte')
+S_infra = Symbol('infra')
+S_step = Symbol('step')
+S_times = Symbol('times')
+S_swaack = Symbol('swaack')
+S_truthy = Symbol('truthy')
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def i(stack, expression, dictionary):
+
'''
+
The i combinator expects a quoted program on the stack and unpacks it
+
onto the pending expression for evaluation.
+
::
+
+
[Q] i
+
-----------
+
Q
+
+
'''
+
quote, stack = stack
+
return stack, pushback(quote, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def x(stack, expression, dictionary):
+
'''
+
::
+
+
x == dup i
+
+
... [Q] x = ... [Q] dup i
+
... [Q] x = ... [Q] [Q] i
+
... [Q] x = ... [Q] Q
+
+
'''
+
quote, _ = stack
+
return stack, pushback(quote, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def b(stack, expression, dictionary):
+
'''
+
::
+
+
b == [i] dip i
+
+
... [P] [Q] b == ... [P] i [Q] i
+
... [P] [Q] b == ... P Q
+
+
'''
+
q, (p, (stack)) = stack
+
return stack, pushback(p, pushback(q, expression)), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def dupdip(stack, expression, dictionary):
+
'''
+
::
+
+
[F] dupdip == dup [F] dip
+
+
... a [F] dupdip
+
... a dup [F] dip
+
... a a [F] dip
+
... a F a
+
+
'''
+
F, stack = stack
+
a = stack[0]
+
return stack, pushback(F, (a, expression)), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def infra(stack, expression, dictionary):
+
'''
+
Accept a quoted program and a list on the stack and run the program
+
with the list as its stack.
+
::
+
+
... [a b c] [Q] . infra
+
-----------------------------
+
c b a . Q [...] swaack
+
+
'''
+
(quote, (aggregate, stack)) = stack
+
return aggregate, pushback(quote, (stack, (S_swaack, expression))), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def genrec(stack, expression, dictionary):
+
'''
+
General Recursion Combinator.
+
::
+
+
[if] [then] [rec1] [rec2] genrec
+
---------------------------------------------------------------------
+
[if] [then] [rec1 [[if] [then] [rec1] [rec2] genrec] rec2] ifte
+
+
From "Recursion Theory and Joy" (j05cmp.html) by Manfred von Thun:
+
"The genrec combinator takes four program parameters in addition to
+
whatever data parameters it needs. Fourth from the top is an if-part,
+
followed by a then-part. If the if-part yields true, then the then-part
+
is executed and the combinator terminates. The other two parameters are
+
the rec1-part and the rec2-part. If the if-part yields false, the
+
rec1-part is executed. Following that the four program parameters and
+
the combinator are again pushed onto the stack bundled up in a quoted
+
form. Then the rec2-part is executed, where it will find the bundled
+
form. Typically it will then execute the bundled form, either with i or
+
with app2, or some other combinator."
+
+
The way to design one of these is to fix your base case [then] and the
+
test [if], and then treat rec1 and rec2 as an else-part "sandwiching"
+
a quotation of the whole function.
+
+
For example, given a (general recursive) function 'F':
+
::
+
+
F == [I] [T] [R1] [R2] genrec
+
+
If the [I] if-part fails you must derive R1 and R2 from:
+
::
+
+
... R1 [F] R2
+
+
Just set the stack arguments in front, and figure out what R1 and R2
+
have to do to apply the quoted [F] in the proper way. In effect, the
+
genrec combinator turns into an ifte combinator with a quoted copy of
+
the original definition in the else-part:
+
::
+
+
F == [I] [T] [R1] [R2] genrec
+
== [I] [T] [R1 [F] R2] ifte
+
+
Primitive recursive functions are those where R2 == i.
+
::
+
+
P == [I] [T] [R] primrec
+
== [I] [T] [R [P] i] ifte
+
== [I] [T] [R P] ifte
+
+
'''
+
(rec2, (rec1, stack)) = stack
+
(then, (if_, _)) = stack
+
F = (if_, (then, (rec1, (rec2, (S_genrec, ())))))
+
else_ = pushback(rec1, (F, rec2))
+
return (else_, stack), (S_ifte, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def map_(S, expression, dictionary):
+
'''
+
Run the quoted program on TOS on the items in the list under it, push a
+
new list with the results (in place of the program and original list.
+
'''
+
# (quote, (aggregate, stack)) = S
+
# results = list_to_stack([
+
# joy((term, stack), quote, dictionary)[0][0]
+
# for term in iter_stack(aggregate)
+
# ])
+
# return (results, stack), expression, dictionary
+
(quote, (aggregate, stack)) = S
+
if not aggregate:
+
return (aggregate, stack), expression, dictionary
+
batch = ()
+
for term in iter_stack(aggregate):
+
s = term, stack
+
batch = (s, (quote, (S_infra, (S_first, batch))))
+
stack = (batch, ((), stack))
+
return stack, (S_infra, expression), dictionary
+
+
+#def cleave(S, expression, dictionary):
+# '''
+# The cleave combinator expects two quotations, and below that an item X.
+# It first executes [P], with X on top, and saves the top result element.
+# Then it executes [Q], again with X, and saves the top result.
+# Finally it restores the stack to what it was below X and pushes the two
+# results P(X) and Q(X).
+# '''
+# (Q, (P, (x, stack))) = S
+# p = joy((x, stack), P, dictionary)[0][0]
+# q = joy((x, stack), Q, dictionary)[0][0]
+# return (q, (p, stack)), expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def branch(stack, expression, dictionary):
+
'''
+
Use a Boolean value to select one of two quoted programs to run.
+
+
::
+
+
branch == roll< choice i
+
+
::
+
+
False [F] [T] branch
+
--------------------------
+
F
+
+
True [F] [T] branch
+
-------------------------
+
T
+
+
'''
+
(then, (else_, (flag, stack))) = stack
+
return stack, pushback(then if flag else else_, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def ifte(stack, expression, dictionary):
+
'''
+
If-Then-Else Combinator
+
::
+
+
... [if] [then] [else] ifte
+
---------------------------------------------------
+
... [[else] [then]] [...] [if] infra select i
+
+
+
+
+
... [if] [then] [else] ifte
+
-------------------------------------------------------
+
... [else] [then] [...] [if] infra first choice i
+
+
+
Has the effect of grabbing a copy of the stack on which to run the
+
if-part using infra.
+
'''
+
(else_, (then, (if_, stack))) = stack
+
expression = (S_infra, (S_first, (S_choice, (S_i, expression))))
+
stack = (if_, (stack, (then, (else_, stack))))
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def dip(stack, expression, dictionary):
+
'''
+
The dip combinator expects a quoted program on the stack and below it
+
some item, it hoists the item into the expression and runs the program
+
on the rest of the stack.
+
::
+
+
... x [Q] dip
+
-------------------
+
... Q x
+
+
'''
+
(quote, (x, stack)) = stack
+
expression = (x, expression)
+
return stack, pushback(quote, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def dipd(S, expression, dictionary):
+
'''
+
Like dip but expects two items.
+
::
+
+
... y x [Q] dip
+
---------------------
+
... Q y x
+
+
'''
+
(quote, (x, (y, stack))) = S
+
expression = (y, (x, expression))
+
return stack, pushback(quote, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def dipdd(S, expression, dictionary):
+
'''
+
Like dip but expects three items.
+
::
+
+
... z y x [Q] dip
+
-----------------------
+
... Q z y x
+
+
'''
+
(quote, (x, (y, (z, stack)))) = S
+
expression = (z, (y, (x, expression)))
+
return stack, pushback(quote, expression), dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def app1(S, expression, dictionary):
+
'''
+
Given a quoted program on TOS and anything as the second stack item run
+
the program and replace the two args with the first result of the
+
program.
+
::
+
+
... x [Q] . app1
+
-----------------------------------
+
... [x ...] [Q] . infra first
+
'''
+
(quote, (x, stack)) = S
+
stack = (quote, ((x, stack), stack))
+
expression = (S_infra, (S_first, expression))
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def app2(S, expression, dictionary):
+
'''Like app1 with two items.
+
::
+
+
... y x [Q] . app2
+
-----------------------------------
+
... [y ...] [Q] . infra first
+
[x ...] [Q] infra first
+
+
'''
+
(quote, (x, (y, stack))) = S
+
expression = (S_infra, (S_first,
+
((x, stack), (quote, (S_infra, (S_first,
+
expression))))))
+
stack = (quote, ((y, stack), stack))
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def app3(S, expression, dictionary):
+
'''Like app1 with three items.
+
::
+
+
... z y x [Q] . app3
+
-----------------------------------
+
... [z ...] [Q] . infra first
+
[y ...] [Q] infra first
+
[x ...] [Q] infra first
+
+
'''
+
(quote, (x, (y, (z, stack)))) = S
+
expression = (S_infra, (S_first,
+
((y, stack), (quote, (S_infra, (S_first,
+
((x, stack), (quote, (S_infra, (S_first,
+
expression))))))))))
+
stack = (quote, ((z, stack), stack))
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def step(S, expression, dictionary):
+
'''
+
Run a quoted program on each item in a sequence.
+
::
+
+
... [] [Q] . step
+
-----------------------
+
... .
+
+
+
... [a] [Q] . step
+
------------------------
+
... a . Q
+
+
+
... [a b c] [Q] . step
+
----------------------------------------
+
... a . Q [b c] [Q] step
+
+
The step combinator executes the quotation on each member of the list
+
on top of the stack.
+
'''
+
(quote, (aggregate, stack)) = S
+
if not aggregate:
+
return stack, expression, dictionary
+
head, tail = aggregate
+
stack = quote, (head, stack)
+
if tail:
+
expression = tail, (quote, (S_step, expression))
+
expression = S_i, expression
+
return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def times(stack, expression, dictionary):
+
'''
+
times == [-- dip] cons [swap] infra [0 >] swap while pop
+
::
+
+
... n [Q] . times
+
--------------------- w/ n <= 0
+
... .
+
+
+
... 1 [Q] . times
+
---------------------------------
+
... . Q
+
+
+
... n [Q] . times
+
--------------------------------- w/ n > 1
+
... . Q (n - 1) [Q] times
+
+
'''
+
# times == [-- dip] cons [swap] infra [0 >] swap while pop
+
(quote, (n, stack)) = stack
+
if n <= 0:
+
return stack, expression, dictionary
+
n -= 1
+
if n:
+
expression = n, (quote, (S_times, expression))
+
expression = pushback(quote, expression)
+
return stack, expression, dictionary
+
+
+# The current definition above works like this:
+
+# [P] [Q] while
+# --------------------------------------
+# [P] nullary [Q [P] nullary] loop
+
+# while == [pop i not] [popop] [dudipd] primrec
+
+#def while_(S, expression, dictionary):
+# '''[if] [body] while'''
+# (body, (if_, stack)) = S
+# while joy(stack, if_, dictionary)[0][0]:
+# stack = joy(stack, body, dictionary)[0]
+# return stack, expression, dictionary
+
+
+[docs]@inscribe
+
@FunctionWrapper
+
def loop(stack, expression, dictionary):
+
'''
+
Basic loop combinator.
+
::
+
+
... True [Q] loop
+
-----------------------
+
... Q [Q] loop
+
+
... False [Q] loop
+
------------------------
+
...
+
+
'''
+
quote, (flag, stack) = stack
+
if flag:
+
expression = pushback(quote, (quote, (S_loop, expression)))
+
return stack, expression, dictionary
+
+
+#def nullary(S, expression, dictionary):
+# '''
+# Run the program on TOS and return its first result without consuming
+# any of the stack (except the program on TOS.)
+# '''
+# (quote, stack) = S
+# result = joy(stack, quote, dictionary)
+# return (result[0][0], stack), expression, dictionary
+#
+#
+#def unary(S, expression, dictionary):
+# (quote, stack) = S
+# _, return_stack = stack
+# result = joy(stack, quote, dictionary)[0]
+# return (result[0], return_stack), expression, dictionary
+#
+#
+#def binary(S, expression, dictionary):
+# (quote, stack) = S
+# _, (_, return_stack) = stack
+# result = joy(stack, quote, dictionary)[0]
+# return (result[0], return_stack), expression, dictionary
+#
+#
+#def ternary(S, expression, dictionary):
+# (quote, stack) = S
+# _, (_, (_, return_stack)) = stack
+# result = joy(stack, quote, dictionary)[0]
+# return (result[0], return_stack), expression, dictionary
+
+
+# FunctionWrapper(binary),
+# FunctionWrapper(cleave),
+# FunctionWrapper(nullary),
+# FunctionWrapper(ternary),
+# FunctionWrapper(unary),
+# FunctionWrapper(while_),
+
+
+for F in (
+ BinaryBuiltinWrapper(operator.add),
+ BinaryBuiltinWrapper(operator.and_),
+ BinaryBuiltinWrapper(operator.div),
+ BinaryBuiltinWrapper(operator.eq),
+ BinaryBuiltinWrapper(operator.floordiv),
+ BinaryBuiltinWrapper(operator.ge),
+ BinaryBuiltinWrapper(operator.gt),
+ BinaryBuiltinWrapper(operator.le),
+ BinaryBuiltinWrapper(operator.lshift),
+ BinaryBuiltinWrapper(operator.lt),
+ BinaryBuiltinWrapper(operator.mod),
+ BinaryBuiltinWrapper(operator.mul),
+ BinaryBuiltinWrapper(operator.ne),
+ BinaryBuiltinWrapper(operator.or_),
+ BinaryBuiltinWrapper(operator.pow),
+ BinaryBuiltinWrapper(operator.rshift),
+ BinaryBuiltinWrapper(operator.sub),
+ BinaryBuiltinWrapper(operator.truediv),
+ BinaryBuiltinWrapper(operator.xor),
+
+ UnaryBuiltinWrapper(abs),
+ UnaryBuiltinWrapper(bool),
+ UnaryBuiltinWrapper(floor),
+ UnaryBuiltinWrapper(operator.neg),
+ UnaryBuiltinWrapper(operator.not_),
+ UnaryBuiltinWrapper(sqrt),
+ ):
+ inscribe(F)
+del F # Otherwise Sphinx autodoc will pick it up.
+
+
+add_aliases(_dictionary, ALIASES)
+
+
+DefinitionWrapper.add_definitions(definitions, _dictionary)
+