diff --git a/implementations/Python/align_trace.py b/implementations/Python/align_trace.py new file mode 100644 index 0000000..51dd0a5 --- /dev/null +++ b/implementations/Python/align_trace.py @@ -0,0 +1,14 @@ +import sys + +SEP = '•' + +lines = sys.stdin.readlines() + +indicies = [line.index(SEP) for line in lines if SEP in line] + +MAX = max(indicies) + +prefix_counts = [MAX - i for i in indicies] + +for count, line in zip(prefix_counts, lines): + print(' ' * count, line, sep='', end = '') diff --git a/implementations/Python/joy/library.py b/implementations/Python/joy/library.py index 6cd5e0c..6dd67d1 100644 --- a/implementations/Python/joy/library.py +++ b/implementations/Python/joy/library.py @@ -7,322 +7,6 @@ ALIASES = ( ('id', [u'•']), ) -@inscribe -@FunctionWrapper -def inscribe_(stack, expression, dictionary): - ''' - Create a new Joy function definition in the Joy dictionary. A - definition is given as a quote with a name followed by a Joy - expression. for example: - - [sqr dup mul] inscribe - - ''' - (name, body), stack = stack - inscribe(Def(name, body), dictionary) - return stack, expression, dictionary - - -@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 - - -@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 - - -@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 - - -@inscribe -@FunctionWrapper -def gcd2(stack, expression, dictionary): - '''Compiled GCD function.''' - (v1, (v2, stack)) = stack - tos = True - while tos: - v3 = v2 % v1 - tos = v3 > 0 - (v1, (v2, stack)) = (v3, (v1, stack)) - return (v2, stack), expression, dictionary - - -@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 - - ''' - (if_, (then, (else_, stack))) = stack - assert isinstance(if_, bool), repr(if_) - return then if if_ else else_, stack - - -@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 - - -@inscribe -@SimpleFunctionWrapper -def max_(S): - '''Given a list find the maximum.''' - tos, stack = S - return max(iter_stack(tos)), stack - - -@inscribe -@SimpleFunctionWrapper -def min_(S): - '''Given a list find the minimum.''' - tos, stack = S - return min(iter_stack(tos)), stack - - -@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 - - -@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. If the list is - empty or the item isn't in the list then the list is unchanged. - :: - - [1 2 3 1] 1 remove - ------------------------ - [2 3 1] - - ''' - (item, (quote, stack)) = S - return _remove(item, quote), stack - - -def _remove(item, quote): - try: head, tail = quote - except ValueError: return quote - return tail if head == item else (head, _remove(item, tail)) - - -@inscribe -@SimpleFunctionWrapper -def unique(S): - '''Given a list remove duplicate items.''' - tos, stack = S - I = list(iter_stack(tos)) - return list_to_stack(sorted(set(I), key=I.index)), stack - - -@inscribe -@SimpleFunctionWrapper -def sort_(S): - '''Given a list return it sorted.''' - tos, stack = S - return list_to_stack(sorted(iter_stack(tos))), stack - - -@inscribe -@SimpleFunctionWrapper -def disenstacken(stack): - ''' - The disenstacken operator expects a list on top of the stack and makes that - the stack discarding the rest of the stack. - ''' - return stack[0] - - -@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 - - -@inscribe -@SimpleFunctionWrapper -def shunt(stack): - ''' - Like concat but reverses the top list into the second. - :: - - shunt == [swons] step == reverse swap concat - - [a b c] [d e f] shunt - --------------------------- - [f e d a b c] - - ''' - (tos, (second, stack)) = stack - while tos: - term, tos = tos - second = term, second - return second, stack - - -@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 - - -@inscribe -@SimpleFunctionWrapper -def succ(S): - '''Increment TOS.''' - (tos, stack) = S - return tos + 1, stack - - -@inscribe -@SimpleFunctionWrapper -def pred(S): - '''Decrement TOS.''' - (tos, stack) = S - return tos - 1, stack - - -@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) - - def floor(n): return int(math.floor(n)) @@ -350,39 +34,6 @@ def id_(stack): -@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 .') - return stack, expression, dictionary - - -@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 - - # # § Combinators # @@ -406,194 +57,9 @@ S_swaack = Symbol('swaack') S_times = Symbol('times') -@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 - - ''' - try: - quote, stack = stack - except ValueError: - raise StackUnderflowError('Not enough values on stack.') - return stack, concat(quote, expression), dictionary -@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, concat(quote, expression), dictionary - - -@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, concat(p, concat(q, expression)), dictionary - - -@inscribe -@FunctionWrapper -def ii(stack, expression, dictionary): - ''' - :: - - ... a [Q] ii - ------------------ - ... Q a Q - - ''' - quote, (a, stack) = stack - expression = concat(quote, (a, concat(quote, expression))) - return stack, expression, dictionary - - -@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, concat(F, (a, expression)), dictionary - - -@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. Does not affect the rest of the stack. - :: - - ... [a b c] [Q] . infra - ----------------------------- - c b a . Q [...] swaack - - ''' - (quote, (aggregate, stack)) = stack - return aggregate, concat(quote, (stack, (S_swaack, expression))), dictionary - - -@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] tailrec - == [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_ = concat(rec1, (F, rec2)) - return (else_, stack), (S_ifte, expression), dictionary - - -@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 @inscribe diff --git a/implementations/Python/simplejoy.py b/implementations/Python/simplejoy.py index 8da0c98..ebba95a 100755 --- a/implementations/Python/simplejoy.py +++ b/implementations/Python/simplejoy.py @@ -307,6 +307,9 @@ def joy(stack, expression, dictionary): ''' expr = push_quote(expression) # We keep a stack-of-stacks, see below. while expr: + print( + f'{stack_to_string(stack)} • {expr_to_string(expr)}' + ) term, expr = next_term(expr) if isinstance(term, Symbol): try: @@ -625,6 +628,13 @@ def expression_to_string(expression): return _stack_to_string(expression, iter_stack) +def expr_to_string(expr): + ''' + Return a "pretty print" string for a stack-of-stacks expression. + ''' + return ' '.join(map(expression_to_string, iter_stack(expr))) + + def _stack_to_string(stack, iterator): isnt_stack(stack) if not stack: # shortcut @@ -1329,13 +1339,675 @@ inscribe(UnaryWrapper(isnt_bool)) inscribe(UnaryWrapper(isnt_stack)) +''' +███████╗██╗ ██╗████████╗██████╗ █████╗ +██╔════╝╚██╗██╔╝╚══██╔══╝██╔══██╗██╔══██╗ +█████╗ ╚███╔╝ ██║ ██████╔╝███████║ +██╔══╝ ██╔██╗ ██║ ██╔══██╗██╔══██║ +███████╗██╔╝ ██╗ ██║ ██║ ██║██║ ██║ +╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝ +''' + + +def dnd(stack, from_index, to_index): + ''' + Given a stack and two indices return a rearranged stack. + First remove the item at from_index and then insert it at to_index, + the second index is relative to the stack after removal of the item + at from_index. + + This function reuses all of the items and as much of the stack as it + can. It's meant to be used by remote clients to support drag-n-drop + rearranging of the stack from e.g. the StackListbox. + ''' + assert 0 <= from_index + assert 0 <= to_index + if from_index == to_index: + return stack + head, n = [], from_index + while True: + item, stack = stack + n -= 1 + if n < 0: + break + head.append(item) + assert len(head) == from_index + # now we have two cases: + diff = from_index - to_index + if diff < 0: + # from < to + # so the destination index is still in the stack + while diff: + h, stack = stack + head.append(h) + diff += 1 + else: + # from > to + # so the destination is in the head list + while diff: + stack = head.pop(), stack + diff -= 1 + stack = item, stack + while head: + stack = head.pop(), stack + return stack + + +def pick(stack, n): + ''' + Return the nth item on the stack. + + :param stack stack: A stack. + :param int n: An index into the stack. + :raises ValueError: if ``n`` is less than zero. + :raises IndexError: if ``n`` is equal to or greater than the length of ``stack``. + :rtype: whatever + ''' + if n < 0: + raise ValueError + while True: + try: + item, stack = stack + except ValueError: + raise IndexError + n -= 1 + if n < 0: + break + return item + + +@inscribe +def inscribe_(stack, expression, dictionary): + ''' + Create a new Joy function definition in the Joy dictionary. A + definition is given as a quote with a name followed by a Joy + expression. for example: + + [sqr dup mul] inscribe + + ''' + (name, body), stack = stack + inscribe(Def(name, body), dictionary) + return stack, expression, dictionary + + +@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 + + +@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 StackUnderflowError + n -= 1 + return Q, stack + + +@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 StackUnderflowError + x = item, x + n -= 1 + return x, stack + + +@inscribe +def gcd2(stack, expression, dictionary): + '''Compiled GCD function.''' + (v1, (v2, stack)) = stack + tos = True + while tos: + v3 = v2 % v1 + tos = v3 > 0 + (v1, (v2, stack)) = (v3, (v1, stack)) + return (v2, stack), expression, dictionary + + +@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 + + ''' + (if_, (then, (else_, stack))) = stack + assert isinstance(if_, bool), repr(if_) + return then if if_ else else_, stack + + +@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 + + +@inscribe +@SimpleFunctionWrapper +def max_(S): + '''Given a list find the maximum.''' + tos, stack = S + return max(iter_stack(tos)), stack + + +@inscribe +@SimpleFunctionWrapper +def min_(S): + '''Given a list find the minimum.''' + tos, stack = S + return min(iter_stack(tos)), stack + + +@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 + + +@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. If the list is + empty or the item isn't in the list then the list is unchanged. + :: + + [1 2 3 1] 1 remove + ------------------------ + [2 3 1] + + ''' + (item, (quote, stack)) = S + return _remove(item, quote), stack + + +def _remove(item, quote): + try: head, tail = quote + except ValueError: return quote + return tail if head == item else (head, _remove(item, tail)) + + +@inscribe +@SimpleFunctionWrapper +def unique(S): + '''Given a list remove duplicate items.''' + tos, stack = S + I = list(iter_stack(tos)) + return list_to_stack(sorted(set(I), key=I.index)), stack + + +@inscribe +@SimpleFunctionWrapper +def sort_(S): + '''Given a list return it sorted.''' + tos, stack = S + return list_to_stack(sorted(iter_stack(tos))), stack + + +@inscribe +@SimpleFunctionWrapper +def disenstacken(stack): + ''' + The disenstacken operator expects a list on top of the stack and makes that + the stack discarding the rest of the stack. + ''' + return stack[0] + + +@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 + + +@inscribe +@SimpleFunctionWrapper +def shunt(stack): + ''' + Like concat but reverses the top list into the second. + :: + + shunt == [swons] step == reverse swap concat + + [a b c] [d e f] shunt + --------------------------- + [f e d a b c] + + ''' + (tos, (second, stack)) = stack + while tos: + term, tos = tos + second = term, second + return second, stack + + +@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 + + +@inscribe +@SimpleFunctionWrapper +def succ(S): + '''Increment TOS.''' + (tos, stack) = S + return tos + 1, stack + + +@inscribe +@SimpleFunctionWrapper +def pred(S): + '''Decrement TOS.''' + (tos, stack) = S + return tos - 1, stack + + +@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) + + +@inscribe +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 .') + return stack, expression, dictionary + + +@inscribe +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 + + +@inscribe +def x(stack, expr, dictionary): + ''' + :: + + x == dup i + + ... [Q] x = ... [Q] dup i + ... [Q] x = ... [Q] [Q] i + ... [Q] x = ... [Q] Q + + ''' + quote, _ = stack + isnt_stack(quote) + return stack, push_quote(quote, expr), dictionary + + +@inscribe +def b(stack, expr, dictionary): + ''' + :: + + b == [i] dip i + + ... [P] [Q] b == ... [P] i [Q] i + ... [P] [Q] b == ... P Q + + ''' + q, (p, (stack)) = stack + isnt_stack(q) + isnt_stack(p) + expr = push_quote(q, expr) + expr = push_quote(p, expr) + return stack, expr, dictionary + + +@inscribe +def ii(stack, expr, dictionary): + ''' + :: + + ... a [Q] ii + ------------------ + ... Q a Q + + ''' + quote, (a, stack) = stack + isnt_stack(quote) + expr = push_quote((a, quote), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +@inscribe +def dupdip(stack, expr, dictionary): + ''' + :: + + [F] dupdip == dup [F] dip + + ... a [F] dupdip + ... a dup [F] dip + ... a a [F] dip + ... a F a + + ''' + quote, stack = stack + isnt_stack(quote) + a = stack[0] + expr = push_quote((a, ()), expr) + expr = push_quote(quote, expr) + return stack, expr, dictionary + + +S_swaack = Symbol('swaack') + + +@inscribe +def infra(stack, expr, dictionary): + ''' + Accept a quoted program and a list on the stack and run the program + with the list as its stack. Does not affect the rest of the stack. + :: + + ... [a b c] [Q] . infra + ----------------------------- + c b a . Q [...] swaack + + ''' + quote, aggregate, stack = get_n_items(2, stack) + isnt_stack(quote) + isnt_stack(aggregate) + expr = push_quote((stack, (S_swaack, ())), expr) + expr = push_quote(quote, expr) + return aggregate, expr, dictionary + + +S_genrec = Symbol('genrec') +S_ifte = Symbol('ifte') + + +@inscribe +def genrec(stack, expr, 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] tailrec + == [I] [T] [R [P] i] ifte + == [I] [T] [R P] ifte + + ''' + rec2, rec1, then, if_, stack = get_n_items(4, stack) + isnt_stack(if_) + isnt_stack(then) + isnt_stack(rec1) + isnt_stack(rec2) + F = (if_, (then, (rec1, (rec2, (S_genrec, ()))))) + else_ = concat(rec1, (F, rec2)) + stack = (else_, (then, (if_, stack))) + expr = push_quote((S_ifte, ()), expr) + return stack, expr, dictionary + + +S_infra = Symbol('infra') +S_first = Symbol('first') + + +@inscribe +def map_(stack, expr, 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 = get_n_items(2, stack) + isnt_stack(quote) + isnt_stack(aggregate) + if not aggregate: + return (aggregate, stack), expr, dictionary + batch = () + for term in iter_stack(aggregate): + s = term, stack + batch = (s, (quote, (S_infra, (S_first, batch)))) + stack = (batch, ((), stack)) + expr = push_quote((S_infra, ()), expr) + return stack, expr, dictionary + + +S_primrec = Symbol('primrec') + + +@inscribe +def primrec(stack, expr, dictionary): + ''' + From the "Overview of the language JOY": + + > The primrec combinator expects two quoted programs in addition to a + data parameter. For an integer data parameter it works like this: If + the data parameter is zero, then the first quotation has to produce + the value to be returned. If the data parameter is positive then the + second has to combine the data parameter with the result of applying + the function to its predecessor.:: + + 5 [1] [*] primrec + + > Then primrec tests whether the top element on the stack (initially + the 5) is equal to zero. If it is, it pops it off and executes one of + the quotations, the [1] which leaves 1 on the stack as the result. + Otherwise it pushes a decremented copy of the top element and + recurses. On the way back from the recursion it uses the other + quotation, [*], to multiply what is now a factorial on top of the + stack by the second element on the stack.:: + + n [Base] [Recur] primrec + + 0 [Base] [Recur] primrec + ------------------------------ + Base + + n [Base] [Recur] primrec + ------------------------------------------ n > 0 + n (n-1) [Base] [Recur] primrec Recur + + ''' + recur, base, n, stack = get_n_items(3, stack) + isnt_stack(recur) + isnt_stack(base) + if n <= 0: + expr = push_quote(base, expr) + else: + expr = push_quote(recur, expr) + expr = push_quote((S_primrec, ()), expr) + stack = recur, (base, (n - 1, (n, stack))) + return stack, expr, dictionary + + if __name__ == '__main__': import sys J = interp if '-q' in sys.argv else repl dictionary = initialize() Def.load_definitions(__doc__.splitlines(), dictionary) - try: - stack = J(dictionary=dictionary) - except SystemExit: - pass +## try: +## stack = J(dictionary=dictionary) +## except SystemExit: +## pass + stack, _ = run("5 [1] [*] primrec", (), dictionary) + print(stack_to_string(stack), '•')