fpc/utils/data2inc.pp
Michaël Van Canneyt e40f6e13f0 * PChar -> PAnsiChar
2023-07-24 14:55:42 +02:00

891 lines
22 KiB
ObjectPascal

{
Copyright (c) 1999-2000 by Peter Vreman (msg2inc) and
Marco van de Voort (data2inc)
Placed under LGPL (See the file COPYING.FPC, included in this
distribution, for details about the copyright)
E-Mail Marco : Marcov@stack.nl
Homepage Marco: www.stack.nl/~marcov/xtdlib.htm
Data2Inc is a heavily modified version of msg2inc.pp which compiles the
inputfile to include files containing array of char( or byte) typed
constants.
(e.g. CONST xxx : ARRAY[0..xxx] OF CHAR =( aa,bb,cc,dd,ee); ,
or the same but ARRAY OF BYTE )
Two types of input file are allowed:
1 A special kind of textfile. Records start with '!'name and all following
non empty and non comment (starting with '#',':' or '%') lines until
the next line starting with '!' or EOF are the data. Data are either
plain text (with \xxx ordinal constants) lines or a kinbd of
Basic DATA command (these lines start with DATA).
See demo.txt included with this package for a commented example.
2 (special parameter -b)
An arbitrary binary file can get converted to constants. In this mode
only one constant per include file is possible.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**********************************************************************}
program data2inc;
uses strings;
CONST
version='1.00';
maxbufsize = 1024*1024; { 1 mb buffer }
type
TOutputMode=(OutByte,OutChar,OutString);
{*****************************************************************************
Simple service routines. These are copied from EPasStr.
*****************************************************************************}
TYPE CHARSET=SET OF ANSICHAR;
FUNCTION NextCharPos(CONST S : AnsiString;C:AnsiCHAR;Count:LONGINT):LONGINT;
VAR I,J:LONGINT;
BEGIN
I:=Length(S);
IF I=0 THEN
J:=0
ELSE
BEGIN
J:=Count;
IF J>I THEN
BEGIN
NextCharPos:=0;
EXIT
END;
WHILE (S[J]<>C) AND (J<=I) DO INC(J);
IF (J>I) THEN
J:=0;
END;
NextCharPos:=J;
END;
FUNCTION NextCharPosSet(CONST S : AnsiString;CONST C:CHARSET;Count:LONGINT):LONGINT;
VAR I,J:LONGINT;
BEGIN
I:=Length(S);
IF I=0 THEN
J:=0
ELSE
BEGIN
J:=Count;
IF J>I THEN
BEGIN
NextCharPosSet:=0;
EXIT;
END;
WHILE (j<=i) AND (NOT (S[J] IN C)) DO INC(J);
IF (J>I) THEN
J:=0; // NOT found.
END;
NextCharPosSet:=J;
END;
PROCEDURE RTrim(VAR P : AnsiString;Ch:AnsiChar);
VAR I,J : LONGINT;
BEGIN
I:=Length(P); { Keeping length in local data eases optimalisations}
IF (I>0) THEN
BEGIN
J:=I;
WHILE (P[J]=Ch) AND (J>0) DO DEC(J);
IF J<>I THEN
Delete(P,J+1,I-J+1);
END;
END;
PROCEDURE UpperCase(VAR S : AnsiString);
VAR L,I : LONGINT;
BEGIN
L:=Length(S);
IF L>0 THEN
FOR I:=1 TO L DO
IF (S[I]>CHR(96)) AND (S[I]<CHR(123)) THEN
S[I]:=CHR(ORD(S[I])-32);
END;
PROCEDURE LTrim(VAR P : AnsiString;Ch:ansiChar);
VAR I,J : LONGINT;
BEGIN
I:=Length(P); { Keeping length in local data eases optimalisations}
IF (I>0) THEN
BEGIN
J:=1;
WHILE (P[J]=Ch) AND (J<=I) DO INC(J);
IF J>1 THEN
Delete(P,1,J-1);
END;
END;
{*****************************************************************************
Parsing helpers
*****************************************************************************}
FUNCTION XlatString(Var S : AnsiString):BOOLEAN;
{replaces \xxx in string S with #x, and \\ with \ (escaped)
which can reduce size of string.
Returns false when an error in the line exists}
Function GetNumber(Position:LONGINT):LONGINT;
VAR C,
Value,
I : LONGINT;
BEGIN
I:=0; Value:=0;
WHILE I<3 DO
BEGIN
C:=ORD(S[Position+I]);
IF (C>47) AND (C<56) THEN
C:=C-48
ELSE
BEGIN
GetNumber:=-1;
EXIT;
END;
IF I=0 THEN
C:=C SHL 6;
IF I=1 THEN
C:=C SHL 3;
Value:=Value + C;
INC(I);
END;
GetNumber:=Value;
END;
VAR S2:AnsiString;
A,B : LONGINT;
Value : LONGINT;
BEGIN
A:=1; B:=1;
WHILE A<=Length(S) DO
BEGIN
IF S[A]='\' THEN
IF S[A+1]='\' THEN
BEGIN
S2[B]:='\';
INC (A,2); INC(B);
END
ELSE
BEGIN
Value:=GetNumber(A+1);
IF Value=-1 THEN
BEGIN
XlatString:=FALSE;
EXIT;
END;
S2[B]:=CHR(Value);
INC(B); INC(A,4);
END
ELSE
BEGIN
S2[B]:=S[A];
INC (A);
INC (B);
END;
END;
SetLength(S2,B-1);
S:=S2;
XlatString:=TRUE;
END;
{Global equates}
VAR
Inname, { Name of input file }
OutName, { Name of output (.inc) file }
BinConstName : Ansistring; { (-b only) commandline name of constant }
OutputMode : TOutputMode; { Output mode (char,byte,string) }
I_Binary : BOOLEAN; { TRUE is binary input, FALSE textual }
MsgTxt : pAnsichar; { Temporary storage of data }
msgsize : longint; { Bytes used in MsgTxt }
C : AnsiCHAR;
{*****************************************************************************
WriteCharFile
*****************************************************************************}
{Dump the contents of MsgTxt (msgsize bytes) to file T (which has been opened),
using CONSTNAME as the name of the ARRAY OF CHAR constant}
procedure WriteCharFile(var t:text;constname:ansistring);
function createconst(b:byte):string;
{decides whether to use the #xxx code or 'c' style for each char}
begin
if (b in [32..127]) and (b<>39) then
createconst:=''''+chr(b)+''''
else
createconst:='#'+chr(b div 100+48)+chr((b mod 100) div 10+48)+chr(b mod 10+48)
end;
var
cidx,i : longint;
p : PAnsiCHAR;
begin
Writeln('Writing constant: ',constname,' to file '#39,outname,#39);
{Open textfile}
write(t,'const ',constname,' : array[0..'); Writeln(t,msgsize-1,'] of char=(');
p:=msgtxt;
cidx:=0;
for i:=0 to msgsize-1 do
begin
if cidx=15 then
begin
if cidx>0 then
writeln(t,',')
else
writeln(t,'');
write(t,' ');
cidx:=0;
end
else
IF cidx>0 THEN
write(t,',')
ELSE
Write(T,' ');
write(t,createconst(ord(p^)));
inc(cidx);
inc(p);
end;
writeln(t,');');
Writeln(T);
end;
{*****************************************************************************
WriteByteFile
*****************************************************************************}
{Dump the contents of MsgTxt (msgsize bytes) to file T (which has been opened),
using CONSTNAME as the name of the ARRAY OF BYTE constant}
procedure WriteByteFile(var t:text;constname:ansistring);
function createconst(b:byte):ansistring;
{Translates byte B to a $xx hex constant}
VAR l : Byte;
begin
createconst[1]:='$'; SetLength(createconst,3);
l:=ORD(B SHR 4) +48;
IF l>57 THEN
l:=L+7;
createconst[2]:=CHR(l);
l:=ORD(B and 15) +48;
IF l>57 THEN
INC(L,7);
createconst[3]:=CHR(l);
end;
var
cidx,i : longint;
p : pansichar;
begin
Writeln('Writing constant: ',constname,' to file '#39,outname,#39);
{Open textfile}
write(t,'const ',constname,' : array[0..'); Writeln(t,msgsize-1,'] of byte=(');
p:=msgtxt;
cidx:=0;
for i:=0 to msgsize-1 do
begin
if cidx=15 then
begin
if cidx>0 then
writeln(t,',')
else
writeln(t,'');
write(t,' ');
cidx:=0;
end
else
IF cidx>0 THEN
write(t,',')
ELSE
Write(T,' ');
write(t,createconst(ord(p^)));
inc(cidx);
inc(p);
end;
writeln(t,');');
Writeln(T);
end;
{*****************************************************************************
WriteStringFile
*****************************************************************************}
procedure WriteStringFile(var t:text;constname:ansistring);
const
maxslen=240; { to overcome aligning problems }
function l0(l:longint):ansistring;
var
s : string[16];
begin
str(l,s);
while (length(s)<5) do
s:='0'+s;
l0:=s;
end;
var
slen,
len,i : longint;
p : pansichar;
start,
quote : boolean;
begin
Writeln('Writing constant: ',constname,' to file '#39,outname,#39);
{Open textfile}
writeln(t,'{$ifdef Delphi}');
writeln(t,'const '+constname+' : array[0..',(msgsize-1) div maxslen,'] of string[',maxslen,']=(');
writeln(t,'{$else Delphi}');
writeln(t,'const '+constname+' : array[0..',(msgsize-1) div maxslen,',1..',maxslen,'] of char=(');
write(t,'{$endif Delphi}');
{Parse buffer in msgbuf and create indexs}
p:=msgtxt;
slen:=0;
len:=0;
quote:=false;
start:=true;
for i:=1 to msgsize do
begin
if slen>=maxslen then
begin
if quote then
begin
write(t,'''');
quote:=false;
end;
write(t,',');
slen:=0;
inc(len);
end;
if (len>70) or (start) then
begin
if quote then
begin
write(t,'''');
quote:=false;
end;
if slen>0 then
writeln(t,'+')
else
writeln(t);
len:=0;
start:=false;
end;
if (len=0) then
write(t,' ');
if (ord(p^)>=32) and (p^<>#39) then
begin
if not quote then
begin
write(t,'''');
quote:=true;
inc(len);
end;
write(t,p^);
inc(len);
end
else
begin
if quote then
begin
write(t,'''');
inc(len);
quote:=false;
end;
write(t,'#'+chr(ord(p^) div 100+48)+chr((ord(p^) mod 100) div 10+48)+chr(ord(p^) mod 10+48));
inc(len,3);
end;
{ start a new line when a #0 or #10 is found }
if p^ in [#0,#10] then
start:=true;
inc(slen);
inc(p);
end;
if quote then
write(t,'''');
writeln(t,'');
writeln(t,');');
end;
{*****************************************************************************
Parser
*****************************************************************************}
FUNCTION SpecialItem(S : AnsiString):LONGINT;
{ This procedure finds the next comma, (or the end of the string)
but comma's within single or double quotes should be ignored.
Single quotes within double quotes and vice versa are also ignored.}
VAR DataItem : LONGINT;
CONST xFcl : CHARSET = [',',#39,'"'];
BEGIN
DataItem:=0;
REPEAT
DataItem:=NextCharPosSet(S,xFcl,DataItem+1); {Find first " ' or ,}
IF (DataItem<>0) AND ((S[DataItem]='"') OR (S[DataItem]=#39)) THEN { (double)Quote found?}
DataItem:=NextCharPos(S,S[DataItem],DataItem+1); { then find other one}
UNTIL (DataItem=0) OR (S[DataItem]=',');
IF DataItem=0 THEN {Last data field of this line?}
DataItem:=Length(S);
SpecialItem:=DataItem;
END;
{ Handles reading and processing of a textual file}
procedure DoFile;
var
Infile,
Outfile : text; {in and output textfiles}
line, DataItem, {line number, position in DATA line}
I1,I2, {4 temporary counters}
I3,I4 : longint;
s,S1 : Ansistring; {S is string after reading, S1 is temporary string or
current DATA-item being processed }
VarName : AnsiString; { Variable name of constant to be written}
PROCEDURE ParseError;
{Extremely simple errorhandler}
BEGIN
Writeln('Error in line : ',Line, ' Somewhere near :',#39,S1,#39);
Close(InfIle); Close(Outfile);
HALT;
END;
PROCEDURE FixDec;
{ Reads decimal value starting at S1[1].
Value in I3, number of digits found in I1}
var I1,I2,i3 : longint;
BEGIN
I1:=1;
WHILE ((S1[I1]>#47) AND (S1[I1]<#58)) AND (I1<=Length(S1)) DO
INC(I1);
DEC(I1);
IF I1=0 THEN
ParseError;
I3:=0;
FOR I2:=1 TO I1 DO
I3:=(I3*10)+ ORD(S1[I2])-48;
{Calc no of bytes(1,2 or 4) required from no of digits found}
IF (I1<3) THEN
I2:=1
ELSE
IF (I1=3) AND (I3<256) THEN
I2:=1
ELSE
BEGIN
IF I1<5 THEN
I2:=2
ELSE
IF (I1=5) AND (i3<65536) THEN
I2:=2
ELSE
I2:=4;
END;
END;
PROCEDURE DoChar;
{ Reads a #xxx constant at S1[1], and puts it in msgtxt array.
Deletes #xxx constant from S1}
BEGIN
Delete(S1,1,1);
FixDec;
msgtxt[Msgsize]:=CHR(I3);
inc(msgsize);
Delete(S1,1,I1);
END;
PROCEDURE DoQuote;
{ Reads a quoted text-string ('xxx' or "xxx"). Quotechar is in S1[1]
(always ' or "), any char except the quotechar is allowed between two
quotechars.
Deletes quoted textstring incl quotes from S1}
VAR
C : AnsiChar;
BEGIN
C:=S1[1];
Delete(S1,1,1);
I1:=Pos(C,S1); {Find other quote}
IF I1=0 THEN
ParseError; {Quotes have to be matched}
Dec(I1);
IF I1<>0 THEN
BEGIN
Move(S1[1],Msgtxt[Msgsize],I1);
INC(msgsize,I1);
END;
Delete(S1,1,I1+1);
LTrim(S1,' ');
END;
PROCEDURE FixHex(base2:LONGINT);
{ Reads a base 2,8 or 16 constant from S1.
Parameter = 2Log of base (1,3 or 4 corresponding to base 2,8 and 16)
Constant is processed, the number of digits estimated (1,2 or 4 bytes) and
the value is appended to msgtxt accordingly}
BEGIN
I3:=0;
I2:=1;
WHILE (S1[I2] IN ['0'..'9','A'..'F','a'..'f']) AND (I2<=Length(S1)) DO
BEGIN
IF (S1[I2]>#47) AND (S1[I2]<#58) THEN
I3:=(I3 SHL base2)+ ORD(S1[I2])-48
ELSE
IF (S1[I2]>#64) AND (S1[I2]<#71) THEN
I3:=(I3 SHL base2)+ ORD(S1[I2])-55
ELSE
IF (S1[I2]>#96) AND (S1[I2]<#103) THEN
I3:=(I3 SHL base2)+ ORD(S1[I2])-87
ELSE
ParseError;
INC(I2);
END;
DEC(I2);
CASE Base2 OF
4 : BEGIN
I4:=(I2 SHR 1);
IF ODD(I2) THEN
INC(I4);
IF I4=3 THEN I4:=4
END;
3 : I4:=(I2*3 DIV 8)+1;
1 : BEGIN
IF I2<9 THEN
I4:=1
ELSE
IF I2<17 THEN
I4:=2
ELSE
I4:=4;
END;
ELSE
BEGIN
Writeln(' severe internal error ');
ParseError;
END; {else}
END; {Case}
move(I3,msgtxt[Msgsize],i4);
inc(msgsize,i4);
END;
PROCEDURE DoTextual;
{ processes aggregates of textual data like 'xxx'+#39"2143124"+'1234'#123}
BEGIN
REPEAT
CASE S1[1] OF
'#' : DoChar;
'"',#39 : DoQuote; {Should I support octal codes here?}
ELSE
ParseError;
END;
LTrim(S1,' ');
IF (S1[1]='+') THEN
Delete(S1,1,1);
LTrim(S1,' ');
UNTIL Length(S1)=0;
END;
PROCEDURE FlushMsgTxt; {Flush MsgTxt array}
BEGIN
IF msgsize>0 THEN {In memory? Then flush}
BEGIN
case outputmode of
OutByte :
WriteByteFile(outfile,Varname);
OutChar :
WriteCharFile(outfile,varname);
OutString :
WriteStringFile(outfile,varname);
end;
msgsize:=0;
END;
END;
{Actual DoFile}
begin
Getmem(msgtxt,maxbufsize);
Writeln('processing file : ',inname);
{Read the message file}
assign(infile,inname);
{$I-}
reset(infile);
{$I+}
if ioresult<>0 then
begin
WriteLn('file '+inname+' not found');
exit;
end;
{Create output file}
assign (outfile,outname);
rewrite(outfile);
msgsize:=0;
Line:=0;
while not eof(infile) do
begin
readln(infile,s); {Read a line}
INC(Line);
S1:=Copy(S,1,5);
Uppercase(S1);
IF S1='DATA ' THEN {DATA keyword?}
BEGIN
Delete(S,1,5);
REPEAT
DataItem:=SpecialItem(S); {Yes. Determine size of DATA field.}
IF DataItem<>0 THEN
BEGIN
I1:=DataItem;
IF DataItem=Length(S) THEN
INC(i1); {DataItem fix for last field}
S1:=Copy(S,1,I1-1); { copy field to S1}
Delete(S,1,I1); {Delete field from S}
LTrim(S1,' ');
RTrim(S1,' ');
LTrim(S,' ');
CASE S1[1] OF {Select field type}
#39,'"','#' : DoTextual; { handles textual aggregates
e.g. #124"142"#123'sdgf''ads'}
'$' : BEGIN {Handle $xxxx hex codes}
Delete(S1,1,1);
RTrim(S1,' ');
IF Length(S1)>0 THEN
FixHex(4)
ELSE
ParseError;
END;
'0'..'9' : BEGIN { handles 0x124,124124,124124H,234h,666o,353d,24b}
IF (Length(S1)>1) AND (S1[2]='x') THEN {C style 0xABCD hex}
BEGIN
Delete(S1,1,2);
FixHex(4);
END
ELSE {other types (HP notation suffix h,o,d and b (and upcase versions,
and no suffix) }
BEGIN
CASE S1[Length(S1)] OF
'H','h' : FixHex(4); {Hex}
'o','O' : FixHex(3); {octal}
'B','b' : BEGIN {Binary}
SetLength(S1,Length(S1)-1); {avoid 'b' char being treated as hex B }
FixHex(1);
END;
'0'..'9','d','D' : BEGIN {decimal versions}
FixDec; {Fixdec is safe for trailing chars}
{I1 =no of digits, I3=value, I2= no bytes needed}
move(I3,msgtxt[Msgsize],i2);
inc(msgsize,i2)
END
ELSE
ParseError; {otherwise wrong suffix}
END {Nested case}
END; { IF S1[2]='x'}
END; { '0'..'9'}
'%' : BEGIN {%101010 binary constants}
Delete(S1,1,1);
FixHex(1);
END;
'\' : BEGIN {\xxx octal constants}
Delete(S1,1,1);
FixHex(3);
END;
END; {Case}
END; {IF <>0}
UNTIL {(DataItem:=Length(S)) OR} (DataItem=0); {parse until String is empty}
END {S1='DATA'}
ELSE
BEGIN {Non DATA line}
IF (Length(S)<>0) AND NOT (S[1] IN ['#',';','%']) THEN
BEGIN
C:=S[1];
IF NOT XlatString(S) THEN {Expand \xxx octal constants}
BEGIN
Writeln('Some error with a \xxx constant or a stale (unescaped) backslash');
ParseError;
END;
IF C='!' THEN { New variable}
BEGIN
FlushMsgTxt;
I1:=1;
OutputMode:=OutChar;
IF S[2]='$' THEN {Flag for ARRAY OF BYTE?}
BEGIN
INC(I1);
OutputMode:=OutByte;
END;
Delete(S,1,I1);
VarName:=S;
END
ELSE
BEGIN {Normal line}
i1:=Length(S);
move(s[1],msgtxt[Msgsize],i1);
inc(msgsize,i1);
END;
END;
END;
end;
close(infile);
FlushMsgTxt; {Flush variable if msgtxt is occupied}
Close(Outfile);
end;
{*****************************************************************************
Binary File
*****************************************************************************}
procedure DoBinary;
var
Infile : File;
Outfile : text;
i : longint;
begin
Writeln('processing file : ',inname);
{ Read the file }
assign(infile,inname);
{$I-}
reset(infile,1);
{$I+}
if ioresult<>0 then
begin
WriteLn('file '+inname+' not found');
exit;
end;
{ First parse the file and count bytes needed }
msgsize:=FileSize(InFile);
Getmem(msgtxt,msgsize);
BlockRead(InFile,msgTxt[0],msgsize,i);
close(infile);
IF I<>msgsize THEN
BEGIN
Writeln('Error while reading file',inName);
HALT(1);
END;
{ Output }
assign (outfile,outname);
rewrite(outfile);
case outputmode of
OutByte :
WriteByteFile(outfile,BinconstName);
OutChar :
WriteCharFile(outfile,BinconstName);
OutString :
WriteStringFile(outfile,BinconstName);
end;
Close(Outfile);
end;
{*****************************************************************************
Main Program
*****************************************************************************}
procedure getpara;
var
ch : char;
para : string;
files,i : word;
procedure helpscreen;
begin
writeln('usage : data2inc [Options] <msgfile> [incfile] [constname]');
Writeln(' The constname parameter is only valid in combination');
writeln(' with -b, otherwise the constname must be specified in the inputfile');
Writeln;
writeln('<Options> can be :');
writeln(' -B File to read is binary.');
writeln(' -A array of byte output (default is array of char)');
writeln(' -S array of string output');
writeln(' -V Show version');
writeln(' -? or -H This HelpScreen');
writeln;
Writeln(' See data2inc.exm for a demonstration source');
halt(1);
end;
begin
I_binary:=FALSE;
OutputMode:=OutChar;
FIles:=0;
for i:=1to paramcount do
begin
para:=paramstr(i);
if (para[1]='-') then
begin
ch:=upcase(para[2]);
delete(para,1,2);
case ch of
'B' : I_Binary:=TRUE;
'A' : OutputMode:=OutByte;
'S' : OutputMode:=OutString;
'V' : begin
Writeln('Data2Inc ',version,' (C) 1999 Peter Vreman and Marco van de Voort');
Writeln;
Halt;
end;
'?','H' : Helpscreen;
end;
end
else
begin
inc(Files);
if Files>3 then
HelpScreen;
case Files of
1 : InName:=Para;
2 : OutName:=Para;
3 : BinConstName:=Para;
end;
end;
END;
if (FIles<3) AND I_Binary then
HelpScreen;
IF Files<2 THEN
HelpScreen;
end;
begin
GetPara;
IF I_Binary THEN
DoBinary
ELSE
DoFile;
end.