📄 ch31.htm
字号:
file, and the decoding functions are declared in <TT><FONT FACE="Courier">P286dec.c</FONT></TT>.
The acid test really is to see if the code compiles. Try these
commands-you should see no errors:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">gcc -c P286enc.c<BR>
gcc -c P286dec.c</FONT></TT>
</BLOCKQUOTE>
<P>
Alas, we still have to write the code to use these functions.
But that's really beyond the scope of this book. What's important
to see is that in a few hours or so, we have created the boring
part of the application and are now ready to proceed with using
these tools.
<P>
During this discussion I have glazed over the details of how the
header and source files are created in the subroutine calls we
made. Let's take a look at the details of how these functions
work.
<H2><A NAME="WritingthecheaderFiles"><B><FONT SIZE=5 COLOR=#FF0000>Writing
the C Header Files</FONT></B></A></H2>
<P>
The first task for generating the header file is to create the
preamble to the <TT><FONT FACE="Courier">include</FONT></TT> file
being created in the file that is pointed to by the <TT><FONT FACE="Courier">HDRS</FONT></TT>
file handle. The code to do this is as follows:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub startHeaderFile() {<BR>
print ( HDRS "#ifndef P286_HDRS\n",
<BR>
"#define P286_HDRS 1\n",<BR>
"#define STRPTR char *\n");<BR>
}</FONT></TT>
</BLOCKQUOTE>
<BLOCKQUOTE>
When the file is closed, you'll want to put in an #endif statement
to allow multiple inclusions of the header file. This is done
with a call to the subroutine sHeaderFile():
</BLOCKQUOTE>
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub closeHeaderFile() {<BR>
printf HDRS "\n#endif\n";<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
The empty pair of parentheses in the function declaration is used
in Perl 5.002 or later to define a function prototype that allows
for no input parameters. When a <TT><FONT FACE="Courier">RECORD</FONT></TT>
header is received, it generates two items: a <TT><FONT FACE="Courier">#define</FONT></TT>
token for a header number and the preamble for the structure to
use. The token is helpful if you want to create a parser that
does a <TT><FONT FACE="Courier">switch()</FONT></TT> statement
on a type of structure. To make sure that each token has a unique
value, a counter is kept in <TT><FONT FACE="Courier">$recordCounter</FONT></TT>
for use in assigning a record type a unique value. The code to
do this is shown here:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub startHeaderRecord {<BR>
my ($name) = @_;<BR>
printf HDRS "\n#define P286_%s_RECTYPE
%d ", $name, $recordCounter++;<BR>
printf HDRS "\n\ntypedef struct P286_%s_type
{", $name;<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
By assigning the <TT><FONT FACE="Courier">@_</FONT></TT> array
to <TT><FONT FACE="Courier">my($name)</FONT></TT> we are actually
permitting more than one argument into the function even though
only the first argument is used. The contents of the <TT><FONT FACE="Courier">@_</FONT></TT>
array are not altered in this case. Using a command like my <TT><FONT FACE="Courier">$name
= shift @_</FONT></TT> would achieve the same purpose but would
also alter the contents of the <TT><FONT FACE="Courier">@_</FONT></TT>
array.
<P>
Each structure definition being created has to be stopped. Two
variable types are constructed: <TT><FONT FACE="Courier">P286_HEADERNAME_TYPE</FONT></TT>
and a pointer type to the structure <TT><FONT FACE="Courier">*P286_HEADERNAME_PTR</FONT></TT>.
The code to do this is shown here:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub stopHeaderRecord($name) {<BR>
my $name = shift;<BR>
my $ntype = "P286_" . $name
. "_TYPE";<BR>
my $nptr = "*P286_"
. $name . "_PTR;";<BR>
print HDRS "\n}" . uc($ntype)
. "," . uc($nptr) ." \n";<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
Note the syntax used for the function prototype shown previously.
The <TT><FONT FACE="Courier">$name</FONT></TT> variable declaration
in the argument list is only valid in Perl 5.002 or later. Formal
parameter lists to subroutines in Perl are not completely supported
in Perl as we go to print. It does not hurt to be prepared for
the future by including formal parameter lists if they do not
affect the underlying code in the subroutine itself. If you want
to force Perl to take only one parameter into this subroutine,
you can also declare this as
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub stopHeaderRecord($) { … }</FONT></TT>
</BLOCKQUOTE>
<P>
The dollar sign in the parentheses will be used by the Perl interpreter
as an indicator that only one parameter is allowed into the subroutine.
<P>
For a non-arrayed item, three variable types are created: a <TT><FONT FACE="Courier">char</FONT></TT>
string of fixed length, an <TT><FONT FACE="Courier">int</FONT></TT>,
and a <TT><FONT FACE="Courier">double</FONT></TT>. The type of
variable to generate a declaration for is passed in as a parameter
to the <TT><FONT FACE="Courier">makeHeaderItem</FONT></TT> function.
Comments are also generated in the header file pointed to by the
<TT><FONT FACE="Courier">HDRS</FONT></TT> file handle to indicate
what columns the data points to. These comments serve as a cross-
reference for when you are debugging the generated code:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub makeHeaderItem {<BR>
my($vname,$vtype,$from,$to) = @_;<BR>
my $len;<BR>
if ($vtype eq 'char') {<BR>
$len = $to - $from + 2 ;<BR>
printf HDRS "\n %s %s[%d]\; \/* %d
%d *\/ ",<BR>
$vtype,
$vname, $len, $from, $to;<BR>
}<BR>
else<BR>
{<BR>
printf HDRS "\n %s %s\; \/* %d %d
*\/", $vtype, $vname, $from, $to;<BR>
}<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
If the variable being generated is in a <TT><FONT FACE="Courier">REPEAT</FONT></TT>
block, it's declared as an array with a call to the <TT><FONT FACE="Courier">MakeArrayedItem()</FONT></TT>
function. The call to generate this arrayed item is
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub makeArrayedItem {<BR>
my ($vname,$vtype,$count,$from,$to) =
@_;<BR>
printf HDRS "\n %s
%s[%d]; \/* from %d %d *\/ ",<BR>
$vtype,
$vname, $count, $from, $to;<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
This code generates the <TT><FONT FACE="Courier">P286.h</FONT></TT>
headers file. Now let's look at the files that will contain the
encode and decode functions.
<H2><A NAME="WritingtheEncoderSourceFile"><B><FONT SIZE=5 COLOR=#FF0000>Writing
the Encoder Source File</FONT></B></A></H2>
<P>
To create the C source file to create encoder for the data file,
use the <TT><FONT FACE="Courier">EncD</FONT></TT> file handle.
The subroutine <TT><FONT FACE="Courier">startEncoderFile()</FONT></TT>
starts the preamble for the file, which includes two items. The
first is the call to include the header file, which is also being
generated. The second is to write out the code to a function that
pads spaces to the right of an incoming string to make the length
equal to 80 characters plus a null character. The code to perform
this preamble is shown here:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub startEncoderFile {<BR>
print (EncD "\/*** C source file to encode records.",
<BR>
"\nDon't edit this file\n ",
<BR>
"*/\n",<BR>
'#include "p286.h" ',<BR>
"\n",<BR>
"\n/* The incoming buffer must be
81 chars! */",<BR>
"\nvoid padTo80(STRPTR buffer)\n{\n",
<BR>
"int i,ln;\n",<BR>
"ln = strlen(buffer);\n",<BR>
"for(i=ln;i<80;i++) buffer[i]
= ' '; \n ",<BR>
"buffer[81] = 0; /* NULL terminate
the string*/",<BR>
"\n} /* end of padding function */\n",
<BR>
"\n");<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
As each new <TT><FONT FACE="Courier">RECORD</FONT></TT> type is
encountered in the input file, its corresponding encoding function
header is created in the output file. A function header is created
that takes a string to put an encoded record in and a pointer
to a structure to unpack. Because the name of the record being
parsed is passed into the code generation function, it's easy
to derive the pointer name for it: <TT><FONT FACE="Courier">P286_HEADERNAME_PTR</FONT></TT>.
The code to accomplish this is shown here:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub startEncoderFunction {<BR>
my($vname) = @_;
# Pick up name of record.<BR>
print (EncD "\n/*: ",<BR>
"\n** Generated
by Perl script -- Avoid editing\n*/\n");<BR>
printf EncD<BR>
"void encode_%s_type(STRPTR
buffer,P286_%s_PTR sp)\n{",<BR>
$vname, $vname;
<BR>
print (EncD "\nSTRPTR ncp; \n",
<BR>
"\nSTRPTR
cp; \n",<BR>
"register
int i; \n",<BR>
"char tempbuffer[80];\n");
<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
When an <TT><FONT FACE="Courier">ENDREC</FONT></TT> line is read,
the function has to be closed. Unmatched <TT><FONT FACE="Courier">RECORD</FONT></TT>
and <TT><FONT FACE="Courier">ENDREC</FONT></TT> lines in the input
file will cause bad, uncompilable code to be generated. The cleanup
at the end of the encode function is done by adding a call the
padding function, <TT><FONT FACE="Courier">padTo80</FONT></TT>,
and printing out the ending curly brace:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub closeEncoderFunction {<BR>
print (EncD "\npadTo80(buffer);",
<BR>
"\n}
/* End of encoding function */ \n");<BR>
}</FONT></TT>
</BLOCKQUOTE>
<P>
As with structure declarations, two types of variables have to
be parsed. One is a non-arrayed element and the other is an arrayed
element. However, within the original loop the <TT><FONT FACE="Courier">$vname</FONT></TT>
being passed into this function is already set up as a variable
or a member of an array so that no further processing is necessary.
<P>
The three types of variables used by the parsing encoder are used
to generate the code. If the variable is a string, it's simply
cut and pasted into its columns in the outgoing buffer. If it's
an integer, the value of the integer is printed in the columns
in the output buffer for the integer. For a <TT><FONT FACE="Courier">double</FONT></TT>,
the <TT><FONT FACE="Courier">$fmt</FONT></TT> string contains
the format string to explicitly place the decimal point at the
right location in the columns for the output buffer. The code
to perform this parsing is shown here:
<BLOCKQUOTE>
<TT><FONT FACE="Courier">sub encodeVariable {<BR>
my($vname,$vtype,$from,$to,$fmt) = @_;
<BR>
my $len = $to - $from + 1;<BR>
printf EncD "\n\n";<BR>
printf EncD "/* Encode:$vtype,$vname,$from,$to,$len,$fmt
*/";<BR>
if ($vtype =~ /char/) {<BR>
printf EncD "\ncp
= (STRPTR )&(buffer[%d]); ",$from-1;<BR>
printf EncD "\nfor
(i=0; i< %d;i++)",$len;<BR>
printf EncD "\n buffer[%d
+ i] = sp->%s[i]; ",$from-1,$vname;<BR>
}<BR>
if ($vtype =~ /double/) {<BR>
if (length($fmt)
> 0)<BR>
{<BR>
printf EncD "\nsprintf(tempbuffer,\"%%%sf\",(sp->%s));",
<BR>
$fmt,$vname;
<BR>
}<BR>
else<BR>
{<BR>
printf EncD "\nsprintf(tempbuffer,\"%%%sf\",(sp->%s));",
<BR>
$len,$vname;
<BR>
}<BR>
printf EncD "\ntempbuffer[%d]= 0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -