dc, 112 107 bytes
?[dz0r^+q]sZ[rd3Rd_3R^q]sE[ilfx1rq]sA[iSplfx1rLprq]sB[z2>Zz2=Ed1=Ard1=B1-rlfx3RSpr1-lfx_3Rri1+Lp1+r3R]dsfxp
Try it online!
Or verify all the test cases.
The input is on stdin (a line with space-separated numbers), and the output is on stdout.
How it works:
dc is a stack-based language. The recursive macro f
does the Conway chained-arrow calculation, but the stack is treated differently from what you usually see:
The input to f
is the entire stack when the call is made. (So f
essentially takes a variable number of arguments.)
If the stack at the time of call is
$$a_1 \; a_2 \; \dots \; a_n$$
(with the top of the stack on the right), f
will compute the value of
$$a_1 \to a_2 \to \dots \to a_n$$
and push it on top of the stack, but it leaves the arguments on the stack also.
So f
turns the stack
$$a_1 \; a_2 \; \dots \; a_n$$
into
$$a_1 \; a_2 \; \dots \; a_n \; [\text{ArrowValue}(a_1 \; a_2 \; \dots \; a_n)]$$
where I've written \$\;[\text{ArrowValue}(a_1 \; a_2 \; \dots \; a_n)]\;\$ for the value of \$\;a_1 \to a_2 \to \dots \to a_n.\$
There are several auxiliary macros as well. All the usual complex control structures other languages have (loops, conditionals, functions) are implemented in dc using macros.
Note that dc produces a few error messages or warnings due to the golfing tricks used, but they don't interrupt program execution, and the messages are just written to stderr. Examples of these: duplicating when there's nothing on the stack, adding when there's only one item on the stack, or setting the input base to an illegal value.
The code also makes use of the fact that we can distinguish positive numbers from \$0\$ by whether the power \$0^x\$ is \$0\$ or \$1.\$
Here's a detailed summary of the program's operation, updated for the revised, shorter answer.
? Read a line of space-separated numbers, written in the usual
Conway chained-arrow order, pushing them onto the stack in turn.
(The chained arrow sequence will start at the bottom of the stack,
since that's pushed first, and will end at the top of the stack, since
that's pushed last.)
MACRO Z
Macro Z will only be called when the stack either is empty or
has just one item p on it. We'll analyze both possibilities.
[ Start macro.
Stack: Empty or p
d Duplicate.
Stack: Empty or p p
z Push the size of the stack.
Stack: 0 or p p 2
0 Push 0.
Stack: 0 0 or p p 2 0
r
Swap.
Stack: 0 0 or p p 0 2
^ Exponentiate.
Stack: 1 or p p 0
+ Add top 2 items if they exist.
Stack: 1 or p p
q Exit this macro and the macro which called it.
]sZ End macro and name it Z.
Summary of Z:
Turn: Empty stack
Into: 1
and
Turn: p
into: p p
MACRO E
[ Start a macro. Assume the stack is: ... p q (top on right).
r Swap. Stack: ... q p
d Duplicate. Stack: ... q p p
3R Rotate left the top 3 items. Stack: ... p p q
d Duplicate. Stack: ... p p q q
_3R Rotate right the top 3 items. Stack: ... p q p q
^ Exponentiate. Stack: ... p q p**q
q Exit this macro and the macro which called it.
]sE End the macro and name it E.
Summary of E:
Turn: ... p q
into: ... p q p**q
MACRO A
[ Start a macro. Assume the stack is: ... p (top on right).
i Discard the top of stack. (Actually make it the new input radix just because dc wants a place to put it.)
Stack: ...
lfx Call f recursively. Stack: ... ArrowValue(...)
1 Push 1. Stack: ... ArrowValue(...) 1
r Swap. Stack: ... 1 ArrowValue(...)
q Exit this macro and the macro which called it.
]sA End the macro and name it A.
Summary of A:
Turn: ... p
into: ... 1 ArrowValue(...)
MACRO B
[ Start a macro. Assume the stack is: ... p q (top on right).
i Discard top of stack (by storing it as the input radix).
Stack: ... p
Sp Pop p off the stack and
push it onto stack p. Stack: ...
lfx Call f recursively. Stack: ... ArrowValue(...)
1 Push 1. Stack: ... ArrowValue(...) 1
r Swap. Stack: ... 1 ArrowValue(...)
Lp Pop the old value of p from stack p.
Stack: ... 1 ArrowValue(...) p
r Swap Stack: ... 1 p ArrowValue(...)
q Exit this macro and the macro which called it.
]sB End the macro and name it B.
Summary of B:
Turn: ... p q
into: ... 1 p ArrowValue(...)
MACRO f
[ Start a macro.
z Push the stack size.
2> If the stack size was 0 or 1,
O then call macro Z and return from f.
In this case, we've turned ...
into ... 1
or we've turned ... p
into ... p p
z2=E If the stack size was 2,
then call macro E and return from f.
In this case, we've turned ... p q
into ... p q p**q
If we get here, the stack size is at least 3.
d1=A If the item at the top of the stack == 1,
then call macro A and return from f.
In this case, we've turned ... 1
into ... 1 ArrowValue(...)
If we get here, the stack size is at least 3 and the item at the top of the stack isn't 1.
Stack: ... p q r
where r != 1.
r Swap. Stack: ... p r q
d1=B If the item at the top of the stack == 1,
then call macro B and return from f.
In this case, we've turned ... p 1 r
into ... p 1 r ArrowValue(... p)
If we get here, the stack size is at least 3, neither of the items at the top of the stack is 1,
and we've already gone from
Stack: ... p q r
to Stack: ... p r q
1- Subtract 1. Stack: ... p r q-1
r Swap. Stack: ... p q-1 r
lfx Call f recursively. Stack: ... p q-1 r [ArrowValue(... p q-1 r)]
3R Rotate left the top 3 items on the stack.
Stack: ... p r [ArrowValue(... p q-1 r)] q-1
Sp Pop q-1 off the stack and push it onto stack p.
Stack: ... p r [ArrowValue(... p q-1 r)]
r Swap. Stack: ... p [ArrowValue(... p q-1 r)] r
1- Subtract 1. Stack: ... p [ArrowValue(... p q-1 r)] r-1
lfx Call f recursively. Stack: ... p [ArrowValue(... p q-1 r)] r-1 [ArrowValue(... p ArrowValue(... p q-1 r) r-1)]
_3R Rotate right the top 3 items on the stack.
Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] [ArrowValue(... p q-1 r)] r-1
r Swap: Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] r-1 [ArrowValue(... p q-1 r)]
i Discard the top item. Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] r-1
1+ Add 1 Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] r
Lp Load the old value of q-1 from stack p.
Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] r q-1
1+ Add 1. Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] r q
r Swap. Stack: ... p [ArrowValue(... p ArrowValue(... p q-1 r) r-1)] q r
3R Rotate left the top 3 items on the stack.
Stack: ... p q r [ArrowValue(... p ArrowValue(... p q-1 r) r-1)]
] End the macro,
dsf save it on the stack, and name it f.
Summary of f:
Turn: ...
into: ... ArrowValue(...)
x Execute f.
p Print the desired value, which is now at the top of the stack.