1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
|
/*
** Copyright (c) 2002 D. Richard Hipp
**
** This program is free software; you can redistribute it and/or
** modify it under the terms of the Simplified BSD License (also
** known as the "2-Clause License" or "FreeBSD License".)
**
** 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.
**
** Author contact information:
** drh@hwaci.com
** http://www.hwaci.com/drh/
**
*******************************************************************************
**
** SYNOPSIS:
**
** Input lines that begin with the "@" character are translated into
** either cgi_printf() statements or string literals and the
** translated code is written on standard output.
**
** The problem this program is attempt to solve is as follows: When
** writing CGI programs in C, we typically want to output a lot of HTML
** text to standard output. In pure C code, this involves doing a
** printf() with a big string containing all that text. But we have
** to insert special codes (ex: \n and \") for many common characters,
** which interferes with the readability of the HTML.
**
** This tool allows us to put raw HTML, without the special codes, in
** the middle of a C program. This program then translates the text
** into standard C by inserting all necessary backslashes and other
** punctuation.
**
** Enhancement #1:
**
** If the last non-whitespace character prior to the first "@" of a
** @-block is "=" or "," then the @-block is a string literal initializer
** rather than text that is to be output via cgi_printf(). Render it
** as such.
**
** Enhancement #2:
**
** Comments of the form: "|* @-comment: CC" (where "|" is really "/")
** cause CC to become a comment character for the @-substitution.
** Typical values for CC are "--" (for SQL text) or "#" (for Tcl script)
** or "//" (for C++ code). Lines of subsequent @-blocks that begin with
** CC are omitted from the output.
**
** Enhancement #3:
**
** If a non-enhancement #1 line ends in backslash, the backslash and the
** newline (\n) are not included in the argument to cgi_printf(). This
** is used to split one long output line across multiple source lines.
*/
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
/*
** Space to hold arguments at the end of the cgi_printf()
*/
#define MX_ARG_SP 10000
static char zArg[MX_ARG_SP];
static int nArg = 0;
/*
** True if we are currently in a cgi_printf()
*/
static int inPrint = 0;
/*
** True if we are currently doing a free string
*/
static int inStr = 0;
/*
** Name of files being processed
*/
static const char *zInFile = "(stdin)";
/*
** The `fossil_isspace()' function copied from the Fossil source code.
** Some MSVC runtime library versions of `isspace()' break with an `assert()' if
** the input is smaller than -1 or greater than 255 in debug builds, due to sign
** extension when promoting `signed char' to `int' for non-ASCII characters. Use
** an `isspace()' replacement instead of explicit type casts to `unsigned char'.
*/
int fossil_isspace(char c){
return c==' ' || (c<='\r' && c>='\t');
}
/*
** Terminate an active cgi_printf() or free string
*/
static void end_block(FILE *out){
if( inPrint ){
zArg[nArg] = 0;
fprintf(out, "%s);\n", zArg);
nArg = 0;
inPrint = 0;
}
}
/*
** Translate the input stream into the output stream
*/
static void trans(FILE *in, FILE *out){
int i, j, k; /* Loop counters */
char c1, c2; /* Characters used to start a comment */
int lastWasEq = 0; /* True if last non-whitespace character was "=" */
int lastWasComma = 0; /* True if last non-whitespace character was "," */
int lineNo = 0; /* Line number */
char zLine[2000]; /* A single line of input */
char zOut[4000]; /* The input line translated into appropriate output */
c1 = c2 = '-';
while( fgets(zLine, sizeof(zLine), in) ){
lineNo++;
for(i=0; zLine[i] && fossil_isspace(zLine[i]); i++){}
if( zLine[i]!='@' ){
if( inPrint || inStr ) end_block(out);
fprintf(out,"%s",zLine);
/* 0123456789 12345 */
if( strncmp(zLine, "/* @-comment: ", 14)==0 ){
c1 = zLine[14];
c2 = zLine[15];
}
i += strlen(&zLine[i]);
while( i>0 && fossil_isspace(zLine[i-1]) ){ i--; }
lastWasEq = i>0 && zLine[i-1]=='=';
lastWasComma = i>0 && zLine[i-1]==',';
}else if( lastWasEq || lastWasComma){
/* If the last non-whitespace character before the first @ was
** an "="(var init/set) or a ","(const definition in list) then
** generate a string literal. But skip comments
** consisting of all text between c1 and c2 (default "--")
** and end of line.
*/
int indent, omitline;
char *zNewline = "\\n";
i++;
if( fossil_isspace(zLine[i]) ){ i++; }
indent = i - 2;
if( indent<0 ) indent = 0;
omitline = 0;
for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
if( zLine[i]==c1 && (c2==' ' || zLine[i+1]==c2) ){
omitline = 1; break;
}
if( zLine[i]=='\\' && (zLine[i+1]==0 || zLine[i+1]=='\r'
|| zLine[i+1]=='\n') ){
zLine[i] = 0;
zNewline = "";
/* fprintf(stderr, "%s:%d: omit newline\n", zInFile, lineNo); */
break;
}
if( zLine[i]=='\\' || zLine[i]=='"' ){ zOut[j++] = '\\'; }
zOut[j++] = zLine[i];
}
if( zNewline[0] ) while( j>0 && fossil_isspace(zOut[j-1]) ){ j--; }
zOut[j] = 0;
if( j<=0 && omitline ){
fprintf(out,"\n");
}else{
fprintf(out,"%*s\"%s%s\"\n",indent, "", zOut, zNewline);
}
}else{
/* Otherwise (if the last non-whitespace was not '=') then generate a
** cgi_printf() statement whose format is the text following the '@'.
** Substrings of the form "%C(...)" (where C is any sequence of characters
** other than \000 and '(') will put "%C" in the format and add the
** "(...)" as an argument to the cgi_printf call. Each '*' character
** present in C (max two) causes one more "(...)" sequence to be consumed.
** For example, "%*.*d(4)(2)(1)" converts to "%*.*d" with arguments "4",
** "2", and "1", which will be used as the field width, precision, and
** value, respectively, producing a final formatted result of " 01".
*/
const char *zNewline = "\\n";
int indent;
int nC;
int nParam;
char c;
i++;
if( fossil_isspace(zLine[i]) ){ i++; }
indent = i;
for(j=0; zLine[i] && zLine[i]!='\r' && zLine[i]!='\n'; i++){
if( zLine[i]=='\\' && (!zLine[i+1] || zLine[i+1]=='\r'
|| zLine[i+1]=='\n') ){
zNewline = "";
break;
}
if( zLine[i]=='"' || zLine[i]=='\\' ){ zOut[j++] = '\\'; }
zOut[j++] = zLine[i];
if( zLine[i]!='%' || zLine[i+1]=='%' || zLine[i+1]==0 ) continue;
nParam=1;
for(nC=1; zLine[i+nC] && zLine[i+nC]!='('; nC++){
if( zLine[i+nC]=='*' && nParam < 3 ) nParam++;
}
if( zLine[i+nC]!='(' || !isalpha(zLine[i+nC-1]) ) continue;
while( --nC ) zOut[j++] = zLine[++i];
do{
zArg[nArg++] = ',';
k = 0; i++;
if( zLine[i]!='(' ) break;
while( (c = zLine[i])!=0 ){
zArg[nArg++] = c;
if( c==')' ){
k--;
if( k==0 ) break;
}else if( c=='(' ){
k++;
}
i++;
}
}while( --nParam );
}
zOut[j] = 0;
if( !inPrint ){
fprintf(out,"%*scgi_printf(\"%s%s\"",indent-2,"", zOut, zNewline);
inPrint = 1;
}else{
fprintf(out,"\n%*s\"%s%s\"",indent+5, "", zOut, zNewline);
}
}
}
}
static void print_source_ref(const char *zSrcFile, FILE *out){
/* Set source line reference to the original source file.
* This makes compiler show the original file name in the compile error
* messages, instead of referring to the translated file.
* NOTE: This somewhat complicates stepping in debugger, as the resuling
* code would not match the referenced sources.
*/
#ifndef FOSSIL_DEBUG
const char *arg;
if( !*zSrcFile ){
return;
}
fprintf(out,"#line 1 \"");
for(arg=zSrcFile; *arg; arg++){
if( *arg!='\\' ){
fprintf(out,"%c", *arg);
}else{
fprintf(out,"\\\\");
}
}
fprintf(out,"\"\n");
#endif
}
int main(int argc, char **argv){
if( argc==2 ){
FILE *in = fopen(argv[1], "r");
if( in==0 ){
fprintf(stderr,"can not open %s\n", argv[1]);
exit(1);
}
zInFile = argv[1];
print_source_ref(zInFile, stdout);
trans(in, stdout);
fclose(in);
}else{
trans(stdin, stdout);
}
return 0;
}
|