Fixes in 2015/burton

Format fixes (maybe typos?) in README.md.

Added calc.1 which is essentially what was calc.man but so one can
actually get expected formatting when using man ./calc.1. If I fixed any
typos they are not fixed in calc.man. I kept the commands the same so
one would have to change calc to ./prog.

Formatting and maybe typos in road.to.obscurity.
This commit is contained in:
Cody Boone Ferguson committed 2023-03-25 09:08:25 -07:00
1 parent dbc6a3b812
commit fa31f80b39
3 files changed
+339 -175

No files matched your search

+76 -61
View File
@@ -46,13 +46,14 @@ grep '[0-9]' prog.c
./prog < test.in
man ./calc.man
man ./calc.1
```
## Alternate code
An alternate version of this entry, `prog.alt.c`, is provided.
This alternate code is discussed in the "prog.c vs prog.alt.c" section of the author's comments below.
This alternate code is discussed in the "prog.c vs prog.alt.c" section of the
author's comments below.
To compile this alternate version:
@@ -66,10 +67,11 @@ Use `prog.alt` as you would `prog` above.
"Look, Ma, no ~~hands~~ digits!"
The place where some arithmetic operations are done, for example,
division or modulo, are easy to find. Where are addition and subtraction?
The place where some arithmetic operations are done, for example, division or
modulo, are easy to find. Where are addition and subtraction?
We were impressed with the level of detail the author "blogged" the obfuscation process.
We were impressed with the level of detail the author "blogged" the obfuscation
process.
## Author's comments:
@@ -80,10 +82,10 @@ See the included man page. ;-)
### Notes
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.
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.
Printed in 8 pt Courier, it makes aesthetically pleasing 8.5 x 11 wall art.
@@ -91,12 +93,13 @@ 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.
The modified code is agnostic to `char` signed-ness.
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?
@@ -105,21 +108,24 @@ Yes, this is another calculator program.
However, unlike any other calculator source code the author is aware of,
this one contains no digits. At all. Anywhere. As in
grep '[0-9]' prog.c || echo no digits!
```sh
grep '[0-9]' prog.c || echo no digits!
```
Nor are the digits simply obfuscated. There are no character constants.
There 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.
Nor are the digits simply obfuscated. There are no character constants. There
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. Hope-less.
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) to exactly 4096 octets.
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.
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?
@@ -130,52 +136,57 @@ The code follows many industry best-practices:
- Numbers and digits have often been used for obfuscation,
so in a bid for better clarity, this code has no digits at all.
- In the spirit of clarity, most of the variables are declared before use, so they
are readily visible, with single letter names that don't clutter the screen,
in a tautological arrangement.
- In the spirit of clarity, most of the variables are declared before use, so
they are readily visible, with single letter names that don't clutter the
screen, in a tautological arrangement.
- More helpful is the use of `typedef`s instead of `#define`, so the types remain useful
even in the debugger, and source code beautifiers will not undo careful presentation.
- More helpful is the use of `typedef`s instead of `#define`, so the types
remain useful even in the debugger, and source code beautifiers will not undo
careful presentation.
- `#defines` are used here in the best spirit of the preprocessor: to shorten
repetitive constructs, not for obscurity.
repetitive constructs, not for obscurity.
- All functions are short (and therefore easy to understand?) and do only one thing, well, ....
- All functions are short (and therefore easy to understand?) and do only one
thing, well, ....
- Curly braces invite endless holy wars over readability. This code only uses them
for function definitions, thereby greatly improving readability, and reducing arguments
about said readability.
- Curly braces invite endless holy wars over readability. This code only uses
them for function definitions, thereby greatly improving readability, and
reducing arguments about said readability.
- One of C's many blessings (until recently) is a modicum of keywords,
of which only 8 are used:
`for`, `return`, `typedef`, `void`, `char`, `short`, `unsigned` and `long`;
10 if `#include` and `#define` are counted.
- One of C's many blessings (until recently) is a modicum of keywords, of which
only 8 are used: `for`, `return`, `typedef`, `void`, `char`, `short`, `unsigned`
and `long`; 10 if `#include` and `#define` are counted.
- The code helpfully declares "syntax_error!" right up front, filling the reader with confidence.
- 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 better qualities.
- 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; it helps
to reformat the code to see the second spelling.
- That the code is obfuscated is spelled out two different ways in the source;
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 trick.
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?
- 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
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?
- 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.
- 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 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:
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:
1<<48
65536 0x00010000
@@ -185,8 +196,10 @@ 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:
-Wno-return-type -Wno-parentheses -Wno-empty-body
When invoked with -ansi -pedantic, there are two warnings, which can be ignored:
* `ISO C90 does not support 'long long'`
@@ -202,12 +215,14 @@ With -Wall -std=c99, both are quite noisy, and all can be ignored:
If the program name begins with an "e", it echoes stdin to stdout.
This allows for a convenient test suite:
ln -s prog eprog
PATH=. eprog < test.in | diff - test.out
```sh
ln -s prog eprog
PATH=. eprog < test.in | diff - test.out
```
### Spoilers
If you do not want to puzzle out how it works, see spoilers.markdown.
If you do not want to puzzle out how it works, see `spoilers.markdown`.
## Copyright:
+130
View File
@@ -0,0 +1,130 @@
.TH calc 1 "25 March 2023" "calc" "IOCCC 2015"
.SH NAME
calc \- an integer expression calculator that outputs in both hex and decimal
.SH SYNOPSIS
\fBcalc\fP \fI[expr]\fP
.SH DESCRIPTION
.BR calc
presents a simple signed integer expression calculator.
The result is printed on
.BR stdout
as both a decimal value and its hex equivalent.
Results are presented as 32-bit values, displaying as 64-bits only if necessary.
If the program name begins with
.I e
the input is also copied to
.BR stdout .
.BR stdin
is read if there are no arguments.
.PP
Constants may be decimal, octal or hex.
There are 26 registers, initialized to zero, named A-Z; the most recent result is always remembered in the register
.IR .
(dot).
Assignments are silent; the character
.IR $
will print a table of the non-zero registers.
.PP
All input is case-insensitive.
Semicolons and/or newlines separate statements.
Whitespace is ignored, except newlines cannot occur within expressions.
Comments are introduced with
.I #
and extend through the following newline.
.PP
Operator precedence is as found in Go:
OR and XOR are treated as additive, SHIFTing and AND are multiplicative.
This allows a more natural expression without parentheses,
as in
.BR a&0xff\ |\ b<<8 \c
\&.
.PP
Supported operations in decreasing precedence:
.TP
.B ()
forced precedence
.TQ
.B \- + ! ~ ?
unary plus/minus, not, compl, byteswap
.TQ
.B * / % & << >> >>>
mul, div, mod, bitand, shift: left, right, unsigned right
.TQ
.B + \- | ^
add, sub, bitor, xor
.TQ
.B =
assignment ([a-z] = expr)
.SH EXAMPLES
.PP
Display the decimal value of 0x42:
.sp
.RS
.ft B
$ calc 0x42
.br
66 0x00000042
.ft R
.RE
.PP
What is the mask value for 22 bits?
.sp
.RS
.ft B
$ calc '1<<22-1'
.br
4194303 0x003fffff
.ft R
.RE
.PP
What is the integer value of byte-swapped 0x11223344?
.RS
.ft B
$ calc ?0x11223344
.br
1144201745 0x44332211
.ft R
.RE
.PP
Some canned expressions:
.RS
.ft B
cat <<! | ecalc
.br
# multiple assignments
.br
a=32;n=10;c=50;m=100;$
.br
7+7/7+7*7-7 # pedmas
.br
m|a-a*3 # convenient precedence
.br
?(1<<14|1<<9)-1<<4-1<<3
.br
57005<<16+48879
.br
-1 >> 32 # signed shift
.br
-1 >>> 32 # unsigned shift
.br
!
.ft R
.RE
.SH BUGS
An assignment with certain types of syntax error will replace the registers with a partial evaluation:
.sp
.RS
.ft B
calc 'a = 1; a; a = 3 4; a'
.br
1 0x00000001
.br
syntax error
.br
3 0x00000003
.ft R
.RE
.PP
Command line expressions longer than 64 characters will give a syntax error.
.SH SEE ALSO
\<https://golang.org/\>
+133 -114
View File
@@ -3,18 +3,19 @@ Development of the obfuscated calculator.
I wanted to enter the IOCCC....
I think I can write something clever. Possibly even obscure. The competition is fierce,
but not a closed door.
I think I can write something clever. Possibly even obscure. The competition
is fierce, but not a closed door.
I needed an interesting program, that had a reasonable chance of begin 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 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.
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
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 fore-shadowing):
() forced precedence
- ~ ? unary minus, compl, byteswap
@@ -22,8 +23,8 @@ Calc is a useful, functioning utility. The operators before the obfuscation dan
+ - | ^ add, sub, bitor, xor
NUM 0-9
This seemed like a reasonable candidate to experiment with obfuscation; perhaps it might
yield good results.
This seemed like a reasonable candidate to experiment with obfuscation; perhaps
it might yield good results.
Principle of operation
--------------------------------------------------
@@ -69,7 +70,8 @@ The algorithm was and is rather straightforward:
Obfuscation is a process
--------------------------------------------------
Within the above algorithm, there are many details unmentioned, all ripe for obfuscation:
Within the above algorithm, there are many details unmentioned, all ripe for
obfuscation:
- input
- tokenization
@@ -79,153 +81,170 @@ Within the above algorithm, there are many details unmentioned, all ripe for obf
- swapping bytes
- 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 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 each.
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
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
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 operator tokenization table. This was relatively
obscure.
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
operator tokenization table. This was relatively obscure.
Next, sprintf() 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 bases: octal, decimal, and hex. The
last result was remembered in a special variable, dot.
Next, sprintf() 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
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 memories.
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
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 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 to follow it. This was a good sign! ;-)
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
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
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?
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?
Wouldn't it be nice to have comments?
Adding new functionality to an obscure program is ... going about it backwards. Get the
functionality correct and complete, THEN obscure. Sounds obvious, but that did not
happen. I got comments working, then desired to have multiple 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.
Adding new functionality to an obscure program is ... going about it backwards.
Get the functionality correct and complete, THEN obscure. Sounds obvious, but
that did not happen. I got comments working, then desired to have multiple
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
--------------------------------------------------
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 before (to my knowledge), and
I was able to work out the how-tos and the compromises. Now I can no longer declare my
arrays, I have to allocate them. Obscurity now increased!
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
before (to my knowledge), and I was able to work out the how-tos and the
compromises. Now I can no longer declare my arrays, I have to allocate them.
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 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 introduced another. Now I was faced with the 4096
rule as well as the 2053 rule. But this was a challenge!
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
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.
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.
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.
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.
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). And the different compilers on different hosts complained loudly and
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).
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.
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.
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. 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!
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.
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!
But wouldn't it be nice to show what the current variables hold?
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.
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 seg fault! 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.
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.
The bugs were fixed, the code re-formatted, and rule2.c was again 4096 bytes.
But wouldn't it be nice for unary plus to work?
Add unary plus, re-balance. Little things now take a fair bit of work. Adding anything
requires taking away in others, but since all the logic is functional, this means
rewriting the logic to be more concise. After several rounds of this, bytes are scarce to
come by.
Add unary plus, re-balance. Little things now take a fair bit of work. Adding
anything requires taking away in others, but since all the logic is functional,
this means rewriting the logic to be more concise. After several rounds of
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 there were 512 bytes of
memory already allocated that could be portably touched for free....
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 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.
It was working again! It formats beautifully, it compiled cleanly (with
apropos -Wno-guards), it remains obscure after cpp and clang-format, 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.
But wouldn't it be nice to have a logical not?
Sigh. This was the last operation added. Another round of code churn, expressions are
difficult to rewrite because they are both obscure and 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.
Sigh. This was the last operation added. Another round of code churn,
expressions are difficult to rewrite because they are both obscure and
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 (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?