INTEGER :: Izero = AND(BIT_SIZE(Izero), NOT(BIT_SIZE(Izero)))
CHARACTER(BIT_SIZE(Izero)) :: out
CHARACTER(BIT_SIZE(Izero)) :: fmt
I = IBSET(Izero, Izero)
I_ = SHIFTL(I, I)
I__ = SHIFTL(I_, I)
I___ = SHIFTL(I__, I)
I____ = SHIFTL(I___, I)
I_____ = SHIFTL(I____, I)
I______ = SHIFTL(I_____, I)
J=I
K=OR(I___,I_____)
fmt(J:J) = ACHAR(K)
J=I_
K=OR(I,I______)
fmt(J:J) = ACHAR(K)
J=OR(I,I_)
K=OR(OR(I,I____),I_____)
fmt(J:J) = ACHAR(K)
J=I__
K=OR(OR(I___,I____),I_____)
fmt(J:J) = ACHAR(K)
J=OR(I,I__)
K=OR(OR(I,I___),I_____)
fmt(J:J) = ACHAR(K)
J=I
K=OR(I____,I______)
out(J:J) = ACHAR(K)
J=I_
K=OR(OR(I____,I_____),I______)
out(J:J) = ACHAR(K)
J=OR(I,I_)
K=OR(OR(I,I____),I_____)
out(J:J) = ACHAR(K)
J=I__
K=OR(OR(I_,I____),I_____)
out(J:J) = ACHAR(K)
J=OR(I,I__)
K=OR(OR(OR(I,I_),I____),I_____)
out(J:J) = ACHAR(K)
J=OR(I_,I__)
K=OR(OR(I__,I____),I_____)
out(J:J) = ACHAR(K)
J=OR(OR(I,I_),I__)
K=OR(OR(OR(I,I__),I____),I_____)
out(J:J) = ACHAR(K)
J=I___
K=OR(OR(OR(I_,I__),I____),I_____)
out(J:J) = ACHAR(K)
J=OR(I,I___)
K=OR(OR(OR(OR(I,I_),I__),I____),I_____)
out(J:J) = ACHAR(K)
J=OR(I_,I___)
K=OR(OR(I___,I____),I_____)
out(J:J) = ACHAR(K)
J=OR(OR(I,I_),I___)
K=OR(OR(OR(I,I___),I____),I_____)
out(J:J) = ACHAR(K)
J=OR(I__,I___)
K=OR(I____,I_____)
out(J:J) = ACHAR(K)
J=OR(OR(I,I__),I___)
K=OR(OR(OR(I,I_),I__),I_____)
out(J:J) = ACHAR(K)
J=OR(OR(I_,I__),I___)
K=OR(I_,I_____)
out(J:J) = ACHAR(K)
J=OR(OR(OR(I,I_),I__),I___)
K=OR(OR(OR(I,I_),I___),I_____)
out(J:J) = ACHAR(K)
J=I____
K=OR(OR(OR(I,I__),I___),I_____)
out(J:J) = ACHAR(K)
J=OR(I,I____)
K=OR(OR(I_,I___),I_____)
out(J:J) = ACHAR(K)
J=OR(I_,I____)
K=OR(OR(OR(OR(I,I_),I__),I___),I_____)
out(J:J) = ACHAR(K)
WRITE(OR(I_,I__), fmt) out
END
Try it online!
Encoding integers without digits was tedious so I used a generator script.
#!/usr/bin/env perl
use warnings;
use strict;
my $output = 'Pp1234567890\'"+-*/';
# Izero = 0, I = 1, I_ = 2, I__ = 4, I___ = 8, ...
sub encode_integer {
my ($n) = @_;
if ($n == 0) {
return 'Izero';
}
my $k = 0;
while (($n & 1) == 0) {
$n >>= 1;
++$k;
}
my $expr = 'I' . '_' x $k;
while ($n != 0) {
$n >>= 1;
++$k;
if ($n & 1) {
$expr = sprintf('OR(%s,%s)', $expr, 'I' . '_' x $k);
}
}
return $expr;
}
sub set_char {
my ($variable, $pos, $num) = @_;
return <<EOF;
J=$pos
K=$num
$variable(J:J) = ACHAR(K)
EOF
}
sub build_string {
my ($variable, $string) = @_;
my @chars = split(//, $string);
my $result = '';
foreach my $i (1..@chars) {
$result .= set_char($variable, $i, ord($chars[$i-1]));
}
return $result;
}
my $header = <<'EOF';
INTEGER :: 0 = AND(BIT_SIZE(0), NOT(BIT_SIZE(0)))
CHARACTER(BIT_SIZE(0)) :: out
CHARACTER(BIT_SIZE(0)) :: fmt
1 = IBSET(0, 0)
2 = SHIFTL(1, 1)
4 = SHIFTL(2, 1)
8 = SHIFTL(4, 1)
16 = SHIFTL(8, 1)
32 = SHIFTL(16, 1)
64 = SHIFTL(32, 1)
EOF
my $footer = <<'EOF';
WRITE(6, fmt) out
END
EOF
my $code = ($header .
build_string('fmt', sprintf('(A%d)', length($output))) .
build_string('out', $output) .
$footer);
$code =~ s/[0-9]+/encode_integer($&)/eg;
$code =~ s/^/ /mg;
print $code;
The most basic way to print something in Fortran is PRINT
but that's forbidden. GFortran has a more convenient function FPUT
to print a character but it's also forbidden. We'll have to use WRITE
. Its syntax is WRITE(unit, format) data
where unit
designates the output file, format
describes how to format the output, and data
is the data to write. WRITE
adds a trailing newline so we'll have to produce the output in one go.
The unit can be either *
meaning the default output, or an expression that returns an integer. Since *
is forbidden, we need an integer. Standard output is unit 6.
The format can be either *
or some specifications in parentheses. It can be written literally in the source code or can come dynamically from a string varible. We'll need forbidden characters in the format so it'll have to be dynamic, in the variable fmt
. We want to print 18 characters so the format is (A18)
.
This leaves the problem of constructing strings. We can construct individual characters from their value with ACHAR
, and set the characters of a string variable one at a time. So all that remains is to construct integers.
Modern Fortran has bitwise operations on integers. I used SHIFTL
and OR
(IOR
in standard Fortran) to construct integers from 0 and 1. For 1, I used IBSET
. For 0, I used AND
of two integers. Finding an integer to start with took me a bit of time because I was looking for a compile-time constant, although that turned out to be unnecessary in the end; I could have used something like TIME()
. The expressions can get big, and classic Fortran (without the forbidden PROGRAM
has a line length limit, so I define constants for powers of 2 and intermediate variable while setting a character of a string).
To declare the strings, I needed to express their length (or more precisely their storage size which could be larger than the effective length) as a compile-time constant without using digits. I finally stumbled on BIT_SIZE
which is conveniently larger than the size I needed (32 >= 18, 6).