web page updated 10.jun.2008,
Many people arrive at this webpage google searching for -dJPEGQ, for that visit:
Ghostscript -dJPEGQ usage explained
General topic: cryptic code hides bugs, dont compress lots of actions into one statement, instead break up the code into lots of actions with only one thing happening per statement. This generally exposes bugs.
Example from the -sDEVICE=jpeg
source code, here is the code
that reads the -dJPEG
switch eg -dJPEGQ=80
:
switch (code = param_read_int(plist, (param_name = "JPEGQ"), &jq)) {
case 0:
if (jq < 0 || jq > 100)
ecode = gs_error_limitcheck;
else
break;
goto jqe;
default:
ecode = code;
jqe:param_signal_error(plist, param_name, ecode);
case 1:
break;
}
After reading this web page look at the ascii version of the code transformation as HTML has a total disregard for spaces tabs and newlines. html was not designed by computer programmers I suspect. In fact it was not designed but appears to have been somehow hodge podged together (probably by physicists)
First point: most of programming time is spent reading programs and not writing them. When you program always try to make life easy for the reader *not* the writer. Badly written programs eat up huge amounts of debugging time. Those seconds that programmers save by cryptic tricks are offset by hours of debug time.
In this code fragment it is impossible to know what is going on, sometimes people do this deliberately to cover their tracks!
Lets look at the first line of the above: 3 different things are happening,
1. param_name
is being set to the value "JPEGQ"
2. A function call is being made: param_read_int(...)
3. A switch(..)
is being made on the return value.
This is very difficult to read in one scan of the fragment. As there are 3 things going on, why dont we break it up into 3 steps:
param_name = "JPEGQ" ;
code = param_read_int( plist , param_name , &jq ) ;
switch(code)
{
etc
There, the program flow is now clear as glass.
What lies inside the switch statement is total chaos!
There are only 3 cases in the switch, yet it is totally knotty program flow with even a goto. And that goto is to 1 line of code. I can understand someone using a goto to save retyping a lot of lines but not one line! Please!!
Lets undo the goto in the case 0:
fragment:
case 0:
if (jq < 0 || jq > 100)
{
ecode = gs_error_limitcheck;
param_signal_error(plist, param_name, ecode);
}
break;
Thats a bit more readable and understandable. As a general rule
c switch cases should always terminate with a break;
unless you have
a very good reason. Its a good idea also for the final case to end with
break;
: this makes the code more stable to cut-and-paste
activities. Anyway as a result we can remove
the label jqe:
from the default statement which can become:
default:
ecode = code;
param_signal_error(plist, param_name, ecode);
break ;
Finally the case 1:
fragment immediately goes to a
break;
ie it is being used as a trick to define the default
case.
This does not look good, programming-by-exclusion is usually a bad idea: as a program expands the excluded set changes, this leads to bugs.
But how else can we do this?
The answer is that using switch for
3 cases where 1 is a default is a bit heavy handed, lets use
if-else
constructs instead. Switch statements are the correct
approach when you have a lot of cases eg 10 or 100.
So rewritten from scratch
the new nice + readable version becomes:
param_name = "JPEGQ" ;
code = param_read_int( plist , param_name , &jq ) ;
if( code==0 )
{
if( jq < 0 || jq > 100 )
{
ecode = gs_error_limitcheck ;
param_signal_error( plist , param_name , ecode ) ;
}
}
else if( code!=1 )
{
ecode = code;
param_signal_error(plist, param_name, ecode);
}
HTML sucks because it doesnt understand spaces, tabs and newlines,
but apart from that glitch we at last can see whats going on!
(much later on: I found how to do tabs etc with html, you use <pre>)
Look at the non html text version, the above
fragment is the second fragment in this non html.
Looking at this version
the if
clause is reporting error if the parameter is out of range.
The else if
clause is clearly also reporting error. But now
I detect a problem: the internal-GS8 convention is that errors are
signified by return values < 0. If code
is eg 2,3,4 etc
it is positive therefore not an error. This code is wrong!!!
It evidently doesnt matter
as I have used -sDEVICE=jpeg -dJPEGQ=...
many times.
Notice also the severity of the response: it appears to be responding
with a signal! This example shows how by clarifying our code bugs become much more
visible. Well written code generally is bug free code, because it makes bugs
stand out like a naked woman on the pavement!
The possibilities for -dJPEGQ=...
are:
1. The switch is absent: experiment reveals the value for code
is
then 1.
2. The code is an integer, here code
is 0.
3. The switch is present but contains garbage: experiment reveals that
either a negative value is returned eg -20 == "typecheck error" or the function never returns but
the program quits! eg -dJPEGQ=x
causes the program to quit
saying -dvar=name
is only allowed for name
equal to null, true
or false
.
So finally we reach the correct version which is:
param_name = "JPEGQ" ;
code = param_read_int( plist , param_name , &jq ) ;
if( code==0 )
{
/* -dJPEGQ=valid integer */
if( jq < 0 || jq > 100 )
{
ecode = gs_error_limitcheck ;
param_signal_error( plist , param_name , ecode ) ;
/*
how terrible, the int is out of range lets
quit work for today and generally give up
*/
}
}
else if( code < 0 ) /* -dJPEGQ=non-integer garbage */
{
ecode = code;
param_signal_error(plist, param_name, ecode);
/* :give up hope at the slightest excuse */
}
Finally we have correct code, you can quickly look at this code and *know* it is correct, you dont even need to compare it with the original (wrong) fragment! Return and look at the original code, the author of it certainly didnt detect this bug! Neither did I until I "improved" the clarity + sequencing of the code.
Of course I wouldnt actually alter the source code of
-sDEVICE=jpeg
as it functions correctly.
Personally I believe in the error-recovery concept: if the value is
garbage issue an error message, ignore the garbage and continue without it!
Again look at the non html text version
to see the 3 phases of this transformation: code viewed via html
always comes out as jibberish. If someone out there knows how to
make html render multiple spaces and tabs correctly, email me!
The reason that I have used Memacs ever since I bought my A500 is because it understands + respects spaces tabs and newlines. In particular it really understands tabs the way I code.
You may be wondering why I was looking at this fragment!
The reason is that for the new GS8 I am working on I need to
be able to extract command line arguments. Integer arguments are
quite important so I was looking to see how
-sDEVICE=jpeg -dJPEGQ=...
did it and ended up at
the incomprehensible code fragment at the top of this web page.
This needed the steps outlined above to reduce it to the last
item in
the non html version before I could
understand it. With that I now understand how to get command line
arguments. (there is no argc , argv
available for this).