Merge pull request #1672 from xexyl/burton2015
Add 2015/burton/try.sh + format/typo checks
This commit is contained in:
8 files changed
+252
-179
No files matched your search
@@ -7,3 +7,4 @@ indent.o
|
||||
prog
|
||||
prog.alt
|
||||
prog.orig
|
||||
rule2
|
||||
+43
-39
@@ -23,42 +23,40 @@ Type in expressions on standard input.
|
||||
### Try:
|
||||
|
||||
```sh
|
||||
./prog '?0x3343<<2'
|
||||
./try.sh
|
||||
```
|
||||
|
||||
echo '?0x3343<<2' | ./prog
|
||||
Notice how the script reads in from a file. If you type just:
|
||||
|
||||
```sh
|
||||
./prog
|
||||
```
|
||||
|
||||
Enter:
|
||||
it will prompt you in the same way. Send ctrl-c to exit.
|
||||
|
||||
|
||||
Finally, if you wish to read the man page:
|
||||
|
||||
```sh
|
||||
a=0xfeedface
|
||||
b=0xcafe
|
||||
a << 16 + b
|
||||
```
|
||||
|
||||
```sh
|
||||
grep '[0-9]' prog.c
|
||||
|
||||
./prog < test.in
|
||||
|
||||
man ./calc.1
|
||||
```
|
||||
|
||||
|
||||
## Alternate code:
|
||||
|
||||
An alternate version of this entry, `prog.alt.c`, is provided.
|
||||
An alternate version of this entry, [prog.alt.c](prog.alt.c), is provided.
|
||||
This alternate code is discussed in the "prog.c vs prog.alt.c" section of the
|
||||
author's remarks below.
|
||||
|
||||
To compile this alternate version:
|
||||
|
||||
### Alternate build:
|
||||
|
||||
|
||||
```sh
|
||||
make alt
|
||||
```
|
||||
|
||||
### Alternate use:
|
||||
|
||||
Use `prog.alt` as you would `prog` above.
|
||||
|
||||
|
||||
@@ -75,17 +73,19 @@ process.
|
||||
|
||||
## Author's remarks:
|
||||
|
||||
|
||||
### calc - an integer expression calculator that outputs in both hex and decimal
|
||||
|
||||
All useful tools should include sufficient documentation.
|
||||
See the included man page. ;-)
|
||||
|
||||
|
||||
### Notes
|
||||
|
||||
This program is named calc. Onomatopoeia of sorts, in the visual realm. The
|
||||
This program is named `calc`. Onomatopoeia of sorts, in the visual realm. The
|
||||
formatting was easier: Form Follows Function (although [Hou][1] did this
|
||||
better). It was tempting to call it iCalc, with i for integer (and it was
|
||||
written on an iDevice), but sanity prevailed.
|
||||
better). It was tempting to call it iCalc, with `i` for integer (and it was
|
||||
written on an `iDevice`), but sanity prevailed.
|
||||
|
||||
Printed in 8 pt Courier, it makes aesthetically pleasing 8.5 x 11 wall art.
|
||||
|
||||
@@ -93,14 +93,15 @@ Printed in 8 pt Courier, it makes aesthetically pleasing 8.5 x 11 wall art.
|
||||
|
||||
### prog.c vs prog.alt.c
|
||||
|
||||
Dominik Muth observed a syntax error on `1+1`, `1-1`, `1|1`, and `1^1` when
|
||||
using an ARM based computer, although `1*1` and others worked correctly. This
|
||||
occurs because some platforms by default treat `char` as `unsigned char`. The
|
||||
original code has this weakness, which can be corrected by adding
|
||||
`-fsigned-char` to the compile line.
|
||||
[Dominik Muth](/winners.html#Dominik_Muth) observed a syntax error on `1+1`,
|
||||
`1-1`, `1|1`, and `1^1` when using an ARM based computer, although `1*1` and
|
||||
others worked correctly. This occurs because some platforms by default treat
|
||||
`char` as `unsigned char`. The original code has this weakness, which can be
|
||||
corrected by adding `-fsigned-char` to the compile line.
|
||||
|
||||
The modified code is agnostic to `char` signedness.
|
||||
|
||||
|
||||
### Why is this entry obfuscated/interesting?
|
||||
|
||||
Yes, this is another calculator program.
|
||||
@@ -117,16 +118,17 @@ is only one single, short string, and it does not contain any obfuscated digits.
|
||||
And yet the output is in decimal and hexadecimal, and it accepts octal, decimal,
|
||||
and hex input.
|
||||
|
||||
Flow control is by `for` and `return`. It is if-less, switch-less,
|
||||
while-less, and hopeless. Literally, if not figuratively. ;-)
|
||||
Flow control is by `for` and `return`. It is `if`-less, `switch`-less,
|
||||
`while`-less, and hopeless. Literally, if not figuratively. ;-)
|
||||
|
||||
The code size is intentionally 2015, as stated by iocccsize -i, and can be
|
||||
reformatted without changes (as in rule2.c) to exactly 4096 octets.
|
||||
The code size is intentionally 2015, as stated by `iocccsize -i`, and can be
|
||||
reformatted without changes (as in [rule2.c](rule2.c)) to exactly 4096 octets.
|
||||
|
||||
Finally, calc is interesting, if for no other reason than it is practically
|
||||
Finally, `calc` is interesting, if for no other reason than it is practically
|
||||
useful: Hex/decimal conversion, arithmetic, byte-swapping, bit-shifting, and
|
||||
logical operations with memory, on the command line, or interactively.
|
||||
|
||||
|
||||
### But the question is why is the entry obfuscated?
|
||||
|
||||
The code follows many industry best-practices:
|
||||
@@ -161,8 +163,8 @@ and `long`; 10 if `#include` and `#define` are counted.
|
||||
- The code helpfully declares `"syntax_error!"` right up front, filling the
|
||||
reader with confidence.
|
||||
|
||||
- In spite of its inherent clarity, the code was presented to both *indent*(1)
|
||||
and *clang-format*(1), after running it through the preprocessor, sustaining its
|
||||
- In spite of its inherent clarity, the code was presented to both `indent(1)`
|
||||
and `clang-format(1)`, after running it through the preprocessor, sustaining its
|
||||
better qualities.
|
||||
|
||||
- That the code is obfuscated is spelled out two different ways in the source;
|
||||
@@ -170,20 +172,21 @@ it helps to reformat the code to see the second spelling.
|
||||
|
||||
- The author is overly fond of the code that handles input. There is no memory
|
||||
allocation, nor pre-allocation of arrays, so where does the memory come from?
|
||||
The multi-base strtol() code is pretty tricky. No special attempt was made to
|
||||
The multi-base `strtol(3)` code is pretty tricky. No special attempt was made to
|
||||
replace operations with obscure mathematical relations, as the (lack of) beauty
|
||||
is in the parsing -- ask not where is the modulus operation, but how does it
|
||||
know when to mod -- okay, but where is add and subtract? And really, who
|
||||
doesn't enjoy a setjmp() in the argument list?
|
||||
doesn't enjoy a `setjmp(3)` in the argument list?
|
||||
|
||||
- Finally, documentation of the source code structure is helpfully provided in
|
||||
rot13 format. But isn't it unsportsmanlike to rely upon it? However, even with
|
||||
this helpful guide, things like reading input lines remain as tricky to read as
|
||||
they are to write.
|
||||
|
||||
|
||||
### Compiler warnings
|
||||
|
||||
Calc is C99 compliant, and is ANSI/C90 compliant except for wanting 64-bit `long
|
||||
`calc` is C99 compliant, and is ANSI/C90 compliant except for wanting 64-bit `long
|
||||
long`. Changing the two `long long` typedefs to `long`, and compiling with `gcc
|
||||
-m32` yields a program that operates on 32-bit values, with wrap-around of
|
||||
shifts mod 32:
|
||||
@@ -193,8 +196,8 @@ shifts mod 32:
|
||||
65536 0x00010000
|
||||
```
|
||||
|
||||
gcc 4.2.1 without arguments compiles cleanly (e.g. cc prog.c -o prog).
|
||||
This is my workaday compiler. gcc is also clean with -ansi, and -std=c99.
|
||||
gcc 4.2.1 without arguments compiles cleanly (e.g. `cc prog.c -o prog`).
|
||||
This is my workaday compiler. gcc is also clean with `-ansi`, and `-std=c99`.
|
||||
|
||||
clang 3.6.0 lives up to its namesake, resounding on non-problems.
|
||||
To stifle these complaints:
|
||||
@@ -203,19 +206,20 @@ To stifle these complaints:
|
||||
-Wno-return-type -Wno-parentheses -Wno-empty-body
|
||||
```
|
||||
|
||||
When invoked with -ansi -pedantic, there are two warnings, which can be ignored:
|
||||
When invoked with `-ansi -pedantic`, there are two warnings, which can be ignored:
|
||||
|
||||
* `ISO C90 does not support 'long long'`
|
||||
|
||||
With -Wall -std=c99, both are quite noisy, and all can be ignored:
|
||||
With `-Wall -std=c99`, both are quite noisy, and all can be ignored:
|
||||
|
||||
* `array subscript has type 'char' [-Wchar-subscripts]`
|
||||
* `value computed is not used [-Wunused-value]`
|
||||
* `operation on 'j' may be undefined [-Wsequence-point]`
|
||||
|
||||
|
||||
### Test Suite:
|
||||
|
||||
If the program name begins with an "e", it echoes stdin to stdout.
|
||||
If the program name begins with an `e`, it echoes `stdin` to `stdout`.
|
||||
This allows for a convenient test suite:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
Development of the obfuscated calculator.
|
||||
--------------------------------------------------
|
||||
# Development of the obfuscated calculator.
|
||||
|
||||
I wanted to enter the IOCCC....
|
||||
|
||||
@@ -8,83 +7,86 @@ is fierce, but not a closed door.
|
||||
|
||||
I needed an interesting program, that had a reasonable chance of being shrunk
|
||||
within 4096 bytes, and lent itself to obfuscation. I had been revisiting
|
||||
utilities I've written over the years. One useful program was calc, a simple
|
||||
utilities I've written over the years. One useful program was `calc`, a simple
|
||||
recursive descent signed integer expression parser I developed from first
|
||||
principles as a demonstration of how computers work for my son. That program
|
||||
did the basic four operations, but as a programmer, I needed more, so those
|
||||
were added over time.
|
||||
|
||||
Calc is a useful, functioning utility. The operators before the obfuscation
|
||||
dance began (this is called fore-shadowing):
|
||||
`calc` is a useful, functioning utility. The operators before the obfuscation
|
||||
dance began (this is called foreshadowing):
|
||||
|
||||
() forced precedence
|
||||
- ~ ? unary minus, compl, byteswap
|
||||
* / % & << >> mul, div, mod, bitand, shift: left, right
|
||||
+ - | ^ add, sub, bitor, xor
|
||||
NUM 0-9
|
||||
```
|
||||
() forced precedence
|
||||
- ~ ? unary minus, compl, byteswap
|
||||
* / % & << >> mul, div, mod, bitand, shift: left, right
|
||||
+ - | ^ add, sub, bitor, xor
|
||||
NUM 0-9
|
||||
```
|
||||
|
||||
This seemed like a reasonable candidate to experiment with obfuscation; perhaps
|
||||
it might yield good results.
|
||||
|
||||
Principle of operation
|
||||
--------------------------------------------------
|
||||
|
||||
## Principle of operation
|
||||
|
||||
The algorithm was and is rather straightforward:
|
||||
|
||||
expr() {
|
||||
n = term()
|
||||
t = token()
|
||||
if (t == ADD) return n += expr()
|
||||
if (t == SUB) return n -= expr()
|
||||
if (t == BOR) return n |= expr()
|
||||
if (t == XOR) return n ^= expr()
|
||||
ungettoken(t)
|
||||
return n
|
||||
}
|
||||
term() {
|
||||
n = factor()
|
||||
t = token()
|
||||
if (t == MUL) return n *= term()
|
||||
if (t == DIV) return n /= term()
|
||||
if (t == AND) return n &= term()
|
||||
if (t == RSH) return n >> term()
|
||||
if (t == LSH) return n << term()
|
||||
ungettoken(t)
|
||||
return n
|
||||
}
|
||||
factor() {
|
||||
t = token()
|
||||
if (t == LPAREN) {
|
||||
n = expr()
|
||||
if token() == RPAREN
|
||||
return n
|
||||
error()
|
||||
}
|
||||
if (t == SUB) return -factor()
|
||||
if (t == CMP) return ~factor()
|
||||
if (t == QUE) return byteswap(factor())
|
||||
if (t == DIG) return strtol()
|
||||
```
|
||||
expr() {
|
||||
n = term()
|
||||
t = token()
|
||||
if (t == ADD) return n += expr()
|
||||
if (t == SUB) return n -= expr()
|
||||
if (t == BOR) return n |= expr()
|
||||
if (t == XOR) return n ^= expr()
|
||||
ungettoken(t)
|
||||
return n
|
||||
}
|
||||
term() {
|
||||
n = factor()
|
||||
t = token()
|
||||
if (t == MUL) return n *= term()
|
||||
if (t == DIV) return n /= term()
|
||||
if (t == AND) return n &= term()
|
||||
if (t == RSH) return n >> term()
|
||||
if (t == LSH) return n << term()
|
||||
ungettoken(t)
|
||||
return n
|
||||
}
|
||||
factor() {
|
||||
t = token()
|
||||
if (t == LPAREN) {
|
||||
n = expr()
|
||||
if token() == RPAREN
|
||||
return n
|
||||
error()
|
||||
}
|
||||
if (t == SUB) return -factor()
|
||||
if (t == CMP) return ~factor()
|
||||
if (t == QUE) return byteswap(factor())
|
||||
if (t == DIG) return strtol()
|
||||
error()
|
||||
}
|
||||
```
|
||||
|
||||
Obfuscation is a process
|
||||
--------------------------------------------------
|
||||
## Obfuscation is a process
|
||||
|
||||
Within the above algorithm, there are many details unmentioned, all ripe for
|
||||
obfuscation:
|
||||
|
||||
- input
|
||||
- tokenization
|
||||
- atoi input values
|
||||
- sprintf results
|
||||
- `atoi(3)` input values
|
||||
- `sprintf(3)` results
|
||||
- error recovery
|
||||
- swapping bytes
|
||||
- user interface (command line vs stdin)
|
||||
- user interface (command line vs `stdin`)
|
||||
|
||||
Uncertain as to how this would progress, I started obfuscating/condensing
|
||||
everything except the conversion operations, leaving those to sprintf() and
|
||||
strtol(). The first version of the obfuscated code used an homage to 1984 anon
|
||||
for I/O, and it featured a #define for else's and the types, to help minimize
|
||||
everything except the conversion operations, leaving those to `sprintf(3)` and
|
||||
`strtol(3)`. The first version of the obfuscated code used an homage to 1984 anon
|
||||
for I/O, and it featured a `#define` for `else`s and the types, to help minimize
|
||||
the repetition. And of course, most functions were renamed with one or two
|
||||
letters, and most of the variables became single letter globals. Tokenization
|
||||
was rather easy with a declared array, just initialized with unique values for
|
||||
@@ -93,33 +95,33 @@ each.
|
||||
This was when the obfuscation took a turn for the better (worse): it was easy
|
||||
to hide the operations via a simple transformation. The code now dealt with
|
||||
small integer values instead of direct ASCII representation of the operations.
|
||||
And the strings where collapsed into a single combined error message and
|
||||
And the strings were collapsed into a single combined error message and
|
||||
operator tokenization table. This was relatively obscure.
|
||||
|
||||
Next, sprintf() was replaced with a custom formatter. This was rather simple,
|
||||
Next, `sprintf(3)` was replaced with a custom formatter. This was rather simple,
|
||||
but at least the formatting was not obvious. Formatting and printing were
|
||||
split early. strtol() was replaced with custom logic, limited to 3 practical
|
||||
split early. `strtol(3)` was replaced with custom logic, limited to 3 practical
|
||||
bases: octal, decimal, and hex. The last result was remembered in a special
|
||||
variable, dot.
|
||||
|
||||
By this time, I had a version which I considered obscure, and shared this early
|
||||
version with a couple of colleagues for comments. I suggested I could improve
|
||||
the obfuscation of a term() and factor(), and would like to have assignable
|
||||
the obfuscation of a `term()` and `factor()`, and would like to have assignable
|
||||
memories.
|
||||
|
||||
This code was pretty dense, but inspired by previous IOCCC entries, I thought I
|
||||
might get rid of all the else's, which showed the structure of the parser
|
||||
might get rid of all the `else`s, which showed the structure of the parser
|
||||
rather clearly. Investing in the ternary operator, the code got quite a bit
|
||||
more difficult to follow, used only for-loops, and I had to start taking notes
|
||||
more difficult to follow, used only `for` loops, and I had to start taking notes
|
||||
to follow it. This was a good sign! ;-)
|
||||
|
||||
I started testing the code. I wrote a test suite to ensure the changes I made
|
||||
did not corrupt the processing (oh so easy to do with this obfuscated mess).
|
||||
Why does -1>>32 yield -1? Oh, yeah: signed arithmetic. Enter Java's unsigned
|
||||
shift: >>>. Better. But with more features, more to get wrong, and harder to
|
||||
know which expression is causing the error. The easy solution: if the name
|
||||
begins with 'e', echo the input. Now I can see the failing expression. Wait,
|
||||
what is this test expression supposed to be testing?
|
||||
Why does `-1>>32` yield `-1`? Oh, yeah: signed arithmetic. Enter Java's
|
||||
`unsigned` shift: `>>>`. Better. But with more features, more to get wrong,
|
||||
and harder to know which expression is causing the error. The easy solution: if
|
||||
the name begins with `e`, echo the input. Now I can see the failing expression.
|
||||
Wait, what is this test expression supposed to be testing?
|
||||
|
||||
Wouldn't it be nice to have comments?
|
||||
|
||||
@@ -130,8 +132,8 @@ statements on a single line (read: semicolon expression terminators). This
|
||||
involved quite a bit of rework of the input code, but now allowed very
|
||||
convenient user interaction.
|
||||
|
||||
Better Obscurity is a laborious process
|
||||
--------------------------------------------------
|
||||
|
||||
## Better Obscurity is a laborious process
|
||||
|
||||
Satisfied with the functionality, but unsatisfied with the obscurity, I
|
||||
pondered if I could get rid of all the constants? This had not been done
|
||||
@@ -141,45 +143,46 @@ Obscurity now increased!
|
||||
|
||||
But wouldn't it be nice to have assignable memories?
|
||||
|
||||
Around this time, the formatted code no longer fit with the 2053 limit (due to
|
||||
a lack of comprehension of the significance of the space-after-brace-and-semi
|
||||
Around this time, the formatted code no longer fit with the 2053 limit (due to a
|
||||
lack of comprehension of the significance of the space-after-brace-and-semi
|
||||
rule, and a layout program that did not know how to do this). Frustrated, I
|
||||
wrote another program to reformat this code into rule2.c, which was a single C
|
||||
token per line. This solved the formatting problem, but intriguingly
|
||||
wrote another program to reformat this code into [rule2.c](rule2.c), which was a
|
||||
single C token per line. This solved the formatting problem, but intriguingly
|
||||
introduced another. Now I was faced with the 4096 rule as well as the 2053
|
||||
rule. But this was a challenge!
|
||||
|
||||
I wrote a de-obfuscator, and worked on both the unobfuscated version and the
|
||||
master code. I worked on the template formatter to make this as automated as
|
||||
possible, and worked to achieve both a perfectly presented picture AND the
|
||||
single-line exactly-at-4096 rule2.c.
|
||||
single-line exactly-at-4096 [rule2.c](rule2.c).
|
||||
|
||||
Finally, "perfection", with assignable memories! Where perfection is defined
|
||||
as a nicely formatted picture at less than 2053, and exactly 4096
|
||||
token-per-line rule2.c.
|
||||
token-per-line [rule2.c](rule2.c).
|
||||
|
||||
Testing Testing Testing
|
||||
--------------------------------------------------
|
||||
|
||||
Cross platform, it works on Mac OSX, fails on Linux. It works if #include
|
||||
<stdlib.h>, but it dumps core without it (the only change!)?. 64bits without
|
||||
proper declaration of ints in syscalls yields unpredictable results. 32bit
|
||||
compilation showed problems with byte- swapping in using addition versus
|
||||
bitwise-or. In 32bit land, x<<32 == x, so x<<32 + x>>32 does not yield the
|
||||
desired result, but x<<32 | x>>32 does (with appropriate care and feeding).
|
||||
## Testing Testing Testing
|
||||
|
||||
Cross platform, it works on Mac OSX, fails on Linux. It works if `#include
|
||||
<stdlib.h>`, but it dumps core without it (the only change!)?. 64-bits without
|
||||
proper declaration of `int`s in syscalls yields unpredictable results. 32-bit
|
||||
compilation showed problems with byte-swapping in using addition versus
|
||||
bitwise-or. In 32-bit land, `x<<32 == x`, so `x<<32 + x>>32` does not yield the
|
||||
desired result, but `x<<32 | x>>32` does (with appropriate care and feeding).
|
||||
And the different compilers on different hosts complained loudly and
|
||||
differently, although all produced correct bits in spite of the noise.
|
||||
|
||||
Further, the program did not do UNIX pipelines well, due to the unbuffered
|
||||
homage code (byte-at-a-time I/O), of which write() was also the source of the
|
||||
portability problem. Replace write() with getchar()/putchar(), and it piped
|
||||
well but lost some obscurity and no longer paid (direct) homage to history.
|
||||
homage code (byte-at-a-time I/O), of which `write(2)` was also the source of the
|
||||
portability problem. Replace `write(2)` with `getchar(3)`/`putchar(3)`, and it
|
||||
piped well but lost some obscurity and no longer paid (direct) homage to
|
||||
history.
|
||||
|
||||
Another problem: only one expression at a time was allowed on the command line,
|
||||
no semis, no comments, and it turns out $2, $3, ... were not handled correctly.
|
||||
no semis, no comments, and it turns out `$2`, `$3`, ... were not handled correctly.
|
||||
Given the new I/O code, it was now possible to copy the command line arguments
|
||||
into a buffer, and write a small routine to retrieve bytes from this buffer
|
||||
like getchar(). Voila! Better obfuscation with much better functionality!
|
||||
like `getchar(3)`. Voila! Better obfuscation with much better functionality!
|
||||
|
||||
But wouldn't it be nice to show what the current variables hold?
|
||||
|
||||
@@ -187,20 +190,20 @@ Bytes are now hard to find to keep exactly at 4096, and adding functionality to
|
||||
obscured code is not easy. Restructuring the logic to gain the bytes
|
||||
necessary, code to display the variables was added.
|
||||
|
||||
Test. Test. Test. Oh, noes! More bugs! Typing "1 + 2 5" yields 3, but no
|
||||
syntax error. "g = 1" yields a syntax error, and "0xg" does not! ")" causes a
|
||||
segfault! Why didn't I test these before I started obfuscating?! By this
|
||||
point, everything I did to the code perturbed the 4096 bytes, all variables
|
||||
were already a single character, and it was really difficult to follow the
|
||||
logic in a debugger (single-stepping an expression did the entire line of code,
|
||||
even if it involved multiple statements -- and it did, everywhere, even in the
|
||||
Test. Test. Test. Oh, noes! More bugs! Typing "`1 + 2 5`" yields `3`, but
|
||||
no syntax error. "`g = 1`" yields a syntax error, and "`0xg`" does not! "`)`"
|
||||
causes a segfault! Why didn't I test these before I started obfuscating?! By
|
||||
this point, everything I did to the code perturbed the 4096 bytes, all variables
|
||||
were already a single character, and it was really difficult to follow the logic
|
||||
in a debugger (single-stepping an expression did the entire line of code, even
|
||||
if it involved multiple statements -- and it did, everywhere, even in the
|
||||
unobfuscated version; think: a complex chain of ternary expressions is a single
|
||||
statement). This involved careful examination of all the expressions within
|
||||
the logic, trying to find a different way of getting the same result, but
|
||||
typically within fewer bytes than before to allow for the added logic to fix
|
||||
the bugs.
|
||||
statement). This involved careful examination of all the expressions within the
|
||||
logic, trying to find a different way of getting the same result, but typically
|
||||
within fewer bytes than before to allow for the added logic to fix the bugs.
|
||||
|
||||
The bugs were fixed, the code re-formatted, and rule2.c was again 4096 bytes.
|
||||
The bugs were fixed, the code re-formatted, and [rule2.c](rule2.c) was again
|
||||
4096 bytes.
|
||||
|
||||
But wouldn't it be nice for unary plus to work?
|
||||
|
||||
@@ -211,22 +214,22 @@ this, bytes are scarce to come by.
|
||||
|
||||
But wouldn't it be nice for assignments to be silent?
|
||||
|
||||
Made them silent (easier said than done!). Why does "1 + 2 5" print a syntax
|
||||
error, but "a = 1 + 2 5" does not? Rewrite(!) the assignment logic -- and this
|
||||
requires more bytes!
|
||||
Made them silent (easier said than done!). Why does "`1 + 2 5`" print a syntax
|
||||
error, but "`a = 1 + 2 5`" does not? Rewrite(!) the assignment logic -- and
|
||||
this requires more bytes!
|
||||
|
||||
Better Obscurity Through Simplification
|
||||
--------------------------------------------------
|
||||
|
||||
Except there were not enough bytes to do this, unless #include <unistd.h> is
|
||||
removed. But then sbrk() must be declared, eating into the savings. If only
|
||||
## Better Obscurity Through Simplification
|
||||
|
||||
Except there were not enough bytes to do this, unless `#include <unistd.h>` is
|
||||
removed. But then `sbrk()` must be declared, eating into the savings. If only
|
||||
there were 512 bytes of memory already allocated that could be portably touched
|
||||
for free....
|
||||
|
||||
Light bulb! Memory allocation was excised, and the code became more obscure.
|
||||
|
||||
It was working again! It formats beautifully, it compiled cleanly (with
|
||||
apropos -Wno-guards), it remains obscure after cpp and clang-format, it
|
||||
It was working again! It formats beautifully, it compiled cleanly (with apropos
|
||||
`-Wno-guards`), it remains obscure after `cpp(1)` and `clang-format(1)`, it
|
||||
measures within the limits and exactly at 4096 (this latter was really getting
|
||||
challenging to maintain). No more bugs known, it is feature complete,
|
||||
documentation written.
|
||||
@@ -239,12 +242,12 @@ relatively tight. But changing around the stable initialization code -- by now
|
||||
the largest piece of logic -- yielded the necessary bytes, and improved
|
||||
obscurity by double duty encoding of the operations initialization.
|
||||
|
||||
The utility is now truly functional, and feature complete, and obscure.
|
||||
Without src.doc (basic documentation of the code structure), and some commented
|
||||
expanded logic stashed away, and the familiarity I have with the code having
|
||||
written and revised it repeatedly, I would not even want to attempt to decipher
|
||||
the logic. Many times in the debugging, I was thwarted by my own obfuscations,
|
||||
and went down blind paths in trying to fix a bug or add functionality. Now I
|
||||
think I have a good entry.
|
||||
The utility is now truly functional, and feature complete, and obscure. Without
|
||||
[src.doc.txt](src.doc.txt) (basic documentation of the code structure), and some
|
||||
commented expanded logic stashed away, and the familiarity I have with the code
|
||||
having written and revised it repeatedly, I would not even want to attempt to
|
||||
decipher the logic. Many times in the debugging, I was thwarted by my own
|
||||
obfuscations, and went down blind paths in trying to fix a bug or add
|
||||
functionality. Now I think I have a good entry.
|
||||
|
||||
But wouldn't it be nice to import pre-set definitions from a file?
|
||||
+26
-22
@@ -1,34 +1,38 @@
|
||||
### Spoilers
|
||||
|
||||
|
||||
Calc is a recursive-descent parser. It allocates from the stack deep enough
|
||||
`calc` is a recursive-descent parser. It allocates from the stack deep enough
|
||||
that all expressions are exhausted before the recursion stack depth reaches the
|
||||
allocations. The memory map and source is documented in src.doc.txt. Tokens
|
||||
are encoded using values 0-20, where 0 is an invalid token, 1 is minus, 2 is
|
||||
space, 3 is plus, etc. The necessary constants are derived at the start of the program,
|
||||
using the guarantee that globals are initialized to zero; so a correct null pointer
|
||||
is obtained from an uninitialized global pointer variable, and the integers from
|
||||
uninitialized global int variables. If x is a global int, then !x is 1, and the
|
||||
derivation proceeds from there, using some identities that are confusingly similar
|
||||
and interestingly combined (increasing obfuscation):
|
||||
allocations. The memory map and source is documented in
|
||||
[src.doc.txt](src.doc.txt). Tokens are encoded using values 0-20, where 0 is an
|
||||
invalid token, 1 is minus, 2 is space, 3 is plus, etc. The necessary constants
|
||||
are derived at the start of the program, using the guarantee that globals are
|
||||
initialized to zero; so a correct NULL pointer is obtained from an uninitialized
|
||||
global pointer variable, and the integers from uninitialized global `int`
|
||||
variables. If `x` is a global `int`, then `!x` is `1`, and the derivation
|
||||
proceeds from there, using some identities that are confusingly similar and
|
||||
interestingly combined (increasing obfuscation):
|
||||
|
||||
obc clr
|
||||
--- ---
|
||||
-~x x+1
|
||||
~-x x-1
|
||||
y-~x y+x+1
|
||||
```
|
||||
obc clr
|
||||
--- ---
|
||||
-~x x+1
|
||||
~-x x-1
|
||||
y-~x y+x+1
|
||||
```
|
||||
|
||||
The derived constants are a compromise between simple expressions, maximum utility,
|
||||
and minimum number of variables used.
|
||||
|
||||
Command line arguments are copied into the upper half of the input buffer,
|
||||
separated by a space. This allows the code to have a single input parser by using
|
||||
a pointer-to-func to either getchar() if live, or a getchar() simulator.
|
||||
a pointer-to-func to either `getchar(3)` if live, or a `getchar(3)` simulator.
|
||||
|
||||
A small amount of obfuscation comes from void functions declared int, but missing return.
|
||||
This is necessary to allow these functions in ternary ?: expressions and paired with && and ||;
|
||||
but the requisite returns would add symbol count at no benefit to obscurity or functionality.
|
||||
Function decomposition was mostly chosen to avoid curly braces:
|
||||
if a for-loop required multiple statements, the body became a new function.
|
||||
Last, but not least: sharing state via global variables is an ancient and honored
|
||||
form of obfuscation, and remarkably effective.
|
||||
A small amount of obfuscation comes from `void` functions declared `int`, but
|
||||
missing `return`. This is necessary to allow these functions in ternary `?:`
|
||||
expressions and paired with `&&` and `||`; but the requisite returns would add
|
||||
symbol count at no benefit to obscurity or functionality. Function
|
||||
decomposition was mostly chosen to avoid curly braces: if a `for`-loop required
|
||||
multiple statements, the body became a new function. Last, but not least:
|
||||
sharing state via global variables is an ancient and honored form of
|
||||
obfuscation, and remarkably effective.
|
||||
@@ -20,7 +20,7 @@ td J jmp_buf
|
||||
c *K pointer to cmd line args
|
||||
td L char
|
||||
v M(u64) detect errors, save answer in dot, format result in decimal and hex
|
||||
c *N freebie null pointer
|
||||
c *N freebie NULL pointer
|
||||
td O unsigned char
|
||||
td P void
|
||||
td Q int
|
||||
@@ -37,12 +37,12 @@ i64 a derived: 32
|
||||
i64 b derived: 1
|
||||
i64 _b(i64) term: << >> >>>
|
||||
i64 c derived: 10
|
||||
i64 _c(c*) convert binary global j to ascii decimal
|
||||
i64 _c(c*) convert binary global j to ASCII decimal
|
||||
i64 d freebie: 0
|
||||
v _d(i64) accumulate converted ascii bytes into j in base passed in
|
||||
v _d(i64) accumulate converted ASCII bytes into j in base passed in
|
||||
i64 e derived: 256
|
||||
i64 f derived: 2
|
||||
i64 _f() convert from ascii to binary: selects convert base (10,8,16), result in j
|
||||
i64 _f() convert from ASCII to binary: selects convert base (10,8,16), result in j
|
||||
u64 g(u16) byte swap 16-bits
|
||||
u64 h(u32) byte swap 32-bits
|
||||
i64 i(u64) select size and byte swap minimal bytes
|
||||
@@ -54,7 +54,7 @@ jb l for parser error recovery
|
||||
c m() scans input for token, ignores whitespace
|
||||
c n() get next input character
|
||||
i64 *o registers
|
||||
u32 p(c*) writes to stdout until null, newline appended intelligently
|
||||
u32 p(c*) writes to stdout until NUL, newline appended intelligently
|
||||
v q(**) init
|
||||
i64 r(c) writes c to stdout
|
||||
i64 s derived: 8
|
||||
|
||||
Executable
+47
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
#
|
||||
# try.sh - demonstrate IOCCC winner 2015/burton
|
||||
#
|
||||
|
||||
# make sure CC is set so that when we do make CC="$CC" it isn't empty. Doing it
|
||||
# this way allows us to have the user specify a different compiler in an easy
|
||||
# way.
|
||||
if [[ -z "$CC" ]]; then
|
||||
CC="cc"
|
||||
fi
|
||||
|
||||
make CC="$CC" all >/dev/null || exit 1
|
||||
|
||||
# clear screen after compilation so that only the entry is shown
|
||||
clear
|
||||
|
||||
echo "$ ./prog '?0x3343<<2'" 1>&2
|
||||
./prog '?0x3343<<2'
|
||||
|
||||
echo "$ echo '?0x3343<<2' | ./prog" 1>&2
|
||||
echo '?0x3343<<2' | ./prog
|
||||
|
||||
|
||||
echo "$ grep '[0-9]' prog.c" 1>&2
|
||||
read -r -n 1 -p "Press any key to continue (notice how there are no digits!): "
|
||||
echo 1>&2
|
||||
grep -c '[0-9]' prog.c
|
||||
|
||||
read -r -n 1 -p "Press any key to run: ./prog < test.in: "
|
||||
echo 1>&2
|
||||
./prog < test.in
|
||||
echo 1>&2
|
||||
|
||||
read -r -n 1 -p "Press any key to show try.this.txt: "
|
||||
echo 1>&2
|
||||
cat try.this.txt
|
||||
echo 1>&2
|
||||
|
||||
read -r -n 1 -p "Press any key to run: ./prog < try.this.txt: "
|
||||
echo 1>&2
|
||||
./prog < try.this.txt
|
||||
|
||||
read -r -n 1 -p "Press any key to show the hex value of 42: "
|
||||
echo 1>&2
|
||||
echo "$ ./prog 42" 1>&2
|
||||
./prog 42
|
||||
@@ -0,0 +1,6 @@
|
||||
a=0xfeedface
|
||||
b=0xcafe
|
||||
a << 16 + b
|
||||
a=0xdeadbeef
|
||||
b=0xcafe
|
||||
a << 16 + b
|
||||
@@ -3796,6 +3796,14 @@ output.
|
||||
# <a name="2015"></a>2015
|
||||
|
||||
|
||||
## <a name="2015_burton"></a>[2015/endoh3](/2015/burton/prog.c) ([README.md](/2015/burton/README.md]))
|
||||
|
||||
[Cody](#cody) added the [try.sh](/2015/burton/try.sh) script. This involved
|
||||
adding a text file, [try.this.txt](/2015/burton/try.this.txt).
|
||||
|
||||
|
||||
|
||||
|
||||
## <a name="2015_dogon"></a>[2015/endoh3](/2015/dogon/prog.c) ([README.md](/2015/dogon/README.md]))
|
||||
|
||||
[Cody](#cody) improved the Makefile so that one can easily change the dimensions
|
||||
|
||||
Reference in new issue
Block a user