UB is only disallowed in C, huh?
'0.<".@|>i\[email protected]'2#!>.(&,\,<.-
This works on x64, at least. Can't guarantee it'll work on other architectures.
Expects the number on STDIN, immediately followed by EOF (no trailing newline). Prints "0" (with trailing newline) iff the digits are unique.
Edit: there is now an online interpreter. This code seems to work in wasm, too (but I still haven’t tested native ARM or other processors).
High-level Overview
Roughly equivalent to this C code:
#include <stdio.h>
int main() {
// A bitfield of the seen numbers:
// e.g. if "12" is input, this is 0b0000'0000'0000'0110;
// if "3" is input, this is 0b0000'0000'0000'1000; etc.
int seen = 0;
while (1) {
// Get a number in. If EOF is read,
// we haven't encountered a duplicate, so indicate success.
int i = getchar();
if (i == EOF) {
puts("0");
break;
}
// Check if the digit has already been seen.
// If so, exit without printing anything.
// If not, add this digit to the bitfield.
i = 1 << (i - '0');
if (i & seen) break;
seen = seen | i;
}
}
How does Trilangle work?
Trilangle is a 2-D programming language laid out in a triangular grid (hence the name). The IP can move in one of six directions; it starts heading southwest. If it walks off the edge of the grid, it continues one row or diagonal to its left.
The memory model is like a stack, but it's possible to index into it.
Explanation of the Code
The interpreter unfolds that code into this triangular grid:
'
0 .
< " .
@ | > i
\ r e @ .
. j . . 2 '
2 # ! > . ( &
, \ , < . - . .
I hope this explanation makes any sense. Here goes nothing.
Below is an image of the code, with the IP's paths highlighted in various colors.
The IP starts on the red path, where it hits the following instructions:
'0
: Push the number 0 to the stack
<
: Change the direction of control flow
i
: Get a character from STDIN and push it to the stack; pushes -1 on EOF
>
: Branch on the sign of the top of the stack
If the value is negative, EOF was read, and the IP continues on the green path:
e
: Pop from the stack, and push 1 << value
. This is the UB: 1 << -1
is undefined. At least on x64, it evaluates to 0.
.
: No-op
!
: Print the top of the stack as a decimal integer
,
: Pop from the stack
i
: Attempt to read from STDIN again (always sees EOF on this path)
@
: End program
The ,i
aren't strictly necessary on this code path, but they're harmless to leave in.
If a character was successfully read, control flow continues on the blue path:
"0
: Push the value of the character '0' (i.e. 48) to the stack
-
: Pop two values from the stack and push their difference
..
: No-ops
e
: Pop from the stack and push 1 << value
|
: Redirect control flow
".,
: Push the value of the character '.' and immediately pop it, effectively a no-op
2
: Duplicate the top of the stack
.
: Another no-op
\
: Redirect control flow
'2
: Push the number 2 to the stack
..
: Even more no-ops
j
: The indexing function. After this, the stack contains { seen, i, i, seen }
(from the C code above), in that order.
.
: Yet another no-op
&
: Pop two values from the stack and push a & b
(bitwise and)
(
: Decrement the top of the stack. If seen & i
is 0, the top of the stack is now negative; otherwise, it's still positive
.
: Yep, still a no-op
>
: Branch
If i
and seen
have any bits in common, the IP follows the yellow path; otherwise, the IP follows the magenta path.
The yellow path contains a harmless r
(bitwise or) instruction, before the IP hits @
and the program ends.
On the magenta path, the IP continues as follows:
<
: Redirect control flow
,
: Pop from the stack
\
: Redirect control flow
#
: Ignore the next instruction (j
)
r
: Pop two values from the stack and push a | b
(bitwise or)
|
: Redirect control flow
<
: Redirect control flow, merging with the red path
From which it reads the next character from STDIN, and continues until it either reads the same character twice or reads EOF.
Disassembly
I recently added a --disassemble
flag to the interpreter. The output is fairly straightforward if you understand the syntax:
0.0: PSI #0
0.2: GTC
0.3: BNG 2.0
1.0: PSC '0' ; 0x30
1.1: SUB
1.4: EXP
1.6: PSC '.' ; 0x2e
1.7: POP
1.8: DUP
1.11: PSI #2
1.14: IDX
1.16: AND
1.17: DEC
1.19: BNG 4.0
JMP 3.0
2.0: EXP
2.2: PTI
2.3: POP
2.4: GTC
2.5: EXT
3.1: IOR
3.2: EXT
4.1: POP
4.4: IOR
4.7: JMP 0.2